diff --git a/Cargo.lock b/Cargo.lock index b01b163..790eda1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "cargo-manifest" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db7ad32d2729eca70d1669bae38b6a29dabc30a16f1892cc00977873f450d0a" +checksum = "cecbe6b6f6285b7016d41a045742e0ffda96b6e8e6fe7a479a7e5bc2bcffddad" dependencies = [ "serde", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 0979ebf..e71a735 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ anyhow = "1.0.80" clap = { version = "4", features = ["derive"] } guppy = "0.17.5" fs-err = "2.11.0" -cargo-manifest = "0.14.0" +cargo-manifest = "0.16.0" toml = "0.8.10" semver = "1.0.22" toml_edit = "0.22.6" diff --git a/README.md b/README.md index 5016a9b..fb52e74 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,33 @@ It collects all the dependencies in your workspace, determines which ones can be the `[workspace.dependencies]` section of the root `Cargo.toml`. It also takes care of updating the members' `Cargo.toml` files, setting the correct `features` field for each package. +To exclude workspace members from the autoinherit process, you can either pass their packgage names as an +option like so: + +```bash +cargo autoinherit -e cargo-inherit-test-web +``` + +or you can define the exclusion in the workspace metadata: + +```toml +# Cargo.toml +[workspace] +members = [ + "cli", + "config", + "db", + "web", + "macros" +] + +[workspace.metadata.cargo-autoinherit] +# Skip cargo-autoinherit for these packages +exclude-members = [ + "cargo-autoinherit-test-web" # <= This member will be excluded +] +``` + ## Installation You can find prebuilt binaries on the [Releases page](https://github.com/mainmatter/cargo-autoinherit/releases). diff --git a/src/lib.rs b/src/lib.rs index cb9483d..aa9faef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ use crate::dedup::MinimalVersionSet; -use anyhow::Context; -use cargo_manifest::{Dependency, DependencyDetail, DepsSet, Manifest}; +use anyhow::{anyhow, Context}; +use cargo_manifest::{Dependency, DependencyDetail, DepsSet, Manifest, Workspace}; use guppy::VersionReq; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Formatter; use toml_edit::{Array, Key}; @@ -15,6 +15,46 @@ pub struct AutoInheritConf { help = "Represents inherited dependencies as `package.workspace = true` if possible." )] pub prefer_simple_dotted: bool, + /// Package name(s) of workspace member(s) to exclude. + #[arg(short, long)] + exclude_members: Vec, +} + +#[derive(Debug, Default)] +struct AutoInheritMetadata { + exclude_members: Vec, +} + +impl AutoInheritMetadata { + fn from_workspace(workspace: &Workspace) -> Result { + fn error() -> anyhow::Error { + anyhow!("Excpected value of `exclude` in `workspace.metadata.cargo-autoinherit` to be an array of strings") + } + + let Some(exclude) = workspace + .metadata + .as_ref() + .and_then(|m| m.get("cargo-autoinherit")) + .and_then(|v| v.as_table()) + .and_then(|t| t.get("exclude-members").or(t.get("exclude_members"))) + else { + return Ok(Self::default()); + }; + + let exclude: Vec = match exclude { + toml::Value::Array(excluded) => excluded + .iter() + .map(|v| v.as_str().ok_or_else(error).map(|s| s.to_string())) + .try_fold(Vec::with_capacity(excluded.len()), |mut res, item| { + res.push(item?); + Ok::<_, anyhow::Error>(res) + })?, + _ => return Err(error()), + }; + Ok(Self { + exclude_members: exclude, + }) + } } /// Rewrites a `path` dependency as being absolute, based on a given path @@ -76,7 +116,7 @@ macro_rules! get_either_table_mut { }; } -pub fn auto_inherit(conf: &AutoInheritConf) -> Result<(), anyhow::Error> { +pub fn auto_inherit(conf: AutoInheritConf) -> Result<(), anyhow::Error> { let metadata = guppy::MetadataCommand::new().exec().context( "Failed to execute `cargo metadata`. Was the command invoked inside a Rust project?", )?; @@ -84,7 +124,7 @@ pub fn auto_inherit(conf: &AutoInheritConf) -> Result<(), anyhow::Error> { .build_graph() .context("Failed to build package graph")?; let workspace_root = graph.workspace().root(); - let mut root_manifest: Manifest = { + let mut root_manifest: Manifest = { let contents = fs_err::read_to_string(workspace_root.join("Cargo.toml").as_std_path()) .context("Failed to read root manifest")?; toml::from_str(&contents).context("Failed to parse root manifest")? @@ -97,6 +137,13 @@ pub fn auto_inherit(conf: &AutoInheritConf) -> Result<(), anyhow::Error> { ) }; + let autoinherit_metadata = AutoInheritMetadata::from_workspace(workspace)?; + let excluded_members = BTreeSet::from_iter( + conf.exclude_members + .into_iter() + .chain(autoinherit_metadata.exclude_members), + ); + let mut package_name2specs: BTreeMap = BTreeMap::new(); if let Some(deps) = &mut workspace.dependencies { rewrite_dep_paths_as_absolute(deps.values_mut(), workspace_root); @@ -106,7 +153,12 @@ pub fn auto_inherit(conf: &AutoInheritConf) -> Result<(), anyhow::Error> { for member_id in graph.workspace().member_ids() { let package = graph.metadata(member_id)?; assert!(package.in_workspace()); + let mut manifest: Manifest = { + if excluded_members.contains(package.name()) { + println!("Excluded workspace member `{}`", package.name()); + continue; + } let contents = fs_err::read_to_string(package.manifest_path().as_std_path()) .context("Failed to read root manifest")?; toml::from_str(&contents).context("Failed to parse root manifest")? @@ -193,6 +245,10 @@ pub fn auto_inherit(conf: &AutoInheritConf) -> Result<(), anyhow::Error> { // Inherit new "shared" dependencies in each member's manifest for member_id in graph.workspace().member_ids() { let package = graph.metadata(member_id)?; + if excluded_members.contains(package.name()) { + continue; + } + let manifest_contents = fs_err::read_to_string(package.manifest_path().as_std_path()) .context("Failed to read root manifest")?; let manifest: Manifest = diff --git a/src/main.rs b/src/main.rs index a58f6ea..b71e492 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,5 +19,5 @@ pub enum CargoInvocation { fn main() -> Result<(), anyhow::Error> { let cli = CliWrapper::parse(); let CargoInvocation::AutoInherit(conf) = cli.command; - auto_inherit(&conf) + auto_inherit(conf) }