Skip to content

Commit af17c99

Browse files
committed
feat: setup.py redesign and helpers
1 parent b3d8fec commit af17c99

File tree

14 files changed

+471
-143
lines changed

14 files changed

+471
-143
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,11 +349,13 @@ jobs:
349349
- name: Install system requirements
350350
run: apk add doxygen python3-dev
351351

352-
- name: Ensure pip
353-
run: python3 -m ensurepip
352+
- name: Ensure and upgrade pip
353+
run: |
354+
python3 -m ensurepip
355+
python3 -m pip -U pip
354356
355357
- name: Install docs & setup requirements
356-
run: python3 -m pip install -r docs/requirements.txt pytest setuptools
358+
run: python3 -m pip install -r docs/requirements.txt pytest setuptools cmake --prefer-binary
357359

358360
- name: Build docs
359361
run: python3 -m sphinx -W -b html docs docs/.build

.github/workflows/format.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ jobs:
1717
- uses: actions/checkout@v2
1818
- uses: actions/setup-python@v2
1919
- uses: pre-commit/action@v2.0.0
20+
with:
21+
extra_args: --hook-stage manual

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ pybind11Config*.cmake
3939
pybind11Targets.cmake
4040
/*env*
4141
/.vscode
42+
/pybind11/include/*
43+
/pybind11/share/*

.pre-commit-config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ repos:
3434
types: [file]
3535
files: (\.cmake|CMakeLists.txt)(.in)?$
3636

37+
- repo: https://github.com/mgedmin/check-manifest
38+
rev: "0.42"
39+
hooks:
40+
- id: check-manifest
41+
stages: [manual]
42+
additional_dependencies: [cmake, ninja]
43+
3744
- repo: local
3845
hooks:
3946
- id: check-style

MANIFEST.in

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
recursive-include include/pybind11 *.h
2-
include LICENSE README.md .github/CONTRIBUTING.md
2+
recursive-include pybind11 *.h
3+
recursive-include pybind11 *.cmake
4+
recursive-include pybind11 *.py
5+
recursive-include tools *.cmake
6+
recursive-include tools *.in
7+
include CMakeLists.txt LICENSE README.md .github/CONTRIBUTING.md

docs/compiling.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,51 @@ the [python_example]_ repository.
1313

1414
.. [python_example] https://github.com/pybind/python_example
1515
16+
A helper file is provided with pybind11 that can simplify usage with setuptools
17+
if you have pybind11 installed as a Python package; the file is also standalone,
18+
if you want to copy it to your package. If use use PEP518's ``pyproject.toml``
19+
file:
20+
21+
.. code-block:: toml
22+
23+
[build-system]
24+
requires = ["setuptools", "wheel", "pybind11==2.6.0"]
25+
build-backend = "setuptools.build_meta"
26+
27+
you can ensure that pybind11 is available during the building of your project
28+
(pip 10+ required).
29+
30+
An example of a ``setup.py`` using pybind11's helpers:
31+
32+
.. code-block:: python
33+
34+
from setuptools import setup, Extension
35+
from pybind11.setup_helpers import BuildExt
36+
37+
ext_modules = [
38+
Extension(
39+
"python_example",
40+
sorted(["src/main.cpp"]),
41+
language="c++",
42+
),
43+
]
44+
45+
setup(
46+
...,
47+
cmdclass={"build_ext": BuildExt},
48+
ext_modules=ext_modules
49+
)
50+
51+
52+
If you copy ``setup_helpers.py`` into your local project to try to support the
53+
classic build procedure, then you will need to use the deprecated
54+
``setup_requires=["pybind11>=2.6.0"]`` keyword argument to setup;
55+
``setup_helpers`` tries to support this as well.
56+
57+
.. versionchanged:: 2.6
58+
59+
Added support file.
60+
1661
Building with cppimport
1762
========================
1863

pybind11/__init__.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
# -*- coding: utf-8 -*-
2-
from ._version import version_info, __version__ # noqa: F401 imported but unused
32

3+
from ._version import version_info, __version__
4+
from .commands import get_include, get_cmake_dir
45

5-
def get_include(user=False):
6-
import os
7-
d = os.path.dirname(__file__)
8-
if os.path.exists(os.path.join(d, "include")):
9-
# Package is installed
10-
return os.path.join(d, "include")
11-
else:
12-
# Package is from a source directory
13-
return os.path.join(os.path.dirname(d), "include")
6+
7+
__all__ = (
8+
"version_info",
9+
"__version__",
10+
"get_include",
11+
"get_cmake_dir",
12+
)

pybind11/__main__.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,34 @@
99

1010

1111
def print_includes():
12-
dirs = [sysconfig.get_path('include'),
13-
sysconfig.get_path('platinclude'),
14-
get_include()]
12+
dirs = [
13+
sysconfig.get_path("include"),
14+
sysconfig.get_path("platinclude"),
15+
get_include(),
16+
]
1517

1618
# Make unique but preserve order
1719
unique_dirs = []
1820
for d in dirs:
1921
if d not in unique_dirs:
2022
unique_dirs.append(d)
2123

22-
print(' '.join('-I' + d for d in unique_dirs))
24+
print(" ".join("-I" + d for d in unique_dirs))
2325

2426

2527
def main():
26-
parser = argparse.ArgumentParser(prog='python -m pybind11')
27-
parser.add_argument('--includes', action='store_true',
28-
help='Include flags for both pybind11 and Python headers.')
28+
parser = argparse.ArgumentParser(prog="python -m pybind11")
29+
parser.add_argument(
30+
"--includes",
31+
action="store_true",
32+
help="Include flags for both pybind11 and Python headers.",
33+
)
2934
args = parser.parse_args()
3035
if not sys.argv[1:]:
3136
parser.print_help()
3237
if args.includes:
3338
print_includes()
3439

3540

36-
if __name__ == '__main__':
41+
if __name__ == "__main__":
3742
main()

pybind11/_version.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# -*- coding: utf-8 -*-
2-
version_info = (2, 5, 'dev1')
3-
__version__ = '.'.join(map(str, version_info))
2+
3+
version_info = (2, 6, 0, "dev1")
4+
__version__ = ".".join(map(str, version_info))

pybind11/commands.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
import os
3+
4+
5+
DIR = os.path.abspath(os.path.dirname(__file__))
6+
7+
8+
def get_include(user=False):
9+
installed_path = os.path.join(DIR, "include")
10+
source_path = os.path.join(DIR, "include")
11+
return installed_path if os.path.exists(installed_path) else source_path
12+
13+
14+
def get_cmake_dir():
15+
cmake_installed_path = os.path.join(DIR, "share", "cmake", "pybind11")
16+
if os.path.exists(cmake_installed_path):
17+
return cmake_installed_path
18+
else:
19+
raise ImportError(
20+
"pybind11 not installed, installation required to access the CMake files"
21+
)

pybind11/setup_helpers.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
This module provides a way to check to see if flag is available,
5+
has_flag (built-in to distutils.CCompiler in Python 3.6+), and
6+
a cpp_flag function, which will compute the highest available
7+
flag (or if a flag is supported).
8+
9+
LICENSE:
10+
11+
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>, All rights reserved.
12+
13+
Redistribution and use in source and binary forms, with or without
14+
modification, are permitted provided that the following conditions are met:
15+
16+
1. Redistributions of source code must retain the above copyright notice, this
17+
list of conditions and the following disclaimer.
18+
19+
2. Redistributions in binary form must reproduce the above copyright notice,
20+
this list of conditions and the following disclaimer in the documentation
21+
and/or other materials provided with the distribution.
22+
23+
3. Neither the name of the copyright holder nor the names of its contributors
24+
may be used to endorse or promote products derived from this software
25+
without specific prior written permission.
26+
27+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
28+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
29+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
31+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
35+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37+
"""
38+
39+
import os
40+
import sys
41+
import tempfile
42+
43+
import distutils.errors
44+
from distutils.command.build_ext import build_ext
45+
46+
47+
# It is recommended to use PEP 518 builds if using this module. However, this
48+
# file explicitly supports being copied into a user's project directory
49+
# standalone, and pulling pybind11 with the deprecated setup_requires feature.
50+
51+
52+
class DelayedPybindInclude(object):
53+
"""
54+
Helper class to determine the pybind11 include path The purpose of this
55+
class is to postpone importing pybind11 until it is actually installed, so
56+
that the ``get_include()`` method can be invoked if pybind11 is loaded via
57+
setup_requires.
58+
"""
59+
60+
def __str__(self):
61+
import pybind11
62+
63+
return pybind11.get_include()
64+
65+
66+
# cf http://bugs.python.org/issue26689
67+
def has_flag(compiler, flagname):
68+
"""
69+
Return a boolean indicating whether a flag name is supported on the
70+
specified compiler.
71+
"""
72+
73+
with tempfile.NamedTemporaryFile("w", suffix=".cpp", delete=False) as f:
74+
f.write("int main (int argc, char **argv) { return 0; }")
75+
fname = f.name
76+
try:
77+
compiler.compile([fname], extra_postargs=[flagname])
78+
except distutils.errors.CompileError:
79+
return False
80+
finally:
81+
try:
82+
os.remove(fname)
83+
except OSError:
84+
pass
85+
return True
86+
87+
88+
def cpp_flag(compiler, value=None):
89+
"""
90+
Return the ``-std=c++[11/14/17]`` compiler flag.
91+
The newer version is preferred over c++11 (when it is available).
92+
"""
93+
94+
flags = ["-std=c++17", "-std=c++14", "-std=c++11"]
95+
96+
if value is not None:
97+
mapping = {17: 0, 14: 1, 11: 2}
98+
flags = [flags[mapping[value]]]
99+
100+
for flag in flags:
101+
if has_flag(compiler, flag):
102+
return flag
103+
104+
raise RuntimeError("Unsupported compiler -- at least C++11 support is needed!")
105+
106+
107+
c_opts = {
108+
"msvc": ["/EHsc"],
109+
"unix": [],
110+
}
111+
112+
l_opts = {
113+
"msvc": [],
114+
"unix": [],
115+
}
116+
117+
if sys.platform == "darwin":
118+
darwin_opts = ["-stdlib=libc++", "-mmacosx-version-min=10.9"]
119+
c_opts["unix"] += darwin_opts
120+
l_opts["unix"] += darwin_opts
121+
122+
123+
class BuildExt(build_ext):
124+
"""
125+
Customized build_ext that can be further customized by users.
126+
127+
Most use cases can be addressed by adding items to the extensions.
128+
However, if you need to customize, try:
129+
130+
class BuildExt(pybind11.setup_utils.BuildExt):
131+
def build_extensions(self):
132+
# Do something here, like add things to extensions
133+
134+
super(BuildExt, self).build_extensions()
135+
136+
One simple customization point is provided: ``self.cxx_std`` lets
137+
you set a C++ standard (None is the default search).
138+
"""
139+
140+
def __init__(self, *args, **kwargs):
141+
super(BuildExt, self).__init__(*args, **kwargs)
142+
self.cxx_std = None
143+
144+
def build_extensions(self):
145+
ct = self.compiler.compiler_type
146+
comp_opts = c_opts.get(ct, [])
147+
link_opts = l_opts.get(ct, [])
148+
if ct == "unix":
149+
comp_opts.append(cpp_flag(self.compiler, self.cxx_std))
150+
if has_flag(self.compiler, "-fvisibility=hidden"):
151+
comp_opts.append("-fvisibility=hidden")
152+
153+
for ext in self.extensions:
154+
ext.extra_compile_args += comp_opts
155+
ext.extra_link_args += link_opts
156+
ext.include_dirs += [DelayedPybindInclude()]
157+
158+
super(BuildExt, self).build_extensions()

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[build-system]
2+
requires = ["setuptools", "wheel", "cmake==3.18.0", "ninja"]
3+
build-backend = "setuptools.build_meta"

0 commit comments

Comments
 (0)