| #!/usr/bin/env python3 | 
 | # | 
 | #  Copyright (C) 2016 Codethink Limited | 
 | # | 
 | #  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 = 7 | 
 |  | 
 | if sys.version_info[0] != REQUIRED_PYTHON_MAJOR or sys.version_info[1] < REQUIRED_PYTHON_MINOR: | 
 |     print("BuildStream requires Python >= 3.7") | 
 |     sys.exit(1) | 
 |  | 
 | try: | 
 |     from setuptools import setup, find_packages, Command, Extension | 
 |     from setuptools.command.easy_install import ScriptWriter | 
 | 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 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("*")] | 
 |  | 
 |  | 
 | ##################################################### | 
 | #    Monkey-patching setuptools for performance     # | 
 | ##################################################### | 
 | # | 
 | # The template of easy_install.ScriptWriter is inefficient in our case as it | 
 | # imports pkg_resources. Patching the template only doesn't work because of the | 
 | # old string formatting used (%). This forces us to overwrite the class function | 
 | # as well. | 
 | # | 
 | # The patch was inspired from https://github.com/ninjaaron/fast-entry_points | 
 | # which we believe was also inspired from the code from `setuptools` project. | 
 | TEMPLATE = """\ | 
 | # -*- coding: utf-8 -*- | 
 | import sys | 
 |  | 
 | from {0} import {1} | 
 |  | 
 | if __name__ == '__main__': | 
 |     sys.exit({2}())""" | 
 |  | 
 |  | 
 | # Modify the get_args() function of the ScriptWriter class | 
 | # Note: the pylint no-member warning has been disabled as the functions: get_header(), | 
 | # ensure_safe_name() and _get_script_args() are all members of this class. | 
 | # pylint: disable=no-member | 
 | @classmethod | 
 | def get_args(cls, dist, header=None): | 
 |     if header is None: | 
 |         header = cls.get_header() | 
 |     for name, ep in dist.get_entry_map("console_scripts").items(): | 
 |         cls._ensure_safe_name(name) | 
 |         script_text = TEMPLATE.format(ep.module_name, ep.attrs[0], ".".join(ep.attrs)) | 
 |         args = cls._get_script_args("console", name, header, script_text) | 
 |         for res in args: | 
 |             yield res | 
 |  | 
 |  | 
 | ScriptWriter.get_args = get_args | 
 |  | 
 |  | 
 | ##################################################### | 
 | #         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            # | 
 | ##################################################### | 
 | # We want to ensure that source distributions always | 
 | # include the .c files, in order to allow users to | 
 | # not need cython when building. | 
 | def assert_cython_required(): | 
 |     if "sdist" not in sys.argv: | 
 |         return | 
 |  | 
 |     print( | 
 |         "Cython is required when building 'sdist' in order to " | 
 |         "ensure source distributions can be built without Cython. " | 
 |         "Please install it using your package manager (usually 'python3-cython') " | 
 |         "or pip (pip install cython).", | 
 |         file=sys.stderr, | 
 |     ) | 
 |  | 
 |     raise SystemExit(1) | 
 |  | 
 |  | 
 | 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): | 
 |     try: | 
 |         from Cython.Build import cythonize as _cythonize | 
 |     except ImportError: | 
 |         assert_cython_required() | 
 |  | 
 |         print("Cython not found. Using preprocessed c files instead") | 
 |  | 
 |         missing_c_sources = [] | 
 |  | 
 |         for extension in extensions: | 
 |             for source in extension.sources: | 
 |                 if source.endswith(".pyx"): | 
 |                     c_file = source.replace(".pyx", ".c") | 
 |  | 
 |                     if not os.path.exists(c_file): | 
 |                         missing_c_sources.append((extension, c_file)) | 
 |  | 
 |         if missing_c_sources: | 
 |             for extension, source in missing_c_sources: | 
 |                 print("Missing '{}' for building extension '{}'".format(source, extension.name)) | 
 |  | 
 |             raise SystemExit(1) | 
 |         return extensions | 
 |  | 
 |     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="BuildStream Developers", | 
 |     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.7", | 
 |         "Programming Language :: Python :: 3.8", | 
 |         "Programming Language :: Python :: 3.9", | 
 |         "Programming Language :: Python :: 3.10", | 
 |         "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://gitlab.com/BuildStream/buildstream", | 
 |         "Documentation": "https://docs.buildstream.build", | 
 |         "Tracker": "https://gitlab.com/BuildStream/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=("tests", "tests.*")), | 
 |     package_data={ | 
 |         "buildstream": [ | 
 |             "py.typed", | 
 |             "plugins/*/*.py", | 
 |             "plugins/*/*.yaml", | 
 |             "data/*.yaml", | 
 |             "data/*.sh.in", | 
 |             *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, | 
 | ) |