https://github.com/ProjectQ-Framework/ProjectQ
Raw File
Tip revision: f26198ad231a8e111f953b15b8133c88cac1135d authored by Nguyen Damien on 02 April 2024, 20:28:54 UTC
Merge pull request #469 from ProjectQ-Framework/pre-commit-ci-update-config
Tip revision: f26198a
setup.py
# Some of the setup.py code is inspired or copied from SQLAlchemy

# SQLAlchemy was created by Michael Bayer.

# Major contributing authors include:

# - Michael Bayer <mike_mp@zzzcomputing.com>
# - Jason Kirtland <jek@discorporate.us>
# - Gaetan de Menten <gdementen@gmail.com>
# - Diana Clarke <diana.joan.clarke@gmail.com>
# - Michael Trier <mtrier@gmail.com>
# - Philip Jenvey <pjenvey@underboss.org>
# - Ants Aasma <ants.aasma@gmail.com>
# - Paul Johnston <paj@pajhome.org.uk>
# - Jonathan Ellis <jbellis@gmail.com>
# - Damien Nguyen <damien1@huawei.com> (ProjectQ)

# Copyright 2005-2020 SQLAlchemy and ProjectQ authors and contributors (see above)

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

"""Setup.py file."""

# pylint: disable=deprecated-module

import os
import platform
import subprocess
import sys
import tempfile
from distutils.cmd import Command
from distutils.errors import (
    CCompilerError,
    CompileError,
    DistutilsExecError,
    DistutilsPlatformError,
    LinkError,
)
from distutils.spawn import find_executable, spawn
from operator import itemgetter
from pathlib import Path

from setuptools import Distribution as _Distribution
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext

try:
    import setuptools_scm  # noqa: F401  # pylint: disable=unused-import

    _HAS_SETUPTOOLS_SCM = True
except ImportError:
    _HAS_SETUPTOOLS_SCM = False

try:
    import tomllib

    def parse_toml(filename):
        """Parse a TOML file."""
        with open(str(filename), 'rb') as toml_file:
            return tomllib.load(toml_file)

except ImportError:
    try:
        import toml

        def parse_toml(filename):
            """Parse a TOML file."""
            return toml.load(filename)

    except ImportError:

        def _find_toml_section_end(lines, start):
            """Find the index of the start of the next section."""
            return (
                next(filter(itemgetter(1), enumerate(line.startswith('[') for line in lines[start + 1 :])))[0]
                + start
                + 1
            )

        def _parse_list(lines):
            """Parse a TOML list into a Python list."""
            # NB: This function expects the TOML list to be formatted like so (ignoring leading and trailing spaces):
            #     name = [
            #          '...',
            #     ]
            #     Any other format is not supported.
            name = None
            elements = []

            for idx, line in enumerate(lines):
                if name is None and not line.startswith("'"):
                    name = line.split('=')[0].strip()
                    continue
                if line.startswith("]"):
                    return (name, elements, idx + 1)
                elements.append(line.rstrip(',').strip("'").strip('"'))

            raise RuntimeError(f'Failed to locate closing "]" for {name}')

        def parse_toml(filename):
            """Very simple parser routine for pyproject.toml."""
            result = {'project': {'optional-dependencies': {}}}
            with open(filename) as toml_file:
                lines = [line.strip() for line in toml_file.readlines()]
            lines = [line for line in lines if line and not line.startswith('#')]

            start = lines.index('[project]')
            project_data = lines[start : _find_toml_section_end(lines, start)]

            start = lines.index('[project.optional-dependencies]')
            optional_dependencies = lines[start + 1 : _find_toml_section_end(lines, start)]

            idx = 0
            N = len(project_data)
            while idx < N:
                line = project_data[idx]
                shift = 1
                if line.startswith('name'):
                    result['project']['name'] = line.split('=')[1].strip().strip("'")
                elif line.startswith('dependencies'):
                    (name, pkgs, shift) = _parse_list(project_data[idx:])
                    result['project'][name] = pkgs
                idx += shift

            idx = 0
            N = len(optional_dependencies)
            while idx < N:
                (opt_name, opt_pkgs, shift) = _parse_list(optional_dependencies[idx:])
                result['project']['optional-dependencies'][opt_name] = opt_pkgs
                idx += shift

            return result


# ==============================================================================
# Helper functions and classes


