xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/context.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 //! Convert annotated metadata into a renderable context
2 
3 pub(crate) mod crate_context;
4 mod platforms;
5 
6 use std::collections::{BTreeMap, BTreeSet};
7 use std::fs;
8 use std::path::{Path, PathBuf};
9 
10 use anyhow::Result;
11 use serde::{Deserialize, Serialize};
12 
13 use crate::config::CrateId;
14 use crate::context::platforms::resolve_cfg_platforms;
15 use crate::lockfile::Digest;
16 use crate::metadata::{Annotations, Dependency};
17 use crate::select::Select;
18 use crate::utils::target_triple::TargetTriple;
19 
20 pub(crate) use self::crate_context::*;
21 
22 /// A struct containing information about a Cargo dependency graph in an easily to consume
23 /// format for rendering reproducible Bazel targets.
24 #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
25 pub(crate) struct Context {
26     /// The collective checksum of all inputs to the context
27     pub(crate) checksum: Option<Digest>,
28 
29     /// The collection of all crates that make up the dependency graph
30     pub(crate) crates: BTreeMap<CrateId, CrateContext>,
31 
32     /// A subset of only crates with binary targets
33     pub(crate) binary_crates: BTreeSet<CrateId>,
34 
35     /// A subset of workspace members mapping to their workspace
36     /// path relative to the workspace root
37     pub(crate) workspace_members: BTreeMap<CrateId, String>,
38 
39     /// A mapping of `cfg` flags to platform triples supporting the configuration
40     pub(crate) conditions: BTreeMap<String, BTreeSet<TargetTriple>>,
41 
42     /// A list of crates visible to any bazel module.
43     pub(crate) direct_deps: BTreeSet<CrateId>,
44 
45     /// A list of crates visible to this bazel module.
46     pub(crate) direct_dev_deps: BTreeSet<CrateId>,
47 }
48 
49 impl Context {
try_from_path<T: AsRef<Path>>(path: T) -> Result<Self>50     pub(crate) fn try_from_path<T: AsRef<Path>>(path: T) -> Result<Self> {
51         let data = fs::read_to_string(path.as_ref())?;
52         Ok(serde_json::from_str(&data)?)
53     }
54 
new(annotations: Annotations, sources_are_present: bool) -> Result<Self>55     pub(crate) fn new(annotations: Annotations, sources_are_present: bool) -> Result<Self> {
56         // Build a map of crate contexts
57         let crates: BTreeMap<CrateId, CrateContext> = annotations
58             .metadata
59             .crates
60             .values()
61             .map(|annotation| {
62                 let context = CrateContext::new(
63                     annotation,
64                     &annotations.metadata.packages,
65                     &annotations.lockfile.crates,
66                     &annotations.pairred_extras,
67                     &annotations.metadata.workspace_metadata.tree_metadata,
68                     annotations.config.generate_binaries,
69                     annotations.config.generate_build_scripts,
70                     sources_are_present,
71                 );
72                 let id = CrateId::new(context.name.clone(), context.version.clone());
73                 (id, context)
74             })
75             .collect();
76 
77         // Filter for any crate that contains a binary
78         let binary_crates: BTreeSet<CrateId> = crates
79             .iter()
80             .filter(|(_, ctx)| ctx.targets.iter().any(|t| matches!(t, Rule::Binary(..))))
81             // Only consider remote repositories (so non-workspace members).
82             .filter(|(_, ctx)| ctx.repository.is_some())
83             .map(|(id, _)| id.clone())
84             .collect();
85 
86         // Given a list of all conditional dependencies, build a set of platform
87         // triples which satisfy the conditions.
88         let conditions = resolve_cfg_platforms(
89             crates.values().collect(),
90             &annotations.config.supported_platform_triples,
91         )?;
92 
93         // Generate a list of all workspace members
94         let workspace_members = annotations
95             .metadata
96             .workspace_members
97             .iter()
98             .filter_map(|id| {
99                 let pkg = &annotations.metadata.packages[id];
100                 let package_path_id = match Self::get_package_path_id(
101                     pkg,
102                     &annotations.metadata.workspace_root,
103                     &annotations.metadata.workspace_metadata.workspace_prefix,
104                     &annotations.metadata.workspace_metadata.package_prefixes,
105                 ) {
106                     Ok(id) => id,
107                     Err(e) => return Some(Err(e)),
108                 };
109                 let crate_id = CrateId::from(pkg);
110 
111                 // Crates that have repository information are not considered workspace members.
112                 // The assumpion is that they are "extra workspace members".
113                 match crates[&crate_id].repository {
114                     Some(_) => None,
115                     None => Some(Ok((crate_id, package_path_id))),
116                 }
117             })
118             .collect::<Result<BTreeMap<CrateId, String>>>()?;
119 
120         let add_crate_ids = |crates: &mut BTreeSet<CrateId>,
121                              deps: &Select<BTreeSet<Dependency>>| {
122             for dep in deps.values() {
123                 crates.insert(CrateId::from(
124                     &annotations.metadata.packages[&dep.package_id],
125                 ));
126             }
127         };
128 
129         let mut direct_deps: BTreeSet<CrateId> = BTreeSet::new();
130         let mut direct_dev_deps: BTreeSet<CrateId> = BTreeSet::new();
131         for id in &annotations.metadata.workspace_members {
132             let deps = &annotations.metadata.crates[id].deps;
133             add_crate_ids(&mut direct_deps, &deps.normal_deps);
134             add_crate_ids(&mut direct_deps, &deps.proc_macro_deps);
135             add_crate_ids(&mut direct_deps, &deps.build_deps);
136             add_crate_ids(&mut direct_deps, &deps.build_link_deps);
137             add_crate_ids(&mut direct_deps, &deps.build_proc_macro_deps);
138             add_crate_ids(&mut direct_dev_deps, &deps.normal_dev_deps);
139             add_crate_ids(&mut direct_dev_deps, &deps.proc_macro_dev_deps);
140         }
141 
142         Ok(Self {
143             checksum: None,
144             crates,
145             binary_crates,
146             workspace_members,
147             conditions,
148             direct_dev_deps: direct_dev_deps.difference(&direct_deps).cloned().collect(),
149             direct_deps,
150         })
151     }
152 
153     // A helper function for locating the unique path in a workspace to a workspace member
get_package_path_id( package: &cargo_metadata::Package, workspace_root: &Path, workspace_prefix: &Option<String>, package_prefixes: &BTreeMap<String, String>, ) -> Result<String>154     fn get_package_path_id(
155         package: &cargo_metadata::Package,
156         workspace_root: &Path,
157         workspace_prefix: &Option<String>,
158         package_prefixes: &BTreeMap<String, String>,
159     ) -> Result<String> {
160         // Locate the package's manifest directory
161         let manifest_dir = package
162             .manifest_path
163             .parent()
164             .expect("Every manifest should have a parent")
165             .as_std_path();
166 
167         // Compare it with the root of the workspace
168         let package_path_diff = pathdiff::diff_paths(manifest_dir, workspace_root)
169             .expect("Every workspace member's manifest is a child of the workspace root");
170 
171         // Ensure the package paths are adjusted in the macros according to the splicing results
172         let package_path = match package_prefixes.get(&package.name) {
173             // Any package prefix should be absolute and therefore always applied
174             Some(prefix) => PathBuf::from(prefix).join(package_path_diff),
175             // If no package prefix is present, attempt to apply the workspace prefix
176             // since workspace members would not have shown up with their own label
177             None => match workspace_prefix {
178                 Some(prefix) => PathBuf::from(prefix).join(package_path_diff),
179                 None => package_path_diff,
180             },
181         };
182 
183         // Sanitize the path for increased consistency
184         let package_path_id = package_path
185             .display()
186             .to_string()
187             .replace('\\', "/")
188             .trim_matches('/')
189             .to_owned();
190 
191         Ok(package_path_id)
192     }
193 
194     /// Create a set of all direct dependencies of workspace member crates.
workspace_member_deps(&self) -> BTreeSet<CrateDependency>195     pub(crate) fn workspace_member_deps(&self) -> BTreeSet<CrateDependency> {
196         self.workspace_members
197             .keys()
198             .map(move |id| &self.crates[id])
199             .flat_map(|ctx| {
200                 IntoIterator::into_iter([
201                     &ctx.common_attrs.deps,
202                     &ctx.common_attrs.deps_dev,
203                     &ctx.common_attrs.proc_macro_deps,
204                     &ctx.common_attrs.proc_macro_deps_dev,
205                 ])
206                 .flat_map(|deps| deps.values())
207             })
208             .collect()
209     }
210 
has_duplicate_workspace_member_dep(&self, dep: &CrateDependency) -> bool211     pub(crate) fn has_duplicate_workspace_member_dep(&self, dep: &CrateDependency) -> bool {
212         1 < self
213             .workspace_member_deps()
214             .into_iter()
215             .filter(|check| check.id.name == dep.id.name && check.alias == dep.alias)
216             .count()
217     }
218 
has_duplicate_binary_crate(&self, bin: &CrateId) -> bool219     pub(crate) fn has_duplicate_binary_crate(&self, bin: &CrateId) -> bool {
220         1 < self
221             .binary_crates
222             .iter()
223             .filter(|check| check.name == bin.name)
224             .count()
225     }
226 }
227 
228 #[cfg(test)]
229 mod test {
230     use super::*;
231     use semver::Version;
232 
233     use crate::config::Config;
234 
mock_context_common() -> Context235     fn mock_context_common() -> Context {
236         let annotations = Annotations::new(
237             crate::test::metadata::common(),
238             crate::test::lockfile::common(),
239             Config::default(),
240         )
241         .unwrap();
242 
243         Context::new(annotations, false).unwrap()
244     }
245 
mock_context_aliases() -> Context246     fn mock_context_aliases() -> Context {
247         let annotations = Annotations::new(
248             crate::test::metadata::alias(),
249             crate::test::lockfile::alias(),
250             Config::default(),
251         )
252         .unwrap();
253 
254         Context::new(annotations, false).unwrap()
255     }
256 
257     #[test]
workspace_member_deps_collection()258     fn workspace_member_deps_collection() {
259         let context = mock_context_common();
260         let workspace_member_deps = context.workspace_member_deps();
261 
262         assert_eq! {
263             workspace_member_deps
264                 .iter()
265                 .map(|dep| (&dep.id, context.has_duplicate_workspace_member_dep(dep)))
266                 .collect::<Vec<_>>(),
267             [
268                 (&CrateId::new("bitflags".to_owned(), Version::new(1, 3, 2)), false),
269                 (&CrateId::new("cfg-if".to_owned(), Version::new(1, 0, 0)), false),
270             ],
271         }
272     }
273 
274     #[test]
workspace_member_deps_with_aliases()275     fn workspace_member_deps_with_aliases() {
276         let context = mock_context_aliases();
277         let workspace_member_deps = context.workspace_member_deps();
278 
279         assert_eq! {
280             workspace_member_deps
281                 .iter()
282                 .map(|dep| (&dep.id, context.has_duplicate_workspace_member_dep(dep)))
283                 .collect::<Vec<_>>(),
284             [
285                 (&CrateId::new("log".to_owned(), Version::new(0, 3, 9)), false),
286                 (&CrateId::new("log".to_owned(), Version::new(0, 4, 21)), false),
287                 (&CrateId::new("names".to_owned(), Version::parse("0.12.1-dev").unwrap()), false),
288                 (&CrateId::new("names".to_owned(), Version::new(0, 13, 0)), false),
289                 (&CrateId::new("surrealdb".to_owned(), Version::new(1, 3, 1)), false),
290                 (&CrateId::new("value-bag".to_owned(), Version::parse("1.0.0-alpha.7").unwrap()), false),
291             ],
292         }
293     }
294 
295     #[test]
serialization()296     fn serialization() {
297         let context = mock_context_aliases();
298 
299         // Serialize and deserialize the context object
300         let json_text = serde_json::to_string(&context).unwrap();
301         let deserialized_context: Context = serde_json::from_str(&json_text).unwrap();
302 
303         // The data should be identical
304         assert_eq!(context, deserialized_context);
305     }
306 }
307