setup.py (226 lines of code) (raw):

#!/usr/bin/env python3 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Authors: # Tristan Van Berkom <tristan.vanberkom@codethink.co.uk> # Benjamin Schubert <bschubert15@bloomberg.net> import os from pathlib import Path import re import sys ################################### # Ensure we have a version number # ################################### # Add local directory to the path, in order to be able to import versioneer sys.path.append(os.path.dirname(__file__)) import versioneer # pylint: disable=wrong-import-position version = versioneer.get_version() if version.startswith("0+untagged"): print( "Your git repository has no tags - BuildStream can't determine its version. Please run `git fetch --tags`.", file=sys.stderr, ) sys.exit(1) ################################################################## # Python requirements ################################################################## REQUIRED_PYTHON_MAJOR = 3 REQUIRED_PYTHON_MINOR = 9 if sys.version_info[0] != REQUIRED_PYTHON_MAJOR or sys.version_info[1] < REQUIRED_PYTHON_MINOR: print("BuildStream requires Python >= 3.9") sys.exit(1) try: from setuptools import setup, find_packages, Command, Extension except ImportError: print( "BuildStream requires setuptools in order to build. Install it using" " your package manager (usually python3-setuptools) or via pip (pip3" " install setuptools)." ) sys.exit(1) ############################################################ # List the BuildBox binaries to ship in the wheel packages # ############################################################ # # BuildBox isn't widely available in OS distributions. To enable a "one click" # install for BuildStream, we bundle prebuilt BuildBox binaries in our binary # wheel packages. # # The binaries are provided by the buildbox-integration Gitlab project: # https://gitlab.com/BuildGrid/buildbox/buildbox-integration # # If you want to build a wheel with the BuildBox binaries included, set the # env var "BST_BUNDLE_BUILDBOX=1" when running setup.py. try: BUNDLE_BUILDBOX = int(os.environ.get("BST_BUNDLE_BUILDBOX", "0")) except ValueError: print("BST_BUNDLE_BUILDBOX must be an integer. Please set it to '1' to enable, '0' to disable", file=sys.stderr) raise SystemExit(1) def list_buildbox_binaries(): expected_binaries = [ "buildbox-casd", "buildbox-fuse", "buildbox-run", ] if BUNDLE_BUILDBOX: bst_package_dir = Path(__file__).parent.joinpath("src/buildstream") buildbox_dir = bst_package_dir.joinpath("subprojects", "buildbox") buildbox_binaries = [buildbox_dir.joinpath(name) for name in expected_binaries] missing_binaries = [path for path in buildbox_binaries if not path.is_file()] if missing_binaries: paths_text = "\n".join([" * {}".format(path) for path in missing_binaries]) print( "Expected BuildBox binaries were not found. " "Set BST_BUNDLE_BUILDBOX=0 or provide:\n\n" "{}\n".format(paths_text), file=sys.stderr, ) raise SystemExit(1) for path in buildbox_binaries: if path.is_symlink(): print( "Bundled BuildBox binaries must not be symlinks. Please fix {}".format(path), file=sys.stderr, ) raise SystemExit(1) return [str(path.relative_to(bst_package_dir)) for path in buildbox_binaries] else: return [] ########################################### # List the pre-built man pages to install # ########################################### # # Man pages are automatically generated however it was too difficult # to integrate with setuptools as a step of the build (FIXME !). # # To update the man pages in tree before a release, run: # # tox -e man # # Then commit the result. # def list_man_pages(): bst_dir = os.path.dirname(os.path.abspath(__file__)) man_dir = os.path.join(bst_dir, "man") try: man_pages = os.listdir(man_dir) return [os.path.join("man", page) for page in man_pages] except FileNotFoundError: # Do not error out when 'man' directory does not exist return [] ###################################################### # List the data files needed by buildstream._testing # ###################################################### # # List the datafiles which need to be installed for the # buildstream._testing package # def list_testing_datafiles(): bst_dir = Path(os.path.dirname(os.path.abspath(__file__))) data_dir = bst_dir.joinpath("src", "buildstream", "_testing", "_sourcetests", "project") return [str(f) for f in data_dir.rglob("*")] ##################################################### # gRPC command for code generation # ##################################################### class BuildGRPC(Command): """Command to generate project *_pb2.py modules from proto files.""" description = "build gRPC protobuf modules" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): try: import grpc_tools.command except ImportError: print( "BuildStream requires grpc_tools in order to build gRPC modules.\n" "Install it via pip (pip3 install grpcio-tools)." ) sys.exit(1) protos_root = "src/buildstream/_protos" grpc_tools.command.build_package_protos(protos_root) # Postprocess imports in generated code for root, _, files in os.walk(protos_root): for filename in files: if filename.endswith(".py"): path = os.path.join(root, filename) with open(path, "r", encoding="utf-8") as f: code = f.read() # All protos are in buildstream._protos code = re.sub(r"^from ", r"from buildstream._protos.", code, flags=re.MULTILINE) # Except for the core google.protobuf protos code = re.sub( r"^from buildstream._protos.google.protobuf", r"from google.protobuf", code, flags=re.MULTILINE ) with open(path, "w", encoding="utf-8") as f: f.write(code) def get_cmdclass(): cmdclass = { "build_grpc": BuildGRPC, } cmdclass.update(versioneer.get_cmdclass()) return cmdclass ##################################################### # Gather requirements # ##################################################### with open("requirements/requirements.in", encoding="utf-8") as install_reqs: install_requires = install_reqs.read().splitlines() ##################################################### # Prepare package description from README # ##################################################### with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "README.rst"), encoding="utf-8") as readme: long_description = readme.read() ##################################################### # Setup Cython and extensions # ##################################################### try: ENABLE_CYTHON_TRACE = int(os.environ.get("BST_CYTHON_TRACE", "0")) except ValueError: print("BST_CYTHON_TRACE must be an integer. Please set it to '1' to enable, '0' to disable", file=sys.stderr) raise SystemExit(1) extension_macros = [("CYTHON_TRACE", ENABLE_CYTHON_TRACE)] def cythonize(extensions, **kwargs): # We want to make sure that generated Cython code is never # included in the source distribution. # # This is because Cython will generate some code which accesses # internal API from CPython, as such we cannot guarantee that # the C code we generated when creating the distribution will # be valid for the target CPython interpretor. # if "sdist" in sys.argv: return extensions try: from Cython.Build import cythonize as _cythonize except ImportError: print( "Cython is required when building BuildStream from sources. " "Please install it using your package manager (usually 'python3-cython') " "or pip (pip install cython).", file=sys.stderr, ) raise SystemExit(1) return _cythonize(extensions, **kwargs) def register_cython_module(module_name, dependencies=None): def files_from_module(modname): basename = "src/{}".format(modname.replace(".", "/")) return "{}.pyx".format(basename), "{}.pxd".format(basename) if dependencies is None: dependencies = [] implementation_file, definition_file = files_from_module(module_name) assert os.path.exists(implementation_file) depends = [] if os.path.exists(definition_file): depends.append(definition_file) for module in dependencies: imp_file, def_file = files_from_module(module) assert os.path.exists(imp_file), "Dependency file not found: {}".format(imp_file) assert os.path.exists(def_file), "Dependency declaration file not found: {}".format(def_file) depends.append(imp_file) depends.append(def_file) BUILD_EXTENSIONS.append( Extension( name=module_name, sources=[implementation_file], depends=depends, define_macros=extension_macros, ) ) BUILD_EXTENSIONS = [] register_cython_module("buildstream.node") register_cython_module("buildstream._loader.loadelement", dependencies=["buildstream.node"]) register_cython_module("buildstream._yaml", dependencies=["buildstream.node"]) register_cython_module("buildstream._types") register_cython_module("buildstream._utils") register_cython_module("buildstream._variables", dependencies=["buildstream.node"]) ##################################################### # Main setup() Invocation # ##################################################### setup( name="BuildStream", version=version, cmdclass=get_cmdclass(), author="The Apache Software Foundation", author_email="dev@buildstream.apache.org", classifiers=[ "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX", "Programming Language :: Python :: 3", "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", "Topic :: Software Development :: Build Tools", ], description="A framework for modelling build pipelines in YAML", license="Apache License Version 2.0", long_description=long_description, long_description_content_type="text/x-rst; charset=UTF-8", url="https://buildstream.build", project_urls={ "Source": "https://github.com/apache/buildstream", "Documentation": "https://docs.buildstream.build", "Tracker": "https://github.com/apache/buildstream/issues", "Mailing List": "https://lists.apache.org/list.html?dev@buildstream.apache.org", }, python_requires="~={}.{}".format(REQUIRED_PYTHON_MAJOR, REQUIRED_PYTHON_MINOR), package_dir={"": "src"}, packages=find_packages(where="src", exclude=("subprojects", "tests", "tests.*")), package_data={ "buildstream": [ "py.typed", "plugins/*/*.py", "plugins/*/*.yaml", "data/*.yaml", "data/*.sh.in", *list_buildbox_binaries(), *list_testing_datafiles(), ] }, data_files=[ # This is a weak attempt to integrate with the user nicely, # installing things outside of the python package itself with pip is # not recommended, but there seems to be no standard structure for # addressing this; so just installing this here. # # These do not get installed in developer mode (`pip install --user -e .`) # # The completions are ignored by bash unless it happens to be installed # in the right directory; this is more like a weak statement that we # attempt to install bash completion scriptlet. # ("share/man/man1", list_man_pages()), ("share/bash-completion/completions", [os.path.join("src", "buildstream", "data", "bst")]), ], install_requires=install_requires, entry_points={"console_scripts": ["bst = buildstream._frontend:cli"]}, ext_modules=cythonize( BUILD_EXTENSIONS, compiler_directives={ # Version of python to use # https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html#arguments "language_level": "3", # Enable line tracing when requested only, this is needed in order to generate coverage. "linetrace": bool(ENABLE_CYTHON_TRACE), "profile": os.environ.get("BST_CYTHON_PROFILE", False), }, ), zip_safe=False, )