class Pybind11Include:  # pylint: disable=too-few-public-methods
    """
    Helper class to determine the pybind11 include path.

    The purpose of this class is to postpone importing pybind11 until it is actually installed, so that the
    ``get_include()`` method can be invoked.
    """

    def __init__(self, user=False):
        """Initialize a Pybind11Include object."""
        self.user = user

    def __str__(self):
        """Conversion to string."""
        import pybind11  # pylint: disable=import-outside-toplevel

        return pybind11.get_include(self.user)


def important_msgs(*msgs):
    """Print an important message."""
    print('*' * 75)
    for msg in msgs:
        print(msg)
    print('*' * 75)


def status_msgs(*msgs):
    """Print a status message."""
    print('-' * 75)
    for msg in msgs:
        print('# INFO: ', msg)
    print('-' * 75)


def compiler_test(
    compiler,
    flagname=None,
    link_executable=False,
    link_shared_lib=False,
    include='',
    body='',
    compile_postargs=None,
    link_postargs=None,
):  # pylint: disable=too-many-arguments,too-many-branches
    """Return a boolean indicating whether a flag name is supported on the specified compiler."""
    fname = None
    with tempfile.NamedTemporaryFile('w', suffix='.cpp', delete=False) as temp:
        temp.write(f'{include}\nint main (int argc, char **argv) {{ {body} return 0; }}')
        fname = temp.name

    if compile_postargs is None:
        compile_postargs = [flagname] if flagname is not None else None
    elif flagname is not None:
        compile_postargs.append(flagname)

    try:
        if compiler.compiler_type == 'msvc':
            olderr = os.dup(sys.stderr.fileno())
            err = open('err.txt', 'w')  # pylint: disable=consider-using-with
            os.dup2(err.fileno(), sys.stderr.fileno())

        obj_file = compiler.compile([fname], extra_postargs=compile_postargs)
        if not os.path.exists(obj_file[0]):
            raise RuntimeError('')
        if link_executable:
            compiler.link_executable(obj_file, os.path.join(tempfile.mkdtemp(), 'test'), extra_postargs=link_postargs)
        elif link_shared_lib:
            if sys.platform == 'win32':
                lib_name = os.path.join(tempfile.mkdtemp(), 'test.dll')
            else:
                lib_name = os.path.join(tempfile.mkdtemp(), 'test.so')
            compiler.link_shared_lib(obj_file, lib_name, extra_postargs=link_postargs)

        if compiler.compiler_type == 'msvc':
            err.close()
            os.dup2(olderr, sys.stderr.fileno())
            with open('err.txt') as err_file:
                if err_file.readlines():
                    raise RuntimeError('')
    except (CompileError, LinkError, RuntimeError):
        return False
    else:
        return True
    finally:
        os.unlink(fname)


def _fix_macosx_header_paths(*args):
    # Fix path to SDK headers if necessary
    _MACOSX_XCODE_REF_PATH = (  # pylint: disable=invalid-name
        '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer'
    )
    _MACOSX_DEVTOOLS_REF_PATH = '/Library/Developer/CommandLineTools/'  # pylint: disable=invalid-name
    _has_xcode = os.path.exists(_MACOSX_XCODE_REF_PATH)
    _has_devtools = os.path.exists(_MACOSX_DEVTOOLS_REF_PATH)
    if not _has_xcode and not _has_devtools:
        important_msgs('ERROR: Must install either Xcode or CommandLineTools!')
        raise BuildFailed()

    for compiler_args in args:
        for idx, item in enumerate(compiler_args):
            if not _has_xcode and _MACOSX_XCODE_REF_PATH in item:
                compiler_args[idx] = item.replace(_MACOSX_XCODE_REF_PATH, _MACOSX_DEVTOOLS_REF_PATH)

            if not _has_devtools and _MACOSX_DEVTOOLS_REF_PATH in item:
                compiler_args[idx] = item.replace(_MACOSX_DEVTOOLS_REF_PATH, _MACOSX_XCODE_REF_PATH)


# ==============================================================================


class BuildFailed(Exception):
    """Extension raised if the build fails for any reason."""

    def __init__(self):
        """Initialize a BuildFailed exception object."""
        super().__init__()
        self.cause = sys.exc_info()[1]  # work around py 2/3 different syntax


# ------------------------------------------------------------------------------
# Python build related variable

