xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/api/lockfile.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
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