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
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod clippy;
|
||||
pub mod licenses;
|
||||
pub mod package_conformity;
|
||||
|
|
|
@ -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<PathBuf> {
|
||||
fn first_license_file(path: impl AsRef<Path>, license_files: &[&str]) -> Option<PathBuf> {
|
||||
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);
|
||||
}
|
||||
|
|
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::{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<Workspace> {
|
||||
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<Metadata> {
|
||||
MetadataCommand::new()
|
||||
.exec()
|
||||
.context("failed to load cargo metadata")
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue