μHAL (v2.8.17)
Part of the IPbus software repository
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
setup.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3# Setup script for PyPI; use CMakeFile.txt to build extension modules
4
5import contextlib
6import os
7import re
8import shutil
9import string
10import subprocess
11import sys
12from pathlib import Path
13from tempfile import TemporaryDirectory
14from typing import Dict, Iterator, List, Union
15
17
18DIR = Path(__file__).parent.absolute()
19VERSION_REGEX = re.compile(
20 r"^\s*#\s*define\s+PYBIND11_VERSION_([A-Z]+)\s+(.*)$", re.MULTILINE
21)
22VERSION_FILE = Path("pybind11/_version.py")
23COMMON_FILE = Path("include/pybind11/detail/common.h")
24
25
26def build_expected_version_hex(matches: Dict[str, str]) -> str:
27 patch_level_serial = matches["PATCH"]
28 serial = None
29 major = int(matches["MAJOR"])
30 minor = int(matches["MINOR"])
31 flds = patch_level_serial.split(".")
32 if flds:
33 patch = int(flds[0])
34 if len(flds) == 1:
35 level = "0"
36 serial = 0
37 elif len(flds) == 2:
38 level_serial = flds[1]
39 for level in ("a", "b", "c", "dev"):
40 if level_serial.startswith(level):
41 serial = int(level_serial[len(level) :])
42 break
43 if serial is None:
44 msg = f'Invalid PYBIND11_VERSION_PATCH: "{patch_level_serial}"'
45 raise RuntimeError(msg)
46 version_hex_str = f"{major:02x}{minor:02x}{patch:02x}{level[:1]}{serial:x}"
47 return f"0x{version_hex_str.upper()}"
48
49
50# PYBIND11_GLOBAL_SDIST will build a different sdist, with the python-headers
51# files, and the sys.prefix files (CMake and headers).
52
53global_sdist = os.environ.get("PYBIND11_GLOBAL_SDIST", False)
54
55setup_py = Path(
56 "tools/setup_global.py.in" if global_sdist else "tools/setup_main.py.in"
57)
58extra_cmd = 'cmdclass["sdist"] = SDist\n'
59
60to_src = (
61 (Path("pyproject.toml"), Path("tools/pyproject.toml")),
62 (Path("setup.py"), setup_py),
63)
64
65
66# Read the listed version
67loc: Dict[str, str] = {}
68code = compile(VERSION_FILE.read_text(encoding="utf-8"), "pybind11/_version.py", "exec")
69exec(code, loc)
70version = loc["__version__"]
71
72# Verify that the version matches the one in C++
73matches = dict(VERSION_REGEX.findall(COMMON_FILE.read_text(encoding="utf8")))
74cpp_version = "{MAJOR}.{MINOR}.{PATCH}".format(**matches)
75if version != cpp_version:
76 msg = f"Python version {version} does not match C++ version {cpp_version}!"
77 raise RuntimeError(msg)
78
79version_hex = matches.get("HEX", "MISSING")
80exp_version_hex = build_expected_version_hex(matches)
81if version_hex != exp_version_hex:
82 msg = f"PYBIND11_VERSION_HEX {version_hex} does not match expected value {exp_version_hex}!"
83 raise RuntimeError(msg)
84
85
86# TODO: use literals & overload (typing extensions or Python 3.8)
88 filename: Path, binary: bool = False, **opts: str
89) -> Union[bytes, str]:
90 if binary:
91 contents = filename.read_bytes()
92 return string.Template(contents.decode()).substitute(opts).encode()
93
94 return string.Template(filename.read_text()).substitute(opts)
95
96
97# Use our input files instead when making the SDist (and anything that depends
98# on it, like a wheel)
99class SDist(setuptools.command.sdist.sdist): # type: ignore[misc]
100 def make_release_tree(self, base_dir: str, files: List[str]) -> None:
101 super().make_release_tree(base_dir, files)
102
103 for to, src in to_src:
104 txt = get_and_replace(src, binary=True, version=version, extra_cmd="")
105
106 dest = Path(base_dir) / to
107
108 # This is normally linked, so unlink before writing!
109 dest.unlink()
110 dest.write_bytes(txt) # type: ignore[arg-type]
111
112
113# Remove the CMake install directory when done
114@contextlib.contextmanager
115def remove_output(*sources: str) -> Iterator[None]:
116 try:
117 yield
118 finally:
119 for src in sources:
120 shutil.rmtree(src)
121
122
123with remove_output("pybind11/include", "pybind11/share"):
124 # Generate the files if they are not present.
125 with TemporaryDirectory() as tmpdir:
126 cmd = ["cmake", "-S", ".", "-B", tmpdir] + [
127 "-DCMAKE_INSTALL_PREFIX=pybind11",
128 "-DBUILD_TESTING=OFF",
129 "-DPYBIND11_NOPYTHON=ON",
130 "-Dprefix_for_pc_file=${pcfiledir}/../../",
131 ]
132 if "CMAKE_ARGS" in os.environ:
133 fcommand = [
134 c
135 for c in os.environ["CMAKE_ARGS"].split()
136 if "DCMAKE_INSTALL_PREFIX" not in c
137 ]
138 cmd += fcommand
139 subprocess.run(cmd, check=True, cwd=DIR, stdout=sys.stdout, stderr=sys.stderr)
140 subprocess.run(
141 ["cmake", "--install", tmpdir],
142 check=True,
143 cwd=DIR,
144 stdout=sys.stdout,
145 stderr=sys.stderr,
146 )
147
148 txt = get_and_replace(setup_py, version=version, extra_cmd=extra_cmd)
149 code = compile(txt, setup_py, "exec")
150 exec(code, {"SDist": SDist})
Definition: pytypes.h:1694
def make_release_tree(self, base_dir, files)
Definition: setup.py:109
size_t len(handle h)
Get the length of a Python object.
Definition: pytypes.h:2002
def remove_output(*sources)
Definition: setup.py:136
def get_and_replace(filename, binary=False, **opts)
Definition: setup.py:96
def build_expected_version_hex(matches)
Definition: setup.py:24
void exec(const str &expr, object global=globals(), object local=object())
Definition: eval.h:88