blob: 3a0327982adc1a37cf3f5a073aa025a45f97ec78 [file] [log] [blame]
Wenzel Jakob929fd7e2015-10-15 18:24:12 +02001#!/usr/bin/env python
Henry Schreinerd8c7ee02020-07-20 13:35:21 -04002# -*- coding: utf-8 -*-
Wenzel Jakob929fd7e2015-10-15 18:24:12 +02003
Dean Moldovana0c1ccf2016-08-12 13:50:00 +02004# Setup script for PyPI; use CMakeFile.txt to build extension modules
Wenzel Jakob929fd7e2015-10-15 18:24:12 +02005
Henry Schreinerfd61f502020-09-16 17:13:41 -04006import contextlib
Sylvain Corlayd5ce82b2017-02-14 13:16:14 +01007import os
Henry Schreinerfd61f502020-09-16 17:13:41 -04008import re
9import shutil
10import string
11import subprocess
12import sys
13import tempfile
Wenzel Jakob929fd7e2015-10-15 18:24:12 +020014
Henry Schreinerfd61f502020-09-16 17:13:41 -040015import setuptools.command.sdist
Isuru Fernando37352492019-11-28 01:59:23 -060016
Henry Schreinerfd61f502020-09-16 17:13:41 -040017DIR = os.path.abspath(os.path.dirname(__file__))
18VERSION_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
25global_sdist = os.environ.get("PYBIND11_GLOBAL_SDIST", False)
26
27setup_py = "tools/setup_global.py.in" if global_sdist else "tools/setup_main.py.in"
28extra_cmd = 'cmdclass["sdist"] = SDist\n'
29
30to_src = (
31 ("pyproject.toml", "tools/pyproject.toml"),
32 ("setup.py", setup_py),
33)
34
35# Read the listed version
36with open("pybind11/_version.py") as f:
37 code = compile(f.read(), "pybind11/_version.py", "exec")
Henry Schreiner99ef2b82020-09-17 09:08:08 -040038loc = {}
39exec(code, loc)
40version = loc["__version__"]
Henry Schreinerfd61f502020-09-16 17:13:41 -040041
42# Verify that the version matches the one in C++
43with open("include/pybind11/detail/common.h") as f:
44 matches = dict(VERSION_REGEX.findall(f.read()))
45cpp_version = "{MAJOR}.{MINOR}.{PATCH}".format(**matches)
46if version != cpp_version:
47 msg = "Python version {} does not match C++ version {}!".format(
48 version, cpp_version
49 )
50 raise RuntimeError(msg)
Sylvain Corlayd5ce82b2017-02-14 13:16:14 +010051
Dean Moldovan1913f252017-08-23 14:20:53 +020052
Henry Schreinerfd61f502020-09-16 17:13:41 -040053def 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 Moldovan1913f252017-08-23 14:20:53 +020061
62
Henry Schreinerfd61f502020-09-16 17:13:41 -040063# Use our input files instead when making the SDist (and anything that depends
64# on it, like a wheel)
65class 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 Fernando37352492019-11-28 01:59:23 -060068
Henry Schreinerfd61f502020-09-16 17:13:41 -040069 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 Fernandoe107fc22020-04-26 14:56:10 +000078
Isuru Fernando37352492019-11-28 01:59:23 -060079
Henry Schreinerfd61f502020-09-16 17:13:41 -040080# Backport from Python 3
81@contextlib.contextmanager
82def 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 Jakob929fd7e2015-10-15 18:24:12 +020089
Wenzel Jakob929fd7e2015-10-15 18:24:12 +020090
Henry Schreinerfd61f502020-09-16 17:13:41 -040091# Remove the CMake install directory when done
92@contextlib.contextmanager
93def remove_output(*sources):
94 try:
95 yield
96 finally:
97 for src in sources:
98 shutil.rmtree(src)
99
100
101with 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})