aboutsummaryrefslogtreecommitdiffstats
path: root/3rdparty/pybind11/setup.py
blob: 3a0327982adc1a37cf3f5a073aa025a45f97ec78 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/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})