| # SPDX-License-Identifier: GPL-2.0 | |
| """Setup script for perf python extension. | |
| This script is used to build and install the perf python binding. | |
| It handles compiler-specific flags, especially for clang, and configures | |
| the setuptools Extension. | |
| """ | |
| import os | |
| import shlex | |
| import shutil | |
| import subprocess | |
| import sysconfig | |
| from setuptools import setup, Extension | |
| from setuptools.command.build_ext import build_ext as _build_ext | |
| from setuptools.command.install_lib import install_lib as _install_lib | |
| def clang_has_option(cc: str, cc_args: list[str], src_feature_tests: str, option: str) -> bool: | |
| """Check if clang supports a specific option. | |
| Args: | |
| cc: The compiler executable. | |
| cc_args: Compiler arguments from CC environment variable. | |
| src_feature_tests: Path to the feature tests directory. | |
| option: The compiler option to check (e.g., "-mcet"). | |
| Returns: | |
| True if the option is supported, False otherwise. | |
| """ | |
| error_substrings = ( | |
| b"unknown argument", | |
| b"is not supported", | |
| b"unknown warning option" | |
| ) | |
| cmd = [cc] + cc_args + [ | |
| option, | |
| "-o", "/dev/null", | |
| os.path.join(src_feature_tests, "test-hello.c") | |
| ] | |
| try: | |
| res = subprocess.run(cmd, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, check=False) | |
| cc_output = res.stderr.splitlines() | |
| except OSError: | |
| return False | |
| return not any(any(error in line for error in error_substrings) for line in cc_output) | |
| def filter_clang_options(cc: str, cc_args: list[str], src_feature_tests: str) -> None: | |
| """Filter out unsupported clang options from sysconfig CFLAGS and OPT. | |
| Args: | |
| cc: The compiler executable. | |
| cc_args: Compiler arguments from CC environment variable. | |
| src_feature_tests: Path to the feature tests directory. | |
| """ | |
| config_vars = sysconfig.get_config_vars() | |
| for var in ('CFLAGS', 'OPT'): | |
| if var not in config_vars: | |
| continue | |
| # Split into individual flags using shlex to preserve quoted arguments | |
| flags = shlex.split(config_vars[var]) | |
| # Remove -specs=... | |
| flags = [f for f in flags if not f.startswith("-specs=")] | |
| options = ( | |
| "-mcet", | |
| "-fcf-protection", | |
| "-fstack-clash-protection", | |
| "-fstack-protector-strong", | |
| "-fno-semantic-interposition", | |
| "-ffat-lto-objects", | |
| "-ftree-loop-distribute-patterns", | |
| "-gno-variable-location-views" | |
| ) | |
| for option in options: | |
| if not clang_has_option(cc, cc_args, src_feature_tests, option): | |
| # Remove the option and any variant (e.g. -option=...) | |
| flags = [f for f in flags if not f.startswith(option)] | |
| # Re-join flags preserving quoting | |
| config_vars[var] = shlex.join(flags) | |
| class BuildExt(_build_ext): | |
| """Custom build_ext command to set output directories.""" | |
| def __init__(self, *args, **kwargs): | |
| self.build_lib = None | |
| self.build_temp = None | |
| super().__init__(*args, **kwargs) | |
| def finalize_options(self) -> None: | |
| _build_ext.finalize_options(self) | |
| build_lib = os.getenv('PYTHON_EXTBUILD_LIB') | |
| build_tmp = os.getenv('PYTHON_EXTBUILD_TMP') | |
| if build_lib: | |
| self.build_lib = build_lib | |
| if build_tmp: | |
| self.build_temp = build_tmp | |
| class InstallLib(_install_lib): | |
| """Custom install_lib command to set output directory.""" | |
| def __init__(self, *args, **kwargs): | |
| self.build_dir = None | |
| super().__init__(*args, **kwargs) | |
| def finalize_options(self) -> None: | |
| _install_lib.finalize_options(self) | |
| build_lib = os.getenv('PYTHON_EXTBUILD_LIB') | |
| if build_lib: | |
| self.build_dir = build_lib | |
| def run(self): | |
| _install_lib.run(self) | |
| srctree = os.getenv('srctree', '.') | |
| src_perf = os.path.join(srctree, 'tools/perf') | |
| shutil.copy2(os.path.join(src_perf, 'python/perf.pyi'), self.install_dir) | |
| def main() -> None: | |
| """Main entry point for the setup script.""" | |
| cc_env = os.getenv("CC") | |
| assert cc_env, "Environment variable CC not set" | |
| # Safe parsing of CC environment variable which might contain options/quotes | |
| cc_tokens = shlex.split(cc_env) | |
| cc = cc_tokens[0] | |
| cc_args = cc_tokens[1:] | |
| # Run CC -v to check if it is clang. | |
| try: | |
| cc_info = subprocess.run( | |
| [cc, "-v"], stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, check=False | |
| ) | |
| cc_is_clang = b"clang version" in cc_info.stderr | |
| except OSError as e: | |
| raise RuntimeError(f"Failed to execute compiler '{cc}': {e}") from e | |
| srctree = os.getenv('srctree') | |
| assert srctree, "Environment variable srctree, for the Linux sources, not set" | |
| src_feature_tests = f'{srctree}/tools/build/feature' | |
| if cc_is_clang: | |
| filter_clang_options(cc, cc_args, src_feature_tests) | |
| # switch off several checks (need to be at the end of cflags list) | |
| cflags = [ | |
| '-fno-strict-aliasing', | |
| '-Wno-write-strings', | |
| '-Wno-unused-parameter', | |
| '-Wno-redundant-decls' | |
| ] | |
| if cc_is_clang: | |
| cflags += ["-Wno-unused-command-line-argument"] | |
| if clang_has_option( | |
| cc, cc_args, src_feature_tests, "-Wno-cast-function-type-mismatch" | |
| ): | |
| cflags += ["-Wno-cast-function-type-mismatch"] | |
| else: | |
| cflags += ['-Wno-cast-function-type'] | |
| # The python headers have mixed code with declarations (decls after asserts, for instance) | |
| cflags += ["-Wno-declaration-after-statement"] | |
| src_perf = f'{srctree}/tools/perf' | |
| perf = Extension( | |
| 'perf', | |
| sources=[os.path.join(src_perf, 'util/python.c')], | |
| include_dirs=['util/include'], | |
| extra_compile_args=cflags, | |
| ) | |
| setup( | |
| name='perf', | |
| version='0.1', | |
| description='Interface with the Linux profiling infrastructure', | |
| author='Arnaldo Carvalho de Melo', | |
| author_email='acme@redhat.com', | |
| license='GPLv2', | |
| url='http://perf.wiki.kernel.org', | |
| ext_modules=[perf], | |
| cmdclass={'build_ext': BuildExt, 'install_lib': InstallLib}, | |
| ) | |
| if __name__ == '__main__': | |
| main() |