| #!/usr/bin/env python |
| # -*- coding: utf-8 -*- |
| |
| # Setup script for PyPI; use CMakeFile.txt to build extension modules |
| |
| import contextlib |
| import os |
| import re |
| import shutil |
| import string |
| import subprocess |
| import sys |
| import tempfile |
| |
| import setuptools.command.sdist |
| |
| DIR = os.path.abspath(os.path.dirname(__file__)) |
| VERSION_REGEX = re.compile( |
| r"^\s*#\s*define\s+PYBIND11_VERSION_([A-Z]+)\s+(.*)$", re.MULTILINE |
| ) |
| |
| # PYBIND11_GLOBAL_SDIST will build a different sdist, with the python-headers |
| # files, and the sys.prefix files (CMake and headers). |
| |
| global_sdist = os.environ.get("PYBIND11_GLOBAL_SDIST", False) |
| |
| setup_py = "tools/setup_global.py.in" if global_sdist else "tools/setup_main.py.in" |
| extra_cmd = 'cmdclass["sdist"] = SDist\n' |
| |
| to_src = ( |
| ("pyproject.toml", "tools/pyproject.toml"), |
| ("setup.py", setup_py), |
| ) |
| |
| # Read the listed version |
| with open("pybind11/_version.py") as f: |
| code = compile(f.read(), "pybind11/_version.py", "exec") |
| loc = {} |
| exec(code, loc) |
| version = loc["__version__"] |
| |
| # Verify that the version matches the one in C++ |
| with open("include/pybind11/detail/common.h") as f: |
| matches = dict(VERSION_REGEX.findall(f.read())) |
| cpp_version = "{MAJOR}.{MINOR}.{PATCH}".format(**matches) |
| if version != cpp_version: |
| msg = "Python version {} does not match C++ version {}!".format( |
| version, cpp_version |
| ) |
| raise RuntimeError(msg) |
| |
| |
| def get_and_replace(filename, binary=False, **opts): |
| with open(filename, "rb" if binary else "r") as f: |
| contents = f.read() |
| # Replacement has to be done on text in Python 3 (both work in Python 2) |
| if binary: |
| return string.Template(contents.decode()).substitute(opts).encode() |
| else: |
| return string.Template(contents).substitute(opts) |
| |
| |
| # Use our input files instead when making the SDist (and anything that depends |
| # on it, like a wheel) |
| class SDist(setuptools.command.sdist.sdist): |
| def make_release_tree(self, base_dir, files): |
| setuptools.command.sdist.sdist.make_release_tree(self, base_dir, files) |
| |
| for to, src in to_src: |
| txt = get_and_replace(src, binary=True, version=version, extra_cmd="") |
| |
| dest = os.path.join(base_dir, to) |
| |
| # This is normally linked, so unlink before writing! |
| os.unlink(dest) |
| with open(dest, "wb") as f: |
| f.write(txt) |
| |
| |
| # Backport from Python 3 |
| @contextlib.contextmanager |
| def TemporaryDirectory(): # noqa: N802 |
| "Prepare a temporary directory, cleanup when done" |
| try: |
| tmpdir = tempfile.mkdtemp() |
| yield tmpdir |
| finally: |
| shutil.rmtree(tmpdir) |
| |
| |
| # Remove the CMake install directory when done |
| @contextlib.contextmanager |
| def remove_output(*sources): |
| try: |
| yield |
| finally: |
| for src in sources: |
| shutil.rmtree(src) |
| |
| |
| with remove_output("pybind11/include", "pybind11/share"): |
| # Generate the files if they are not present. |
| with TemporaryDirectory() as tmpdir: |
| cmd = ["cmake", "-S", ".", "-B", tmpdir] + [ |
| "-DCMAKE_INSTALL_PREFIX=pybind11", |
| "-DBUILD_TESTING=OFF", |
| "-DPYBIND11_NOPYTHON=ON", |
| ] |
| cmake_opts = dict(cwd=DIR, stdout=sys.stdout, stderr=sys.stderr) |
| subprocess.check_call(cmd, **cmake_opts) |
| subprocess.check_call(["cmake", "--install", tmpdir], **cmake_opts) |
| |
| txt = get_and_replace(setup_py, version=version, extra_cmd=extra_cmd) |
| code = compile(txt, setup_py, "exec") |
| exec(code, {"SDist": SDist}) |