Skip to content

Commit 7c98e23

Browse files
authored
Add public interface to lockfile interospection (#2515)
https://github.com/Calsign/gazelle_rust currently relies on a patch to rules_rust which makes the `Context` struct public. Instead, supply the information they need via a public API. All names (and whether we want to hide these structs behind traits and just return `impl Trait`s) are open to discussion. Fixes #1725 cc @Calsign
1 parent ebbd6a2 commit 7c98e23

File tree

9 files changed

+12390
-8
lines changed

9 files changed

+12390
-8
lines changed

crate_universe/private/srcs.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
# Run 'bazel run //crate_universe/private:srcs_module.install' to regenerate.
77

88
CARGO_BAZEL_SRCS = [
9+
Label("//crate_universe:src/api.rs"),
10+
Label("//crate_universe:src/api/lockfile.rs"),
911
Label("//crate_universe:src/cli.rs"),
1012
Label("//crate_universe:src/cli/generate.rs"),
1113
Label("//crate_universe:src/cli/query.rs"),

crate_universe/src/api.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//! Module api provides a publicly consumable API over rules_rust's crate_universe.
2+
//! While it has no formal compatibility guarantees, it is much less likely to break than other types in this library.
3+
4+
pub mod lockfile;

crate_universe/src/api/lockfile.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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.
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.
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).
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 {
43+
fn workspace_members(&self) -> BTreeSet<CrateId> {
44+
self.0.workspace_members.keys().cloned().collect()
45+
}
46+
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.
75+
pub fn name(&self) -> &str {
76+
&self.name
77+
}
78+
79+
/// The version of the crate.
80+
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.
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.
91+
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.
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.
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.
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.
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]
125+
fn test() {
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
147+
.rlocation("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+
}

crate_universe/src/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use self::crate_context::*;
2323
/// A struct containing information about a Cargo dependency graph in an easily to consume
2424
/// format for rendering reproducible Bazel targets.
2525
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
26-
pub struct Context {
26+
pub(crate) struct Context {
2727
/// The collective checksum of all inputs to the context
2828
pub checksum: Option<Digest>,
2929

crate_universe/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![allow(clippy::large_enum_variant)]
22

3+
pub mod api;
4+
35
pub mod cli;
46

57
mod config;

crate_universe/src/lockfile.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Utility module for interracting with different kinds of lock files
1+
//! Utility module for interacting with the cargo-bazel lockfile.
22
33
use std::collections::BTreeMap;
44
use std::convert::TryFrom;
@@ -17,7 +17,7 @@ use crate::context::Context;
1717
use crate::metadata::Cargo;
1818
use crate::splicing::{SplicingManifest, SplicingMetadata};
1919

20-
pub fn lock_context(
20+
pub(crate) fn lock_context(
2121
mut context: Context,
2222
config: &Config,
2323
splicing_manifest: &SplicingManifest,
@@ -37,7 +37,7 @@ pub fn lock_context(
3737
}
3838

3939
/// Write a [crate::context::Context] to disk
40-
pub fn write_lockfile(lockfile: Context, path: &Path, dry_run: bool) -> Result<()> {
40+
pub(crate) fn write_lockfile(lockfile: Context, path: &Path, dry_run: bool) -> Result<()> {
4141
let content = serde_json::to_string_pretty(&lockfile)?;
4242

4343
if dry_run {
@@ -58,7 +58,7 @@ pub fn write_lockfile(lockfile: Context, path: &Path, dry_run: bool) -> Result<(
5858
pub struct Digest(String);
5959

6060
impl Digest {
61-
pub fn new(
61+
pub(crate) fn new(
6262
context: &Context,
6363
config: &Config,
6464
splicing_manifest: &SplicingManifest,

crate_universe/src/rendering.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl Renderer {
4545
}
4646
}
4747

48-
pub fn render(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
48+
pub(crate) fn render(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
4949
let mut output = BTreeMap::new();
5050

5151
let platforms = self.render_platform_labels(context);

crate_universe/src/rendering/template_engine.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,11 @@ impl TemplateEngine {
133133
Ok(header)
134134
}
135135

136-
pub fn render_module_bzl(&self, data: &Context, platforms: &Platforms) -> Result<String> {
136+
pub(crate) fn render_module_bzl(
137+
&self,
138+
data: &Context,
139+
platforms: &Platforms,
140+
) -> Result<String> {
137141
let mut context = self.new_tera_ctx();
138142
context.insert("context", data);
139143
context.insert("platforms", platforms);
@@ -143,7 +147,7 @@ impl TemplateEngine {
143147
.context("Failed to render crates module")
144148
}
145149

146-
pub fn render_vendor_module_file(&self, data: &Context) -> Result<String> {
150+
pub(crate) fn render_vendor_module_file(&self, data: &Context) -> Result<String> {
147151
let mut context = self.new_tera_ctx();
148152
context.insert("context", data);
149153

0 commit comments

Comments
 (0)