From 0841f340a4b0208a69668e66b34b47f0dec8a07b Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Mon, 6 Jul 2026 23:02:24 -0700 Subject: [PATCH 1/3] Add regression test for install_cargo_config PermissionError path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install_cargo_config catches PermissionError from get_toolchain — that error surfaces on non-root installs where the process can't create DATA_DIR (e.g. salt-master running as the salt user against a /opt/saltstack/salt install). A prior refactor lost the `toolchain = None` initializer before the try/except, so the subsequent `if not toolchain:` check fired UnboundLocalError instead of degrading quietly. Commit d0df0af ("Fix missing toolchain variable") restored the initializer but landed without a test to keep future refactors honest. Add a stubbed test that forces get_toolchain to raise PermissionError and asserts install_cargo_config returns cleanly without writing a cargo config. Verified the test fails on the pre-d0df0af code with the exact error from the reporter's traceback (https://github.com/saltstack/relenv/issues/252#issuecomment-4478458740), and passes on current main. --- tests/test_runtime.py | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/test_runtime.py b/tests/test_runtime.py index 1fb3b86f..1f1ae572 100644 --- a/tests/test_runtime.py +++ b/tests/test_runtime.py @@ -175,6 +175,54 @@ def __init__(self, data: pathlib.Path) -> None: assert "x86_64-unknown-linux-gnu" in config_path.read_text() +def test_install_cargo_config_toolchain_permission_error( + tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + Regression for + https://github.com/saltstack/relenv/issues/252#issuecomment-4478458740. + + ``common().get_toolchain()`` calls ``os.makedirs(DATA_DIR)`` and can raise + ``PermissionError`` under a non-root process whose install dir isn't + writable. ``install_cargo_config`` catches the ``PermissionError`` but + then checks ``if not toolchain:``. If the local ``toolchain`` isn't + initialized before the ``try:``, the check fires ``UnboundLocalError: + local variable 'toolchain' referenced before assignment`` — surfacing as + a noisy traceback from ``site.addpackage`` on every interpreter start. + + ``install_cargo_config`` must degrade quietly (log via ``debug`` and + return) whether ``get_toolchain`` raises ``PermissionError`` or returns + ``None``. + """ + monkeypatch.setattr(relenv.runtime.sys, "platform", "linux") + data_dir = tmp_path / "data" + data_dir.mkdir() + + class StubDirs: + def __init__(self, data: pathlib.Path) -> None: + self.data = data + + stub_dirs = StubDirs(data_dir) + + def _raise_permission_error() -> pathlib.Path: + raise PermissionError("[Errno 13] Permission denied: '/opt/example/.local'") + + stub_common = SimpleNamespace( + DATA_DIR=tmp_path, + work_dirs=lambda: stub_dirs, + get_triplet=lambda: "x86_64-linux-gnu", + get_toolchain=_raise_permission_error, + ) + monkeypatch.setattr(relenv.runtime, "common", lambda: stub_common) + + # Must not raise UnboundLocalError (or anything else). + relenv.runtime.install_cargo_config() + + # And must not have written a cargo config, since there's no toolchain + # to point it at. + assert not (data_dir / "cargo" / "config.toml").exists() + + def test_build_shebang_value_error(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(relenv.runtime.sys, "RELENV", pathlib.Path("/rel"), raising=False) monkeypatch.setattr( From 9b313534b6e7f988de268b641d911a4657f1c489 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Tue, 7 Jul 2026 23:49:54 -0700 Subject: [PATCH 2/3] Skip pyzmq 26.2.0 on Linux until upstream pin lands pyzmq 26.2.0's pyproject.toml uses `cmake.targets = ["pyzmq"]`, which scikit-build-core renamed to `build.targets` starting in 1.0.0 (released 2026-07-06). pyzmq 26.2.0 leaves scikit-build-core unpinned in build-system.requires, so `pip install pyzmq==26.2.0` on any host that resolves scikit-build-core >= 1.0.0 aborts at "Getting requirements to build wheel" with: ERROR: Use build.targets instead of cmake.targets for scikit-build-core >= 0.10 That break landed on 2026-07-06 and broke every "Verify Linux" matrix entry on the next CI run (2026-07-07). macOS and Windows already xfail this parametrization for unrelated pre-existing reasons, which is why only the Linux jobs turned red. pyzmq 26.4.0 already uses `build.targets`, so leaving the other three parametrizations green preserves the coverage the test was providing. Match the shape of the surrounding xfail block and pin the reason to the upstream cause so a follow-up bump (pyzmq >= 26.4 in the parametrize list) can drop this guard cleanly. --- tests/test_verify_build.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_verify_build.py b/tests/test_verify_build.py index 76d73766..1c677b69 100644 --- a/tests/test_verify_build.py +++ b/tests/test_verify_build.py @@ -468,6 +468,17 @@ def test_pip_install_pyzmq( if pyzmq_version == "26.2.0" and sys.platform == "win32": pytest.xfail("vcredist not found as of 9/9/24") + if pyzmq_version == "26.2.0" and sys.platform == "linux": + # pyzmq 26.2.0's pyproject.toml uses scikit-build-core's + # `cmake.targets = ["pyzmq"]` option, which was renamed to + # `build.targets` in scikit-build-core 1.0.0 (released 2026-07-06). + # pyzmq 26.2.0 leaves scikit-build-core unpinned in build-system.requires, + # so pip picks up the newest release and the build errors out with: + # ERROR: Use build.targets instead of cmake.targets for + # scikit-build-core >= 0.10 + # Fixed upstream in pyzmq 26.4.0 (which uses build.targets). + pytest.xfail("pyzmq 26.2.0 incompatible with scikit-build-core >= 1.0") + if pyzmq_version == "26.4.0" and sys.platform == "win32": pytest.xfail("Needs troubleshooting 4/12/25") From 6efeeab559d6985ae6900e35c0f59357004bdf50 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Wed, 15 Jul 2026 14:53:55 -0700 Subject: [PATCH 3/3] Set HOST_PYTHON so CPython 3.10 build skips nuget path CPython 3.10's PCbuild\find_python.bat only probes py -3.9 / py -3.8 before falling back to `nuget install pythonx86`. actions/setup-python installs 3.11, which the py-launcher loop misses, so 3.10 always takes the nuget path. That nuget invocation currently fails on windows-2022 ("The system cannot execute the specified program"), breaking every Windows 3.10 job since ~2026-07-07. find_python.bat accepts HOST_PYTHON (any Python >= 3.8) before the py-launcher / nuget probes, so pointing it at the setup-python interpreter sidesteps the broken path entirely. 3.11+ probe newer py versions and are unaffected; setting HOST_PYTHON for them is a no-op. --- .github/workflows/build-native-action.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/build-native-action.yml b/.github/workflows/build-native-action.yml index 16eb6579..a4ec2bcb 100644 --- a/.github/workflows/build-native-action.yml +++ b/.github/workflows/build-native-action.yml @@ -307,6 +307,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python 3.11 + id: setup-python uses: actions/setup-python@v5 with: python-version: '3.11' @@ -339,6 +340,16 @@ jobs: # SSE intrinsics in that break C compiles under the # v140 toolset Python 3.10/3.11 PCbuild targets. WindowsTargetPlatformVersion: "10.0.19041.0" + # CPython 3.10's PCbuild\find_python.bat only probes `py -3.9` + # / `py -3.8` before falling back to a nuget-based install of + # pythonx86. That nuget path is currently broken on + # windows-2022 ("The system cannot execute the specified + # program" from `nuget install pythonx86`). Point HOST_PYTHON + # at the setup-python interpreter so find_python.bat accepts + # it directly (checked before the py-launcher / nuget path) + # and never invokes nuget. 3.11+ probe newer py versions and + # are unaffected, but setting HOST_PYTHON for them is a no-op. + HOST_PYTHON: ${{ steps.setup-python.outputs.python-path }} run: | python -m relenv build --no-pretty --arch=${{ matrix.arch }} --python=${{ steps.python-version.outputs.version }}