cargo-tribute
Generate a REUSE-style LICENSES/ folder and a per-crate attribution manifest from a Cargo dependency tree, instead of hand-maintaining third-party license notices.
cargo tribute walks the normal-dependency closure of your workspace, resolves each crate's SPDX license expression against an accepted list, and writes:
LICENSES/<id>.txt-- one canonical license text per license actually usedNOTICES/<crate>-<version>.txt-- NOTICE files shipped by dependencies (the ones Apache-2.0 section 4(d) asks redistributors to pass along), only when a dependency actually ships oneTHIRD-PARTY.md-- dependencies grouped by license with each crate's copyright holders, linking to the texts
It is a policy gate (fails if a dependency's license is not accepted) and, with --check, a staleness gate (fails if the committed output no longer matches the dependency tree) -- both suitable for CI.

Install
cargo install cargo-tribute
Or, for a prebuilt binary via cargo-binstall:
cargo binstall cargo-tribute
Usage
cargo tribute # write LICENSES/, NOTICES/ and THIRD-PARTY.md
cargo tribute init # scaffold a commented tribute.toml
cargo tribute --check # verify they are current and every license is accepted
cargo tribute --audit # compare declared licenses against the license files the
# crates actually ship (advisory report, never fails)
cargo tribute -p NAME # attribute only this workspace member's deps (repeatable)
cargo tribute --from-deny deny.toml # reuse cargo-deny's [licenses] allow list + exceptions
cargo tribute --manifest-path P # run against a specific Cargo.toml (writes at its workspace root)
cargo tribute --locked --check # forward --locked/--offline/--frozen to cargo metadata (for CI)
cargo tribute --all-features # forward --features/--all-features/--filter-platform too, to
# attribute optional (feature-gated) or platform-specific deps
cargo tribute --json # print the resolved attribution as JSON (no files written)
cargo tribute --format text # one flat plain-text document: attribution list, license
# texts, NOTICE bodies (for an "open source licenses" screen)
cargo tribute --format cyclonedx # CycloneDX 1.6 SBOM carrying the license texts and
# per-component copyright (no files written)
cargo tribute --quiet # suppress the success summary
cargo tribute --help
Exit codes distinguish the failure: 1 license policy failed, 2 output out of date (--check), 3 anything else.
Use in CI
Fail the build when a dependency's license is not accepted, or when the committed LICENSES/, NOTICES/, and THIRD-PARTY.md drift from the dependency tree:
# .github/workflows/licenses.yml
name: licenses
on:
jobs:
tribute:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cargo install cargo-tribute # or: cargo binstall cargo-tribute
- run: cargo tribute --locked --check
Compared to other tools
All of these are good tools; this is where cargo-tribute differs (behavior as of writing -- check each project's latest docs).
| cargo-tribute | cargo-about | cargo-deny | cargo-license | |
|---|---|---|---|---|
| Attribution output | THIRD-PARTY.md + REUSE LICENSES/ folder |
one file from a template | none (license linter) | lists to stdout |
| Copyright lines + NOTICE files | yes | no | no | authors only |
| Accepted-license gate | yes | yes (config) | yes (its focus) | no |
| Per-crate exceptions | yes ([[exception]]) |
per-crate accepted | yes (exceptions) |
no |
| Vendored non-crate code | yes ([[extra]]) |
no | no | no |
| SBOM output | CycloneDX 1.6 with license texts | no | no | no |
| Declared-vs-shipped audit | yes (--audit) |
n/a (harvests files) | no | no |
Staleness --check for CI |
yes | no | n/a | no |
| Setup | zero-config (optional tribute.toml) |
template + about.toml |
deny.toml |
flags only |
Want a broad supply-chain linter (advisories, source bans, duplicate detection)? Reach for cargo-deny. cargo-tribute stays focused on generating and gating the attribution output.
Configuration
A tribute.toml in the project root overrides the defaults (all fields optional):
= ["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC", "0BSD", "Zlib", "Unlicense", "Unicode-3.0"]
= false # also attribute dev-dependencies
= false # also attribute build-dependencies
= false # skip path/git/non-crates.io dependencies (first-party;
# their crates.io deps are still walked and attributed)
= false # skip proc-macro crates and their compile-time subtree
= "THIRD-PARTY.md" # attribution manifest path
= "LICENSES" # folder for the canonical license texts
= "NOTICES" # folder for NOTICE files shipped by dependencies
# override a crate's license -- for crates that declare `license-file` instead of
# `license`, or whose `license` field is wrong or non-SPDX. Repeatable.
[[]]
= "ring"
= "0.17.8" # optional semver req (like Cargo); omit to match any version
= "MIT AND ISC AND OpenSSL"
# allow extra licenses for one crate only, without widening the global accepted
# set. Repeatable; `version` optional, like [[clarify]].
[[]]
= "unicode-ident"
= ["Unicode-DFS-2016"]
# attribute third-party code the crate graph can't see -- C sources vendored in
# a -sys crate, a bundled font. Same accepted policy; url/copyright optional.
[[]]
= "zlib (bundled in libz-sys)"
= "Zlib"
= "https://zlib.net"
= "Copyright (C) 1995-2024 Jean-loup Gailly and Mark Adler"
# local text for a license outside the SPDX corpus, named as LicenseRef-<id> in
# `accepted`, a [[clarify]], or an [[extra]]; copied into the licenses folder.
[[]]
= "LicenseRef-weird"
= "licenses-extra/weird.txt"
How a license is chosen
Each crate's SPDX expression is evaluated against accepted (which is also the OR preference order): for A OR B it picks the preferred accepted license, for A AND B it keeps both. Legacy /-separated expressions (MIT/Apache-2.0) are accepted. A crate whose expression cannot be satisfied from the accepted set is a hard error.
An accepted entry can also be a pairing like "GPL-2.0-only WITH Classpath-exception-2.0", which allows exactly that combination without accepting the bare license. A [[exception]] entry allows extra licenses for one named crate only; they lose the OR preference to globally accepted ones. When accepted is set explicitly, an entry that no dependency's expression references is warned about, so a stale allowlist stays visible.
Code the crate graph can't see -- C sources vendored in a -sys crate, a bundled font -- can be attributed with an [[extra]] entry: its expression flows through the same accepted policy, and its licenses join LICENSES/, THIRD-PARTY.md, and the --json report. A license outside the SPDX corpus is named with a LicenseRef-<id> expression plus a [[license-text]] entry pointing at a local text file, which is copied into the licenses folder (and cleaned up, and --checked) like a canonical text.
Other output formats
--format json|text|cyclonedx prints the resolved attribution to stdout instead of writing files. text is one flat plain-text document -- the attribution list, the full license texts, then the NOTICE bodies -- ready for an "open source licenses" screen (save it, commit it, include_str! it). cyclonedx is a CycloneDX 1.6 SBOM whose components carry the full license texts and a per-component copyright, the fields id-only SBOM generators leave empty; serialNumber and timestamp are deliberately omitted so the output stays deterministic (same tree, same bytes).
Auditing declared licenses
crates.io license metadata is occasionally wrong -- a crate declares BSD-2-Clause but ships an extra license file. cargo tribute --audit scans each dependency's bundled license files, matches them against the SPDX corpus, and reports files whose best match is not covered by the crate's declared expression. It is advisory only: findings do not fail the run, and near-identical corpus texts (Apache-2.0 vs Pixar) are not reported when the declared license matches about as well.
--audit is behind the opt-in audit cargo feature (it pulls in text-detection dependencies). The prebuilt release binaries ship with it; a source install needs cargo install cargo-tribute --features audit.
Reusing a cargo-deny allowlist
Teams already gating licenses with cargo-deny keep the allowlist in deny.toml; duplicating it in tribute.toml invites drift. cargo tribute --from-deny deny.toml takes [licenses].allow as the accepted list (WITH pairings included) and maps [licenses].exceptions onto per-crate [[exception]] entries. Setting accepted in tribute.toml at the same time is an error -- keep one source.
Only normal (runtime) dependencies are attributed by default -- set include-dev/include-build to attribute (and gate) dev- and build-dependencies too. In the other direction, skip-private drops path/git/non-crates.io dependencies (first-party code; their crates.io deps are still walked) and skip-proc-macros drops proc-macro crates together with their compile-time subtree. By default cargo metadata resolves the default feature set, so optional (feature-gated) dependencies are not attributed unless you enable them with --features/--all-features. Canonical license texts (and WITH exception texts) come from the spdx crate, so every SPDX license and exception is covered with no texts to hand-maintain.
A crate with no license field (it declares license-file instead), or a wrong or non-SPDX one, is a hard error until you give it an SPDX expression with a [[clarify]] entry; the clarified expression then flows through the same accepted-set policy.
Copyright lines and NOTICE files
A canonical license text alone is not complete attribution: the MIT/BSD family asks for the copyright notice itself to be reproduced, and Apache-2.0 section 4(d) asks redistributors to pass NOTICE files along. So each dependency's local sources (the same files cargo builds from -- nothing is downloaded) are scanned:
Copyright ...lines found in the crate's bundled license/notice files appear beside the crate inTHIRD-PARTY.md; a crate that ships none falls back to itsauthorsmetadata.- A
NOTICEfile is bundled intoNOTICES/<crate>-<version>.txtand linked from the crate's entry. The folder only exists while a dependency actually ships one, and stale files are cleaned up (and flagged by--check) like license texts.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.