Skip to content
Merged
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
10 changes: 9 additions & 1 deletion .agents/docs/release/embed-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ Operational doc for the `--embed-python=bundle` slice (plan:
archive and attached to our GitHub release, mirrored so we do not depend on
a third-party asset staying up. The `licenses/` directory of the `full`
archive travels inside the pack.
- The third-party license texts are also vendored in the wheel as package
data (`src/pyscriptc/pbs_licenses/`, sha256-pinned in
`packaging/pbs_licenses.toml`): the pinned `install_only` asset ships no
`licenses/` directory, but a bundle output must carry the texts without a
repository checkout. A bump refreshes the vendored texts
(`uv run python scripts/sync_pbs_licenses.py --fetch`), the inventory
(`packaging/third-party.toml`) and `NOTICE` together.

## Manual runtime bump (works before the scheduled workflow lands)

Expand All @@ -27,7 +34,8 @@ Operational doc for the `--embed-python=bundle` slice (plan:
2. Update `packaging/runtime.lock.toml`: new release tag, asset names,
sha256 (from the PBS release's `SHA256SUMS`), new CPython micro.
3. Update `packaging/third-party.toml` from the new `full` archive's
`licenses/` directory and re-run
`licenses/` directory, refresh the vendored texts with
`uv run python scripts/sync_pbs_licenses.py --fetch` and re-run
`uv run python scripts/gen_third_party_notices.py`.
4. Open the PR. The embed CI jobs (`.github/workflows/embed.yml`) must be
green; they run the empty-environment acceptance programs against the new
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/embed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ jobs:
compare "bundled pure dependency" "$WORK/fresh/sitedep/bin/depprog" sitedep
compare "program data file" "$WORK/fresh/datafile/bin/prog" datafile

# Issue #709: the output carries the license texts.
for f in LICENSE.cpython.txt LICENSE.expat.txt NOTICE; do
[ -f "$WORK/fresh/stdlib/share/licenses/$f" ] \
|| { echo "::error::share/licenses/$f missing from the bundle" >&2; status=1; }
done

# Loader assertions: the relative rpath is present and no absolute
# path into the build tree or the pack survives in the image.
if [ "${{ matrix.os }}" = "macos-14" ]; then
Expand Down
11 changes: 7 additions & 4 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ Full license texts
------------------

The complete license text of CPython and of every component above ships in
the `licenses/` directory of the runtime pack and is copied into
`OUT/share/licenses/` of every output directory built with
`--embed-python=bundle`. This file names the components; the pack carries
the texts. This file is generated by scripts/gen_third_party_notices.py;
the `share/licenses/` directory of every output built with
`--embed-python=bundle`: the CPython text is copied from the bundled runtime
pack, the third-party texts are vendored in the compiler's package data
(`pyscriptc/pbs_licenses/`, refreshed from the python-build-standalone
`full` archive by scripts/sync_pbs_licenses.py), and this notice is copied
next to them. This file names the components; the texts travel with the
output. This file is generated by scripts/gen_third_party_notices.py;
edit the inputs, not the output.
2 changes: 1 addition & 1 deletion docs/getting-started/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ pyscriptc compile --embed-python=bundle -o OUT prog.py

The pack is fetched on demand, sha256-verified against the pinned lock and cached under `~/.cache/pyscriptc/runtime/`. The first download is tens of megabytes (about 27 MB on macOS arm64, 35 MB on Linux x86_64). An already-unpacked pack can be used offline by pointing `PYSC_RUNTIME_DIR` at it.

The output directory carries the CPython license and the third-party notices of everything compiled into the bundled interpreter, under `OUT/share/licenses/`. The [runtime pack page](../reference/runtime-pack.md) has the details, including the pinned versions and the provenance of the pack.
The output directory carries the CPython license, the third-party notices of everything compiled into the bundled interpreter, and the licenses of any bundled third-party distribution, under `OUT/share/licenses/`. The [runtime pack page](../reference/runtime-pack.md) has the details, including the pinned versions and the provenance of the pack.

## Cross-compiling

Expand Down
10 changes: 6 additions & 4 deletions docs/reference/runtime-pack.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ Offline and air-gapped machines point `PYSC_RUNTIME_DIR` at an already-unpacked

## Licenses in the output

The CPython license (PSF License Agreement) is permissive and allows redistribution in binary form provided the license text and copyright notice travel with the binary. A `bundle` compile therefore copies:
The CPython license (PSF License Agreement) is permissive and allows redistribution in binary form provided the license text and copyright notice travel with the binary. A `bundle` compile therefore writes `OUT/share/licenses/` with:

- the pack's `licenses/` tree to `OUT/share/licenses/`, and
- pyscriptc's own Apache-2.0 `LICENSE` and `NOTICE` next to it.
- `LICENSE.cpython.txt`, the license text of the bundled runtime itself, copied from the pack (`lib/pythonX.Y/LICENSE.txt` of the pinned asset),
- one `LICENSE.<component>.txt` per third-party component compiled into libpython (expat, libffi, mpdecimal, OpenSSL, SQLite, zlib, zstd, ncurses and the rest of the inventory in `packaging/third-party.toml`); the texts are vendored in the compiler's package data (`pyscriptc/pbs_licenses/`) and copied into the output,
- `NOTICE`, the third-party notice rollup of the bundled runtime, generated by `scripts/gen_third_party_notices.py` from `packaging/third-party.toml` and the runtime lock, and
- one directory per bundled third-party distribution (issue #660), holding the `licenses/` tree of its `*.dist-info` metadata.

Third-party notices of the bundled runtime are generated by `scripts/gen_third_party_notices.py` into `NOTICE` at the repository root and refreshed whenever the runtime lock changes; the generator output carries the component inventory from the pack build.
The third-party texts are vendored because the pinned python-build-standalone asset the pack is cut from (`install_only_stripped`) ships no `licenses/` directory; the `full` archive of the same release does, and `scripts/sync_pbs_licenses.py` vendors its texts with their sha256 pinned in `packaging/pbs_licenses.toml`. A runtime bump refreshes the vendored texts, the inventory and `NOTICE` together (see [the operational doc](../../../.agents/docs/release/embed-runtime.md)).

The compiler itself is Apache-2.0; the preflight checker (pyrefly, MIT) ships inside the install. Neither license changes when a program is compiled with a bundled runtime, but the runtime's own licenses must be present in every redistributed output directory, which the bundle step does automatically.

Expand Down
2 changes: 2 additions & 0 deletions packaging/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ args=(
"--include-data-files=$pyrefly_exe=$pyrefly_dest"
"--include-data-files=$nroot/src/pyscriptc/runtime/*.c=pyscriptc/runtime/"
"--include-data-files=$nroot/src/pyscriptc/runtime/*.h=pyscriptc/runtime/"
# The third-party license texts a bundle output stages (issue #709).
"--include-data-files=$nroot/src/pyscriptc/pbs_licenses=pyscriptc/pbs_licenses/"
--nofollow-import-to=pytest
--nofollow-import-to=mypy
--nofollow-import-to=tkinter
Expand Down
68 changes: 68 additions & 0 deletions packaging/pbs_licenses.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# sha256 of the license texts vendored in src/pyscriptc/pbs_licenses/.
#
# The texts come from the `licenses/` directory of the python-build-
# standalone `full` archive of the pinned release (the `install_only`
# asset the runtime pack is cut from ships no such directory). Refresh
# them with `uv run python scripts/sync_pbs_licenses.py --fetch`.

schema = 1

[license."LICENSE.bdb.txt"]
sha256 = "3bdff06e69991c94664f2ef5c5f8096f60b7dbec071756ea6cc26b445e06ec5b"

[license."LICENSE.bzip2.txt"]
sha256 = "1f38bbc7caacafd65169276d759c0d88c991b753b643ce35d0e45ea1971dd441"

[license."LICENSE.cpython.txt"]
sha256 = "86e61415828a8b5b06ec8d024e6f086ce155a8b85fd0c419c0ba4dc004e74fdd"

[license."LICENSE.expat.txt"]
sha256 = "122f2c27000472a201d337b9b31f7eb2b52d091b02857061a8880371612d9534"

[license."LICENSE.libX11.txt"]
sha256 = "2daec087a88e7c9b8082557cdeebad5bbb8155a4137472f0b22e269cd99d0c1e"

[license."LICENSE.libXau.txt"]
sha256 = "56abe29bb1d9806a9e04fa9f80fed2c0f18027594df3f098148d814aef6bddfa"

[license."LICENSE.libedit.txt"]
sha256 = "29cea33c32bbc9785142386377915612a2fa786482c46843383384aded2e09b1"

[license."LICENSE.libffi.txt"]
sha256 = "deaf3a42effb551a5b140fa9afefed183a27f1341c6d1bf430d106a5e6931fc0"

[license."LICENSE.liblzma.txt"]
sha256 = "9a4062de0a2c388a98cf35a35d348b62fa97c838a71c3c28ee1a2d7d0a565b02"

[license."LICENSE.libuuid.txt"]
sha256 = "122ee1f7e258f2c3c0e538a75c037684f420454bf3850ddc74ce750bbf5fe86b"

[license."LICENSE.libxcb.txt"]
sha256 = "c5ffbfeaa501071ceeb97b7de2c0d703fdaa35de01c0fb6cbac1c28453a3e9fd"

[license."LICENSE.mpdecimal.txt"]
sha256 = "669512af7219f58be03a398766d7c9da11a3b3df9d3f05cb74c5ceca25c8da3b"

[license."LICENSE.ncurses.txt"]
sha256 = "87a4c4442337b8968ef956031c406b74f9cb7149b7ba87311bdaba534816201c"

[license."LICENSE.openssl-3.txt"]
sha256 = "7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a"

[license."LICENSE.sqlite.txt"]
sha256 = "38bef3d28b24f145ea293bd3b6eb4b20396982abc8303128fb493986ea5bc719"

[license."LICENSE.tcl.txt"]
sha256 = "c0a69a2bfd757361ec7e6143973b103c90409316b49e9c88db26ad6388e79f16"

[license."LICENSE.tix.txt"]
sha256 = "3ac5cdd0bef6c43ce34c6a7ced452081d9e5a0bf94082b9f9147d23ec9e214f5"

[license."LICENSE.zlib-ng.txt"]
sha256 = "6c9f0d975b41afaa34d22f55bb8986ce69e5cb7ad327cb2b28820cd425edf5ee"

[license."LICENSE.zlib.txt"]
sha256 = "818922b2620f12801a12bf78e399644a30990e66824abd8ca8ec24d451d6f92c"

[license."LICENSE.zstd.txt"]
sha256 = "7055266497633c9025b777c78eb7235af13922117480ed5c674677adc381c9d8"
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ pyscriptc = [
"_typeshed/LICENSE",
"_typeshed/stdlib/VERSIONS",
"_typeshed/stdlib/**/*.pyi",
"pbs_licenses/LICENSE.*",
"pbs_licenses/NOTICE",
]

[tool.pytest.ini_options]
Expand Down
38 changes: 29 additions & 9 deletions scripts/gen_third_party_notices.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
LOCK = ROOT / "packaging" / "runtime.lock.toml"
INVENTORY = ROOT / "packaging" / "third-party.toml"
NOTICE = ROOT / "NOTICE"
# The packaged copy: a bundle output stages it from the installed package, so
# the notice travels without a repository checkout (issue #709).
PACKAGED_NOTICE = ROOT / "src" / "pyscriptc" / "pbs_licenses" / "NOTICE"

HEADER = """\
NOTICE
Expand All @@ -59,10 +62,13 @@
------------------

The complete license text of CPython and of every component above ships in
the `licenses/` directory of the runtime pack and is copied into
`OUT/share/licenses/` of every output directory built with
`--embed-python=bundle`. This file names the components; the pack carries
the texts. This file is generated by scripts/gen_third_party_notices.py;
the `share/licenses/` directory of every output built with
`--embed-python=bundle`: the CPython text is copied from the bundled runtime
pack, the third-party texts are vendored in the compiler's package data
(`pyscriptc/pbs_licenses/`, refreshed from the python-build-standalone
`full` archive by scripts/sync_pbs_licenses.py), and this notice is copied
next to them. This file names the components; the texts travel with the
output. This file is generated by scripts/gen_third_party_notices.py;
edit the inputs, not the output.
"""

Expand Down Expand Up @@ -113,17 +119,31 @@ def main(argv: list[str] | None = None) -> int:
has_lock = LOCK.exists()
text = render(LOCK if has_lock else None, INVENTORY)
if args.check:
current = NOTICE.read_text(encoding="utf-8") if NOTICE.exists() else ""
if current != text:
stale = []
for target in (NOTICE, PACKAGED_NOTICE):
current = target.read_text(encoding="utf-8") if target.exists() else ""
if current != text:
stale.append(target)
if stale:
names = []
for target in stale:
try:
names.append(str(target.relative_to(ROOT)))
except AttributeError, ValueError, TypeError:
names.append(str(target))
print(
"NOTICE is stale: run `uv run python scripts/gen_third_party_notices.py`",
f"NOTICE is stale: run `uv run python scripts/gen_third_party_notices.py` ({', '.join(names)})",
file=sys.stderr,
)
return 1
print("NOTICE is up to date.")
return 0
NOTICE.write_text(text, encoding="utf-8")
print(f"wrote {NOTICE.relative_to(ROOT)}")
for target in (NOTICE, PACKAGED_NOTICE):
target.write_text(text, encoding="utf-8")
try:
print(f"wrote {target.relative_to(ROOT)}")
except AttributeError, ValueError, TypeError:
print(f"wrote {target}")
return 0


Expand Down
Loading
Loading