diff --git a/Cargo.lock b/Cargo.lock index 1fc22838..cf8b8740 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,6 +257,7 @@ dependencies = [ "regex", "remain", "serde", + "serde_json", "similar-asserts", "snapbox", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 0e6f4aed..5d8cee96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ octocrab = "0.32" once_cell = "1.18" regex = "1.10" remain = "0.2" +serde = "1.0" +serde_json = "1.0" tempfile = "3.8" termcolor = "1.3" tokio = "1.34" diff --git a/README.md b/README.md index daaa44d8..575984b0 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,15 @@ irrecoverable errors occurred, 1 if unmaintained packages were found, and 2 if a error occurred. ``` +## Ignoring packages + +If a workspace's `Cargo.toml` file includes a `workspace.metadata.unmaintained.ignore` array, all packages named therein will be ignored. Example: + +```toml +[package.metadata.unmaintained] +ignore = ["proc-macro-error"] +``` + ## Known problems Repositories whose urls change across versions may be incorrectly reported as unmaintained. diff --git a/src/main.rs b/src/main.rs index e9c589f4..3b755e95 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use once_cell::sync::Lazy; use regex::Regex; use std::{ cell::RefCell, - collections::HashMap, + collections::{HashMap, HashSet}, env::{args, var}, ffi::OsStr, fs::{read_to_string, remove_dir_all, File}, @@ -417,10 +417,16 @@ fn unmaintained() -> Result { fn filter_packages(metadata: &Metadata) -> Result> { let mut packages = Vec::new(); + let ignored_packages = ignored_packages(metadata)?; + // smoelius: If a project relies on multiple versions of a package, check only the latest one. let metadata_latest_version_map = build_metadata_latest_version_map(metadata); for pkg in &metadata.packages { + if ignored_packages.contains(&pkg.name) { + continue; + } + #[allow(clippy::panic)] let version = metadata_latest_version_map .get(&pkg.name) @@ -457,6 +463,22 @@ fn filter_packages(metadata: &Metadata) -> Result> { Ok(packages) } +#[derive(serde::Deserialize)] +struct UnmaintainedMetadata { + ignored: Option>, +} + +pub fn ignored_packages(metadata: &Metadata) -> Result> { + let serde_json::Value::Object(object) = &metadata.workspace_metadata else { + return Ok(HashSet::default()); + }; + let Some(value) = object.get("unmaintained") else { + return Ok(HashSet::default()); + }; + let metadata = serde_json::value::from_value::(value.clone())?; + Ok(metadata.ignored.unwrap_or_default().into_iter().collect()) +} + fn build_metadata_latest_version_map(metadata: &Metadata) -> HashMap { let mut map: HashMap = HashMap::new(); diff --git a/tests/ignored.rs b/tests/ignored.rs new file mode 100644 index 00000000..bf0bdf54 --- /dev/null +++ b/tests/ignored.rs @@ -0,0 +1,47 @@ +#![cfg_attr(dylint_lib = "general", allow(crate_wide_allow))] +#![cfg_attr(dylint_lib = "try_io_result", allow(try_io_result))] + +use anyhow::{ensure, Result}; +use snapbox::cmd::cargo_bin; +use std::{fs::OpenOptions, io::Write, path::Path, process::Command}; +use tempfile::tempdir; + +#[test] +fn ignored() -> Result<()> { + let tempdir = tempdir()?; + + let status = Command::new("cargo") + .args(["init", "--name=test-package"]) + .current_dir(&tempdir) + .status()?; + ensure!(status.success()); + + let mut manifest = OpenOptions::new() + .append(true) + .open(tempdir.path().join("Cargo.toml"))?; + writeln!(manifest, r#"lz4-compress = "*""#)?; + + let status = cargo_unmaintained(tempdir.path()).status()?; + ensure!(!status.success()); + + writeln!( + manifest, + r#" +[workspace.metadata.unmaintained] +ignored = ["lz4-compress"] +"# + )?; + + let status = cargo_unmaintained(tempdir.path()).status()?; + ensure!(status.success()); + + Ok(()) +} + +fn cargo_unmaintained(dir: &Path) -> Command { + let mut command = Command::new(cargo_bin("cargo-unmaintained")); + command + .args(["unmaintained", "--fail-fast"]) + .current_dir(dir); + command +}