gh-141691: Apply ruff rules to Apple folder. (#141694)

Add ruff configuration to run over the Apple build tooling and testbed script.
This commit is contained in:
Russell Keith-Magee 2025-11-19 06:39:21 +08:00 committed by GitHub
parent 4cfa695c95
commit 17636ba48c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 126 additions and 91 deletions

View file

@ -2,6 +2,10 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.13.2 rev: v0.13.2
hooks: hooks:
- id: ruff-check
name: Run Ruff (lint) on Apple/
args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml]
files: ^Apple/
- id: ruff-check - id: ruff-check
name: Run Ruff (lint) on Doc/ name: Run Ruff (lint) on Doc/
args: [--exit-non-zero-on-fix] args: [--exit-non-zero-on-fix]
@ -30,6 +34,10 @@ repos:
name: Run Ruff (lint) on Tools/wasm/ name: Run Ruff (lint) on Tools/wasm/
args: [--exit-non-zero-on-fix, --config=Tools/wasm/.ruff.toml] args: [--exit-non-zero-on-fix, --config=Tools/wasm/.ruff.toml]
files: ^Tools/wasm/ files: ^Tools/wasm/
- id: ruff-format
name: Run Ruff (format) on Apple/
args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml]
files: ^Apple
- id: ruff-format - id: ruff-format
name: Run Ruff (format) on Doc/ name: Run Ruff (format) on Doc/
args: [--check] args: [--check]

22
Apple/.ruff.toml Normal file
View file

@ -0,0 +1,22 @@
extend = "../.ruff.toml" # Inherit the project-wide settings
[format]
preview = true
docstring-code-format = true
[lint]
select = [
"C4", # flake8-comprehensions
"E", # pycodestyle
"F", # pyflakes
"I", # isort
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"PGH", # pygrep-hooks
"PT", # flake8-pytest-style
"PYI", # flake8-pyi
"RUF100", # Ban unused `# noqa` comments
"UP", # pyupgrade
"W", # pycodestyle
"YTT", # flake8-2020
]

View file

