Skip to content

Commit 36ab0fe

Browse files
committed
Update backend.py
update backend to bootstrap cmake
1 parent 236d5d6 commit 36ab0fe

File tree

1 file changed

+93
-4
lines changed

1 file changed

+93
-4
lines changed

_build_backend/backend.py

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import os
24

35
from scikit_build_core import build as _orig
@@ -7,7 +9,6 @@
79
if hasattr(_orig, "prepare_metadata_for_build_wheel"):
810
prepare_metadata_for_build_wheel = _orig.prepare_metadata_for_build_wheel
911
build_editable = _orig.build_editable
10-
build_wheel = _orig.build_wheel
1112
build_sdist = _orig.build_sdist
1213
get_requires_for_build_editable = _orig.get_requires_for_build_editable
1314
get_requires_for_build_sdist = _orig.get_requires_for_build_sdist
@@ -25,7 +26,9 @@ def strtobool(value: str) -> bool:
2526
return value not in {"n", "no", "off", "false", "f"}
2627

2728

28-
def get_requires_for_build_wheel(config_settings=None):
29+
def get_requires_for_build_wheel(
30+
config_settings: dict[str, str | list[str]] | None = None,
31+
) -> list[str]:
2932
packages_orig = _orig.get_requires_for_build_wheel(config_settings)
3033
allow_ninja = any(
3134
strtobool(os.environ.get(var, ""))
@@ -35,9 +38,95 @@ def get_requires_for_build_wheel(config_settings=None):
3538
for package in packages_orig:
3639
package_name = package.lower().split(">")[0].strip()
3740
if package_name == "cmake":
38-
msg = f"CMake PyPI distibution requires {package} to be available on the build system"
39-
raise ValueError(msg)
41+
continue
4042
if package_name == "ninja" and not allow_ninja:
4143
continue
4244
packages.append(package)
4345
return packages
46+
47+
48+
def bootstrap_build(temp_path: str, config_settings: dict[str, list[str] | str] | None = None) -> str:
49+
import hashlib
50+
import re
51+
import shutil
52+
import subprocess
53+
import tarfile
54+
import urllib.request
55+
from pathlib import Path
56+
57+
env = os.environ.copy()
58+
temp_path_ = Path(temp_path)
59+
60+
if "MAKE" not in env:
61+
make_path = None
62+
make_candidates = ("gmake", "make", "smake")
63+
for candidate in make_candidates:
64+
make_path = shutil.which(candidate)
65+
if make_path is not None:
66+
break
67+
if make_path is None:
68+
msg = f"Could not find a make program. Tried {make_candidates!r}"
69+
raise ValueError(msg)
70+
env["MAKE"] = make_path
71+
make_path = env["MAKE"]
72+
73+
archive_path = temp_path_
74+
if config_settings:
75+
archive_path = Path(config_settings.get("cmake.define.CMakePythonDistributions_ARCHIVE_DOWNLOAD_DIR", archive_path))
76+
archive_path.mkdir(parents=True, exist_ok=True)
77+
78+
cmake_urls = Path("CMakeUrls.cmake").read_text()
79+
source_url = re.findall(r'set\(unix_source_url\s+"(?P<data>.*)"\)$', cmake_urls, flags=re.MULTILINE)[0]
80+
source_sha256 = re.findall(r'set\(unix_source_sha256\s+"(?P<data>.*)"\)$', cmake_urls, flags=re.MULTILINE)[0]
81+
82+
tarball_name = source_url.rsplit("/", maxsplit=1)[1]
83+
assert tarball_name.endswith(".tar.gz")
84+
source_tarball = archive_path / tarball_name
85+
if not source_tarball.exists():
86+
with urllib.request.urlopen(source_url) as response:
87+
source_tarball.write_bytes(response.read())
88+
89+
sha256 = hashlib.sha256(source_tarball.read_bytes()).hexdigest()
90+
if source_sha256.lower() != sha256.lower():
91+
msg = f"Invalid sha256 for {source_url!r}. Expected {source_sha256!r}, got {sha256!r}"
92+
raise ValueError(msg)
93+
94+
tar_filter_kwargs = {"filter": "tar"} if hasattr(tarfile, "tar_filter") else {}
95+
assert "filter" in tar_filter_kwargs
96+
with tarfile.open(source_tarball) as tar:
97+
tar.extractall(path=temp_path_, **tar_filter_kwargs)
98+
99+
bootstrap_path = next(temp_path_.glob("cmake-*/bootstrap"))
100+
prefix_path = temp_path_ / "cmake-install"
101+
bootstrap_args = [f"--prefix={prefix_path}", "--no-qt-gui", "--no-debugger"]
102+
previous_cwd = Path().absolute()
103+
os.chdir(bootstrap_path.parent)
104+
try:
105+
subprocess.run([bootstrap_path, *bootstrap_args], env=env, check=True)
106+
subprocess.run([make_path], env=env, check=True)
107+
subprocess.run([make_path, "install"], env=env, check=True)
108+
finally:
109+
os.chdir(previous_cwd)
110+
111+
return str(prefix_path / "bin" / "cmake")
112+
113+
114+
def build_wheel(
115+
wheel_directory: str,
116+
config_settings: dict[str, list[str] | str] | None = None,
117+
metadata_directory: str | None = None,
118+
) -> str:
119+
from scikit_build_core.errors import CMakeNotFoundError
120+
121+
try:
122+
return _orig.build_wheel(wheel_directory, config_settings, metadata_directory)
123+
except CMakeNotFoundError:
124+
if os.name != "posix":
125+
raise
126+
# Let's try bootstrapping CMake
127+
import tempfile
128+
with tempfile.TemporaryDirectory() as temp_path:
129+
cmake_path = bootstrap_build(temp_path, config_settings)
130+
assert cmake_path
131+
os.environ["CMAKE_EXECUTABLE"] = cmake_path
132+
return _orig.build_wheel(wheel_directory, config_settings, metadata_directory)

0 commit comments

Comments
 (0)