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
rev: v0.13.2
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
name: Run Ruff (lint) on Doc/
args: [--exit-non-zero-on-fix]
@ -30,6 +34,10 @@ repos:
name: Run Ruff (lint) on Tools/wasm/
args: [--exit-non-zero-on-fix, --config=Tools/wasm/.ruff.toml]
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
name: Run Ruff (format) on Doc/
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 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]
@ -140,17 +139,15 @@ def print_env(env: EnvironmentT) -> None:
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
@ -196,14 +193,10 @@ def clean(context: argparse.Namespace, target: str = "all") -> None:
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)
@ -352,18 +345,16 @@ def download(url: str, target_dir: 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
@ -468,8 +459,7 @@ def package_version(prefix_path: Path) -> str:
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"
@ -478,7 +468,7 @@ def lib_platform_files(dirname, names):
):
return names
elif path.parts[-2] == "lib" and path.parts[-1].startswith("python"):
ignored_names = set(
ignored_names = {
name
for name in names
if (
@ -486,7 +476,7 @@ def lib_platform_files(dirname, names):
or name.startswith("_sysconfig_vars_")
or name == "build-details.json"
)
)
}
else:
ignored_names = set()
@ -499,7 +489,9 @@ def lib_non_platform_files(dirname, names):
"""
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()
@ -514,7 +506,8 @@ def create_xcframework(platform: str) -> str:
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 = []
@ -607,7 +600,7 @@ def create_xcframework(platform: str) -> str:
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",
@ -659,7 +652,8 @@ def create_xcframework(platform: str) -> str:
# 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 [
@ -669,7 +663,8 @@ def create_xcframework(platform: str) -> str:
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")
@ -692,18 +687,16 @@ def package(context: argparse.Namespace) -> None:
# 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 = (
@ -757,7 +750,7 @@ def build(context: argparse.Namespace, host: str | None = None) -> None:
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
@ -795,18 +788,16 @@ def test(context: argparse.Namespace, host: str | None = None) -> None:
/ 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(
[
@ -840,7 +831,7 @@ def apple_sim_host(platform_name: str) -> str:
"""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
@ -968,20 +959,29 @@ def parse_args() -> argparse.Namespace:
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(

View file

@ -32,15 +32,15 @@ def select_simulator_device(platform):
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"]
@ -295,7 +295,8 @@ def main():
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."
),
)
@ -336,7 +337,10 @@ def main():
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 "
@ -397,9 +401,9 @@ def main():
/ "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)
@ -411,7 +415,8 @@ def main():
)
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)