@ -46,13 +46,12 @@
import sys import sys
import sysconfig import sysconfig
import time import time
from collections.abc import Sequence from collections.abc import Callable, Sequence
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime, timezone from datetime import datetime, timezone
from os.path import basename, relpath from os.path import basename, relpath
from pathlib import Path from pathlib import Path
from subprocess import CalledProcessError from subprocess import CalledProcessError
from typing import Callable
EnvironmentT = dict[str, str] EnvironmentT = dict[str, str]
ArgsT = Sequence[str | Path] ArgsT = Sequence[str | Path]
@ -140,17 +139,15 @@ def print_env(env: EnvironmentT) -> None:
def apple_env(host: str) -> EnvironmentT: def apple_env(host: str) -> EnvironmentT:
"""Construct an Apple development environment for the given host.""" """Construct an Apple development environment for the given host."""
env = { env = {
"PATH": ":".join( "PATH": ":".join([
[ str(PYTHON_DIR / "Apple/iOS/Resources/bin"),
str(PYTHON_DIR / "Apple/iOS/Resources/bin"), str(subdir(host) / "prefix"),
str(subdir(host) / "prefix"), "/usr/bin",
"/usr/bin", "/bin",
"/bin", "/usr/sbin",
"/usr/sbin", "/sbin",
"/sbin", "/Library/Apple/usr/bin",
"/Library/Apple/usr/bin", ]),
]
),
} }
return env return env
@ -196,14 +193,10 @@ def clean(context: argparse.Namespace, target: str = "all") -> None:
paths.append(target) paths.append(target)
if target in {"all", "hosts", "test"}: if target in {"all", "hosts", "test"}:
paths.extend( paths.extend([
[ path.name
path.name for path in CROSS_BUILD_DIR.glob(f"{context.platform}-testbed.*")
for path in CROSS_BUILD_DIR.glob( ])
f"{context.platform}-testbed.*"
)
]
)
for path in paths: for path in paths:
delete_path(path) delete_path(path)
@ -352,18 +345,16 @@ def download(url: str, target_dir: Path) -> Path:
out_path = target_path / basename(url) out_path = target_path / basename(url)
if not Path(out_path).is_file(): if not Path(out_path).is_file():
run( run([
[ "curl",
"curl", "-Lf",
"-Lf", "--retry",
"--retry", "5",
"5", "--retry-all-errors",
"--retry-all-errors", "-o",
"-o", out_path,
out_path, url,
url, ])
]
)
else: else:
print(f"Using cached version of {basename(url)}") print(f"Using cached version of {basename(url)}")
return out_path return out_path
@ -468,8 +459,7 @@ def package_version(prefix_path: Path) -> str:
def lib_platform_files(dirname, names): 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) path = Path(dirname)
if ( if (
path.parts[-3] == "lib" path.parts[-3] == "lib"
@ -478,7 +468,7 @@ def lib_platform_files(dirname, names):
): ):
return names return names
elif path.parts[-2] == "lib" and path.parts[-1].startswith("python"): elif path.parts[-2] == "lib" and path.parts[-1].startswith("python"):
ignored_names = set( ignored_names = {
name name
for name in names for name in names
if ( if (
@ -486,7 +476,7 @@ def lib_platform_files(dirname, names):
or name.startswith("_sysconfig_vars_") or name.startswith("_sysconfig_vars_")
or name == "build-details.json" or name == "build-details.json"
) )
) }
else: else:
ignored_names = set() ignored_names = set()
@ -499,7 +489,9 @@ def lib_non_platform_files(dirname, names):
""" """
path = Path(dirname) path = Path(dirname)
if path.parts[-2] == "lib" and path.parts[-1].startswith("python"): 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: else:
return set() return set()
@ -514,7 +506,8 @@ def create_xcframework(platform: str) -> str:
package_path.mkdir() package_path.mkdir()
except FileExistsError: except FileExistsError:
raise RuntimeError( 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 ) from None
frameworks = [] frameworks = []
@ -607,7 +600,7 @@ def create_xcframework(platform: str) -> str:
print(f" - {slice_name} binaries") print(f" - {slice_name} binaries")
shutil.copytree(first_path / "bin", slice_path / "bin") 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") print(f" - {slice_name} include files")
shutil.copytree( shutil.copytree(
first_path / "include", first_path / "include",
@ -659,7 +652,8 @@ def create_xcframework(platform: str) -> str:
# statically link those libraries into a Framework, you become # statically link those libraries into a Framework, you become
# responsible for providing a privacy manifest for that framework. # responsible for providing a privacy manifest for that framework.
xcprivacy_file = { xcprivacy_file = {
"OpenSSL": subdir(host_triple) / "prefix/share/OpenSSL.xcprivacy" "OpenSSL": subdir(host_triple)
/ "prefix/share/OpenSSL.xcprivacy"
} }
print(f" - {multiarch} xcprivacy files") print(f" - {multiarch} xcprivacy files")
for module, lib in [ for module, lib in [
@ -669,7 +663,8 @@ def create_xcframework(platform: str) -> str:
shutil.copy( shutil.copy(
xcprivacy_file[lib], xcprivacy_file[lib],
slice_path 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") print(" - build tools")
@ -692,18 +687,16 @@ def package(context: argparse.Namespace) -> None:
# Clone testbed # Clone testbed
print() print()
run( run([
[ sys.executable,
sys.executable, "Apple/testbed",
"Apple/testbed", "clone",
"clone", "--platform",
"--platform", context.platform,
context.platform, "--framework",
"--framework", CROSS_BUILD_DIR / context.platform / "Python.xcframework",
CROSS_BUILD_DIR / context.platform / "Python.xcframework", CROSS_BUILD_DIR / context.platform / "testbed",
CROSS_BUILD_DIR / context.platform / "testbed", ])
]
)
# Build the final archive # Build the final archive
archive_name = ( archive_name = (
@ -757,7 +750,7 @@ def build(context: argparse.Namespace, host: str | None = None) -> None:
package(context) 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.""" """The implementation of the "test" command."""
if host is None: if host is None:
host = context.host host = context.host
@ -795,18 +788,16 @@ def test(context: argparse.Namespace, host: str | None = None) -> None:
/ f"Frameworks/{apple_multiarch(host)}" / f"Frameworks/{apple_multiarch(host)}"
) )
run( run([
[ sys.executable,
sys.executable, "Apple/testbed",
"Apple/testbed", "clone",
"clone", "--platform",
"--platform", context.platform,
context.platform, "--framework",
"--framework", framework_path,
framework_path, testbed_dir,
testbed_dir, ])
]
)
run( run(
[ [
@ -840,7 +831,7 @@ def apple_sim_host(platform_name: str) -> str:
"""Determine the native simulator target for this platform.""" """Determine the native simulator target for this platform."""
for _, slice_parts in HOSTS[platform_name].items(): for _, slice_parts in HOSTS[platform_name].items():
for host_triple in slice_parts: for host_triple in slice_parts:
parts = host_triple.split('-') parts = host_triple.split("-")
if parts[0] == platform.machine() and parts[-1] == "simulator": if parts[0] == platform.machine() and parts[-1] == "simulator":
return host_triple return host_triple
@ -968,20 +959,29 @@ def parse_args() -> argparse.Namespace:
cmd.add_argument( cmd.add_argument(
"--simulator", "--simulator",
help=( help=(
"The name of the simulator to use (eg: 'iPhone 16e'). Defaults to " "The name of the simulator to use (eg: 'iPhone 16e'). "
"the most recently released 'entry level' iPhone device. Device " "Defaults to the most recently released 'entry level' "
"architecture and OS version can also be specified; e.g., " "iPhone device. Device architecture and OS version can also "
"`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would run on " "be specified; e.g., "
"an ARM64 iPhone 16 Pro simulator running iOS 26.0." "`--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 = cmd.add_mutually_exclusive_group()
group.add_argument( group.add_argument(
"--fast-ci", action="store_const", dest="ci_mode", const="fast", "--fast-ci",
help="Add test arguments for GitHub Actions") action="store_const",
dest="ci_mode",
const="fast",
help="Add test arguments for GitHub Actions",
)
group.add_argument( group.add_argument(
"--slow-ci", action="store_const", dest="ci_mode", const="slow", "--slow-ci",
help="Add test arguments for buildbots") action="store_const",
dest="ci_mode",
const="slow",
help="Add test arguments for buildbots",
)
for subcommand in [configure_build, configure_host, build, ci]: for subcommand in [configure_build, configure_host, build, ci]:
subcommand.add_argument( subcommand.add_argument(

View file

@ -32,15 +32,15 @@ def select_simulator_device(platform):
json_data = json.loads(raw_json) json_data = json.loads(raw_json)
if platform == "iOS": if platform == "iOS":
# Any iOS device will do; we'll look for "SE" devices - but the name isn't # Any iOS device will do; we'll look for "SE" devices - but the name
# consistent over time. Older Xcode versions will use "iPhone SE (Nth # isn't consistent over time. Older Xcode versions will use "iPhone SE
# generation)"; As of 2025, they've started using "iPhone 16e". # (Nth generation)"; As of 2025, they've started using "iPhone 16e".
# #
# When Xcode is updated after a new release, new devices will be available # When Xcode is updated after a new release, new devices will be
# and old ones will be dropped from the set available on the latest iOS # available and old ones will be dropped from the set available on the
# version. Select the one with the highest minimum runtime version - this # latest iOS version. Select the one with the highest minimum runtime
# is an indicator of the "newest" released device, which should always be # version - this is an indicator of the "newest" released device, which
# supported on the "most recent" iOS version. # should always be supported on the "most recent" iOS version.
se_simulators = sorted( se_simulators = sorted(
(devicetype["minRuntimeVersion"], devicetype["name"]) (devicetype["minRuntimeVersion"], devicetype["name"])
for devicetype in json_data["devicetypes"] for devicetype in json_data["devicetypes"]
@ -295,7 +295,8 @@ def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=( description=(
"Manages the process of testing an Apple Python project through Xcode." "Manages the process of testing an Apple Python project "
"through Xcode."
), ),
) )
@ -336,7 +337,10 @@ def main():
run = subcommands.add_parser( run = subcommands.add_parser(
"run", "run",
usage="%(prog)s [-h] [--simulator SIMULATOR] -- <test arg> [<test arg> ...]", usage=(
"%(prog)s [-h] [--simulator SIMULATOR] -- "
"<test arg> [<test arg> ...]"
),
description=( description=(
"Run a testbed project. The arguments provided after `--` will be " "Run a testbed project. The arguments provided after `--` will be "
"passed to the running iOS process as if they were arguments to " "passed to the running iOS process as if they were arguments to "
@ -397,9 +401,9 @@ def main():
/ "bin" / "bin"
).is_dir(): ).is_dir():
print( print(
f"Testbed does not contain a compiled Python framework. Use " "Testbed does not contain a compiled Python framework. "
f"`python {sys.argv[0]} clone ...` to create a runnable " f"Use `python {sys.argv[0]} clone ...` to create a "
f"clone of this testbed." "runnable clone of this testbed."
) )
sys.exit(20) sys.exit(20)
@ -411,7 +415,8 @@ def main():
) )
else: else:
print( 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() print()
parser.print_help(sys.stderr) parser.print_help(sys.stderr)