def _parse_setuptools_arguments(setup_attrs):
    """This function instantiates a Distribution object and
    parses the command line arguments.

    It returns a tuple (display_only, help_commands, commands) where
     - display_only is a boolean indicating if an argument like '--help',
     '--help-commands' or '--author' was passed.
     - help_commands is a boolean indicating if argument '--help-commands'
     was passed.
     - commands contains the list of commands that were passed.

    Otherwise it raises DistutilsArgError exception if there are
    any error on the command-line, and it raises DistutilsGetoptError
    if there any error in the command 'options' attribute.

    The code has been adapted from the setup() function available
    in distutils/core.py.
    """
    setup_attrs = dict(setup_attrs)

    setup_attrs['script_name'] = os.path.basename(sys.argv[0])

    dist = upstream_Distribution(setup_attrs)

    # Find and parse the config file(s): they will override options from
    # the setup script, but be overridden by the command line.
    dist.parse_config_files()

    # Parse the command line and override config files; any
    # command-line errors are the end user's fault, so turn them into
    # SystemExit to suppress tracebacks.

    with _capture_output():
        result = dist.parse_command_line()
        display_only = not result

    return display_only, dist.help_commands, dist.commands
Beispiel #2
0
def _parse_setuptools_arguments(setup_attrs):
    """This function instantiates a Distribution object and
    parses the command line arguments.

    It returns the tuple
        ``(display_only, help_commands, commands,
        hide_listing, force_cmake, skip_cmake)``
    where
    - display_only is a boolean indicating if an argument like '--help',
      '--help-commands' or '--author' was passed.
    - help_commands is a boolean indicating if argument '--help-commands'
      was passed.
    - commands contains the list of commands that were passed.
    - hide_listing is a boolean indicating if the list of files being included
      in the distribution is displayed or not.
    - force_cmake a boolean indicating that CMake should always be executed.
    - skip_cmake is a boolean indicating if the execution of CMake should
      explicitly be skipped.

    Otherwise it raises DistutilsArgError exception if there are
    any error on the command-line, and it raises DistutilsGetoptError
    if there any error in the command 'options' attribute.

    The code has been adapted from the setup() function available
    in distutils/core.py.
    """
    setup_attrs = dict(setup_attrs)

    setup_attrs['script_name'] = os.path.basename(sys.argv[0])

    dist = upstream_Distribution(setup_attrs)

    # Update class attribute to also ensure the argument is processed
    # when ``upstream_setup`` is called.
    upstream_Distribution.global_options.append(
        ('hide-listing', None, "do not display list of files being "
         "included in the distribution"))
    upstream_Distribution.global_options.append(
        ('force-cmake', None, "always run CMake"))
    upstream_Distribution.global_options.append(
        ('skip-cmake', None, "do not run CMake"))

    # Find and parse the config file(s): they will override options from
    # the setup script, but be overridden by the command line.
    dist.parse_config_files()

    # Parse the command line and override config files; any
    # command-line errors are the end user's fault, so turn them into
    # SystemExit to suppress tracebacks.

    with _capture_output():
        result = dist.parse_command_line()
        display_only = not result
        if not hasattr(dist, 'hide_listing'):
            dist.hide_listing = False
        if not hasattr(dist, 'force_cmake'):
            dist.force_cmake = False
        if not hasattr(dist, 'skip_cmake'):
            dist.skip_cmake = False

    return (display_only, dist.help_commands, dist.commands, dist.hide_listing,
            dist.force_cmake, dist.skip_cmake)
