diff --git a/docs/src/checks/licenses/cfg.md b/docs/src/checks/licenses/cfg.md index eba7b90d2..9e02d0a6a 100644 --- a/docs/src/checks/licenses/cfg.md +++ b/docs/src/checks/licenses/cfg.md @@ -101,6 +101,21 @@ The name of the crate that you are adding an exception for An optional version constraint specifying the range of crate versions you are excepting. Defaults to any version. +### Additional exceptions configuration file + +In some cases it's useful to have global cargo-deny config and project-local exceptions. This can be accomplished with a project exceptions file in any of these locations relative to your top level `Cargo.toml` manifest file. + +`cargo-deny` will look for the following files: `/deny.exceptions.toml`, `/.deny.exceptions.toml` and `/.cargo/deny.exceptions.toml` + +Only the exceptions field should be set: + +```ini +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow list. + { allow = ["CDDL-1.0"], name = "inferno", version = "*" }, +] +``` + #### The `allow` field This is the exact same as the general `allow` field. diff --git a/src/cargo-deny/check.rs b/src/cargo-deny/check.rs index 59ef609d0..767fc2fc5 100644 --- a/src/cargo-deny/check.rs +++ b/src/cargo-deny/check.rs @@ -147,6 +147,7 @@ struct ValidConfig { impl ValidConfig { fn load( cfg_path: Option, + exceptions_cfg_path: Option, files: &mut Files, log_ctx: crate::common::LogContext, ) -> Result { @@ -170,9 +171,44 @@ impl ValidConfig { } }; - let cfg: Config = toml::from_str(&cfg_contents) + let mut cfg: Config = toml::from_str(&cfg_contents) .with_context(|| format!("failed to deserialize config from '{cfg_path}'"))?; + // Allow for project-local exceptions. Relevant in corporate environments. + // https://github.com/EmbarkStudios/cargo-deny/issues/541 + // + // This isn't the cleanest, but cfg.licenses isn't mutable, so appending/extending the Vec + // isn't possible. Similarly, the various Config/ValidConfig structs don't derive from + // Copy/Clone, so cloning and updating isn't possible either. + // + // This is the most minimally invasive approach I could come up with. + if let Some(exceptions_cfg_path) = exceptions_cfg_path { + // TOML can't have unnamed arrays at the root. + #[derive(Deserialize)] + pub struct ExceptionsConfig { + pub exceptions: Vec, + } + + let content = std::fs::read_to_string(&exceptions_cfg_path) + .with_context(|| format!("failed to read config from {exceptions_cfg_path}"))?; + + let ex_cfg: ExceptionsConfig = toml::from_str(&content).with_context(|| { + format!("failed to deserialize config from '{exceptions_cfg_path}'") + })?; + + if cfg.licenses.is_some() { + let l = cfg.licenses.unwrap_or_default(); + + let exceptions = l + .exceptions + .into_iter() + .chain(ex_cfg.exceptions.into_iter()) + .collect(); + + cfg.licenses = Some(licenses::Config { exceptions, ..l }); + } + }; + log::info!("using config from {cfg_path}"); let id = files.add(&cfg_path, cfg_contents); @@ -259,6 +295,7 @@ pub(crate) fn cmd( features, } = ValidConfig::load( krate_ctx.get_config_path(args.config.clone()), + krate_ctx.get_local_exceptions_path(), &mut files, log_ctx, )?; @@ -321,7 +358,7 @@ pub(crate) fn cmd( match cl { CodeOrLevel::Code(code) => { if let Some(current) = code_overrides.get(code.as_str()) { - anyhow::bail!("unable to override code '{code}' to '{severity:?}', it has already been overriden to '{current:?}'"); + anyhow::bail!("unable to override code '{code}' to '{severity:?}', it has already been overridden to '{current:?}'"); } code_overrides.insert(code.as_str(), severity); @@ -337,7 +374,7 @@ pub(crate) fn cmd( } }) { - anyhow::bail!("unable to override level '{level:?}' to '{severity:?}', it has already been overriden to '{current:?}'"); + anyhow::bail!("unable to override level '{level:?}' to '{severity:?}', it has already been overridden to '{current:?}'"); } level_overrides.push((ls, severity)); diff --git a/src/cargo-deny/common.rs b/src/cargo-deny/common.rs index f56a687d5..75567556f 100644 --- a/src/cargo-deny/common.rs +++ b/src/cargo-deny/common.rs @@ -102,6 +102,37 @@ impl KrateContext { } } + // Allow for project-local exceptions. Relevant in corporate environments. + // https://github.com/EmbarkStudios/cargo-deny/issues/541 + pub fn get_local_exceptions_path(&self) -> Option { + let mut p = self.manifest_path.parent(); + + while let Some(parent) = p { + let mut config_path = parent.join("deny.exceptions.toml"); + + if config_path.exists() { + return Some(config_path); + } + + config_path.pop(); + config_path.push(".deny.exceptions.toml"); + + if config_path.exists() { + return Some(config_path); + } + + config_path.pop(); + config_path.push(".cargo/deny.exceptions.toml"); + if config_path.exists() { + return Some(config_path); + } + + p = parent.parent(); + } + + None + } + #[inline] pub fn fetch_krates(&self) -> anyhow::Result<()> { fetch(MetadataOptions {