Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ cmake.build-type = "Release"
# The source directory to use when building the project.
cmake.source-dir = "."

# Do not pass the current environment's python hints such as ``Python_EXECUTABLE``.
cmake.python-hints = true

# The versions of Ninja to allow.
ninja.version = ">=1.5"

Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,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)
Expand Down
77 changes: 73 additions & 4 deletions docs/guide/crosscompile.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 <https://tttapa.github.io/py-build-cmake/Cross-compilation.html>`\_
for an alternative package's usage of toolchain files.
See [manual cross compilation] section for the general approach.

### Intel to Emscripten (Pyodide)

Expand All @@ -67,3 +68,71 @@ so FindPython will report the wrong values, but pyodide-build will rename the

pyodide-build will also set `_PYTHON_HOST_PLATFORM` to the target Pyodide
platform, so scikit-build-core can use that to compute the correct wheel name.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`CMAKE_TOOLCHAIN_FILE`, or the `cmake.toolchain-file` pyproject option. You may
`CMAKE_TOOLCHAIN_FILE`, or the `cmake.toolchain-file` config-settings option. You may

I'm not sure this is good to hard code? Though for this one (and the other), we could possibly allow it, but we probably shouldn't suggest it. It might be useful in overrides, though. Actually, maybe tags would be too.

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
34 changes: 33 additions & 1 deletion docs/reference/configs.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ print(mk_skbuild_docs())
.. confval:: fail
:type: ``bool``

Immediately fail the build. This is only useful in overrides.
Immediately fail the build. This is only allowed in overrides or config-settings.
```

```{eval-rst}
Expand Down Expand Up @@ -198,6 +198,16 @@ print(mk_skbuild_docs())
DEPRECATED in 0.8; use version instead.
```

```{eval-rst}
.. confval:: cmake.python-hints
:type: ``bool``
:default: true

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.
```

```{eval-rst}
.. confval:: cmake.source-dir
:type: ``Path``
Expand All @@ -215,6 +225,15 @@ print(mk_skbuild_docs())
DEPRECATED in 0.10; use build.targets instead.
```

```{eval-rst}
.. confval:: cmake.toolchain-file
:type: ``Path``

The CMAKE_TOOLCHAIN_FILE / --toolchain used for cross-compilation.

This is only allowed in overrides or config-settings.
```

```{eval-rst}
.. confval:: cmake.verbose
:type: ``bool``
Expand Down Expand Up @@ -573,4 +592,17 @@ print(mk_skbuild_docs())
This value is used to construct ``SKBUILD_SABI_COMPONENT`` CMake variable.
```

```{eval-rst}
.. confval:: wheel.tags
:type: ``list[str]``

Wheel tags to manually force, {interpreter}-{abi}-{platform} format.

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. This is only allowed in overrides or
config-settings.
```

<!-- [[[end]]] -->
9 changes: 7 additions & 2 deletions src/scikit_build_core/build/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,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
Expand Down Expand Up @@ -268,6 +269,10 @@ def _build_wheel_impl_impl(
platform.machine(),
)

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"
Expand Down Expand Up @@ -372,7 +377,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,
Expand Down Expand Up @@ -488,7 +493,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,
Expand Down
40 changes: 21 additions & 19 deletions src/scikit_build_core/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,25 +242,26 @@ def configure(
"Python 3.13.4 on Windows is broken for building, 3.13.5 was rushed out to fix it. Use an older, newer, or free-threaded version instead."
)

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

Expand Down Expand Up @@ -294,6 +295,7 @@ def configure(
self.config.configure(
defines=cmake_defines,
cmake_args=[*self.get_cmake_args(), *configure_args],
toolchain=self.settings.cmake.toolchain_file,
)

def build(self, build_args: Sequence[str]) -> None:
Expand Down
18 changes: 15 additions & 3 deletions src/scikit_build_core/cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from pathlib import Path
from typing import TYPE_CHECKING, Any

from packaging.version import Version

from . import __version__
from ._compat.builtins import ExceptionGroup
from ._logging import logger
Expand All @@ -25,7 +27,6 @@
from collections.abc import Generator, Iterable, Mapping, Sequence

from packaging.specifiers import SpecifierSet
from packaging.version import Version

from ._compat.typing import Self
from .file_api.model.index import Index
Expand Down Expand Up @@ -211,11 +212,21 @@ def init_cache(
)

def _compute_cmake_args(
self, defines: Mapping[str, str | os.PathLike[str] | bool]
self,
defines: Mapping[str, str | os.PathLike[str] | bool],
toolchain: os.PathLike[str] | None,
) -> Generator[str, None, None]:
yield f"-S{self.source_dir}"
yield f"-B{self.build_dir}"

if toolchain is not None:
toolchain_str = str(toolchain).replace("\\", "/")
yield (
f"--toolchain={toolchain_str}"
if self.cmake.version >= Version("3.21")
else f"-DCMAKE_TOOLCHAIN_FILE:PATH={toolchain_str}"
)

if self.init_cache_file.is_file():
yield f"-C{self.init_cache_file}"

Expand Down Expand Up @@ -244,8 +255,9 @@ def configure(
*,
defines: Mapping[str, str | os.PathLike[str] | bool] | None = None,
cmake_args: Sequence[str] = (),
toolchain: os.PathLike[str] | None = None,
) -> None:
_cmake_args = self._compute_cmake_args(defines or {})
_cmake_args = self._compute_cmake_args(defines or {}, toolchain)
all_args = [*_cmake_args, *cmake_args]

gen = self.get_generator(*all_args)
Expand Down
23 changes: 22 additions & 1 deletion src/scikit_build_core/resources/scikit-build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@
},
"description": "DEPRECATED in 0.10; use build.targets instead.",
"deprecated": true
},
"toolchain-file": {
"type": "string",
"description": "The CMAKE_TOOLCHAIN_FILE / --toolchain used for cross-compilation.",
"scikit-build:override-only": true
},
"python-hints": {
"type": "boolean",
"default": true,
"description": "Do not pass the current environment's python hints such as ``Python_EXECUTABLE``."
}
}
},
Expand Down Expand Up @@ -239,6 +249,14 @@
"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": "Wheel tags to manually force, {interpreter}-{abi}-{platform} format.",
"scikit-build:override-only": true
}
}
},
Expand Down Expand Up @@ -561,6 +579,9 @@
},
"exclude": {
"$ref": "#/$defs/inherit"
},
"tags": {
"$ref": "#/$defs/inherit"
}
}
},
Expand Down Expand Up @@ -644,7 +665,7 @@
},
"fail": {
"type": "boolean",
"description": "Immediately fail the build. This is only useful in overrides."
"description": "Immediately fail the build. This is only allowed in overrides or config-settings."
}
}
}
Expand Down
Loading