mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-27 07:32:02 +00:00
9df2f1ff13
* Apply isort * Tweak isort config * Add env.py as a known_first_party * Add one missing known first party * Make config compat with older isort versions * Add another comment * Revert pyproject setting
156 lines
4.7 KiB
Python
156 lines
4.7 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Setup script for PyPI; use CMakeFile.txt to build extension modules
|
|
|
|
import contextlib
|
|
import io
|
|
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
|
|
)
|
|
|
|
|
|
def build_expected_version_hex(matches):
|
|
patch_level_serial = matches["PATCH"]
|
|
serial = None
|
|
try:
|
|
major = int(matches["MAJOR"])
|
|
minor = int(matches["MINOR"])
|
|
flds = patch_level_serial.split(".")
|
|
if flds:
|
|
patch = int(flds[0])
|
|
level = None
|
|
if len(flds) == 1:
|
|
level = "0"
|
|
serial = 0
|
|
elif len(flds) == 2:
|
|
level_serial = flds[1]
|
|
for level in ("a", "b", "c", "dev"):
|
|
if level_serial.startswith(level):
|
|
serial = int(level_serial[len(level) :])
|
|
break
|
|
except ValueError:
|
|
pass
|
|
if serial is None:
|
|
msg = 'Invalid PYBIND11_VERSION_PATCH: "{}"'.format(patch_level_serial)
|
|
raise RuntimeError(msg)
|
|
return "0x{:02x}{:02x}{:02x}{}{:x}".format(
|
|
major, minor, patch, level[:1].upper(), serial
|
|
)
|
|
|
|
|
|
# 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 io.open("include/pybind11/detail/common.h", encoding="utf8") 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)
|
|
|
|
version_hex = matches.get("HEX", "MISSING")
|
|
expected_version_hex = build_expected_version_hex(matches)
|
|
if version_hex != expected_version_hex:
|
|
msg = "PYBIND11_VERSION_HEX {} does not match expected value {}!".format(
|
|
version_hex,
|
|
expected_version_hex,
|
|
)
|
|
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})
|