cpython = platform.python_implementation() == 'CPython'
ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError)
if sys.platform == 'win32':
    # 2.6's distutils.msvc9compiler can raise an IOError when failing to
    # find the compiler
    ext_errors += (IOError,)

# ==============================================================================
# ProjectQ C++ extensions

ext_modules = [
    Extension(
        'projectq.backends._sim._cppsim',
        ['projectq/backends/_sim/_cppsim.cpp'],
        include_dirs=[
            # Path to pybind11 headers
            Pybind11Include(),
            Pybind11Include(user=True),
        ],
        language='c++',
    ),
]

# ==============================================================================


class BuildExt(build_ext):
    """A custom build extension for adding compiler-specific options."""

    c_opts = {
        'msvc': ['/EHsc'],
        'unix': [],
    }

    user_options = build_ext.user_options + [
        (
            'gen-compiledb',
            None,
            'Generate a compile_commands.json alongside the compilation implies (-n/--dry-run)',
        ),
    ]

    boolean_options = build_ext.boolean_options + ['gen-compiledb']

    def initialize_options(self):
        """Initialize this command's options."""
        build_ext.initialize_options(self)
        self.gen_compiledb = None

    def finalize_options(self):
        """Finalize this command's options."""
        build_ext.finalize_options(self)
        if self.gen_compiledb:
            self.dry_run = True  # pylint: disable=attribute-defined-outside-init

    def run(self):
        """Execute this command."""
        try:
            build_ext.run(self)
        except DistutilsPlatformError as err:
            raise BuildFailed() from err

    def build_extensions(self):
        """Build the individual C/C++ extensions."""
        self._configure_compiler()

        for ext in self.extensions:
            ext.extra_compile_args = self.opts
            ext.extra_link_args = self.link_opts

        if self.compiler.compiler_type == 'unix' and self.gen_compiledb:
            compile_commands = []
            for ext in self.extensions:
                commands = self._get_compilation_commands(ext)
                for cmd, src in commands:
                    compile_commands.append(
                        {
                            'directory': os.path.dirname(os.path.abspath(__file__)),
                            'command': cmd,
                            'file': os.path.abspath(src),
                        }
                    )

            import json  # pylint: disable=import-outside-toplevel

            with open(
                os.path.join(os.path.dirname(os.path.abspath(__file__)), 'compile_commands.json'),
                'w',
            ) as json_file:
                json.dump(compile_commands, json_file, sort_keys=True, indent=4)

        try:
            build_ext.build_extensions(self)
        except ext_errors as err:
            raise BuildFailed() from err
        except ValueError as err:
            # this can happen on Windows 64 bit, see Python issue 7511
            if "'path'" in str(sys.exc_info()[1]):  # works with both py 2/3
                raise BuildFailed() from err
            raise

    def _get_compilation_commands(self, ext):
        # pylint: disable=protected-access
        (
            _,
            objects,
            extra_postargs,
            pp_opts,
            build,
        ) = self.compiler._setup_compile(
            outdir=self.build_temp,
            sources=ext.sources,
            macros=ext.define_macros,
            incdirs=ext.include_dirs,
            extra=ext.extra_compile_args,
            depends=ext.depends,
        )

        cc_args = self.compiler._get_cc_args(pp_opts=pp_opts, debug=self.debug, before=None)
        compiler_so = self.compiler.compiler_so
        compiler_so[0] = find_executable(compiler_so[0])

        commands = []
        for obj in objects:
            try:
                src, ext = build[obj]
            except KeyError:
                continue

            commands.append(
                (
                    ' '.join(
                        compiler_so + cc_args + [os.path.abspath(src), "-o", os.path.abspath(obj)] + extra_postargs
                    ),
                    src,
                )
            )
        return commands

    def _configure_compiler(self):
        # pylint: disable=attribute-defined-outside-init

        # Force dry_run = False to allow for compiler feature testing
        dry_run_old = self.compiler.dry_run
        self.compiler.dry_run = False

        if (
            int(os.environ.get('PROJECTQ_CLEANUP_COMPILER_FLAGS', 0))
            and self.compiler.compiler_type == 'unix'
            and sys.platform != 'darwin'
        ):
            self._cleanup_compiler_flags()

        if sys.platform == 'darwin':
            _fix_macosx_header_paths(self.compiler.compiler, self.compiler.compiler_so)

            if compiler_test(self.compiler, '-stdlib=libc++'):
                self.c_opts['unix'] += ['-stdlib=libc++']

        compiler_type = self.compiler.compiler_type
        self.opts = self.c_opts.get(compiler_type, [])
        self.link_opts = []

        if not compiler_test(self.compiler):
            important_msgs(
                'ERROR: something is wrong with your C++ compiler.\nFailed to compile a simple test program!'
            )
            raise BuildFailed()

        # ------------------------------

        status_msgs('Configuring OpenMP')
        self._configure_openmp()
        status_msgs('Configuring compiler intrinsics')
        self._configure_intrinsics()
        status_msgs('Configuring C++ standard')
        self._configure_cxx_standard()

        # ------------------------------
        # Other compiler tests

        status_msgs('Other compiler tests')
        self.compiler.define_macro('VERSION_INFO', f'"{self.distribution.get_version()}"')
        if compiler_type == 'unix' and compiler_test(self.compiler, '-fvisibility=hidden'):
            self.opts.append('-fvisibility=hidden')

        self.compiler.dry_run = dry_run_old
        status_msgs('Finished configuring compiler!')

    def _configure_openmp(self):
        if self.compiler.compiler_type == 'msvc':
            return

        kwargs = {
            'link_shared_lib': True,
            'include': '#include <omp.h>',
            'body': 'int a = omp_get_num_threads(); ++a;',
        }

        for flag in ['-openmp', '-fopenmp', '-qopenmp', '/Qopenmp']:
            if compiler_test(self.compiler, flag, link_postargs=[flag], **kwargs):
                self.opts.append(flag)
                self.link_opts.append(flag)
                return

        flag = '-fopenmp'
        if sys.platform == 'darwin' and compiler_test(self.compiler, flag):
            try:
                llvm_root = subprocess.check_output(['brew', '--prefix', 'llvm']).decode('utf-8')[:-1]
                compiler_root = subprocess.check_output(['which', self.compiler.compiler[0]]).decode('utf-8')[:-1]

                # Only add the flag if the compiler we are using is the one
                # from HomeBrew
                if llvm_root in compiler_root:
                    l_arg = f'-L{llvm_root}/lib'
                    if compiler_test(self.compiler, flag, link_postargs=[l_arg, flag], **kwargs):
                        self.opts.append(flag)
                        self.link_opts.extend((l_arg, flag))
                        return
            except subprocess.CalledProcessError:
                pass

            try:
                # Only relevant for MacPorts users with clang-3.7
                port_path = subprocess.check_output(['which', 'port']).decode('utf-8')[:-1]
                macports_root = os.path.dirname(os.path.dirname(port_path))
                compiler_root = subprocess.check_output(['which', self.compiler.compiler[0]]).decode('utf-8')[:-1]

                # Only add the flag if the compiler we are using is the one
                # from MacPorts
                if macports_root in compiler_root:
                    inc_dir = f'{macports_root}/include/libomp'
                    lib_dir = f'{macports_root}/lib/libomp'
                    c_arg = '-I' + inc_dir
                    l_arg = '-L' + lib_dir

                    if compiler_test(self.compiler, flag, compile_postargs=[c_arg], link_postargs=[l_arg], **kwargs):
                        self.compiler.add_include_dir(inc_dir)
                        self.compiler.add_library_dir(lib_dir)
                        return
            except subprocess.CalledProcessError:
                pass

        important_msgs('WARNING: compiler does not support OpenMP!')

    def _configure_intrinsics(self):
        flags = [
            '-march=native',
            '-mavx2',
            '/arch:AVX2',
            '/arch:CORE-AVX2',
            '/arch:AVX',
        ]

        if int(os.environ.get('PROJECTQ_NOINTRIN', '0')) or (
            sys.platform == 'darwin'
            and platform.machine() == 'arm64'
            and (sys.version_info.major == 3 and sys.version_info.minor < 9)
        ):
            important_msgs(
                'Either requested no-intrinsics or detected potentially unsupported Python version on '
                'Apple Silicon: disabling intrinsics'
            )
            self.compiler.define_macro('NOINTRIN')
            return
        if os.environ.get('PROJECTQ_DISABLE_ARCH_NATIVE'):
            flags = flags[1:]

        for flag in flags:
            if compiler_test(
                self.compiler,
                flagname=flag,
                include='#include <immintrin.h>',
                body='__m256d neg = _mm256_set1_pd(1.0); (void)neg;',
            ):
                self.opts.append(flag)
                self.compiler.define_macro("INTRIN")
                break

        for flag in ['-ffast-math', '-fast', '/fast', '/fp:precise']:
            if compiler_test(self.compiler, flagname=flag):
                self.opts.append(flag)
                break

    def _configure_cxx_standard(self):
        if self.compiler.compiler_type == 'msvc':
            return

        cxx_standards = [17, 14, 11]
        if sys.platform == 'darwin':
            major_version = int(platform.mac_ver()[0].split('.')[0])
            minor_version = int(platform.mac_ver()[0].split('.')[1])
            if major_version <= 10 and minor_version < 14:
                cxx_standards = [year for year in cxx_standards if year < 17]

        for year in cxx_standards:
            flag = f'-std=c++{year}'
            if compiler_test(self.compiler, flag):
                self.opts.append(flag)
                return
            flag = f'/Qstd=c++{year}'
            if compiler_test(self.compiler, flag):
                self.opts.append(flag)
                return

        important_msgs('ERROR: compiler needs to have at least C++11 support!')
        raise BuildFailed()

    def _cleanup_compiler_flags(self):
        status_msgs('INFO: Performing compiler flags cleanup')
        compiler_exe = self.compiler.compiler[0]
        compiler_exe_so = self.compiler.compiler_so[0]
        linker_so = self.compiler.linker_so[0]
        compiler_flags = set(self.compiler.compiler[1:])
        compiler_so_flags = set(self.compiler.compiler_so[1:])
        linker_so_flags = set(self.compiler.linker_so[1:])

        all_common_flags = compiler_flags & compiler_so_flags & linker_so_flags
        common_compiler_flags = (compiler_flags & compiler_so_flags) - all_common_flags

        compiler_flags = compiler_flags - common_compiler_flags - all_common_flags
        compiler_so_flags = compiler_so_flags - common_compiler_flags - all_common_flags

        flags = []
        for flag in common_compiler_flags:
            compiler = type(self.compiler)()
            compiler.set_executables(compiler=compiler_exe, compiler_so=compiler_exe_so, linker_so=linker_so)

            compiler.debug_print(f'INFO: trying out {flag}')
            if compiler_test(compiler, flag, link_shared_lib=True, compile_postargs=['-fPIC']):
                flags.append(flag)
            else:
                important_msgs(f'WARNING: ignoring unsupported compiler flag: {flag}')

        self.compiler.compiler = [compiler_exe] + list(compiler_flags)
        self.compiler.compiler_so = [compiler_exe_so] + list(compiler_so_flags)
        self.compiler.linker_so = [linker_so] + list(linker_so_flags - all_common_flags)

        self.compiler.compiler.extend(flags)
        self.compiler.compiler_so.extend(flags)

        flags = []
        for flag in all_common_flags:
            if compiler_test(self.compiler, flag):
                flags.append(flag)
            else:
                important_msgs(f'WARNING: ignoring unsupported compiler flag: {flag}')

        self.compiler.compiler.extend(flags)
        self.compiler.compiler_so.extend(flags)
        self.compiler.linker_so.extend(flags)