def setup(*args, **kw):  # noqa: C901
    """This function wraps setup() so that we can run cmake, make,
    CMake build, then proceed as usual with setuptools, appending the
    CMake-generated output as necessary.

    The CMake project is re-configured only if needed. This is achieved by (1) retrieving the environment mapping
    associated with the generator set in the ``CMakeCache.txt`` file, (2) saving the CMake configure arguments and
    version in :func:`skbuild.constants.CMAKE_SPEC_FILE()`: and (3) re-configuring only if either the generator or
    the CMake specs change.
    """
    sys.argv, cmake_executable, skip_generator_test, cmake_args, make_args = parse_args()

    # work around https://bugs.python.org/issue1011113
    # (patches provided, but no updates since 2014)
    cmdclass = kw.get('cmdclass', {})
    cmdclass['build'] = cmdclass.get('build', build.build)
    cmdclass['build_py'] = cmdclass.get('build_py', build_py.build_py)
    cmdclass['build_ext'] = cmdclass.get('build_ext', build_ext.build_ext)
    cmdclass['install'] = cmdclass.get('install', install.install)
    cmdclass['install_lib'] = cmdclass.get('install_lib',
                                           install_lib.install_lib)
    cmdclass['install_scripts'] = cmdclass.get('install_scripts',
                                               install_scripts.install_scripts)
    cmdclass['clean'] = cmdclass.get('clean', clean.clean)
    cmdclass['sdist'] = cmdclass.get('sdist', sdist.sdist)
    cmdclass['bdist'] = cmdclass.get('bdist', bdist.bdist)
    cmdclass['bdist_wheel'] = cmdclass.get(
        'bdist_wheel', bdist_wheel.bdist_wheel)
    cmdclass['egg_info'] = cmdclass.get('egg_info', egg_info.egg_info)
    cmdclass['generate_source_manifest'] = cmdclass.get(
        'generate_source_manifest',
        generate_source_manifest.generate_source_manifest)
    cmdclass['test'] = cmdclass.get('test', test.test)
    kw['cmdclass'] = cmdclass

    # Extract setup keywords specific to scikit-build and remove them from kw.
    # Removing the keyword from kw need to be done here otherwise, the
    # following call to _parse_setuptools_arguments would complain about
    # unknown setup options.
    parameters = {
        'cmake_args': [],
        'cmake_install_dir': '',
        'cmake_source_dir': '',
        'cmake_with_sdist': False,
        'cmake_languages': ('C', 'CXX'),
        'cmake_minimum_required_version': None
    }
    skbuild_kw = {param: kw.pop(param, parameters[param])
                  for param in parameters}

    # ... and validate them
    try:
        _check_skbuild_parameters(skbuild_kw)
    except SKBuildError as ex:
        import traceback
        print("Traceback (most recent call last):")
        traceback.print_tb(sys.exc_info()[2])
        print('')
        sys.exit(ex)

    # Convert source dir to a path relative to the root
    # of the project
    cmake_source_dir = skbuild_kw['cmake_source_dir']
    if cmake_source_dir == ".":
        cmake_source_dir = ""
    if os.path.isabs(cmake_source_dir):
        cmake_source_dir = os.path.relpath(cmake_source_dir)

    # Skip running CMake in the following cases:
    # * flag "--skip-cmake" is provided
    # * "display only" argument is provided (e.g  '--help', '--author', ...)
    # * no command-line arguments or invalid ones are provided
    # * no command requiring cmake is provided
    # * no CMakeLists.txt if found
    display_only = has_invalid_arguments = help_commands = False
    force_cmake = skip_cmake = False
    commands = []
    try:
        (display_only, help_commands, commands,
         hide_listing, force_cmake, skip_cmake,
         plat_name, build_ext_inplace) = \
            _parse_setuptools_arguments(kw)
    except (DistutilsArgError, DistutilsGetoptError):
        has_invalid_arguments = True

    has_cmakelists = os.path.exists(
        os.path.join(cmake_source_dir, "CMakeLists.txt"))
    if not has_cmakelists:
        print('skipping skbuild (no CMakeLists.txt found)')

    skip_skbuild = (display_only
                    or has_invalid_arguments
                    or not _should_run_cmake(commands,
                                             skbuild_kw["cmake_with_sdist"])
                    or not has_cmakelists)
    if skip_skbuild and not force_cmake:
        if help_commands:
            # Prepend scikit-build help. Generate option descriptions using
            # argparse.
            skbuild_parser = create_skbuild_argparser()
            arg_descriptions = [
                line for line in skbuild_parser.format_help().split('\n')
                if line.startswith('  ')
                ]
            print('scikit-build options:')
            print('\n'.join(arg_descriptions))
            print('')
            print('Arguments following a "--" are passed directly to CMake '
                  '(e.g. -DMY_VAR:BOOL=TRUE).')
            print('Arguments following a second "--" are passed directly to '
                  ' the build tool.')
            print('')
        return upstream_setup(*args, **kw)

    developer_mode = "develop" in commands or "test" in commands or build_ext_inplace

    packages = kw.get('packages', [])
    package_dir = kw.get('package_dir', {})
    package_data = copy.deepcopy(kw.get('package_data', {}))

    py_modules = kw.get('py_modules', [])
    new_py_modules = {py_module: False for py_module in py_modules}

    scripts = kw.get('scripts', [])
    new_scripts = {script: False for script in scripts}

    data_files = {
        (parent_dir or '.'): set(file_list)
        for parent_dir, file_list in kw.get('data_files', [])
    }

    # Since CMake arguments provided through the command line have more
    # weight and when CMake is given multiple times a argument, only the last
    # one is considered, let's prepend the one provided in the setup call.
    cmake_args = skbuild_kw['cmake_args'] + cmake_args

    if sys.platform == 'darwin':

        # If no ``--plat-name`` argument was passed, set default value.
        if plat_name is None:
            plat_name = skbuild_plat_name()

        (_, version, machine) = plat_name.split('-')

        # The loop here allows for CMAKE_OSX_* command line arguments to overload
        # values passed with either the ``--plat-name`` command-line argument
        # or the ``cmake_args`` setup option.
        for cmake_arg in cmake_args:
            if 'CMAKE_OSX_DEPLOYMENT_TARGET' in cmake_arg:
                version = cmake_arg.split('=')[1]
            if 'CMAKE_OSX_ARCHITECTURES' in cmake_arg:
                machine = cmake_arg.split('=')[1]

        set_skbuild_plat_name("macosx-{}-{}".format(version, machine))

        # Set platform env. variable so that commands (e.g. bdist_wheel)
        # uses this information. The _PYTHON_HOST_PLATFORM env. variable is
        # used in distutils.util.get_platform() function.
        os.environ['_PYTHON_HOST_PLATFORM'] = skbuild_plat_name()

        # Set CMAKE_OSX_DEPLOYMENT_TARGET and CMAKE_OSX_ARCHITECTURES if not already
        # specified
        (_, version, machine) = skbuild_plat_name().split('-')
        if not cmaker.has_cmake_cache_arg(
                cmake_args, 'CMAKE_OSX_DEPLOYMENT_TARGET'):
            cmake_args.append(
                '-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=%s' % version
            )
        if not cmaker.has_cmake_cache_arg(
                cmake_args, 'CMAKE_OSX_ARCHITECTURES'):
            cmake_args.append(
                '-DCMAKE_OSX_ARCHITECTURES:STRING=%s' % machine
            )

    # Install cmake if listed in `setup_requires`
    for package in kw.get('setup_requires', []):
        if Requirement(package).name == 'cmake':
            setup_requires = [package]
            dist = upstream_Distribution({'setup_requires': setup_requires})
            dist.fetch_build_eggs(setup_requires)

            # Considering packages associated with "setup_requires" keyword are
            # installed in .eggs subdirectory without honoring setuptools "console_scripts"
            # entry_points and without settings the expected executable permissions, we are
            # taking care of it below.
            import cmake
            for executable in ['cmake', 'cpack', 'ctest']:
                executable = os.path.join(cmake.CMAKE_BIN_DIR, executable)
                if platform.system().lower() == 'windows':
                    executable += '.exe'
                st = os.stat(executable)
                permissions = (
                        st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
                )
                os.chmod(executable, permissions)
            cmake_executable = os.path.join(cmake.CMAKE_BIN_DIR, 'cmake')
            break

    # Languages are used to determine a working generator
    cmake_languages = skbuild_kw['cmake_languages']

    try:
        if cmake_executable is None:
            cmake_executable = CMAKE_DEFAULT_EXECUTABLE
        cmkr = cmaker.CMaker(cmake_executable)
        if not skip_cmake:
            cmake_minimum_required_version = skbuild_kw['cmake_minimum_required_version']
            if cmake_minimum_required_version is not None:
                if parse_version(cmkr.cmake_version) < parse_version(cmake_minimum_required_version):
                    raise SKBuildError(
                        "CMake version %s or higher is required. CMake version %s is being used" % (
                            cmake_minimum_required_version, cmkr.cmake_version))
            # Used to confirm that the cmake executable is the same, and that the environment
            # didn't change
            cmake_spec = {
                'args': [which(CMAKE_DEFAULT_EXECUTABLE)] + cmake_args,
                'version': cmkr.cmake_version,
                'environment': {
                    'PYTHONNOUSERSITE': os.environ.get("PYTHONNOUSERSITE"),
                    'PYTHONPATH': os.environ.get("PYTHONPATH")
                }
            }

            # skip the configure step for a cached build
            env = cmkr.get_cached_generator_env()
            if env is None or cmake_spec != _load_cmake_spec():
                env = cmkr.configure(cmake_args,
                                     skip_generator_test=skip_generator_test,
                                     cmake_source_dir=cmake_source_dir,
                                     cmake_install_dir=skbuild_kw['cmake_install_dir'],
                                     languages=cmake_languages
                                     )
                _save_cmake_spec(cmake_spec)
            cmkr.make(make_args, env=env)
    except SKBuildGeneratorNotFoundError as ex:
        sys.exit(ex)
    except SKBuildError as ex:
        import traceback
        print("Traceback (most recent call last):")
        traceback.print_tb(sys.exc_info()[2])
        print('')
        sys.exit(ex)

    # If any, strip ending slash from each package directory
    package_dir = {package: prefix[:-1] if prefix[-1] == "/" else prefix
                   for package, prefix in package_dir.items()}

    # If needed, set reasonable defaults for package_dir
    for package in packages:
        if package not in package_dir:
            package_dir[package] = package.replace(".", "/")
            if '' in package_dir:
                package_dir[package] = to_unix_path(os.path.join(package_dir[''], package_dir[package]))

    package_prefixes = _collect_package_prefixes(package_dir, packages)

    _classify_installed_files(cmkr.install(), package_data, package_prefixes,
                              py_modules, new_py_modules,
                              scripts, new_scripts,
                              data_files,
                              cmake_source_dir, skbuild_kw['cmake_install_dir'])

    original_manifestin_data_files = []
    if kw.get("include_package_data", False):
        original_manifestin_data_files = parse_manifestin(os.path.join(os.getcwd(), "MANIFEST.in"))
        for path in original_manifestin_data_files:
            _classify_file(path, package_data, package_prefixes,
                           py_modules, new_py_modules,
                           scripts, new_scripts,
                           data_files)

    if developer_mode:
        # Copy packages
        for package, package_file_list in package_data.items():
            for package_file in package_file_list:
                package_file = os.path.join(package_dir[package], package_file)
                cmake_file = os.path.join(CMAKE_INSTALL_DIR(), package_file)
                if os.path.exists(cmake_file):
                    _copy_file(cmake_file, package_file, hide_listing)

        # Copy modules
        for py_module in py_modules:
            package_file = py_module + ".py"
            cmake_file = os.path.join(CMAKE_INSTALL_DIR(), package_file)
            if os.path.exists(cmake_file):
                _copy_file(cmake_file, package_file, hide_listing)
    else:
        _consolidate_package_modules(
            cmake_source_dir, packages, package_dir, py_modules, package_data, hide_listing)

        original_package_data = kw.get('package_data', {}).copy()
        _consolidate_package_data_files(original_package_data, package_prefixes, hide_listing)

        for data_file in original_manifestin_data_files:
            dest_data_file = os.path.join(CMAKE_INSTALL_DIR(), data_file)
            _copy_file(data_file, dest_data_file, hide_listing)

    kw['package_data'] = package_data
    kw['package_dir'] = {
        package: (
            os.path.join(CMAKE_INSTALL_DIR(), prefix)
            if os.path.exists(os.path.join(CMAKE_INSTALL_DIR(), prefix))
            else prefix)
        for prefix, package in package_prefixes
    }

    kw['scripts'] = [
        os.path.join(CMAKE_INSTALL_DIR(), script) if mask else script
        for script, mask in new_scripts.items()
    ]

    kw['data_files'] = [
        (parent_dir, list(file_set))
        for parent_dir, file_set in data_files.items()
    ]

    if 'zip_safe' not in kw:
        kw['zip_safe'] = False

    # Adapted from espdev/ITKPythonInstaller/setup.py.in
    # pylint: disable=missing-docstring
    class BinaryDistribution(upstream_Distribution):
        def has_ext_modules(self):  # pylint: disable=no-self-use
            return has_cmakelists
    kw['distclass'] = BinaryDistribution

    print("")

    return upstream_setup(*args, **kw)
