anza_xtask/commands/
dev_deps.rs1use {
2 anyhow::{Context, Result},
3 clap::Subcommand,
4 log::{error, warn},
5 std::{
6 collections::HashMap,
7 fs,
8 path::{Path, PathBuf},
9 },
10 toml_edit::DocumentMut,
11};
12
13#[derive(clap::Args, Debug)]
14pub struct CommandArgs {
15 #[command(subcommand)]
16 command: DevDepsCommands,
17}
18
19#[derive(Subcommand, Debug)]
20enum DevDepsCommands {
21 #[command(about = "Check dev-dependencies for workspace members using 'workspace = true'")]
22 Check(CheckArgs),
23}
24
25#[derive(clap::Args, Debug)]
26pub struct CheckArgs {
27 #[arg(long, default_value = "Cargo.toml")]
29 pub manifest_path: PathBuf,
30}
31
32pub fn run(args: CommandArgs) -> Result<()> {
33 match args.command {
34 DevDepsCommands::Check(check_args) => run_check(check_args),
35 }
36}
37
38fn run_check(args: CheckArgs) -> Result<()> {
39 let workspace_root = args
40 .manifest_path
41 .parent()
42 .unwrap_or_else(|| Path::new("."));
43
44 let workspace_toml_content = fs::read_to_string(&args.manifest_path)
46 .with_context(|| format!("Failed to read {:?}", args.manifest_path))?;
47 let workspace_doc = workspace_toml_content
48 .parse::<DocumentMut>()
49 .with_context(|| format!("Failed to parse {:?}", args.manifest_path))?;
50
51 let members = workspace_doc
52 .get("workspace")
53 .and_then(|ws| ws.get("members"))
54 .and_then(|m| m.as_array())
55 .context("No workspace.members found")?;
56
57 let mut package_to_member = HashMap::new();
59 for member in members.iter() {
60 let member_path = member.as_str().context("Member path is not a string")?;
61 let member_cargo_toml = workspace_root.join(member_path).join("Cargo.toml");
62
63 if !member_cargo_toml.exists() {
64 warn!("(Skipping) {member_path}/Cargo.toml not found");
65 continue;
66 }
67
68 let content = fs::read_to_string(&member_cargo_toml)?;
69 let doc = content.parse::<DocumentMut>()?;
70
71 if let Some(package_name) = doc
72 .get("package")
73 .and_then(|p| p.get("name"))
74 .and_then(|n| n.as_str())
75 {
76 package_to_member.insert(package_name.to_string(), member_path);
77 }
78 }
79
80 let mut total_issues = 0;
82 for member in members.iter() {
83 let member_path = member.as_str().context("Member path is not a string")?;
84 let member_cargo_toml = workspace_root.join(member_path).join("Cargo.toml");
85
86 if !member_cargo_toml.exists() {
87 continue;
88 }
89
90 let content = fs::read_to_string(&member_cargo_toml)?;
91 let doc = content.parse::<DocumentMut>()?;
92
93 if let Some(dev_deps) = doc.get("dev-dependencies").and_then(|dd| dd.as_table()) {
94 for (dep_name, dep_value) in dev_deps.iter() {
95 if let Some(table) = dep_value.as_table_like() {
97 if table
98 .get("workspace")
99 .and_then(|w| w.as_bool())
100 .unwrap_or(false)
101 {
102 if package_to_member.contains_key(dep_name) {
104 error!(
105 "{}/Cargo.toml - `{}` in dev-dependencies should use path = \"...\"",
106 member_path, dep_name
107 );
108 total_issues += 1;
109 }
110 }
111 }
112 }
113 }
114 }
115
116 if total_issues == 0 {
117 Ok(())
118 } else {
119 anyhow::bail!(
120 "Found {} dev-dependencies using 'workspace = true' that should use path",
121 total_issues
122 )
123 }
124}