Skip to content

feat(embed): trimmed stdlib zip and bundled pure-Python dependencies (#659, #660) - #716

Open
espetro wants to merge 3 commits into
masterfrom
work/emb659
Open

espetro wants to merge 3 commits into
masterfrom
work/emb659

Conversation

@espetro

@espetro espetro commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Closes #659
Closes #660

#659: the bundled stdlib is a stored zip of .pyc (lib/python314.zip) plus a real lib-dynload directory. The default --embed-stdlib=safe drops only a documented deny list (test, idlelib, tkinter, turtledemo, ensurepip, pydoc_data, __phello__). --embed-stdlib=minimal (or [tool.pyscriptc.island] stdlib) keeps the import closure of the island bootstrap plus an allowlist. Before staging, every stdlib import of the program is checked against the kept modules and a miss refuses with PS4301.

#660: a bundle compile copies the distributions behind the program's third-party import roots into lib/pythonX.Y/site-packages/ as a transitive closure over Requires-Dist (extras skipped, markers honored). A distribution carrying a compiled extension refuses the compile with PS4302 before anything is staged. Uninstalled roots warn and are skipped.

Tests: tests/test_stdlibzip.py, tests/test_sitedeps.py and tests/test_embed.py pass locally with the census repinned for the two new modules. Verified on Linux x86_64 by the author session; macOS arm64 runs in CI. Not covered here: license staging for bundled packages (#709).

Agent and others added 3 commits October 26, 2026 15:45
…inimal trim

Closes the #659 half of the embedding slice (on top of the #657 runtime pack).

- src/pyscriptc/stdlibzip.py: the bundled stdlib is a stored zip of .pyc
  bodies written at stage time, not the 10 MB unpacked tree; a real
  lib/python3.14/lib-dynload/ directory travels with it because getpath
  prints a fatal-looking warning without it (PBS builds most extensions
  into libpython, so the directory is near-empty but must exist).
- safe trim (the default, --embed-stdlib=safe): everything except the
  documented deny list in SAFE_TRIM (test, idlelib, tkinter, turtledemo,
  ensurepip, pydoc_data, __phello__).
- minimal trim (opt-in, --embed-stdlib=minimal): a frozen closure over the
  3.14 import graph seeded from the island bootstrap needs plus an explicit
  allowlist, both in-module tuples; seed packages travel whole (encodings
  pulls codecs and aliases through dynamic imports a static walk misses).
- manifest check fails closed: a bundle compile first checks every stdlib
  import of the program against the trim's kept modules and refuses with
  PS4301 when one is missing (verified: a program importing idlelib is
  refused instead of producing a broken bundle).
- --embed-stdlib flag and the [tool.pyscriptc.island] stdlib config key,
  resolved flag-over-config like embed.

Measured on this host (linux-x86_64, PBS 20261021, CPython 3.14.9), the
same island program (fractions+json) bundled before and after:
- before (unpacked safe tree, minus test dir the stripped asset omits):
  lib/python3.14 23,110,923 B; bundle total 57,305,095 B
- after safe trim zip: lib/python314.zip 16,824,059 B (6,502,454 B gzipped);
  bundle total 51,018,231 B
- after minimal trim zip: python314.zip 8,258,962 B; bundle total
  41,822,435 B (a datetime+json island program; runs in an empty env)
All three bundles run with env -i (exit 0).
…packages

Closes #660, stacked on the #659 stdlib zip work.

- src/pyscriptc/sitedeps.py: a --embed-python=bundle (or auto-fallback)
  compile copies the distributions behind the program's third-party import
  roots into the bundle's lib/pythonX.Y/site-packages/ tree. The copy set is
  the transitive closure over Requires-Dist (python-dateutil needs six and
  tzdata; verified end to end), with extra markers skipped and failing
  environment markers honored when packaging is importable.
- a dependency that ships a native extension is refused with PS4302 before
  anything is staged, never silently dropped: the check runs on the whole
  closure, so a native dependency-of-dependency refuses too. Verified with
  MarkupSafe 3.0.4 (cp314 manylinux wheel, _speedups .so): the compile exits
  1 with 'PS4302: the dependency MarkupSafe (import markupsafe) cannot be
  bundled'.
- a root no installed distribution provides is warned about on stderr and
  skipped, so a dead-branch import fails at run time exactly like CPython.
- src/pyscriptc/imports.py: _read_wheel_record now also reads an unpacked
  *.dist-info/RECORD (previously only wheel zips, so every installed
  extension wheel read as pure), distribution keys normalize case, spaces
  and a trailing version (markupsafe-3.0.4 -> markupsafe), and
  distribution_is_native accepts versioned names.

Measured on this host (linux-x86_64, PBS 20261021 pack, CPython 3.14.9),
same island program bundled before and after:
- program with no third-party imports: 51,018,231 B (unchanged; no
  site-packages tree is written)
- program importing dateutil: 52,287,313 B total, of which
  lib/python3.14/site-packages 1,269,050 B (dateutil + six + tzdata); runs
  with env -i and prints the parsed date (exit 0)
- program importing markupsafe (native): refused, exit 1, PS4302, no
  bundle directory written

Tests: tests/test_sitedeps.py (10) plus new record-reader cases in
tests/test_imports.py; targeted suite 144 passed, 1 pre-existing skip.
@espetro espetro added this to the v0.7.0 milestone Oct 26, 2026
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying pyscriptc with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9fe799e
Status:🚫  Build failed.

View logs

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

1 participant