mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-24 22:25:10 +00:00
feat(cmake): add installation support for pkg-config dependency detection (#4077)
* add installation support for pkg-config dependency detection pkg-config is a buildsystem-agnostic alternative to `pybind11Config.cmake` that can be used from build systems other than cmake. Fixes #230 * tests: add test for pkg config Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com>
This commit is contained in:
parent
14c84654f8
commit
5bdd3d59be
@ -12,6 +12,9 @@
|
||||
#
|
||||
# See https://github.com/pre-commit/pre-commit
|
||||
|
||||
# third-party content
|
||||
exclude: ^tools/JoinPaths.cmake$
|
||||
|
||||
repos:
|
||||
# Standard hooks
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
|
@ -198,6 +198,9 @@ else()
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake")
|
||||
# https://github.com/jtojnar/cmake-snips/#concatenating-paths-when-building-pkg-config-files
|
||||
# TODO: cmake 3.20 adds the cmake_path() function, which obsoletes this snippet
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/tools/JoinPaths.cmake")
|
||||
|
||||
# Relative directory setting
|
||||
if(USE_PYTHON_INCLUDE_DIR AND DEFINED Python_INCLUDE_DIRS)
|
||||
@ -262,6 +265,16 @@ if(PYBIND11_INSTALL)
|
||||
NAMESPACE "pybind11::"
|
||||
DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR})
|
||||
|
||||
# pkg-config support
|
||||
if(NOT prefix_for_pc_file)
|
||||
set(prefix_for_pc_file "${CMAKE_INSTALL_PREFIX}")
|
||||
endif()
|
||||
join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11.pc.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" @ONLY)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc"
|
||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig/")
|
||||
|
||||
# Uninstall target
|
||||
if(PYBIND11_MASTER_PROJECT)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake_uninstall.cmake.in"
|
||||
|
@ -27,7 +27,7 @@ def lint(session: nox.Session) -> None:
|
||||
Lint the codebase (except for clang-format/tidy).
|
||||
"""
|
||||
session.install("pre-commit")
|
||||
session.run("pre-commit", "run", "-a")
|
||||
session.run("pre-commit", "run", "-a", *session.posargs)
|
||||
|
||||
|
||||
@nox.session(python=PYTHON_VERSIONS)
|
||||
@ -58,7 +58,7 @@ def tests_packaging(session: nox.Session) -> None:
|
||||
"""
|
||||
|
||||
session.install("-r", "tests/requirements.txt", "--prefer-binary")
|
||||
session.run("pytest", "tests/extra_python_package")
|
||||
session.run("pytest", "tests/extra_python_package", *session.posargs)
|
||||
|
||||
|
||||
@nox.session(reuse_venv=True)
|
||||
|
@ -6,11 +6,12 @@ if sys.version_info < (3, 6):
|
||||
|
||||
|
||||
from ._version import __version__, version_info
|
||||
from .commands import get_cmake_dir, get_include
|
||||
from .commands import get_cmake_dir, get_include, get_pkgconfig_dir
|
||||
|
||||
__all__ = (
|
||||
"version_info",
|
||||
"__version__",
|
||||
"get_include",
|
||||
"get_cmake_dir",
|
||||
"get_pkgconfig_dir",
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ import argparse
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
from .commands import get_cmake_dir, get_include
|
||||
from .commands import get_cmake_dir, get_include, get_pkgconfig_dir
|
||||
|
||||
|
||||
def print_includes() -> None:
|
||||
@ -36,6 +36,11 @@ def main() -> None:
|
||||
action="store_true",
|
||||
help="Print the CMake module directory, ideal for setting -Dpybind11_ROOT in CMake.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pkgconfigdir",
|
||||
action="store_true",
|
||||
help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if not sys.argv[1:]:
|
||||
parser.print_help()
|
||||
@ -43,6 +48,8 @@ def main() -> None:
|
||||
print_includes()
|
||||
if args.cmakedir:
|
||||
print(get_cmake_dir())
|
||||
if args.pkgconfigdir:
|
||||
print(get_pkgconfig_dir())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -23,3 +23,15 @@ def get_cmake_dir() -> str:
|
||||
|
||||
msg = "pybind11 not installed, installation required to access the CMake files"
|
||||
raise ImportError(msg)
|
||||
|
||||
|
||||
def get_pkgconfig_dir() -> str:
|
||||
"""
|
||||
Return the path to the pybind11 pkgconfig directory.
|
||||
"""
|
||||
pkgconfig_installed_path = os.path.join(DIR, "share", "pkgconfig")
|
||||
if os.path.exists(pkgconfig_installed_path):
|
||||
return pkgconfig_installed_path
|
||||
|
||||
msg = "pybind11 not installed, installation required to access the pkgconfig files"
|
||||
raise ImportError(msg)
|
||||
|
1
setup.py
1
setup.py
@ -127,6 +127,7 @@ with remove_output("pybind11/include", "pybind11/share"):
|
||||
"-DCMAKE_INSTALL_PREFIX=pybind11",
|
||||
"-DBUILD_TESTING=OFF",
|
||||
"-DPYBIND11_NOPYTHON=ON",
|
||||
"-Dprefix_for_pc_file=${pcfiledir}/../../",
|
||||
]
|
||||
if "CMAKE_ARGS" in os.environ:
|
||||
fcommand = [
|
||||
|
@ -12,6 +12,16 @@ import zipfile
|
||||
DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
|
||||
|
||||
PKGCONFIG = """\
|
||||
prefix=${{pcfiledir}}/../../
|
||||
includedir=${{prefix}}/include
|
||||
|
||||
Name: pybind11
|
||||
Description: Seamless operability between C++11 and Python
|
||||
Version: {VERSION}
|
||||
Cflags: -I${{includedir}}
|
||||
"""
|
||||
|
||||
|
||||
main_headers = {
|
||||
"include/pybind11/attr.h",
|
||||
@ -59,6 +69,10 @@ cmake_files = {
|
||||
"share/cmake/pybind11/pybind11Tools.cmake",
|
||||
}
|
||||
|
||||
pkgconfig_files = {
|
||||
"share/pkgconfig/pybind11.pc",
|
||||
}
|
||||
|
||||
py_files = {
|
||||
"__init__.py",
|
||||
"__main__.py",
|
||||
@ -69,7 +83,7 @@ py_files = {
|
||||
}
|
||||
|
||||
headers = main_headers | detail_headers | stl_headers
|
||||
src_files = headers | cmake_files
|
||||
src_files = headers | cmake_files | pkgconfig_files
|
||||
all_files = src_files | py_files
|
||||
|
||||
|
||||
@ -82,6 +96,7 @@ sdist_files = {
|
||||
"pybind11/share",
|
||||
"pybind11/share/cmake",
|
||||
"pybind11/share/cmake/pybind11",
|
||||
"pybind11/share/pkgconfig",
|
||||
"pyproject.toml",
|
||||
"setup.cfg",
|
||||
"setup.py",
|
||||
@ -101,22 +116,25 @@ local_sdist_files = {
|
||||
}
|
||||
|
||||
|
||||
def read_tz_file(tar: tarfile.TarFile, name: str) -> bytes:
|
||||
start = tar.getnames()[0] + "/"
|
||||
inner_file = tar.extractfile(tar.getmember(f"{start}{name}"))
|
||||
assert inner_file
|
||||
with contextlib.closing(inner_file) as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def normalize_line_endings(value: bytes) -> bytes:
|
||||
return value.replace(os.linesep.encode("utf-8"), b"\n")
|
||||
|
||||
|
||||
def test_build_sdist(monkeypatch, tmpdir):
|
||||
|
||||
monkeypatch.chdir(MAIN_DIR)
|
||||
|
||||
out = subprocess.check_output(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"build",
|
||||
"--sdist",
|
||||
"--outdir",
|
||||
str(tmpdir),
|
||||
]
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "build", "--sdist", f"--outdir={tmpdir}"], check=True
|
||||
)
|
||||
if hasattr(out, "decode"):
|
||||
out = out.decode()
|
||||
|
||||
(sdist,) = tmpdir.visit("*.tar.gz")
|
||||
|
||||
@ -125,25 +143,17 @@ def test_build_sdist(monkeypatch, tmpdir):
|
||||
version = start[9:-1]
|
||||
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
|
||||
|
||||
with contextlib.closing(
|
||||
tar.extractfile(tar.getmember(start + "setup.py"))
|
||||
) as f:
|
||||
setup_py = f.read()
|
||||
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"
|
||||
)
|
||||
|
||||
with contextlib.closing(
|
||||
tar.extractfile(tar.getmember(start + "pyproject.toml"))
|
||||
) as f:
|
||||
pyproject_toml = f.read()
|
||||
|
||||
with contextlib.closing(
|
||||
tar.extractfile(
|
||||
tar.getmember(
|
||||
start + "pybind11/share/cmake/pybind11/pybind11Config.cmake"
|
||||
)
|
||||
)
|
||||
) as f:
|
||||
contents = f.read().decode("utf8")
|
||||
assert 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' in contents
|
||||
assert (
|
||||
'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")'
|
||||
in cmake_cfg.decode("utf-8")
|
||||
)
|
||||
|
||||
files = {f"pybind11/{n}" for n in all_files}
|
||||
files |= sdist_files
|
||||
@ -154,9 +164,9 @@ def test_build_sdist(monkeypatch, tmpdir):
|
||||
|
||||
with open(os.path.join(MAIN_DIR, "tools", "setup_main.py.in"), "rb") as f:
|
||||
contents = (
|
||||
string.Template(f.read().decode())
|
||||
string.Template(f.read().decode("utf-8"))
|
||||
.substitute(version=version, extra_cmd="")
|
||||
.encode()
|
||||
.encode("utf-8")
|
||||
)
|
||||
assert setup_py == contents
|
||||
|
||||
@ -164,25 +174,19 @@ def test_build_sdist(monkeypatch, tmpdir):
|
||||
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
|
||||
|
||||
|
||||
def test_build_global_dist(monkeypatch, tmpdir):
|
||||
|
||||
monkeypatch.chdir(MAIN_DIR)
|
||||
monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1")
|
||||
out = subprocess.check_output(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"build",
|
||||
"--sdist",
|
||||
"--outdir",
|
||||
str(tmpdir),
|
||||
]
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "build", "--sdist", "--outdir", str(tmpdir)], check=True
|
||||
)
|
||||
|
||||
if hasattr(out, "decode"):
|
||||
out = out.decode()
|
||||
|
||||
(sdist,) = tmpdir.visit("*.tar.gz")
|
||||
|
||||
with tarfile.open(str(sdist), "r:gz") as tar:
|
||||
@ -190,15 +194,17 @@ def test_build_global_dist(monkeypatch, tmpdir):
|
||||
version = start[16:-1]
|
||||
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
|
||||
|
||||
with contextlib.closing(
|
||||
tar.extractfile(tar.getmember(start + "setup.py"))
|
||||
) as f:
|
||||
setup_py = f.read()
|
||||
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"
|
||||
)
|
||||
|
||||
with contextlib.closing(
|
||||
tar.extractfile(tar.getmember(start + "pyproject.toml"))
|
||||
) as f:
|
||||
pyproject_toml = f.read()
|
||||
assert (
|
||||
'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")'
|
||||
in cmake_cfg.decode("utf-8")
|
||||
)
|
||||
|
||||
files = {f"pybind11/{n}" for n in all_files}
|
||||
files |= sdist_files
|
||||
@ -209,7 +215,7 @@ def test_build_global_dist(monkeypatch, tmpdir):
|
||||
contents = (
|
||||
string.Template(f.read().decode())
|
||||
.substitute(version=version, extra_cmd="")
|
||||
.encode()
|
||||
.encode("utf-8")
|
||||
)
|
||||
assert setup_py == contents
|
||||
|
||||
@ -217,12 +223,16 @@ def test_build_global_dist(monkeypatch, tmpdir):
|
||||
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
|
||||
|
||||
|
||||
def tests_build_wheel(monkeypatch, tmpdir):
|
||||
monkeypatch.chdir(MAIN_DIR)
|
||||
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)]
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)], check=True
|
||||
)
|
||||
|
||||
(wheel,) = tmpdir.visit("*.whl")
|
||||
@ -249,8 +259,8 @@ def tests_build_global_wheel(monkeypatch, tmpdir):
|
||||
monkeypatch.chdir(MAIN_DIR)
|
||||
monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1")
|
||||
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)]
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)], check=True
|
||||
)
|
||||
|
||||
(wheel,) = tmpdir.visit("*.whl")
|
||||
|
23
tools/JoinPaths.cmake
Normal file
23
tools/JoinPaths.cmake
Normal file
@ -0,0 +1,23 @@
|
||||
# This module provides function for joining paths
|
||||
# known from most languages
|
||||
#
|
||||
# SPDX-License-Identifier: (MIT OR CC0-1.0)
|
||||
# Copyright 2020 Jan Tojnar
|
||||
# https://github.com/jtojnar/cmake-snips
|
||||
#
|
||||
# Modelled after Python’s os.path.join
|
||||
# https://docs.python.org/3.7/library/os.path.html#os.path.join
|
||||
# Windows not supported
|
||||
function(join_paths joined_path first_path_segment)
|
||||
set(temp_path "${first_path_segment}")
|
||||
foreach(current_segment IN LISTS ARGN)
|
||||
if(NOT ("${current_segment}" STREQUAL ""))
|
||||
if(IS_ABSOLUTE "${current_segment}")
|
||||
set(temp_path "${current_segment}")
|
||||
else()
|
||||
set(temp_path "${temp_path}/${current_segment}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
set(${joined_path} "${temp_path}" PARENT_SCOPE)
|
||||
endfunction()
|
7
tools/pybind11.pc.in
Normal file
7
tools/pybind11.pc.in
Normal file
@ -0,0 +1,7 @@
|
||||
prefix=@prefix_for_pc_file@
|
||||
includedir=@includedir_for_pc_file@
|
||||
|
||||
Name: @PROJECT_NAME@
|
||||
Description: Seamless operability between C++11 and Python
|
||||
Version: @PROJECT_VERSION@
|
||||
Cflags: -I${includedir}
|
@ -29,6 +29,7 @@ main_headers = glob.glob("pybind11/include/pybind11/*.h")
|
||||
detail_headers = glob.glob("pybind11/include/pybind11/detail/*.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 + detail_headers + stl_headers
|
||||
|
||||
cmdclass = {"install_headers": InstallHeadersNested}
|
||||
@ -51,6 +52,7 @@ setup(
|
||||
headers=headers,
|
||||
data_files=[
|
||||
(base + "share/cmake/pybind11", cmake_files),
|
||||
(base + "share/pkgconfig", pkgconfig_files),
|
||||
(base + "include/pybind11", main_headers),
|
||||
(base + "include/pybind11/detail", detail_headers),
|
||||
(base + "include/pybind11/stl", stl_headers),
|
||||
|
@ -17,6 +17,7 @@ setup(
|
||||
"pybind11.include.pybind11.detail",
|
||||
"pybind11.include.pybind11.stl",
|
||||
"pybind11.share.cmake.pybind11",
|
||||
"pybind11.share.pkgconfig",
|
||||
],
|
||||
package_data={
|
||||
"pybind11": ["py.typed"],
|
||||
@ -24,6 +25,7 @@ setup(
|
||||
"pybind11.include.pybind11.detail": ["*.h"],
|
||||
"pybind11.include.pybind11.stl": ["*.h"],
|
||||
"pybind11.share.cmake.pybind11": ["*.cmake"],
|
||||
"pybind11.share.pkgconfig": ["*.pc"],
|
||||
},
|
||||
extras_require={
|
||||
"global": ["pybind11_global==$version"]
|
||||
|
Loading…
Reference in New Issue
Block a user