# ------------------------------------------------------------------------------


class ClangTidy(Command):
    """A custom command to run Clang-Tidy on all C/C++ source files."""

    description = 'run Clang-Tidy on all C/C++ source files'
    user_options = [('warning-as-errors', None, 'Warning as errors')]
    boolean_options = ['warning-as-errors']

    sub_commands = [('build_ext', None)]

    def initialize_options(self):
        """Initialize this command's options."""
        self.warning_as_errors = None

    def finalize_options(self):
        """Finalize this command's options."""

    def run(self):
        """Execute this command."""
        # Ideally we would use self.run_command(command) but we need to ensure
        # that --dry-run --gen-compiledb are passed to build_ext regardless of
        # other arguments
        command = 'build_ext'
        # distutils.log.info("running %s --dry-run --gen-compiledb", command)
        cmd_obj = self.get_finalized_command(command)
        cmd_obj.dry_run = True
        cmd_obj.gen_compiledb = True
        try:
            cmd_obj.run()
            self.distribution.have_run[command] = 1
        except BuildFailed as err:
            # distutils.log.error('build_ext --dry-run --gen-compiledb command failed!')
            raise RuntimeError('build_ext --dry-run --gen-compiledb command failed!') from err

        command = ['clang-tidy']
        if self.warning_as_errors:
            command.append('--warnings-as-errors=*')
        for ext in self.distribution.ext_modules:
            command.extend(os.path.abspath(p) for p in ext.sources)
        spawn(command, dry_run=self.dry_run)


