cargo_bazel/api/
lockfile.rs

1//! The lockfile::public module represents a reasonable stable API for inspecting the contents of a lockfile which others can code against.
2
3use std::collections::BTreeSet;
4use std::fs::File;
5use std::io::BufReader;
6use std::path::Path;
7
8use anyhow::Result;
9use serde::Deserialize;
10
11pub use crate::config::CrateId;
12use crate::context::crate_context::{CrateDependency, Rule};
13use crate::context::{CommonAttributes, Context};
14use crate::select::Select;
15
16/// Parse a lockfile at a path on disk.
17pub fn parse(path: &Path) -> Result<impl CargoBazelLockfile> {
18    let reader = BufReader::new(File::open(path)?);
19    let lockfile: CargoBazelLockfileImpl = serde_json::from_reader(reader)?;
20    Ok(lockfile)
21}
22
23/// `CargoBazelLockfile` provides a view over `cargo-bazel`'s lockfile format.
24///
25/// This trait provides information about the third-party dependencies of a workspace.
26/// While the lockfile's format doesn't provide any kind of compatibility guarantees over time,
27/// this type offers an interface which is likely to be publicly supportable.
28/// No formal compatibility guarantees are offered around this type - it may change at any time,
29/// but the maintainers will attempt to keep it as stable they reasonably can.
30pub trait CargoBazelLockfile {
31    /// Get the members of the local workspace.
32    /// These are typically not very interesting on their own, but can be used as roots for navigating what dependencies these crates have.
33    fn workspace_members(&self) -> BTreeSet<CrateId>;
34
35    /// Get information about a specific crate (which may be in the local workspace, or an external dependency).
36    fn crate_info(&self, crate_id: &CrateId) -> Option<CrateInfo>;
37}
38
39#[derive(Deserialize)]
40#[serde(transparent)]
41struct CargoBazelLockfileImpl(Context);
42
43impl CargoBazelLockfile for CargoBazelLockfileImpl {
44    fn workspace_members(&self) -> BTreeSet<CrateId> {
45        self.0.workspace_members.keys().cloned().collect()
46    }
47
48    fn crate_info(&self, crate_id: &CrateId) -> Option<CrateInfo> {
49        let crate_context = self.0.crates.get(crate_id)?;
50        Some(CrateInfo {
51            name: crate_context.name.clone(),
52            version: crate_context.version.clone(),
53            library_target_name: crate_context.library_target_name.clone(),
54            is_proc_macro: crate_context
55                .targets
56                .iter()
57                .any(|t| matches!(t, Rule::ProcMacro(_))),
58            common_attributes: crate_context.common_attrs.clone(),
59        })
60    }
61}
62
63/// Information about a crate (which may be in-workspace or a dependency).
64#[derive(Deserialize, PartialEq, Eq, Debug)]
65pub struct CrateInfo {
66    name: String,
67    version: semver::Version,
68    library_target_name: Option<String>,
69    is_proc_macro: bool,
70
71    common_attributes: CommonAttributes,
72}
73
74impl CrateInfo {
75    /// The name of the crate.
76    pub fn name(&self) -> &str {
77        &self.name
78    }
79
80    /// The version of the crate.
81    pub fn version(&self) -> &semver::Version {
82        &self.version
83    }
84
85    /// The name of the crate's root library target. This is the target that a dependent
86    /// would get if they were to depend on this crate.
87    pub fn library_target_name(&self) -> Option<&str> {
88        self.library_target_name.as_deref()
89    }
90
91    /// Whether the crate is a procedural macro.
92    pub fn is_proc_macro(&self) -> bool {
93        self.is_proc_macro
94    }
95
96    /// Dependencies required to compile the crate, without procedural macro dependencies.
97    pub fn normal_deps(&self) -> Select<BTreeSet<CrateDependency>> {
98        self.common_attributes.deps.clone()
99    }
100
101    /// Dependencies required to compile the tests for the crate, but not needed to compile the crate itself, without procedural macro dependencies.
102    pub fn dev_deps(&self) -> Select<BTreeSet<CrateDependency>> {
103        self.common_attributes.deps_dev.clone()
104    }
105
106    /// Procedural macro dependencies required to compile the crate.
107    pub fn proc_macro_deps(&self) -> Select<BTreeSet<CrateDependency>> {
108        self.common_attributes.proc_macro_deps.clone()
109    }
110
111    /// Procedural macro dependencies required to compile the tests for the crate, but not needed to compile the crate itself.
112    pub fn proc_macro_dev_deps(&self) -> Select<BTreeSet<CrateDependency>> {
113        self.common_attributes.proc_macro_deps_dev.clone()
114    }
115}
116
117#[cfg(test)]
118mod test {
119    use super::{parse, CargoBazelLockfile};
120    use crate::config::CrateId;
121    use crate::context::crate_context::CrateDependency;
122    use semver::Version;
123    use std::collections::BTreeSet;
124
125    #[test]
126    fn exercise_public_lockfile_api() {
127        let pkg_a = CrateId {
128            name: String::from("pkg_a"),
129            version: Version::new(0, 1, 0),
130        };
131
132        let want_workspace_member_names = {
133            let mut set = BTreeSet::new();
134            set.insert(pkg_a.clone());
135            set.insert(CrateId {
136                name: String::from("pkg_b"),
137                version: Version::new(0, 1, 0),
138            });
139            set.insert(CrateId {
140                name: String::from("pkg_c"),
141                version: Version::new(0, 1, 0),
142            });
143            set
144        };
145
146        let runfiles = runfiles::Runfiles::create().unwrap();
147        let path = runfiles::rlocation!(
148            runfiles, "rules_rust/crate_universe/test_data/cargo_bazel_lockfile/multi_package-cargo-bazel-lock.json").unwrap();
149
150        let parsed = parse(&path).unwrap();
151        assert_eq!(parsed.workspace_members(), want_workspace_member_names);
152
153        let got_pkg_a = parsed.crate_info(&pkg_a).unwrap();
154        assert_eq!(got_pkg_a.name(), "pkg_a");
155        assert_eq!(got_pkg_a.version(), &Version::new(0, 1, 0));
156        assert_eq!(got_pkg_a.library_target_name(), Some("pkg_a"));
157        assert!(!got_pkg_a.is_proc_macro());
158
159        let serde_derive = CrateId {
160            name: String::from("serde_derive"),
161            version: Version::new(1, 0, 152),
162        };
163        let got_serde_derive = parsed.crate_info(&serde_derive).unwrap();
164        assert_eq!(got_serde_derive.name(), "serde_derive");
165        assert_eq!(got_serde_derive.version(), &Version::new(1, 0, 152));
166        assert_eq!(got_serde_derive.library_target_name(), Some("serde_derive"));
167        assert!(got_serde_derive.is_proc_macro);
168
169        assert_eq!(
170            got_pkg_a.normal_deps().values(),
171            vec![
172                CrateDependency {
173                    id: CrateId {
174                        name: String::from("anyhow"),
175                        version: Version::new(1, 0, 69),
176                    },
177                    target: String::from("anyhow"),
178                    alias: None,
179                    local_path: None,
180                },
181                CrateDependency {
182                    id: CrateId {
183                        name: String::from("reqwest"),
184                        version: Version::new(0, 11, 14),
185                    },
186                    target: String::from("reqwest"),
187                    alias: None,
188                    local_path: None,
189                },
190            ],
191        );
192
193        let async_process = CrateId {
194            name: String::from("async-process"),
195            version: Version::new(1, 6, 0),
196        };
197        let got_async_process = parsed.crate_info(&async_process).unwrap();
198        let got_async_process_deps: BTreeSet<(Option<String>, String)> = got_async_process
199            .normal_deps()
200            .items()
201            .into_iter()
202            .map(|(config, dep)| (config, dep.id.name))
203            .collect();
204        assert_eq!(
205            got_async_process_deps,
206            vec![
207                (None, "async-lock"),
208                (None, "async-process"),
209                (None, "cfg-if"),
210                (None, "event-listener"),
211                (None, "futures-lite"),
212                (Some("cfg(unix)"), "async-io"),
213                (Some("cfg(unix)"), "libc"),
214                (Some("cfg(unix)"), "signal-hook"),
215                (Some("cfg(windows)"), "blocking"),
216                (Some("cfg(windows)"), "windows-sys"),
217            ]
218            .into_iter()
219            .map(|(config, dep)| (config.map(String::from), String::from(dep)))
220            .collect::<BTreeSet<_>>(),
221        );
222    }
223}