diff options
Diffstat (limited to 'crate_universe/src/api/lockfile.rs')
-rw-r--r-- | crate_universe/src/api/lockfile.rs | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/crate_universe/src/api/lockfile.rs b/crate_universe/src/api/lockfile.rs new file mode 100644 index 00000000..0c7a1dfc --- /dev/null +++ b/crate_universe/src/api/lockfile.rs @@ -0,0 +1,220 @@ +//! The lockfile::public module represents a reasonable stable API for inspecting the contents of a lockfile which others can code against. + +use std::collections::BTreeSet; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; + +use anyhow::Result; +use serde::Deserialize; + +pub use crate::config::CrateId; +use crate::context::crate_context::{CrateDependency, Rule}; +use crate::context::{CommonAttributes, Context}; +use crate::select::Select; + +/// Parse a lockfile at a path on disk. +pub fn parse(path: &Path) -> Result<impl CargoBazelLockfile> { + let reader = BufReader::new(File::open(path)?); + let lockfile: CargoBazelLockfileImpl = serde_json::from_reader(reader)?; + Ok(lockfile) +} + +/// CargoBazelLockfile provides a view over cargo-bazel's lockfile format, +/// providing information about the third-party dependencies of a workspace. +/// While the lockfile's format doesn't provide any kind of compatibility guarantees over time, +/// this type offers an interface which is likely to be publicly supportable. +/// No formal compatibility guarantees are offered around this type - it may change at any time, +/// but the maintainers will attempt to keep it as stable they reasonably can. +pub trait CargoBazelLockfile { + /// Get the members of the local workspace. + /// These are typically not very interesting on their own, but can be used as roots for navigating what dependencies these crates have. + fn workspace_members(&self) -> BTreeSet<CrateId>; + + /// Get information about a specific crate (which may be in the local workspace, or an external dependency). + fn crate_info(&self, crate_id: &CrateId) -> Option<CrateInfo>; +} + +#[derive(Deserialize)] +#[serde(transparent)] +struct CargoBazelLockfileImpl(Context); + +impl CargoBazelLockfile for CargoBazelLockfileImpl { + fn workspace_members(&self) -> BTreeSet<CrateId> { + self.0.workspace_members.keys().cloned().collect() + } + + fn crate_info(&self, crate_id: &CrateId) -> Option<CrateInfo> { + let crate_context = self.0.crates.get(crate_id)?; + Some(CrateInfo { + name: crate_context.name.clone(), + version: crate_context.version.clone(), + library_target_name: crate_context.library_target_name.clone(), + is_proc_macro: crate_context + .targets + .iter() + .any(|t| matches!(t, Rule::ProcMacro(_))), + common_attributes: crate_context.common_attrs.clone(), + }) + } +} + +/// Information about a crate (which may be in-workspace or a dependency). +#[derive(Deserialize, PartialEq, Eq, Debug)] +pub struct CrateInfo { + name: String, + version: semver::Version, + library_target_name: Option<String>, + is_proc_macro: bool, + + common_attributes: CommonAttributes, +} + +impl CrateInfo { + /// The name of the crate. + pub fn name(&self) -> &str { + &self.name + } + + /// The version of the crate. + pub fn version(&self) -> &semver::Version { + &self.version + } + + /// The name of the crate's root library target. This is the target that a dependent + /// would get if they were to depend on this crate. + pub fn library_target_name(&self) -> Option<&str> { + self.library_target_name.as_deref() + } + + /// Whether the crate is a procedural macro. + pub fn is_proc_macro(&self) -> bool { + self.is_proc_macro + } + + /// Dependencies required to compile the crate, without procedural macro dependencies. + pub fn normal_deps(&self) -> Select<BTreeSet<CrateDependency>> { + self.common_attributes.deps.clone() + } + + /// Dependencies required to compile the tests for the crate, but not needed to compile the crate itself, without procedural macro dependencies. + pub fn dev_deps(&self) -> Select<BTreeSet<CrateDependency>> { + self.common_attributes.deps_dev.clone() + } + + /// Procedural macro dependencies required to compile the crate. + pub fn proc_macro_deps(&self) -> Select<BTreeSet<CrateDependency>> { + self.common_attributes.proc_macro_deps.clone() + } + + /// Procedural macro dependencies required to compile the tests for the crate, but not needed to compile the crate itself. + pub fn proc_macro_dev_deps(&self) -> Select<BTreeSet<CrateDependency>> { + self.common_attributes.proc_macro_deps_dev.clone() + } +} + +#[cfg(test)] +mod test { + use super::{parse, CargoBazelLockfile}; + use crate::config::CrateId; + use crate::context::crate_context::CrateDependency; + use semver::Version; + use std::collections::BTreeSet; + + #[test] + fn test() { + let pkg_a = CrateId { + name: String::from("pkg_a"), + version: Version::new(0, 1, 0), + }; + + let want_workspace_member_names = { + let mut set = BTreeSet::new(); + set.insert(pkg_a.clone()); + set.insert(CrateId { + name: String::from("pkg_b"), + version: Version::new(0, 1, 0), + }); + set.insert(CrateId { + name: String::from("pkg_c"), + version: Version::new(0, 1, 0), + }); + set + }; + + let runfiles = runfiles::Runfiles::create().unwrap(); + let path = runfiles + .rlocation("rules_rust/crate_universe/test_data/cargo_bazel_lockfile/multi_package-cargo-bazel-lock.json"); + + let parsed = parse(&path).unwrap(); + assert_eq!(parsed.workspace_members(), want_workspace_member_names); + + let got_pkg_a = parsed.crate_info(&pkg_a).unwrap(); + assert_eq!(got_pkg_a.name(), "pkg_a"); + assert_eq!(got_pkg_a.version(), &Version::new(0, 1, 0)); + assert_eq!(got_pkg_a.library_target_name(), Some("pkg_a")); + assert!(!got_pkg_a.is_proc_macro()); + + let serde_derive = CrateId { + name: String::from("serde_derive"), + version: Version::new(1, 0, 152), + }; + let got_serde_derive = parsed.crate_info(&serde_derive).unwrap(); + assert_eq!(got_serde_derive.name(), "serde_derive"); + assert_eq!(got_serde_derive.version(), &Version::new(1, 0, 152)); + assert_eq!(got_serde_derive.library_target_name(), Some("serde_derive")); + assert!(got_serde_derive.is_proc_macro); + + assert_eq!( + got_pkg_a.normal_deps().values(), + vec![ + CrateDependency { + id: CrateId { + name: String::from("anyhow"), + version: Version::new(1, 0, 69), + }, + target: String::from("anyhow"), + alias: None, + }, + CrateDependency { + id: CrateId { + name: String::from("reqwest"), + version: Version::new(0, 11, 14), + }, + target: String::from("reqwest"), + alias: None, + }, + ], + ); + + let async_process = CrateId { + name: String::from("async-process"), + version: Version::new(1, 6, 0), + }; + let got_async_process = parsed.crate_info(&async_process).unwrap(); + let got_async_process_deps: BTreeSet<(Option<String>, String)> = got_async_process + .normal_deps() + .items() + .into_iter() + .map(|(config, dep)| (config, dep.id.name)) + .collect(); + assert_eq!( + got_async_process_deps, + vec![ + (None, "async-lock"), + (None, "async-process"), + (None, "cfg-if"), + (None, "event-listener"), + (None, "futures-lite"), + (Some("cfg(unix)"), "async-io"), + (Some("cfg(unix)"), "libc"), + (Some("cfg(unix)"), "signal-hook"), + (Some("cfg(windows)"), "blocking"), + (Some("cfg(windows)"), "windows-sys"), + ] + .into_iter() + .map(|(config, dep)| (config.map(String::from), String::from(dep))) + .collect::<BTreeSet<_>>(), + ); + } +} |