Detailed changes
@@ -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]]
@@ -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"] }
@@ -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);
}
@@ -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")
}