packaging: stop exact-pinning build backends

Exact setuptools/wheel pins block platform-specific backend patches and
make isolated pip installs fail on Android/Termux Python. Keep a setuptools
lower bound only, drop the unused wheel backend pin, keep requirements.txt
aligned with core package deps, and install the Spaces extra in Docker
instead of pulling bitsandbytes by default.

Android/Termux is still not a supported runtime; the new platform note
documents host wheels, ANDROID_API_LEVEL, and --no-build-isolation so
pip does not isolate a second setuptools or compile manylinux-only
extensions.
This commit is contained in:
Brian
2026-09-20 06:25:34 -07:00
parent 205d28a113
commit 73601bb13d
8 changed files with 114 additions and 7 deletions
+5 -3
View File
@@ -10,15 +10,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
WORKDIR /app
# Install Python deps first (cache layer)
# Install Python deps first (cache layer). requirements.txt is the core
# package graph; the Spaces extra adds Gradio for app.py without pulling
# the quantization extra.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy project
COPY . .
# Install the package itself (for obliteratus imports)
RUN pip install --no-cache-dir .
# Install the package itself (for obliteratus imports) plus the UI extra.
RUN pip install --no-cache-dir ".[spaces]"
# Run as non-root user for security
RUN useradd -m appuser
+3 -1
View File
@@ -170,7 +170,9 @@ and rollback requirements.
Install `.[spaces,quantization]` instead when the UI must load supported
bitsandbytes 8-bit or 4-bit models. Jetson users must follow the dedicated
[Jetson bootstrap](docs/platforms/jetson.md); its bitsandbytes path is not yet
supported.
supported. Android/Termux is not a supported runtime; if you install there
anyway, follow the [Android install notes](docs/platforms/android.md) so pip
does not isolate a second setuptools or replace host wheels.
### 3. Google Colab (free GPU)
+3
View File
@@ -20,6 +20,9 @@
"scripts/select_pr_tests.py",
"scripts/setup_jetson.py",
"pyproject.toml",
"requirements.txt",
"Dockerfile",
"docs/platforms/**",
"uv.lock",
"tests/fixtures/distributed_checkpoints/**"
],
+56
View File
@@ -0,0 +1,56 @@
# Android / Termux install notes
Android is not a supported OBLITERATUS runtime. These notes exist so a
Termux (or other Android Python) install does not fight host wheels or
download a second setuptools behind PEP 517 isolation.
`sys.platform` on current Android CPython is `android`, not `linux`. The
locked CPU PyTorch index in `pyproject.toml` is intentionally limited to
`linux` and `win32` manylinux/Windows wheels. Do not point Android at that
index.
## Layout
1. Install the platform's own `python`, `setuptools`, and scientific
wheels first (`torch`, `numpy`, and whatever Termux already packages).
Those builds carry the Android patches that PyPI wheels do not.
`safetensors` and `tokenizers` are Rust extensions; rustup does not
host `aarch64-linux-android`, so install Termux `rust` before pip
and export the API level Python was built against (24 on current
Termux):
```bash
pkg install rust
export ANDROID_API_LEVEL=24
```
2. Isolated pip builds of meson packages (pandas, matplotlib) will try
to compile `cmake`/`ninja` from source and can hang under proot.
Install those binaries from Termux first (`pkg install ninja cmake
python-matplotlib python-scipy python-pillow python-psutil
python-pyarrow python-greenlet python-brotli python-cryptography
python-rpds-py python-msgpack libraqm harfbuzz`). PyPI `psutil`
sdists reject `sys.platform == "android"`; the Termux package is
patched. Termux matplotlib needs `libraqm`. PyPI `tokenizers` abi3
wheels fail on CPython 3.14 (`PyBaseObject_Type`); rebuild without
the `abi3` cargo feature so the extension is `cp314` instead of
`abi3`. Then install OBLITERATUS without build isolation:
```bash
python -m pip install --no-build-isolation -e ".[spaces]"
```
3. If pip still tries to compile `torch` or `numpy` from source, install
the project over the host packages instead:
```bash
python -m pip install --no-build-isolation --no-deps -e .
python -m pip install --no-build-isolation "gradio>=6.7,<7.0"
```
Do not use `uv sync --locked` on Android. The lock resolves Linux/Windows
CPU PyTorch and does not contain `android_*` wheels for torch, numpy,
pyarrow, tokenizers, or bitsandbytes.
The `quantization` and `qwen-hybrid` extras are CUDA-oriented and are not
an Android install path.
+3 -1
View File
@@ -1,5 +1,7 @@
[build-system]
requires = ["setuptools==80.9.0", "wheel==0.45.1"]
# Lower bound only. Exact backend pins block platform-specific setuptools patches.
# A separate wheel package is not required for the PEP 517 backend.
requires = ["setuptools>=80.9.0"]
build-backend = "setuptools.build_meta"
[project]
+2 -2
View File
@@ -1,4 +1,5 @@
gradio>=6.7,<7.0
# Mirrors [project].dependencies in pyproject.toml.
# Optional extras (spaces, quantization, qwen-hybrid, dev) are not included.
torch>=2.0
transformers>=4.40
datasets>=2.14
@@ -13,4 +14,3 @@ numpy>=1.24
scikit-learn>=1.3
tqdm>=4.64
optuna>=4.9,<5
bitsandbytes>=0.46.1
+22
View File
@@ -225,6 +225,28 @@ def test_ci_evidence_names_the_pull_request_head_not_the_synthetic_merge_commit(
assert '--head-sha "$GITHUB_SHA"' not in workflow
def test_build_backend_requirements_use_lower_bounds_not_exact_pins():
pyproject = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
match = re.search(
r"(?ms)^\[build-system\]\n(?:#[^\n]*\n)*requires = \[([^\]]+)\]",
pyproject,
)
assert match is not None
requires = re.findall(r'"([^"]+)"', match.group(1))
assert requires
for requirement in requires:
spec = requirement.split(";", 1)[0]
name = re.split(r"[<>=!~]", spec, maxsplit=1)[0].strip()
assert name != "wheel", requirement
assert "==" not in spec, requirement
assert ">=" in spec, requirement
lower = re.findall(r">=([^,]+)", spec)
upper = re.findall(r"<=([^,]+)", spec)
assert not (
lower and upper and lower[0] == upper[0]
), f"collapsed exact pin written as a range: {requirement}"
def test_ci_runs_checkpoint_portability_contracts_on_windows():
workflow = WORKFLOW.read_text(encoding="utf-8")
checkpoint_job = workflow.split(" checkpoint-windows:\n", maxsplit=1)[1].split(
+20
View File
@@ -217,6 +217,26 @@ def test_bitsandbytes_is_opt_in_for_quantization_only():
assert extras["quantization"] == ["bitsandbytes>=0.46.1"]
def test_requirements_txt_mirrors_core_pyproject_dependencies():
metadata = tomllib.loads((ROOT / "pyproject.toml").read_text())
requirements = [
line.strip()
for line in (ROOT / "requirements.txt").read_text().splitlines()
if line.strip() and not line.startswith("#")
]
assert requirements == metadata["project"]["dependencies"]
assert not any(value.startswith("bitsandbytes") for value in requirements)
assert not any(value.startswith("gradio") for value in requirements)
def test_dockerfile_installs_spaces_extra_not_quantization():
dockerfile = (ROOT / "Dockerfile").read_text()
assert 'pip install --no-cache-dir ".[spaces]"' in dockerfile
assert "bitsandbytes" not in dockerfile
def test_jetson_issue_form_requires_reproducible_sanitized_hardware_evidence():
form = yaml.safe_load(
(ROOT / ".github" / "ISSUE_TEMPLATE" / "jetson-runtime.yml").read_text(),