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. """ sys.argv, 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['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) 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 } 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) = \ _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_cmake = ( skip_cmake or display_only or has_invalid_arguments or not _should_run_cmake(commands, skbuild_kw["cmake_with_sdist"]) or not has_cmakelists) if skip_cmake 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 packages = kw.get('packages', []) package_dir = kw.get('package_dir', {}) package_data = kw.get('package_data', {}).copy() 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 try: cmkr = cmaker.CMaker() env = cmkr.configure(cmake_args, cmake_source_dir=cmake_source_dir, cmake_install_dir=skbuild_kw['cmake_install_dir']) 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 needed, set reasonable defaults for package_dir for package in packages: if package not in package_dir: package_dir[package] = package.replace(".", os.path.sep) package_prefixes = _collect_package_prefixes(package_dir, packages) _classify_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']) if developer_mode: 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) else: _consolidate(cmake_source_dir, packages, package_dir, py_modules, package_data, 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['py_modules'] = [ os.path.join(CMAKE_INSTALL_DIR, py_module) if mask else py_module for py_module, mask in new_py_modules.items() ] 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()] # 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 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 setup(*args, **kw): """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. """ sys.argv, 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['install'] = cmdclass.get('install', install.install) 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) kw['cmdclass'] = cmdclass # Skip running CMake in the following cases: # * no command-line arguments or invalid ones are provided # * "display only" argument like '--help', '--help-commands' # or '--author' are provided display_only = has_invalid_arguments = help_commands = False commands = [] try: (display_only, help_commands, commands) = \ _parse_setuptools_arguments(kw) except (DistutilsArgError, DistutilsGetoptError): has_invalid_arguments = True is_cmake_project = os.path.exists("CMakeLists.txt") if not is_cmake_project: print('skipping skbuild (no CMakeLists.txt found)') skip_cmake = (display_only or has_invalid_arguments or 'clean' in commands or not is_cmake_project) if skip_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) packages = kw.get('packages', []) package_dir = kw.get('package_dir', {}) package_data = kw.get('package_data', {}).copy() py_modules = kw.get('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', []) } try: cmkr = cmaker.CMaker() cmkr.configure(cmake_args) cmkr.make(make_args) except SKBuildError as e: import traceback print("Traceback (most recent call last):") traceback.print_tb(sys.exc_info()[2]) print('') sys.exit(e) package_prefixes = _collect_package_prefixes(package_dir, packages) _classify_files(cmkr.install(), package_data, package_prefixes, py_modules, scripts, new_scripts, data_files) kw['package_data'] = package_data kw['package_dir'] = { package: os.path.join(cmaker.CMAKE_INSTALL_DIR, prefix) for prefix, package in package_prefixes } kw['py_modules'] = py_modules kw['scripts'] = [ os.path.join(cmaker.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() ] # Adapted from espdev/ITKPythonInstaller/setup.py.in class BinaryDistribution(upstream_Distribution): def has_ext_modules(self): return True kw['distclass'] = BinaryDistribution return upstream_setup(*args, **kw)
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 setup(*args, **kw): """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. """ sys.argv, cmake_args, make_args = parse_args() packages = kw.get('packages', []) package_dir = kw.get('package_dir', {}) package_data = kw.get('package_data', {}).copy() py_modules = kw.get('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', []) } # collect the list of prefixes for all packages # # The list is used to match paths in the install manifest to packages # specified in the setup.py script. # # The list is sorted in decreasing order of prefix length so that paths are # matched with their immediate parent package, instead of any of that # package's ancestors. # # For example, consider the project structure below. Assume that the # setup call was made with a package list featuring "top" and "top.bar", but # not "top.not_a_subpackage". # # top/ -> top/ # __init__.py -> top/__init__.py (parent: top) # foo.py -> top/foo.py (parent: top) # bar/ -> top/bar/ (parent: top) # __init__.py -> top/bar/__init__.py (parent: top.bar) # # not_a_subpackage/ -> top/not_a_subpackage/ (parent: top) # data_0.txt -> top/not_a_subpackage/data_0.txt (parent: top) # data_1.txt -> top/not_a_subpackage/data_1.txt (parent: top) # # The paths in the generated install manifest are matched to packages # according to the parents indicated on the right. Only packages that are # specified in the setup() call are considered. Because of the sort order, # the data files on the bottom would have been mapped to # "top.not_a_subpackage" instead of "top", proper -- had such a package been # specified. package_prefixes = list(sorted( ( (package_dir[package].replace('.', '/'), package) for package in packages ), key=lambda tup: len(tup[0]), reverse=True )) try: cmkr = cmaker.CMaker() cmkr.configure(cmake_args) cmkr.make(make_args) except SKBuildError as e: import traceback print("Traceback (most recent call last):") traceback.print_tb(sys.exc_info()[2]) print() sys.exit(e) _classify_files(cmkr.install(), package_data, package_prefixes, py_modules, scripts, new_scripts, data_files) kw['package_data'] = package_data kw['package_dir'] = { package: os.path.join(cmaker.CMAKE_INSTALL_DIR, prefix) for prefix, package in package_prefixes } kw['py_modules'] = py_modules kw['scripts'] = [ os.path.join(cmaker.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() ] # 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['install'] = cmdclass.get('install', install.install) cmdclass['clean'] = cmdclass.get('clean', clean.clean) cmdclass['bdist'] = cmdclass.get('bdist', bdist.bdist) cmdclass['bdist_wheel'] = cmdclass.get( 'bdist_wheel', bdist_wheel.bdist_wheel) kw['cmdclass'] = cmdclass return upstream_setup(*args, **kw)