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 .is_some_and(|grandparent_dir| grandparent_dir.ends_with("extensions"));
25
26 let cargo_toml = read_cargo_toml(&package.manifest_path)?;
27
28 let is_using_workspace_lints = cargo_toml.lints.is_some_and(|lints| lints.workspace);
29 if !is_using_workspace_lints {
30 eprintln!(
31 "{package:?} is not using workspace lints",
32 package = package.name
33 );
34 }
35
36 // Extensions should not use workspace dependencies.
37 if is_extension || package.name == "zed_extension_api" {
38 continue;
39 }
40
41 for dependencies in [
42 &cargo_toml.dependencies,
43 &cargo_toml.dev_dependencies,
44 &cargo_toml.build_dependencies,
45 ] {
46 for (name, dependency) in dependencies {
47 if let Dependency::Inherited(_) = dependency {
48 continue;
49 }
50
51 non_workspace_dependencies
52 .entry(name.to_owned())
53 .or_insert_with(Vec::new)
54 .push(package.name.clone());
55 }
56 }
57 }
58
59 for (dependency, packages) in non_workspace_dependencies {
60 eprintln!(
61 "{dependency} is being used as a non-workspace dependency: {}",
62 packages.join(", ")
63 );
64 }
65
66 Ok(())
67}
68
69/// Returns the contents of the `Cargo.toml` file at the given path.
70fn read_cargo_toml(path: impl AsRef<Path>) -> Result<Manifest> {
71 let path = path.as_ref();
72 let cargo_toml_bytes = fs::read(path)?;
73 Manifest::from_slice(&cargo_toml_bytes)
74 .with_context(|| format!("reading Cargo.toml at {path:?}"))
75}