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