def _parse_setuptools_arguments(setup_attrs):
    """This function instantiates a Distribution object and
    parses the command line arguments.

    It returns the tuple ``(display_only, help_commands, commands, hide_listing, force_cmake, skip_cmake, plat_name)``
    where

    - display_only is a boolean indicating if an argument like '--help',
      '--help-commands' or '--author' was passed.
    - help_commands is a boolean indicating if argument '--help-commands'
      was passed.
    - commands contains the list of commands that were passed.
    - hide_listing is a boolean indicating if the list of files being included
      in the distribution is displayed or not.
    - force_cmake a boolean indicating that CMake should always be executed.
    - skip_cmake is a boolean indicating if the execution of CMake should
      explicitly be skipped.
    - plat_name is a string identifying the platform name to embed in generated
      filenames. It defaults to :func:`skbuild.constants.skbuild_plat_name()`.
    - build_ext_inplace is a boolean indicating if ``build_ext`` command was
      specified along with the --inplace argument.

    Otherwise it raises DistutilsArgError exception if there are
    any error on the command-line, and it raises DistutilsGetoptError
    if there any error in the command 'options' attribute.

    The code has been adapted from the setup() function available
    in distutils/core.py.
    """
    setup_attrs = dict(setup_attrs)

    setup_attrs['script_name'] = os.path.basename(sys.argv[0])

    dist = upstream_Distribution(setup_attrs)

    # Update class attribute to also ensure the argument is processed
    # when ``upstream_setup`` is called.
    # pylint:disable=no-member
    upstream_Distribution.global_options.extend([
        ('hide-listing', None, "do not display list of files being "
                               "included in the distribution"),
        ('force-cmake', None, "always run CMake"),
        ('skip-cmake', None, "do not run CMake"),
    ])

    # Find and parse the config file(s): they will override options from
    # the setup script, but be overridden by the command line.
    dist.parse_config_files()

    # Parse the command line and override config files; any
    # command-line errors are the end user's fault, so turn them into
    # SystemExit to suppress tracebacks.

    with _capture_output():
        result = dist.parse_command_line()
        display_only = not result
        if not hasattr(dist, 'hide_listing'):
            dist.hide_listing = False
        if not hasattr(dist, 'force_cmake'):
            dist.force_cmake = False
        if not hasattr(dist, 'skip_cmake'):
            dist.skip_cmake = False

    plat_names = set()
    for cmd in [dist.get_command_obj(command) for command in dist.commands]:
        if getattr(cmd, 'plat_name', None) is not None:
            plat_names.add(cmd.plat_name)
    if not plat_names:
        plat_names.add(None)
    elif len(plat_names) > 1:
        raise SKBuildError(
            "--plat-name is ambiguous: %s" % ", ".join(plat_names))
    plat_name = list(plat_names)[0]

    build_ext_inplace = dist.get_command_obj('build_ext').inplace

    return (display_only, dist.help_commands, dist.commands,
            dist.hide_listing, dist.force_cmake, dist.skip_cmake,
            plat_name, build_ext_inplace)
