xtask/cmd/
workspace_deps.rs

1use std::collections::{HashMap, HashSet};
2
3use anyhow::Context;
4use cargo_metadata::DependencyKind;
5
6use crate::cmd::IGNORED_PACKAGES;
7
8#[derive(Debug, Clone, clap::Parser)]
9pub struct WorkspaceDeps {
10    #[clap(long, short, value_delimiter = ',')]
11    #[clap(alias = "package")]
12    /// Packages to test
13    packages: Vec<String>,
14    #[clap(long, short, value_delimiter = ',')]
15    #[clap(alias = "exclude-package")]
16    /// Packages to exclude from testing
17    exclude_packages: Vec<String>,
18}
19
20impl WorkspaceDeps {
21    pub fn run(self) -> anyhow::Result<()> {
22        let start = std::time::Instant::now();
23
24        let metadata = crate::utils::metadata()?;
25
26        let workspace_package_ids = metadata.workspace_members.iter().cloned().collect::<HashSet<_>>();
27
28        let workspace_packages = metadata
29            .packages
30            .iter()
31            .filter(|p| workspace_package_ids.contains(&p.id))
32            .map(|p| (&p.id, p))
33            .collect::<HashMap<_, _>>();
34
35        let path_to_package = workspace_packages
36            .values()
37            .map(|p| (p.manifest_path.parent().unwrap(), &p.id))
38            .collect::<HashMap<_, _>>();
39
40        for package in metadata.packages.iter().filter(|p| workspace_package_ids.contains(&p.id)) {
41            if (IGNORED_PACKAGES.contains(&package.name.as_str()) || self.exclude_packages.contains(&package.name))
42                && (self.packages.is_empty() || !self.packages.contains(&package.name))
43            {
44                continue;
45            }
46
47            let toml = std::fs::read_to_string(&package.manifest_path)
48                .with_context(|| format!("failed to read manifest for {}", package.name))?;
49            let mut doc = toml
50                .parse::<toml_edit::DocumentMut>()
51                .with_context(|| format!("failed to parse manifest for {}", package.name))?;
52            let mut changes = false;
53
54            for dependency in package
55                .dependencies
56                .iter()
57                .filter(|dep| dep.kind == DependencyKind::Development)
58            {
59                let Some(path) = dependency.path.as_deref() else {
60                    continue;
61                };
62
63                if path_to_package.get(path).and_then(|id| workspace_packages.get(id)).is_none() {
64                    continue;
65                }
66
67                doc["dev-dependencies"].as_table_mut().unwrap().remove(&dependency.name);
68                println!("Removed dev-dependency `{}` in package `{}`", dependency.name, package.name);
69                changes = true;
70            }
71
72            if changes {
73                std::fs::write(&package.manifest_path, doc.to_string())
74                    .with_context(|| format!("failed to write manifest for {}", package.name))?;
75                println!("Removed dev-dependencies in {} @ {}", package.name, package.manifest_path);
76            }
77        }
78
79        println!("Done in {:?}", start.elapsed());
80
81        Ok(())
82    }
83}