diff --git a/setup.py b/setup.py index 56aadf6..69e7e90 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +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 @@ -65,18 +68,39 @@ 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_rendered = False + 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 = [] + + self.render_pyrobuf_builtins() + 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 render_pyrobuf_builtins(self): + if self._pyrobuf_builtins_rendered: + return - 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 +130,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 +139,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} @@ -131,17 +155,44 @@ 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): + 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_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 new file mode 100644 index 0000000..caee898 --- /dev/null +++ b/tests/test_setup_metadata.py @@ -0,0 +1,47 @@ +import os +import subprocess +import sys +import zipfile + + +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 + + +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