From 70e816d908cf042aed58b906bc28e82827428138 Mon Sep 17 00:00:00 2001 From: namingbe Date: Sat, 13 Jun 2026 14:37:21 +0200 Subject: [PATCH 1/6] Build honest wheels: gate Rust ext on NSV_BUILD_RUST, exclude tests 0.2.3 shipped a cp314-tagged platform wheel with no _rust binary inside: the try/except on setuptools_rust silently zeroed the extension list when the package was absent from the build shell, and optional=True swallowed compile failures (PyO3 0.20 couldn't target 3.14 anyway). The result installed only on the exact arch it was mistagged for and ran the Python fallback regardless. Decouple tag from build accident: - Gate the Rust extension on NSV_BUILD_RUST=1. Default build is pure and emits an honest py3-none-any wheel; the runtime try/except in core.py still falls back to Python, so end users never need Rust. - optional=False under the gate: a build that explicitly asked for Rust fails loudly instead of producing a mistagged pure wheel. - find_packages(exclude=tests*) so the test suite stops leaking into site-packages as a top-level package. - Drop zip_safe=False (forced Root-Is-Purelib: false) and the redundant setup_requires; remove setuptools-rust from unconditional build requires so the default PEP-517 build is pure. Verified on this machine: default -> py3-none-any (no binary, no tests); NSV_BUILD_RUST=1 -> cp314 wheel with nsv/_rust.so, Rust path active at runtime. Both backends agree on all 1093 nsv-tests enum fixtures and the 92MB champernowne fixed-point roundtrip. Co-Authored-By: Claude Opus 4.8 --- pyproject.toml | 7 ++++++- setup.py | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0de2d7c..f0008c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,8 @@ [build-system] -requires = ["setuptools>=61.0", "setuptools-rust>=1.0"] +# setuptools-rust is intentionally NOT listed here: the default build is +# pure-Python and produces a py3-none-any wheel. To build the Rust extension, +# install setuptools-rust into the build env and set NSV_BUILD_RUST=1, e.g. +# pip install build setuptools-rust +# NSV_BUILD_RUST=1 python -m build --wheel --no-isolation +requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index 37f3e9f..25db616 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,24 @@ +import os + from setuptools import setup, find_packages -try: +# Build the Rust extension only when explicitly requested. When unset, the build +# is pure-Python and bdist_wheel emits an honest py3-none-any wheel; nsv.core +# falls back to the Python implementation at import time. When set, a missing +# toolchain or an incompatible PyO3/interpreter pairing fails the build loudly +# (optional=False) instead of silently shipping a mistagged platform wheel with +# no binary inside. +build_rust = os.environ.get("NSV_BUILD_RUST", "0") == "1" +if build_rust: from setuptools_rust import Binding, RustExtension - rust_extensions = [RustExtension("nsv._rust", path="rust/Cargo.toml", binding=Binding.PyO3, optional=True)] -except ImportError: + rust_extensions = [RustExtension("nsv._rust", path="rust/Cargo.toml", binding=Binding.PyO3, optional=False)] +else: rust_extensions = [] setup( name="nsv", version="0.2.3", - packages=find_packages(), + packages=find_packages(exclude=["tests", "tests.*"]), description="Python implementation of the NSV (Newline-Separated Values) format", long_description=open("README.md").read(), long_description_content_type="text/markdown", @@ -45,6 +54,4 @@ "pandas": ["pandas"], }, rust_extensions=rust_extensions, - setup_requires=["setuptools-rust>=1.0"] if rust_extensions else [], - zip_safe=False, ) From 32e1bd414145463758570bb1f0c798fee67a95c7 Mon Sep 17 00:00:00 2001 From: namingbe Date: Sat, 13 Jun 2026 14:49:24 +0200 Subject: [PATCH 2/6] cut the bs --- pyproject.toml | 7 +------ setup.py | 6 ------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f0008c0..0de2d7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,3 @@ [build-system] -# setuptools-rust is intentionally NOT listed here: the default build is -# pure-Python and produces a py3-none-any wheel. To build the Rust extension, -# install setuptools-rust into the build env and set NSV_BUILD_RUST=1, e.g. -# pip install build setuptools-rust -# NSV_BUILD_RUST=1 python -m build --wheel --no-isolation -requires = ["setuptools>=61.0"] +requires = ["setuptools>=61.0", "setuptools-rust>=1.0"] build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index 25db616..a638b9d 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,6 @@ from setuptools import setup, find_packages -# Build the Rust extension only when explicitly requested. When unset, the build -# is pure-Python and bdist_wheel emits an honest py3-none-any wheel; nsv.core -# falls back to the Python implementation at import time. When set, a missing -# toolchain or an incompatible PyO3/interpreter pairing fails the build loudly -# (optional=False) instead of silently shipping a mistagged platform wheel with -# no binary inside. build_rust = os.environ.get("NSV_BUILD_RUST", "0") == "1" if build_rust: from setuptools_rust import Binding, RustExtension From e514a05f656b2994326c4aeadeaf7e3a8ba2d47e Mon Sep 17 00:00:00 2001 From: namingbe Date: Sat, 13 Jun 2026 15:39:44 +0200 Subject: [PATCH 3/6] attempt wiring wheel builds --- .github/workflows/ci.yml | 69 +++++++++++++++++++++++++++++++++++++ .github/workflows/tests.yml | 28 --------------- pyproject.toml | 8 +++++ rust/Cargo.toml | 2 +- setup.py | 6 +++- 5 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..20b658c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +name: CI + +on: + push: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package + run: | + python -m pip install --upgrade pip + pip install -e ".[pandas]" + + - name: Run tests + run: python -m unittest discover -s tests -p 'test*.py' -v + + wheels: + needs: test + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + - os: ubuntu-24.04-arm + - os: macos-13 + - os: macos-14 + - os: windows-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + # Linux installs Rust in-container via cibuildwheel before-all. + - uses: dtolnay/rust-toolchain@stable + if: runner.os != 'Linux' + + - name: Build wheels + uses: pypa/cibuildwheel@v2.21 + + - uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: wheelhouse/*.whl + + sdist: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 92c2c38..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Tests - -on: - push: - workflow_dispatch: - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install package - run: | - python -m pip install --upgrade pip - pip install -e ".[pandas]" - - - name: Run tests - run: python -m unittest discover -s tests -p 'test*.py' -v diff --git a/pyproject.toml b/pyproject.toml index 0de2d7c..eabc8e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,11 @@ [build-system] requires = ["setuptools>=61.0", "setuptools-rust>=1.0"] build-backend = "setuptools.build_meta" + +[tool.cibuildwheel] +build = "cp312-*" +environment = { NSV_BUILD_RUST = "1" } + +[tool.cibuildwheel.linux] +before-all = "curl -sSf https://sh.rustup.rs | sh -s -- -y" +environment = { NSV_BUILD_RUST = "1", PATH = "$HOME/.cargo/bin:$PATH" } diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 328b24a..d4efb96 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,5 +8,5 @@ name = "_rust" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.29", features = ["extension-module"] } +pyo3 = { version = "0.29", features = ["extension-module", "abi3-py38"] } nsv = "0.0.12" diff --git a/setup.py b/setup.py index a638b9d..91c2aae 100644 --- a/setup.py +++ b/setup.py @@ -5,9 +5,12 @@ build_rust = os.environ.get("NSV_BUILD_RUST", "0") == "1" if build_rust: from setuptools_rust import Binding, RustExtension - rust_extensions = [RustExtension("nsv._rust", path="rust/Cargo.toml", binding=Binding.PyO3, optional=False)] + rust_extensions = [RustExtension("nsv._rust", path="rust/Cargo.toml", binding=Binding.PyO3, optional=False, py_limited_api=True)] + # abi3-py38: one wheel per platform, loads on CPython 3.8+ + options = {"bdist_wheel": {"py_limited_api": "cp38"}} else: rust_extensions = [] + options = {} setup( name="nsv", @@ -48,4 +51,5 @@ "pandas": ["pandas"], }, rust_extensions=rust_extensions, + options=options, ) From 8c760dd807028556b0231e0e39213c46f7b15912 Mon Sep 17 00:00:00 2001 From: namingbe Date: Sat, 13 Jun 2026 15:56:14 +0200 Subject: [PATCH 4/6] CI: drop i686 wheels, pin macOS 15 for both arches - archs=auto64: the 32-bit i686 manylinux build failed (rustup-init can't load libatomic.so.1 in that container) and isn't a target we ship. - macos-15 (arm64) + macos-15-intel (x86_64): macos-13 is retired and never schedules; keep both arches on one OS version. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 4 ++-- pyproject.toml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20b658c..2e497b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,8 +35,8 @@ jobs: include: - os: ubuntu-latest - os: ubuntu-24.04-arm - - os: macos-13 - - os: macos-14 + - os: macos-15-intel + - os: macos-15 - os: windows-latest runs-on: ${{ matrix.os }} steps: diff --git a/pyproject.toml b/pyproject.toml index eabc8e6..3ed04a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,5 +7,6 @@ build = "cp312-*" environment = { NSV_BUILD_RUST = "1" } [tool.cibuildwheel.linux] +archs = ["auto64"] before-all = "curl -sSf https://sh.rustup.rs | sh -s -- -y" environment = { NSV_BUILD_RUST = "1", PATH = "$HOME/.cargo/bin:$PATH" } From 3c6e1404969631aa994adf6c0763fa2d6619e5bc Mon Sep 17 00:00:00 2001 From: namingbe Date: Sat, 13 Jun 2026 16:29:02 +0200 Subject: [PATCH 5/6] deslop --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 91c2aae..c928281 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,6 @@ if build_rust: from setuptools_rust import Binding, RustExtension rust_extensions = [RustExtension("nsv._rust", path="rust/Cargo.toml", binding=Binding.PyO3, optional=False, py_limited_api=True)] - # abi3-py38: one wheel per platform, loads on CPython 3.8+ options = {"bdist_wheel": {"py_limited_api": "cp38"}} else: rust_extensions = [] From d3a0aad690f9c4893946bd71fc8c6ccdfab178ea Mon Sep 17 00:00:00 2001 From: namingbe Date: Sat, 13 Jun 2026 16:34:56 +0200 Subject: [PATCH 6/6] bruh --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e497b6..7e17eb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,10 +42,6 @@ jobs: steps: - uses: actions/checkout@v4 - # Linux installs Rust in-container via cibuildwheel before-all. - - uses: dtolnay/rust-toolchain@stable - if: runner.os != 'Linux' - - name: Build wheels uses: pypa/cibuildwheel@v2.21