diff --git a/.github/actions/cargo-about/action.yml b/.github/actions/cargo-about/action.yml new file mode 100644 index 0000000..2f603e4 --- /dev/null +++ b/.github/actions/cargo-about/action.yml @@ -0,0 +1,36 @@ +# `cargo-about` on the runner, for `tools/notices.sh`: from the release +# archive where there is one, built from source on the Intel Mac, which +# has none. Used by the wasm and native jobs of `ci.yml` and by the +# release build through `.github/dist-build-setup.yml`. +name: cargo-about +description: Installs cargo-about into ~/.cargo/bin +inputs: + version: + description: The cargo-about release + default: "0.9.2" +runs: + using: composite + steps: + - shell: bash + env: + VERSION: ${{ inputs.version }} + run: | + case "$RUNNER_OS-$RUNNER_ARCH" in + Linux-X64) triple=x86_64-unknown-linux-musl ;; + Linux-ARM64) triple=aarch64-unknown-linux-musl ;; + macOS-ARM64) triple=aarch64-apple-darwin ;; + Windows-X64) triple=x86_64-pc-windows-msvc ;; + *) triple= ;; + esac + if [ -z "$triple" ]; then + cargo install cargo-about --version "$VERSION" --locked --features cli + exit 0 + fi + # Paths as this bash sees them: `$RUNNER_TEMP` is a Windows path on + # the Windows runner, which its `tar` cannot open, and the bin + # directory is wherever `cargo` is. + tmp=$(mktemp -d) + curl -sSL "https://github.com/EmbarkStudios/cargo-about/releases/download/$VERSION/cargo-about-$VERSION-$triple.tar.gz" \ + | tar -xz -C "$tmp" + cp "$tmp/cargo-about-$VERSION-$triple"/cargo-about* "$(dirname "$(command -v cargo)")/" + cargo about --version diff --git a/.github/dist-build-setup.yml b/.github/dist-build-setup.yml index 58f41e4..80ab64a 100644 --- a/.github/dist-build-setup.yml +++ b/.github/dist-build-setup.yml @@ -26,3 +26,11 @@ - name: nasm for libaom on Windows if: runner.os == 'Windows' uses: ilammy/setup-nasm@v1 +# The licence notices of the crates in this target's binary, for the +# archive: `include` in `dist-workspace.toml` names the file, and the +# script writes it for the job's target before `dist build` runs. +- name: cargo-about + uses: ./.github/actions/cargo-about +- name: Third-party notices for the archive + shell: bash + run: tools/notices.sh crates/sqzer-cli/Cargo.toml "${{ join(matrix.targets, ' ') }}" native THIRD-PARTY-LICENSES.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab18733..1794839 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,6 +132,16 @@ jobs: - uses: Swatinem/rust-cache@v2 with: key: native + # The notices the release archive for this target carries, written + # the way the release build writes them, and checked for the C + # libraries' own notices and the patent grants at the end. + - uses: ./.github/actions/cargo-about + - shell: bash + run: | + tools/notices.sh crates/sqzer-cli/Cargo.toml "${{ matrix.target }}" native THIRD-PARTY-LICENSES.txt + for needle in "Alliance for Open Media" "Copyright (c) 2010, Google Inc" "Copyright 2019 Joe Drago" "Patent grants" "Additional IP Rights Grant"; do + grep -q "$needle" THIRD-PARTY-LICENSES.txt || { echo "THIRD-PARTY-LICENSES.txt lacks: $needle"; exit 1; } + done # Testing one package at a time needs the `package/feature` # spelling and keeps a crash in one backend from hiding the other # results. @@ -155,7 +165,6 @@ jobs: runs-on: ubuntu-latest env: WASM_PACK: "0.15.0" - CARGO_ABOUT: "0.9.2" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -167,10 +176,8 @@ jobs: curl -sSL "https://github.com/wasm-bindgen/wasm-pack/releases/download/v$WASM_PACK/wasm-pack-v$WASM_PACK-x86_64-unknown-linux-musl.tar.gz" \ | tar -xz --strip-components=1 -C "$HOME/.cargo/bin" "wasm-pack-v$WASM_PACK-x86_64-unknown-linux-musl/wasm-pack" # `cargo-about` writes the licence notices `pack.sh` puts in the - # package, from the same release archives `wasm-pack` comes from. - - run: | - curl -sSL "https://github.com/EmbarkStudios/cargo-about/releases/download/$CARGO_ABOUT/cargo-about-$CARGO_ABOUT-x86_64-unknown-linux-musl.tar.gz" \ - | tar -xz --strip-components=1 -C "$HOME/.cargo/bin" "cargo-about-$CARGO_ABOUT-x86_64-unknown-linux-musl/cargo-about" + # package. + - uses: ./.github/actions/cargo-about # Every command below runs without the workflow's `RUSTFLAGS`: the # variable replaces the flags of `.cargo/config.toml` instead of # adding to them, and this target would be built without `simd128` diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 268a510..06772d8 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -33,7 +33,6 @@ jobs: contents: write env: WASM_PACK: "0.15.0" - CARGO_ABOUT: "0.9.2" PLAN: ${{ inputs.plan }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: @@ -55,10 +54,8 @@ jobs: curl -sSL "https://github.com/wasm-bindgen/wasm-pack/releases/download/v$WASM_PACK/wasm-pack-v$WASM_PACK-x86_64-unknown-linux-musl.tar.gz" \ | tar -xz --strip-components=1 -C "$HOME/.cargo/bin" "wasm-pack-v$WASM_PACK-x86_64-unknown-linux-musl/wasm-pack" # `cargo-about` writes the licence notices `pack.sh` puts in the - # package, from the same release archives `wasm-pack` comes from. - - run: | - curl -sSL "https://github.com/EmbarkStudios/cargo-about/releases/download/$CARGO_ABOUT/cargo-about-$CARGO_ABOUT-x86_64-unknown-linux-musl.tar.gz" \ - | tar -xz --strip-components=1 -C "$HOME/.cargo/bin" "cargo-about-$CARGO_ABOUT-x86_64-unknown-linux-musl/cargo-about" + # package. + - uses: ./.github/actions/cargo-about - run: crates/sqzer-wasm/pack.sh # The package carries the workspace version, which dist has already # matched against the tag for the binaries. Checked again here diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 16c71df..c29461f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,6 +148,11 @@ jobs: - name: "nasm for libaom on Windows" uses: "ilammy/setup-nasm@v1" if: "runner.os == 'Windows'" + - name: "cargo-about" + uses: "./.github/actions/cargo-about" + - name: "Third-party notices for the archive" + run: "tools/notices.sh crates/sqzer-cli/Cargo.toml \"${{ join(matrix.targets, ' ') }}\" native THIRD-PARTY-LICENSES.txt" + shell: "bash" - name: Install dist run: ${{ matrix.install_dist.run }} # Get the dist-manifest diff --git a/.gitignore b/.gitignore index d67348a..7f32af1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target /dist +# Written by `tools/notices.sh` for the release archives. +/THIRD-PARTY-LICENSES.txt *.profraw .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bdd8a1..c7ca205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,8 @@ ### Bug Fixes -* **wasm:** the npm package carries `THIRD-PARTY-LICENSES.txt`, the licence notice of every crate compiled into `sqzer_bg.wasm`, generated by `cargo about` from the wasm32 dependency tree as `pack.sh` builds the package. The C library inside `libdeflate-sys` has its own MIT notice there, and `avif-parse`, under MPL-2.0, is listed with its repository. The package shipped its own two licence texts and nothing for the 170-odd crates in the module ([#54](https://github.com/sqzer-dev/sqzer/issues/54)) +* **wasm:** the npm package carries `THIRD-PARTY-LICENSES.txt`, the licence notice of every crate compiled into `sqzer_bg.wasm`, generated by `cargo about` from the wasm32 dependency tree as `pack.sh` builds the package, and the patent grant `rav1e` ships with its licence. The C library inside `libdeflate-sys` has its own MIT notice there, and `avif-parse`, under MPL-2.0, is listed with its repository. The package shipped its own two licence texts and nothing for the 170-odd crates in the module ([#54](https://github.com/sqzer-dev/sqzer/issues/54)) +* **cli:** every release archive carries `THIRD-PARTY-LICENSES.txt` next to the binary: the notice of every crate compiled into it for that target, the C and C++ libraries of the native tier under the crates that vendor them, `libwebp`, `libaom`, `libavif`, `libjxl` and `jpegli` with the third-party code inside their trees, and the patent grants that travel with the AV1, WebP and JPEG XL licences. The archives shipped the project's own two licence texts and nothing else ([#62](https://github.com/sqzer-dev/sqzer/issues/62)) * **sqzer:** an explicit quality with no format, on a graphic the portable tier sends to lossless WebP, is reported as what it was: `Output::target` is `Resolved::Lossless` and `Output::lossless` is `true`, where the record said the quality asked for and `lossless: false` over the same lossless bytes. `--json`, `{quality}` in a `--template` and the npm package follow ([#56](https://github.com/sqzer-dev/sqzer/issues/56)) ## 0.3.0 diff --git a/CLAUDE.md b/CLAUDE.md index cc59a86..ec30a7f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,6 +61,7 @@ cargo build -p sqzer-cli --features native # needs C libs on PATH cargo build -p sqzer-wasm --target wasm32-unknown-unknown # proves portable builds without a libc; needs clang (cd crates/sqzer-wasm && wasm-pack test --node) # the JavaScript API over the fixtures; needs wasm-pack, and no RUSTFLAGS set crates/sqzer-wasm/pack.sh # the npm package as a tag publishes it, packed and run from its tarball; needs cargo-about and jq +tools/notices.sh crates/sqzer-cli/Cargo.toml native THIRD-PARTY-LICENSES.txt # the notices a release archive carries; about.toml clarifies what cargo about misses cargo test --workspace cargo clippy --workspace --all-targets --all-features # pedantic is on, warnings are errors in CI cargo fmt --all diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe627f3..bc7bd1c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,6 +47,10 @@ cargo build -p sqzer-wasm --target wasm32-unknown-unknown # the npm package as a tag publishes it, with the licence notices of every crate in # the module; needs wasm-pack, cargo-about (`cargo install cargo-about --features cli`), jq and Node crates/sqzer-wasm/pack.sh + +# the licence notices a release archive carries, for one target; the native CI job runs +# it for each. `about.toml` at the root clarifies what `cargo about` cannot read on its own +tools/notices.sh crates/sqzer-cli/Cargo.toml x86_64-unknown-linux-gnu native THIRD-PARTY-LICENSES.txt ``` > **Note**: The golden tests compare each encoder's SSIMULACRA2 score on the fixtures against a committed value. If a change moves a score, say why in the PR; do not just update the number. diff --git a/README.md b/README.md index 7dd4341..7b7a4a1 100644 --- a/README.md +++ b/README.md @@ -299,4 +299,4 @@ Issues and pull requests are welcome, in English preferably. [`ROADMAP.md`](ROAD ## Licence -MIT or Apache-2.0, at your option. +MIT or Apache-2.0, at your option. The release archives and the npm package carry `THIRD-PARTY-LICENSES.txt`, the notice of every crate compiled into the binary or the module, the C libraries of the native tier included, and the patent grants that travel with some of those licences. diff --git a/about.hbs b/about.hbs new file mode 100644 index 0000000..a7701c2 --- /dev/null +++ b/about.hbs @@ -0,0 +1,25 @@ +Third-party notices for sqzer + +sqzer itself is MIT OR Apache-2.0, in LICENSE-MIT and LICENSE-APACHE next +to this file. The binary or module this file ships with is built from the +crates below, each under the licence named, with the notice the crate +ships, or the one its repository holds where the crate ships none. A C or +C++ library compiled from the source its crate vendors is listed under +that crate, with the notices of the third-party code inside it. A crate +under MPL-2.0 has its source at the repository given with it. The patent +grants that travel with some of these licences are at the end. + +{{#each licenses}} +{{#unless (eq used_by.0.crate.repository "https://github.com/sqzer-dev/sqzer")}} +================================================================================ +{{{name}}} + +Used by: +{{#each used_by}} + {{{crate.name}}} {{{crate.version}}}{{#if crate.repository}} {{{crate.repository}}}{{/if}} +{{/each}} + +{{{text}}} + +{{/unless}} +{{/each}} diff --git a/about.toml b/about.toml index a96bbe0..aa2d1d4 100644 --- a/about.toml +++ b/about.toml @@ -1,9 +1,11 @@ # `cargo about` writes the licence notices of everything compiled into a -# build: `crates/sqzer-wasm/pack.sh` runs it over the npm package into -# `THIRD-PARTY-LICENSES.txt` with `crates/sqzer-wasm/about.hbs`. The -# accepted list is the allow-list of `deny.toml`, in the order a crate -# that offers a choice is taken: a crate that is `MIT OR Apache-2.0` -# ships its MIT notice. +# build, `THIRD-PARTY-LICENSES.txt`: `tools/notices.sh` runs it with +# `about.hbs` next to this file, for the npm package and for each +# release archive. The accepted list is the allow-list of `deny.toml`, in +# the order a crate that offers a choice is taken: a crate that is +# `MIT OR Apache-2.0` ships its MIT notice. The last two are not crate +# licences, `deny.toml` never sees them: they are the terms of C code +# inside a vendored tree, named by the clarifications below. accepted = [ "MIT", "Apache-2.0", @@ -16,14 +18,17 @@ accepted = [ "Unicode-3.0", "Unicode-DFS-2016", "CC0-1.0", + "IJG", + "BSD-3-Clause-Clear", ] -# The package is the wasm32 build; what only a desktop target compiles -# is not in it. Build scripts and their dependencies (`cc`) do not ship, -# and neither does `sqzer-wasm` itself, `publish = false`: the package's -# own licence is next to the file. The four published workspace crates -# are left out by the template, on their repository. -targets = ["wasm32-unknown-unknown"] -ignore-build-dependencies = true +# The target comes from `tools/notices.sh`, so what only another target +# compiles is not in the file. Dev dependencies do not ship. Build +# dependencies are kept, for one of them: `jpegxl-src`, a build +# dependency of `gamut-jxl-sys`, is where the libjxl source compiled into +# the binary lives. `sqzer-wasm` itself, `publish = false`, is out, and +# the four published workspace crates are left out by the template, on +# their repository: sqzer's own licence is next to the file. +ignore-build-dependencies = false ignore-dev-dependencies = true private = { ignore = true } # A notice lives at the root of a crate. Scanned deeper, the tree gave a @@ -37,8 +42,9 @@ max-depth = 1 # entry is checked again. `files` are read from the crate as published, # `git` from its repository at the commit it was published from, for a # crate that ships no notice and keeps one at the root of its repository. -# Two crates stay on the bare licence text: `simd_helpers` has no notice -# in its repository either, and `zune-inflate` names no repository. +# Three crates stay on the bare licence text: `simd_helpers` has no +# notice in its repository either, `zune-inflate` names no repository, +# and the MIT bindings of `libwebp-sys` have none anywhere. # Vendors and compiles `libdeflate`, a C library under its own MIT # notice, which the crate's Apache-2.0 field does not carry. @@ -114,3 +120,68 @@ clarify = { license = "BSD-3-Clause", git = [ clarify = { license = "BSD-2-Clause", git = [ { path = "LICENSE", checksum = "acc9e236b0011c638714a16f5634fa5bcd3be111182e237c3049db700c3521e2" }, ] } + +# The C and C++ libraries of the native tier, compiled from the source +# their crate vendors and listed under it: the notice of the library and +# of the third-party code inside it that the build compiles. The patent +# grants next to these notices are appended by `tools/notices.sh`. +[libwebp-sys] +clarify = { license = "MIT AND BSD-3-Clause", files = [ + { path = "vendor/COPYING", license = "BSD-3-Clause", checksum = "5aec868f669e384a22372a4e8a1a6cd7d44c64cd451f960ca69cc170d1e13acf" }, +] } +[libavif-sys] +clarify = { license = "BSD-2-Clause", files = [ + { path = "libavif/LICENSE", checksum = "10952217a6d404de8bf8a997fbea9b88f682df1fe98cb9b9f467ade641525639" }, +] } +[libaom-sys] +clarify = { license = "BSD-2-Clause AND BSD-3-Clause AND BSD-3-Clause-Clear AND MIT AND ISC", files = [ + { path = "vendor/LICENSE", license = "BSD-2-Clause", checksum = "4764a286d8b2faeaf42f4418e7d7a28d58fc8fd4d00a3d0a7f44b0a4099de7f2" }, + { path = "vendor/third_party/libyuv/LICENSE", license = "BSD-3-Clause", checksum = "2b2cc1180c7e6988328ad2033b04b80117419db9c4c584918bbb3cfec7e9364f" }, + { path = "vendor/third_party/fastfeat/LICENSE", license = "BSD-3-Clause", checksum = "043dcfd059386f9facd376351b2bd79325778744aa442177390cdfcca54babed" }, + { path = "vendor/third_party/SVT-AV1/LICENSE.md", license = "BSD-3-Clause-Clear", checksum = "0acc2fcb27472bdc9aaf8b71f37055bbdac4f54671b7d922f241bd7fcd0dd3e6" }, + { path = "vendor/third_party/vector/LICENSE", license = "MIT", checksum = "ff6c857ce2708e87701323078aef115c1872a1f80a9649f86dc899b48083bc60" }, + { path = "vendor/third_party/x86inc/LICENSE", license = "ISC", checksum = "719d8fa235f2068e0ae6d6a7dceb0a7720d7840f0f0ebed29957989e6ded3cd8" }, +] } +# libjxl, vendored twice: `jpegxl-src` builds it for `gamut-jxl-sys`, +# `jpegli-sys` builds its jpegli with the libjpeg-turbo inside the tree. +[jpegxl-src] +clarify = { license = "BSD-3-Clause AND Apache-2.0 AND MIT", files = [ + { path = "LICENSE", license = "BSD-3-Clause", checksum = "240df475ce8999f8200aa705a1a119ac5caaaaba7c7f87b2bab18af8c663b7ff" }, + { path = "libjxl/LICENSE", license = "BSD-3-Clause", checksum = "8405932022a556380c2d8c272eff154a923feb197233f348ce5f7334fb0a5ede" }, + { path = "libjxl/third_party/skcms/LICENSE", license = "BSD-3-Clause", checksum = "e59bb5c5c6ba426a9ac4ba9fe667ad14c5166b12aa25be8af1d122b14fbe2e36" }, + { path = "libjxl/third_party/highway/LICENSE", license = "Apache-2.0", checksum = "43070e2d4e532684de521b885f385d0841030efa2b1a20bafb76133a5e1379c1" }, + { path = "libjxl/third_party/brotli/LICENSE", license = "MIT", checksum = "3d180008e36922a4e8daec11c34c7af264fed5962d07924aea928c38e8663c94" }, +] } +[jpegli-sys] +clarify = { license = "BSD-3-Clause AND Apache-2.0 AND MIT AND IJG", files = [ + { path = "LICENSE", license = "BSD-3-Clause", checksum = "2fbf612cf8db413c570f11b875cf96d89266c275b4712902e76d7375a1902624" }, + { path = "libjxl/LICENSE", license = "BSD-3-Clause", checksum = "8405932022a556380c2d8c272eff154a923feb197233f348ce5f7334fb0a5ede" }, + { path = "libjxl/third_party/skcms/LICENSE", license = "BSD-3-Clause", checksum = "e59bb5c5c6ba426a9ac4ba9fe667ad14c5166b12aa25be8af1d122b14fbe2e36" }, + { path = "libjxl/third_party/highway/LICENSE", license = "Apache-2.0", checksum = "43070e2d4e532684de521b885f385d0841030efa2b1a20bafb76133a5e1379c1" }, + { path = "libjxl/third_party/brotli/LICENSE", license = "MIT", checksum = "3d180008e36922a4e8daec11c34c7af264fed5962d07924aea928c38e8663c94" }, + { path = "libjxl/third_party/libjpeg-turbo/LICENSE.md", license = "IJG", checksum = "60c756742db3ad1913304e8b13f0e86e22e51adb50cc0b3333c163f7e45ceec1" }, + { path = "libjxl/third_party/libjpeg-turbo/README.ijg", license = "IJG", checksum = "4b7b9f8c03bb8d60270dfd12684e70ab21e4abfd27e73905cd1a7c4cae6f5cdb" }, +] } + +# Crates of the native tier published without their notice, read from +# the repository, and one that keeps it in a form the scan does not take. +[gamut-codec-abi] +clarify = { license = "MIT", git = [ + { path = "LICENSE-MIT", checksum = "91704430e98ce519c0e316079b12529b37c0b5dccb50e29b44f2cfe0e0887b05" }, +] } +[gamut-core] +clarify = { license = "MIT", git = [ + { path = "LICENSE-MIT", checksum = "91704430e98ce519c0e316079b12529b37c0b5dccb50e29b44f2cfe0e0887b05" }, +] } +[gamut-jxl] +clarify = { license = "MIT", git = [ + { path = "LICENSE-MIT", checksum = "91704430e98ce519c0e316079b12529b37c0b5dccb50e29b44f2cfe0e0887b05" }, +] } +[gamut-jxl-sys] +clarify = { license = "MIT", git = [ + { path = "LICENSE-MIT", checksum = "91704430e98ce519c0e316079b12529b37c0b5dccb50e29b44f2cfe0e0887b05" }, +] } +[siphasher] +clarify = { license = "MIT", files = [ + { path = "COPYING", checksum = "c962ee4d1d05ddc138b202b2540219ebc57893fcf97b364852094a9a94ce1365" }, +] } diff --git a/crates/sqzer-wasm/about.hbs b/crates/sqzer-wasm/about.hbs deleted file mode 100644 index 2ff059f..0000000 --- a/crates/sqzer-wasm/about.hbs +++ /dev/null @@ -1,22 +0,0 @@ -Third-party notices for the npm package sqzer - -The package itself is MIT OR Apache-2.0, in LICENSE-MIT and LICENSE-APACHE -next to this file. sqzer_bg.wasm is compiled from the crates below, each -under the licence named, with the notice the crate ships, or the one its -repository holds where the crate ships none. A crate under MPL-2.0 has its -source at the repository given with it. - -{{#each licenses}} -{{#unless (eq used_by.0.crate.repository "https://github.com/sqzer-dev/sqzer")}} -================================================================================ -{{{name}}} - -Used by: -{{#each used_by}} - {{{crate.name}}} {{{crate.version}}}{{#if crate.repository}} {{{crate.repository}}}{{/if}} -{{/each}} - -{{{text}}} - -{{/unless}} -{{/each}} diff --git a/crates/sqzer-wasm/pack.sh b/crates/sqzer-wasm/pack.sh index e7af69b..cf67fe0 100755 --- a/crates/sqzer-wasm/pack.sh +++ b/crates/sqzer-wasm/pack.sh @@ -20,22 +20,11 @@ env -u RUSTFLAGS wasm-pack build --release --target web --out-name sqzer # root. cp ../../LICENSE-MIT ../../LICENSE-APACHE pkg/ -# The notices of every crate compiled into the module, from `about.toml` -# at the repository root and `about.hbs` here. Most of their licences ask -# that the copyright notice travels with a binary; this is where it -# travels. `--fail` refuses a crate whose licence cannot be read. A stale -# clarification in `about.toml`, a crate whose licence file moved with a -# bump, is only a warning to `cargo about`, which then drops that notice -# and goes on; here a warning fails the build, so nothing is left out -# without anyone knowing. -about_log=$(cargo about generate --fail --locked --target wasm32-unknown-unknown \ - -o pkg/THIRD-PARTY-LICENSES.txt about.hbs 2>&1) || { echo "$about_log" >&2; exit 1; } -if grep -q WARN <<< "$about_log"; then - echo "$about_log" >&2 - echo "cargo about warned: a notice is missing, or a clarification in about.toml is stale" >&2 - exit 1 -fi -test -s pkg/THIRD-PARTY-LICENSES.txt +# The notices of every crate compiled into the module and the patent +# grants that travel with them, as `tools/notices.sh` writes them for the +# wasm32 tree. Most of those licences ask that the notice travels with a +# binary; this is where it travels. +../../tools/notices.sh Cargo.toml wasm32-unknown-unknown "" pkg/THIRD-PARTY-LICENSES.txt jq '.name = "sqzer" | .files += ["snippets", "LICENSE-MIT", "LICENSE-APACHE", "THIRD-PARTY-LICENSES.txt"]' \ pkg/package.json > pkg/package.json.new diff --git a/crates/sqzer-wasm/tests/package.mjs b/crates/sqzer-wasm/tests/package.mjs index 822e8fa..063cdd2 100644 --- a/crates/sqzer-wasm/tests/package.mjs +++ b/crates/sqzer-wasm/tests/package.mjs @@ -20,7 +20,7 @@ for (const file of ["LICENSE-MIT", "LICENSE-APACHE", "README.md", "sqzer.d.ts"]) // The notices of the crates in the module, one `Used by` block per // licence text, with the C library inside `libdeflate-sys` among them. const notices = await readFile(installed("THIRD-PARTY-LICENSES.txt"), "utf8"); -for (const needle of ["Used by:", "ravif", "mozjpeg-rs", "libdeflate-sys", "Eric Biggers", "avif-parse"]) { +for (const needle of ["Used by:", "ravif", "mozjpeg-rs", "libdeflate-sys", "Eric Biggers", "avif-parse", "Patent grants", "Alliance for Open Media"]) { assert.ok(notices.includes(needle), `THIRD-PARTY-LICENSES.txt lacks ${needle}`); } diff --git a/dist-workspace.toml b/dist-workspace.toml index 3cfd71c..14dc9a2 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -29,6 +29,11 @@ install-updater = false # five. features = ["native"] precise-builds = true +# The licence notices of the crates in the binary, next to its own two +# licence texts. The build job writes the file for its target before +# `dist build` runs (`tools/notices.sh`, called from the build-setup steps +# below); `dist plan` does not need it to exist. +include = ["THIRD-PARTY-LICENSES.txt"] # `brew install sqzer-dev/tap/sqzer`. The tap is the # `sqzer-dev/homebrew-tap` repository; the publish job writes the formula # there with the `HOMEBREW_TAP_TOKEN` secret of this repository. diff --git a/tools/notices.sh b/tools/notices.sh new file mode 100755 index 0000000..31e186f --- /dev/null +++ b/tools/notices.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Writes the third-party notices of one build of sqzer: the notice of +# every crate in its dependency tree, through `cargo about` with +# `about.toml` and `about.hbs` at the repository root, then every PATENTS +# file a crate ships, a grant that travels with the licence and that +# `cargo about` has no slot for. `crates/sqzer-wasm/pack.sh` runs it for +# the npm package; the release build of `release.yml` runs it for each +# archive, through `.github/dist-build-setup.yml`; the native job of +# `ci.yml` runs it for its target and checks the result. +# +# tools/notices.sh +# tools/notices.sh crates/sqzer-cli/Cargo.toml x86_64-unknown-linux-gnu native THIRD-PARTY-LICENSES.txt +# +# `features` is the feature list to resolve the tree with, "" for the +# defaults. Needs `cargo-about` and `jq`. +set -euo pipefail +manifest=$1 +target=$2 +features=$3 +out=$4 +root=$(cd "$(dirname "$0")/.." && pwd) +# Expanded with the guard below: an empty array under `set -u` is an +# error in the bash 3.2 of macOS. +feature_args=() +if [ -n "$features" ]; then + feature_args=(--features "$features") +fi + +# `--fail` refuses a crate whose licence cannot be read. A stale +# clarification in `about.toml`, a crate whose notice moved with a bump, +# is only a warning to `cargo about`, which then drops that notice and +# goes on; here a warning fails the run, so nothing is left out without +# anyone knowing. Its "warning: config found for a crate not present in +# the graph" is lowercase and expected: `about.toml` covers the npm +# package and the binaries, and no one tree holds every crate it names. +log=$(cargo about generate --fail --locked -c "$root/about.toml" -m "$manifest" \ + --target "$target" ${feature_args[@]+"${feature_args[@]}"} -o "$out" "$root/about.hbs" 2>&1) || { + echo "$log" >&2 + exit 1 +} +if grep -q WARN <<< "$log"; then + echo "$log" >&2 + echo "cargo about warned: a notice is missing, or a clarification in about.toml is stale" >&2 + exit 1 +fi + +# Every PATENTS file shipped by a crate of the tree, by name, four levels +# deep so the third-party code inside a vendored C tree is covered. The +# tree is the one `cargo about` saw: `cargo tree` for the target, over +# normal and build dependencies, never dev ones. `cargo metadata` only +# maps a crate to its directory: its own resolve enables the features of +# every platform at once, so on musl it would add the JPEG XL crates the +# musl binary does not carry. A path crate, printed with its directory, +# is ours and is not shipped. One such file, `rav1e`'s, is Windows-1252; +# the output stays UTF-8. +{ + echo "================================================================================" + echo "Patent grants" + echo + echo "Additional rights granted with a licence above, as the crate ships them." +} >> "$out" +# A file, not an associative array: the macOS runners have bash 3.2. +dirs=$(mktemp) +trap 'rm -f "$dirs"' EXIT +cargo metadata --format-version 1 --locked --manifest-path "$manifest" ${feature_args[@]+"${feature_args[@]}"} | + jq -r '.packages[] | select(.source != null) + | "\(.name) \(.version)\t\(.manifest_path | sub("[/\\\\]Cargo\\.toml$"; ""))"' > "$dirs" +cargo tree --locked --manifest-path "$manifest" --target "$target" ${feature_args[@]+"${feature_args[@]}"} \ + -e normal,build --prefix none --format '{p}' | + awk '$3 == "" || $3 == "(*)" || $3 == "(proc-macro)" { sub(/^v/, "", $2); print $1 " " $2 }' | + sort -u | + while read -r crate; do + dir=$(awk -F '\t' -v crate="$crate" '$1 == crate { print $2; exit }' "$dirs") + if [ -z "$dir" ]; then + echo "no directory known for $crate" >&2 + exit 1 + fi + # A Windows path from `cargo metadata`, for the Git Bash `find`. + if command -v cygpath > /dev/null; then + dir=$(cygpath -u "$dir") + fi + find "$dir" -maxdepth 4 -type f -iname 'PATENTS*' | sort | while read -r file; do + printf '\n--------------------------------------------------------------------------------\n%s %s\n\n' "$crate" "${file#"$dir"/}" + if iconv -f utf-8 -t utf-8 "$file" > /dev/null 2>&1; then + cat "$file" + else + iconv -f windows-1252 -t utf-8 "$file" + fi + echo + done + done >> "$out" +test -s "$out"