cargo-about

cargo-about is a cargo plugin that lets you generate license information for all crates in a dependency graph.

Quickstart

Installs cargo-about, initializes your project with a default configuration, then generates a licenses.html file.

cargo install --locked cargo-about && cargo about init && cargo about generate -o licenses.html about.hbs

Command Line Interface

cargo-about is intended to be used as a Command Line Tool, see the link for the available commands and options.

API

cargo-about is primarily meant to be used as a cargo plugin, but a majority of its functionality is within a library whose docs you may view on docs.rs

Commands

common

-L, --log-level

The log level for messages, only log messages at or above the level will be emitted.

Possible values:

  • off - No output will be emitted
  • error
  • warn (default)
  • info
  • debug
  • trace

--color

Whether coloring is applied to human-formatted output, using it on JSON output has no effect.

Possible values:

  • auto (default) - Coloring is applied if the output stream is a TTY
  • always - Coloring is always applied
  • never - No coloring is applied for any output

init

Initializes an about.toml configuration.

Flags

--no-handlebars

Disables the generation of the default handlebars template.

--overwrite

Overwrites an existing about.toml file with the default configuration.

generate

The generate subcommand is the primary subcommand of cargo-about. It attempts to find and satisfy all license requirements for a crate's or workspace's dependency graph and generate licensing output based on one or more handlebar templates.

Flags

--all-features (single crate or workspace)

Enables all features when determining which crates to consider. Works for both single crates and workspaces.

--no-default-features (single crate only)

Disables the default feature for a crate when determining which crates to consider.

--workspace

Scan licenses for the entire workspace, not just the active package.

--fail

Exits with a non-zero exit code if any crate's license cannot be reasonably determined

Options

-c, --config

Path to the config to use. Will default to <manifest_root/about.toml> if not specified.

--features (single crate only)

Space-separated list of features to enable when determining which crates to consider.

-i, --include-local

Include local crates beneath one or more directories, local crates are disregarded by default.

-m, --manifest-path

The path of the Cargo.toml for the root crate, defaults to the current crate or workspace in the current working directory.

-n, --name

The name of the template to use when rendering. If only passing a single template file to templates this is not used.

-o, --output-file

A file to write the generated output to. Typically an .html file.

--threshold (default: 0.8)

The confidence threshold required for license files to be positively identified: 0.0 - 1.0

--format <json|handlebars> (default: handlebars)

The format to output the license + crate data in.

Args

<templates>

The template(s) or template directory to use. Must either be a .hbs file, or have at least one .hbs file in it if it is a directory. Required if --format = handlebars (the default).

Config

Contains all of the configuration options used when running generate

The accepted field

Priority list of all the accepted licenses for a project. cargo-about will try to satisfy the licenses in the order that they are declared in this list. So in the below example, if a crate is licensed with the typical Apache-2.0 OR MIT license expression, only the Apache-2.0 license would be used as it has higher priority than MIT only one of them is required. This list applies globally to all crates. The licenses specified here are used to satisfy the license expressions for every crate, if they can't be satisfied then cargo-about will emit an error for why.

accepted = [
    "Apache-2.0",
    "MIT",
]

The targets field (optional)

A list of targets that are actually building for. Crates which are only included via cfg() expressions that don't match one or more of the listed targets will be ignored. Note that currently the targets are evaluated all at once, so there might be cases where a crate is included that is actually impossible for any one target alone.

targets = [
    "x86_64-unknown-linux-gnu",
    "x86_64-unknown-linux-musl",
    "x86_64-pc-windows-msvc",
    "x86_64-apple-darwin",
]

The ignore-build-dependencies field (optional)

If true, all crates that are only used as build dependencies will be ignored.

ignore-build-dependencies = true

The ignore-dev-dependencies field (optional)

If true, all crates that are only used as dev dependencies will be ignored.

ignore-dev-dependencies = true

The ignore-transitive-dependencies field (optional)

If true, only direct dependencies of crates in the workspace will be included in the graph, transitive dependencies (dependencies of dependencies) will be ignored.

ignore-transitive-dependencies = true

The no-clearly-defined field (optional)

If true, will not attempt to lookup licensing information for any crate from https://clearlydefined.io, only user clarifications, workarounds, and local file scanning will be used to determine licensing information.

The filter-noassertion field (optional)

