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