# ------------------------------------------------------------------------------


class GenerateRequirementFile(Command):
    """A custom command to list the dependencies of the current."""

    description = 'List the dependencies of the current package'
    user_options = [
        ('include-all-extras', None, 'Include all "extras_require" into the list'),
        ('include-extras=', None, 'Include some of extras_requires into the list (comma separated)'),
    ]

    boolean_options = ['include-all-extras']

    def initialize_options(self):
        """Initialize this command's options."""
        self.include_extras = None
        self.include_all_extras = None
        self.extra_pkgs = []
        self.dependencies = []

    def finalize_options(self):
        """Finalize this command's options."""
        include_extras = self.include_extras.split(',') if self.include_extras else []
        pyproject_toml = parse_toml(Path(__file__).parent / 'pyproject.toml')

        for name, pkgs in pyproject_toml['project']['optional-dependencies'].items():
            if self.include_all_extras or name in include_extras:
                self.extra_pkgs.extend(pkgs)

        # pylint: disable=attribute-defined-outside-init
        self.dependencies = self.distribution.install_requires
        if not self.dependencies:
            self.dependencies = pyproject_toml['project']['dependencies']

    def run(self):
        """Execute this command."""
        with open('requirements.txt', 'w') as req_file:
            for pkg in self.dependencies:
                req_file.write(f'{pkg}\n')
            req_file.write('\n')
            for pkg in self.extra_pkgs:
                req_file.write(f'{pkg}\n')


