blob: 0c7a1dfc235b35eeda630f88cea6904b6ccd29ea [file] [log] [blame]
//! 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<_>>(),
);
}
}