From af98f882e00c4bfa621f2e70f4ca7a4f30c6c306 Mon Sep 17 00:00:00 2001 From: "LOPEZ, Ramon" Date: Mon, 23 Mar 2026 15:30:33 +0100 Subject: [PATCH 1/2] Avoid building pyrobuf extensions during metadata --- setup.py | 42 +++++++++++++++++++++++++++++------- tests/test_setup_metadata.py | 21 ++++++++++++++++++ 2 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 tests/test_setup_metadata.py diff --git a/setup.py b/setup.py index 56aadf6..1a27810 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ from distutils import log from setuptools import setup, find_packages, Distribution +from setuptools.command.build_ext import build_ext as _build_ext import os import os.path @@ -65,18 +66,35 @@ def run(self): class PyrobufDistribution(Distribution): - def run_commands(self): - # By now the setup_requires deps have been fetched. + def __init__(self, attrs=None): + super().__init__(attrs) + self._pyrobuf_builtins_added = False + + def has_ext_modules(self): + return True + + def ensure_pyrobuf_builtins(self): + if self._pyrobuf_builtins_added: + return + if not self.ext_modules: - self.ext_modules = list() - self.ext_modules.extend(self.pyrobufize_builtins()) - Distribution.run_commands(self) + self.ext_modules = [] + + builtin_ext_modules = self.pyrobufize_builtins() + for ext in builtin_ext_modules: + if not hasattr(ext, '_needs_stub'): + ext._needs_stub = False + + self.ext_modules.extend(builtin_ext_modules) + self._pyrobuf_builtins_added = True def pyrobufize_builtins(self): from jinja2 import Environment, PackageLoader from Cython.Build import cythonize env = Environment(loader=PackageLoader('pyrobuf.protobuf', 'templates')) + dry_run = getattr(self, 'dry_run', False) + templ_pyx = env.get_template('pyrobuf_list_pyx.tmpl') templ_pxd = env.get_template('pyrobuf_list_pxd.tmpl') @@ -106,7 +124,7 @@ def pyrobufize_builtins(self): # Even if PYROBUF_LIST_PYX and PYROBUF_LIST_PXD already exist they should be re-built because # we don't know whether they were built using *this* version of Python. path = os.path.join(HERE, 'pyrobuf', 'src', PYROBUF_LIST_PYX) - if not self.dry_run: + if not dry_run: with open(path, 'w') as fp: fp.write(templ_pyx.render( {'def': listdict, 'version_major': sys.version_info.major, 'format_map': format_map} @@ -115,7 +133,7 @@ def pyrobufize_builtins(self): log.info("rendering '%s' from '%s'" % (PYROBUF_LIST_PYX, templ_pyx.filename)) path = os.path.join(HERE, 'pyrobuf', 'src', PYROBUF_LIST_PXD) - if not self.dry_run: + if not dry_run: with open(path, 'w') as fp: fp.write(templ_pxd.render( {'def': listdict, 'version_major': sys.version_info.major, 'format_map': format_map} @@ -135,13 +153,21 @@ def pyrobufize_builtins(self): include_path=['pyrobuf/src']) +class build_ext(_build_ext): + + def run(self): + self.distribution.ensure_pyrobuf_builtins() + self.extensions = self.distribution.ext_modules + _build_ext.run(self) + + setup( distclass=PyrobufDistribution, name="pyrobuf", version=VERSION, packages=find_packages(), include_package_data=True, - cmdclass={'clean': clean}, + cmdclass={'build_ext': build_ext, 'clean': clean}, entry_points={ 'console_scripts': ['pyrobuf = pyrobuf.__main__:main'], 'distutils.setup_keywords': [ diff --git a/tests/test_setup_metadata.py b/tests/test_setup_metadata.py new file mode 100644 index 0000000..923e2d9 --- /dev/null +++ b/tests/test_setup_metadata.py @@ -0,0 +1,21 @@ +import os +import subprocess +import sys + + +def test_setup_dist_info_succeeds(tmp_path): + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + cmd = [sys.executable, 'setup.py', 'egg_info', '--egg-base', str(tmp_path)] + + proc = subprocess.run( + cmd, + cwd=root, + capture_output=True, + text=True, + ) + + output = proc.stdout + proc.stderr + + assert proc.returncode == 0, output + assert "rendering 'pyrobuf_list.pyx'" not in output + assert 'Cythonizing pyrobuf/src' not in output \ No newline at end of file From c3d1972111e74dbe89b2feda375d2a50e9668df8 Mon Sep 17 00:00:00 2001 From: "LOPEZ, Ramon" Date: Mon, 23 Mar 2026 17:33:27 +0100 Subject: [PATCH 2/2] Package generated builtin headers in wheels --- setup.py | 31 ++++++++++++++++++++++++++++--- tests/test_setup_metadata.py | 28 +++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 1a27810..69e7e90 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,9 @@ from distutils import log from setuptools import setup, find_packages, Distribution +from setuptools.command.build_py import build_py as _build_py from setuptools.command.build_ext import build_ext as _build_ext +from setuptools.command.sdist import sdist as _sdist import os import os.path @@ -68,6 +70,7 @@ class PyrobufDistribution(Distribution): def __init__(self, attrs=None): super().__init__(attrs) + self._pyrobuf_builtins_rendered = False self._pyrobuf_builtins_added = False def has_ext_modules(self): @@ -80,6 +83,7 @@ def ensure_pyrobuf_builtins(self): if not self.ext_modules: self.ext_modules = [] + self.render_pyrobuf_builtins() builtin_ext_modules = self.pyrobufize_builtins() for ext in builtin_ext_modules: if not hasattr(ext, '_needs_stub'): @@ -88,9 +92,11 @@ def ensure_pyrobuf_builtins(self): self.ext_modules.extend(builtin_ext_modules) self._pyrobuf_builtins_added = True - def pyrobufize_builtins(self): + def render_pyrobuf_builtins(self): + if self._pyrobuf_builtins_rendered: + return + from jinja2 import Environment, PackageLoader - from Cython.Build import cythonize env = Environment(loader=PackageLoader('pyrobuf.protobuf', 'templates')) dry_run = getattr(self, 'dry_run', False) @@ -149,10 +155,29 @@ def pyrobufize_builtins(self): defs.write('\n'.join('DEF PYROBUF_UNAME_%s = "%s"' % (k, v) for k, v in uname)) defs.write('\n') + self._pyrobuf_builtins_rendered = True + + def pyrobufize_builtins(self): + from Cython.Build import cythonize + return cythonize(['pyrobuf/src/*.pyx'], include_path=['pyrobuf/src']) +class build_py(_build_py): + + def run(self): + self.distribution.render_pyrobuf_builtins() + _build_py.run(self) + + +class sdist(_sdist): + + def run(self): + self.distribution.render_pyrobuf_builtins() + _sdist.run(self) + + class build_ext(_build_ext): def run(self): @@ -167,7 +192,7 @@ def run(self): version=VERSION, packages=find_packages(), include_package_data=True, - cmdclass={'build_ext': build_ext, 'clean': clean}, + cmdclass={'build_py': build_py, 'build_ext': build_ext, 'clean': clean, 'sdist': sdist}, entry_points={ 'console_scripts': ['pyrobuf = pyrobuf.__main__:main'], 'distutils.setup_keywords': [ diff --git a/tests/test_setup_metadata.py b/tests/test_setup_metadata.py index 923e2d9..caee898 100644 --- a/tests/test_setup_metadata.py +++ b/tests/test_setup_metadata.py @@ -1,6 +1,7 @@ import os import subprocess import sys +import zipfile def test_setup_dist_info_succeeds(tmp_path): @@ -18,4 +19,29 @@ def test_setup_dist_info_succeeds(tmp_path): assert proc.returncode == 0, output assert "rendering 'pyrobuf_list.pyx'" not in output - assert 'Cythonizing pyrobuf/src' not in output \ No newline at end of file + assert 'Cythonizing pyrobuf/src' not in output + + +def test_wheel_contains_generated_builtin_headers(tmp_path): + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + cmd = [sys.executable, 'setup.py', 'bdist_wheel', '--dist-dir', str(tmp_path)] + + proc = subprocess.run( + cmd, + cwd=root, + capture_output=True, + text=True, + ) + + output = proc.stdout + proc.stderr + + assert proc.returncode == 0, output + + wheels = sorted(tmp_path.glob('pyrobuf-*.whl')) + assert wheels, output + + with zipfile.ZipFile(str(wheels[0])) as archive: + names = set(archive.namelist()) + + assert 'pyrobuf/src/pyrobuf_list.pxd' in names + assert 'pyrobuf/src/pyrobuf_list.pyx' in names \ No newline at end of file