Wenzel Jakob | 929fd7e | 2015-10-15 18:24:12 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
Henry Schreiner | d8c7ee0 | 2020-07-20 13:35:21 -0400 | [diff] [blame] | 2 | # -*- coding: utf-8 -*- |
Wenzel Jakob | 929fd7e | 2015-10-15 18:24:12 +0200 | [diff] [blame] | 3 | |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 4 | # Setup script for PyPI; use CMakeFile.txt to build extension modules |
Wenzel Jakob | 929fd7e | 2015-10-15 18:24:12 +0200 | [diff] [blame] | 5 | |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 6 | import contextlib |
Sylvain Corlay | d5ce82b | 2017-02-14 13:16:14 +0100 | [diff] [blame] | 7 | import os |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 8 | import re |
| 9 | import shutil |
| 10 | import string |
| 11 | import subprocess |
| 12 | import sys |
| 13 | import tempfile |
Wenzel Jakob | 929fd7e | 2015-10-15 18:24:12 +0200 | [diff] [blame] | 14 | |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 15 | import setuptools.command.sdist |
Isuru Fernando | 3735249 | 2019-11-28 01:59:23 -0600 | [diff] [blame] | 16 | |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 17 | DIR = os.path.abspath(os.path.dirname(__file__)) |
| 18 | VERSION_REGEX = re.compile( |
| 19 | r"^\s*#\s*define\s+PYBIND11_VERSION_([A-Z]+)\s+(.*)$", re.MULTILINE |
| 20 | ) |
| 21 | |
| 22 | # PYBIND11_GLOBAL_SDIST will build a different sdist, with the python-headers |
| 23 | # files, and the sys.prefix files (CMake and headers). |
| 24 | |
| 25 | global_sdist = os.environ.get("PYBIND11_GLOBAL_SDIST", False) |
| 26 | |
| 27 | setup_py = "tools/setup_global.py.in" if global_sdist else "tools/setup_main.py.in" |
| 28 | extra_cmd = 'cmdclass["sdist"] = SDist\n' |
| 29 | |
| 30 | to_src = ( |
| 31 | ("pyproject.toml", "tools/pyproject.toml"), |
| 32 | ("setup.py", setup_py), |
| 33 | ) |
| 34 | |
| 35 | # Read the listed version |
| 36 | with open("pybind11/_version.py") as f: |
| 37 | code = compile(f.read(), "pybind11/_version.py", "exec") |
Henry Schreiner | 99ef2b8 | 2020-09-17 09:08:08 -0400 | [diff] [blame] | 38 | loc = {} |
| 39 | exec(code, loc) |
| 40 | version = loc["__version__"] |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 41 | |
| 42 | # Verify that the version matches the one in C++ |
| 43 | with open("include/pybind11/detail/common.h") as f: |
| 44 | matches = dict(VERSION_REGEX.findall(f.read())) |
| 45 | cpp_version = "{MAJOR}.{MINOR}.{PATCH}".format(**matches) |
| 46 | if version != cpp_version: |
| 47 | msg = "Python version {} does not match C++ version {}!".format( |
| 48 | version, cpp_version |
| 49 | ) |
| 50 | raise RuntimeError(msg) |
Sylvain Corlay | d5ce82b | 2017-02-14 13:16:14 +0100 | [diff] [blame] | 51 | |
Dean Moldovan | 1913f25 | 2017-08-23 14:20:53 +0200 | [diff] [blame] | 52 | |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 53 | def get_and_replace(filename, binary=False, **opts): |
| 54 | with open(filename, "rb" if binary else "r") as f: |
| 55 | contents = f.read() |
| 56 | # Replacement has to be done on text in Python 3 (both work in Python 2) |
| 57 | if binary: |
| 58 | return string.Template(contents.decode()).substitute(opts).encode() |
| 59 | else: |
| 60 | return string.Template(contents).substitute(opts) |
Dean Moldovan | 1913f25 | 2017-08-23 14:20:53 +0200 | [diff] [blame] | 61 | |
| 62 | |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 63 | # Use our input files instead when making the SDist (and anything that depends |
| 64 | # on it, like a wheel) |
| 65 | class SDist(setuptools.command.sdist.sdist): |
| 66 | def make_release_tree(self, base_dir, files): |
| 67 | setuptools.command.sdist.sdist.make_release_tree(self, base_dir, files) |
Isuru Fernando | 3735249 | 2019-11-28 01:59:23 -0600 | [diff] [blame] | 68 | |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 69 | for to, src in to_src: |
| 70 | txt = get_and_replace(src, binary=True, version=version, extra_cmd="") |
| 71 | |
| 72 | dest = os.path.join(base_dir, to) |
| 73 | |
| 74 | # This is normally linked, so unlink before writing! |
| 75 | os.unlink(dest) |
| 76 | with open(dest, "wb") as f: |
| 77 | f.write(txt) |
Isuru Fernando | e107fc2 | 2020-04-26 14:56:10 +0000 | [diff] [blame] | 78 | |
Isuru Fernando | 3735249 | 2019-11-28 01:59:23 -0600 | [diff] [blame] | 79 | |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 80 | # Backport from Python 3 |
| 81 | @contextlib.contextmanager |
| 82 | def TemporaryDirectory(): # noqa: N802 |
| 83 | "Prepare a temporary directory, cleanup when done" |
| 84 | try: |
| 85 | tmpdir = tempfile.mkdtemp() |
| 86 | yield tmpdir |
| 87 | finally: |
| 88 | shutil.rmtree(tmpdir) |
Wenzel Jakob | 929fd7e | 2015-10-15 18:24:12 +0200 | [diff] [blame] | 89 | |
Wenzel Jakob | 929fd7e | 2015-10-15 18:24:12 +0200 | [diff] [blame] | 90 | |
Henry Schreiner | fd61f50 | 2020-09-16 17:13:41 -0400 | [diff] [blame] | 91 | # Remove the CMake install directory when done |
| 92 | @contextlib.contextmanager |
| 93 | def remove_output(*sources): |
| 94 | try: |
| 95 | yield |
| 96 | finally: |
| 97 | for src in sources: |
| 98 | shutil.rmtree(src) |
| 99 | |
| 100 | |
| 101 | with remove_output("pybind11/include", "pybind11/share"): |
| 102 | # Generate the files if they are not present. |
| 103 | with TemporaryDirectory() as tmpdir: |
| 104 | cmd = ["cmake", "-S", ".", "-B", tmpdir] + [ |
| 105 | "-DCMAKE_INSTALL_PREFIX=pybind11", |
| 106 | "-DBUILD_TESTING=OFF", |
| 107 | "-DPYBIND11_NOPYTHON=ON", |
| 108 | ] |
| 109 | cmake_opts = dict(cwd=DIR, stdout=sys.stdout, stderr=sys.stderr) |
| 110 | subprocess.check_call(cmd, **cmake_opts) |
| 111 | subprocess.check_call(["cmake", "--install", tmpdir], **cmake_opts) |
| 112 | |
| 113 | txt = get_and_replace(setup_py, version=version, extra_cmd=extra_cmd) |
| 114 | code = compile(txt, setup_py, "exec") |
| 115 | exec(code, {"SDist": SDist}) |