If using https://clearlydefined.io to gather license information, that service will conservatively add NOASSERTION to the expression for files that contain license like data, but an SPDX license ID could not be confidently ascribed to it. This can result in the license expression for the crate to contain 1 or more NOASSERTION identifiers, which would require the user to accept that (not really valid) ID to pass the license check. By setting this field to true, files that have a NOASSERTION id will instead be scanned locally, which will generally either figure out the license, or else skip that file.

For a real world example of what this looks like, webpki:0.22.0's LICENSE file is an ISC license, however it has a preamble that is not part of the ISC license that trips up clearly defined's inspection, causing it to be attributed with ISC AND NOASSERTION. Locally scanning the file will be more tolerant and just attribute it with ISC.

The workarounds field (optional)

Unfortunately, not all crates properly package their licenses, or if they do, sometimes in a non-machine readable format, or in a few cases, are slightly wrong. These can be clarified manually via configuration, but some crates that are widely used in the Rust ecosystem have these issues, and rather than require that every cargo-about user who happens to have a dependency on one or more of these crates specify the same config to get it working, cargo-about instead includes a few built-in clarifications that can be opted into with a single config entry rather than redoing work.

See Workarounds for a list of the workarounds currently built-in to cargo-about

workarounds = [
    "ring",
    "rustls",
]

The private field (optional)

It's often not useful or wanted to check for licenses in your own private workspace crates. So the private field allows you to do so.

The ignore field

If true, workspace members will not have their license expression checked, if they are not published.

# Cargo.toml
[package]
name = "sekret"
license = "¯\_(ツ)_/¯"
publish = false # "private"!
# about.toml

# The sekret package would be ignored now
private = { ignore = true }

The registries field

A list of private registries you may publish your workspace crates to. If a workspace member only publishes to private registries, it will also be ignored if private.ignore = true

# Cargo.toml
[package]
name = "sekret"
license = "¯\_(ツ)_/¯"
publish = ["sauce"]
# about.toml

# Still ignored!
private = { ignore = true, registries = ["sauce"] }

Crate configuration

Along with the global options, crates can be individually configured as well, using the name of the crate as the key. Crate specific configuration must come last in the config file.

The accepted field (optional)

Just as with the global accepted field, this accepts specific licenses for the crate. These licenses are appended to the global list, and are again in priority order. So for example, if the global accept was like this:

accepted = ["MIT", "ISC"]

And we are using ring, which also is licensed under the OpenSSL license, we could use the following configuration to satisfy the license requirements of ring.

accepted = ["MIT", "ISC"]

[ring]
accepted = ["OpenSSL"]

The clarify field (optional)

As noted in the workarounds, some crates have complicated or incomplete licensing that messes up the harvesting of the license info in an automated fashion. While the workarounds exists for popular crates (and can always be expanded with PRs!) there are often going to be crates that you will need to clarify yourself until a new release of the crate, etc, which is the purpose of the clarify field, to specify exactly what the license information is, and how to verify that the license terms are still the same as when they were clarified, using hashes of the input files.

Note that since clarifications are human supplied in your project's own configuration, they take precedence over all other methods. If a crate is clarified, it will not be retrieved from clearlydefined.io nor via local file harvesting.

The license field

This is the top level SPDX expression for the crate as a whole. It should be noted that this actually overrides the license expression of the crate itself if it exists, though in most cases this will be the same as the stated license, it is simply required so that you can't accidentally forget it in the cases where it does differ.

[ring.clarify]
license = "ISC AND MIT AND OpenSSL"

The override-git-commit field (optional)

When clarifying a crate with files pulled from its source git repository, cargo-about will normally read the contents of the .cargo_vcs_info.json file that is usually part of a published crate's contents, which includes the full commit hash at the time the crate was published. However, this file is not guaranteed to be present (eg, if the crate is published with cargo publish --allow-dirty), so in that case the git ref to pull the license file contents must be supplied. This can either be a full git revision, or a git tag.

[core-graphics-types.clarify]
override-git-commit = "3841d2bb3aa76dec2ea6319e757603fb923b5a50"

The files and/or git field

When clarifying the license of a crate, it is required to give a source of truth for the licenses in the expression, to prevent drift between the clarification and the actual licensing of the crate in question. For example, if a crate uses the Zlib license, then changes between releases to use the MIT license instead, the source of truth (eg. the LICENSE file) would also (hopefully...) change resulting in a hash mismatch that means the clarification would not be used.

