1use std::collections::BTreeMap;
2use std::fs;
3use std::path::Path;
4
5use anyhow::{Context as _, Result, anyhow};
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 {
40 continue;
41 }
42
43 for dependencies in [
44 &cargo_toml.dependencies,
45 &cargo_toml.dev_dependencies,
46 &cargo_toml.build_dependencies,
47 ] {
48 for (name, dependency) in dependencies {
49 if let Dependency::Inherited(_) = dependency {
50 continue;
51 }
52
53 non_workspace_dependencies
54 .entry(name.to_owned())
55 .or_insert_with(Vec::new)
56 .push(package.name.clone());
57 }
58 }
59 }
60
61 for (dependency, packages) in non_workspace_dependencies {
62 eprintln!(
63 "{dependency} is being used as a non-workspace dependency: {}",
64 packages.join(", ")
65 );
66 }
67
68 Ok(())
69}
70
71/// Returns the contents of the `Cargo.toml` file at the given path.
72fn read_cargo_toml(path: impl AsRef<Path>) -> Result<Manifest> {
73 let path = path.as_ref();
74 let cargo_toml_bytes = fs::read(path)?;
75 Manifest::from_slice(&cargo_toml_bytes)
76 .with_context(|| anyhow!("failed to read Cargo.toml at {path:?}"))
77}