1use 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
16pub 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
23pub trait CargoBazelLockfile {
31 fn workspace_members(&self) -> BTreeSet<CrateId>;
34
35 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#[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 pub fn name(&self) -> &str {
77 &self.name
78 }
79
80 pub fn version(&self) -> &semver::Version {
82 &self.version
83 }
84
85 pub fn library_target_name(&self) -> Option<&str> {
88 self.library_target_name.as_deref()
89 }
90
91 pub fn is_proc_macro(&self) -> bool {
93 self.is_proc_macro
94 }
95
96 pub fn normal_deps(&self) -> Select<BTreeSet<CrateDependency>> {
98 self.common_attributes.deps.clone()
99 }
100
101 pub fn dev_deps(&self) -> Select<BTreeSet<CrateDependency>> {
103 self.common_attributes.deps_dev.clone()
104 }
105
106 pub fn proc_macro_deps(&self) -> Select<BTreeSet<CrateDependency>> {
108 self.common_attributes.proc_macro_deps.clone()
109 }
110
111 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}