We'll be using this example for the ring crate

[ring.clarify]
license = "ISC AND MIT AND OpenSSL"

[[ring.clarify.files]]
path = 'LICENSE'
license = 'OpenSSL'
checksum = '53552a9b197cd0db29bd085d81253e67097eedd713706e8cd2a3cc6c29850ceb'
start = '/* ===================================================================='
end = '\n * Hudson (tjh@cryptsoft.com).\n *\n */'
The path field

This is the relative path to the file from the root. For files this is the root of the crate, but for git this is the repo root.

The license field (optional)

In a multiple license situation it can be useful to supply the exact license for the file. If this is not supplied the parent clarify.license expression is used instead.

The checksum field

This is the full sha-256 checksum of the contents. If this doesn't match the computed checksum, the clarification will not be used.

The start field (optional)

In some cases, crates concatenate multiple licenses together into a single file, which confuses machine readers, and makes splatting the license text into the final generated template a pain, so in those cases you need to supply a place in the text that a license starts and/or ends from. This is just a simple substring find.

The end field (optional)

Just as with start, this is just a simple substring find, however, it will only match text that comes after the position the start text (or beginning of the file) was found.

Workarounds

Here's a list of the current set of workarounds, the crates they apply to, and why the workaround is needed

bitvec

The bitvec crate and one of its dependencies by the same author don't include the license information in the crate source.

chrono

The chrono crate puts both the Apache-2.0 and MIT license texts in the same file, which confuses askalono and also means the SPDX expression is not machine readable.

cocoa

Some of the crates published from https://github.com/servo/core-foundation-rs do not properly package the license text when publishing the crate.

gtk

The various gtk crates don't include the license text in older versions, though versions published after 2021-10-21 will have the license information in the packaged source so the workaround is not needed for those versions.

prost

The various prost crates don't include the license text in the published crates.

ring

The ring crate puts puts 4 different licenses in a single file which confuses tools and also doesn't declare its expression in the Cargo.toml manifest.

rustls

The rustls crate puts puts 3 different licenses in a single file which confuses tools. This should be fixed in later versions.

sentry

None of the crates published from https://github.com/getsentry/sentry-rust include the license text.

tonic

None of the crates published from https://github.com/hyperium/tonic include the license text.

tract

None of the crates published from https://github.com/sonos/tract included the license text previous to versions 0.15.4. Versions after this do include the license text and this workaround is not needed

wasmtime

The crates around wasmtime and cranelift, many but not all of which are published from https://github.com/bytecodealliance/wasmtime, use the Apache-2.0 WITH LLVM-exception, and the license text reflects this. However, neither clearlydefined.io nor askalono report the inclusion of the LLVM-exception, so this workaround just clarifies that.

Output template

cargo-about uses handlebars templates to take the output of license gathering and transform it into your desired output. See handlebars for how handlebar templates work generally.

Types

LicenseSet

  • count - The number of times the license was used to satisfy a license expression for a crate
  • name - The name of the license
  • id - The id of the license

License

  • name - The full name of the license
  • id - The SPDX identifier
  • text - The full license text
  • source_path - The path of the license if it was pulled from the source code of the crate
  • used_by A list of UsedBy

UsedBy

  • crate - Metadata for a cargo package
  • path - Optional path of the dependency that is being used by the license

Variables

These are the variables that are exposed to the templates

Example

<ul class="licenses-overview">
    {{#each overview}}
    <li><a href="#{{id}}">{{name}}</a> ({{count}})</li>
    {{/each}}
</ul>

Preview of the default about.hbs

license

You can view the full license here.

clarify

Computes a clarification for a file

Options

-s, --subsections

One or more subsections in the file which is itself its own license. Uses !! as the separator between the start and end of the subsection.

--threshold (default: 0.8)

The minimum confidence score a license must have

Args

<path>

The relative path from the root of the source of the file to clarify.

Subcommands

crate

Retrieves the file from the git repository and commit associated with the specified crate and version.

Args

<spec>

The crate's <name>-<version> spec to retrieve. The crate source must already be downloaded.

repo

Pulls the file from a git repository rather than the file system.

Args

<rev>

The git revision to retrieve. Can either be a commit hash or a tag.

<repo>

The full URL to the git repo. Only github.com, gitlab.com, and bitbucket.org are currently supported.

path

Reads the license information from a path on disk.

Args

<root>

The path root.