xtask: Add command for checking packages conform to certain standards (#15236)
This PR adds a new `xtask` command for checking that packages conform to certain standards. Still a work-in-progress, but right now it checks: - If `[lints] workspace = true` is set - If packages are using non-workspace dependencies Release Notes: - N/A
This commit is contained in:
parent
13693ff80f
commit
f2060ccbe0
8 changed files with 136 additions and 22 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -1906,6 +1906,15 @@ dependencies = [
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "camino"
|
||||||
|
version = "1.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cap-fs-ext"
|
name = "cap-fs-ext"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
|
@ -1983,6 +1992,29 @@ dependencies = [
|
||||||
"winx",
|
"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]]
|
[[package]]
|
||||||
name = "cargo_toml"
|
name = "cargo_toml"
|
||||||
version = "0.20.2"
|
version = "0.20.2"
|
||||||
|
@ -9401,6 +9433,9 @@ name = "semver"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
|
@ -13429,9 +13464,9 @@ name = "xtask"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"cargo_metadata",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
"clap",
|
"clap",
|
||||||
"toml 0.8.10",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -304,6 +304,7 @@ bitflags = "2.6.0"
|
||||||
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "7e497c534d5d4a30c18d9eb182cf39eaf0aaa25e" }
|
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "7e497c534d5d4a30c18d9eb182cf39eaf0aaa25e" }
|
||||||
blade-macros = { 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" }
|
blade-util = { git = "https://github.com/zed-industries/blade", rev = "7e497c534d5d4a30c18d9eb182cf39eaf0aaa25e" }
|
||||||
|
cargo_metadata = "0.18"
|
||||||
cargo_toml = "0.20"
|
cargo_toml = "0.20"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
|
|
|
@ -10,6 +10,6 @@ workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
cargo_metadata.workspace = true
|
||||||
cargo_toml.workspace = true
|
cargo_toml.workspace = true
|
||||||
clap = { workspace = true, features = ["derive"] }
|
clap = { workspace = true, features = ["derive"] }
|
||||||
toml.workspace = true
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ enum CliCommand {
|
||||||
/// Runs `cargo clippy`.
|
/// Runs `cargo clippy`.
|
||||||
Clippy(tasks::clippy::ClippyArgs),
|
Clippy(tasks::clippy::ClippyArgs),
|
||||||
Licenses(tasks::licenses::LicensesArgs),
|
Licenses(tasks::licenses::LicensesArgs),
|
||||||
|
/// Checks that packages conform to a set of standards.
|
||||||
|
PackageConformity(tasks::package_conformity::PackageConformityArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
@ -24,5 +26,8 @@ fn main() -> Result<()> {
|
||||||
match args.command {
|
match args.command {
|
||||||
CliCommand::Clippy(args) => tasks::clippy::run_clippy(args),
|
CliCommand::Clippy(args) => tasks::clippy::run_clippy(args),
|
||||||
CliCommand::Licenses(args) => tasks::licenses::run_licenses(args),
|
CliCommand::Licenses(args) => tasks::licenses::run_licenses(args),
|
||||||
|
CliCommand::PackageConformity(args) => {
|
||||||
|
tasks::package_conformity::run_package_conformity(args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod clippy;
|
pub mod clippy;
|
||||||
pub mod licenses;
|
pub mod licenses;
|
||||||
|
pub mod package_conformity;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use crate::workspace::load_workspace;
|
use crate::workspace::load_workspace;
|
||||||
|
@ -13,8 +13,11 @@ pub fn run_licenses(_args: LicensesArgs) -> Result<()> {
|
||||||
|
|
||||||
let workspace = load_workspace()?;
|
let workspace = load_workspace()?;
|
||||||
|
|
||||||
for member in workspace.members {
|
for package in workspace.workspace_packages() {
|
||||||
let crate_dir = PathBuf::from(&member);
|
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 let Some(license_file) = first_license_file(&crate_dir, &LICENSE_FILES) {
|
||||||
if !license_file.is_symlink() {
|
if !license_file.is_symlink() {
|
||||||
|
@ -24,15 +27,15 @@ pub fn run_licenses(_args: LicensesArgs) -> Result<()> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Missing license: {member}");
|
println!("Missing license: {}", package.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first_license_file(path: &Path, license_files: &[&str]) -> Option<PathBuf> {
|
fn first_license_file(path: impl AsRef<Path>, license_files: &[&str]) -> Option<PathBuf> {
|
||||||
for license_file in license_files {
|
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() {
|
if path_to_license.exists() {
|
||||||
return Some(path_to_license);
|
return Some(path_to_license);
|
||||||
}
|
}
|
||||||
|
|
77
tooling/xtask/src/tasks/package_conformity.rs
Normal file
77
tooling/xtask/src/tasks/package_conformity.rs
Normal file
|
@ -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<Path>) -> Result<Manifest> {
|
||||||
|
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:?}"))
|
||||||
|
}
|
|
@ -1,17 +1,9 @@
|
||||||
use std::fs;
|
use anyhow::{Context, Result};
|
||||||
|
use cargo_metadata::{Metadata, MetadataCommand};
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use cargo_toml::{Manifest, Workspace};
|
|
||||||
use toml;
|
|
||||||
|
|
||||||
/// Returns the Cargo workspace.
|
/// Returns the Cargo workspace.
|
||||||
pub fn load_workspace() -> Result<Workspace> {
|
pub fn load_workspace() -> Result<Metadata> {
|
||||||
let workspace_cargo_toml = fs::read_to_string("Cargo.toml")?;
|
MetadataCommand::new()
|
||||||
let workspace_cargo_toml: Manifest = toml::from_str(&workspace_cargo_toml)?;
|
.exec()
|
||||||
|
.context("failed to load cargo metadata")
|
||||||
let workspace = workspace_cargo_toml
|
|
||||||
.workspace
|
|
||||||
.ok_or_else(|| anyhow!("top-level Cargo.toml is not a Cargo workspace"))?;
|
|
||||||
|
|
||||||
Ok(workspace)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue