From ae23101ac56b8621333b0dc89cbc89a771cca4b3 Mon Sep 17 00:00:00 2001 From: Divyam Talwar Date: Tue, 8 Sep 2026 20:26:12 +0530 Subject: [PATCH] fix: reset the cached compiler when build_ext is reinitialized MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `httptools_build_ext.initialize_options()` returns early once `_initialized` is set, deliberately preserving the Cython and extension mutations applied on the first pass. Setuptools reinitializes the `build_ext` command between phases, so a chained invocation such as `setup.py build bdist_wheel` reaches the wheel phase holding the compiler instance the earlier phase already configured, and the build fails instead of selecting a compiler for the current phase. Clear only the transient `compiler` attribute in that early-return path. Delegating to the base initializer instead would discard exactly the cached command options and extension state the early return exists to protect, so the reset is kept as narrow as the problem. `tests/test_build.py` loads the real `setup.py`, finalizes the command, attaches a compiler, reinitializes, and asserts the run proceeds — it fails without this change. Verified: - Focused regression — 1 passed - `python setup.py build bdist_wheel` — succeeds - `make test` — 42 passed - `make typecheck` — 0 errors, 0 warnings - `git diff --check` — clean Fixes #126 --- setup.py | 1 + tests/test_build.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/test_build.py diff --git a/setup.py b/setup.py index 3279165..c3b3960 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ def initialize_options(self): # same command object, so make sure not to override previously # set options. if getattr(self, '_initialized', False): + self.compiler = None return super().initialize_options() diff --git a/tests/test_build.py b/tests/test_build.py new file mode 100644 index 0000000..b49b2ef --- /dev/null +++ b/tests/test_build.py @@ -0,0 +1,29 @@ +import runpy +import unittest +from pathlib import Path +from unittest import mock + +from setuptools import Distribution, Extension +from setuptools._distutils.ccompiler import new_compiler + + +class TestBuildExt(unittest.TestCase): + def test_reinitialized_command_uses_fresh_compiler(self): + setup_py = Path(__file__).parents[1] / "setup.py" + with mock.patch("setuptools.setup") as setup: + runpy.run_path(str(setup_py)) + + build_ext = setup.call_args.kwargs["cmdclass"]["build_ext"] + distribution = Distribution({ + "cmdclass": {"build_ext": build_ext}, + "ext_modules": [Extension("test", ["test.c"])], + }) + command = distribution.get_command_obj("build_ext") + command.ensure_finalized() + command.compiler = new_compiler() + + distribution.reinitialize_command("build_ext") + command.build_extensions = mock.Mock() + command.run() + + command.build_extensions.assert_called_once_with()