# ------------------------------------------------------------------------------


class Distribution(_Distribution):
    """Distribution class."""

    def has_ext_modules(self):
        """Return whether this distribution has some external modules."""
        # We want to always claim that we have ext_modules. This will be fine
        # if we don't actually have them (such as on PyPy) because nothing
        # will get built, however we don't want to provide an overally broad
        # Wheel package when building a wheel without C support. This will
        # ensure that Wheel knows to treat us as if the build output is
        # platform specific.
        return True


# ==============================================================================


def run_setup(with_cext):
    """Run the setup() function."""
    kwargs = {}
    if with_cext:
        kwargs['ext_modules'] = ext_modules
    else:
        kwargs['ext_modules'] = []

    # NB: Workaround for people calling setup.py without a proper environment containing setuptools-scm
    #     This can typically be the case when calling the `gen_reqfile` or `clang_tidy`commands
    if not _HAS_SETUPTOOLS_SCM:
        kwargs['version'] = '0.0.0'

    setup(
        cmdclass={
            'build_ext': BuildExt,
            'clang_tidy': ClangTidy,
            'gen_reqfile': GenerateRequirementFile,
        },
        distclass=Distribution,
        **kwargs,
    )


# ==============================================================================

if not cpython:
    run_setup(False)
    important_msgs(
        'WARNING: C/C++ extensions are not supported on some features are disabled (e.g. C++ simulator).',
        'Plain-Python build succeeded.',
    )
elif os.environ.get('PROJECTQ_DISABLE_CEXT'):
    run_setup(False)
    important_msgs(
        'PROJECTQ_DISABLE_CEXT is set; not attempting to build C/C++ extensions.',
        'Plain-Python build succeeded.',
    )

else:
    try:
        run_setup(True)
    except BuildFailed as exc:
        if os.environ.get('PROJECTQ_CI_BUILD'):
            raise exc

        important_msgs(
            exc.cause,
            'WARNING: Some C/C++ extensions could not be compiled, '
            + 'some features are disabled (e.g. C++ simulator).',
            'Failure information, if any, is above.',
            'Retrying the build without the C/C++ extensions now.',
        )

        run_setup(False)

        important_msgs(
            'WARNING: Some C/C++ extensions could not be compiled, '
            + 'some features are disabled (e.g. C++ simulator).',
            'Plain-Python build succeeded.',
        )
back to top