mkwheels
Build a reproducible, pinned Python wheels tarball for vendoring into a
SlackBuild (or any offline pip install). Generic over package + version, with
two source modes: PyPI packages and GitHub source releases.
Usage
mkwheels pypi --name PKG --ver VER [--epoch N]
mkwheels gh --repo OWNER/REPO --ver VER [--name PKG] [--tag TAG] [--epoch N]
Common flags:
--ver VER— version for the output filename. A leadingvis stripped.--epoch N— optionalSOURCE_DATE_EPOCH. Omitted → auto-derived (see each mode). Pass it to override.OUTPUTenv var overrides the output directory (default: current dir).
pypi mode
--name PKG— the PyPI package, downloaded at exactly--ver.- Epoch auto-derived from the PyPI release upload time.
gh mode
For packages not on PyPI (GitHub source, possibly with git dependencies). The
tagged source is downloaded and pip builds the project plus its whole
dependency tree (PyPI deps and any git+ deps) into wheels.
--repo OWNER/REPO— the GitHub repository.--name PKG— output name; defaults to the repo basename, lowercased.--tag TAG— git tag to fetch; a leadingvis stripped for naming, and the real ref is resolved by trying<tag>thenv<tag>. Defaults to--ver.--env KEY=VAL— extra env var for the source build (repeatable).- Epoch auto-derived from the GitHub release
published_at(the repo must publish a GitHub Release for the tag).
Because the source tarball has no .git, projects using dynamic versioning
would fail VCS detection at build time. gh mode therefore sets
POETRY_DYNAMIC_VERSIONING_BYPASS and SETUPTOOLS_SCM_PRETEND_VERSION to
--ver automatically (harmless when unused); --env overrides them and adds
anything else the build needs.
Outputs <name>-wheels-<ver>.tar.gz and requirements.txt (pinned + hashed).
Prints the md5sum and the resolved epoch. The requirements.txt is an audit
record of the resolved versions, not the install input: the SlackBuild installs
straight from the wheel files (--find-links), it does not re-resolve the
lockfile.
Requirements
bash, python3 + pip, jq, curl, tar, gzip, md5sum.
Reproducibility
PyPI releases are immutable, so the wheel set for a fixed version is
deterministic. The tarball normalizes tar metadata (sorted entries, fixed
mtime/owner, gzip -n) so it is byte-identical for the same inputs + epoch.
In gh mode the project (and any source-only deps) are built from source.
With a fixed epoch this is byte-identical on the same machine, which is what
vendoring needs: build once, upload, pin the md5. Wheels containing compiled
extensions may differ across machines/toolchains, so build the vendored tarball
on the target platform.
Git-sourced dependencies (packages whose upstream pins a git URL) are frozen at
build time: pip resolves whatever is current, and the emitted requirements.txt
records the exact resolved versions. Once built, the tarball is the source of
truth.
SBo integration
Run mkwheels, upload the tarball to your package host, and set
DOWNLOAD_x86_64 / MD5SUM_x86_64 in the SlackBuild .info to point at it.
The SlackBuild then pip install --no-index --find-links=<wheels> into a venv.
Test
./selftest builds twice with a fixed epoch in both modes (pypi six,
gh pyparsing) and asserts each pair of wheels tarballs is byte-identical. Run
it after changing the tar/packing or mode-resolution logic.
License
GPLv2 (v2-only). See LICENSE. Copyright (C) 2026 Danilo M. danix@danix.xyz.
