1*dbb99499SAndroid Build Coastguard Workerimport contextlib 2*dbb99499SAndroid Build Coastguard Workerimport os 3*dbb99499SAndroid Build Coastguard Workerimport platform 4*dbb99499SAndroid Build Coastguard Workerimport re 5*dbb99499SAndroid Build Coastguard Workerimport shutil 6*dbb99499SAndroid Build Coastguard Workerfrom pathlib import Path 7*dbb99499SAndroid Build Coastguard Workerfrom typing import Any, Generator 8*dbb99499SAndroid Build Coastguard Worker 9*dbb99499SAndroid Build Coastguard Workerimport setuptools 10*dbb99499SAndroid Build Coastguard Workerfrom setuptools.command import build_ext 11*dbb99499SAndroid Build Coastguard Worker 12*dbb99499SAndroid Build Coastguard WorkerIS_WINDOWS = platform.system() == "Windows" 13*dbb99499SAndroid Build Coastguard WorkerIS_MAC = platform.system() == "Darwin" 14*dbb99499SAndroid Build Coastguard WorkerIS_LINUX = platform.system() == "Linux" 15*dbb99499SAndroid Build Coastguard Worker 16*dbb99499SAndroid Build Coastguard Worker# hardcoded SABI-related options. Requires that each Python interpreter 17*dbb99499SAndroid Build Coastguard Worker# (hermetic or not) participating is of the same major-minor version. 18*dbb99499SAndroid Build Coastguard Workerversion_tuple = tuple(int(i) for i in platform.python_version_tuple()) 19*dbb99499SAndroid Build Coastguard Workerpy_limited_api = version_tuple >= (3, 12) 20*dbb99499SAndroid Build Coastguard Workeroptions = {"bdist_wheel": {"py_limited_api": "cp312"}} if py_limited_api else {} 21*dbb99499SAndroid Build Coastguard Worker 22*dbb99499SAndroid Build Coastguard Worker 23*dbb99499SAndroid Build Coastguard Workerdef is_cibuildwheel() -> bool: 24*dbb99499SAndroid Build Coastguard Worker return os.getenv("CIBUILDWHEEL") is not None 25*dbb99499SAndroid Build Coastguard Worker 26*dbb99499SAndroid Build Coastguard Worker 27*dbb99499SAndroid Build Coastguard Worker@contextlib.contextmanager 28*dbb99499SAndroid Build Coastguard Workerdef _maybe_patch_toolchains() -> Generator[None, None, None]: 29*dbb99499SAndroid Build Coastguard Worker """ 30*dbb99499SAndroid Build Coastguard Worker Patch rules_python toolchains to ignore root user error 31*dbb99499SAndroid Build Coastguard Worker when run in a Docker container on Linux in cibuildwheel. 32*dbb99499SAndroid Build Coastguard Worker """ 33*dbb99499SAndroid Build Coastguard Worker 34*dbb99499SAndroid Build Coastguard Worker def fmt_toolchain_args(matchobj): 35*dbb99499SAndroid Build Coastguard Worker suffix = "ignore_root_user_error = True" 36*dbb99499SAndroid Build Coastguard Worker callargs = matchobj.group(1) 37*dbb99499SAndroid Build Coastguard Worker # toolchain def is broken over multiple lines 38*dbb99499SAndroid Build Coastguard Worker if callargs.endswith("\n"): 39*dbb99499SAndroid Build Coastguard Worker callargs = callargs + " " + suffix + ",\n" 40*dbb99499SAndroid Build Coastguard Worker # toolchain def is on one line. 41*dbb99499SAndroid Build Coastguard Worker else: 42*dbb99499SAndroid Build Coastguard Worker callargs = callargs + ", " + suffix 43*dbb99499SAndroid Build Coastguard Worker return "python.toolchain(" + callargs + ")" 44*dbb99499SAndroid Build Coastguard Worker 45*dbb99499SAndroid Build Coastguard Worker CIBW_LINUX = is_cibuildwheel() and IS_LINUX 46*dbb99499SAndroid Build Coastguard Worker try: 47*dbb99499SAndroid Build Coastguard Worker if CIBW_LINUX: 48*dbb99499SAndroid Build Coastguard Worker module_bazel = Path("MODULE.bazel") 49*dbb99499SAndroid Build Coastguard Worker content: str = module_bazel.read_text() 50*dbb99499SAndroid Build Coastguard Worker module_bazel.write_text( 51*dbb99499SAndroid Build Coastguard Worker re.sub( 52*dbb99499SAndroid Build Coastguard Worker r"python.toolchain\(([\w\"\s,.=]*)\)", 53*dbb99499SAndroid Build Coastguard Worker fmt_toolchain_args, 54*dbb99499SAndroid Build Coastguard Worker content, 55*dbb99499SAndroid Build Coastguard Worker ) 56*dbb99499SAndroid Build Coastguard Worker ) 57*dbb99499SAndroid Build Coastguard Worker yield 58*dbb99499SAndroid Build Coastguard Worker finally: 59*dbb99499SAndroid Build Coastguard Worker if CIBW_LINUX: 60*dbb99499SAndroid Build Coastguard Worker module_bazel.write_text(content) 61*dbb99499SAndroid Build Coastguard Worker 62*dbb99499SAndroid Build Coastguard Worker 63*dbb99499SAndroid Build Coastguard Workerclass BazelExtension(setuptools.Extension): 64*dbb99499SAndroid Build Coastguard Worker """A C/C++ extension that is defined as a Bazel BUILD target.""" 65*dbb99499SAndroid Build Coastguard Worker 66*dbb99499SAndroid Build Coastguard Worker def __init__(self, name: str, bazel_target: str, **kwargs: Any): 67*dbb99499SAndroid Build Coastguard Worker super().__init__(name=name, sources=[], **kwargs) 68*dbb99499SAndroid Build Coastguard Worker 69*dbb99499SAndroid Build Coastguard Worker self.bazel_target = bazel_target 70*dbb99499SAndroid Build Coastguard Worker stripped_target = bazel_target.split("//")[-1] 71*dbb99499SAndroid Build Coastguard Worker self.relpath, self.target_name = stripped_target.split(":") 72*dbb99499SAndroid Build Coastguard Worker 73*dbb99499SAndroid Build Coastguard Worker 74*dbb99499SAndroid Build Coastguard Workerclass BuildBazelExtension(build_ext.build_ext): 75*dbb99499SAndroid Build Coastguard Worker """A command that runs Bazel to build a C/C++ extension.""" 76*dbb99499SAndroid Build Coastguard Worker 77*dbb99499SAndroid Build Coastguard Worker def run(self): 78*dbb99499SAndroid Build Coastguard Worker for ext in self.extensions: 79*dbb99499SAndroid Build Coastguard Worker self.bazel_build(ext) 80*dbb99499SAndroid Build Coastguard Worker super().run() 81*dbb99499SAndroid Build Coastguard Worker # explicitly call `bazel shutdown` for graceful exit 82*dbb99499SAndroid Build Coastguard Worker self.spawn(["bazel", "shutdown"]) 83*dbb99499SAndroid Build Coastguard Worker 84*dbb99499SAndroid Build Coastguard Worker def copy_extensions_to_source(self): 85*dbb99499SAndroid Build Coastguard Worker """ 86*dbb99499SAndroid Build Coastguard Worker Copy generated extensions into the source tree. 87*dbb99499SAndroid Build Coastguard Worker This is done in the ``bazel_build`` method, so it's not necessary to 88*dbb99499SAndroid Build Coastguard Worker do again in the `build_ext` base class. 89*dbb99499SAndroid Build Coastguard Worker """ 90*dbb99499SAndroid Build Coastguard Worker pass 91*dbb99499SAndroid Build Coastguard Worker 92*dbb99499SAndroid Build Coastguard Worker def bazel_build(self, ext: BazelExtension) -> None: 93*dbb99499SAndroid Build Coastguard Worker """Runs the bazel build to create the package.""" 94*dbb99499SAndroid Build Coastguard Worker temp_path = Path(self.build_temp) 95*dbb99499SAndroid Build Coastguard Worker # omit the patch version to avoid build errors if the toolchain is not 96*dbb99499SAndroid Build Coastguard Worker # yet registered in the current @rules_python version. 97*dbb99499SAndroid Build Coastguard Worker # patch version differences should be fine. 98*dbb99499SAndroid Build Coastguard Worker python_version = ".".join(platform.python_version_tuple()[:2]) 99*dbb99499SAndroid Build Coastguard Worker 100*dbb99499SAndroid Build Coastguard Worker bazel_argv = [ 101*dbb99499SAndroid Build Coastguard Worker "bazel", 102*dbb99499SAndroid Build Coastguard Worker "run", 103*dbb99499SAndroid Build Coastguard Worker ext.bazel_target, 104*dbb99499SAndroid Build Coastguard Worker f"--symlink_prefix={temp_path / 'bazel-'}", 105*dbb99499SAndroid Build Coastguard Worker f"--compilation_mode={'dbg' if self.debug else 'opt'}", 106*dbb99499SAndroid Build Coastguard Worker # C++17 is required by nanobind 107*dbb99499SAndroid Build Coastguard Worker f"--cxxopt={'/std:c++17' if IS_WINDOWS else '-std=c++17'}", 108*dbb99499SAndroid Build Coastguard Worker f"--@rules_python//python/config_settings:python_version={python_version}", 109*dbb99499SAndroid Build Coastguard Worker ] 110*dbb99499SAndroid Build Coastguard Worker 111*dbb99499SAndroid Build Coastguard Worker if ext.py_limited_api: 112*dbb99499SAndroid Build Coastguard Worker bazel_argv += ["--@nanobind_bazel//:py-limited-api=cp312"] 113*dbb99499SAndroid Build Coastguard Worker 114*dbb99499SAndroid Build Coastguard Worker if IS_WINDOWS: 115*dbb99499SAndroid Build Coastguard Worker # Link with python*.lib. 116*dbb99499SAndroid Build Coastguard Worker for library_dir in self.library_dirs: 117*dbb99499SAndroid Build Coastguard Worker bazel_argv.append("--linkopt=/LIBPATH:" + library_dir) 118*dbb99499SAndroid Build Coastguard Worker elif IS_MAC: 119*dbb99499SAndroid Build Coastguard Worker # C++17 needs macOS 10.14 at minimum 120*dbb99499SAndroid Build Coastguard Worker bazel_argv.append("--macos_minimum_os=10.14") 121*dbb99499SAndroid Build Coastguard Worker 122*dbb99499SAndroid Build Coastguard Worker with _maybe_patch_toolchains(): 123*dbb99499SAndroid Build Coastguard Worker self.spawn(bazel_argv) 124*dbb99499SAndroid Build Coastguard Worker 125*dbb99499SAndroid Build Coastguard Worker if IS_WINDOWS: 126*dbb99499SAndroid Build Coastguard Worker suffix = ".pyd" 127*dbb99499SAndroid Build Coastguard Worker else: 128*dbb99499SAndroid Build Coastguard Worker suffix = ".abi3.so" if ext.py_limited_api else ".so" 129*dbb99499SAndroid Build Coastguard Worker 130*dbb99499SAndroid Build Coastguard Worker # copy the Bazel build artifacts into setuptools' libdir, 131*dbb99499SAndroid Build Coastguard Worker # from where the wheel is built. 132*dbb99499SAndroid Build Coastguard Worker pkgname = "google_benchmark" 133*dbb99499SAndroid Build Coastguard Worker pythonroot = Path("bindings") / "python" / "google_benchmark" 134*dbb99499SAndroid Build Coastguard Worker srcdir = temp_path / "bazel-bin" / pythonroot 135*dbb99499SAndroid Build Coastguard Worker libdir = Path(self.build_lib) / pkgname 136*dbb99499SAndroid Build Coastguard Worker for root, dirs, files in os.walk(srcdir, topdown=True): 137*dbb99499SAndroid Build Coastguard Worker # exclude runfiles directories and children. 138*dbb99499SAndroid Build Coastguard Worker dirs[:] = [d for d in dirs if "runfiles" not in d] 139*dbb99499SAndroid Build Coastguard Worker 140*dbb99499SAndroid Build Coastguard Worker for f in files: 141*dbb99499SAndroid Build Coastguard Worker print(f) 142*dbb99499SAndroid Build Coastguard Worker fp = Path(f) 143*dbb99499SAndroid Build Coastguard Worker should_copy = False 144*dbb99499SAndroid Build Coastguard Worker # we do not want the bare .so file included 145*dbb99499SAndroid Build Coastguard Worker # when building for ABI3, so we require a 146*dbb99499SAndroid Build Coastguard Worker # full and exact match on the file extension. 147*dbb99499SAndroid Build Coastguard Worker if "".join(fp.suffixes) == suffix: 148*dbb99499SAndroid Build Coastguard Worker should_copy = True 149*dbb99499SAndroid Build Coastguard Worker elif fp.suffix == ".pyi": 150*dbb99499SAndroid Build Coastguard Worker should_copy = True 151*dbb99499SAndroid Build Coastguard Worker elif Path(root) == srcdir and f == "py.typed": 152*dbb99499SAndroid Build Coastguard Worker # copy py.typed, but only at the package root. 153*dbb99499SAndroid Build Coastguard Worker should_copy = True 154*dbb99499SAndroid Build Coastguard Worker 155*dbb99499SAndroid Build Coastguard Worker if should_copy: 156*dbb99499SAndroid Build Coastguard Worker shutil.copyfile(root / fp, libdir / fp) 157*dbb99499SAndroid Build Coastguard Worker 158*dbb99499SAndroid Build Coastguard Worker 159*dbb99499SAndroid Build Coastguard Workersetuptools.setup( 160*dbb99499SAndroid Build Coastguard Worker cmdclass=dict(build_ext=BuildBazelExtension), 161*dbb99499SAndroid Build Coastguard Worker package_data={"google_benchmark": ["py.typed", "*.pyi"]}, 162*dbb99499SAndroid Build Coastguard Worker ext_modules=[ 163*dbb99499SAndroid Build Coastguard Worker BazelExtension( 164*dbb99499SAndroid Build Coastguard Worker name="google_benchmark._benchmark", 165*dbb99499SAndroid Build Coastguard Worker bazel_target="//bindings/python/google_benchmark:benchmark_stubgen", 166*dbb99499SAndroid Build Coastguard Worker py_limited_api=py_limited_api, 167*dbb99499SAndroid Build Coastguard Worker ) 168*dbb99499SAndroid Build Coastguard Worker ], 169*dbb99499SAndroid Build Coastguard Worker options=options, 170*dbb99499SAndroid Build Coastguard Worker) 171