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