1use std::collections::BTreeMap;
2use std::fs;
3use std::path::Path;
4
5use anyhow::{Context as _, Result};
6use cargo_toml::{Dependency, Manifest};
7use clap::Parser;
8
9use crate::workspace::load_workspace;
10
11#[derive(Parser)]
12pub struct PackageConformityArgs {}
13
14pub fn run_package_conformity(_args: PackageConformityArgs) -> Result<()> {
15 let workspace = load_workspace()?;
16
17 let mut non_workspace_dependencies = BTreeMap::new();
18
19 for package in workspace.workspace_packages() {
20 let is_extension = package
21 .manifest_path
22 .parent()
23 .and_then(|parent| parent.parent())
24 .map_or(false, |grandparent_dir| {
25 grandparent_dir.ends_with("extensions")
26 });
27
28 let cargo_toml = read_cargo_toml(&package.manifest_path)?;
29
30 let is_using_workspace_lints = cargo_toml.lints.map_or(false, |lints| lints.workspace);
31 if !is_using_workspace_lints {
32 eprintln!(
33 "{package:?} is not using workspace lints",
34 package = package.name
35 );
36 }
37
38 // Extensions should not use workspace dependencies.
39 if is_extension || package.name == "zed_extension_api" {
40 continue;
41 }
42
43 // Ignore `workspace-hack`, as it produces a lot of false positives.
44 if package.name == "workspace-hack" {
45 continue;
46 }
47
48 for dependencies in [
49 &cargo_toml.dependencies,
50 &cargo_toml.dev_dependencies,
51 &cargo_toml.build_dependencies,
52 ] {
53 for (name, dependency) in dependencies {
54 if let Dependency::Inherited(_) = dependency {
55 continue;
56 }
57
58 non_workspace_dependencies
59 .entry(name.to_owned())
60 .or_insert_with(Vec::new)
61 .push(package.name.clone());
62 }
63 }
64 }
65
66 for (dependency, packages) in non_workspace_dependencies {
67 eprintln!(
68 "{dependency} is being used as a non-workspace dependency: {}",
69 packages.join(", ")
70 );
71 }
72
73 Ok(())
74}
75
76/// Returns the contents of the `Cargo.toml` file at the given path.
77fn read_cargo_toml(path: impl AsRef<Path>) -> Result<Manifest> {
78 let path = path.as_ref();
79 let cargo_toml_bytes = fs::read(path)?;
80 Manifest::from_slice(&cargo_toml_bytes)
81 .with_context(|| format!("reading Cargo.toml at {path:?}"))
82}