def setup(*args, **kw):  # noqa: C901
    """This function wraps setup() so that we can run cmake, make,
    CMake build, then proceed as usual with setuptools, appending the
    CMake-generated output as necessary.

    The CMake project is re-configured only if needed. This is achieved by (1) retrieving the environment mapping
    associated with the generator set in the ``CMakeCache.txt`` file, (2) saving the CMake configure arguments and
    version in :func:`skbuild.constants.CMAKE_SPEC_FILE()`: and (3) re-configuring only if either the generator or
    the CMake specs change.
    """
    sys.argv, cmake_executable, skip_generator_test, cmake_args, make_args = parse_args(
    )

    # work around https://bugs.python.org/issue1011113
    # (patches provided, but no updates since 2014)
    cmdclass = kw.get('cmdclass', {})
    cmdclass['build'] = cmdclass.get('build', build.build)
    cmdclass['build_py'] = cmdclass.get('build_py', build_py.build_py)
    cmdclass['build_ext'] = cmdclass.get('build_ext', build_ext.build_ext)
    cmdclass['install'] = cmdclass.get('install', install.install)
    cmdclass['install_lib'] = cmdclass.get('install_lib',
                                           install_lib.install_lib)
    cmdclass['install_scripts'] = cmdclass.get('install_scripts',
                                               install_scripts.install_scripts)
    cmdclass['clean'] = cmdclass.get('clean', clean.clean)
    cmdclass['sdist'] = cmdclass.get('sdist', sdist.sdist)
    cmdclass['bdist'] = cmdclass.get('bdist', bdist.bdist)
    cmdclass['bdist_wheel'] = cmdclass.get('bdist_wheel',
                                           bdist_wheel.bdist_wheel)
    cmdclass['egg_info'] = cmdclass.get('egg_info', egg_info.egg_info)
    cmdclass['generate_source_manifest'] = cmdclass.get(
        'generate_source_manifest',
        generate_source_manifest.generate_source_manifest)
    cmdclass['test'] = cmdclass.get('test', test.test)
    kw['cmdclass'] = cmdclass

    # Extract setup keywords specific to scikit-build and remove them from kw.
    # Removing the keyword from kw need to be done here otherwise, the
    # following call to _parse_setuptools_arguments would complain about
    # unknown setup options.
    parameters = {
        'cmake_args': [],
        'cmake_install_dir': '',
        'cmake_source_dir': '',
        'cmake_with_sdist': False,
        'cmake_languages': ('C', 'CXX'),
        'cmake_minimum_required_version': None,
        'cmake_process_manifest_hook': None
    }
    skbuild_kw = {
        param: kw.pop(param, parameters[param])
        for param in parameters
    }

    # ... and validate them
    try:
        _check_skbuild_parameters(skbuild_kw)
    except SKBuildError as ex:
        import traceback
        print("Traceback (most recent call last):")
        traceback.print_tb(sys.exc_info()[2])
        print('')
        sys.exit(ex)

    # Convert source dir to a path relative to the root
    # of the project
    cmake_source_dir = skbuild_kw['cmake_source_dir']
    if cmake_source_dir == ".":
        cmake_source_dir = ""
    if os.path.isabs(cmake_source_dir):
        cmake_source_dir = os.path.relpath(cmake_source_dir)

    # Skip running CMake in the following cases:
    # * flag "--skip-cmake" is provided
    # * "display only" argument is provided (e.g  '--help', '--author', ...)
    # * no command-line arguments or invalid ones are provided
    # * no command requiring cmake is provided
    # * no CMakeLists.txt if found
    display_only = has_invalid_arguments = help_commands = False
    force_cmake = skip_cmake = False
    commands = []
    try:
        (display_only, help_commands, commands,
         hide_listing, force_cmake, skip_cmake,
         plat_name, build_ext_inplace) = \
            _parse_setuptools_arguments(kw)
    except (DistutilsArgError, DistutilsGetoptError):
        has_invalid_arguments = True

    has_cmakelists = os.path.exists(
        os.path.join(cmake_source_dir, "CMakeLists.txt"))
    if not has_cmakelists:
        print('skipping skbuild (no CMakeLists.txt found)')

    skip_skbuild = (
        display_only or has_invalid_arguments
        or not _should_run_cmake(commands, skbuild_kw["cmake_with_sdist"])
        or not has_cmakelists)
    if skip_skbuild and not force_cmake:
        if help_commands:
            # Prepend scikit-build help. Generate option descriptions using
            # argparse.
            skbuild_parser = create_skbuild_argparser()
            arg_descriptions = [
                line for line in skbuild_parser.format_help().split('\n')
                if line.startswith('  ')
            ]
            print('scikit-build options:')
            print('\n'.join(arg_descriptions))
            print('')
            print('Arguments following a "--" are passed directly to CMake '
                  '(e.g. -DMY_VAR:BOOL=TRUE).')
            print('Arguments following a second "--" are passed directly to '
                  ' the build tool.')
            print('')
        return upstream_setup(*args, **kw)

    developer_mode = "develop" in commands or "test" in commands or build_ext_inplace

    packages = kw.get('packages', [])
    package_dir = kw.get('package_dir', {})
    package_data = copy.deepcopy(kw.get('package_data', {}))

    py_modules = kw.get('py_modules', [])
    new_py_modules = {py_module: False for py_module in py_modules}

    scripts = kw.get('scripts', [])
    new_scripts = {script: False for script in scripts}

    data_files = {(parent_dir or '.'): set(file_list)
                  for parent_dir, file_list in kw.get('data_files', [])}

    # Since CMake arguments provided through the command line have more
    # weight and when CMake is given multiple times a argument, only the last
    # one is considered, let's prepend the one provided in the setup call.
    cmake_args = skbuild_kw['cmake_args'] + cmake_args

    if sys.platform == 'darwin':

        # If no ``--plat-name`` argument was passed, set default value.
        if plat_name is None:
            plat_name = skbuild_plat_name()

        (_, version, machine) = plat_name.split('-')

        # The loop here allows for CMAKE_OSX_* command line arguments to overload
        # values passed with either the ``--plat-name`` command-line argument
        # or the ``cmake_args`` setup option.
        for cmake_arg in cmake_args:
            if 'CMAKE_OSX_DEPLOYMENT_TARGET' in cmake_arg:
                version = cmake_arg.split('=')[1]
            if 'CMAKE_OSX_ARCHITECTURES' in cmake_arg:
                machine = cmake_arg.split('=')[1]
                if set(machine.split(';')) == {'x86_64', 'arm64'}:
                    machine = 'universal2'

        set_skbuild_plat_name("macosx-{}-{}".format(version, machine))

        # Set platform env. variable so that commands (e.g. bdist_wheel)
        # uses this information. The _PYTHON_HOST_PLATFORM env. variable is
        # used in distutils.util.get_platform() function.
        os.environ.setdefault('_PYTHON_HOST_PLATFORM', skbuild_plat_name())

        # Set CMAKE_OSX_DEPLOYMENT_TARGET and CMAKE_OSX_ARCHITECTURES if not already
        # specified
        (_, version, machine) = skbuild_plat_name().split('-')
        if not cmaker.has_cmake_cache_arg(cmake_args,
                                          'CMAKE_OSX_DEPLOYMENT_TARGET'):
            cmake_args.append('-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=%s' %
                              version)
        if not cmaker.has_cmake_cache_arg(cmake_args,
                                          'CMAKE_OSX_ARCHITECTURES'):
            machine_archs = 'x86_64;arm64' if machine == 'universal2' else machine
            cmake_args.append('-DCMAKE_OSX_ARCHITECTURES:STRING=%s' %
                              machine_archs)

    # Install cmake if listed in `setup_requires`
    for package in kw.get('setup_requires', []):
        if Requirement(package).name == 'cmake':
            setup_requires = [package]
            dist = upstream_Distribution({'setup_requires': setup_requires})
            dist.fetch_build_eggs(setup_requires)

            # Considering packages associated with "setup_requires" keyword are
            # installed in .eggs subdirectory without honoring setuptools "console_scripts"
            # entry_points and without settings the expected executable permissions, we are
            # taking care of it below.
            import cmake
            for executable in ['cmake', 'cpack', 'ctest']:
                executable = os.path.join(cmake.CMAKE_BIN_DIR, executable)
                if platform.system().lower() == 'windows':
                    executable += '.exe'
                st = os.stat(executable)
                permissions = (st.st_mode | stat.S_IXUSR | stat.S_IXGRP
                               | stat.S_IXOTH)
                os.chmod(executable, permissions)
            cmake_executable = os.path.join(cmake.CMAKE_BIN_DIR, 'cmake')
            break

    # Languages are used to determine a working generator
    cmake_languages = skbuild_kw['cmake_languages']

    try:
        if cmake_executable is None:
            cmake_executable = CMAKE_DEFAULT_EXECUTABLE
        cmkr = cmaker.CMaker(cmake_executable)
        if not skip_cmake:
            cmake_minimum_required_version = skbuild_kw[
                'cmake_minimum_required_version']
            if cmake_minimum_required_version is not None:
                if parse_version(cmkr.cmake_version) < parse_version(
                        cmake_minimum_required_version):
                    raise SKBuildError(
                        "CMake version {} or higher is required. CMake version {} is being used"
                        .format(cmake_minimum_required_version,
                                cmkr.cmake_version))
            # Used to confirm that the cmake executable is the same, and that the environment
            # didn't change
            cmake_spec = {
                'args': [which(CMAKE_DEFAULT_EXECUTABLE)] + cmake_args,
                'version': cmkr.cmake_version,
                'environment': {
                    'PYTHONNOUSERSITE': os.environ.get("PYTHONNOUSERSITE"),
                    'PYTHONPATH': os.environ.get("PYTHONPATH")
                }
            }

            # skip the configure step for a cached build
            env = cmkr.get_cached_generator_env()
            if env is None or cmake_spec != _load_cmake_spec():
                env = cmkr.configure(
                    cmake_args,
                    skip_generator_test=skip_generator_test,
                    cmake_source_dir=cmake_source_dir,
                    cmake_install_dir=skbuild_kw['cmake_install_dir'],
                    languages=cmake_languages)
                _save_cmake_spec(cmake_spec)
            cmkr.make(make_args, env=env)
    except SKBuildGeneratorNotFoundError as ex:
        sys.exit(ex)
    except SKBuildError as ex:
        import traceback
        print("Traceback (most recent call last):")
        traceback.print_tb(sys.exc_info()[2])
        print('')
        sys.exit(ex)

    # If any, strip ending slash from each package directory
    package_dir = {
        package: prefix[:-1] if prefix and prefix[-1] == "/" else prefix
        for package, prefix in package_dir.items()
    }

    # If needed, set reasonable defaults for package_dir
    for package in packages:
        if package not in package_dir:
            package_dir[package] = package.replace(".", "/")
            if '' in package_dir:
                package_dir[package] = to_unix_path(
                    os.path.join(package_dir[''], package_dir[package]))

    package_prefixes = _collect_package_prefixes(package_dir, packages)

    # This hook enables custom processing of the cmake manifest
    cmake_manifest = cmkr.install()
    process_manifest = skbuild_kw.get('cmake_process_manifest_hook')
    if process_manifest is not None:
        if callable(process_manifest):
            cmake_manifest = process_manifest(cmake_manifest)
        else:
            raise SKBuildError(
                'The cmake_process_manifest_hook argument should be callable.')

    _classify_installed_files(cmake_manifest, package_data, package_prefixes,
                              py_modules, new_py_modules, scripts, new_scripts,
                              data_files, cmake_source_dir,
                              skbuild_kw['cmake_install_dir'])

    original_manifestin_data_files = []
    if kw.get("include_package_data", False):
        original_manifestin_data_files = parse_manifestin(
            os.path.join(os.getcwd(), "MANIFEST.in"))
        for path in original_manifestin_data_files:
            _classify_file(path, package_data, package_prefixes, py_modules,
                           new_py_modules, scripts, new_scripts, data_files)

    if developer_mode:
        # Copy packages
        for package, package_file_list in package_data.items():
            for package_file in package_file_list:
                package_file = os.path.join(package_dir[package], package_file)
                cmake_file = os.path.join(CMAKE_INSTALL_DIR(), package_file)
                if os.path.exists(cmake_file):
                    _copy_file(cmake_file, package_file, hide_listing)

        # Copy modules
        for py_module in py_modules:
            package_file = py_module + ".py"
            cmake_file = os.path.join(CMAKE_INSTALL_DIR(), package_file)
            if os.path.exists(cmake_file):
                _copy_file(cmake_file, package_file, hide_listing)
    else:
        _consolidate_package_modules(cmake_source_dir, packages, package_dir,
                                     py_modules, package_data, hide_listing)

        original_package_data = kw.get('package_data', {}).copy()
        _consolidate_package_data_files(original_package_data,
                                        package_prefixes, hide_listing)

        for data_file in original_manifestin_data_files:
            dest_data_file = os.path.join(CMAKE_INSTALL_DIR(), data_file)
            _copy_file(data_file, dest_data_file, hide_listing)

    kw['package_data'] = package_data
    kw['package_dir'] = {
        package: (os.path.join(CMAKE_INSTALL_DIR(), prefix) if os.path.exists(
            os.path.join(CMAKE_INSTALL_DIR(), prefix)) else prefix)
        for prefix, package in package_prefixes
    }

    kw['scripts'] = [
        os.path.join(CMAKE_INSTALL_DIR(), script) if mask else script
        for script, mask in new_scripts.items()
    ]

    kw['data_files'] = [(parent_dir, list(file_set))
                        for parent_dir, file_set in data_files.items()]

    if 'zip_safe' not in kw:
        kw['zip_safe'] = False

    # Adapted from espdev/ITKPythonInstaller/setup.py.in
    # pylint: disable=missing-docstring
    class BinaryDistribution(upstream_Distribution):
        def has_ext_modules(self):  # pylint: disable=no-self-use
            return has_cmakelists

    kw['distclass'] = BinaryDistribution

    print("")

    return upstream_setup(*args, **kw)
