feat: Android improvements needed for building NumPy and related packages (#2695)
* Use auditwheel on Android * Add auditwheel command to defaults * pkgconfig fixes * Pre-import ctypes before monkey-patching in _cross_venv * Set PKG_CONFIG and PKG_CONFIG_RELOCATE_PATHS variables * Initial attempt at using mzakharo/android-gfortran * Switch to using termux/ndk-toolchain-clang-with-flang * Move PKG_CONFIG variables from build_env to android_env * Simplify flang installation * Add cross build files for NumPy * Add ldpaths entry for libomp * Add `--rm` to docker command line * Make Rust and Fortran shims consistent * Update documentation * Default to API level 24 on all Python versions * Clarify comments * Cleanups * Fix tests: * Set up Android env after installing pkgconf * Add tests for successfully using an older API level * Previous commit's auditwheel failure is fixed in the auditwheel PR * Update how-it-works diagram * Add tests for repair errors * Add more repair tests * Add test for Meson and Fortran * Add test for cross build files * Improve test_api_level error message * Add xbuild-files option * use pypa/auditwheel@main * Remove dependencies which are no longer needed * Fix README * Update to auditwheel 6.7.0 * Fix compatibility with pkgconf 2.5.1.post2 * Documentation clarifications Co-authored-by: Joe Rickerby <joerick@mac.com> * Fortran shim improvements * Add Jinja variables to new_meson_project * Update run_example_ci_configs for changed new_meson_project signature * Add missing dependency to run_example_ci_configs --------- Co-authored-by: mayeut <mayeut@users.noreply.github.com> Co-authored-by: Joe Rickerby <joerick@mac.com>
This commit is contained in:
co-authored by
Joe Rickerby
mayeut
parent
e3e7cc9e07
commit
1cf64d0d3c
@@ -39,7 +39,6 @@ repos:
|
|||||||
- nox>=2025.2.9
|
- nox>=2025.2.9
|
||||||
- orjson
|
- orjson
|
||||||
- packaging
|
- packaging
|
||||||
- pyelftools
|
|
||||||
- pygithub
|
- pygithub
|
||||||
- pytest
|
- pytest
|
||||||
- rich
|
- rich
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ The following diagram summarises the steps that cibuildwheel takes on each platf
|
|||||||
| | [`before-all`](https://cibuildwheel.pypa.io/en/stable/options/#before-all) | Execute a shell command on the build system before any wheels are built. |
|
| | [`before-all`](https://cibuildwheel.pypa.io/en/stable/options/#before-all) | Execute a shell command on the build system before any wheels are built. |
|
||||||
| | [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) | Execute a shell command preparing each wheel's build |
|
| | [`before-build`](https://cibuildwheel.pypa.io/en/stable/options/#before-build) | Execute a shell command preparing each wheel's build |
|
||||||
| | [`xbuild-tools`](https://cibuildwheel.pypa.io/en/stable/options/#xbuild-tools) | Binaries on the path that should be included in an isolated cross-build environment. |
|
| | [`xbuild-tools`](https://cibuildwheel.pypa.io/en/stable/options/#xbuild-tools) | Binaries on the path that should be included in an isolated cross-build environment. |
|
||||||
|
| | [`xbuild-files`](https://cibuildwheel.pypa.io/en/stable/options/#xbuild-files) | Platform-specific files in the build environment |
|
||||||
| | [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) | Execute a shell command to repair each built wheel |
|
| | [`repair-wheel-command`](https://cibuildwheel.pypa.io/en/stable/options/#repair-wheel-command) | Execute a shell command to repair each built wheel |
|
||||||
| | [`manylinux-*-image`<br>`musllinux-*-image`](https://cibuildwheel.pypa.io/en/stable/options/#linux-image) | Specify manylinux / musllinux container images |
|
| | [`manylinux-*-image`<br>`musllinux-*-image`](https://cibuildwheel.pypa.io/en/stable/options/#linux-image) | Specify manylinux / musllinux container images |
|
||||||
| | [`container-engine`](https://cibuildwheel.pypa.io/en/stable/options/#container-engine) | Specify the container engine to use when building Linux wheels |
|
| | [`container-engine`](https://cibuildwheel.pypa.io/en/stable/options/#container-engine) | Specify the container engine to use when building Linux wheels |
|
||||||
@@ -173,7 +174,7 @@ The following diagram summarises the steps that cibuildwheel takes on each platf
|
|||||||
| | [`build-verbosity`](https://cibuildwheel.pypa.io/en/stable/options/#build-verbosity) | Increase/decrease the output of the build |
|
| | [`build-verbosity`](https://cibuildwheel.pypa.io/en/stable/options/#build-verbosity) | Increase/decrease the output of the build |
|
||||||
|
|
||||||
|
|
||||||
<!--[[[end]]] (sum: b7YIjCyCkf) -->
|
<!--[[[end]]] (sum: Of/28Z7Nut) -->
|
||||||
|
|
||||||
These options can be specified in a pyproject.toml file, or as environment variables, see [configuration docs](https://cibuildwheel.pypa.io/en/latest/configuration/).
|
These options can be specified in a pyproject.toml file, or as environment variables, see [configuration docs](https://cibuildwheel.pypa.io/en/latest/configuration/).
|
||||||
|
|
||||||
|
|||||||
+14
-7
@@ -15,6 +15,8 @@ parser = argparse.ArgumentParser(allow_abbrev=False)
|
|||||||
parser.add_argument("--schemastore", action="store_true", help="Generate schema_store version")
|
parser.add_argument("--schemastore", action="store_true", help="Generate schema_store version")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# The defaults in the schema are used by external tools for validation and IDE support. They
|
||||||
|
# should match the values in defaults.toml, which are used by cibuildwheel itself.
|
||||||
starter = """
|
starter = """
|
||||||
$schema: http://json-schema.org/draft-07/schema#
|
$schema: http://json-schema.org/draft-07/schema#
|
||||||
$id: https://github.com/pypa/cibuildwheel/blob/main/cibuildwheel/resources/cibuildwheel.schema.json
|
$id: https://github.com/pypa/cibuildwheel/blob/main/cibuildwheel/resources/cibuildwheel.schema.json
|
||||||
@@ -200,6 +202,9 @@ properties:
|
|||||||
xbuild-tools:
|
xbuild-tools:
|
||||||
description: Binaries on the path that should be included in an isolated cross-build environment
|
description: Binaries on the path that should be included in an isolated cross-build environment
|
||||||
type: string_array
|
type: string_array
|
||||||
|
xbuild-files:
|
||||||
|
description: Platform-specific files in the build environment
|
||||||
|
type: string_table_array
|
||||||
pyodide-version:
|
pyodide-version:
|
||||||
type: string
|
type: string
|
||||||
description: Specify the version of Pyodide to use
|
description: Specify the version of Pyodide to use
|
||||||
@@ -319,6 +324,7 @@ items:
|
|||||||
before-all: {"$ref": "#/$defs/inherit"}
|
before-all: {"$ref": "#/$defs/inherit"}
|
||||||
before-build: {"$ref": "#/$defs/inherit"}
|
before-build: {"$ref": "#/$defs/inherit"}
|
||||||
xbuild-tools: {"$ref": "#/$defs/inherit"}
|
xbuild-tools: {"$ref": "#/$defs/inherit"}
|
||||||
|
xbuild-files: {"$ref": "#/$defs/inherit"}
|
||||||
before-test: {"$ref": "#/$defs/inherit"}
|
before-test: {"$ref": "#/$defs/inherit"}
|
||||||
config-settings: {"$ref": "#/$defs/inherit"}
|
config-settings: {"$ref": "#/$defs/inherit"}
|
||||||
container-engine: {"$ref": "#/$defs/inherit"}
|
container-engine: {"$ref": "#/$defs/inherit"}
|
||||||
@@ -374,14 +380,15 @@ oses = {
|
|||||||
"ios": as_object(not_linux),
|
"ios": as_object(not_linux),
|
||||||
}
|
}
|
||||||
|
|
||||||
oses["linux"]["properties"]["repair-wheel-command"] = {
|
for os_name, command in [
|
||||||
|
("linux", "auditwheel repair -w {dest_dir} {wheel}"),
|
||||||
|
("macos", "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"),
|
||||||
|
("android", "auditwheel repair --ldpaths {ldpaths} -w {dest_dir} {wheel}"),
|
||||||
|
]:
|
||||||
|
oses[os_name]["properties"]["repair-wheel-command"] = {
|
||||||
**schema["properties"]["repair-wheel-command"],
|
**schema["properties"]["repair-wheel-command"],
|
||||||
"default": "auditwheel repair -w {dest_dir} {wheel}",
|
"default": command,
|
||||||
}
|
}
|
||||||
oses["macos"]["properties"]["repair-wheel-command"] = {
|
|
||||||
**schema["properties"]["repair-wheel-command"],
|
|
||||||
"default": "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}",
|
|
||||||
}
|
|
||||||
|
|
||||||
del oses["linux"]["properties"]["dependency-versions"]
|
del oses["linux"]["properties"]["dependency-versions"]
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# /// script
|
# /// script
|
||||||
# dependencies = [
|
# dependencies = [
|
||||||
# "click",
|
# "click",
|
||||||
|
# "jinja2",
|
||||||
# ]
|
# ]
|
||||||
# ///
|
# ///
|
||||||
|
|
||||||
@@ -39,13 +40,16 @@ def generate_project(path: Path, build_backend: BuildBackend) -> None:
|
|||||||
sys.path.insert(0, "")
|
sys.path.insert(0, "")
|
||||||
match build_backend:
|
match build_backend:
|
||||||
case "meson":
|
case "meson":
|
||||||
from test.test_projects.meson import new_meson_project as new_project # noqa: PLC0415
|
from test.test_projects.meson import new_meson_project # noqa: PLC0415
|
||||||
|
|
||||||
|
project = new_meson_project()
|
||||||
case "setuptools":
|
case "setuptools":
|
||||||
from test.test_projects.setuptools import new_c_project as new_project # noqa: PLC0415
|
from test.test_projects.setuptools import new_c_project # noqa: PLC0415
|
||||||
|
|
||||||
|
project = new_c_project()
|
||||||
case _:
|
case _:
|
||||||
typing.assert_never(build_backend)
|
typing.assert_never(build_backend)
|
||||||
|
|
||||||
project = new_project()
|
|
||||||
project.generate(path)
|
project.generate(path)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ class BuildOptions:
|
|||||||
before_all: str
|
before_all: str
|
||||||
before_build: str | None
|
before_build: str | None
|
||||||
xbuild_tools: list[str] | None
|
xbuild_tools: list[str] | None
|
||||||
|
xbuild_files: dict[str, list[str]]
|
||||||
repair_command: str
|
repair_command: str
|
||||||
manylinux_images: dict[str, str] | None
|
manylinux_images: dict[str, str] | None
|
||||||
musllinux_images: dict[str, str] | None
|
musllinux_images: dict[str, str] | None
|
||||||
@@ -772,6 +773,14 @@ class Options:
|
|||||||
if xbuild_tools == ["\u0000"]:
|
if xbuild_tools == ["\u0000"]:
|
||||||
xbuild_tools = None
|
xbuild_tools = None
|
||||||
|
|
||||||
|
xbuild_files = parse_key_value_string(
|
||||||
|
self.reader.get(
|
||||||
|
"xbuild-files",
|
||||||
|
option_format=ShlexTableFormat(sep="; ", pair_sep=":", allow_merge=False),
|
||||||
|
),
|
||||||
|
kw_arg_names=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
test_sources = shlex.split(
|
test_sources = shlex.split(
|
||||||
self.reader.get(
|
self.reader.get(
|
||||||
"test-sources", option_format=ListFormat(sep=" ", quote=shlex.quote)
|
"test-sources", option_format=ListFormat(sep=" ", quote=shlex.quote)
|
||||||
@@ -925,6 +934,7 @@ class Options:
|
|||||||
before_all=before_all,
|
before_all=before_all,
|
||||||
build_verbosity=build_verbosity,
|
build_verbosity=build_verbosity,
|
||||||
xbuild_tools=xbuild_tools,
|
xbuild_tools=xbuild_tools,
|
||||||
|
xbuild_files=xbuild_files,
|
||||||
repair_command=repair_command,
|
repair_command=repair_command,
|
||||||
environment=environment,
|
environment=environment,
|
||||||
dependency_constraints=dependency_constraints,
|
dependency_constraints=dependency_constraints,
|
||||||
|
|||||||
+129
-179
@@ -1,16 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import csv
|
|
||||||
import hashlib
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sysconfig
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from os.path import relpath
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from runpy import run_path
|
from runpy import run_path
|
||||||
@@ -19,9 +16,8 @@ from typing import Any
|
|||||||
|
|
||||||
from build import ProjectBuilder
|
from build import ProjectBuilder
|
||||||
from build.env import IsolatedEnv
|
from build.env import IsolatedEnv
|
||||||
from elftools.common.exceptions import ELFError
|
|
||||||
from elftools.elf.elffile import ELFFile
|
|
||||||
from filelock import FileLock
|
from filelock import FileLock
|
||||||
|
from packaging.utils import canonicalize_name
|
||||||
|
|
||||||
from cibuildwheel import errors, platforms # pylint: disable=cyclic-import
|
from cibuildwheel import errors, platforms # pylint: disable=cyclic-import
|
||||||
from cibuildwheel.architecture import Architecture, arch_synonym
|
from cibuildwheel.architecture import Architecture, arch_synonym
|
||||||
@@ -42,10 +38,9 @@ from cibuildwheel.venv import constraint_flags, find_uv, virtualenv
|
|||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Iterable, Iterator, MutableMapping
|
|
||||||
|
|
||||||
from cibuildwheel.options import BuildOptions, Options
|
from cibuildwheel.options import BuildOptions, Options
|
||||||
from cibuildwheel.selector import BuildSelector
|
from cibuildwheel.selector import BuildSelector
|
||||||
|
from cibuildwheel.typing import PathOrStr
|
||||||
|
|
||||||
RESOURCES_ANDROID = resources.PATH / "android"
|
RESOURCES_ANDROID = resources.PATH / "android"
|
||||||
ANDROID_TRIPLET = {
|
ANDROID_TRIPLET = {
|
||||||
@@ -148,6 +143,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
|||||||
state = BuildState(
|
state = BuildState(
|
||||||
config, build_options, build_path, python_dir, build_env, android_env
|
config, build_options, build_path, python_dir, build_env, android_env
|
||||||
)
|
)
|
||||||
|
setup_xbuild_files(state)
|
||||||
|
|
||||||
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
|
compatible_wheel = find_compatible_wheel(built_wheels, config.identifier)
|
||||||
if compatible_wheel:
|
if compatible_wheel:
|
||||||
@@ -162,7 +158,7 @@ def build(options: Options, tmp_path: Path) -> None:
|
|||||||
repaired_wheel = repair_wheel(state, built_wheel)
|
repaired_wheel = repair_wheel(state, built_wheel)
|
||||||
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
|
run_audit(tmp_dir=tmp_path, build_options=build_options, wheel=repaired_wheel)
|
||||||
|
|
||||||
test_wheel(state, repaired_wheel, build_frontend=build_options.build_frontend.name)
|
test_wheel(state, repaired_wheel)
|
||||||
|
|
||||||
output_wheel: Path | None = None
|
output_wheel: Path | None = None
|
||||||
if compatible_wheel is None:
|
if compatible_wheel is None:
|
||||||
@@ -194,6 +190,11 @@ def setup_target_python(config: PythonConfiguration, build_path: Path) -> Path:
|
|||||||
# updated to Python versions that include the fix.
|
# updated to Python versions that include the fix.
|
||||||
call("patch", "-p1", "-i", RESOURCES_ANDROID / "android.patch", cwd=python_dir)
|
call("patch", "-p1", "-i", RESOURCES_ANDROID / "android.patch", cwd=python_dir)
|
||||||
|
|
||||||
|
# Work around https://github.com/python/cpython/issues/138800. This can be removed
|
||||||
|
# once we've updated to Python versions that include the fix.
|
||||||
|
pc_path = python_dir / f"prefix/lib/pkgconfig/python-{config.version}.pc"
|
||||||
|
pc_path.write_text(pc_path.read_text().replace("$(BLDLIBRARY)", f"-lpython{config.version}"))
|
||||||
|
|
||||||
return python_dir
|
return python_dir
|
||||||
|
|
||||||
|
|
||||||
@@ -207,13 +208,7 @@ def setup_env(
|
|||||||
* android_env, which uses the environment while simulating running on Android.
|
* android_env, which uses the environment while simulating running on Android.
|
||||||
"""
|
"""
|
||||||
log.step("Setting up build environment...")
|
log.step("Setting up build environment...")
|
||||||
build_frontend = build_options.build_frontend.name
|
use_uv, pip = find_pip(build_options)
|
||||||
use_uv = build_frontend in {"build[uv]", "uv"}
|
|
||||||
uv_path = find_uv()
|
|
||||||
if use_uv and uv_path is None:
|
|
||||||
msg = "uv not found"
|
|
||||||
raise AssertionError(msg)
|
|
||||||
pip = ["pip"] if not use_uv else [str(uv_path), "pip"]
|
|
||||||
|
|
||||||
# Create virtual environment
|
# Create virtual environment
|
||||||
python_exe = create_python_build_standalone_environment(
|
python_exe = create_python_build_standalone_environment(
|
||||||
@@ -228,6 +223,9 @@ def setup_env(
|
|||||||
)
|
)
|
||||||
create_cmake_toolchain(config, build_path, python_dir, build_env)
|
create_cmake_toolchain(config, build_path, python_dir, build_env)
|
||||||
|
|
||||||
|
# See platforms.md for the reason why we use this default API level.
|
||||||
|
build_env.setdefault("ANDROID_API_LEVEL", "24")
|
||||||
|
|
||||||
# Apply custom environment variables, and check environment is still valid
|
# Apply custom environment variables, and check environment is still valid
|
||||||
build_env = build_options.environment.as_dictionary(build_env)
|
build_env = build_options.environment.as_dictionary(build_env)
|
||||||
build_env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
build_env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
||||||
@@ -245,14 +243,14 @@ def setup_env(
|
|||||||
else:
|
else:
|
||||||
call(command, "--version", env=build_env)
|
call(command, "--version", env=build_env)
|
||||||
|
|
||||||
# Construct an altered environment which simulates running on Android.
|
|
||||||
android_env = setup_android_env(config, python_dir, venv_dir, build_env)
|
|
||||||
|
|
||||||
# Install build tools
|
# Install build tools
|
||||||
if build_frontend not in {"build", "build[uv]", "uv"}:
|
tools = ["auditwheel", "patchelf", "pkgconf"]
|
||||||
msg = "Android requires the build frontend to be 'build' or 'uv'"
|
if build_options.build_frontend.name in {"build", "build[uv]"}:
|
||||||
raise errors.FatalError(msg)
|
tools.append("build")
|
||||||
call(*pip, "install", "build", *constraint_flags(dependency_constraint), env=build_env)
|
call(*pip, "install", *tools, *constraint_flags(dependency_constraint), env=build_env)
|
||||||
|
|
||||||
|
# Construct an altered environment which simulates running on Android.
|
||||||
|
android_env = setup_android_env(config, python_dir, build_env)
|
||||||
|
|
||||||
# Build-time requirements must be queried within android_env, because
|
# Build-time requirements must be queried within android_env, because
|
||||||
# `get_requires_for_build` can run arbitrary code in setup.py scripts, which may be
|
# `get_requires_for_build` can run arbitrary code in setup.py scripts, which may be
|
||||||
@@ -343,8 +341,7 @@ def localized_vars(
|
|||||||
final = final.replace(orig_prefix, str(prefix))
|
final = final.replace(orig_prefix, str(prefix))
|
||||||
|
|
||||||
if key == "ANDROID_API_LEVEL":
|
if key == "ANDROID_API_LEVEL":
|
||||||
if api_level := build_env.get(key):
|
final = int(build_env[key])
|
||||||
final = int(api_level)
|
|
||||||
|
|
||||||
# Build systems vary in whether FLAGS variables are read from sysconfig, and if so,
|
# Build systems vary in whether FLAGS variables are read from sysconfig, and if so,
|
||||||
# whether they're replaced by environment variables or combined with them. Even
|
# whether they're replaced by environment variables or combined with them. Even
|
||||||
@@ -367,15 +364,15 @@ def localized_vars(
|
|||||||
|
|
||||||
|
|
||||||
def setup_android_env(
|
def setup_android_env(
|
||||||
config: PythonConfiguration, python_dir: Path, venv_dir: Path, build_env: dict[str, str]
|
config: PythonConfiguration, python_dir: Path, build_env: dict[str, str]
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
site_packages = next(venv_dir.glob("lib/python*/site-packages"))
|
site_packages = find_site_packages(build_env)
|
||||||
for suffix in ["pth", "py"]:
|
for suffix in ["pth", "py"]:
|
||||||
shutil.copy(RESOURCES_ANDROID / f"_cross_venv.{suffix}", site_packages)
|
shutil.copy(RESOURCES_ANDROID / f"_cross_venv.{suffix}", site_packages)
|
||||||
|
|
||||||
sysconfigdata_path = Path(
|
sysconfigdata_path = Path(
|
||||||
shutil.copy(
|
shutil.copy(
|
||||||
next(python_dir.glob("prefix/lib/python*/_sysconfigdata_*.py")),
|
glob1(python_dir, "prefix/lib/python*/_sysconfigdata_*.py"),
|
||||||
site_packages,
|
site_packages,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -414,6 +411,17 @@ def setup_android_env(
|
|||||||
# Cargo target linker needs to be specified after CC is set
|
# Cargo target linker needs to be specified after CC is set
|
||||||
setup_rust(config, python_dir, android_env)
|
setup_rust(config, python_dir, android_env)
|
||||||
|
|
||||||
|
# Create shims which install additional build tools on first use.
|
||||||
|
setup_fortran(android_env)
|
||||||
|
|
||||||
|
# `android.py env` returns PKG_CONFIG="pkg-config --define-prefix", but some build
|
||||||
|
# systems can't handle arguments in that variable. Since we have a known version
|
||||||
|
# of pkgconf, it's safe to use PKG_CONFIG_RELOCATE_PATHS instead.
|
||||||
|
android_env["PKG_CONFIG"] = call(
|
||||||
|
"which", "pkgconf-pypi", env=build_env, capture_stdout=True
|
||||||
|
).strip()
|
||||||
|
android_env["PKG_CONFIG_RELOCATE_PATHS"] = "1"
|
||||||
|
|
||||||
# Format the environment so it can be pasted into a shell when debugging.
|
# Format the environment so it can be pasted into a shell when debugging.
|
||||||
for key, value in sorted(android_env.items()):
|
for key, value in sorted(android_env.items()):
|
||||||
if os.environ.get(key) != value:
|
if os.environ.get(key) != value:
|
||||||
@@ -422,11 +430,7 @@ def setup_android_env(
|
|||||||
return android_env
|
return android_env
|
||||||
|
|
||||||
|
|
||||||
def setup_rust(
|
def setup_rust(config: PythonConfiguration, python_dir: Path, env: dict[str, str]) -> None:
|
||||||
config: PythonConfiguration,
|
|
||||||
python_dir: Path,
|
|
||||||
env: MutableMapping[str, str],
|
|
||||||
) -> None:
|
|
||||||
cargo_target = android_triplet(config.identifier)
|
cargo_target = android_triplet(config.identifier)
|
||||||
|
|
||||||
# CARGO_BUILD_TARGET is the variable used by Cargo and setuptools_rust
|
# CARGO_BUILD_TARGET is the variable used by Cargo and setuptools_rust
|
||||||
@@ -450,13 +454,83 @@ def setup_rust(
|
|||||||
shim_path.chmod(0o755)
|
shim_path.chmod(0o755)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_fortran(env: dict[str, str]) -> None:
|
||||||
|
# In case there's any autodetection based on the executable name, use the same name
|
||||||
|
# as the real executable (see fortran_shim.run_flang)
|
||||||
|
shim_in = RESOURCES_ANDROID / "fortran_shim.py"
|
||||||
|
shim_out = Path(env["VIRTUAL_ENV"]) / "bin/flang-new"
|
||||||
|
|
||||||
|
# The hashbang line runs the shim in cibuildwheel's own virtual environment, so it
|
||||||
|
# has access to utility functions for downloading and caching files.
|
||||||
|
shim_out.write_text(f"#!{sys.executable}\n\n" + shim_in.read_text())
|
||||||
|
shim_out.chmod(0o755)
|
||||||
|
env["FC"] = str(shim_out)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_xbuild_files(state: BuildState) -> None:
|
||||||
|
_, pip = find_pip(state.options)
|
||||||
|
xbf_dir = state.build_path / "xbuild_files"
|
||||||
|
xbf_dir.mkdir()
|
||||||
|
|
||||||
|
for requirement in call(*pip, "freeze", env=state.build_env, capture_stdout=True).splitlines():
|
||||||
|
name, _, _ = requirement.strip().partition("==")
|
||||||
|
xbuild_files = state.options.xbuild_files.get(canonicalize_name(name), [])
|
||||||
|
if xbuild_files:
|
||||||
|
log.step(f"Installing xbuild-files for {name}...")
|
||||||
|
pip_install_android(state, xbf_dir, "--no-deps", requirement)
|
||||||
|
for xbf in xbuild_files:
|
||||||
|
if (xbf_dir / xbf).exists():
|
||||||
|
shutil.copy(
|
||||||
|
xbf_dir / xbf,
|
||||||
|
find_site_packages(state.build_env) / xbf,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
log.warning(f"{xbf_dir / xbf} does not exist")
|
||||||
|
|
||||||
|
|
||||||
|
def pip_install_android(state: BuildState, target: Path, *args: PathOrStr) -> None:
|
||||||
|
use_uv, pip = find_pip(state.options)
|
||||||
|
call(
|
||||||
|
*pip,
|
||||||
|
"install",
|
||||||
|
"--only-binary=:all:",
|
||||||
|
*(["--python-platform", android_triplet(state.config.identifier)] if use_uv else []),
|
||||||
|
"--target",
|
||||||
|
target,
|
||||||
|
*args,
|
||||||
|
env=state.android_env,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def find_site_packages(env: dict[str, str]) -> Path:
|
||||||
|
return glob1(Path(env["VIRTUAL_ENV"]), "lib/python*/site-packages")
|
||||||
|
|
||||||
|
|
||||||
|
def glob1(base: Path, pattern: str) -> Path:
|
||||||
|
results = list(base.glob(pattern))
|
||||||
|
if len(results) != 1:
|
||||||
|
msg = f"{base} contains {len(results)} paths matching '{pattern}'; expected 1"
|
||||||
|
raise errors.FatalError(msg)
|
||||||
|
return results[0]
|
||||||
|
|
||||||
|
|
||||||
|
def find_pip(build_options: BuildOptions) -> tuple[bool, list[str]]:
|
||||||
|
use_uv = build_options.build_frontend.name in {"build[uv]", "uv"}
|
||||||
|
uv_path = find_uv()
|
||||||
|
if use_uv and uv_path is None:
|
||||||
|
msg = "uv not found"
|
||||||
|
raise AssertionError(msg)
|
||||||
|
pip = ["pip"] if not use_uv else [str(uv_path), "pip"]
|
||||||
|
return use_uv, pip
|
||||||
|
|
||||||
|
|
||||||
def before_build(state: BuildState) -> None:
|
def before_build(state: BuildState) -> None:
|
||||||
if state.options.before_build:
|
if state.options.before_build:
|
||||||
log.step("Running before_build...")
|
log.step("Running before_build...")
|
||||||
shell_prepared(
|
shell_prepared(
|
||||||
state.options.before_build,
|
state.options.before_build,
|
||||||
build_options=state.options,
|
build_options=state.options,
|
||||||
env=state.build_env,
|
env=state.android_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -507,15 +581,10 @@ def build_wheel(state: BuildState) -> Path:
|
|||||||
env=state.android_env,
|
env=state.android_env,
|
||||||
)
|
)
|
||||||
case x:
|
case x:
|
||||||
msg = f"Invalid build backend {x!r}"
|
msg = f"Android requires the build frontend to be 'build' or 'uv', not {x!r}"
|
||||||
raise AssertionError(msg)
|
|
||||||
|
|
||||||
built_wheels = list(built_wheel_dir.glob("*.whl"))
|
|
||||||
if len(built_wheels) != 1:
|
|
||||||
msg = f"{built_wheel_dir} contains {len(built_wheels)} wheels; expected 1"
|
|
||||||
raise errors.FatalError(msg)
|
raise errors.FatalError(msg)
|
||||||
built_wheel = built_wheels[0]
|
|
||||||
|
|
||||||
|
built_wheel = glob1(built_wheel_dir, "*.whl")
|
||||||
if built_wheel.name.endswith("none-any.whl"):
|
if built_wheel.name.endswith("none-any.whl"):
|
||||||
raise errors.NonPlatformWheelError()
|
raise errors.NonPlatformWheelError()
|
||||||
return built_wheel
|
return built_wheel
|
||||||
@@ -527,9 +596,20 @@ def repair_wheel(state: BuildState, built_wheel: Path) -> Path:
|
|||||||
repaired_wheel_dir.mkdir()
|
repaired_wheel_dir.mkdir()
|
||||||
|
|
||||||
if state.options.repair_command:
|
if state.options.repair_command:
|
||||||
|
# Tell auditwheel the locations of compiler libraries.
|
||||||
|
toolchain = Path(state.android_env["CC"]).parent.parent
|
||||||
|
triplet = android_triplet(state.config.identifier)
|
||||||
|
ldpaths = ":".join(
|
||||||
|
str(glob1(toolchain, pattern))
|
||||||
|
for pattern in [
|
||||||
|
f"lib/clang/*/lib/linux/{triplet.split('-')[0]}", # libomp
|
||||||
|
f"sysroot/usr/lib/{triplet}", # libc++_shared
|
||||||
|
]
|
||||||
|
)
|
||||||
shell(
|
shell(
|
||||||
prepare_command(
|
prepare_command(
|
||||||
state.options.repair_command,
|
state.options.repair_command,
|
||||||
|
ldpaths=ldpaths,
|
||||||
wheel=built_wheel,
|
wheel=built_wheel,
|
||||||
dest_dir=repaired_wheel_dir,
|
dest_dir=repaired_wheel_dir,
|
||||||
package=state.options.package_dir,
|
package=state.options.package_dir,
|
||||||
@@ -538,14 +618,15 @@ def repair_wheel(state: BuildState, built_wheel: Path) -> Path:
|
|||||||
env=state.build_env,
|
env=state.build_env,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
repair_default(state.android_env, built_wheel, repaired_wheel_dir)
|
shutil.move(built_wheel, repaired_wheel_dir)
|
||||||
|
|
||||||
repaired_wheels = list(repaired_wheel_dir.glob("*.whl"))
|
repaired_wheels = list(repaired_wheel_dir.glob("*.whl"))
|
||||||
if len(repaired_wheels) == 0:
|
if len(repaired_wheels) == 0:
|
||||||
raise errors.RepairStepProducedNoWheelError()
|
raise errors.RepairStepProducedNoWheelError()
|
||||||
if len(repaired_wheels) != 1:
|
if len(repaired_wheels) != 1:
|
||||||
msg = f"{repaired_wheel_dir} contains {len(repaired_wheels)} wheels; expected 1"
|
raise errors.RepairStepProducedMultipleWheelsError(
|
||||||
raise errors.FatalError(msg)
|
[rw.name for rw in repaired_wheels],
|
||||||
|
)
|
||||||
repaired_wheel = repaired_wheels[0]
|
repaired_wheel = repaired_wheels[0]
|
||||||
|
|
||||||
if repaired_wheel.name.endswith("none-any.whl"):
|
if repaired_wheel.name.endswith("none-any.whl"):
|
||||||
@@ -553,119 +634,12 @@ def repair_wheel(state: BuildState, built_wheel: Path) -> Path:
|
|||||||
return repaired_wheel
|
return repaired_wheel
|
||||||
|
|
||||||
|
|
||||||
def repair_default(
|
def test_wheel(state: BuildState, wheel: Path) -> None:
|
||||||
android_env: dict[str, str], built_wheel: Path, repaired_wheel_dir: Path
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Adds libc++ to the wheel if anything links against it. In the future this should be
|
|
||||||
moved to auditwheel and generalized to support more libraries.
|
|
||||||
"""
|
|
||||||
if (match := re.search(r"^(.+?)-", built_wheel.name)) is None:
|
|
||||||
msg = f"Failed to parse wheel filename: {built_wheel.name}"
|
|
||||||
raise errors.FatalError(msg)
|
|
||||||
wheel_name = match[1]
|
|
||||||
|
|
||||||
unpacked_dir = repaired_wheel_dir / "unpacked"
|
|
||||||
unpacked_dir.mkdir()
|
|
||||||
shutil.unpack_archive(built_wheel, unpacked_dir, format="zip")
|
|
||||||
|
|
||||||
# Some build systems are inconsistent about name normalization, so don't assume the
|
|
||||||
# dist-info name is identical to the wheel name.
|
|
||||||
record_paths = list(unpacked_dir.glob("*.dist-info/RECORD"))
|
|
||||||
if len(record_paths) != 1:
|
|
||||||
msg = f"{built_wheel.name} contains {len(record_paths)} dist-info/RECORD files; expected 1"
|
|
||||||
raise errors.FatalError(msg)
|
|
||||||
|
|
||||||
old_soname = "libc++_shared.so"
|
|
||||||
paths_to_patch = []
|
|
||||||
for path, elffile in elf_file_filter(
|
|
||||||
unpacked_dir / filename
|
|
||||||
for filename, *_ in csv.reader(record_paths[0].read_text().splitlines())
|
|
||||||
):
|
|
||||||
if (dynamic := elffile.get_section_by_name(".dynamic")) and any( # type: ignore[no-untyped-call]
|
|
||||||
tag.entry.d_tag == "DT_NEEDED" and tag.needed == old_soname
|
|
||||||
for tag in dynamic.iter_tags()
|
|
||||||
):
|
|
||||||
paths_to_patch.append(path)
|
|
||||||
|
|
||||||
if not paths_to_patch:
|
|
||||||
shutil.copyfile(built_wheel, repaired_wheel_dir / built_wheel.name)
|
|
||||||
else:
|
|
||||||
# Android doesn't support DT_RPATH, but supports DT_RUNPATH since API level 24
|
|
||||||
# (https://github.com/aosp-mirror/platform_bionic/blob/master/android-changes-for-ndk-developers.md).
|
|
||||||
if int(sysconfig_print('get_config_vars()["ANDROID_API_LEVEL"]', android_env)) < 24:
|
|
||||||
msg = f"Adding {old_soname} requires ANDROID_API_LEVEL to be at least 24"
|
|
||||||
raise errors.FatalError(msg)
|
|
||||||
|
|
||||||
toolchain = Path(android_env["CC"]).parent.parent
|
|
||||||
src_path = toolchain / f"sysroot/usr/lib/{android_env['CIBW_HOST_TRIPLET']}/{old_soname}"
|
|
||||||
|
|
||||||
# Use the same library location as auditwheel would.
|
|
||||||
libs_dir = unpacked_dir / (wheel_name + ".libs")
|
|
||||||
libs_dir.mkdir()
|
|
||||||
new_soname = soname_with_hash(src_path)
|
|
||||||
dst_path = libs_dir / new_soname
|
|
||||||
shutil.copyfile(src_path, dst_path)
|
|
||||||
call(which("patchelf"), "--set-soname", new_soname, dst_path)
|
|
||||||
|
|
||||||
for path in paths_to_patch:
|
|
||||||
call(which("patchelf"), "--replace-needed", old_soname, new_soname, path)
|
|
||||||
call(
|
|
||||||
which("patchelf"),
|
|
||||||
"--set-rpath",
|
|
||||||
f"${{ORIGIN}}/{relpath(libs_dir, path.parent)}",
|
|
||||||
path,
|
|
||||||
)
|
|
||||||
call(which("wheel"), "pack", unpacked_dir, "-d", repaired_wheel_dir)
|
|
||||||
|
|
||||||
|
|
||||||
# If cibuildwheel was called without activating its environment, its scripts directory
|
|
||||||
# will not be on the PATH.
|
|
||||||
def which(cmd: str) -> str:
|
|
||||||
scripts_dir = sysconfig.get_path("scripts")
|
|
||||||
result = shutil.which(cmd, path=scripts_dir + os.pathsep + os.environ["PATH"])
|
|
||||||
if result is None:
|
|
||||||
msg = f"Couldn't find {cmd!r} in {scripts_dir} or on the PATH"
|
|
||||||
raise errors.FatalError(msg)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def elf_file_filter(paths: Iterable[Path]) -> Iterator[tuple[Path, ELFFile]]:
|
|
||||||
"""Filter through an iterator of filenames and load up only ELF files"""
|
|
||||||
for path in paths:
|
|
||||||
if not path.name.endswith(".py"):
|
|
||||||
try:
|
|
||||||
with open(path, "rb") as f:
|
|
||||||
candidate = ELFFile(f) # type: ignore[no-untyped-call]
|
|
||||||
yield path, candidate
|
|
||||||
except ELFError:
|
|
||||||
pass # Not an ELF file
|
|
||||||
|
|
||||||
|
|
||||||
def soname_with_hash(src_path: Path) -> str:
|
|
||||||
"""Return the same library filename as auditwheel would"""
|
|
||||||
shorthash = hashlib.sha256(src_path.read_bytes()).hexdigest()[:8]
|
|
||||||
src_name = src_path.name
|
|
||||||
base, ext = src_name.split(".", 1)
|
|
||||||
if not base.endswith(f"-{shorthash}"):
|
|
||||||
return f"{base}-{shorthash}.{ext}"
|
|
||||||
else:
|
|
||||||
return src_name
|
|
||||||
|
|
||||||
|
|
||||||
def test_wheel(state: BuildState, wheel: Path, *, build_frontend: str) -> None:
|
|
||||||
test_command = state.options.test_command
|
test_command = state.options.test_command
|
||||||
if not (test_command and state.options.test_selector(state.config.identifier)):
|
if not (test_command and state.options.test_selector(state.config.identifier)):
|
||||||
return
|
return
|
||||||
|
|
||||||
log.step("Testing wheel...")
|
log.step("Testing wheel...")
|
||||||
use_uv = build_frontend in {"build[uv]", "uv"}
|
|
||||||
uv_path = find_uv()
|
|
||||||
if use_uv and uv_path is None:
|
|
||||||
msg = "uv not found"
|
|
||||||
raise AssertionError(msg)
|
|
||||||
pip = ["pip"] if not use_uv else [str(uv_path), "pip"]
|
|
||||||
|
|
||||||
native_arch = arch_synonym(platform.machine(), platforms.native_platform(), "android")
|
native_arch = arch_synonym(platform.machine(), platforms.native_platform(), "android")
|
||||||
if state.config.arch != native_arch:
|
if state.config.arch != native_arch:
|
||||||
log.warning(
|
log.warning(
|
||||||
@@ -678,31 +652,17 @@ def test_wheel(state: BuildState, wheel: Path, *, build_frontend: str) -> None:
|
|||||||
shell_prepared(
|
shell_prepared(
|
||||||
state.options.before_test,
|
state.options.before_test,
|
||||||
build_options=state.options,
|
build_options=state.options,
|
||||||
env=state.build_env,
|
env=state.android_env,
|
||||||
)
|
|
||||||
|
|
||||||
platform_args = (
|
|
||||||
["--python-platform", android_triplet(state.config.identifier)]
|
|
||||||
if use_uv
|
|
||||||
else [
|
|
||||||
"--platform",
|
|
||||||
sysconfig_print("get_platform()", state.android_env).replace("-", "_"),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Install the wheel and test-requires.
|
# Install the wheel and test-requires.
|
||||||
site_packages_dir = state.build_path / "site-packages"
|
site_packages_dir = state.build_path / "site-packages"
|
||||||
site_packages_dir.mkdir()
|
site_packages_dir.mkdir()
|
||||||
call(
|
pip_install_android(
|
||||||
*pip,
|
state,
|
||||||
"install",
|
|
||||||
"--only-binary=:all:",
|
|
||||||
*platform_args,
|
|
||||||
"--target",
|
|
||||||
site_packages_dir,
|
site_packages_dir,
|
||||||
f"{wheel}{state.options.test_extras}",
|
f"{wheel}{state.options.test_extras}",
|
||||||
*state.options.test_requires,
|
*state.options.test_requires,
|
||||||
env=state.android_env,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Copy test-sources.
|
# Copy test-sources.
|
||||||
@@ -772,13 +732,3 @@ def test_wheel(state: BuildState, wheel: Path, *, build_frontend: str) -> None:
|
|||||||
*test_args,
|
*test_args,
|
||||||
env=state.build_env,
|
env=state.build_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def sysconfig_print(method_call: str, env: dict[str, str]) -> str:
|
|
||||||
return call(
|
|
||||||
"python",
|
|
||||||
"-c",
|
|
||||||
f'import sysconfig; print(sysconfig.{method_call}, end="")',
|
|
||||||
env=env,
|
|
||||||
capture_stdout=True,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ def initialize() -> None:
|
|||||||
if not (host_triplet := os.environ.get("CIBW_HOST_TRIPLET")):
|
if not (host_triplet := os.environ.get("CIBW_HOST_TRIPLET")):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Pre-import any modules which would fail to import after the monkey-patching.
|
||||||
|
import ctypes # noqa: F401, PLC0415 - uses get_config_var("LDLIBRARY")
|
||||||
|
|
||||||
# os ######################################################################
|
# os ######################################################################
|
||||||
def cross_os_uname() -> os.uname_result:
|
def cross_os_uname() -> os.uname_result:
|
||||||
return os.uname_result(
|
return os.uname_result(
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# This file intentionally has no hashbang line in the source: cibuildwheel will add it
|
||||||
|
# above this comment when the file is deployed.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from tempfile import gettempdir
|
||||||
|
|
||||||
|
from filelock import FileLock
|
||||||
|
|
||||||
|
from cibuildwheel.util.file import CIBW_CACHE_PATH, download
|
||||||
|
|
||||||
|
# In the future we might pick a different Flang release depending on the NDK version,
|
||||||
|
# but so far all Python versions use the same NDK version, so there's no need.
|
||||||
|
RELEASE_URL = "https://github.com/termux/ndk-toolchain-clang-with-flang/releases/download"
|
||||||
|
RELEASE_VERSION = "r27c"
|
||||||
|
ARCHS = ["aarch64", "x86_64"]
|
||||||
|
|
||||||
|
# The compiler is built for Linux x86_64, so we use Docker on macOS.
|
||||||
|
DOCKER_IMAGE = "debian:trixie-slim"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
cache_dir = CIBW_CACHE_PATH / f"flang-android-{RELEASE_VERSION}"
|
||||||
|
with FileLock(f"{cache_dir}.lock"):
|
||||||
|
if not cache_dir.exists():
|
||||||
|
download_flang(cache_dir)
|
||||||
|
|
||||||
|
run_flang(cache_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def download_flang(cache_dir: Path) -> None:
|
||||||
|
tmp_dir = Path(f"{cache_dir}.tmp")
|
||||||
|
if tmp_dir.exists():
|
||||||
|
shutil.rmtree(tmp_dir)
|
||||||
|
tmp_dir.mkdir(parents=True)
|
||||||
|
|
||||||
|
for archive_name in [f"package-flang-{arch}.tar.bz2" for arch in ARCHS] + [
|
||||||
|
"package-flang-host.tar.bz2",
|
||||||
|
"package-install.tar.bz2",
|
||||||
|
]:
|
||||||
|
archive_path = tmp_dir / archive_name
|
||||||
|
download(f"{RELEASE_URL}/{RELEASE_VERSION}/{archive_name}", archive_path)
|
||||||
|
shutil.unpack_archive(archive_path, tmp_dir)
|
||||||
|
archive_path.unlink()
|
||||||
|
|
||||||
|
# Merge the extracted trees together, along with the necessary parts of the NDK. Based on
|
||||||
|
# https://github.com/kivy/python-for-android/blob/develop/pythonforandroid/recipes/fortran/__init__.py.
|
||||||
|
flang_toolchain = tmp_dir / "toolchain"
|
||||||
|
(tmp_dir / "out/install/linux-x86/clang-dev").rename(flang_toolchain)
|
||||||
|
|
||||||
|
ndk_toolchain = Path(os.environ["CC"]).parents[1]
|
||||||
|
if (clang_ver_flang := clang_ver(flang_toolchain)) != (
|
||||||
|
clang_ver_ndk := clang_ver(ndk_toolchain)
|
||||||
|
):
|
||||||
|
msg = f"Flang uses Clang {clang_ver_flang}, but NDK uses Clang {clang_ver_ndk}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
clang_lib_path = f"lib/clang/{clang_ver_ndk}/lib"
|
||||||
|
shutil.rmtree(flang_toolchain / clang_lib_path)
|
||||||
|
|
||||||
|
for src, dst in [
|
||||||
|
(f"{tmp_dir}/build-{arch}-install", f"sysroot/usr/lib/{arch}-linux-android")
|
||||||
|
for arch in ARCHS
|
||||||
|
] + [
|
||||||
|
(f"{tmp_dir}/build-host-install", ""),
|
||||||
|
(f"{ndk_toolchain}/{clang_lib_path}", clang_lib_path),
|
||||||
|
(f"{ndk_toolchain}/sysroot", "sysroot"),
|
||||||
|
]:
|
||||||
|
shutil.copytree(src, flang_toolchain / dst, symlinks=True, dirs_exist_ok=True)
|
||||||
|
|
||||||
|
flang_toolchain.rename(cache_dir)
|
||||||
|
shutil.rmtree(tmp_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def clang_ver(toolchain: Path) -> str:
|
||||||
|
versions = [p.name for p in (toolchain / "lib/clang").iterdir()]
|
||||||
|
assert len(versions) == 1
|
||||||
|
return versions[0]
|
||||||
|
|
||||||
|
|
||||||
|
def run_flang(cache_dir: Path) -> None:
|
||||||
|
match = re.fullmatch(r".+/(.+)-clang", os.environ["CC"])
|
||||||
|
assert match is not None
|
||||||
|
target = match[1]
|
||||||
|
|
||||||
|
# In a future Flang version the executable name will change to "flang"
|
||||||
|
# (https://blog.llvm.org/posts/2025-03-11-flang-new/).
|
||||||
|
flang_args = [f"{cache_dir}/bin/flang-new", f"--target={target}", *sys.argv[1:]]
|
||||||
|
|
||||||
|
if sys.platform == "linux":
|
||||||
|
args = flang_args
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
args = ["docker", "run", "--rm", "--platform", "linux/amd64"]
|
||||||
|
# Docker on macOS only allows certain directories to be mounted as volumes
|
||||||
|
# by default, but they include all the locations we're likely to need.
|
||||||
|
for path in [
|
||||||
|
# Location of CIBW_CACHE_PATH (containing the Fortran compiler), and
|
||||||
|
# probably the project directory.
|
||||||
|
Path.home(),
|
||||||
|
# Temporary directories created by build backends and cibuildwheel itself.
|
||||||
|
# This is controlled by the TMPDIR environment variable, which is set to
|
||||||
|
# an unpredictable subdirectory of /var/folders. /var is itself a symlink
|
||||||
|
# to /private/var.
|
||||||
|
Path(gettempdir()),
|
||||||
|
Path(gettempdir()).resolve(),
|
||||||
|
]:
|
||||||
|
args += ["-v", f"{path}:{path}"]
|
||||||
|
args += ["--workdir", str(Path.cwd())]
|
||||||
|
args += ["--entrypoint", flang_args[0], DOCKER_IMAGE, *flang_args[1:]]
|
||||||
|
else:
|
||||||
|
msg = f"unknown platform: {sys.platform}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
os.execvp(args[0], args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -460,6 +460,34 @@
|
|||||||
],
|
],
|
||||||
"title": "CIBW_XBUILD_TOOLS"
|
"title": "CIBW_XBUILD_TOOLS"
|
||||||
},
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"description": "Platform-specific files in the build environment",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"patternProperties": {
|
||||||
|
".+": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "CIBW_XBUILD_FILES"
|
||||||
|
},
|
||||||
"pyodide-version": {
|
"pyodide-version": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Specify the version of Pyodide to use",
|
"description": "Specify the version of Pyodide to use",
|
||||||
@@ -679,6 +707,9 @@
|
|||||||
"xbuild-tools": {
|
"xbuild-tools": {
|
||||||
"$ref": "#/$defs/inherit"
|
"$ref": "#/$defs/inherit"
|
||||||
},
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"$ref": "#/$defs/inherit"
|
||||||
|
},
|
||||||
"before-test": {
|
"before-test": {
|
||||||
"$ref": "#/$defs/inherit"
|
"$ref": "#/$defs/inherit"
|
||||||
},
|
},
|
||||||
@@ -807,6 +838,9 @@
|
|||||||
"xbuild-tools": {
|
"xbuild-tools": {
|
||||||
"$ref": "#/properties/xbuild-tools"
|
"$ref": "#/properties/xbuild-tools"
|
||||||
},
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"$ref": "#/properties/xbuild-files"
|
||||||
|
},
|
||||||
"pyodide-version": {
|
"pyodide-version": {
|
||||||
"$ref": "#/properties/pyodide-version"
|
"$ref": "#/properties/pyodide-version"
|
||||||
},
|
},
|
||||||
@@ -931,6 +965,9 @@
|
|||||||
"xbuild-tools": {
|
"xbuild-tools": {
|
||||||
"$ref": "#/properties/xbuild-tools"
|
"$ref": "#/properties/xbuild-tools"
|
||||||
},
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"$ref": "#/properties/xbuild-files"
|
||||||
|
},
|
||||||
"pyodide-version": {
|
"pyodide-version": {
|
||||||
"$ref": "#/properties/pyodide-version"
|
"$ref": "#/properties/pyodide-version"
|
||||||
},
|
},
|
||||||
@@ -1013,6 +1050,9 @@
|
|||||||
"xbuild-tools": {
|
"xbuild-tools": {
|
||||||
"$ref": "#/properties/xbuild-tools"
|
"$ref": "#/properties/xbuild-tools"
|
||||||
},
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"$ref": "#/properties/xbuild-files"
|
||||||
|
},
|
||||||
"pyodide-version": {
|
"pyodide-version": {
|
||||||
"$ref": "#/properties/pyodide-version"
|
"$ref": "#/properties/pyodide-version"
|
||||||
},
|
},
|
||||||
@@ -1082,6 +1122,9 @@
|
|||||||
"xbuild-tools": {
|
"xbuild-tools": {
|
||||||
"$ref": "#/properties/xbuild-tools"
|
"$ref": "#/properties/xbuild-tools"
|
||||||
},
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"$ref": "#/properties/xbuild-files"
|
||||||
|
},
|
||||||
"pyodide-version": {
|
"pyodide-version": {
|
||||||
"$ref": "#/properties/pyodide-version"
|
"$ref": "#/properties/pyodide-version"
|
||||||
},
|
},
|
||||||
@@ -1164,6 +1207,9 @@
|
|||||||
"xbuild-tools": {
|
"xbuild-tools": {
|
||||||
"$ref": "#/properties/xbuild-tools"
|
"$ref": "#/properties/xbuild-tools"
|
||||||
},
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"$ref": "#/properties/xbuild-files"
|
||||||
|
},
|
||||||
"pyodide-version": {
|
"pyodide-version": {
|
||||||
"$ref": "#/properties/pyodide-version"
|
"$ref": "#/properties/pyodide-version"
|
||||||
},
|
},
|
||||||
@@ -1233,11 +1279,27 @@
|
|||||||
"xbuild-tools": {
|
"xbuild-tools": {
|
||||||
"$ref": "#/properties/xbuild-tools"
|
"$ref": "#/properties/xbuild-tools"
|
||||||
},
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"$ref": "#/properties/xbuild-files"
|
||||||
|
},
|
||||||
"pyodide-version": {
|
"pyodide-version": {
|
||||||
"$ref": "#/properties/pyodide-version"
|
"$ref": "#/properties/pyodide-version"
|
||||||
},
|
},
|
||||||
"repair-wheel-command": {
|
"repair-wheel-command": {
|
||||||
"$ref": "#/properties/repair-wheel-command"
|
"description": "Execute a shell command to repair each built wheel.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "CIBW_REPAIR_WHEEL_COMMAND",
|
||||||
|
"default": "auditwheel repair --ldpaths {ldpaths} -w {dest_dir} {wheel}"
|
||||||
},
|
},
|
||||||
"test-command": {
|
"test-command": {
|
||||||
"$ref": "#/properties/test-command"
|
"$ref": "#/properties/test-command"
|
||||||
@@ -1302,6 +1364,9 @@
|
|||||||
"xbuild-tools": {
|
"xbuild-tools": {
|
||||||
"$ref": "#/properties/xbuild-tools"
|
"$ref": "#/properties/xbuild-tools"
|
||||||
},
|
},
|
||||||
|
"xbuild-files": {
|
||||||
|
"$ref": "#/properties/xbuild-files"
|
||||||
|
},
|
||||||
"pyodide-version": {
|
"pyodide-version": {
|
||||||
"$ref": "#/properties/pyodide-version"
|
"$ref": "#/properties/pyodide-version"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ attrs==26.1.0
|
|||||||
# via
|
# via
|
||||||
# cattrs
|
# cattrs
|
||||||
# requests-cache
|
# requests-cache
|
||||||
|
auditwheel==6.7.0
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
build==1.5.0
|
build==1.5.0
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
cattrs==26.1.0
|
cattrs==26.1.0
|
||||||
@@ -47,21 +49,28 @@ mdurl==0.1.2
|
|||||||
packaging==26.2
|
packaging==26.2
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
# build
|
# build
|
||||||
# delocate
|
# delocate
|
||||||
|
patchelf==0.17.2.4
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
pefile==2024.8.26
|
pefile==2024.8.26
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
# delvewheel
|
# delvewheel
|
||||||
pip==26.1.1
|
pip==26.1.1
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
|
pkgconf==2.5.1.post2
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
platformdirs==4.9.6
|
platformdirs==4.9.6
|
||||||
# via
|
# via
|
||||||
# python-discovery
|
# python-discovery
|
||||||
# requests-cache
|
# requests-cache
|
||||||
# virtualenv
|
# virtualenv
|
||||||
pyelftools==0.32
|
pyelftools==0.32
|
||||||
# via abi3audit
|
# via
|
||||||
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
pygments==2.20.0
|
pygments==2.20.0
|
||||||
# via rich
|
# via rich
|
||||||
pyproject-hooks==1.2.0
|
pyproject-hooks==1.2.0
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ attrs==26.1.0
|
|||||||
# via
|
# via
|
||||||
# cattrs
|
# cattrs
|
||||||
# requests-cache
|
# requests-cache
|
||||||
|
auditwheel==6.7.0
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
build==1.5.0
|
build==1.5.0
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
cattrs==26.1.0
|
cattrs==26.1.0
|
||||||
@@ -43,8 +45,11 @@ mdurl==0.1.2
|
|||||||
packaging==26.2
|
packaging==26.2
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
# build
|
# build
|
||||||
# delocate
|
# delocate
|
||||||
|
patchelf==0.17.2.4
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
pefile==2024.8.26
|
pefile==2024.8.26
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
@@ -52,13 +57,17 @@ pefile==2024.8.26
|
|||||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
|
pkgconf==2.5.1.post2
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
platformdirs==4.9.6
|
platformdirs==4.9.6
|
||||||
# via
|
# via
|
||||||
# python-discovery
|
# python-discovery
|
||||||
# requests-cache
|
# requests-cache
|
||||||
# virtualenv
|
# virtualenv
|
||||||
pyelftools==0.32
|
pyelftools==0.32
|
||||||
# via abi3audit
|
# via
|
||||||
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
pygments==2.20.0
|
pygments==2.20.0
|
||||||
# via rich
|
# via rich
|
||||||
pyproject-hooks==1.2.0
|
pyproject-hooks==1.2.0
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ attrs==26.1.0
|
|||||||
# via
|
# via
|
||||||
# cattrs
|
# cattrs
|
||||||
# requests-cache
|
# requests-cache
|
||||||
|
auditwheel==6.7.0
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
build==1.5.0
|
build==1.5.0
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
cattrs==26.1.0
|
cattrs==26.1.0
|
||||||
@@ -43,8 +45,11 @@ mdurl==0.1.2
|
|||||||
packaging==26.2
|
packaging==26.2
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
# build
|
# build
|
||||||
# delocate
|
# delocate
|
||||||
|
patchelf==0.17.2.4
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
pefile==2024.8.26
|
pefile==2024.8.26
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
@@ -52,13 +57,17 @@ pefile==2024.8.26
|
|||||||
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
pip==26.1.1; implementation_name != "graalpy" or platform_system != "Windows"
|
||||||
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
pip==26.0.1; implementation_name == "graalpy" and platform_system == "Windows"
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
|
pkgconf==2.5.1.post2
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
platformdirs==4.9.6
|
platformdirs==4.9.6
|
||||||
# via
|
# via
|
||||||
# python-discovery
|
# python-discovery
|
||||||
# requests-cache
|
# requests-cache
|
||||||
# virtualenv
|
# virtualenv
|
||||||
pyelftools==0.32
|
pyelftools==0.32
|
||||||
# via abi3audit
|
# via
|
||||||
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
pygments==2.20.0
|
pygments==2.20.0
|
||||||
# via rich
|
# via rich
|
||||||
pyproject-hooks==1.2.0
|
pyproject-hooks==1.2.0
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ attrs==26.1.0
|
|||||||
# via
|
# via
|
||||||
# cattrs
|
# cattrs
|
||||||
# requests-cache
|
# requests-cache
|
||||||
|
auditwheel==6.7.0
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
build==1.5.0
|
build==1.5.0
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
cattrs==26.1.0
|
cattrs==26.1.0
|
||||||
@@ -43,21 +45,28 @@ mdurl==0.1.2
|
|||||||
packaging==26.2
|
packaging==26.2
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
# build
|
# build
|
||||||
# delocate
|
# delocate
|
||||||
|
patchelf==0.17.2.4
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
pefile==2024.8.26
|
pefile==2024.8.26
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
# delvewheel
|
# delvewheel
|
||||||
pip==26.1.1
|
pip==26.1.1
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
|
pkgconf==2.5.1.post2
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
platformdirs==4.9.6
|
platformdirs==4.9.6
|
||||||
# via
|
# via
|
||||||
# python-discovery
|
# python-discovery
|
||||||
# requests-cache
|
# requests-cache
|
||||||
# virtualenv
|
# virtualenv
|
||||||
pyelftools==0.32
|
pyelftools==0.32
|
||||||
# via abi3audit
|
# via
|
||||||
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
pygments==2.20.0
|
pygments==2.20.0
|
||||||
# via rich
|
# via rich
|
||||||
pyproject-hooks==1.2.0
|
pyproject-hooks==1.2.0
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ attrs==26.1.0
|
|||||||
# via
|
# via
|
||||||
# cattrs
|
# cattrs
|
||||||
# requests-cache
|
# requests-cache
|
||||||
|
auditwheel==6.7.0
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
build==1.5.0
|
build==1.5.0
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
cattrs==26.1.0
|
cattrs==26.1.0
|
||||||
@@ -43,21 +45,28 @@ mdurl==0.1.2
|
|||||||
packaging==26.2
|
packaging==26.2
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
# build
|
# build
|
||||||
# delocate
|
# delocate
|
||||||
|
patchelf==0.17.2.4
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
pefile==2024.8.26
|
pefile==2024.8.26
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
# delvewheel
|
# delvewheel
|
||||||
pip==26.1.1
|
pip==26.1.1
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
|
pkgconf==2.5.1.post2
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
platformdirs==4.9.6
|
platformdirs==4.9.6
|
||||||
# via
|
# via
|
||||||
# python-discovery
|
# python-discovery
|
||||||
# requests-cache
|
# requests-cache
|
||||||
# virtualenv
|
# virtualenv
|
||||||
pyelftools==0.32
|
pyelftools==0.32
|
||||||
# via abi3audit
|
# via
|
||||||
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
pygments==2.20.0
|
pygments==2.20.0
|
||||||
# via rich
|
# via rich
|
||||||
pyproject-hooks==1.2.0
|
pyproject-hooks==1.2.0
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ attrs==26.1.0
|
|||||||
# via
|
# via
|
||||||
# cattrs
|
# cattrs
|
||||||
# requests-cache
|
# requests-cache
|
||||||
|
auditwheel==6.7.0
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
build==1.5.0
|
build==1.5.0
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
cattrs==26.1.0
|
cattrs==26.1.0
|
||||||
@@ -43,21 +45,28 @@ mdurl==0.1.2
|
|||||||
packaging==26.2
|
packaging==26.2
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
# build
|
# build
|
||||||
# delocate
|
# delocate
|
||||||
|
patchelf==0.17.2.4
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
pefile==2024.8.26
|
pefile==2024.8.26
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
# delvewheel
|
# delvewheel
|
||||||
pip==26.1.1
|
pip==26.1.1
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
|
pkgconf==2.5.1.post2
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
platformdirs==4.9.6
|
platformdirs==4.9.6
|
||||||
# via
|
# via
|
||||||
# python-discovery
|
# python-discovery
|
||||||
# requests-cache
|
# requests-cache
|
||||||
# virtualenv
|
# virtualenv
|
||||||
pyelftools==0.32
|
pyelftools==0.32
|
||||||
# via abi3audit
|
# via
|
||||||
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
pygments==2.20.0
|
pygments==2.20.0
|
||||||
# via rich
|
# via rich
|
||||||
pyproject-hooks==1.2.0
|
pyproject-hooks==1.2.0
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ attrs==26.1.0
|
|||||||
# via
|
# via
|
||||||
# cattrs
|
# cattrs
|
||||||
# requests-cache
|
# requests-cache
|
||||||
|
auditwheel==6.4.2
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
build==1.4.4
|
build==1.4.4
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
cattrs==25.3.0
|
cattrs==25.3.0
|
||||||
@@ -35,7 +37,9 @@ idna==3.16
|
|||||||
# requests
|
# requests
|
||||||
# url-normalize
|
# url-normalize
|
||||||
importlib-metadata==8.7.1
|
importlib-metadata==8.7.1
|
||||||
# via build
|
# via
|
||||||
|
# build
|
||||||
|
# pkgconf
|
||||||
kaitaistruct==0.11
|
kaitaistruct==0.11
|
||||||
# via abi3audit
|
# via abi3audit
|
||||||
macholib==1.16.4
|
macholib==1.16.4
|
||||||
@@ -47,21 +51,28 @@ mdurl==0.1.2
|
|||||||
packaging==25.0
|
packaging==25.0
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
# build
|
# build
|
||||||
# delocate
|
# delocate
|
||||||
|
patchelf==0.17.2.4
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
pefile==2024.8.26
|
pefile==2024.8.26
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
# delvewheel
|
# delvewheel
|
||||||
pip==26.0.1
|
pip==26.0.1
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
|
pkgconf==2.4.3.post2
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
platformdirs==4.4.0
|
platformdirs==4.4.0
|
||||||
# via
|
# via
|
||||||
# python-discovery
|
# python-discovery
|
||||||
# requests-cache
|
# requests-cache
|
||||||
# virtualenv
|
# virtualenv
|
||||||
pyelftools==0.32
|
pyelftools==0.32
|
||||||
# via abi3audit
|
# via
|
||||||
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
pygments==2.20.0
|
pygments==2.20.0
|
||||||
# via rich
|
# via rich
|
||||||
pyproject-hooks==1.2.0
|
pyproject-hooks==1.2.0
|
||||||
|
|||||||
@@ -4,3 +4,8 @@ delocate
|
|||||||
delvewheel
|
delvewheel
|
||||||
virtualenv
|
virtualenv
|
||||||
abi3audit
|
abi3audit
|
||||||
|
|
||||||
|
# Android
|
||||||
|
auditwheel
|
||||||
|
patchelf
|
||||||
|
pkgconf
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ attrs==26.1.0
|
|||||||
# via
|
# via
|
||||||
# cattrs
|
# cattrs
|
||||||
# requests-cache
|
# requests-cache
|
||||||
|
auditwheel==6.7.0
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
build==1.5.0
|
build==1.5.0
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
cattrs==26.1.0
|
cattrs==26.1.0
|
||||||
@@ -43,21 +45,28 @@ mdurl==0.1.2
|
|||||||
packaging==26.2
|
packaging==26.2
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
# build
|
# build
|
||||||
# delocate
|
# delocate
|
||||||
|
patchelf==0.17.2.4
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
pefile==2024.8.26
|
pefile==2024.8.26
|
||||||
# via
|
# via
|
||||||
# abi3audit
|
# abi3audit
|
||||||
# delvewheel
|
# delvewheel
|
||||||
pip==26.1.1
|
pip==26.1.1
|
||||||
# via -r cibuildwheel/resources/constraints.in
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
|
pkgconf==2.5.1.post2
|
||||||
|
# via -r cibuildwheel/resources/constraints.in
|
||||||
platformdirs==4.9.6
|
platformdirs==4.9.6
|
||||||
# via
|
# via
|
||||||
# python-discovery
|
# python-discovery
|
||||||
# requests-cache
|
# requests-cache
|
||||||
# virtualenv
|
# virtualenv
|
||||||
pyelftools==0.32
|
pyelftools==0.32
|
||||||
# via abi3audit
|
# via
|
||||||
|
# abi3audit
|
||||||
|
# auditwheel
|
||||||
pygments==2.20.0
|
pygments==2.20.0
|
||||||
# via rich
|
# via rich
|
||||||
pyproject-hooks==1.2.0
|
pyproject-hooks==1.2.0
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# These are the defaults used by cibuildwheel itself. They should match the values in
|
||||||
|
# generate_schema.py, which are used by external tools for validation and IDE support.
|
||||||
|
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
build = "*"
|
build = "*"
|
||||||
skip = ""
|
skip = ""
|
||||||
@@ -52,6 +55,13 @@ musllinux-s390x-image = "musllinux_1_2"
|
|||||||
musllinux-armv7l-image = "musllinux_1_2"
|
musllinux-armv7l-image = "musllinux_1_2"
|
||||||
musllinux-riscv64-image = "musllinux_1_2"
|
musllinux-riscv64-image = "musllinux_1_2"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.xbuild-files]
|
||||||
|
numpy = [
|
||||||
|
"numpy/_core/include/numpy/_numpyconfig.h",
|
||||||
|
"numpy/_core/include/numpy/numpyconfig.h",
|
||||||
|
"numpy/_core/lib/libnpymath.a",
|
||||||
|
"numpy/random/lib/libnpyrandom.a",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.cibuildwheel.linux]
|
[tool.cibuildwheel.linux]
|
||||||
repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
|
repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}"
|
||||||
@@ -63,6 +73,7 @@ repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest
|
|||||||
repair-wheel-command = "delvewheel repair -w {dest_dir} -v {wheel}"
|
repair-wheel-command = "delvewheel repair -w {dest_dir} -v {wheel}"
|
||||||
|
|
||||||
[tool.cibuildwheel.android]
|
[tool.cibuildwheel.android]
|
||||||
|
repair-wheel-command = "auditwheel repair --ldpaths {ldpaths} -w {dest_dir} {wheel}"
|
||||||
|
|
||||||
[tool.cibuildwheel.ios]
|
[tool.cibuildwheel.ios]
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ def parse_key_value_string(
|
|||||||
if kw_arg_names is None:
|
if kw_arg_names is None:
|
||||||
kw_arg_names = []
|
kw_arg_names = []
|
||||||
|
|
||||||
all_field_names = [*positional_arg_names, *kw_arg_names]
|
all_field_names = None if ("*" in kw_arg_names) else [*positional_arg_names, *kw_arg_names]
|
||||||
|
|
||||||
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";")
|
shlexer = shlex.shlex(key_value_string, posix=True, punctuation_chars=";")
|
||||||
shlexer.commenters = ""
|
shlexer.commenters = ""
|
||||||
@@ -126,7 +126,7 @@ def parse_key_value_string(
|
|||||||
# check to see if the option name is specified
|
# check to see if the option name is specified
|
||||||
field_name, sep, first_value = field[0].partition(":")
|
field_name, sep, first_value = field[0].partition(":")
|
||||||
if sep:
|
if sep:
|
||||||
if field_name not in all_field_names:
|
if (all_field_names is not None) and (field_name not in all_field_names):
|
||||||
msg = f"Failed to parse {key_value_string!r}. Unknown field name {field_name!r}"
|
msg = f"Failed to parse {key_value_string!r}. Unknown field name {field_name!r}"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ cibuildwheel to run tests, add the following YAML to your CI config file:
|
|||||||
You can configure cibuildwheel with a config file, such as `pyproject.toml`.
|
You can configure cibuildwheel with a config file, such as `pyproject.toml`.
|
||||||
Options have the same names as the environment variable overrides, but are
|
Options have the same names as the environment variable overrides, but are
|
||||||
placed in `[tool.cibuildwheel]` and are lower case, with dashes, following
|
placed in `[tool.cibuildwheel]` and are lower case, with dashes, following
|
||||||
common [TOML](https://toml.io) practice. Anything placed in subsections `linux`, `windows`,
|
common [TOML](https://toml.io) practice. Anything placed in subsections
|
||||||
`macos`, or `pyodide` will only affect those platforms. Lists can be used
|
named after a platform will only affect those platforms. Lists can be used
|
||||||
instead of strings for items that are naturally a list. Multiline strings also
|
instead of strings for items that are naturally a list. Multiline strings also
|
||||||
work just like in the environment variables. Environment variables will take
|
work just like in the environment variables. Environment variables will take
|
||||||
precedence if defined.
|
precedence if defined.
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
+2
-2
@@ -179,14 +179,14 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Repair wheel',
|
label: 'Repair using auditwheel',
|
||||||
href: 'options/#repair-wheel-command',
|
href: 'options/#repair-wheel-command',
|
||||||
platforms: ['android'],
|
platforms: ['android'],
|
||||||
style: 'block',
|
style: 'block',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
title: 'CIBW_REPAIR_WHEEL_COMMAND',
|
title: 'CIBW_REPAIR_WHEEL_COMMAND',
|
||||||
tag: 'Customisable step',
|
tag: 'Customisable step',
|
||||||
description: 'By default, bundles libc++ into the wheel if any shared library links against it, using patchelf. Can be overridden with a custom repair command.'
|
description: 'Bundle shared libraries by running auditwheel on each built wheel.'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+31
-2
@@ -916,6 +916,35 @@ Platform-specific environment variables are also available on platforms that use
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### `xbuild-files` {: #xbuild-files env-var toml}
|
||||||
|
> Platform-specific files in the build environment
|
||||||
|
|
||||||
|
When cross-compiling a package for Android, any dependencies in its [`build-system.requires`](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/) are installed for the build platform. However, some dependencies contain platform-specific files such as headers and static libraries, which must correspond to the target platform.
|
||||||
|
|
||||||
|
This option maps a [normalized](https://packaging.python.org/en/latest/specifications/name-normalization/#name-normalization) package name to a list of paths within that package. If the package is present in the build environment, then a matching version will be downloaded for the target platform, and used to overwrite the given paths within the build environment.
|
||||||
|
|
||||||
|
The default value of this option includes [paths from popular packages](configuration.md#configuration-file).
|
||||||
|
|
||||||
|
Platform-specific environment variables are also available:<br/>
|
||||||
|
`CIBW_XBUILD_FILES_ANDROID`
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
!!! tab examples "pyproject.toml"
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.cibuildwheel.xbuild-files]
|
||||||
|
package1 = ["some/header.h", "some/library.a"]
|
||||||
|
package2 = ["other/header.h"]
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! tab examples "Environment variables"
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
CIBW_XBUILD_FILES: "package1: some/header.h some/library.a; package2: other/header.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### `repair-wheel-command` {: #repair-wheel-command env-var toml}
|
### `repair-wheel-command` {: #repair-wheel-command env-var toml}
|
||||||
> Execute a shell command to repair each built wheel
|
> Execute a shell command to repair each built wheel
|
||||||
|
|
||||||
@@ -923,8 +952,7 @@ Default:
|
|||||||
|
|
||||||
- on Linux: `'auditwheel repair -w {dest_dir} {wheel}'`
|
- on Linux: `'auditwheel repair -w {dest_dir} {wheel}'`
|
||||||
- on macOS: `'delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}'`
|
- on macOS: `'delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}'`
|
||||||
- on Android: There is no default command, but cibuildwheel will add `libc++` to the
|
- on Android: `'auditwheel repair --ldpaths {ldpaths} -w {dest_dir} {wheel}'`
|
||||||
wheel if anything links against it. Setting a command will replace this behavior.
|
|
||||||
- on Pyodide: You can use `pyodide auditwheel repair --libdir /path/to/libraries --output-dir {dest_dir} {wheel}` command to repair the wheel.
|
- on Pyodide: You can use `pyodide auditwheel repair --libdir /path/to/libraries --output-dir {dest_dir} {wheel}` command to repair the wheel.
|
||||||
Unlike other platforms, this command is not set by default as you need to explicitly
|
Unlike other platforms, this command is not set by default as you need to explicitly
|
||||||
specify the library directory. You might not want to use the libraries in the system
|
specify the library directory. You might not want to use the libraries in the system
|
||||||
@@ -939,6 +967,7 @@ The following placeholders must be used inside the command and will be replaced
|
|||||||
- `{wheel}` for the absolute path to the built wheel
|
- `{wheel}` for the absolute path to the built wheel
|
||||||
- `{dest_dir}` for the absolute path of the directory where to create the repaired wheel
|
- `{dest_dir}` for the absolute path of the directory where to create the repaired wheel
|
||||||
- `{delocate_archs}` (macOS only) comma-separated list of architectures in the wheel.
|
- `{delocate_archs}` (macOS only) comma-separated list of architectures in the wheel.
|
||||||
|
- `{ldpaths}` (Android only) colon-separated list of directories to search for external libraries, set by cibuildwheel to include any necessary locations in the NDK. You can add more directories by appending them with a colon separator after the placeholder, or by setting the `AUDITWHEEL_LD_LIBRARY_PATH` environment variable.
|
||||||
|
|
||||||
You can use the `{package}` or `{project}` placeholders in your `repair-wheel-command` to refer to the package being built or the project root, respectively.
|
You can use the `{package}` or `{project}` placeholders in your `repair-wheel-command` to refer to the package being built or the project root, respectively.
|
||||||
|
|
||||||
|
|||||||
+3
-7
@@ -219,13 +219,9 @@ It also requires the following commands to be on the `PATH`:
|
|||||||
|
|
||||||
Android builds will honor the `ANDROID_API_LEVEL` environment variable to set the
|
Android builds will honor the `ANDROID_API_LEVEL` environment variable to set the
|
||||||
minimum supported [API level](https://developer.android.com/tools/releases/platforms)
|
minimum supported [API level](https://developer.android.com/tools/releases/platforms)
|
||||||
for generated wheels. This will default to the minimum API level of the selected Python
|
for generated wheels. This defaults to 24, which is supported by [99% of active
|
||||||
version.
|
devices](https://dl.google.com/android/studio/metadata/distributions.json), and is the first
|
||||||
|
version to support RUNPATH, which auditwheel needs in order to graft external libraries.
|
||||||
If the [`repair-wheel-command`](options.md#repair-wheel-command) adds any libraries to
|
|
||||||
the wheel, then `ANDROID_API_LEVEL` must be at least 24. This is already the default
|
|
||||||
when building for Python 3.14 and later, but you may need to set it when building for
|
|
||||||
Python 3.13.
|
|
||||||
|
|
||||||
### Build frontend support
|
### Build frontend support
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,7 @@ dependencies = [
|
|||||||
"filelock",
|
"filelock",
|
||||||
"humanize",
|
"humanize",
|
||||||
"packaging>=20.9",
|
"packaging>=20.9",
|
||||||
# patchelf is used for Android
|
|
||||||
"patchelf; (sys_platform == 'linux' or sys_platform == 'darwin') and (platform_machine == 'x86_64' or platform_machine == 'arm64' or platform_machine == 'aarch64')",
|
|
||||||
"platformdirs",
|
"platformdirs",
|
||||||
"pyelftools>=0.29",
|
|
||||||
"wheel>=0.33.6",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
+197
-17
@@ -13,7 +13,7 @@ from zipfile import ZipFile
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .test_projects import new_c_project
|
from .test_projects import new_c_project, new_meson_project
|
||||||
from .utils import cibuildwheel_run, expected_wheels
|
from .utils import cibuildwheel_run, expected_wheels
|
||||||
|
|
||||||
pytestmark = pytest.mark.android
|
pytestmark = pytest.mark.android
|
||||||
@@ -125,7 +125,7 @@ def test_frontend_good(tmp_path: Path, build_frontend_env: dict[str, str]) -> No
|
|||||||
tmp_path,
|
tmp_path,
|
||||||
add_env={**cp313_env, **build_frontend_env, "CIBW_TEST_COMMAND": "python -m site"},
|
add_env={**cp313_env, **build_frontend_env, "CIBW_TEST_COMMAND": "python -m site"},
|
||||||
)
|
)
|
||||||
assert wheels == [f"spam-0.1.0-cp313-cp313-android_21_{native_arch.android_abi}.whl"]
|
assert wheels == [f"spam-0.1.0-cp313-cp313-android_24_{native_arch.android_abi}.whl"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("frontend", ["pip"])
|
@pytest.mark.parametrize("frontend", ["pip"])
|
||||||
@@ -136,7 +136,10 @@ def test_frontend_bad(frontend: str, tmp_path: Path, capfd: pytest.CaptureFixtur
|
|||||||
tmp_path,
|
tmp_path,
|
||||||
add_env={**cp313_env, "CIBW_BUILD_FRONTEND": frontend},
|
add_env={**cp313_env, "CIBW_BUILD_FRONTEND": frontend},
|
||||||
)
|
)
|
||||||
assert "Android requires the build frontend to be 'build'" in capfd.readouterr().err
|
assert (
|
||||||
|
f"Android requires the build frontend to be 'build' or 'uv', not '{frontend}'"
|
||||||
|
in capfd.readouterr().err
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@needs_emulator
|
@needs_emulator
|
||||||
@@ -162,7 +165,7 @@ def test_archs(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert wheels == [f"spam-0.1.0-cp313-cp313-android_21_{arch.android_abi}.whl" for arch in archs]
|
assert wheels == [f"spam-0.1.0-cp313-cp313-android_24_{arch.android_abi}.whl" for arch in archs]
|
||||||
|
|
||||||
stdout, stderr = capfd.readouterr()
|
stdout, stderr = capfd.readouterr()
|
||||||
lines = (line for line in stdout.splitlines() if line.startswith("Hello from"))
|
lines = (line for line in stdout.splitlines() if line.startswith("Hello from"))
|
||||||
@@ -435,13 +438,25 @@ def test_verbosity(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
|||||||
|
|
||||||
@needs_emulator
|
@needs_emulator
|
||||||
def test_api_level(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
def test_api_level(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
||||||
project = new_c_project()
|
project = new_c_project(
|
||||||
|
# Check that the the compiler options are set correctly.
|
||||||
|
spam_c_top_level_add=dedent(
|
||||||
|
"""\
|
||||||
|
#if __ANDROID_API__ != 33
|
||||||
|
#error Unexpected API level; the following syntax error will show the actual value:
|
||||||
|
__ANDROID_API__
|
||||||
|
#endif
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
project.files["pyproject.toml"] = dedent(
|
project.files["pyproject.toml"] = dedent(
|
||||||
"""\
|
"""\
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools"]
|
requires = ["setuptools"]
|
||||||
|
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
|
# Test setting API level in pyproject.toml (test_libcxx covers setting
|
||||||
|
# it in the outer environment.)
|
||||||
android.environment.ANDROID_API_LEVEL = "33"
|
android.environment.ANDROID_API_LEVEL = "33"
|
||||||
android.environment.PIP_EXTRA_INDEX_URL = "https://chaquo.com/pypi-13.1"
|
android.environment.PIP_EXTRA_INDEX_URL = "https://chaquo.com/pypi-13.1"
|
||||||
"""
|
"""
|
||||||
@@ -486,17 +501,21 @@ def test_libcxx(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
|||||||
"PATH": non_venv_path,
|
"PATH": non_venv_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Including external libraries requires API level 24.
|
# Including external libraries requires API level 24. This is enforced by auditwheel.
|
||||||
|
cp313_android_21_env = {
|
||||||
|
**cp313_test_env,
|
||||||
|
# Test setting API level in the outer environment (test_api_level covers setting
|
||||||
|
# it in pyproject.toml.)
|
||||||
|
"ANDROID_API_LEVEL": "21",
|
||||||
|
}
|
||||||
with pytest.raises(CalledProcessError):
|
with pytest.raises(CalledProcessError):
|
||||||
cibuildwheel_run(project_dir, add_env=cp313_test_env, output_dir=output_dir)
|
cibuildwheel_run(project_dir, add_env=cp313_android_21_env, output_dir=output_dir)
|
||||||
assert "libc++_shared.so requires ANDROID_API_LEVEL to be at least 24" in capfd.readouterr().err
|
assert (
|
||||||
|
"Grafting libraries with RUNPATH requires API level 24 or higher" in capfd.readouterr().err
|
||||||
wheels = cibuildwheel_run(
|
|
||||||
project_dir,
|
|
||||||
add_env={**cp313_test_env, "ANDROID_API_LEVEL": "24"},
|
|
||||||
output_dir=output_dir,
|
|
||||||
)
|
)
|
||||||
assert len(wheels) == 1
|
|
||||||
|
wheels = cibuildwheel_run(project_dir, add_env=cp313_test_env, output_dir=output_dir)
|
||||||
|
assert wheels == [f"spam-0.1.0-cp313-cp313-android_24_{native_arch.android_abi}.whl"]
|
||||||
names = ZipFile(output_dir / wheels[0]).namelist()
|
names = ZipFile(output_dir / wheels[0]).namelist()
|
||||||
libcxx_names = [
|
libcxx_names = [
|
||||||
name for name in names if re.fullmatch(r"spam\.libs/libc\+\+_shared-[0-9a-f]{8}\.so", name)
|
name for name in names if re.fullmatch(r"spam\.libs/libc\+\+_shared-[0-9a-f]{8}\.so", name)
|
||||||
@@ -504,16 +523,177 @@ def test_libcxx(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
|||||||
assert len(libcxx_names) == 1
|
assert len(libcxx_names) == 1
|
||||||
assert "ham: 1, spam: 0" in capfd.readouterr().out
|
assert "ham: 1, spam: 0" in capfd.readouterr().out
|
||||||
|
|
||||||
# A C package should not include libc++.
|
# A C package should not include libc++, and can therefore use an older API level.
|
||||||
rmtree(project_dir)
|
rmtree(project_dir)
|
||||||
rmtree(output_dir)
|
rmtree(output_dir)
|
||||||
new_c_project().generate(project_dir)
|
new_c_project().generate(project_dir)
|
||||||
wheels = cibuildwheel_run(project_dir, add_env=cp313_env, output_dir=output_dir)
|
wheels = cibuildwheel_run(project_dir, add_env=cp313_android_21_env, output_dir=output_dir)
|
||||||
assert len(wheels) == 1
|
assert wheels == [f"spam-0.1.0-cp313-cp313-android_21_{native_arch.android_abi}.whl"]
|
||||||
for name in ZipFile(output_dir / wheels[0]).namelist():
|
for name in ZipFile(output_dir / wheels[0]).namelist():
|
||||||
assert ".libs" not in name
|
assert ".libs" not in name
|
||||||
|
|
||||||
|
|
||||||
|
@needs_emulator
|
||||||
|
def test_repair_none(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
||||||
|
new_c_project(setup_py_extension_args_add="language='c++'").generate(tmp_path)
|
||||||
|
with pytest.raises(CalledProcessError):
|
||||||
|
cibuildwheel_run(
|
||||||
|
tmp_path,
|
||||||
|
add_env={
|
||||||
|
**cp313_env,
|
||||||
|
"CIBW_REPAIR_WHEEL_COMMAND": "",
|
||||||
|
"CIBW_TEST_COMMAND": "python -c 'import spam'",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert 'dlopen failed: library "libc++_shared.so" not found' in capfd.readouterr().err
|
||||||
|
|
||||||
|
|
||||||
|
def test_repair_ldpaths(tmp_path: Path) -> None:
|
||||||
|
new_c_project().generate(tmp_path)
|
||||||
|
repair_path = tmp_path / "repair.py"
|
||||||
|
repair_path.write_text(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
assert len(sys.argv) == 4, sys.argv
|
||||||
|
ldpaths = list(map(Path, sys.argv[1].split(":")))
|
||||||
|
dest_dir = sys.argv[2]
|
||||||
|
wheel = sys.argv[3]
|
||||||
|
|
||||||
|
for name in ["libc++_shared.so", "libomp.so"]:
|
||||||
|
assert any((lp / name).exists() for lp in ldpaths), (name, ldpaths)
|
||||||
|
|
||||||
|
shutil.copy(wheel, dest_dir)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
repair_path.chmod(0o755)
|
||||||
|
|
||||||
|
wheels = cibuildwheel_run(
|
||||||
|
tmp_path,
|
||||||
|
add_env={
|
||||||
|
**cp313_env,
|
||||||
|
"CIBW_REPAIR_WHEEL_COMMAND": f"{repair_path} {{ldpaths}} {{dest_dir}} {{wheel}}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert wheels == [f"spam-0.1.0-cp313-cp313-android_24_{native_arch.android_abi}.whl"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("script", "error"),
|
||||||
|
[
|
||||||
|
("", "did not produce a wheel"),
|
||||||
|
("touch $dest_dir/one.whl $dest_dir/two.whl", "produced multiple wheels"),
|
||||||
|
("touch $dest_dir/one-0.0.1-py3-none-any.whl", "pure Python wheel was generated"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_repair_error(
|
||||||
|
script: str, error: str, tmp_path: Path, capfd: pytest.CaptureFixture[str]
|
||||||
|
) -> None:
|
||||||
|
new_c_project().generate(tmp_path)
|
||||||
|
repair_path = tmp_path / "repair.sh"
|
||||||
|
repair_path.write_text(
|
||||||
|
dedent(
|
||||||
|
f"""\
|
||||||
|
#!/bin/sh
|
||||||
|
dest_dir=$1
|
||||||
|
{script}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
repair_path.chmod(0o755)
|
||||||
|
|
||||||
|
with pytest.raises(CalledProcessError):
|
||||||
|
cibuildwheel_run(
|
||||||
|
tmp_path,
|
||||||
|
add_env={**cp313_env, "CIBW_REPAIR_WHEEL_COMMAND": f"{repair_path} {{dest_dir}}"},
|
||||||
|
)
|
||||||
|
assert error in capfd.readouterr().err
|
||||||
|
|
||||||
|
|
||||||
|
# This also tests integration with pkgconf, because Meson uses it to find Python.
|
||||||
|
@needs_emulator
|
||||||
|
def test_meson(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
||||||
|
# Alter spam.filter to return the value of a Fortran function.
|
||||||
|
new_meson_project(
|
||||||
|
spam_c_top_level_add="int fortran_func_();",
|
||||||
|
spam_c_function_add="sts = fortran_func_();",
|
||||||
|
project_args_add="'fortran',",
|
||||||
|
extension_args_add="'fortran.f90', link_language: 'fortran',",
|
||||||
|
).generate(tmp_path)
|
||||||
|
|
||||||
|
# TODO: remove once meson-python has a release with Android support.
|
||||||
|
pyproject_path = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_path.write_text(
|
||||||
|
pyproject_path.read_text().replace(
|
||||||
|
"meson-python", "meson-python @ git+https://github.com/mesonbuild/meson-python@main"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add Fortran code.
|
||||||
|
(tmp_path / "fortran.f90").write_text(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
integer*4 function fortran_func()
|
||||||
|
fortran_func = 42
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
script = 'import spam; print(f"result: {spam.filter("")}")'
|
||||||
|
cibuildwheel_run(
|
||||||
|
tmp_path,
|
||||||
|
add_env={**cp313_env, "CIBW_TEST_COMMAND": f"python -c '{script}'"},
|
||||||
|
)
|
||||||
|
assert "result: 42" in capfd.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
@needs_emulator
|
||||||
|
def test_xbuild_files(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
||||||
|
# Verify that we've replaced the correct files by compiling against a non-trivial
|
||||||
|
# function from libnpymath.a.
|
||||||
|
new_c_project(
|
||||||
|
setup_py_add=dedent(
|
||||||
|
"""\
|
||||||
|
import numpy as np
|
||||||
|
np_include = np.get_include()
|
||||||
|
np_lib = f"{np_include}/../lib"
|
||||||
|
libraries.append("npymath")
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
setup_py_extension_args_add="include_dirs=[np_include], library_dirs=[np_lib]",
|
||||||
|
spam_c_top_level_add="#include <numpy/halffloat.h>",
|
||||||
|
spam_c_function_add="sts = npy_float_to_half(42);",
|
||||||
|
).generate(tmp_path)
|
||||||
|
|
||||||
|
(tmp_path / "pyproject.toml").write_text(
|
||||||
|
dedent(
|
||||||
|
"""\
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "numpy==2.3.2"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
script = 'import spam; print(f"result: {spam.filter(""):#x}")'
|
||||||
|
cibuildwheel_run(
|
||||||
|
tmp_path,
|
||||||
|
add_env={
|
||||||
|
**cp313_env,
|
||||||
|
# TODO: remove this once there are official Android NumPy wheels on PyPI.
|
||||||
|
"PIP_EXTRA_INDEX_URL": "https://chaquo.com/pypi-test",
|
||||||
|
"CIBW_ARCHS": "all", # Include both native and non-native archs.
|
||||||
|
"CIBW_TEST_COMMAND": f"python -c '{script}'",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert "result: 0x5140" in capfd.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
@needs_emulator
|
@needs_emulator
|
||||||
def test_setuptools_rust(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
def test_setuptools_rust(tmp_path: Path, capfd: pytest.CaptureFixture[str]) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ archs = ["auto64"]
|
|||||||
|
|
||||||
MESON_BUILD_TEMPLATE = r"""
|
MESON_BUILD_TEMPLATE = r"""
|
||||||
project('spam', 'c',
|
project('spam', 'c',
|
||||||
|
{{ project_args_add | indent(2) }}
|
||||||
version: '0.1.0',
|
version: '0.1.0',
|
||||||
default_options: ['warning_level=2'],
|
default_options: ['warning_level=2'],
|
||||||
)
|
)
|
||||||
@@ -30,6 +31,7 @@ py = import('python').find_installation(pure: false)
|
|||||||
|
|
||||||
py.extension_module('spam',
|
py.extension_module('spam',
|
||||||
'spam.c',
|
'spam.c',
|
||||||
|
{{ extension_args_add | indent(2) }}
|
||||||
install: true,
|
install: true,
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
@@ -39,6 +41,8 @@ def new_meson_project(
|
|||||||
*,
|
*,
|
||||||
spam_c_top_level_add: str = "",
|
spam_c_top_level_add: str = "",
|
||||||
spam_c_function_add: str = "",
|
spam_c_function_add: str = "",
|
||||||
|
project_args_add: str = "",
|
||||||
|
extension_args_add: str = "",
|
||||||
) -> TestProject:
|
) -> TestProject:
|
||||||
project = TestProject()
|
project = TestProject()
|
||||||
|
|
||||||
@@ -54,6 +58,8 @@ def new_meson_project(
|
|||||||
{
|
{
|
||||||
"spam_c_top_level_add": spam_c_top_level_add,
|
"spam_c_top_level_add": spam_c_top_level_add,
|
||||||
"spam_c_function_add": spam_c_function_add,
|
"spam_c_function_add": spam_c_function_add,
|
||||||
|
"project_args_add": project_args_add,
|
||||||
|
"extension_args_add": extension_args_add,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -82,9 +82,10 @@ import sys
|
|||||||
|
|
||||||
from setuptools import setup, Extension
|
from setuptools import setup, Extension
|
||||||
|
|
||||||
|
libraries = []
|
||||||
|
|
||||||
{{ setup_py_add }}
|
{{ setup_py_add }}
|
||||||
|
|
||||||
libraries = []
|
|
||||||
# Emscripten fails if you pass -lc...
|
# Emscripten fails if you pass -lc...
|
||||||
# See: https://github.com/emscripten-core/emscripten/issues/16680
|
# See: https://github.com/emscripten-core/emscripten/issues/16680
|
||||||
if sys.platform.startswith('linux') and "emscripten" not in os.environ.get("_PYTHON_HOST_PLATFORM", ""):
|
if sys.platform.startswith('linux') and "emscripten" not in os.environ.get("_PYTHON_HOST_PLATFORM", ""):
|
||||||
|
|||||||
+7
-6
@@ -177,6 +177,7 @@ def expected_wheels(
|
|||||||
musllinux_versions: list[str] | None = None,
|
musllinux_versions: list[str] | None = None,
|
||||||
macosx_deployment_target: str | None = None,
|
macosx_deployment_target: str | None = None,
|
||||||
iphoneos_deployment_target: str | None = None,
|
iphoneos_deployment_target: str | None = None,
|
||||||
|
android_api_level: int | None = None,
|
||||||
machine_arch: str | None = None,
|
machine_arch: str | None = None,
|
||||||
platform: str | None = None,
|
platform: str | None = None,
|
||||||
python_abi_tags: list[str] | None = None,
|
python_abi_tags: list[str] | None = None,
|
||||||
@@ -201,6 +202,9 @@ def expected_wheels(
|
|||||||
if iphoneos_deployment_target is None:
|
if iphoneos_deployment_target is None:
|
||||||
iphoneos_deployment_target = os.environ.get("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
|
iphoneos_deployment_target = os.environ.get("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
|
||||||
|
|
||||||
|
if android_api_level is None:
|
||||||
|
android_api_level = int(os.environ.get("ANDROID_API_LEVEL", "24"))
|
||||||
|
|
||||||
architectures = [machine_arch]
|
architectures = [machine_arch]
|
||||||
if not single_arch:
|
if not single_arch:
|
||||||
if platform == "linux" and full_auto:
|
if platform == "linux" and full_auto:
|
||||||
@@ -226,6 +230,7 @@ def expected_wheels(
|
|||||||
musllinux_versions=musllinux_versions,
|
musllinux_versions=musllinux_versions,
|
||||||
macosx_deployment_target=macosx_deployment_target,
|
macosx_deployment_target=macosx_deployment_target,
|
||||||
iphoneos_deployment_target=iphoneos_deployment_target,
|
iphoneos_deployment_target=iphoneos_deployment_target,
|
||||||
|
android_api_level=android_api_level,
|
||||||
platform=platform,
|
platform=platform,
|
||||||
python_abi_tags=python_abi_tags,
|
python_abi_tags=python_abi_tags,
|
||||||
include_universal2=include_universal2,
|
include_universal2=include_universal2,
|
||||||
@@ -242,6 +247,7 @@ def _expected_wheels(
|
|||||||
musllinux_versions: list[str] | None,
|
musllinux_versions: list[str] | None,
|
||||||
macosx_deployment_target: str,
|
macosx_deployment_target: str,
|
||||||
iphoneos_deployment_target: str,
|
iphoneos_deployment_target: str,
|
||||||
|
android_api_level: int,
|
||||||
platform: str,
|
platform: str,
|
||||||
python_abi_tags: list[str] | None,
|
python_abi_tags: list[str] | None,
|
||||||
include_universal2: bool,
|
include_universal2: bool,
|
||||||
@@ -387,12 +393,7 @@ def _expected_wheels(
|
|||||||
platform_tags.append(f"macosx_{min_macosx.replace('.', '_')}_universal2")
|
platform_tags.append(f"macosx_{min_macosx.replace('.', '_')}_universal2")
|
||||||
|
|
||||||
elif platform == "android":
|
elif platform == "android":
|
||||||
api_level = {
|
platform_tags = [f"android_{android_api_level}_{machine_arch}"]
|
||||||
"cp313-cp313": 21,
|
|
||||||
"cp314-cp314": 24,
|
|
||||||
"cp315-cp315": 24,
|
|
||||||
}[python_abi_tag]
|
|
||||||
platform_tags = [f"android_{api_level}_{machine_arch}"]
|
|
||||||
|
|
||||||
elif platform == "ios":
|
elif platform == "ios":
|
||||||
if machine_arch == "x86_64":
|
if machine_arch == "x86_64":
|
||||||
|
|||||||
@@ -667,3 +667,51 @@ def test_xbuild_tools_handling(tmp_path: Path, definition: str, expected: list[s
|
|||||||
|
|
||||||
local = options.build_options("cp313-ios_13_0_arm64_iphoneos")
|
local = options.build_options("cp313-ios_13_0_arm64_iphoneos")
|
||||||
assert local.xbuild_tools == expected
|
assert local.xbuild_tools == expected
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_XBUILD_FILES = {
|
||||||
|
"numpy": [
|
||||||
|
"numpy/_core/include/numpy/_numpyconfig.h",
|
||||||
|
"numpy/_core/include/numpy/numpyconfig.h",
|
||||||
|
"numpy/_core/lib/libnpymath.a",
|
||||||
|
"numpy/random/lib/libnpyrandom.a",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("definition", "expected"),
|
||||||
|
[
|
||||||
|
("", DEFAULT_XBUILD_FILES),
|
||||||
|
('xbuild-files = ""', {}),
|
||||||
|
('xbuild-files = "package1: file1"', {"package1": ["file1"]}),
|
||||||
|
(
|
||||||
|
"xbuild-files = \"package1: 'file1 with space'; package2: file2a file2b\"",
|
||||||
|
{"package1": ["file1 with space"], "package2": ["file2a", "file2b"]},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_xbuild_files(tmp_path: Path, definition: str, expected: dict[str, list[str]]) -> None:
|
||||||
|
args = CommandLineArguments.defaults()
|
||||||
|
args.package_dir = tmp_path
|
||||||
|
|
||||||
|
pyproject_toml: Path = tmp_path / "pyproject.toml"
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
[tool.cibuildwheel]
|
||||||
|
{definition}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
options = Options(platform="ios", command_line_arguments=args, env={})
|
||||||
|
assert options.build_options(None).xbuild_files == expected
|
||||||
|
|
||||||
|
pyproject_toml.write_text(
|
||||||
|
"[tool.cibuildwheel.xbuild-files]\n"
|
||||||
|
+ "\n".join(f"{package} = {files}" for package, files in expected.items())
|
||||||
|
)
|
||||||
|
|
||||||
|
options = Options(platform="ios", command_line_arguments=args, env={})
|
||||||
|
assert options.build_options(None).xbuild_files == expected
|
||||||
|
|||||||
@@ -212,6 +212,28 @@ def test_parse_key_value_string() -> None:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_key_value_string_unknown_name() -> None:
|
||||||
|
# Unknown fields are not allowed by default.
|
||||||
|
with pytest.raises(ValueError, match=r"Failed to parse 'key: value'. Unknown field name 'key'"):
|
||||||
|
parse_key_value_string("key: value")
|
||||||
|
|
||||||
|
# Unknown fields can be enabled by passing "*".
|
||||||
|
assert parse_key_value_string(
|
||||||
|
"key: value",
|
||||||
|
kw_arg_names=["*"],
|
||||||
|
) == {
|
||||||
|
"key": ["value"],
|
||||||
|
}
|
||||||
|
|
||||||
|
assert parse_key_value_string(
|
||||||
|
"key1: value1a value1b; key2: value2",
|
||||||
|
kw_arg_names=["*"],
|
||||||
|
) == {
|
||||||
|
"key1": ["value1a", "value1b"],
|
||||||
|
"key2": ["value2"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_flexible_version_comparisons() -> None:
|
def test_flexible_version_comparisons() -> None:
|
||||||
assert FlexibleVersion("2.0") == FlexibleVersion("2")
|
assert FlexibleVersion("2.0") == FlexibleVersion("2")
|
||||||
assert FlexibleVersion("2.0") < FlexibleVersion("2.1")
|
assert FlexibleVersion("2.0") < FlexibleVersion("2.1")
|
||||||
|
|||||||
Reference in New Issue
Block a user