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
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)