import sys
import sysconfig
import time
-from collections.abc import Sequence
+from collections.abc import Callable, Sequence
from contextlib import contextmanager
from datetime import datetime, timezone
from os.path import basename, relpath
from pathlib import Path
from subprocess import CalledProcessError
-from typing import Callable
EnvironmentT = dict[str, str]
ArgsT = Sequence[str | Path]
def apple_env(host: str) -> EnvironmentT:
"""Construct an Apple development environment for the given host."""
env = {
- "PATH": ":".join(
- [
- str(PYTHON_DIR / "Apple/iOS/Resources/bin"),
- str(subdir(host) / "prefix"),
- "/usr/bin",
- "/bin",
- "/usr/sbin",
- "/sbin",
- "/Library/Apple/usr/bin",
- ]
- ),
+ "PATH": ":".join([
+ str(PYTHON_DIR / "Apple/iOS/Resources/bin"),
+ str(subdir(host) / "prefix"),
+ "/usr/bin",
+ "/bin",
+ "/usr/sbin",
+ "/sbin",
+ "/Library/Apple/usr/bin",
+ ]),
}
return env
paths.append(target)
if target in {"all", "hosts", "test"}:
- paths.extend(
- [
- path.name
- for path in CROSS_BUILD_DIR.glob(
- f"{context.platform}-testbed.*"
- )
- ]
- )
+ paths.extend([
+ path.name
+ for path in CROSS_BUILD_DIR.glob(f"{context.platform}-testbed.*")
+ ])
for path in paths:
delete_path(path)
out_path = target_path / basename(url)
if not Path(out_path).is_file():
- run(
- [
- "curl",
- "-Lf",
- "--retry",
- "5",
- "--retry-all-errors",
- "-o",
- out_path,
- url,
- ]
- )
+ run([
+ "curl",
+ "-Lf",
+ "--retry",
+ "5",
+ "--retry-all-errors",
+ "-o",
+ out_path,
+ url,
+ ])
else:
print(f"Using cached version of {basename(url)}")
return out_path
def lib_platform_files(dirname, names):
- """A file filter that ignores platform-specific files in the lib directory.
- """
+ """A file filter that ignores platform-specific files in lib."""
path = Path(dirname)
if (
path.parts[-3] == "lib"
):
return names
elif path.parts[-2] == "lib" and path.parts[-1].startswith("python"):
- ignored_names = set(
+ ignored_names = {
name
for name in names
if (
or name.startswith("_sysconfig_vars_")
or name == "build-details.json"
)
- )
+ }
else:
ignored_names = set()
"""
path = Path(dirname)
if path.parts[-2] == "lib" and path.parts[-1].startswith("python"):
- return set(names) - lib_platform_files(dirname, names) - {"lib-dynload"}
+ return (
+ set(names) - lib_platform_files(dirname, names) - {"lib-dynload"}
+ )
else:
return set()
package_path.mkdir()
except FileExistsError:
raise RuntimeError(
- f"{platform} XCframework already exists; do you need to run with --clean?"
+ f"{platform} XCframework already exists; do you need to run "
+ "with --clean?"
) from None
frameworks = []
print(f" - {slice_name} binaries")
shutil.copytree(first_path / "bin", slice_path / "bin")
- # Copy the include path (this will be a symlink to the framework headers)
+ # Copy the include path (a symlink to the framework headers)
print(f" - {slice_name} include files")
shutil.copytree(
first_path / "include",
# statically link those libraries into a Framework, you become
# responsible for providing a privacy manifest for that framework.
xcprivacy_file = {
- "OpenSSL": subdir(host_triple) / "prefix/share/OpenSSL.xcprivacy"
+ "OpenSSL": subdir(host_triple)
+ / "prefix/share/OpenSSL.xcprivacy"
}
print(f" - {multiarch} xcprivacy files")
for module, lib in [
shutil.copy(
xcprivacy_file[lib],
slice_path
- / f"lib-{arch}/python{version_tag}/lib-dynload/{module}.xcprivacy",
+ / f"lib-{arch}/python{version_tag}"
+ / f"lib-dynload/{module}.xcprivacy",
)
print(" - build tools")
# Clone testbed
print()
- run(
- [
- sys.executable,
- "Apple/testbed",
- "clone",
- "--platform",
- context.platform,
- "--framework",
- CROSS_BUILD_DIR / context.platform / "Python.xcframework",
- CROSS_BUILD_DIR / context.platform / "testbed",
- ]
- )
+ run([
+ sys.executable,
+ "Apple/testbed",
+ "clone",
+ "--platform",
+ context.platform,
+ "--framework",
+ CROSS_BUILD_DIR / context.platform / "Python.xcframework",
+ CROSS_BUILD_DIR / context.platform / "testbed",
+ ])
# Build the final archive
archive_name = (
package(context)
-def test(context: argparse.Namespace, host: str | None = None) -> None:
+def test(context: argparse.Namespace, host: str | None = None) -> None: # noqa: PT028
"""The implementation of the "test" command."""
if host is None:
host = context.host
/ f"Frameworks/{apple_multiarch(host)}"
)
- run(
- [
- sys.executable,
- "Apple/testbed",
- "clone",
- "--platform",
- context.platform,
- "--framework",
- framework_path,
- testbed_dir,
- ]
- )
+ run([
+ sys.executable,
+ "Apple/testbed",
+ "clone",
+ "--platform",
+ context.platform,
+ "--framework",
+ framework_path,
+ testbed_dir,
+ ])
run(
[
"""Determine the native simulator target for this platform."""
for _, slice_parts in HOSTS[platform_name].items():
for host_triple in slice_parts:
- parts = host_triple.split('-')
+ parts = host_triple.split("-")
if parts[0] == platform.machine() and parts[-1] == "simulator":
return host_triple
cmd.add_argument(
"--simulator",
help=(
- "The name of the simulator to use (eg: 'iPhone 16e'). Defaults to "
- "the most recently released 'entry level' iPhone device. Device "
- "architecture and OS version can also be specified; e.g., "
- "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would run on "
- "an ARM64 iPhone 16 Pro simulator running iOS 26.0."
+ "The name of the simulator to use (eg: 'iPhone 16e'). "
+ "Defaults to the most recently released 'entry level' "
+ "iPhone device. Device architecture and OS version can also "
+ "be specified; e.g., "
+ "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would "
+ "run on an ARM64 iPhone 16 Pro simulator running iOS 26.0."
),
)
group = cmd.add_mutually_exclusive_group()
group.add_argument(
- "--fast-ci", action="store_const", dest="ci_mode", const="fast",
- help="Add test arguments for GitHub Actions")
+ "--fast-ci",
+ action="store_const",
+ dest="ci_mode",
+ const="fast",
+ help="Add test arguments for GitHub Actions",
+ )
group.add_argument(
- "--slow-ci", action="store_const", dest="ci_mode", const="slow",
- help="Add test arguments for buildbots")
+ "--slow-ci",
+ action="store_const",
+ dest="ci_mode",
+ const="slow",
+ help="Add test arguments for buildbots",
+ )
for subcommand in [configure_build, configure_host, build, ci]:
subcommand.add_argument(
json_data = json.loads(raw_json)
if platform == "iOS":
- # Any iOS device will do; we'll look for "SE" devices - but the name isn't
- # consistent over time. Older Xcode versions will use "iPhone SE (Nth
- # generation)"; As of 2025, they've started using "iPhone 16e".
+ # Any iOS device will do; we'll look for "SE" devices - but the name
+ # isn't consistent over time. Older Xcode versions will use "iPhone SE
+ # (Nth generation)"; As of 2025, they've started using "iPhone 16e".
#
- # When Xcode is updated after a new release, new devices will be available
- # and old ones will be dropped from the set available on the latest iOS
- # version. Select the one with the highest minimum runtime version - this
- # is an indicator of the "newest" released device, which should always be
- # supported on the "most recent" iOS version.
+ # When Xcode is updated after a new release, new devices will be
+ # available and old ones will be dropped from the set available on the
+ # latest iOS version. Select the one with the highest minimum runtime
+ # version - this is an indicator of the "newest" released device, which
+ # should always be supported on the "most recent" iOS version.
se_simulators = sorted(
(devicetype["minRuntimeVersion"], devicetype["name"])
for devicetype in json_data["devicetypes"]
parser = argparse.ArgumentParser(
description=(
- "Manages the process of testing an Apple Python project through Xcode."
+ "Manages the process of testing an Apple Python project "
+ "through Xcode."
),
)
run = subcommands.add_parser(
"run",
- usage="%(prog)s [-h] [--simulator SIMULATOR] -- <test arg> [<test arg> ...]",
+ usage=(
+ "%(prog)s [-h] [--simulator SIMULATOR] -- "
+ "<test arg> [<test arg> ...]"
+ ),
description=(
"Run a testbed project. The arguments provided after `--` will be "
"passed to the running iOS process as if they were arguments to "
/ "bin"
).is_dir():
print(
- f"Testbed does not contain a compiled Python framework. Use "
- f"`python {sys.argv[0]} clone ...` to create a runnable "
- f"clone of this testbed."
+ "Testbed does not contain a compiled Python framework. "
+ f"Use `python {sys.argv[0]} clone ...` to create a "
+ "runnable clone of this testbed."
)
sys.exit(20)
)
else:
print(
- f"Must specify test arguments (e.g., {sys.argv[0]} run -- test)"
+ "Must specify test arguments "
+ f"(e.g., {sys.argv[0]} run -- test)"
)
print()
parser.print_help(sys.stderr)