diff --git a/Cargo.lock b/Cargo.lock index 98d9d34ee3..ef78cc39e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1906,6 +1906,15 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "camino" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +dependencies = [ + "serde", +] + [[package]] name = "cap-fs-ext" version = "3.0.0" @@ -1983,6 +1992,29 @@ dependencies = [ "winx", ] +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cargo_toml" version = "0.20.2" @@ -9401,6 +9433,9 @@ name = "semver" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -13429,9 +13464,9 @@ name = "xtask" version = "0.1.0" dependencies = [ "anyhow", + "cargo_metadata", "cargo_toml", "clap", - "toml 0.8.10", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index be030ae5ff..855d7ace1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -304,6 +304,7 @@ bitflags = "2.6.0" blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "7e497c534d5d4a30c18d9eb182cf39eaf0aaa25e" } blade-macros = { git = "https://github.com/zed-industries/blade", rev = "7e497c534d5d4a30c18d9eb182cf39eaf0aaa25e" } blade-util = { git = "https://github.com/zed-industries/blade", rev = "7e497c534d5d4a30c18d9eb182cf39eaf0aaa25e" } +cargo_metadata = "0.18" cargo_toml = "0.20" chrono = { version = "0.4", features = ["serde"] } clap = { version = "4.4", features = ["derive"] } diff --git a/tooling/xtask/Cargo.toml b/tooling/xtask/Cargo.toml index c83a7f083d..3e2017b35e 100644 --- a/tooling/xtask/Cargo.toml +++ b/tooling/xtask/Cargo.toml @@ -10,6 +10,6 @@ workspace = true [dependencies] anyhow.workspace = true +cargo_metadata.workspace = true cargo_toml.workspace = true clap = { workspace = true, features = ["derive"] } -toml.workspace = true diff --git a/tooling/xtask/src/main.rs b/tooling/xtask/src/main.rs index cb7e84f5ae..0da9131fd3 100644 --- a/tooling/xtask/src/main.rs +++ b/tooling/xtask/src/main.rs @@ -16,6 +16,8 @@ enum CliCommand { /// Runs `cargo clippy`. Clippy(tasks::clippy::ClippyArgs), Licenses(tasks::licenses::LicensesArgs), + /// Checks that packages conform to a set of standards. + PackageConformity(tasks::package_conformity::PackageConformityArgs), } fn main() -> Result<()> { @@ -24,5 +26,8 @@ fn main() -> Result<()> { match args.command { CliCommand::Clippy(args) => tasks::clippy::run_clippy(args), CliCommand::Licenses(args) => tasks::licenses::run_licenses(args), + CliCommand::PackageConformity(args) => { + tasks::package_conformity::run_package_conformity(args) + } } } diff --git a/tooling/xtask/src/tasks.rs b/tooling/xtask/src/tasks.rs index a13bdcaeaa..ea5f3afb56 100644 --- a/tooling/xtask/src/tasks.rs +++ b/tooling/xtask/src/tasks.rs @@ -1,2 +1,3 @@ pub mod clippy; pub mod licenses; +pub mod package_conformity; diff --git a/tooling/xtask/src/tasks/licenses.rs b/tooling/xtask/src/tasks/licenses.rs index c0b82d9a18..0a4dedbf98 100644 --- a/tooling/xtask/src/tasks/licenses.rs +++ b/tooling/xtask/src/tasks/licenses.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::Parser; use crate::workspace::load_workspace; @@ -13,8 +13,11 @@ pub fn run_licenses(_args: LicensesArgs) -> Result<()> { let workspace = load_workspace()?; - for member in workspace.members { - let crate_dir = PathBuf::from(&member); + for package in workspace.workspace_packages() { + let crate_dir = package + .manifest_path + .parent() + .ok_or_else(|| anyhow!("no crate directory for {}", package.name))?; if let Some(license_file) = first_license_file(&crate_dir, &LICENSE_FILES) { if !license_file.is_symlink() { @@ -24,15 +27,15 @@ pub fn run_licenses(_args: LicensesArgs) -> Result<()> { continue; } - println!("Missing license: {member}"); + println!("Missing license: {}", package.name); } Ok(()) } -fn first_license_file(path: &Path, license_files: &[&str]) -> Option { +fn first_license_file(path: impl AsRef, license_files: &[&str]) -> Option { for license_file in license_files { - let path_to_license = path.join(license_file); + let path_to_license = path.as_ref().join(license_file); if path_to_license.exists() { return Some(path_to_license); } diff --git a/tooling/xtask/src/tasks/package_conformity.rs b/tooling/xtask/src/tasks/package_conformity.rs new file mode 100644 index 0000000000..f639f1b98b --- /dev/null +++ b/tooling/xtask/src/tasks/package_conformity.rs @@ -0,0 +1,77 @@ +use std::collections::BTreeMap; +use std::fs; +use std::path::Path; + +use anyhow::{anyhow, Context, Result}; +use cargo_toml::{Dependency, Manifest}; +use clap::Parser; + +use crate::workspace::load_workspace; + +#[derive(Parser)] +pub struct PackageConformityArgs {} + +pub fn run_package_conformity(_args: PackageConformityArgs) -> Result<()> { + let workspace = load_workspace()?; + + let mut non_workspace_dependencies = BTreeMap::new(); + + for package in workspace.workspace_packages() { + let is_extension = package + .manifest_path + .parent() + .and_then(|parent| parent.parent()) + .map_or(false, |grandparent_dir| { + grandparent_dir.ends_with("extensions") + }); + + let cargo_toml = read_cargo_toml(&package.manifest_path)?; + + let is_using_workspace_lints = cargo_toml.lints.map_or(false, |lints| lints.workspace); + if !is_using_workspace_lints { + eprintln!( + "{package:?} is not using workspace lints", + package = package.name + ); + } + + // Extensions should not use workspace dependencies. + if is_extension { + continue; + } + + for dependencies in [ + &cargo_toml.dependencies, + &cargo_toml.dev_dependencies, + &cargo_toml.build_dependencies, + ] { + for (name, dependency) in dependencies { + if let Dependency::Inherited(_) = dependency { + continue; + } + + non_workspace_dependencies + .entry(name.to_owned()) + .or_insert_with(Vec::new) + .push(package.name.clone()); + } + } + } + + for (dependency, packages) in non_workspace_dependencies { + eprintln!( + "{dependency} is being used as a non-workspace dependency: {}", + packages.join(", ") + ); + } + + Ok(()) +} + +/// Returns the contents of the `Cargo.toml` file at the given path. +fn read_cargo_toml(path: impl AsRef) -> Result { + let path = path.as_ref(); + let cargo_toml_bytes = fs::read(&path)?; + Manifest::from_slice(&cargo_toml_bytes) + .with_context(|| anyhow!("failed to read Cargo.toml at {path:?}")) +} diff --git a/tooling/xtask/src/workspace.rs b/tooling/xtask/src/workspace.rs index 23cb161f83..4c4ece6cff 100644 --- a/tooling/xtask/src/workspace.rs +++ b/tooling/xtask/src/workspace.rs @@ -1,17 +1,9 @@ -use std::fs; - -use anyhow::{anyhow, Result}; -use cargo_toml::{Manifest, Workspace}; -use toml; +use anyhow::{Context, Result}; +use cargo_metadata::{Metadata, MetadataCommand}; /// Returns the Cargo workspace. -pub fn load_workspace() -> Result { - let workspace_cargo_toml = fs::read_to_string("Cargo.toml")?; - let workspace_cargo_toml: Manifest = toml::from_str(&workspace_cargo_toml)?; - - let workspace = workspace_cargo_toml - .workspace - .ok_or_else(|| anyhow!("top-level Cargo.toml is not a Cargo workspace"))?; - - Ok(workspace) +pub fn load_workspace() -> Result { + MetadataCommand::new() + .exec() + .context("failed to load cargo metadata") }