def _parse_setuptools_arguments(setup_attrs):
    """This function instantiates a Distribution object and
    parses the command line arguments.

    It returns the tuple ``(display_only, help_commands, commands, hide_listing, force_cmake, skip_cmake, plat_name)``
    where

    - display_only is a boolean indicating if an argument like '--help',
      '--help-commands' or '--author' was passed.
    - help_commands is a boolean indicating if argument '--help-commands'
      was passed.
    - commands contains the list of commands that were passed.
    - hide_listing is a boolean indicating if the list of files being included
      in the distribution is displayed or not.
    - force_cmake a boolean indicating that CMake should always be executed.
    - skip_cmake is a boolean indicating if the execution of CMake should
      explicitly be skipped.
    - plat_name is a string identifying the platform name to embed in generated
      filenames. It defaults to :func:`skbuild.constants.skbuild_plat_name()`.
    - build_ext_inplace is a boolean indicating if ``build_ext`` command was
      specified along with the --inplace argument.

    Otherwise it raises DistutilsArgError exception if there are
    any error on the command-line, and it raises DistutilsGetoptError
    if there any error in the command 'options' attribute.

    The code has been adapted from the setup() function available
    in distutils/core.py.
    """
    setup_attrs = dict(setup_attrs)

    setup_attrs['script_name'] = os.path.basename(sys.argv[0])

    dist = upstream_Distribution(setup_attrs)

    # Update class attribute to also ensure the argument is processed
    # when ``upstream_setup`` is called.
    # pylint:disable=no-member
    upstream_Distribution.global_options.extend([
        ('hide-listing', None, "do not display list of files being "
         "included in the distribution"),
        ('force-cmake', None, "always run CMake"),
        ('skip-cmake', None, "do not run CMake"),
    ])

    # Find and parse the config file(s): they will override options from
    # the setup script, but be overridden by the command line.
    dist.parse_config_files()

    # Parse the command line and override config files; any
    # command-line errors are the end user's fault, so turn them into
    # SystemExit to suppress tracebacks.

    with _capture_output():
        result = dist.parse_command_line()
        display_only = not result
        if not hasattr(dist, 'hide_listing'):
            dist.hide_listing = False
        if not hasattr(dist, 'force_cmake'):
            dist.force_cmake = False
        if not hasattr(dist, 'skip_cmake'):
            dist.skip_cmake = False

    plat_names = set()
    for cmd in [dist.get_command_obj(command) for command in dist.commands]:
        if getattr(cmd, 'plat_name', None) is not None:
            plat_names.add(cmd.plat_name)
    if not plat_names:
        plat_names.add(None)
    elif len(plat_names) > 1:
        raise SKBuildError("--plat-name is ambiguous: %s" %
                           ", ".join(plat_names))
    plat_name = list(plat_names)[0]

    build_ext_inplace = dist.get_command_obj('build_ext').inplace

    return (display_only, dist.help_commands, dist.commands, dist.hide_listing,
            dist.force_cmake, dist.skip_cmake, plat_name, build_ext_inplace)