diff --git a/README.md b/README.md index f28300e8..bc532ed2 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,14 @@ cmake.source-dir = "." # DEPRECATED in 0.10; use build.targets instead. cmake.targets = "" +# The CMAKE_TOOLCHAIN_FILE used for cross-compilation. +cmake.toolchain-file = "" + +# Do not pass the current environment's python hints such as +# ``Python_EXECUTABLE``. Primarily used for cross-compilation where the +# CMAKE_TOOLCHAIN_FILE should handle it instead. +cmake.no-python-hints = false + # The versions of Ninja to allow. If Ninja is not present on the system or does # not pass this specifier, it will be downloaded via PyPI if possible. An empty # string will disable this check. @@ -257,6 +265,12 @@ wheel.exclude = [] # The build tag to use for the wheel. If empty, no build tag is used. wheel.build-tag = "" +# Manually specify the wheel tags to use, ignoring other inputs such as +# ``wheel.py-api``. Each tag must be of the format +# {interpreter}-{abi}-{platform}. If not specified, these tags are automatically +# calculated. +wheel.tags = [] + # If CMake is less than this value, backport a copy of FindPython. Set to 0 # disable this, or the empty string. backport.find-python = "3.26.1" diff --git a/docs/conf.py b/docs/conf.py index 23b35cef..60a06157 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -134,6 +134,7 @@ myst_substitutions = { "version": version, } +myst_heading_anchors = 2 # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section) diff --git a/docs/guide/crosscompile.md b/docs/guide/crosscompile.md index 97b24531..ea71e441 100644 --- a/docs/guide/crosscompile.md +++ b/docs/guide/crosscompile.md @@ -1,5 +1,9 @@ # Cross-compiling +Generally scikit-build-core will try to account for environment variables that +specify to CMake directly how to cross-compile. Alternatively, you can define +manually how to cross-compile as detailed in [manual cross compilation] section. + ## macOS Unlike the other platforms, macOS has the ability to target older operating @@ -50,10 +54,7 @@ correct suffix. These values are set by cibuildwheel when cross-compiling. ## Linux -It should be possible to cross-compile to Linux, but due to the challenges of -getting the manylinux RHEL devtoolkit compilers, this is currently a TODO. See -`py-build-cmake `\_ -for an alternative package's usage of toolchain files. +See [manual cross compilation] section for the general approach. ### Intel to Emscripten (Pyodide) @@ -64,3 +65,71 @@ by setting `_PYTHON_SYSCONFIGDATA_NAME`. This causes values like `SOABI` and This is unfortunately incorrectly stripped from the cmake wrapper pyodide uses, so FindPython will report the wrong values, but pyodide-build will rename the .so's afterwards. + +## Manual cross compilation + +The manual cross compilation assumes you have [toolchain file] prepared defining +the cross-compilers and where to search for the target development files, +including the python library. A simple setup of this is to use the clang +compiler and point `CMAKE_SYSROOT` to a mounted copy of the target system's root + +```cmake +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR aarch64) + +set(triple aarch64-linux-gnu) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_C_COMPILER_TARGET ${triple}) +set(CMAKE_CXX_COMPILER_TARGET ${triple}) + +set(CMAKE_SYSROOT "/path/to/aarch64/mount/") +``` + +For more complex environments such as embedded devices, Android or iOS see +CMake's guide on how to write the [toolchain file]. + +You can pass the toolchain file using the environment variable +`CMAKE_TOOLCHAIN_FILE`, or the `cmake.toolchain-file` pyproject option. You may +also need to use `wheel.tags` to manually specify the wheel tags to use for the +file and `cmake.no-python-hints` if the target python should be detected using +the toolchain file instead. + +:::{note} + +Because most of the logic in [`FindPython`] is gated by the +`CMAKE_CROSSCOMPILING`, you generally should _not_ include the `Interpreter` +component in the `find_package` command or use the `Python_ARTIFACTS_PREFIX` +feature to distinguish the system and target components. + +::: + +:::{versionadded} 0.11 + +::: + +### Crossenv + +[Crossenv] cross compilation is supported in scikit-build-core. This tool +creates a fake virtual environment where configuration hints such as +`EXT_SUFFIX` are overwritten with the target's values. This should work without +specifying `wheel.tags` overwrites manually. + +:::{note} + +Because the target Python executable is being faked, the usage of +`CMAKE_CROSSCOMPILING_EMULATOR` for the `Interpreter` would not be correct in +this case. + +::: + +:::{versionadded} 0.11 + +::: + +[manual cross compilation]: #manual-cross-compilation +[toolchain file]: + https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling +[crossenv]: https://crossenv.readthedocs.io/en/latest/ +[`FindPython`]: https://cmake.org/cmake/help/git-master/module/FindPython.html diff --git a/src/scikit_build_core/build/wheel.py b/src/scikit_build_core/build/wheel.py index 47e223bb..9e122ba4 100644 --- a/src/scikit_build_core/build/wheel.py +++ b/src/scikit_build_core/build/wheel.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any, Literal from packaging.requirements import Requirement +from packaging.tags import Tag from packaging.utils import canonicalize_name from .._compat import tomllib @@ -260,6 +261,10 @@ def _build_wheel_impl_impl( f"{{red}}({state})", ) + override_wheel_tags = None + if settings.wheel.tags: + override_wheel_tags = {Tag(*tag.split("-")) for tag in settings.wheel.tags} + with tempfile.TemporaryDirectory() as tmpdir: build_tmp_folder = Path(tmpdir) wheel_dir = build_tmp_folder / "wheel" @@ -364,7 +369,7 @@ def _build_wheel_impl_impl( wheel = WheelWriter( metadata, Path(metadata_directory), - tags.as_tags_set(), + override_wheel_tags or tags.as_tags_set(), WheelMetadata( root_is_purelib=targetlib == "purelib", build_tag=settings.wheel.build_tag, @@ -480,7 +485,7 @@ def _build_wheel_impl_impl( with WheelWriter( metadata, Path(wheel_directory), - tags.as_tags_set(), + override_wheel_tags or tags.as_tags_set(), WheelMetadata( root_is_purelib=targetlib == "purelib", build_tag=settings.wheel.build_tag, diff --git a/src/scikit_build_core/builder/builder.py b/src/scikit_build_core/builder/builder.py index 7079fd50..cdcde3da 100644 --- a/src/scikit_build_core/builder/builder.py +++ b/src/scikit_build_core/builder/builder.py @@ -231,25 +231,29 @@ def configure( python_include_dir = get_python_include_dir() numpy_include_dir = get_numpy_include_dir() - # Classic Find Python - cache_config["PYTHON_EXECUTABLE"] = Path(sys.executable) - cache_config["PYTHON_INCLUDE_DIR"] = python_include_dir - if python_library: - cache_config["PYTHON_LIBRARY"] = python_library - - # Modern Find Python - for prefix in ("Python", "Python3"): - cache_config[f"{prefix}_EXECUTABLE"] = Path(sys.executable) - cache_config[f"{prefix}_ROOT_DIR"] = Path(sys.prefix) - cache_config[f"{prefix}_INCLUDE_DIR"] = python_include_dir - cache_config[f"{prefix}_FIND_REGISTRY"] = "NEVER" - # FindPython may break if this is set - only useful on Windows - if python_library and sysconfig.get_platform().startswith("win"): - cache_config[f"{prefix}_LIBRARY"] = python_library - if python_sabi_library and sysconfig.get_platform().startswith("win"): - cache_config[f"{prefix}_SABI_LIBRARY"] = python_sabi_library - if numpy_include_dir: - cache_config[f"{prefix}_NumPy_INCLUDE_DIR"] = numpy_include_dir + if self.settings.cmake.toolchain_file: + cache_config["CMAKE_TOOLCHAIN_FILE"] = self.settings.cmake.toolchain_file + + if not self.settings.cmake.no_python_hints: + # Classic Find Python + cache_config["PYTHON_EXECUTABLE"] = Path(sys.executable) + cache_config["PYTHON_INCLUDE_DIR"] = python_include_dir + if python_library: + cache_config["PYTHON_LIBRARY"] = python_library + + # Modern Find Python + for prefix in ("Python", "Python3"): + cache_config[f"{prefix}_EXECUTABLE"] = Path(sys.executable) + cache_config[f"{prefix}_ROOT_DIR"] = Path(sys.base_exec_prefix) + cache_config[f"{prefix}_INCLUDE_DIR"] = python_include_dir + cache_config[f"{prefix}_FIND_REGISTRY"] = "NEVER" + # FindPython may break if this is set - only useful on Windows + if python_library and sysconfig.get_platform().startswith("win"): + cache_config[f"{prefix}_LIBRARY"] = python_library + if python_sabi_library and sysconfig.get_platform().startswith("win"): + cache_config[f"{prefix}_SABI_LIBRARY"] = python_sabi_library + if numpy_include_dir: + cache_config[f"{prefix}_NumPy_INCLUDE_DIR"] = numpy_include_dir cache_config["SKBUILD_SOABI"] = get_soabi(self.config.env, abi3=limited_api) diff --git a/src/scikit_build_core/resources/scikit-build.schema.json b/src/scikit_build_core/resources/scikit-build.schema.json index e95d65c7..0f1fcf9d 100644 --- a/src/scikit_build_core/resources/scikit-build.schema.json +++ b/src/scikit_build_core/resources/scikit-build.schema.json @@ -102,6 +102,15 @@ }, "description": "DEPRECATED in 0.10; use build.targets instead.", "deprecated": true + }, + "toolchain-file": { + "type": "string", + "description": "The CMAKE_TOOLCHAIN_FILE used for cross-compilation." + }, + "no-python-hints": { + "type": "boolean", + "default": false, + "description": "Do not pass the current environment's python hints such as ``Python_EXECUTABLE``. Primarily used for cross-compilation where the CMAKE_TOOLCHAIN_FILE should handle it instead." } } }, @@ -239,6 +248,13 @@ "type": "string", "default": "", "description": "The build tag to use for the wheel. If empty, no build tag is used." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Manually specify the wheel tags to use, ignoring other inputs such as ``wheel.py-api``. Each tag must be of the format {interpreter}-{abi}-{platform}. If not specified, these tags are automatically calculated." } } }, @@ -566,6 +582,9 @@ }, "exclude": { "$ref": "#/$defs/inherit" + }, + "tags": { + "$ref": "#/$defs/inherit" } } }, diff --git a/src/scikit_build_core/settings/skbuild_model.py b/src/scikit_build_core/settings/skbuild_model.py index d25a4a84..8c75ad00 100644 --- a/src/scikit_build_core/settings/skbuild_model.py +++ b/src/scikit_build_core/settings/skbuild_model.py @@ -111,6 +111,18 @@ class CMakeSettings: DEPRECATED in 0.10; use build.targets instead. """ + toolchain_file: Optional[Path] = None + """ + The CMAKE_TOOLCHAIN_FILE used for cross-compilation. + """ + + no_python_hints: bool = False + """ + Do not pass the current environment's python hints such as ``Python_EXECUTABLE``. + Primarily used for cross-compilation where the CMAKE_TOOLCHAIN_FILE should handle it + instead. + """ + @dataclasses.dataclass class SearchSettings: @@ -254,6 +266,13 @@ class WheelSettings: The build tag to use for the wheel. If empty, no build tag is used. """ + tags: List[str] = dataclasses.field(default_factory=list) + """ + Manually specify the wheel tags to use, ignoring other inputs such as + ``wheel.py-api``. Each tag must be of the format {interpreter}-{abi}-{platform}. + If not specified, these tags are automatically calculated. + """ + @dataclasses.dataclass class BackportSettings: