Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/src/checks/licenses/cfg.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ allow = [

If `true`, licenses are checked even for `dev-dependencies`. By default this is false as `dev-dependencies` are not used by downstream crates, nor part of binary artifacts.

### The `include-build` field (optional)

If `true`, licenses are checked for `build-dependencies`. By default this is true because build-dependencies can influence build artifacts and are often relevant to licensing. Set this to `false` if you wish to exclude `build-dependencies` from license checks.

### The `version` field (optional)

```ini
Expand Down
29 changes: 28 additions & 1 deletion src/licenses/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ pub struct Config {
/// If true, performs license checks for dev-dependencies for workspace
/// crates as well
pub include_dev: bool,
/// If true, performs license checks for build-dependencies for workspace
/// crates as well
pub include_build: bool,
deprecated_spans: Vec<Span>,
}

Expand All @@ -237,6 +240,7 @@ impl Default for Config {
clarify: Vec::new(),
exceptions: Vec::new(),
include_dev: false,
include_build: true,
deprecated_spans: Vec::new(),
}
}
Expand Down Expand Up @@ -266,7 +270,8 @@ impl<'de> Deserialize<'de> for Config {
.unwrap_or(LintLevel::Warn);
let clarify = th.optional("clarify").unwrap_or_default();
let exceptions = th.optional("exceptions").unwrap_or_default();
let include_dev = th.optional("include-dev").unwrap_or_default();
let include_dev = th.optional("include-dev").unwrap_or_default();
let include_build = th.optional("include-build").unwrap_or(true);

th.finalize(None)?;

Expand All @@ -278,6 +283,7 @@ impl<'de> Deserialize<'de> for Config {
clarify,
exceptions,
include_dev,
include_build,
deprecated_spans: fdeps,
})
}
Expand Down Expand Up @@ -380,6 +386,7 @@ impl crate::cfg::UnvalidatedConfig for Config {
allowed,
ignore_sources,
include_dev: self.include_dev,
include_build: self.include_build,
}
}
}
Expand Down Expand Up @@ -474,6 +481,7 @@ pub struct ValidConfig {
pub exceptions: Vec<ValidException>,
pub ignore_sources: Vec<url::Url>,
pub include_dev: bool,
pub include_build: bool,
}

#[cfg(test)]
Expand Down Expand Up @@ -509,4 +517,23 @@ mod test {

insta::assert_json_snapshot!(validated);
}

#[test]
fn include_build_field_deserializes() {
let cd = ConfigData::<Licenses>::load_str(
"tests/cfg/include-build.toml",
"[licenses]\ninclude-build = false\n",
);

let validated = cd.validate_with_diags(
|l| l.licenses,
|files, diags| {
let diags = write_diagnostics(files, diags.into_iter());
// There should be no diagnostics for a simple config
assert!(diags.is_empty());
},
);

assert!(!validated.include_build, "include-build should be false");
}
}
35 changes: 30 additions & 5 deletions src/licenses/gather.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,11 +492,36 @@ impl Gatherer {

let files_lock = std::sync::Arc::new(parking_lot::RwLock::new(files));

// Most users will not care about licenses for dev dependencies
let krates = if cfg.is_some_and(|cfg| cfg.include_dev) {
krates.krates().collect()
} else {
krates.krates_filtered(krates::DepKind::Dev)
// Determine which dependency kinds to include in license checking.
// By default dev-dependencies are excluded (include_dev = false),
// while build-dependencies are included (include_build = true).
let (include_dev, include_build) = match cfg {
Some(cfg) => (cfg.include_dev, cfg.include_build),
None => (false, true),
};

let krates = match (include_dev, include_build) {
(true, true) => krates.krates().collect(),
(true, false) => krates.krates_filtered(krates::DepKind::Build),
(false, true) => krates.krates_filtered(krates::DepKind::Dev),
(false, false) => {
// Exclude crates that are only reachable via dev _or_ build
// dependency edges. Compute the intersection of the sets that
// remain when dev-only and build-only crates are removed.
let filtered_dev = krates.krates_filtered(krates::DepKind::Dev);
let filtered_build = krates.krates_filtered(krates::DepKind::Build);

// Both filtered lists are sorted by id; compute intersection by
// comparing ids. We'll build a vector of crates present in both.
let mut build_ids: Vec<_> = filtered_build.iter().map(|k| k.id.clone()).collect();
// Ensure sorted for binary_search (should already be sorted, but be safe)
build_ids.sort();

filtered_dev
.into_iter()
.filter(|k| build_ids.binary_search(&k.id).is_ok())
.collect()
}
};

let lic_rx = regex::Regex::new(LICENSE_RX).expect("failed to compile regex");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ expression: validated
}
],
"ignore_sources": [],
"include_dev": false
"include_dev": false,
"include_build": true
}