Skip to content

Commit ccae332

Browse files
committed
Verify final crate graph against stdlibs lock file.
1 parent 7bb7b53 commit ccae332

File tree

4 files changed

+179
-3
lines changed

4 files changed

+179
-3
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cargo-util/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cargo-util"
3-
version = "0.2.10"
3+
version = "0.2.11"
44
rust-version = "1.75.0" # MSRV:1
55
edition.workspace = true
66
license.workspace = true

src/cargo/core/resolver/encode.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,10 @@ impl EncodableResolve {
406406
HashMap::new(),
407407
))
408408
}
409+
410+
pub fn package(&self) -> Option<&Vec<EncodableDependency>> {
411+
self.package.as_ref()
412+
}
409413
}
410414

411415
fn build_path_deps(ws: &Workspace<'_>) -> CargoResult<HashMap<String, SourceId>> {
@@ -489,6 +493,20 @@ pub struct EncodableDependency {
489493
replace: Option<EncodablePackageId>,
490494
}
491495

496+
impl EncodableDependency {
497+
pub fn name(&self) -> &str {
498+
&self.name
499+
}
500+
501+
pub fn version(&self) -> &str {
502+
&self.version
503+
}
504+
505+
pub fn dependencies(&self) -> Option<&Vec<EncodablePackageId>> {
506+
self.dependencies.as_ref()
507+
}
508+
}
509+
492510
/// Pretty much equivalent to [`SourceId`] with a different serialization method.
493511
///
494512
/// The serialization for `SourceId` doesn't do URL encode for parameters.
@@ -574,6 +592,16 @@ pub struct EncodablePackageId {
574592
source: Option<EncodableSourceId>,
575593
}
576594

595+
impl EncodablePackageId {
596+
pub fn name(&self) -> &str {
597+
&self.name
598+
}
599+
600+
pub fn version(&self) -> Option<&str> {
601+
self.version.as_ref().map(|x| x.as_str())
602+
}
603+
}
604+
577605
impl fmt::Display for EncodablePackageId {
578606
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579607
write!(f, "{}", self.name)?;

tests/build-std/main.rs

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
#![allow(clippy::disallowed_methods)]
2222

2323
use cargo_test_support::*;
24-
use std::env;
2524
use std::path::Path;
25+
use std::{env, fs};
2626

2727
fn enable_build_std(e: &mut Execs, arg: Option<&str>) {
2828
e.env_remove("CARGO_HOME");
@@ -129,6 +129,154 @@ fn basic() {
129129
assert_eq!(p.glob(deps_dir.join("*.dylib")).count(), 0);
130130
}
131131

132+
#[cargo_test(build_std_real)]
133+
fn validate_std_crate_graph() {
134+
let p = project()
135+
.file(
136+
"src/main.rs",
137+
"
138+
fn main() {
139+
foo::f();
140+
}
141+
142+
#[test]
143+
fn smoke_bin_unit() {
144+
foo::f();
145+
}
146+
",
147+
)
148+
.file(
149+
"src/lib.rs",
150+
"
151+
extern crate alloc;
152+
extern crate proc_macro;
153+
154+
/// ```
155+
/// foo::f();
156+
/// ```
157+
pub fn f() {
158+
}
159+
160+
#[test]
161+
fn smoke_lib_unit() {
162+
f();
163+
}
164+
",
165+
)
166+
.file(
167+
"tests/smoke.rs",
168+
"
169+
#[test]
170+
fn smoke_integration() {
171+
foo::f();
172+
}
173+
",
174+
)
175+
.build();
176+
177+
// Extract serialized crate graph from build.
178+
let serialized_graph = p
179+
.cargo("build -Zunstable-options --unit-graph")
180+
.build_std()
181+
.target_host()
182+
.exec_with_output()
183+
.unwrap();
184+
let crate_graph: serde_json::Value =
185+
serde_json::from_slice(serialized_graph.stdout.as_slice()).unwrap();
186+
187+
// Find the stdlib sysroot.
188+
let sysroot = String::from_utf8(
189+
p.cargo("rustc -Zunstable-options --print sysroot")
190+
.masquerade_as_nightly_cargo(&["unstable-options"])
191+
.exec_with_output()
192+
.unwrap()
193+
.stdout,
194+
)
195+
.unwrap();
196+
197+
// Load stdlib lockfile.
198+
let s = fs::read_to_string(Path::new(sysroot.trim()).join("lib/rustlib/src/rust/Cargo.lock"))
199+
.unwrap();
200+
let encoded_lockfile: cargo::core::resolver::EncodableResolve = toml::from_str(&s).unwrap();
201+
202+
// Extract the graph from both of the versions.
203+
let out_graph = crate_graph
204+
.as_object()
205+
.unwrap()
206+
.get("units")
207+
.unwrap()
208+
.as_array()
209+
.unwrap();
210+
let lockfile_graph = encoded_lockfile.package().unwrap();
211+
212+
// Check that resolved graph is subgraph of lockfile.
213+
for out_unit in out_graph.iter() {
214+
// Extract package name, version from ID.
215+
let mut id_elems = out_unit.get("pkg_id").unwrap().as_str().unwrap().split(" ");
216+
let pkg_id = id_elems.next().unwrap();
217+
let pkg_version = id_elems.next().unwrap();
218+
219+
// Ignore if this is our dummy package.
220+
if pkg_id == "foo" {
221+
continue;
222+
}
223+
224+
// Extract package dependencies.
225+
let dependencies = out_unit.get("dependencies").unwrap().as_array().unwrap();
226+
227+
// Ensure this package exists in the lockfile & versions match.
228+
let lockfile_pkg = lockfile_graph.iter().find(|pkg| pkg.name() == pkg_id);
229+
if lockfile_pkg.is_none() {
230+
panic!(
231+
"Package '{}' ({}) was present in build-std unit graph, but not in lockfile.",
232+
pkg_id, pkg_version
233+
);
234+
}
235+
let lockfile_pkg = lockfile_pkg.unwrap();
236+
if lockfile_pkg.version() != pkg_version {
237+
panic!("Package '{}' ({}) from build-std unit graph does not match version in lockfile ({}).", pkg_id, pkg_version, lockfile_pkg.version());
238+
}
239+
240+
// Check dependencies match (resolved must be subset of lockfile).
241+
if lockfile_pkg.dependencies().is_some() && dependencies.len() > 0 {
242+
let lockfile_deps = lockfile_pkg
243+
.dependencies()
244+
.unwrap()
245+
.iter()
246+
.map(|x| x.name().replace("-", "_"))
247+
// Lockfile dependencies may have a prefix.
248+
.map(|x| {
249+
x.strip_prefix("rustc_std_workspace_")
250+
.unwrap_or(&x)
251+
.to_string()
252+
})
253+
.collect::<Vec<String>>();
254+
255+
// When collecting resolved dependencies, we ignore the build script unit.
256+
let dep_names = dependencies
257+
.iter()
258+
.map(|x| x.get("extern_crate_name").unwrap().as_str().unwrap())
259+
// Resolved dependencies may start with a prefix.
260+
.map(|x| {
261+
x.strip_prefix("rustc_std_workspace_")
262+
.unwrap_or(&x)
263+
.to_string()
264+
})
265+
.filter(|x| *x != "build_script_build");
266+
267+
for dep in dep_names {
268+
if !lockfile_deps.contains(&dep.to_string()) {
269+
panic!("Package '{}' from build-std unit graph lists differing dependencies in unit graph from lockfile.", pkg_id);
270+
}
271+
}
272+
} else if lockfile_pkg.dependencies().is_some()
273+
&& dependencies.len() > lockfile_pkg.dependencies().unwrap().len()
274+
{
275+
panic!("Package '{}' from build-std unit graph lists differing dependencies in unit graph from lockfile.", pkg_id);
276+
}
277+
}
278+
}
279+
132280
#[cargo_test(build_std_real)]
133281
fn cross_custom() {
134282
let p = project()

0 commit comments

Comments
 (0)