Skip to content

Commit b930b40

Browse files
committed
Add Rust script to verify centralized dependencies
Partial, short-term solution for Azure#1591. Still need to hook it into the build after we centralize all dependencies.
1 parent f72ad46 commit b930b40

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

eng/scripts/verify-dependencies.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env -S cargo +nightly -Zscript
2+
---
3+
[package]
4+
edition = "2021"
5+
6+
[dependencies]
7+
cargo-util-schemas = "0.1.0"
8+
serde = { version = "1.0.197", features = ["derive"] }
9+
serde_json = "1.0.114"
10+
toml = "0.8.10"
11+
---
12+
13+
use cargo_util_schemas::manifest::{InheritableDependency, TomlDependency, TomlManifest};
14+
use serde::Deserialize;
15+
use std::{
16+
io::Read,
17+
path::PathBuf,
18+
process::{Command, Stdio},
19+
};
20+
21+
fn main() {
22+
let manifest_path = std::env::args()
23+
.nth(1)
24+
.or_else(|| {
25+
find_file(
26+
std::env::current_dir().expect("current directory"),
27+
"Cargo.toml",
28+
)
29+
})
30+
.expect("manifest path");
31+
32+
let package_manifest_path = package_manifest_path(&manifest_path);
33+
let workspace_manifest_path = workspace_manifest_path(&manifest_path);
34+
35+
eprintln!("Checking {}", package_manifest_path.display());
36+
let package_manifest_content =
37+
std::fs::read_to_string(package_manifest_path).expect("read package manifest");
38+
let package_manifest: TomlManifest =
39+
toml::from_str(&package_manifest_content).expect("deserialize package manifest");
40+
let Some(dependencies) = package_manifest.dependencies else {
41+
eprintln!("No package dependencies");
42+
return;
43+
};
44+
45+
let mut dependencies: Vec<String> = dependencies
46+
.into_iter()
47+
.filter_map(|v| match v.1 {
48+
InheritableDependency::Value(dep) => match dep {
49+
TomlDependency::Simple(_) => Some(v.0.to_string()),
50+
TomlDependency::Detailed(details) if details.path.is_none() => {
51+
Some(v.0.to_string())
52+
}
53+
_ => None,
54+
},
55+
InheritableDependency::Inherit(_) => None,
56+
})
57+
.collect();
58+
59+
if !dependencies.is_empty() {
60+
dependencies.sort();
61+
println!(
62+
"The following dependencies do not inherit from workspace {}:\n",
63+
workspace_manifest_path.display()
64+
);
65+
println!("* {}\n", dependencies.join("\n* "));
66+
println!("Add dependencies to workspace and change the package dependency to `{{ workspace = true }}`.");
67+
println!("See https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace for more information.");
68+
69+
std::process::exit(1);
70+
}
71+
}
72+
73+
fn package_manifest_path(manifest_path: &str) -> PathBuf {
74+
let mut cmd = Command::new("cargo")
75+
.args(&["read-manifest", "--manifest-path", manifest_path])
76+
.stdout(Stdio::piped())
77+
.spawn()
78+
.expect("executing cargo read-manifest");
79+
80+
let path: PathBuf = read_manifest(&mut cmd)
81+
.manifest_path
82+
.expect("manifest_path")
83+
.into();
84+
85+
if !path.exists() {
86+
panic!("package manifest not found");
87+
}
88+
89+
path
90+
}
91+
92+
fn workspace_manifest_path(manifest_path: &str) -> PathBuf {
93+
let mut cmd = Command::new("cargo")
94+
.args(&[
95+
"metadata",
96+
"--format-version",
97+
"1",
98+
"--all-features",
99+
"--manifest-path",
100+
manifest_path,
101+
])
102+
.stdout(Stdio::piped())
103+
.spawn()
104+
.expect("executing cargo metadata");
105+
106+
let path: PathBuf = read_manifest(&mut cmd)
107+
.workspace_root
108+
.expect("workspace_root")
109+
.into();
110+
let path = path.join("Cargo.toml");
111+
112+
if !path.exists() {
113+
panic!("workspace manifest not found");
114+
}
115+
116+
path
117+
}
118+
119+
fn find_file(dir: impl AsRef<std::path::Path>, name: &str) -> Option<String> {
120+
for dir in dir.as_ref().ancestors() {
121+
let path = dir.join(name);
122+
if path.exists() {
123+
return Some(path.to_str().unwrap().into());
124+
}
125+
}
126+
None
127+
}
128+
129+
fn read_manifest(cmd: &mut std::process::Child) -> CargoManifest {
130+
let mut reader = std::io::BufReader::new(cmd.stdout.take().expect("buffering stdout"));
131+
132+
let mut content: String = Default::default();
133+
reader.read_to_string(&mut content).expect("reading stdout");
134+
135+
serde_json::from_str::<CargoManifest>(&content).expect("deserializing manifest")
136+
}
137+
138+
#[derive(Deserialize)]
139+
struct CargoManifest {
140+
pub manifest_path: Option<String>,
141+
pub workspace_root: Option<String>,
142+
}
143+

0 commit comments

Comments
 (0)