Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: custom cross compiling environment #355

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
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