aboutsummaryrefslogtreecommitdiff
path: root/crate_universe/src/api/lockfile.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crate_universe/src/api/lockfile.rs')
-rw-r--r--crate_universe/src/api/lockfile.rs220
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<_>>(),
+ );
+ }
+}