mirror of https://github.com/google/benchmark.git
Add Python bindings build using bzlmod (#1764)
* Add a bzlmod Python bindings build Uses the newly started `@nanobind_bazel` project to build nanobind extensions. This means that we can drop all in-tree custom build defs and build files for nanobind and the C++ Python headers. Additionally, the temporary WORKSPACE overwrite hack naturally goes away due to the WORKSPACE system being obsolete. * Bump ruff -> v0.3.1, change ruff settings The latest minor releases incurred some formatting and configuration changes, this commit rolls them out. --------- Co-authored-by: dominic <510002+dmah42@users.noreply.github.com>
This commit is contained in:
parent
c64b144f42
commit
eaafe694d2
|
@ -11,7 +11,7 @@ repos:
|
||||||
types_or: [ python, pyi ]
|
types_or: [ python, pyi ]
|
||||||
args: [ "--ignore-missing-imports", "--scripts-are-modules" ]
|
args: [ "--ignore-missing-imports", "--scripts-are-modules" ]
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.1.13
|
rev: v0.3.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [ --fix, --exit-non-zero-on-fix ]
|
args: [ --fix, --exit-non-zero-on-fix ]
|
||||||
|
|
22
MODULE.bazel
22
MODULE.bazel
|
@ -4,11 +4,11 @@ module(
|
||||||
)
|
)
|
||||||
|
|
||||||
bazel_dep(name = "bazel_skylib", version = "1.5.0")
|
bazel_dep(name = "bazel_skylib", version = "1.5.0")
|
||||||
bazel_dep(name = "platforms", version = "0.0.7")
|
bazel_dep(name = "platforms", version = "0.0.8")
|
||||||
bazel_dep(name = "rules_foreign_cc", version = "0.10.1")
|
bazel_dep(name = "rules_foreign_cc", version = "0.10.1")
|
||||||
bazel_dep(name = "rules_cc", version = "0.0.9")
|
bazel_dep(name = "rules_cc", version = "0.0.9")
|
||||||
|
|
||||||
bazel_dep(name = "rules_python", version = "0.27.1", dev_dependency = True)
|
bazel_dep(name = "rules_python", version = "0.31.0", dev_dependency = True)
|
||||||
bazel_dep(name = "googletest", version = "1.12.1", dev_dependency = True, repo_name = "com_google_googletest")
|
bazel_dep(name = "googletest", version = "1.12.1", dev_dependency = True, repo_name = "com_google_googletest")
|
||||||
|
|
||||||
bazel_dep(name = "libpfm", version = "4.11.0")
|
bazel_dep(name = "libpfm", version = "4.11.0")
|
||||||
|
@ -19,7 +19,18 @@ bazel_dep(name = "libpfm", version = "4.11.0")
|
||||||
# of relying on the changing default version from rules_python.
|
# of relying on the changing default version from rules_python.
|
||||||
|
|
||||||
python = use_extension("@rules_python//python/extensions:python.bzl", "python", dev_dependency = True)
|
python = use_extension("@rules_python//python/extensions:python.bzl", "python", dev_dependency = True)
|
||||||
|
python.toolchain(python_version = "3.8")
|
||||||
python.toolchain(python_version = "3.9")
|
python.toolchain(python_version = "3.9")
|
||||||
|
python.toolchain(python_version = "3.10")
|
||||||
|
python.toolchain(python_version = "3.11")
|
||||||
|
python.toolchain(
|
||||||
|
is_default = True,
|
||||||
|
python_version = "3.12",
|
||||||
|
)
|
||||||
|
use_repo(
|
||||||
|
python,
|
||||||
|
python = "python_versions",
|
||||||
|
)
|
||||||
|
|
||||||
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip", dev_dependency = True)
|
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip", dev_dependency = True)
|
||||||
pip.parse(
|
pip.parse(
|
||||||
|
@ -30,3 +41,10 @@ pip.parse(
|
||||||
use_repo(pip, "tools_pip_deps")
|
use_repo(pip, "tools_pip_deps")
|
||||||
|
|
||||||
# -- bazel_dep definitions -- #
|
# -- bazel_dep definitions -- #
|
||||||
|
|
||||||
|
bazel_dep(name = "nanobind_bazel", version = "", dev_dependency = True)
|
||||||
|
git_override(
|
||||||
|
module_name = "nanobind_bazel",
|
||||||
|
commit = "97e3db2744d3f5da244a0846a0644ffb074b4880",
|
||||||
|
remote = "https://github.com/nicholasjng/nanobind-bazel",
|
||||||
|
)
|
||||||
|
|
|
@ -22,9 +22,3 @@ pip_parse(
|
||||||
load("@tools_pip_deps//:requirements.bzl", "install_deps")
|
load("@tools_pip_deps//:requirements.bzl", "install_deps")
|
||||||
|
|
||||||
install_deps()
|
install_deps()
|
||||||
|
|
||||||
new_local_repository(
|
|
||||||
name = "python_headers",
|
|
||||||
build_file = "@//bindings/python:python_headers.BUILD",
|
|
||||||
path = "<PYTHON_INCLUDE_PATH>", # May be overwritten by setup.py.
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
exports_files(glob(["*.BUILD"]))
|
|
||||||
|
|
||||||
exports_files(["build_defs.bzl"])
|
|
|
@ -1,29 +0,0 @@
|
||||||
"""
|
|
||||||
This file contains some build definitions for C++ extensions used in the Google Benchmark Python bindings.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_SHARED_LIB_SUFFIX = {
|
|
||||||
"//conditions:default": ".so",
|
|
||||||
"//:windows": ".dll",
|
|
||||||
}
|
|
||||||
|
|
||||||
def py_extension(name, srcs, hdrs = [], copts = [], features = [], deps = []):
|
|
||||||
for shared_lib_suffix in _SHARED_LIB_SUFFIX.values():
|
|
||||||
shared_lib_name = name + shared_lib_suffix
|
|
||||||
native.cc_binary(
|
|
||||||
name = shared_lib_name,
|
|
||||||
linkshared = True,
|
|
||||||
linkstatic = True,
|
|
||||||
srcs = srcs + hdrs,
|
|
||||||
copts = copts,
|
|
||||||
features = features,
|
|
||||||
deps = deps,
|
|
||||||
)
|
|
||||||
|
|
||||||
return native.py_library(
|
|
||||||
name = name,
|
|
||||||
data = select({
|
|
||||||
platform: [name + shared_lib_suffix]
|
|
||||||
for platform, shared_lib_suffix in _SHARED_LIB_SUFFIX.items()
|
|
||||||
}),
|
|
||||||
)
|
|
|
@ -1,4 +1,4 @@
|
||||||
load("//bindings/python:build_defs.bzl", "py_extension")
|
load("@nanobind_bazel//:build_defs.bzl", "nanobind_extension")
|
||||||
|
|
||||||
py_library(
|
py_library(
|
||||||
name = "google_benchmark",
|
name = "google_benchmark",
|
||||||
|
@ -9,22 +9,10 @@ py_library(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
py_extension(
|
nanobind_extension(
|
||||||
name = "_benchmark",
|
name = "_benchmark",
|
||||||
srcs = ["benchmark.cc"],
|
srcs = ["benchmark.cc"],
|
||||||
copts = [
|
deps = ["//:benchmark"],
|
||||||
"-fexceptions",
|
|
||||||
"-fno-strict-aliasing",
|
|
||||||
],
|
|
||||||
features = [
|
|
||||||
"-use_header_modules",
|
|
||||||
"-parse_headers",
|
|
||||||
],
|
|
||||||
deps = [
|
|
||||||
"//:benchmark",
|
|
||||||
"@nanobind",
|
|
||||||
"@python_headers",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
py_test(
|
py_test(
|
||||||
|
|
|
@ -26,6 +26,7 @@ Example usage:
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
benchmark.main()
|
benchmark.main()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
from absl import app
|
from absl import app
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
load("@bazel_skylib//lib:selects.bzl", "selects")
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
config_setting(
|
|
||||||
name = "msvc_compiler",
|
|
||||||
flag_values = {"@bazel_tools//tools/cpp:compiler": "msvc-cl"},
|
|
||||||
)
|
|
||||||
|
|
||||||
selects.config_setting_group(
|
|
||||||
name = "winplusmsvc",
|
|
||||||
match_all = [
|
|
||||||
"@platforms//os:windows",
|
|
||||||
":msvc_compiler",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
cc_library(
|
|
||||||
name = "nanobind",
|
|
||||||
srcs = glob([
|
|
||||||
"src/*.cpp",
|
|
||||||
]),
|
|
||||||
additional_linker_inputs = select({
|
|
||||||
"@platforms//os:macos": [":cmake/darwin-ld-cpython.sym"],
|
|
||||||
"//conditions:default": [],
|
|
||||||
}),
|
|
||||||
copts = select({
|
|
||||||
":msvc_compiler": [
|
|
||||||
"/EHsc", # exceptions
|
|
||||||
"/Os", # size optimizations
|
|
||||||
"/GL", # LTO / whole program optimization
|
|
||||||
],
|
|
||||||
# these should work on both clang and gcc.
|
|
||||||
"//conditions:default": [
|
|
||||||
"-fexceptions",
|
|
||||||
"-flto",
|
|
||||||
"-Os",
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
includes = [
|
|
||||||
"ext/robin_map/include",
|
|
||||||
"include",
|
|
||||||
],
|
|
||||||
linkopts = select({
|
|
||||||
":winplusmsvc": ["/LTGC"], # Windows + MSVC.
|
|
||||||
"@platforms//os:macos": ["-Wl,@$(location :cmake/darwin-ld-cpython.sym)"], # Apple.
|
|
||||||
"//conditions:default": [],
|
|
||||||
}),
|
|
||||||
textual_hdrs = glob(
|
|
||||||
[
|
|
||||||
"include/**/*.h",
|
|
||||||
"src/*.h",
|
|
||||||
"ext/robin_map/include/tsl/*.h",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
deps = ["@python_headers"],
|
|
||||||
)
|
|
|
@ -1,10 +0,0 @@
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
cc_library(
|
|
||||||
name = "python_headers",
|
|
||||||
hdrs = glob(["**/*.h"]),
|
|
||||||
includes = ["."],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
|
@ -75,11 +75,12 @@ src = ["bindings/python"]
|
||||||
line-length = 80
|
line-length = 80
|
||||||
target-version = "py311"
|
target-version = "py311"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
# Enable pycodestyle (`E`, `W`), Pyflakes (`F`), and isort (`I`) codes by default.
|
# Enable pycodestyle (`E`, `W`), Pyflakes (`F`), and isort (`I`) codes by default.
|
||||||
select = ["E", "F", "I", "W"]
|
select = ["E", "F", "I", "W"]
|
||||||
ignore = [
|
ignore = [
|
||||||
"E501", # line too long
|
"E501", # line too long
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.isort]
|
[tool.ruff.lint.isort]
|
||||||
combine-as-imports = true
|
combine-as-imports = true
|
||||||
|
|
118
setup.py
118
setup.py
|
@ -1,46 +1,27 @@
|
||||||
import contextlib
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
import sysconfig
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Generator
|
from typing import Any
|
||||||
|
|
||||||
import setuptools
|
import setuptools
|
||||||
from setuptools.command import build_ext
|
from setuptools.command import build_ext
|
||||||
|
|
||||||
PYTHON_INCLUDE_PATH_PLACEHOLDER = "<PYTHON_INCLUDE_PATH>"
|
|
||||||
|
|
||||||
IS_WINDOWS = platform.system() == "Windows"
|
IS_WINDOWS = platform.system() == "Windows"
|
||||||
IS_MAC = platform.system() == "Darwin"
|
IS_MAC = platform.system() == "Darwin"
|
||||||
|
|
||||||
|
# hardcoded SABI-related options. Requires that each Python interpreter
|
||||||
@contextlib.contextmanager
|
# (hermetic or not) participating is of the same major-minor version.
|
||||||
def temp_fill_include_path(fp: str) -> Generator[None, None, None]:
|
version_tuple = tuple(int(i) for i in platform.python_version_tuple())
|
||||||
"""Temporarily set the Python include path in a file."""
|
py_limited_api = version_tuple >= (3, 12)
|
||||||
with open(fp, "r+") as f:
|
options = {"bdist_wheel": {"py_limited_api": "cp312"}} if py_limited_api else {}
|
||||||
try:
|
|
||||||
content = f.read()
|
|
||||||
replaced = content.replace(
|
|
||||||
PYTHON_INCLUDE_PATH_PLACEHOLDER,
|
|
||||||
Path(sysconfig.get_paths()["include"]).as_posix(),
|
|
||||||
)
|
|
||||||
f.seek(0)
|
|
||||||
f.write(replaced)
|
|
||||||
f.truncate()
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
# revert to the original content after exit
|
|
||||||
f.seek(0)
|
|
||||||
f.write(content)
|
|
||||||
f.truncate()
|
|
||||||
|
|
||||||
|
|
||||||
class BazelExtension(setuptools.Extension):
|
class BazelExtension(setuptools.Extension):
|
||||||
"""A C/C++ extension that is defined as a Bazel BUILD target."""
|
"""A C/C++ extension that is defined as a Bazel BUILD target."""
|
||||||
|
|
||||||
def __init__(self, name: str, bazel_target: str):
|
def __init__(self, name: str, bazel_target: str, **kwargs: Any):
|
||||||
super().__init__(name=name, sources=[])
|
super().__init__(name=name, sources=[], **kwargs)
|
||||||
|
|
||||||
self.bazel_target = bazel_target
|
self.bazel_target = bazel_target
|
||||||
stripped_target = bazel_target.split("//")[-1]
|
stripped_target = bazel_target.split("//")[-1]
|
||||||
|
@ -67,49 +48,58 @@ class BuildBazelExtension(build_ext.build_ext):
|
||||||
|
|
||||||
def bazel_build(self, ext: BazelExtension) -> None:
|
def bazel_build(self, ext: BazelExtension) -> None:
|
||||||
"""Runs the bazel build to create the package."""
|
"""Runs the bazel build to create the package."""
|
||||||
with temp_fill_include_path("WORKSPACE"):
|
temp_path = Path(self.build_temp)
|
||||||
temp_path = Path(self.build_temp)
|
# omit the patch version to avoid build errors if the toolchain is not
|
||||||
|
# yet registered in the current @rules_python version.
|
||||||
|
# patch version differences should be fine.
|
||||||
|
python_version = ".".join(platform.python_version_tuple()[:2])
|
||||||
|
|
||||||
bazel_argv = [
|
bazel_argv = [
|
||||||
"bazel",
|
"bazel",
|
||||||
"build",
|
"build",
|
||||||
ext.bazel_target,
|
ext.bazel_target,
|
||||||
"--enable_bzlmod=false",
|
f"--symlink_prefix={temp_path / 'bazel-'}",
|
||||||
f"--symlink_prefix={temp_path / 'bazel-'}",
|
f"--compilation_mode={'dbg' if self.debug else 'opt'}",
|
||||||
f"--compilation_mode={'dbg' if self.debug else 'opt'}",
|
# C++17 is required by nanobind
|
||||||
# C++17 is required by nanobind
|
f"--cxxopt={'/std:c++17' if IS_WINDOWS else '-std=c++17'}",
|
||||||
f"--cxxopt={'/std:c++17' if IS_WINDOWS else '-std=c++17'}",
|
f"--@rules_python//python/config_settings:python_version={python_version}",
|
||||||
]
|
]
|
||||||
|
|
||||||
if IS_WINDOWS:
|
if ext.py_limited_api:
|
||||||
# Link with python*.lib.
|
bazel_argv += ["--@nanobind_bazel//:py-limited-api=cp312"]
|
||||||
for library_dir in self.library_dirs:
|
|
||||||
bazel_argv.append("--linkopt=/LIBPATH:" + library_dir)
|
|
||||||
elif IS_MAC:
|
|
||||||
if platform.machine() == "x86_64":
|
|
||||||
# C++17 needs macOS 10.14 at minimum
|
|
||||||
bazel_argv.append("--macos_minimum_os=10.14")
|
|
||||||
|
|
||||||
# cross-compilation for Mac ARM64 on GitHub Mac x86 runners.
|
if IS_WINDOWS:
|
||||||
# ARCHFLAGS is set by cibuildwheel before macOS wheel builds.
|
# Link with python*.lib.
|
||||||
archflags = os.getenv("ARCHFLAGS", "")
|
for library_dir in self.library_dirs:
|
||||||
if "arm64" in archflags:
|
bazel_argv.append("--linkopt=/LIBPATH:" + library_dir)
|
||||||
bazel_argv.append("--cpu=darwin_arm64")
|
elif IS_MAC:
|
||||||
bazel_argv.append("--macos_cpus=arm64")
|
if platform.machine() == "x86_64":
|
||||||
|
# C++17 needs macOS 10.14 at minimum
|
||||||
|
bazel_argv.append("--macos_minimum_os=10.14")
|
||||||
|
|
||||||
elif platform.machine() == "arm64":
|
# cross-compilation for Mac ARM64 on GitHub Mac x86 runners.
|
||||||
bazel_argv.append("--macos_minimum_os=11.0")
|
# ARCHFLAGS is set by cibuildwheel before macOS wheel builds.
|
||||||
|
archflags = os.getenv("ARCHFLAGS", "")
|
||||||
|
if "arm64" in archflags:
|
||||||
|
bazel_argv.append("--cpu=darwin_arm64")
|
||||||
|
bazel_argv.append("--macos_cpus=arm64")
|
||||||
|
|
||||||
self.spawn(bazel_argv)
|
elif platform.machine() == "arm64":
|
||||||
|
bazel_argv.append("--macos_minimum_os=11.0")
|
||||||
|
|
||||||
shared_lib_suffix = ".dll" if IS_WINDOWS else ".so"
|
self.spawn(bazel_argv)
|
||||||
ext_name = ext.target_name + shared_lib_suffix
|
|
||||||
ext_bazel_bin_path = (
|
|
||||||
temp_path / "bazel-bin" / ext.relpath / ext_name
|
|
||||||
)
|
|
||||||
|
|
||||||
ext_dest_path = Path(self.get_ext_fullpath(ext.name))
|
if IS_WINDOWS:
|
||||||
shutil.copyfile(ext_bazel_bin_path, ext_dest_path)
|
suffix = ".pyd"
|
||||||
|
else:
|
||||||
|
suffix = ".abi3.so" if ext.py_limited_api else ".so"
|
||||||
|
|
||||||
|
ext_name = ext.target_name + suffix
|
||||||
|
ext_bazel_bin_path = temp_path / "bazel-bin" / ext.relpath / ext_name
|
||||||
|
ext_dest_path = Path(self.get_ext_fullpath(ext.name)).with_name(
|
||||||
|
ext_name
|
||||||
|
)
|
||||||
|
shutil.copyfile(ext_bazel_bin_path, ext_dest_path)
|
||||||
|
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
|
@ -118,6 +108,8 @@ setuptools.setup(
|
||||||
BazelExtension(
|
BazelExtension(
|
||||||
name="google_benchmark._benchmark",
|
name="google_benchmark._benchmark",
|
||||||
bazel_target="//bindings/python/google_benchmark:_benchmark",
|
bazel_target="//bindings/python/google_benchmark:_benchmark",
|
||||||
|
py_limited_api=py_limited_api,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
options=options,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""util.py - General utilities for running, loading, and processing benchmarks
|
"""util.py - General utilities for running, loading, and processing benchmarks"""
|
||||||
"""
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -37,7 +37,7 @@ def is_executable_file(filename):
|
||||||
elif sys.platform.startswith("win"):
|
elif sys.platform.startswith("win"):
|
||||||
return magic_bytes == b"MZ"
|
return magic_bytes == b"MZ"
|
||||||
else:
|
else:
|
||||||
return magic_bytes == b"\x7FELF"
|
return magic_bytes == b"\x7fELF"
|
||||||
|
|
||||||
|
|
||||||
def is_json_file(filename):
|
def is_json_file(filename):
|
||||||
|
|
Loading…
Reference in New Issue