Skip to content

[DRAFT] All changes for pyiceberg-core wheel license attribution - #3380

Draft
dannycjones wants to merge 3 commits into
apache:mainfrom
dannycjones:wheel-license-attribution-v2
Draft

dannycjones wants to merge 3 commits into
apache:mainfrom
dannycjones:wheel-license-attribution-v2

Conversation

@dannycjones

@dannycjones dannycjones commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

This is a draft of all the changes required to have wheels with correct license attribution, the blocker for releasing 0.11.

Please refer to individual PRs for review. This is available to preview what the full changes will look like.

This change migrates from the old shell script for cargo-deny to a new Python script.
This new script will contain logic for dealing with dependencies and their licenses. This commit simply translates the existing logic.

In later changes, this script will be extended with:
- Clarifications for existing dependencies where licenses are misreported, with additional verifications added in the script itself
- Logic for generating the attribution documentation for the pyiceberg-core package.
…hey ship

Two dependencies declare licenses that do not describe the code actually
compiled into the project:

  zstd-sys declares only "MIT/Apache-2.0", which covers its Rust wrapper and
  the pre-generated bindings. It also vendors Zstandard's C sources, which are
  dual BSD-3-Clause / GPL-2.0-only. There is no GPL entry in the allow list;
  the BSD-3-Clause half of that choice is what makes the crate acceptable, and
  the clarification is what records that this was decided rather than missed.

  brotli-decompressor declares "BSD-3-Clause/MIT", the legacy slash form, which
  reads as a choice. It is not one: src/context.rs is Google's Brotli code under
  MIT and is compiled in, so both licenses apply at once and Dropbox cannot
  offer that file under BSD-3-Clause. Upstream reached the same conclusion for
  the sibling crate after dropbox/rust-brotli#218. This is not cosmetic -- under
  the declared OR, BSD-3-Clause alone satisfies the allow list, so removing MIT
  from it would greenlight this crate while its MIT code still shipped.

A clarification is attested by the hash of every license file it relies on, and
that is where the problem lies: cargo-deny discards the entire block if any one
hash stops matching, reports it at warning level only, and then judges the crate
on its own manifest terms. The run still exits 0. Corrupting one of the five
hashes in the zstd-sys block changes nothing a reader would notice. So a crate
upgrade can retire a clarification silently, which is the opposite of what
attesting a hash is for.

`dependencies check` therefore cross-checks that every block applied, comparing
sets of crate versions rather than counting clarifications. cargo-deny reports
one per crate *instance*, not one per block, so a count fails open: a block
matching two versions of a crate inflates the total and hides a second block
that was discarded. Comparing versions also catches a block that reached some
versions of a crate and not others, which is a real case -- getrandom appears
three times in this graph with three different license texts.

Two further checks come from the same reading. A clarification naming a crate no
longer in the graph is reported separately, because the remedy is to delete it
rather than re-hash it. And a version-pinned spec is rejected outright: cargo-deny
accepts `foo@<2.1` as valid config and then applies the block to nothing, with no
diagnostic, so the pin a reader would reach for to say "before the version that
relicensed" is the one that silently does nothing.

`dependencies clarification-hash` prints the hash deny.toml should record for a
license file, so the remediation the failure suggests is a command rather than an
algorithm. It needs xxhash, declared in the script's PEP 723 header, and imports
it lazily -- every other command stays standard library only, so CI keeps
invoking this with plain `python`.

Recorded in deny.toml for the next audit: libsqlite3-sys has the same shape as
zstd-sys, declaring MIT while vendoring SQLite's C sources, but SQLite is public
domain and adds nothing to satisfy. aws-lc-sys already declares every license of
everything it vendors.
…wheels

The wheels ship a compiled extension module that statically links every Rust
dependency, so they redistribute that code and take on its attribution
conditions: the full license text of each crate, and for Apache-2.0 dependencies
a relay of their NOTICE files under section 4(d). Today they carry only the
project's own LICENSE and NOTICE. Source releases and crates.io publications
bundle none of that code and are unaffected.

Generated per wheel, not once. Each platform links a different set of crates, so
a single bundle would either overclaim or omit; cargo-about runs against the Rust
target triples behind each maturin target, and universal2-apple-darwin covers two
triples because it is a fat binary. None of it is checked in: the release
workflows generate the files once, upload them as an artifact, and stage the
right set per wheel before maturin runs.

Nothing upstream makes a mistake here loud, so the generator refuses to produce a
bundle it cannot vouch for:

  maturin exits 0 and warns nothing when a `license-files` glob matches nothing,
  contrary to what PEP 639 requires of it, so `wheel verify` opens the built
  wheels and asserts the files are present, non-trivial, and generated for that
  wheel.

  cargo-about reproduces only the license files it matched by name, silently, so
  a crate keeping its license under an unmatched name loses its attribution. Four
  crates needed clarifying: ring, whose vendored BoringSSL and fiat-crypto code
  is compiled in; zstd-sys, which vendors Zstandard's C sources; libm; and
  alloc-stdlib, whose text exists only upstream.

  cargo-about also discards a clarification whose checksum no longer matches, at
  warning level, and then matches by name again -- the same failure mode as
  cargo-deny's, which is why both halves of this script cross-check that the
  clarifications they were given actually applied. Corrupting one of ring's three
  checksums drops the fiat-crypto notice and 16KB from the bundle without failing.

  Three crates ship no matchable license file at all, so cargo-about fell back to
  the SPDX template and emitted "Copyright (c) <year> <copyright holders>" -- a
  notice attributing nobody. require_no_placeholder_notices rejects those.

brotli-decompressor is disclosed rather than fixed: src/context.rs is Google's
MIT-licensed Brotli code and is compiled in, but the crate ships no MIT text, so
it is listed under BSD-3-Clause only. The MIT text and its notice do reach the
wheel via the brotli crate in the same graph, so the obligation is met; what is
missing is the cross-reference. dropbox/rust-brotli-decompressor#32 fixes it
upstream.

Where a crate offers a choice of licenses this selects one, preferring
Apache-2.0, and reproduces only that, per
https://www.apache.org/legal/resolved.html#mutually-exclusive. LICENSE records
the choice and points at THIRD-PARTY-LICENSES.
@dannycjones
dannycjones force-pushed the wheel-license-attribution-v2 branch from b9ccc01 to 69b7daf Compare October 9, 2026 18:42

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant