Browse Source

chore: use scikit-build-core for the build (#5598)

* chore: use scikit-build-core for the build

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* fix: support tests job

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* refactor: use tomlkit instead of manual parsing

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* tests: add tests for output

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* chore: remove more unused files

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* fix: restore global pin

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* fix: test and fix pinning

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
pull/5668/head
Henry Schreiner 2 months ago committed by GitHub
parent
commit
af231a6054
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .github/workflows/format.yml
  2. 6
      .gitignore
  3. 9
      .pre-commit-config.yaml
  4. 21
      CMakeLists.txt
  5. 6
      MANIFEST.in
  6. 7
      docs/conf.py
  7. 50
      noxfile.py
  8. 24
      pybind11/_version.py
  9. 127
      pyproject.toml
  10. 42
      setup.cfg
  11. 153
      setup.py
  12. 237
      tests/extra_python_package/test_files.py
  13. 1
      tests/requirements.txt
  14. 33
      tools/make_global.py
  15. 3
      tools/pyproject.toml
  16. 66
      tools/setup_global.py.in
  17. 50
      tools/setup_main.py.in

3
.github/workflows/format.yml

@ -32,9 +32,6 @@ jobs: @@ -32,9 +32,6 @@ jobs:
- name: Add matchers
run: echo "::add-matcher::$GITHUB_WORKSPACE/.github/matchers/pylint.json"
- uses: pre-commit/action@v3.0.1
with:
# Slow hooks are marked with manual - slow is okay here, run them too
extra_args: --hook-stage manual --all-files
clang-tidy:
# When making changes here, please also review the "Clang-Tidy" section

6
.gitignore vendored

@ -45,3 +45,9 @@ pybind11Targets.cmake @@ -45,3 +45,9 @@ pybind11Targets.cmake
.ipynb_checkpoints/
tests/main.cpp
CMakeUserPresents.json
/Python
/tmp*
.ruby-version
.*cache*/
*.lock

9
.pre-commit-config.yaml

@ -108,15 +108,6 @@ repos: @@ -108,15 +108,6 @@ repos:
- id: rst-directive-colons
- id: rst-inline-touching-normal
# Checks the manifest for missing files (native support)
- repo: https://github.com/mgedmin/check-manifest
rev: "0.50"
hooks:
- id: check-manifest
# This is a slow hook, so only run this if --hook-stage manual is passed
stages: [manual]
additional_dependencies: [cmake, ninja]
# Check for spelling
# Use tools/codespell_ignore_lines_from_errors.py
# to rebuild .codespell-ignore-lines

21
CMakeLists.txt

@ -65,6 +65,13 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) @@ -65,6 +65,13 @@ if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
message(STATUS "CMake ${CMAKE_VERSION}")
if(DEFINED SKBUILD AND DEFINED $ENV{PYBIND11_GLOBAL_PREFIX})
message(
FATAL_ERROR
"PYBIND11_GLOBAL_PREFIX is not supported, use nox -s build_global or a pybind11-global SDist instead."
)
endif()
if(CMAKE_CXX_STANDARD)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -295,6 +302,9 @@ elseif(USE_PYTHON_INCLUDE_DIR AND DEFINED PYTHON_INCLUDE_DIR) @@ -295,6 +302,9 @@ elseif(USE_PYTHON_INCLUDE_DIR AND DEFINED PYTHON_INCLUDE_DIR)
endif()
if(PYBIND11_INSTALL)
if(DEFINED SKBUILD_PROJECT_NAME AND SKBUILD_PROJECT_NAME STREQUAL "pybind11_global")
install(DIRECTORY ${pybind11_INCLUDE_DIR}/pybind11 DESTINATION "${SKBUILD_HEADERS_DIR}")
endif()
install(DIRECTORY ${pybind11_INCLUDE_DIR}/pybind11 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
set(PYBIND11_CMAKECONFIG_INSTALL_DIR
"${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}"
@ -361,6 +371,17 @@ if(PYBIND11_INSTALL) @@ -361,6 +371,17 @@ if(PYBIND11_INSTALL)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig/")
# When building a wheel, include __init__.py's for modules
# (see https://github.com/pybind/pybind11/pull/5552)
if(DEFINED SKBUILD_PROJECT_NAME AND SKBUILD_PROJECT_NAME STREQUAL "pybind11")
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/empty")
file(TOUCH "${CMAKE_CURRENT_BINARY_DIR}/empty/__init__.py")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/empty/__init__.py"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/empty/__init__.py"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig/")
endif()
# Uninstall target
if(PYBIND11_MASTER_PROJECT)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake_uninstall.cmake.in"

6
MANIFEST.in

@ -1,6 +0,0 @@ @@ -1,6 +0,0 @@
prune tests
recursive-include pybind11/include/pybind11 *.h
recursive-include pybind11 *.py
recursive-include pybind11 py.typed
include pybind11/share/cmake/pybind11/*.cmake
include LICENSE README.rst SECURITY.md pyproject.toml setup.py setup.cfg

7
docs/conf.py

@ -69,9 +69,10 @@ author = "Wenzel Jakob" @@ -69,9 +69,10 @@ author = "Wenzel Jakob"
# built documents.
# Read the listed version
with open("../pybind11/_version.py") as f:
code = compile(f.read(), "../pybind11/_version.py", "exec")
loc = {}
version_file = DIR.parent / "pybind11/_version.py"
with version_file.open(encoding="utf-8") as f:
code = compile(f.read(), version_file, "exec")
loc = {"__file__": str(version_file)}
exec(code, loc)
# The full version, including alpha/beta/rc tags.

50
noxfile.py

@ -7,6 +7,13 @@ @@ -7,6 +7,13 @@
from __future__ import annotations
import argparse
import contextlib
import os
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Generator
import nox
@ -99,13 +106,46 @@ def make_changelog(session: nox.Session) -> None: @@ -99,13 +106,46 @@ def make_changelog(session: nox.Session) -> None:
@nox.session(reuse_venv=True, default=False)
def build(session: nox.Session) -> None:
"""
Build SDists and wheels.
Build SDist and wheel.
"""
session.install("build")
session.log("Building normal files")
session.run("python", "-m", "build", *session.posargs)
session.log("Building pybind11-global files (PYBIND11_GLOBAL_SDIST=1)")
session.run(
"python", "-m", "build", *session.posargs, env={"PYBIND11_GLOBAL_SDIST": "1"}
)
@contextlib.contextmanager
def preserve_file(filename: Path) -> Generator[str, None, None]:
"""
Causes a file to be stored and preserved when the context manager exits.
"""
old_stat = filename.stat()
old_file = filename.read_text(encoding="utf-8")
try:
yield old_file
finally:
filename.write_text(old_file, encoding="utf-8")
os.utime(filename, (old_stat.st_atime, old_stat.st_mtime))
@nox.session(reuse_venv=True)
def build_global(session: nox.Session) -> None:
"""
Build global SDist and wheel.
"""
installer = ["--installer=uv"] if session.venv_backend == "uv" else []
session.install("build", "tomlkit")
session.log("Building pybind11-global files")
pyproject = Path("pyproject.toml")
with preserve_file(pyproject):
newer_txt = session.run("python", "tools/make_global.py", silent=True)
assert isinstance(newer_txt, str)
pyproject.write_text(newer_txt, encoding="utf-8")
session.run(
"python",
"-m",
"build",
*installer,
*session.posargs,
)

24
pybind11/_version.py

@ -1,5 +1,28 @@ @@ -1,5 +1,28 @@
# This file will be replaced in the wheel with a hard-coded version. This only
# exists to allow running directly from source without installing (not
# recommended, but supported).
from __future__ import annotations
import re
from pathlib import Path
DIR = Path(__file__).parent.resolve()
input_file = DIR.parent / "include/pybind11/detail/common.h"
regex = re.compile(
r"""
\#define \s+ PYBIND11_VERSION_MAJOR \s+ (?P<major>\d+) .*?
\#define \s+ PYBIND11_VERSION_MINOR \s+ (?P<minor>\d+) .*?
\#define \s+ PYBIND11_VERSION_PATCH \s+ (?P<patch>\S+)
""",
re.MULTILINE | re.DOTALL | re.VERBOSE,
)
match = regex.search(input_file.read_text(encoding="utf-8"))
assert match, "Unable to find version in pybind11/detail/common.h"
__version__ = "{major}.{minor}.{patch}".format(**match.groupdict())
def _to_int(s: str) -> int | str:
try:
@ -8,5 +31,4 @@ def _to_int(s: str) -> int | str: @@ -8,5 +31,4 @@ def _to_int(s: str) -> int | str:
return s
__version__ = "3.0.0.dev1"
version_info = tuple(_to_int(s) for s in __version__.split("."))

127
pyproject.toml

@ -1,25 +1,118 @@ @@ -1,25 +1,118 @@
[build-system]
requires = ["setuptools>=42", "cmake>=3.18", "ninja"]
build-backend = "setuptools.build_meta"
requires = ["scikit-build-core >=0.11.2"]
build-backend = "scikit_build_core.build"
[project]
name = "pybind11"
description = "Seamless operability between C++11 and Python"
authors = [{name = "Wenzel Jakob", email = "wenzel.jakob@epfl.ch"}]
license = "BSD-3-Clause"
license-files = ["LICENSE"]
readme = "README.rst"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Utilities",
"Programming Language :: C++",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: PyPy",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: C++",
"Topic :: Software Development :: Libraries :: Python Modules",
]
keywords = [
"C++11",
"Python bindings",
]
dynamic = ["version", "optional-dependencies"]
requires-python = ">=3.8"
[tool.check-manifest]
ignore = [
"tests/**",
"docs/**",
"tools/**",
"include/**",
".*",
"pybind11/include/**",
"pybind11/share/**",
"CMakeLists.txt",
"CMakePresets.json",
"noxfile.py",
[project.urls]
Homepage = "https://github.com/pybind/pybind11"
Documentation = "https://pybind11.readthedocs.io/"
"Bug Tracker" = "https://github.com/pybind/pybind11/issues"
Discussions = "https://github.com/pybind/pybind11/discussions"
Changelog = "https://pybind11.readthedocs.io/en/latest/changelog.html"
Chat = "https://gitter.im/pybind/Lobby"
[project.scripts]
pybind11-config = "pybind11.__main__:main"
[project.entry-points."pipx.run"]
pybind11 = "pybind11.__main__:main"
[project.entry-points.pkg_config]
pybind11 = "pybind11.share.pkgconfig"
[dependency-groups]
test = [
"pytest",
"build",
"tomlkit",
]
dev = [{ include-group = "test" }]
[tool.scikit-build]
minimum-version = "build-system.requires"
sdist.exclude = [
"/docs/**",
"/.**",
]
wheel.install-dir = "pybind11"
wheel.platlib = false
[tool.scikit-build.cmake.define]
BUILD_TESTING = false
PYBIND11_NOPYTHON = true
prefix_for_pc_file = "${pcfiledir}/../../"
[tool.scikit-build.metadata.version]
provider = "scikit_build_core.metadata.regex"
input = "include/pybind11/detail/common.h"
regex = '''(?sx)
\#define \s+ PYBIND11_VERSION_MAJOR \s+ (?P<major>\d+) .*?
\#define \s+ PYBIND11_VERSION_MINOR \s+ (?P<minor>\d+) .*?
\#define \s+ PYBIND11_VERSION_PATCH \s+ (?P<patch>\S+)
'''
result = "{major}.{minor}.{patch}"
[tool.scikit-build.metadata.optional-dependencies]
provider = "scikit_build_core.metadata.template"
result = { global = ["pybind11-global=={project[version]}"]}
[[tool.scikit-build.generate]]
path = "pybind11/_version.py"
template = '''
from __future__ import annotations
def _to_int(s: str) -> int | str:
try:
return int(s)
except ValueError:
return s
__version__ = "$version"
version_info = tuple(_to_int(s) for s in __version__.split("."))
'''
# Can't use tool.uv.sources with requirements.txt
[tool.uv]
# Can't use tool.uv.sources with requirements.txt
index-strategy = "unsafe-best-match"
# This extra confuses uv
override-dependencies = ["pybind11-global"]
[tool.mypy]
files = ["pybind11"]
@ -29,7 +122,7 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] @@ -29,7 +122,7 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
warn_unreachable = true
[[tool.mypy.overrides]]
module = ["ghapi.*"]
module = ["ghapi.*", "tomlkit"] # tomlkit has types, but not very helpful
ignore_missing_imports = true
@ -46,10 +139,10 @@ messages_control.disable = [ @@ -46,10 +139,10 @@ messages_control.disable = [
"protected-access",
"missing-module-docstring",
"unused-argument", # covered by Ruff ARG
"consider-using-f-string", # triggers in _version.py incorrectly
]
[tool.ruff]
target-version = "py38"
src = ["src"]
[tool.ruff.lint]

42
setup.cfg

@ -1,42 +0,0 @@ @@ -1,42 +0,0 @@
[metadata]
long_description = file: README.rst
long_description_content_type = text/x-rst
description = Seamless operability between C++11 and Python
author = Wenzel Jakob
author_email = wenzel.jakob@epfl.ch
url = https://github.com/pybind/pybind11
license = BSD
classifiers =
Development Status :: 5 - Production/Stable
Intended Audience :: Developers
Topic :: Software Development :: Libraries :: Python Modules
Topic :: Utilities
Programming Language :: C++
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: 3.13
License :: OSI Approved :: BSD License
Programming Language :: Python :: Implementation :: PyPy
Programming Language :: Python :: Implementation :: CPython
Programming Language :: C++
Topic :: Software Development :: Libraries :: Python Modules
keywords =
C++11
Python bindings
project_urls =
Documentation = https://pybind11.readthedocs.io/
Bug Tracker = https://github.com/pybind/pybind11/issues
Discussions = https://github.com/pybind/pybind11/discussions
Changelog = https://pybind11.readthedocs.io/en/latest/changelog.html
Chat = https://gitter.im/pybind/Lobby
[options]
python_requires = >=3.8
zip_safe = False

153
setup.py

@ -1,153 +0,0 @@ @@ -1,153 +0,0 @@
#!/usr/bin/env python3
# Setup script for PyPI; use CMakeFile.txt to build extension modules
from __future__ import annotations
import contextlib
import os
import re
import shutil
import string
import subprocess
import sys
from collections.abc import Generator
from pathlib import Path
from tempfile import TemporaryDirectory
import setuptools.command.sdist
DIR = Path(__file__).parent.absolute()
VERSION_REGEX = re.compile(
r"^\s*#\s*define\s+PYBIND11_VERSION_([A-Z]+)\s+(.*)$", re.MULTILINE
)
VERSION_FILE = Path("pybind11/_version.py")
COMMON_FILE = Path("include/pybind11/detail/common.h")
def build_expected_version_hex(matches: dict[str, str]) -> str:
patch_level_serial = matches["PATCH"]
serial = None
major = int(matches["MAJOR"])
minor = int(matches["MINOR"])
flds = patch_level_serial.split(".")
if flds:
patch = int(flds[0])
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
if serial is None:
msg = f'Invalid PYBIND11_VERSION_PATCH: "{patch_level_serial}"'
raise RuntimeError(msg)
version_hex_str = f"{major:02x}{minor:02x}{patch:02x}{level[:1]}{serial:x}"
return f"0x{version_hex_str.upper()}"
# 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")
setup_py = Path(
"tools/setup_global.py.in" if global_sdist else "tools/setup_main.py.in"
)
extra_cmd = 'cmdclass["sdist"] = SDist\n'
to_src = (
(Path("pyproject.toml"), Path("tools/pyproject.toml")),
(Path("setup.py"), setup_py),
)
# Read the listed version
loc: dict[str, str] = {}
code = compile(VERSION_FILE.read_text(encoding="utf-8"), "pybind11/_version.py", "exec")
exec(code, loc)
version = loc["__version__"]
# Verify that the version matches the one in C++
matches = dict(VERSION_REGEX.findall(COMMON_FILE.read_text(encoding="utf8")))
cpp_version = "{MAJOR}.{MINOR}.{PATCH}".format(**matches)
if version != cpp_version:
msg = f"Python version {version} does not match C++ version {cpp_version}!"
raise RuntimeError(msg)
version_hex = matches.get("HEX", "MISSING")
exp_version_hex = build_expected_version_hex(matches)
if version_hex != exp_version_hex:
msg = f"PYBIND11_VERSION_HEX {version_hex} does not match expected value {exp_version_hex}!"
raise RuntimeError(msg)
# TODO: use literals & overload (typing extensions or Python 3.8)
def get_and_replace(filename: Path, binary: bool = False, **opts: str) -> bytes | str:
if binary:
contents = filename.read_bytes()
return string.Template(contents.decode()).substitute(opts).encode()
return string.Template(filename.read_text()).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: str, files: list[str]) -> None:
super().make_release_tree(base_dir, files)
for to, src in to_src:
txt = get_and_replace(src, binary=True, version=version, extra_cmd="")
dest = Path(base_dir) / to
# This is normally linked, so unlink before writing!
dest.unlink()
dest.write_bytes(txt) # type: ignore[arg-type]
# Remove the CMake install directory when done
@contextlib.contextmanager
def remove_output(*sources: str) -> Generator[None, None, None]:
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",
"-Dprefix_for_pc_file=${pcfiledir}/../../",
]
if "CMAKE_ARGS" in os.environ:
fcommand = [
c
for c in os.environ["CMAKE_ARGS"].split()
if "DCMAKE_INSTALL_PREFIX" not in c
]
cmd += fcommand
subprocess.run(cmd, check=True, cwd=DIR, stdout=sys.stdout, stderr=sys.stderr)
subprocess.run(
["cmake", "--install", tmpdir],
check=True,
cwd=DIR,
stdout=sys.stdout,
stderr=sys.stderr,
)
# pkgconf-pypi needs pybind11/share/pkgconfig to be importable
Path("pybind11/share/__init__.py").touch()
Path("pybind11/share/pkgconfig/__init__.py").touch()
txt = get_and_replace(setup_py, version=version, extra_cmd=extra_cmd)
code = compile(txt, setup_py, "exec")
exec(code, {"SDist": SDist})

237
tests/extra_python_package/test_files.py

@ -3,16 +3,23 @@ from __future__ import annotations @@ -3,16 +3,23 @@ from __future__ import annotations
import contextlib
import os
import shutil
import string
import subprocess
import sys
import tarfile
import zipfile
from pathlib import Path
from typing import Generator
# These tests must be run explicitly
DIR = os.path.abspath(os.path.dirname(__file__))
MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
DIR = Path(__file__).parent.resolve()
MAIN_DIR = DIR.parent.parent
# Newer pytest has global path setting, but keeping old pytest for now
sys.path.append(str(MAIN_DIR / "tools"))
from make_global import get_global # noqa: E402
HAS_UV = shutil.which("uv") is not None
UV_ARGS = ["--installer=uv"] if HAS_UV else []
@ -120,44 +127,44 @@ py_files = { @@ -120,44 +127,44 @@ py_files = {
}
headers = main_headers | conduit_headers | detail_headers | eigen_headers | stl_headers
src_files = headers | cmake_files | pkgconfig_files
all_files = src_files | py_files
generated_files = cmake_files | pkgconfig_files
all_files = headers | generated_files | py_files
sdist_files = {
"pybind11",
"pybind11/include",
"pybind11/include/pybind11",
"pybind11/include/pybind11/conduit",
"pybind11/include/pybind11/detail",
"pybind11/include/pybind11/eigen",
"pybind11/include/pybind11/stl",
"pybind11/share",
"pybind11/share/cmake",
"pybind11/share/cmake/pybind11",
"pybind11/share/pkgconfig",
"pyproject.toml",
"setup.cfg",
"setup.py",
"LICENSE",
"MANIFEST.in",
"README.rst",
"PKG-INFO",
"SECURITY.md",
}
local_sdist_files = {
".egg-info",
".egg-info/PKG-INFO",
".egg-info/SOURCES.txt",
".egg-info/dependency_links.txt",
".egg-info/not-zip-safe",
".egg-info/top_level.txt",
}
@contextlib.contextmanager
def preserve_file(filename: Path) -> Generator[str, None, None]:
old_stat = filename.stat()
old_file = filename.read_text(encoding="utf-8")
try:
yield old_file
finally:
filename.write_text(old_file, encoding="utf-8")
os.utime(filename, (old_stat.st_atime, old_stat.st_mtime))
@contextlib.contextmanager
def build_global() -> Generator[None, None, None]:
"""
Build global SDist and wheel.
"""
pyproject = MAIN_DIR / "pyproject.toml"
with preserve_file(pyproject):
newer_txt = get_global()
pyproject.write_text(newer_txt, encoding="utf-8")
yield
def read_tz_file(tar: tarfile.TarFile, name: str) -> bytes:
start = tar.getnames()[0] + "/"
start = tar.getnames()[0].split("/")[0] + "/"
inner_file = tar.extractfile(tar.getmember(f"{start}{name}"))
assert inner_file
with contextlib.closing(inner_file) as f:
@ -177,95 +184,58 @@ def test_build_sdist(monkeypatch, tmpdir): @@ -177,95 +184,58 @@ def test_build_sdist(monkeypatch, tmpdir):
)
(sdist,) = tmpdir.visit("*.tar.gz")
version = sdist.basename.split("-")[1][:-7]
with tarfile.open(str(sdist), "r:gz") as tar:
start = tar.getnames()[0] + "/"
version = start[9:-1]
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
(pkg_info_path,) = (n for n in simpler if n.endswith("PKG-INFO"))
setup_py = read_tz_file(tar, "setup.py")
pyproject_toml = read_tz_file(tar, "pyproject.toml")
pkgconfig = read_tz_file(tar, "pybind11/share/pkgconfig/pybind11.pc")
cmake_cfg = read_tz_file(
tar, "pybind11/share/cmake/pybind11/pybind11Config.cmake"
)
pkg_info = read_tz_file(tar, pkg_info_path).decode("utf-8")
assert (
'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")'
in cmake_cfg.decode("utf-8")
)
files = headers | sdist_files
assert files <= simpler
files = {f"pybind11/{n}" for n in all_files}
files |= sdist_files
files |= {f"pybind11{n}" for n in local_sdist_files}
files.add("pybind11.egg-info/entry_points.txt")
files.add("pybind11.egg-info/requires.txt")
assert simpler == files
with open(os.path.join(MAIN_DIR, "tools", "setup_main.py.in"), "rb") as f:
contents = (
string.Template(f.read().decode("utf-8"))
.substitute(version=version, extra_cmd="")
.encode("utf-8")
)
assert setup_py == contents
with open(os.path.join(MAIN_DIR, "tools", "pyproject.toml"), "rb") as f:
contents = f.read()
assert pyproject_toml == contents
simple_version = ".".join(version.split(".")[:3])
pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version).encode("utf-8")
assert normalize_line_endings(pkgconfig) == pkgconfig_expected
assert b'name = "pybind11"' in pyproject_toml
assert "License-Expression: BSD-3-Clause" in pkg_info
assert "License-File: LICENSE" in pkg_info
assert "Provides-Extra: global" in pkg_info
assert f'Requires-Dist: pybind11-global=={version}; extra == "global"' in pkg_info
def test_build_global_dist(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR)
monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1")
subprocess.run(
[sys.executable, "-m", "build", "--sdist", "--outdir", str(tmpdir), *UV_ARGS],
check=True,
)
with build_global():
subprocess.run(
[
sys.executable,
"-m",
"build",
"--sdist",
"--outdir",
str(tmpdir),
*UV_ARGS,
],
check=True,
)
(sdist,) = tmpdir.visit("*.tar.gz")
with tarfile.open(str(sdist), "r:gz") as tar:
start = tar.getnames()[0] + "/"
version = start[16:-1]
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
(pkg_info_path,) = (n for n in simpler if n.endswith("PKG-INFO"))
setup_py = read_tz_file(tar, "setup.py")
pyproject_toml = read_tz_file(tar, "pyproject.toml")
pkgconfig = read_tz_file(tar, "pybind11/share/pkgconfig/pybind11.pc")
cmake_cfg = read_tz_file(
tar, "pybind11/share/cmake/pybind11/pybind11Config.cmake"
)
assert (
'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")'
in cmake_cfg.decode("utf-8")
)
pkg_info = read_tz_file(tar, pkg_info_path).decode("utf-8")
files = {f"pybind11/{n}" for n in all_files}
files |= sdist_files
files |= {f"pybind11_global{n}" for n in local_sdist_files}
assert simpler == files
with open(os.path.join(MAIN_DIR, "tools", "setup_global.py.in"), "rb") as f:
contents = (
string.Template(f.read().decode())
.substitute(version=version, extra_cmd="")
.encode("utf-8")
)
assert setup_py == contents
with open(os.path.join(MAIN_DIR, "tools", "pyproject.toml"), "rb") as f:
contents = f.read()
assert pyproject_toml == contents
files = headers | sdist_files
assert files <= simpler
simple_version = ".".join(version.split(".")[:3])
pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version).encode("utf-8")
assert normalize_line_endings(pkgconfig) == pkgconfig_expected
assert b'name = "pybind11-global"' in pyproject_toml
assert "License-Expression: BSD-3-Clause" in pkg_info
assert "License-File: LICENSE" in pkg_info
assert "Provides-Extra: global" not in pkg_info
assert 'Requires-Dist: pybind11-global; extra == "global"' not in pkg_info
def tests_build_wheel(monkeypatch, tmpdir):
@ -280,47 +250,94 @@ def tests_build_wheel(monkeypatch, tmpdir): @@ -280,47 +250,94 @@ def tests_build_wheel(monkeypatch, tmpdir):
files = {f"pybind11/{n}" for n in all_files}
files |= {
"dist-info/LICENSE",
"dist-info/licenses/LICENSE",
"dist-info/METADATA",
"dist-info/RECORD",
"dist-info/WHEEL",
"dist-info/entry_points.txt",
"dist-info/top_level.txt",
}
with zipfile.ZipFile(str(wheel)) as z:
names = z.namelist()
share = zipfile.Path(z, "pybind11/share")
pkgconfig = (share / "pkgconfig/pybind11.pc").read_text(encoding="utf-8")
cmakeconfig = (share / "cmake/pybind11/pybind11Config.cmake").read_text(
encoding="utf-8"
)
(pkg_info_path,) = (n for n in names if n.endswith("METADATA"))
pkg_info = zipfile.Path(z, pkg_info_path).read_text(encoding="utf-8")
trimmed = {n for n in names if "dist-info" not in n}
trimmed |= {f"dist-info/{n.split('/', 1)[-1]}" for n in names if "dist-info" in n}
assert files == trimmed
assert 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' in cmakeconfig
version = wheel.basename.split("-")[1]
simple_version = ".".join(version.split(".")[:3])
pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version)
assert pkgconfig_expected == pkgconfig
assert "License-Expression: BSD-3-Clause" in pkg_info
assert "License-File: LICENSE" in pkg_info
assert "Provides-Extra: global" in pkg_info
assert f'Requires-Dist: pybind11-global=={version}; extra == "global"' in pkg_info
def tests_build_global_wheel(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR)
monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1")
subprocess.run(
[sys.executable, "-m", "build", "--wheel", "--outdir", str(tmpdir), *UV_ARGS],
check=True,
)
with build_global():
subprocess.run(
[
sys.executable,
"-m",
"build",
"--wheel",
"--outdir",
str(tmpdir),
*UV_ARGS,
],
check=True,
)
(wheel,) = tmpdir.visit("*.whl")
files = {f"data/data/{n}" for n in src_files}
files = {f"data/data/{n}" for n in headers}
files |= {f"data/headers/{n[8:]}" for n in headers}
files |= {f"data/data/{n}" for n in generated_files}
files |= {
"dist-info/LICENSE",
"dist-info/licenses/LICENSE",
"dist-info/METADATA",
"dist-info/WHEEL",
"dist-info/top_level.txt",
"dist-info/RECORD",
}
with zipfile.ZipFile(str(wheel)) as z:
names = z.namelist()
beginning = names[0].split("/", 1)[0].rsplit(".", 1)[0]
share = zipfile.Path(z, f"{beginning}.data/data/share")
pkgconfig = (share / "pkgconfig/pybind11.pc").read_text(encoding="utf-8")
cmakeconfig = (share / "cmake/pybind11/pybind11Config.cmake").read_text(
encoding="utf-8"
)
(pkg_info_path,) = (n for n in names if n.endswith("METADATA"))
pkg_info = zipfile.Path(z, pkg_info_path).read_text(encoding="utf-8")
assert "License-Expression: BSD-3-Clause" in pkg_info
assert "License-File: LICENSE" in pkg_info
assert "Provides-Extra: global" not in pkg_info
assert 'Requires-Dist: pybind11-global; extra == "global"' not in pkg_info
beginning = names[0].split("/", 1)[0].rsplit(".", 1)[0]
trimmed = {n[len(beginning) + 1 :] for n in names}
assert files == trimmed
assert 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' in cmakeconfig
version = wheel.basename.split("-")[1]
simple_version = ".".join(version.split(".")[:3])
pkgconfig_expected = PKGCONFIG.format(VERSION=simple_version)
assert pkgconfig_expected == pkgconfig

1
tests/requirements.txt

@ -15,3 +15,4 @@ scipy~=1.5.4; platform_python_implementation=="CPython" and python_version<"3.10 @@ -15,3 +15,4 @@ scipy~=1.5.4; platform_python_implementation=="CPython" and python_version<"3.10
scipy~=1.8.0; platform_python_implementation=="CPython" and python_version=="3.10" and sys_platform!="win32"
scipy~=1.11.1; platform_python_implementation=="CPython" and python_version>="3.11" and python_version<"3.13" and sys_platform!="win32"
scipy~=1.15.2; platform_python_implementation=="CPython" and python_version=="3.13" and sys_platform!="win32"
tomlkit

33
tools/make_global.py

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
#!/usr/bin/env -S uv run -q
# /// script
# dependencies = ["tomlkit"]
# ///
from __future__ import annotations
from pathlib import Path
import tomlkit
DIR = Path(__file__).parent.resolve()
PYPROJECT = DIR.parent / "pyproject.toml"
def get_global() -> str:
pyproject = tomlkit.parse(PYPROJECT.read_text())
del pyproject["tool"]["scikit-build"]["generate"]
del pyproject["project"]["entry-points"]
del pyproject["project"]["scripts"]
del pyproject["tool"]["scikit-build"]["metadata"]["optional-dependencies"]
pyproject["project"]["name"] = "pybind11-global"
pyproject["tool"]["scikit-build"]["experimental"] = True
pyproject["tool"]["scikit-build"]["wheel"]["install-dir"] = "/data"
pyproject["tool"]["scikit-build"]["wheel"]["packages"] = []
result = tomlkit.dumps(pyproject)
assert isinstance(result, str)
return result
if __name__ == "__main__":
print(get_global())

3
tools/pyproject.toml

@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

66
tools/setup_global.py.in

@ -1,66 +0,0 @@ @@ -1,66 +0,0 @@
#!/usr/bin/env python3
# Setup script for pybind11-global (in the sdist or in tools/setup_global.py in the repository)
# This package is targeted for easy use from CMake.
import glob
import os
import re
# Setuptools has to be before distutils
from setuptools import setup
from distutils.command.install_headers import install_headers
class InstallHeadersNested(install_headers):
def run(self):
headers = self.distribution.headers or []
for header in headers:
# Remove pybind11/include/
short_header = header.split("/", 2)[-1]
dst = os.path.join(self.install_dir, os.path.dirname(short_header))
self.mkpath(dst)
(out, _) = self.copy_file(header, dst)
self.outfiles.append(out)
main_headers = glob.glob("pybind11/include/pybind11/*.h")
conduit_headers = sum([glob.glob(f"pybind11/include/pybind11/conduit/*.{ext}")
for ext in ("h", "txt")], [])
detail_headers = glob.glob("pybind11/include/pybind11/detail/*.h")
eigen_headers = glob.glob("pybind11/include/pybind11/eigen/*.h")
stl_headers = glob.glob("pybind11/include/pybind11/stl/*.h")
cmake_files = glob.glob("pybind11/share/cmake/pybind11/*.cmake")
pkgconfig_files = glob.glob("pybind11/share/pkgconfig/*.pc")
headers = main_headers + conduit_headers + detail_headers + eigen_headers + stl_headers
cmdclass = {"install_headers": InstallHeadersNested}
$extra_cmd
# This will _not_ affect installing from wheels,
# only building wheels or installing from SDist.
# Primarily intended on Windows, where this is sometimes
# customized (for example, conda-forge uses Library/)
base = os.environ.get("PYBIND11_GLOBAL_PREFIX", "")
# Must have a separator
if base and not base.endswith("/"):
base += "/"
setup(
name="pybind11_global",
version="$version",
packages=[],
headers=headers,
data_files=[
(base + "share/cmake/pybind11", cmake_files),
(base + "share/pkgconfig", pkgconfig_files),
(base + "include/pybind11", main_headers),
(base + "include/pybind11/conduit", conduit_headers),
(base + "include/pybind11/detail", detail_headers),
(base + "include/pybind11/eigen", eigen_headers),
(base + "include/pybind11/stl", stl_headers),
],
cmdclass=cmdclass,
)

50
tools/setup_main.py.in

@ -1,50 +0,0 @@ @@ -1,50 +0,0 @@
#!/usr/bin/env python3
# Setup script (in the sdist or in tools/setup_main.py in the repository)
from setuptools import setup
cmdclass = {}
$extra_cmd
setup(
name="pybind11",
version="$version",
download_url='https://github.com/pybind/pybind11/tarball/v$version',
packages=[
"pybind11",
"pybind11.include.pybind11",
"pybind11.include.pybind11.conduit",
"pybind11.include.pybind11.detail",
"pybind11.include.pybind11.eigen",
"pybind11.include.pybind11.stl",
"pybind11.share",
"pybind11.share.cmake.pybind11",
"pybind11.share.pkgconfig",
],
package_data={
"pybind11": ["py.typed"],
"pybind11.include.pybind11": ["*.h"],
"pybind11.include.pybind11.conduit": ["*.h", "*.txt"],
"pybind11.include.pybind11.detail": ["*.h"],
"pybind11.include.pybind11.eigen": ["*.h"],
"pybind11.include.pybind11.stl": ["*.h"],
"pybind11.share.cmake.pybind11": ["*.cmake"],
"pybind11.share.pkgconfig": ["*.pc"],
},
extras_require={
"global": ["pybind11_global==$version"]
},
entry_points={
"console_scripts": [
"pybind11-config = pybind11.__main__:main",
],
"pipx.run": [
"pybind11 = pybind11.__main__:main",
],
"pkg_config": [
"pybind11 = pybind11.share.pkgconfig",
],
},
cmdclass=cmdclass
)
Loading…
Cancel
Save