Make requirements.lock universal, so Windows can install it at all

The Windows entry of the CI build job has never been able to install from
this lockfile, and no arrangement of pip-compile can fix that.

The lockfile has to serve Linux, macOS and Windows across Python 3.10-3.12.
pip-compile resolves for the one environment it runs on and emits no markers,
and two dependencies are environment-conditional: aiohttp needs async-timeout
only below 3.11, and portalocker needs pywin32 only on Windows. So every
possible pip-compile output is wrong somewhere. A Linux/3.11 lockfile killed
Windows with "must have their versions pinned" for pywin32; a Windows/3.10
one could never match the diff on the other platforms. Both failure modes
turned up in this session, one after the other, as each fix exposed the next.

`uv pip compile --universal` resolves across all target environments and
emits the markers — `pywin32 ; sys_platform == 'win32'`,
`async-timeout ; python_full_version < '3.11'` — pinned and hashed, so one
file covers everything.

This changes the lock GENERATOR from pip-tools to uv, which is a real tooling
decision and worth saying plainly. What it does not change is the consumer:
the output is an ordinary pip requirements file, and every build job still
installs it with plain `pip install --require-hashes -r requirements.lock`.
Nothing depends on uv at install time, so reverting means regenerating the
file, not unpicking anything. uv is pinned to 0.10.4 so the regenerate-and-
diff stays deterministic.

Verified locally: regenerating produces a byte-identical file, and it
installs under --require-hashes on both 3.10 and 3.11.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
This commit is contained in:
thePR0M3TH3ANandClaude Opus 5 committed 2026-08-20 09:43:23 -04:00
1 parent c4c1e1dff9
commit 4503b7eee4
2 files changed
+81 -32

No files matched your search

+24 -18
View File
@@ -10,36 +10,42 @@ on:
jobs:
# Regenerating the lockfile and diffing it proves the committed file really
# came from src/requirements.txt. It has to happen on ONE interpreter, and
# specifically on LOCK_PYTHON below, so it cannot ride the build matrix.
# came from src/requirements.txt. That is one question about one artifact,
# so it is asked once here rather than once per matrix entry.
#
# pip-compile resolves version-conditional dependencies for the interpreter
# it runs on, and the result carries no markers. Python 3.10 needs
# async-timeout (aiohttp requires it below 3.11) and 3.11 does not, so the
# two resolutions are genuinely different files -- 135 lines apart,
# argon2-cffi-bindings among them. One committed lockfile cannot equal both.
# WHY uv AND NOT pip-compile
#
# Compiling on the LOWEST supported version is what makes a single lockfile
# work everywhere: 3.10's resolution is a superset, and it installs cleanly
# under --require-hashes on 3.11. The reverse does not -- a 3.11 lockfile
# is missing async-timeout, so on 3.10 pip tries to fetch it unpinned and
# --require-hashes refuses. Both halves of that were failing here: the diff
# could never match on one platform, and the install could never succeed on
# the other.
# This lockfile has to serve Linux, macOS and Windows across Python
# 3.10-3.12, and pip-compile resolves for the single environment it runs on
# and emits no markers. Two dependencies make that impossible: aiohttp
# needs async-timeout only below 3.11, and portalocker needs pywin32 only
# on Windows. So every pip-compile output was wrong somewhere -- a
# Linux/3.11 lockfile made the Windows job fail with "must have their
# versions pinned" for pywin32, and a Windows/3.10 one could never match
# the diff elsewhere. The Windows entry of this job had never been able to
# install from this file.
#
# `uv pip compile --universal` resolves across all of them and emits the
# markers, so ONE lockfile covers every target. The output is still an
# ordinary pip requirements file: nothing downstream changes, the build
# jobs still install it with plain `pip install --require-hashes`. Only the
# generator is different, and it is pinned so this diff stays deterministic.
lockfile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
# Must equal the minimum in pyproject's `python = ">=3.10,<3.13"`.
# Raising that floor means regenerating requirements.lock here.
python-version: "3.10"
- name: Verify the lockfile matches src/requirements.txt
run: |
python -m pip install --upgrade pip
pip install pip-tools
pip-compile --generate-hashes --output-file=requirements.lock src/requirements.txt
pip install uv==0.10.4
# --python-version must equal the minimum in pyproject's
# `python = ">=3.10,<3.13"`. Raising that floor means regenerating
# requirements.lock with the new value.
uv pip compile --universal --generate-hashes --python-version 3.10 \
-o requirements.lock src/requirements.txt
git diff --exit-code requirements.lock
secret-scan: