Skip to content

Commit 639b2ee

Browse files
etrepumclaude
andauthored
Add Python 2.7 wheel builds for Windows platforms (#378)
* Build the pure-Python fallback wheel as universal (py2.py3-none-any) Fixes #377. Pre-4.0 simplejson installed cleanly from PyPI on offline Python 2.7 builds via a wheelhouse populated with `pip download`. 4.0+ ships a `py3-none-any.whl` alongside the sdist, so Python 2.7 wheelhouses contain only the sdist for the pure-Python path. Modern pip then runs a PEP 517 isolated build on that sdist and requires setuptools>=42 from the wheelhouse, which offline users usually do not seed. Add `--universal` to the `bdist_wheel` step in the build_sdist job so the wheel is tagged `py2.py3-none-any` and is usable on both interpreters. The sdist install path and the separate C-extension wheels produced by cibuildwheel are unchanged, and pyproject.toml / the test_pep517_build job stay in place for modern ecosystem tooling. https://claude.ai/code/session_01Sc7XDDu56uaU1xZEjJR1GY * Build cp27 Windows AMD64 wheels in build_wheels_py27 Expands build_wheels_py27 into a matrix that covers both Linux x86_64 (unchanged) and Windows AMD64. Published cp27 wheels previously only covered manylinux1 / manylinux2010 x86_64, so Py2.7-on-Windows users had no matching binary wheel on PyPI, pip fell through to the sdist, and the PEP 517 isolated build failed under --no-index on wheelhouses without setuptools>=42. This was the scenario in #377. Ships alongside the universal py2.py3-none-any fallback wheel from the previous commit, which still catches platforms this matrix does not cover (macOS, aarch64 Linux, ppc64le, etc.). Matrix notes: - cibuildwheel v1 looks at the arch env var for the runner platform it actually runs on and ignores the others, so setting both CIBW_ARCHS_LINUX and CIBW_ARCHS_WINDOWS is safe. - Artifact names are now wheels-py27-<os>-<arch> so the Linux and Windows uploads don't collide under upload-artifact@v7's unique- name requirement. upload_pypi / upload_pypi_test use download-artifact@v8 with merge-multiple: true, so the rename is transparent to release uploads. - Adds build_wheels_py27 to gate_windows.needs so Windows branch protection catches Py2.7 Windows build failures. Per AGENTS.md, Py2 builds cannot be reproduced locally - if Windows cp27 fails under cibuildwheel v1.12.0 on windows-latest (VC++ 9.0 toolchain surface), fail-fast: false keeps the Linux wheel green while we iterate. https://claude.ai/code/session_01Sc7XDDu56uaU1xZEjJR1GY * Also build cp27 Windows x86 (32-bit) wheels Adds a third entry to the build_wheels_py27 matrix covering windows-latest/x86, so 32-bit Py2.7 interpreters on Windows (still common in legacy corporate installs, where the default Py2.7 MSI was 32-bit for years) also get a pre-built wheel on PyPI. Switches the arch env from the platform-specific CIBW_ARCHS_{LINUX,WINDOWS} pair to the platform-agnostic CIBW_ARCHS, matching the pattern in build_wheels. Drops fail-fast: false - branch protection won't merge with a red matrix entry anyway, so letting siblings cancel early on a failure is the more useful default. https://claude.ai/code/session_01Sc7XDDu56uaU1xZEjJR1GY * Revert universal py2.py3-none-any wheel from build_sdist Reverts the --universal flag added in 8fc52d0. Retagging the fallback pure-Python wheel to py2.py3-none-any would be picked up by pip on any Py2.7 platform without a matching cp27 binary wheel (macOS, aarch64/ppc64le Linux). Those users currently build from the sdist and get the C extension; under the universal wheel they would silently get the pure-Python implementation instead - a measurable perf regression with no user-visible signal. The cp27 Windows AMD64 + x86 wheels added in ae97829 and 61a04d5 cover the original #377 reporter (Py2.7 on Windows). Py2.7 users on macOS or non-x86_64 Linux fall back to the sdist as they did before, which is loud-failure behavior (online: builds fine, gets speedups; offline: clear error) rather than silent slowdown. https://claude.ai/code/session_01Sc7XDDu56uaU1xZEjJR1GY * Pin cibuildwheel to v1.11.1 for the Py2.7 wheel job v1.12.0 removed cp27 from WINDOWS_PYTHONS, so the Windows matrix entries added in ae97829 / 61a04d5 fail at identifier selection ("cibuildwheel: No build identifiers selected: BuildSelector('cp27-*' - 'pp*')") before the build phase even starts. v1.11.1 is the last release that still carries cp27 identifiers for both Linux (manylinux1 Docker image) and Windows (NuGet python2 package), so one version pin serves all three matrix entries (linux/x86_64, windows/AMD64, windows/x86). Also updates the AGENTS.md cibuildwheel gotchas note, which was pinning readers to the broken v1.12.0, so the next person touching this job learns the v1.11.1 reasoning from the doc rather than rediscovering it from CI. https://claude.ai/code/session_01Sc7XDDu56uaU1xZEjJR1GY * Enable cp27 Windows wheels via DISTUTILS_USE_SDK + msvc-dev-cmd My earlier v1.11.1 pin (85aba3c) did not fix "No build identifiers selected" on Windows, which confirmed a reading of the cibuildwheel changelog: v1.11.0 did not *remove* cp27 from Windows, it gated it behind a custom-compiler flag. cibuildwheel/windows.py filters cp27 out of WINDOWS_PYTHONS during identifier selection unless both DISTUTILS_USE_SDK and MSSdk are present in the cibuildwheel process environment; Microsoft pulled the VC 9.0 ("Visual C++ Compiler for Python 2.7") download years ago, so cibuildwheel won't pretend it has a usable compiler by default. v2.0.0 is the release that dropped cp27 entirely, not v1.12. So the fix is not a version pin, it is environment: - Restore pypa/cibuildwheel@v1.12.0 (the latest v1 release). - Set DISTUTILS_USE_SDK=1 and MSSdk=1 at the step scope so cibuildwheel itself sees them during identifier selection, and mirror them via CIBW_ENVIRONMENT_WINDOWS so the per-wheel build subprocess forwards them to distutils. - Add an ilammy/msvc-dev-cmd@v1 activation step on Windows runners with arch matching the wheel (x64 for AMD64, x86 for x86), so distutils finds an MSVC toolchain via INCLUDE/LIB/PATH in place of the missing VC 9.0 installer. Also updates AGENTS.md to document the real gate (env vars, not version number) and adds a "do not chase a v1.11.x pin" warning so the next person looking at a "No build identifiers selected" failure reaches the right fix instead of the one I tried first. https://claude.ai/code/session_01Sc7XDDu56uaU1xZEjJR1GY * Scope cp27 Windows env vars to Windows runners only The DISTUTILS_USE_SDK / MSSdk / CIBW_ENVIRONMENT_WINDOWS triple added in e494f4f was set unconditionally across all three build_wheels_py27 matrix entries. That's functionally a no-op on the Linux cp27 entry (distutils on POSIX doesn't consult those vars, and cibuildwheel targeting Linux ignores CIBW_ENVIRONMENT_WINDOWS), but it reads like those env vars matter everywhere. Guard them with `runner.os == 'Windows'` so the Linux cp27 entry sees an empty env and the Windows- only intent is explicit at the call site. build_wheels (the modern Py3 job) is a separate job that doesn't touch any of this - its cibuildwheel@v3.4.1 step has no DISTUTILS_ USE_SDK / MSSdk / msvc-dev-cmd wiring and isn't affected. https://claude.ai/code/session_01Sc7XDDu56uaU1xZEjJR1GY * Release 4.1.1 Bumps VERSION in setup.py, conf.py, and simplejson/__init__.py to 4.1.1 and stamps the CHANGES.txt entry with today's date. The 4.1.1 release closes #377: offline / --no-index installs on Py2.7-on- Windows were failing at the PEP 517 isolated-build step because no cp27 win_amd64 / win32 wheel existed on PyPI and the sdist fallback required setuptools>=42 in the wheelhouse. build_wheels_py27 now builds those wheels directly via cibuildwheel v1.12.0 plus the DISTUTILS_USE_SDK / MSSdk gate and ilammy/msvc-dev-cmd. https://claude.ai/code/session_01Sc7XDDu56uaU1xZEjJR1GY * Restrict push trigger to main and release tags Dedupe CI: feature-branch pushes with an open PR were firing both the push and pull_request events on the same SHA, doubling the workflow runs (and the cibuildwheel minute burn). Scoping push to branches: [main] + tags: [v*, test-v*] leaves pull_request as the single trigger for development branches, while preserving the existing release flow - upload_pypi / upload_pypi_test gate on startsWith(github.event.ref, 'refs/tags/v') / 'refs/tags/test-v', and those still fire under the new push config. merge_group keeps the merge-queue gate jobs triggerable. Also drops the commented-out release-trigger example that was sitting above the old on: line; if we ever want that, the git history has it. https://claude.ai/code/session_01Sc7XDDu56uaU1xZEjJR1GY --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0fd3185 commit 639b2ee

6 files changed

Lines changed: 84 additions & 17 deletions

File tree

.github/workflows/build-and-deploy.yml

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
name: Build and upload to PyPI
22

3-
on: [push, pull_request, merge_group]
4-
# Alternatively, to publish when a (published) GitHub Release is created, use the following:
5-
# on:
6-
# push:
7-
# pull_request:
8-
# release:
9-
# types:
10-
# - published
3+
on:
4+
# Restrict the push trigger to main and release tags so feature-
5+
# branch pushes don't double up: a branch push with an open PR
6+
# would otherwise fire both the push and pull_request events on
7+
# the same SHA. Merges to main still run (push), releases still
8+
# run (push on tags, keying upload_pypi's refs/tags/v gate), PRs
9+
# still run (pull_request), and the merge queue still runs
10+
# (merge_group) - but only once per commit.
11+
push:
12+
branches: [main]
13+
tags: ['v*', 'test-v*']
14+
pull_request:
15+
merge_group:
1116

1217
jobs:
1318
test_pure_python:
@@ -204,24 +209,57 @@ jobs:
204209
path: tsan_report.txt
205210

206211
build_wheels_py27:
207-
name: Build Python 2.7 wheels (linux x86_64)
208-
runs-on: ubuntu-latest
212+
name: Build Python 2.7 wheels (${{ matrix.os }} ${{ matrix.arch }})
213+
runs-on: ${{ matrix.os }}
214+
strategy:
215+
matrix:
216+
include:
217+
- os: ubuntu-latest
218+
arch: x86_64
219+
- os: windows-latest
220+
arch: AMD64
221+
msvc_arch: x64
222+
- os: windows-latest
223+
arch: x86
224+
msvc_arch: x86
209225
steps:
210226
- uses: actions/checkout@v6
211227

228+
- name: Activate MSVC toolchain (Windows only)
229+
if: runner.os == 'Windows'
230+
uses: ilammy/msvc-dev-cmd@v1
231+
with:
232+
arch: ${{ matrix.msvc_arch }}
233+
212234
- name: Build Python 2.7 wheels
213235
uses: pypa/cibuildwheel@v1.12.0
214236
env:
215237
CIBW_TEST_COMMAND: >-
216238
python -m simplejson.tests._cibw_runner "{project}"
217239
CIBW_BUILD: "cp27-*"
218240
CIBW_SKIP: "pp*"
219-
CIBW_ARCHS_LINUX: x86_64
241+
CIBW_ARCHS: ${{ matrix.arch }}
242+
# Windows-only gate for cp27: cibuildwheel v1.11+ filters
243+
# cp27 out of WINDOWS_PYTHONS during identifier selection
244+
# unless both DISTUTILS_USE_SDK and MSSdk are set in its
245+
# own process environment (see cibuildwheel/windows.py).
246+
# Without them the job errors with "No build identifiers
247+
# selected" before the build phase starts. Mirrored via
248+
# CIBW_ENVIRONMENT_WINDOWS so the per-wheel build
249+
# subprocess forwards the vars to distutils, which then
250+
# uses the active MSVC toolchain (set up by
251+
# ilammy/msvc-dev-cmd above) instead of hunting for the
252+
# removed VS 2008 / VC 9.0 installer. Scoped to Windows
253+
# runners so stray Windows env vars don't leak onto the
254+
# Linux cp27 matrix entry.
255+
DISTUTILS_USE_SDK: ${{ runner.os == 'Windows' && '1' || '' }}
256+
MSSdk: ${{ runner.os == 'Windows' && '1' || '' }}
257+
CIBW_ENVIRONMENT_WINDOWS: ${{ runner.os == 'Windows' && 'DISTUTILS_USE_SDK=1 MSSdk=1' || '' }}
220258

221259
- uses: actions/upload-artifact@v7
222260
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/')
223261
with:
224-
name: wheels-py27
262+
name: wheels-py27-${{ matrix.os }}-${{ matrix.arch }}
225263
path: ./wheelhouse/*.whl
226264

227265
build_wheels:
@@ -373,7 +411,7 @@ jobs:
373411

374412
gate_windows:
375413
name: Build wheels on windows-latest
376-
needs: [build_wheels]
414+
needs: [build_wheels, build_wheels_py27]
377415
runs-on: ubuntu-latest
378416
steps:
379417
- run: echo "All windows-latest checks passed"

AGENTS.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,24 @@ Two cibuildwheel versions run in one job:
8787
that wasn't enabled.` Do not set it.
8888
- **`Build Python 2.7 wheels` step** uses `pypa/cibuildwheel@v1.12.0`.
8989
That version *does* build PyPy by default, so this step **must**
90-
keep `CIBW_SKIP: "pp*"`.
90+
keep `CIBW_SKIP: "pp*"`. v2.0.0 dropped cp27 entirely; v1.12.0 is
91+
the last release that still carries it. *Do not* chase a v1.11.x
92+
pin to "fix" cp27 Windows — the Windows gate is the env vars
93+
below, not the version number.
94+
- **cp27 on Windows** requires `DISTUTILS_USE_SDK=1` and `MSSdk=1`
95+
in cibuildwheel's own environment (not just
96+
`CIBW_ENVIRONMENT_WINDOWS`). cibuildwheel v1.11+ filters cp27 out
97+
of `WINDOWS_PYTHONS` during identifier selection when those env
98+
vars are unset — the symptom is the job erroring with
99+
`cibuildwheel: No build identifiers selected` *before* the build
100+
phase starts. The comment in `cibuildwheel/windows.py` explains:
101+
"Only supported with custom compiler, since MS removed the 2008
102+
compiler download." Set them at step scope *and* mirror via
103+
`CIBW_ENVIRONMENT_WINDOWS=DISTUTILS_USE_SDK=1 MSSdk=1` so the
104+
per-wheel build subprocess also sees them, then activate a modern
105+
MSVC in the job via `ilammy/msvc-dev-cmd@v1` with
106+
`arch: x64`/`arch: x86` matching the wheel arch — distutils will
107+
use that in place of the missing VC 9.0.
91108
- `CIBW_ENABLE: "cpython-freethreading"` is deprecated in v3.4+
92109
(free-threaded builds are on by default). Remove it.
93110

CHANGES.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
Version 4.1.1 released 2026-04-24
2+
3+
* The ``build_wheels_py27`` CI job now also builds Python 2.7 wheels
4+
for Windows AMD64 and Windows x86, joining the existing Py2.7
5+
manylinux1 / manylinux2010 x86_64 wheels. This unblocks offline /
6+
``--no-index`` installs on Py2.7-on-Windows (the original
7+
reporter's case), which previously had no matching binary wheel on
8+
PyPI, fell through to the sdist, and failed on the PEP 517
9+
isolated-build step complaining that ``setuptools>=42`` was not in
10+
the wheelhouse.
11+
https://github.com/simplejson/simplejson/issues/377
12+
113
Version 4.1.0 released 2026-04-22
214

315
* The C extension now accelerates encoding when ``indent=`` is set.

conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
# The short X.Y version.
4545
version = '4.1'
4646
# The full version, including alpha/beta/rc tags.
47-
release = '4.1.0'
47+
release = '4.1.1'
4848

4949
# There are two options for replacing |today|: either, you set today to some
5050
# non-false value, then it is used:

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
IS_PYPY = hasattr(sys, 'pypy_translation_info')
1414
IS_GRAALPY = getattr(getattr(sys, "implementation", None), "name", None) == "graalpy"
15-
VERSION = '4.1.0'
15+
VERSION = '4.1.1'
1616
DESCRIPTION = "Simple, fast, extensible JSON encoder/decoder for Python"
1717

1818
with open('README.rst', 'r') as f:

simplejson/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
119119
"""
120120
from __future__ import absolute_import
121-
__version__ = '4.1.0'
121+
__version__ = '4.1.1'
122122
__all__ = [
123123
'dump', 'dumps', 'load', 'loads',
124124
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',

0 commit comments

Comments
 (0)