Skip to content
8 changes: 8 additions & 0 deletions docs/api/scikit_build_core.builder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ scikit\_build\_core.builder.builder module
:undoc-members:
:show-inheritance:

scikit\_build\_core.builder.cross\_compile module
-------------------------------------------------

.. automodule:: scikit_build_core.builder.cross_compile
:members:
:undoc-members:
:show-inheritance:

scikit\_build\_core.builder.generator module
--------------------------------------------

Expand Down
10 changes: 6 additions & 4 deletions src/scikit_build_core/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ..cmake import CMaker
from ..resources import find_python
from ..settings.skbuild_model import ScikitBuildSettings
from .cross_compile import auto_cross_compile_env
from .generator import set_environment_for_gen
from .sysconfig import (
get_platform,
Expand Down Expand Up @@ -183,10 +184,11 @@ def configure(
# Add the pre-defined or passed CMake defines
cmake_defines.update(self.settings.cmake.define)

self.config.configure(
defines=cmake_defines,
cmake_args=[*self.get_cmake_args(), *configure_args],
)
with auto_cross_compile_env(self.config.env):
self.config.configure(
defines=cmake_defines,
cmake_args=[*self.get_cmake_args(), *configure_args],
)

def build(self, build_args: list[str]) -> None:
self.config.build(build_args=build_args, verbose=self.settings.cmake.verbose)
Expand Down
79 changes: 79 additions & 0 deletions src/scikit_build_core/builder/cross_compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from __future__ import annotations

import contextlib
import os
import sysconfig
import tempfile
from collections.abc import Generator, MutableMapping
from pathlib import Path

from .._logging import logger

__all__ = ["set_cross_compile_env", "auto_cross_compile_env"]


def __dir__() -> list[str]:
return __all__


@contextlib.contextmanager
def auto_cross_compile_env(
env: MutableMapping[str, str]
) -> Generator[None, None, None]:
if "SETUPTOOLS_EXT_SUFFIX" not in env:
yield
return

with set_cross_compile_env(env["SETUPTOOLS_EXT_SUFFIX"], env):
yield


@contextlib.contextmanager
def set_cross_compile_env(
ext_suffix: str,
env: MutableMapping[str, str],
) -> Generator[None, None, None]:
"""
Generate python file and set environment variables to cross-compile Python
extensions. Do not call if _PYTHON_SYSCONFIGDATA_NAME is already set.
"""

if "_PYTHON_SYSCONFIGDATA_NAME" in env:
logger.debug(
"Not setting up cross compiling explicitly due to _PYTHON_SYSCONFIGDATA_NAME already set."
)
yield
return

with tempfile.TemporaryDirectory() as tmpdir:
tmp_dir = Path(tmpdir).resolve()
cross_compile_file = (
tmp_dir / f"_cross_compile_{ext_suffix.replace('.', '_')}.py"
)
build_time_vars = sysconfig.get_config_vars()
build_time_vars["EXT_SUFFIX"] = ext_suffix
build_time_vars["SOABI"] = ext_suffix.rsplit(maxsplit=1)[0]
output_text = f"build_time_vars = {build_time_vars!r}\n"
cross_compile_file.write_text(output_text)
current_path = env.get("PYTHONPATH", "")
env["PYTHONPATH"] = (
os.pathsep.join([current_path, str(tmp_dir)])
if current_path
else str(tmp_dir)
)
env["_PYTHON_SYSCONFIGDATA_NAME"] = cross_compile_file.stem
logger.info("Cross-compiling is enabled to {!r}.", ext_suffix)
logger.debug(
"Setting _PYTHON_SYSCONFIGDATA_NAME to {!r}.",
env["_PYTHON_SYSCONFIGDATA_NAME"],
)
logger.debug("Setting PYTHONPATH to {!r}.", env["PYTHONPATH"])
logger.debug("Cross compile output file contents: {}", output_text)
try:
yield
finally:
del env["_PYTHON_SYSCONFIGDATA_NAME"]
if current_path:
env["PYTHONPATH"] = current_path
else:
del env["PYTHONPATH"]
39 changes: 39 additions & 0 deletions tests/test_cross_compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import annotations

import os
import subprocess
import sys
import sysconfig

import pytest

from scikit_build_core.builder.cross_compile import set_cross_compile_env

ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")


@pytest.mark.skipif(
ext_suffix != ".cp311-win_amd64.pyd",
reason=f"Only tests '.cp311-win_amd64.pyd', got {ext_suffix!r}",
)
def test_environment():
env = os.environ.copy()
cmd = [
sys.executable,
"-c",
"import sysconfig; print(sysconfig.get_config_var('SOABI'), sysconfig.get_config_var('EXT_SUFFIX'))",
]

with set_cross_compile_env(".cp311-win_arm64.pyd", env):
result = subprocess.run(
cmd, check=True, capture_output=True, text=True, env=env
)
soabi, ext_suffix = result.stdout.strip().split()
print(soabi, ext_suffix)
assert soabi == "cp311-win_arm64"
assert ext_suffix == ".cp311-win_arm64.pyd"

result = subprocess.run(cmd, check=True, capture_output=True, text=True, env=env)
soabi, ext_suffix = result.stdout.strip().split()
assert soabi == "cp311-win_amd64"
assert ext_suffix == ".cp311-win_amd64.pyd"
12 changes: 10 additions & 2 deletions tests/test_module_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ def on_all_modules(

def test_all_modules_filter_all():
all_modules = on_all_modules("scikit_build_core", pkg=False)
all_modules = (n for n in all_modules if not n.split(".")[-1].startswith("__"))
all_modules = (
n
for n in all_modules
if not n.split(".")[-1].startswith("__") and "resources" not in n
)
for name in all_modules:
module = importlib.import_module(name)

Expand All @@ -45,7 +49,11 @@ def test_all_modules_filter_all():

def test_all_modules_has_all():
all_modules = on_all_modules("scikit_build_core", pkg=True)
all_modules = (n for n in all_modules if not n.split(".")[-1].startswith("_"))
all_modules = (
n
for n in all_modules
if not n.split(".")[-1].startswith("_") and "resources" not in n
)
for name in all_modules:
module = importlib.import_module(name)

Expand Down