diff --git a/pyproject.toml b/pyproject.toml index 3e18ffe99e2..3e409a54153 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,6 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "!python/**/**.py" = ["ANN"] -"python/servo/**.py" = ["ANN"] "**/test.py" = ["ANN"] "**/*_tests.py" = ["ANN"] "**/tests/**/*.py" = ["ANN"] diff --git a/python/servo/bootstrap_commands.py b/python/servo/bootstrap_commands.py index 41b72f13fb3..da3d215e74d 100644 --- a/python/servo/bootstrap_commands.py +++ b/python/servo/bootstrap_commands.py @@ -40,7 +40,7 @@ class MachCommands(CommandBase): @CommandArgument("--force", "-f", action="store_true", help="Boostrap without confirmation") @CommandArgument("--skip-platform", action="store_true", help="Skip platform bootstrapping.") @CommandArgument("--skip-lints", action="store_true", help="Skip tool necessary for linting.") - def bootstrap(self, force=False, skip_platform=False, skip_lints=False) -> int: + def bootstrap(self, force: bool = False, skip_platform: bool = False, skip_lints: bool = False) -> int: # Note: This entry point isn't actually invoked by ./mach bootstrap. # ./mach bootstrap calls mach_bootstrap.bootstrap_command_only so that # it can install dependencies without needing mach's dependencies @@ -57,7 +57,7 @@ class MachCommands(CommandBase): category="bootstrap", ) @CommandArgument("--force", "-f", action="store_true", help="Boostrap without confirmation") - def bootstrap_gstreamer(self, force=False) -> int: + def bootstrap_gstreamer(self, force: bool = False) -> int: try: servo.platform.get().bootstrap_gstreamer(force) except NotImplementedError as exception: @@ -66,7 +66,7 @@ class MachCommands(CommandBase): return 0 @Command("update-hsts-preload", description="Download the HSTS preload list", category="bootstrap") - def bootstrap_hsts_preload(self, force=False) -> None: + def bootstrap_hsts_preload(self, force: bool = False) -> None: preload_filename = "hsts_preload.fstmap" preload_path = path.join(self.context.topdir, "resources") @@ -104,7 +104,7 @@ class MachCommands(CommandBase): description="Download the public domains list and update resources/public_domains.txt", category="bootstrap", ) - def bootstrap_pub_suffix(self, force=False) -> None: + def bootstrap_pub_suffix(self, force: bool = False) -> None: list_url = "https://publicsuffix.org/list/public_suffix_list.dat" dst_filename = path.join(self.context.topdir, "resources", "public_domains.txt") not_implemented_case = re.compile(r"^[^*]+\*") diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index cf90776c4e9..f70d00af536 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -18,7 +18,7 @@ import subprocess import sys from time import time -from typing import Optional, List, Dict, Union +from typing import Optional, Union, Any from mach.decorators import ( CommandArgument, @@ -54,7 +54,7 @@ SUPPORTED_TSAN_TARGETS = [ ] -def get_rustc_llvm_version() -> Optional[List[int]]: +def get_rustc_llvm_version() -> Optional[list[int]]: """Determine the LLVM version of `rustc` and return it as a List[major, minor, patch, ...] In some cases we want to ensure that the LLVM version of rustc and clang match, e.g. @@ -94,14 +94,14 @@ class MachCommands(CommandBase): def build( self, build_type: BuildType, - jobs=None, - params=None, - no_package=False, - verbose=False, - very_verbose=False, + jobs: str | None = None, + params: list[str] | None = None, + no_package: bool = False, + verbose: bool = False, + very_verbose: bool = False, sanitizer: SanitizerKind = SanitizerKind.NONE, - flavor=None, - **kwargs, + flavor: str | None = None, + **kwargs: Any, ) -> int: opts = params or [] @@ -200,7 +200,7 @@ class MachCommands(CommandBase): @CommandArgument("--manifest-path", default=None, help="Path to the manifest to the package to clean") @CommandArgument("--verbose", "-v", action="store_true", help="Print verbose output") @CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to Cargo") - def clean(self, manifest_path=None, params=[], verbose=False) -> None: + def clean(self, manifest_path: str | None = None, params: list[str] = [], verbose: bool = False) -> None: self.ensure_bootstrapped() virtualenv_path = path.join(self.get_top_dir(), ".venv") @@ -215,7 +215,7 @@ class MachCommands(CommandBase): return check_call(["cargo", "clean"] + opts, env=self.build_env(), verbose=verbose) def build_sanitizer_env( - self, env: Dict, opts: List[str], kwargs, target_triple: str, sanitizer: SanitizerKind = SanitizerKind.NONE + self, env: dict, opts: list[str], kwargs: Any, target_triple: str, sanitizer: SanitizerKind = SanitizerKind.NONE ) -> None: if sanitizer.is_none(): return diff --git a/python/servo/command_base.py b/python/servo/command_base.py index 522917abd84..74a14d018e3 100644 --- a/python/servo/command_base.py +++ b/python/servo/command_base.py @@ -19,6 +19,7 @@ import shutil import subprocess import sys import tarfile +from tarfile import TarInfo import urllib import zipfile import urllib.error @@ -28,7 +29,8 @@ from enum import Enum from glob import glob from os import path from subprocess import PIPE, CompletedProcess -from typing import Any, Dict, List, Optional, Union, LiteralString, cast +from typing import Any, Optional, Union, LiteralString, cast +from collections.abc import Generator, Callable from xml.etree.ElementTree import XML import toml @@ -93,7 +95,7 @@ class BuildType: @contextlib.contextmanager -def cd(new_path: str): +def cd(new_path: str) -> Generator: """Context manager for changing the current working directory""" previous_path = os.getcwd() try: @@ -104,7 +106,7 @@ def cd(new_path: str): @contextlib.contextmanager -def setlocale(name: str): +def setlocale(name: str) -> Generator: """Context manager for changing the current locale""" saved_locale = locale.setlocale(locale.LC_ALL) try: @@ -113,7 +115,7 @@ def setlocale(name: str): locale.setlocale(locale.LC_ALL, saved_locale) -def find_dep_path_newest(package, bin_path): +def find_dep_path_newest(package: str, bin_path: str) -> str | None: deps_path = path.join(path.split(bin_path)[0], "build") candidates = [] with cd(deps_path): @@ -126,12 +128,12 @@ def find_dep_path_newest(package, bin_path): return None -def archive_deterministically(dir_to_archive, dest_archive, prepend_path=None) -> None: +def archive_deterministically(dir_to_archive: str, dest_archive: str, prepend_path: str | None = None) -> None: """Create a .tar.gz archive in a deterministic (reproducible) manner. See https://reproducible-builds.org/docs/archives/ for more details.""" - def reset(tarinfo): + def reset(tarinfo: TarInfo) -> TarInfo: """Helper to reset owner/group and modification time for tar entries""" tarinfo.uid = tarinfo.gid = 0 tarinfo.uname = tarinfo.gname = "root" @@ -177,7 +179,7 @@ def archive_deterministically(dir_to_archive, dest_archive, prepend_path=None) - os.rename(temp_file, dest_archive) -def call(*args, **kwargs) -> int: +def call(*args: Any, **kwargs: Any) -> int: """Wrap `subprocess.call`, printing the command if verbose=True.""" verbose = kwargs.pop("verbose", False) if verbose: @@ -188,7 +190,7 @@ def call(*args, **kwargs) -> int: return subprocess.call(*args, **kwargs) -def check_output(*args, **kwargs) -> Union[str, bytes]: +def check_output(*args: Any, **kwargs: Any) -> Union[str, bytes]: """Wrap `subprocess.call`, printing the command if verbose=True.""" verbose = kwargs.pop("verbose", False) if verbose: @@ -199,7 +201,7 @@ def check_output(*args, **kwargs) -> Union[str, bytes]: return subprocess.check_output(*args, **kwargs) -def check_call(*args, **kwargs) -> None: +def check_call(*args: Any, **kwargs: Any) -> None: """Wrap `subprocess.check_call`, printing the command if verbose=True. Also fix any unicode-containing `env`, for subprocess""" @@ -239,10 +241,10 @@ def is_linux() -> bool: class BuildNotFound(Exception): - def __init__(self, message) -> None: + def __init__(self, message: str) -> None: self.message = message - def __str__(self): + def __str__(self) -> str: return self.message @@ -253,7 +255,7 @@ class CommandBase(object): target: BuildTarget - def __init__(self, context) -> None: + def __init__(self, context: Any) -> None: self.context = context self.enable_media = False self.features = [] @@ -321,7 +323,7 @@ class CommandBase(object): _rust_toolchain = None - def rust_toolchain(self): + def rust_toolchain(self) -> str: if self._rust_toolchain: return self._rust_toolchain @@ -329,7 +331,7 @@ class CommandBase(object): self._rust_toolchain = toml.load(toolchain_file)["toolchain"]["channel"] return self._rust_toolchain - def get_top_dir(self): + def get_top_dir(self) -> str: return self.context.topdir def get_binary_path(self, build_type: BuildType, sanitizer: SanitizerKind = SanitizerKind.NONE) -> str: @@ -356,7 +358,7 @@ class CommandBase(object): if os.path.exists(mounted_volume): self.detach_volume(mounted_volume) - def mount_dmg(self, dmg_path) -> None: + def mount_dmg(self, dmg_path: str) -> None: print("Mounting dmg {}".format(dmg_path)) try: subprocess.check_call(["hdiutil", "attach", dmg_path]) @@ -394,7 +396,7 @@ class CommandBase(object): return path.join(destination_folder, "servo", "servo") return path.join(destination_folder, "servo") - def get_nightly_binary_path(self, nightly_date) -> str | None: + def get_nightly_binary_path(self, nightly_date: str | None) -> str | None: if nightly_date is None: return if not nightly_date: @@ -462,7 +464,7 @@ class CommandBase(object): return self.get_executable(destination_folder) - def msvc_package_dir(self, package) -> str: + def msvc_package_dir(self, package: str) -> str: return servo.platform.windows.get_dependency_dir(package) def build_env(self) -> dict[str, str]: @@ -531,8 +533,11 @@ class CommandBase(object): @staticmethod def common_command_arguments( - build_configuration=False, build_type=False, binary_selection=False, package_configuration=False - ): + build_configuration: bool = False, + build_type: bool = False, + binary_selection: bool = False, + package_configuration: bool = False, + ) -> Callable: decorators = [] if build_type or binary_selection: decorators += [ @@ -654,8 +659,8 @@ class CommandBase(object): CommandArgument("--nightly", "-n", default=None, help="Specify a YYYY-MM-DD nightly build to run"), ] - def decorator_function(original_function): - def configuration_decorator(self, *args, **kwargs): + def decorator_function(original_function: Callable) -> Callable: + def configuration_decorator(self: CommandBase, *args: Any, **kwargs: Any) -> Callable: if build_type or binary_selection: # If `build_type` already exists in kwargs we are doing a recursive dispatch. if "build_type" not in kwargs: @@ -680,7 +685,10 @@ class CommandBase(object): kwargs["servo_binary"] = ( kwargs.get("bin") or self.get_nightly_binary_path(kwargs.get("nightly")) - or self.get_binary_path(kwargs.get("build_type"), sanitizer=kwargs.get("sanitizer")) + or self.get_binary_path( + cast(BuildType, kwargs.get("build_type")), + sanitizer=cast(SanitizerKind, kwargs.get("sanitizer")), + ) ) kwargs.pop("bin") kwargs.pop("nightly") @@ -699,8 +707,8 @@ class CommandBase(object): return decorator_function @staticmethod - def allow_target_configuration(original_function): - def target_configuration_decorator(self, *args, **kwargs): + def allow_target_configuration(original_function: Callable) -> Callable: + def target_configuration_decorator(self: CommandBase, *args: Any, **kwargs: Any) -> Callable: self.configure_build_target(kwargs, suppress_log=True) kwargs.pop("target", False) kwargs.pop("android", False) @@ -741,7 +749,7 @@ class CommandBase(object): else: return BuildType.custom(profile) - def configure_build_target(self, kwargs: Dict[str, Any], suppress_log: bool = False) -> None: + def configure_build_target(self, kwargs: dict[str, Any], suppress_log: bool = False) -> None: if hasattr(self.context, "target"): # This call is for a dispatched command and we've already configured # the target, so just use it. @@ -778,7 +786,7 @@ class CommandBase(object): if self.target.is_cross_build() and not suppress_log: print(f"Targeting '{self.target.triple()}' for cross-compilation") - def is_media_enabled(self, media_stack: Optional[str]): + def is_media_enabled(self, media_stack: Optional[str]) -> bool: """Determine whether media is enabled based on the value of the build target platform and the value of the '--media-stack' command-line argument. Returns true if media is enabled.""" @@ -803,16 +811,16 @@ class CommandBase(object): def run_cargo_build_like_command( self, command: str, - cargo_args: List[str], - env=None, - verbose=False, - debug_mozjs=False, - with_debug_assertions=False, - with_frame_pointer=False, - use_crown=False, - capture_output=False, + cargo_args: list[str], + env: dict[str, Any] | None = None, + verbose: bool = False, + debug_mozjs: bool = False, + with_debug_assertions: bool = False, + with_frame_pointer: bool = False, + use_crown: bool = False, + capture_output: bool = False, target_override: Optional[str] = None, - **_kwargs, + **_kwargs: Any, ) -> CompletedProcess[bytes] | int: env = cast(dict[str, str], env or self.build_env()) @@ -888,14 +896,14 @@ class CommandBase(object): return call(["cargo", command] + args + cargo_args, env=env, verbose=verbose) - def android_adb_path(self, env) -> LiteralString: + def android_adb_path(self, env: dict[str, Any]) -> LiteralString: if "ANDROID_SDK_ROOT" in env: sdk_adb = path.join(env["ANDROID_SDK_ROOT"], "platform-tools", "adb") if path.exists(sdk_adb): return sdk_adb return "adb" - def android_emulator_path(self, env) -> LiteralString: + def android_emulator_path(self, env: dict[str, Any]) -> LiteralString: if "ANDROID_SDK_ROOT" in env: sdk_adb = path.join(env["ANDROID_SDK_ROOT"], "emulator", "emulator") if path.exists(sdk_adb): @@ -919,7 +927,7 @@ class CommandBase(object): if self.target.triple() not in installed_targets: check_call(["rustup", "target", "add", self.target.triple()], cwd=self.context.topdir) - def ensure_clobbered(self, target_dir=None) -> None: + def ensure_clobbered(self, target_dir: str | None = None) -> None: if target_dir is None: target_dir = util.get_target_dir() auto = True if os.environ.get("AUTOCLOBBER", False) else False diff --git a/python/servo/devenv_commands.py b/python/servo/devenv_commands.py index 87ad307ccff..9739272905d 100644 --- a/python/servo/devenv_commands.py +++ b/python/servo/devenv_commands.py @@ -9,6 +9,7 @@ from subprocess import CompletedProcess import json +from typing import Any from mach.decorators import ( Command, @@ -27,7 +28,7 @@ class MachCommands(CommandBase): "params", default=None, nargs="...", help="Command-line arguments to be passed through to cargo check" ) @CommandBase.common_command_arguments(build_configuration=True, build_type=False) - def check(self, params, **kwargs) -> int: + def check(self, params: list[str], **kwargs: Any) -> int: if not params: params = [] @@ -42,7 +43,7 @@ class MachCommands(CommandBase): @Command("rustc", description="Run the Rust compiler", category="devenv") @CommandArgument("params", default=None, nargs="...", help="Command-line arguments to be passed through to rustc") - def rustc(self, params) -> int: + def rustc(self, params: list[str]) -> int: if params is None: params = [] @@ -54,7 +55,7 @@ class MachCommands(CommandBase): "params", default=None, nargs="...", help="Command-line arguments to be passed through to cargo-fix" ) @CommandBase.common_command_arguments(build_configuration=True, build_type=False) - def cargo_fix(self, params, **kwargs) -> int: + def cargo_fix(self, params: list[str], **kwargs: Any) -> int: if not params: params = [] @@ -73,7 +74,7 @@ class MachCommands(CommandBase): help="Emit the clippy warnings in the Github Actions annotations format", ) @CommandBase.common_command_arguments(build_configuration=True, build_type=False) - def cargo_clippy(self, params, github_annotations=False, **kwargs) -> int: + def cargo_clippy(self, params: list[str], github_annotations: bool = False, **kwargs: Any) -> int: if not params: params = [] diff --git a/python/servo/gstreamer.py b/python/servo/gstreamer.py index 763f294298d..593443a1732 100644 --- a/python/servo/gstreamer.py +++ b/python/servo/gstreamer.py @@ -11,7 +11,7 @@ import os.path import shutil import subprocess import sys -from typing import Set +from collections.abc import Set # This file is called as a script from components/servo/build.rs, so # we need to explicitly modify the search path here. diff --git a/python/servo/mutation/init.py b/python/servo/mutation/init.py index f1a2c5ee077..f58449a3530 100644 --- a/python/servo/mutation/init.py +++ b/python/servo/mutation/init.py @@ -18,7 +18,7 @@ import random test_summary = {test.Status.KILLED: 0, test.Status.SURVIVED: 0, test.Status.SKIPPED: 0, test.Status.UNEXPECTED: 0} -def get_folders_list(path): +def get_folders_list(path: str) -> list[str]: folder_list = [] for filename in listdir(path): if isdir(join(path, filename)): diff --git a/python/servo/mutation/mutator.py b/python/servo/mutation/mutator.py index aa61ef677ea..72ed9c7796b 100644 --- a/python/servo/mutation/mutator.py +++ b/python/servo/mutation/mutator.py @@ -11,14 +11,14 @@ import fileinput import re from re import Match import random -from typing import Iterator +from collections.abc import Iterator def is_comment(line: str) -> Match[str] | None: return re.search(r"\/\/.*", line) -def init_variables(if_blocks): +def init_variables(if_blocks: list[int]) -> tuple[int, int, int, int, int]: random_index = random.randint(0, len(if_blocks) - 1) start_counter = 0 end_counter = 0 @@ -27,7 +27,7 @@ def init_variables(if_blocks): return random_index, start_counter, end_counter, lines_to_delete, line_to_mutate -def deleteStatements(file_name, line_numbers) -> None: +def deleteStatements(file_name: str, line_numbers: list[int]) -> None: for line in fileinput.input(file_name, inplace=True): if fileinput.lineno() not in line_numbers: print(line.rstrip()) @@ -38,7 +38,7 @@ class Strategy: self._strategy_name = "" self._replace_strategy = {} - def mutate(self, file_name): + def mutate(self, file_name: str) -> int: line_numbers = [] for line in fileinput.input(file_name): if not is_comment(line) and re.search(self._replace_strategy["regex"], line): @@ -140,7 +140,7 @@ class DeleteIfBlock(Strategy): self.if_block = r"^\s+if\s(.+)\s\{" self.else_block = r"\selse(.+)\{" - def mutate(self, file_name): + def mutate(self, file_name: str) -> int: code_lines = [] if_blocks = [] for line in fileinput.input(file_name): @@ -192,8 +192,8 @@ def get_strategies() -> Iterator[Strategy]: class Mutator: - def __init__(self, strategy) -> None: + def __init__(self, strategy: Strategy) -> None: self._strategy = strategy - def mutate(self, file_name): + def mutate(self, file_name: str) -> int: return self._strategy.mutate(file_name) diff --git a/python/servo/package_commands.py b/python/servo/package_commands.py index 9921877c6eb..eda2676eab9 100644 --- a/python/servo/package_commands.py +++ b/python/servo/package_commands.py @@ -10,7 +10,7 @@ from datetime import datetime import random import time -from typing import List +from collections.abc import Generator from github import Github import hashlib @@ -68,14 +68,14 @@ PACKAGES = { } -def packages_for_platform(platform): +def packages_for_platform(platform: str) -> Generator[str]: target_dir = get_target_dir() for package in PACKAGES[platform]: yield path.join(target_dir, package) -def listfiles(directory) -> list[str]: +def listfiles(directory: str) -> list[str]: return [f for f in os.listdir(directory) if path.isfile(path.join(directory, f))] @@ -85,7 +85,7 @@ def copy_windows_dependencies(binary_path: str, destination: str) -> None: shutil.copy(path.join(binary_path, f), destination) -def check_call_with_randomized_backoff(args: List[str], retries: int) -> int: +def check_call_with_randomized_backoff(args: list[str], retries: int) -> int: """ Run the given command-line arguments via `subprocess.check_call()`. If the command fails sleep for a random number of seconds between 2 and 5 and then try to the command @@ -111,7 +111,9 @@ class PackageCommands(CommandBase): @CommandArgument("--target", "-t", default=None, help="Package for given target platform") @CommandBase.common_command_arguments(build_configuration=False, build_type=True, package_configuration=True) @CommandBase.allow_target_configuration - def package(self, build_type: BuildType, flavor=None, sanitizer: SanitizerKind = SanitizerKind.NONE) -> int | None: + def package( + self, build_type: BuildType, flavor: str | None = None, sanitizer: SanitizerKind = SanitizerKind.NONE + ) -> int | None: env = self.build_env() binary_path = self.get_binary_path(build_type, sanitizer=sanitizer) dir_to_root = self.get_top_dir() @@ -398,10 +400,10 @@ class PackageCommands(CommandBase): def install( self, build_type: BuildType, - emulator=False, - usb=False, + emulator: bool = False, + usb: bool = False, sanitizer: SanitizerKind = SanitizerKind.NONE, - flavor=None, + flavor: str | None = None, ) -> int: env = self.build_env() try: @@ -453,10 +455,10 @@ class PackageCommands(CommandBase): @CommandArgument( "--github-release-id", default=None, type=int, help="The github release to upload the nightly builds." ) - def upload_nightly(self, platform, secret_from_environment, github_release_id) -> int: + def upload_nightly(self, platform: str, secret_from_environment: bool, github_release_id: int | None) -> int: import boto3 - def get_s3_secret(): + def get_s3_secret() -> tuple: aws_access_key = None aws_secret_access_key = None if secret_from_environment: @@ -465,13 +467,13 @@ class PackageCommands(CommandBase): aws_secret_access_key = secret["aws_secret_access_key"] return (aws_access_key, aws_secret_access_key) - def nightly_filename(package, timestamp) -> str: + def nightly_filename(package: str, timestamp: datetime) -> str: return "{}-{}".format( timestamp.isoformat() + "Z", # The `Z` denotes UTC path.basename(package), ) - def upload_to_github_release(platform, package: str, package_hash: str) -> None: + def upload_to_github_release(platform: str, package: str, package_hash: str) -> None: if not github_release_id: return @@ -488,7 +490,7 @@ class PackageCommands(CommandBase): package_hash_fileobj, package_hash_fileobj.getbuffer().nbytes, name=f"{asset_name}.sha256" ) - def upload_to_s3(platform, package: str, package_hash: str, timestamp: datetime) -> None: + def upload_to_s3(platform: str, package: str, package_hash: str, timestamp: datetime) -> None: (aws_access_key, aws_secret_access_key) = get_s3_secret() s3 = boto3.client("s3", aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_access_key) diff --git a/python/servo/platform/__init__.py b/python/servo/platform/__init__.py index e996ac2ccdd..077b668a529 100644 --- a/python/servo/platform/__init__.py +++ b/python/servo/platform/__init__.py @@ -47,7 +47,7 @@ def host_triple() -> str: return f"{cpu_type}-{os_type}" -def get(): +def get(): # noqa # pylint: disable=global-statement global __platform__ if __platform__: diff --git a/python/servo/platform/build_target.py b/python/servo/platform/build_target.py index c0341816634..8bdd0d9fb4b 100644 --- a/python/servo/platform/build_target.py +++ b/python/servo/platform/build_target.py @@ -20,7 +20,7 @@ from enum import Enum from os import path from packaging.version import parse as parse_version -from typing import Any, Dict, Optional +from typing import Any, Optional import servo.platform import servo.util as util @@ -70,7 +70,7 @@ class BuildTarget(object): def binary_name(self) -> str: return f"servo{servo.platform.get().executable_suffix()}" - def configure_build_environment(self, env: Dict[str, str], config: Dict[str, Any], topdir: pathlib.Path) -> None: + def configure_build_environment(self, env: dict[str, str], config: dict[str, Any], topdir: pathlib.Path) -> None: pass def is_cross_build(self) -> bool: @@ -88,7 +88,7 @@ class CrossBuildTarget(BuildTarget): class AndroidTarget(CrossBuildTarget): DEFAULT_TRIPLE = "aarch64-linux-android" - def ndk_configuration(self) -> Dict[str, str]: + def ndk_configuration(self) -> dict[str, str]: target = self.triple() config = {} if target == "armv7-linux-androideabi": @@ -125,7 +125,7 @@ class AndroidTarget(CrossBuildTarget): return config - def configure_build_environment(self, env: Dict[str, str], config: Dict[str, Any], topdir: pathlib.Path) -> None: + def configure_build_environment(self, env: dict[str, str], config: dict[str, Any], topdir: pathlib.Path) -> None: # Paths to Android build tools: if config["android"]["sdk"]: env["ANDROID_SDK_ROOT"] = config["android"]["sdk"] @@ -296,7 +296,7 @@ class AndroidTarget(CrossBuildTarget): class OpenHarmonyTarget(CrossBuildTarget): DEFAULT_TRIPLE = "aarch64-unknown-linux-ohos" - def configure_build_environment(self, env: Dict[str, str], config: Dict[str, Any], topdir: pathlib.Path) -> None: + def configure_build_environment(self, env: dict[str, str], config: dict[str, Any], topdir: pathlib.Path) -> None: # Paths to OpenHarmony SDK and build tools: # Note: `OHOS_SDK_NATIVE` is the CMake variable name the `hvigor` build-system # uses for the native directory of the SDK, so we use the same name to be consistent. diff --git a/python/servo/platform/linux.py b/python/servo/platform/linux.py index 25a55d7caa1..8464a338e81 100644 --- a/python/servo/platform/linux.py +++ b/python/servo/platform/linux.py @@ -11,7 +11,7 @@ import distro import os import subprocess import shutil -from typing import Optional +from typing import Optional, Any from .base import Base from .build_target import BuildTarget @@ -164,7 +164,7 @@ GSTREAMER_URL = ( class Linux(Base): - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args: str, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.is_linux = True self.distro = distro.name() diff --git a/python/servo/platform/macos.py b/python/servo/platform/macos.py index 89a95b985b5..020efdde47d 100644 --- a/python/servo/platform/macos.py +++ b/python/servo/platform/macos.py @@ -10,7 +10,7 @@ import os import subprocess import tempfile -from typing import Optional +from typing import Optional, Any from .. import util from .base import Base @@ -24,7 +24,7 @@ GSTREAMER_ROOT = "/Library/Frameworks/GStreamer.framework/Versions/1.0" class MacOS(Base): - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args: str, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.is_macos = True diff --git a/python/servo/post_build_commands.py b/python/servo/post_build_commands.py index 40bb09038ab..f0ecb34ab45 100644 --- a/python/servo/post_build_commands.py +++ b/python/servo/post_build_commands.py @@ -13,7 +13,7 @@ import os.path as path import subprocess from subprocess import CompletedProcess from shutil import copy2 -from typing import List +from typing import Any import mozdebug @@ -36,7 +36,7 @@ from servo.platform.build_target import is_android ANDROID_APP_NAME = "org.servo.servoshell" -def read_file(filename, if_exists=False) -> str | None: +def read_file(filename: str, if_exists: bool = False) -> str | None: if if_exists and not path.exists(filename): return None with open(filename) as f: @@ -44,7 +44,7 @@ def read_file(filename, if_exists=False) -> str | None: # Copied from Python 3.3+'s shlex.quote() -def shell_quote(arg: str): +def shell_quote(arg: str) -> str: # use single quotes, and put single quotes into double quotes # the string $'b is then quoted as '$'"'"'b' return "'" + arg.replace("'", "'\"'\"'") + "'" @@ -75,26 +75,26 @@ class PostBuildCommands(CommandBase): def run( self, servo_binary: str, - params, - debugger=False, - debugger_cmd=None, - headless=False, - software=False, - emulator=False, - usb=False, + params: list[str], + debugger: bool = False, + debugger_cmd: str | None = None, + headless: bool = False, + software: bool = False, + emulator: bool = False, + usb: bool = False, ) -> int | None: return self._run(servo_binary, params, debugger, debugger_cmd, headless, software, emulator, usb) def _run( self, servo_binary: str, - params, - debugger=False, - debugger_cmd=None, - headless=False, - software=False, - emulator=False, - usb=False, + params: list[str], + debugger: bool = False, + debugger_cmd: str | None = None, + headless: bool = False, + software: bool = False, + emulator: bool = False, + usb: bool = False, ) -> int | None: env = self.build_env() env["RUST_BACKTRACE"] = "1" @@ -194,7 +194,7 @@ class PostBuildCommands(CommandBase): @Command("android-emulator", description="Run the Android emulator", category="post-build") @CommandArgument("args", nargs="...", help="Command-line arguments to be passed through to the emulator") - def android_emulator(self, args=None) -> int: + def android_emulator(self, args: list[str] | None = None) -> int: if not args: args = [] print("AVDs created by `./mach bootstrap-android` are servo-arm and servo-x86.") @@ -204,7 +204,7 @@ class PostBuildCommands(CommandBase): @Command("rr-record", description="Run Servo whilst recording execution with rr", category="post-build") @CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to Servo") @CommandBase.common_command_arguments(binary_selection=True) - def rr_record(self, servo_binary: str, params=[]) -> None: + def rr_record(self, servo_binary: str, params: list[str] = []) -> None: env = self.build_env() env["RUST_BACKTRACE"] = "1" @@ -235,7 +235,7 @@ class PostBuildCommands(CommandBase): @Command("doc", description="Generate documentation", category="post-build") @CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to cargo doc") @CommandBase.common_command_arguments(build_configuration=True, build_type=False) - def doc(self, params: List[str], **kwargs) -> CompletedProcess[bytes] | int | None: + def doc(self, params: list[str], **kwargs: Any) -> CompletedProcess[bytes] | int | None: self.ensure_bootstrapped() docs = path.join(servo.util.get_target_dir(), "doc") diff --git a/python/servo/testing_commands.py b/python/servo/testing_commands.py index 4ed241aea49..fff3c9b5526 100644 --- a/python/servo/testing_commands.py +++ b/python/servo/testing_commands.py @@ -118,7 +118,7 @@ class MachCommands(CommandBase): DEFAULT_RENDER_MODE = "cpu" HELP_RENDER_MODE = "Value can be 'cpu', 'gpu' or 'both' (default " + DEFAULT_RENDER_MODE + ")" - def __init__(self, context) -> None: + def __init__(self, context: Any) -> None: CommandBase.__init__(self, context) if not hasattr(self.context, "built_tests"): self.context.built_tests = False @@ -127,7 +127,7 @@ class MachCommands(CommandBase): @CommandArgument("--base", default=None, help="the base URL for testcases") @CommandArgument("--date", default=None, help="the datestamp for the data") @CommandArgument("--submit", "-a", default=False, action="store_true", help="submit the data to perfherder") - def test_perf(self, base=None, date=None, submit=False) -> int: + def test_perf(self, base: str | None = None, date: str | None = None, submit: bool = False) -> int: env = self.build_env() cmd = ["bash", "test_perf.sh"] if base: @@ -147,7 +147,13 @@ class MachCommands(CommandBase): ) @CommandBase.common_command_arguments(build_configuration=True, build_type=True) def test_unit( - self, build_type: BuildType, test_name=None, package=None, bench=False, nocapture=False, **kwargs + self, + build_type: BuildType, + test_name: list[str] | None = None, + package: str | None = None, + bench: bool = False, + nocapture: bool = False, + **kwargs: Any, ) -> int: if test_name is None: test_name = [] @@ -265,7 +271,7 @@ class MachCommands(CommandBase): action="store_true", help="Emit tidy warnings in the Github Actions annotations format", ) - def test_tidy(self, all_files, no_progress, github_annotations) -> int: + def test_tidy(self, all_files: bool, no_progress: bool, github_annotations: bool) -> int: tidy_failed = tidy.scan(not all_files, not no_progress, github_annotations) print("\r ➤ Checking formatting of Rust files...") @@ -299,7 +305,7 @@ class MachCommands(CommandBase): @CommandArgument( "tests", default=None, nargs="...", help="Specific WebIDL tests to run, relative to the tests directory" ) - def test_scripts(self, verbose, very_verbose, all, tests) -> int: + def test_scripts(self, verbose: bool, very_verbose: bool, all: bool, tests: list[str]) -> int: if very_verbose: logging.getLogger().level = logging.DEBUG elif verbose: @@ -357,7 +363,7 @@ class MachCommands(CommandBase): @Command("test-devtools", description="Run tests for devtools.", category="testing") @CommandArgument("test_names", nargs=argparse.REMAINDER, help="Only run tests that match these patterns") @CommandBase.common_command_arguments(build_type=True) - def test_devtools(self, build_type: BuildType, test_names: list[str], **kwargs) -> int: + def test_devtools(self, build_type: BuildType, test_names: list[str], **kwargs: Any) -> int: print("Running devtools tests...") passed = servo.devtools_tests.run_tests(SCRIPT_PATH, build_type, test_names) return 0 if passed else 1 @@ -369,7 +375,7 @@ class MachCommands(CommandBase): parser=wpt.create_parser, ) @CommandBase.common_command_arguments(build_configuration=False, build_type=True) - def test_wpt_failure(self, build_type: BuildType, **kwargs) -> bool: + def test_wpt_failure(self, build_type: BuildType, **kwargs: Any) -> bool: kwargs["pause_after_test"] = False kwargs["include"] = ["infrastructure/failing-test.html"] return not self._test_wpt(build_type=build_type, **kwargs) @@ -378,11 +384,11 @@ class MachCommands(CommandBase): "test-wpt", description="Run the regular web platform test suite", category="testing", parser=wpt.create_parser ) @CommandBase.common_command_arguments(binary_selection=True) - def test_wpt(self, servo_binary: str, **kwargs): + def test_wpt(self, servo_binary: str, **kwargs: Any) -> int: return self._test_wpt(servo_binary, **kwargs) @CommandBase.allow_target_configuration - def _test_wpt(self, servo_binary: str, **kwargs) -> int: + def _test_wpt(self, servo_binary: str, **kwargs: Any) -> int: # TODO(mrobinson): Why do we pass the wrong binary path in when running WPT on Android? return_value = wpt.run.run_tests(servo_binary, **kwargs) return return_value if not kwargs["always_succeed"] else 0 @@ -393,7 +399,7 @@ class MachCommands(CommandBase): category="testing", parser=wpt.manifestupdate.create_parser, ) - def update_manifest(self, **kwargs) -> int: + def update_manifest(self, **kwargs: Any) -> int: return wpt.manifestupdate.update(check_clean=False) @Command("fmt", description="Format Rust, Python, and TOML files", category="testing") @@ -411,7 +417,7 @@ class MachCommands(CommandBase): @Command( "update-wpt", description="Update the web platform tests", category="testing", parser=wpt.update.create_parser ) - def update_wpt(self, **kwargs) -> int: + def update_wpt(self, **kwargs: Any) -> int: patch = kwargs.get("patch", False) if not patch and kwargs["sync"]: print("Are you sure you don't want a patch?") @@ -427,7 +433,7 @@ class MachCommands(CommandBase): @CommandArgument("tests", default=["recommended"], nargs="...", help="Specific tests to run") @CommandArgument("--bmf-output", default=None, help="Specify BMF JSON output file") @CommandBase.common_command_arguments(binary_selection=True) - def test_dromaeo(self, tests, servo_binary: str, bmf_output: str | None = None) -> None: + def test_dromaeo(self, tests: list[str], servo_binary: str, bmf_output: str | None = None) -> None: return self.dromaeo_test_runner(tests, servo_binary, bmf_output) @Command("test-speedometer", description="Run servo's speedometer", category="testing") @@ -455,7 +461,7 @@ class MachCommands(CommandBase): @CommandArgument( "params", default=None, nargs="...", help=" filepaths of output files of two runs of dromaeo test " ) - def compare_dromaeo(self, params) -> None: + def compare_dromaeo(self, params: list[str]) -> None: prev_op_filename = params[0] cur_op_filename = params[1] result = {"Test": [], "Prev_Time": [], "Cur_Time": [], "Difference(%)": []} @@ -552,7 +558,7 @@ class MachCommands(CommandBase): return call([run_file, cmd, bin_path, base_dir]) - def dromaeo_test_runner(self, tests, binary: str, bmf_output: str | None) -> None: + def dromaeo_test_runner(self, tests: list[str], binary: str, bmf_output: str | None) -> None: base_dir = path.abspath(path.join("tests", "dromaeo")) dromaeo_dir = path.join(base_dir, "dromaeo") run_file = path.join(base_dir, "run_dromaeo.py") @@ -582,7 +588,7 @@ class MachCommands(CommandBase): output = dict() profile = "" if profile is None else profile + "/" - def parse_speedometer_result(result) -> None: + def parse_speedometer_result(result: dict[str, Any]) -> None: if result["unit"] == "ms": output[profile + f"Speedometer/{result['name']}"] = { "latency": { # speedometer has ms we need to convert to ns @@ -700,7 +706,7 @@ class MachCommands(CommandBase): description="Update the net unit tests with cookie tests from http-state", category="testing", ) - def update_net_cookies(self): + def update_net_cookies(self) -> int: cache_dir = path.join(self.config["tools"]["cache-dir"], "tests") run_file = path.abspath( path.join(PROJECT_TOPLEVEL_PATH, "components", "net", "tests", "cookie_http_state_utils.py") @@ -713,7 +719,7 @@ class MachCommands(CommandBase): "update-webgl", description="Update the WebGL conformance suite tests from Khronos repo", category="testing" ) @CommandArgument("--version", default="2.0.0", help="WebGL conformance suite version") - def update_webgl(self, version=None): + def update_webgl(self, version: str | None = None) -> None: base_dir = path.abspath(path.join(PROJECT_TOPLEVEL_PATH, "tests", "wpt", "mozilla", "tests", "webgl")) run_file = path.join(base_dir, "tools", "import-conformance-tests.py") dest_folder = path.join(base_dir, "conformance-%s" % version) @@ -729,7 +735,7 @@ class MachCommands(CommandBase): @Command("update-webgpu", description="Update the WebGPU conformance test suite", category="testing") @CommandArgument("--repo", "-r", default="https://github.com/gpuweb/cts", help="Repo to vendor cts from") @CommandArgument("--checkout", "-c", default="main", help="Branch or commit of repo") - def cts(self, repo="https://github.com/gpuweb/cts", checkout="main"): + def cts(self, repo: str = "https://github.com/gpuweb/cts", checkout: str = "main") -> int: tdir = path.join(self.context.topdir, "tests/wpt/webgpu/tests") clone_dir = path.join(tdir, "cts_clone") # clone @@ -786,7 +792,7 @@ class MachCommands(CommandBase): ) @CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to Servo") @CommandBase.common_command_arguments(binary_selection=True) - def smoketest(self, servo_binary: str, params, **kwargs) -> int | None: + def smoketest(self, servo_binary: str, params: list[str], **kwargs: Any) -> int | None: # We pass `-f` here so that any thread panic will cause Servo to exit, # preventing a panic from hanging execution. This means that these kind # of panics won't cause timeouts on CI. diff --git a/python/servo/try_parser.py b/python/servo/try_parser.py index bd3f42cd1fe..8e240139f11 100644 --- a/python/servo/try_parser.py +++ b/python/servo/try_parser.py @@ -13,7 +13,7 @@ from __future__ import annotations import json import sys -from typing import ClassVar, List, Optional +from typing import ClassVar, Optional, Any import unittest import logging @@ -44,7 +44,7 @@ class JobConfig(object): number_of_wpt_chunks: int = 20 # These are the fields that must match in between two JobConfigs for them to be able to be # merged. If you modify any of the fields above, make sure to update this line as well. - merge_compatibility_fields: ClassVar[List[str]] = ["workflow", "profile", "wpt_args", "build_args"] + merge_compatibility_fields: ClassVar[list[str]] = ["workflow", "profile", "wpt_args", "build_args"] def merge(self, other: JobConfig) -> bool: """Try to merge another job with this job. Returns True if merging is successful @@ -177,7 +177,7 @@ def handle_modifier(config: Optional[JobConfig], s: str) -> Optional[JobConfig]: class Encoder(json.JSONEncoder): - def default(self, o): + def default(self, o: Any) -> Any: if isinstance(o, (Config, JobConfig)): return o.__dict__ return json.JSONEncoder.default(self, o) @@ -236,7 +236,7 @@ class Config(object): return self.matrix.append(job) - def to_json(self, **kwargs) -> str: + def to_json(self, **kwargs: Any) -> str: return json.dumps(self, cls=Encoder, **kwargs) diff --git a/python/servo/util.py b/python/servo/util.py index 03be77e005e..65b701f25e7 100644 --- a/python/servo/util.py +++ b/python/servo/util.py @@ -7,6 +7,7 @@ # option. This file may not be copied, modified, or distributed # except according to those terms. +from os import PathLike import hashlib import os import os.path @@ -18,7 +19,8 @@ import urllib.error import urllib.request import zipfile from zipfile import ZipInfo -from typing import Dict, List, Union +from typing import Union, Any +from collections.abc import Callable from io import BufferedIOBase, BytesIO from socket import error as socket_error @@ -27,13 +29,13 @@ SCRIPT_PATH = os.path.abspath(os.path.dirname(__file__)) SERVO_ROOT = os.path.abspath(os.path.join(SCRIPT_PATH, "..", "..")) -def remove_readonly(func, path, _) -> None: +def remove_readonly(func: Callable[[str], None], path: str, _: Any) -> None: "Clear the readonly bit and reattempt the removal" os.chmod(path, stat.S_IWRITE) func(path) -def delete(path) -> None: +def delete(path: str) -> None: if os.path.isdir(path) and not os.path.islink(path): shutil.rmtree(path, onerror=remove_readonly) else: @@ -123,7 +125,7 @@ def download_file(description: str, url: str, destination_path: str) -> None: # https://stackoverflow.com/questions/39296101/python-zipfile-removes-execute-permissions-from-binaries # In particular, we want the executable bit for executable files. class ZipFileWithUnixPermissions(zipfile.ZipFile): - def extract(self, member, path=None, pwd=None) -> str: + def extract(self, member: ZipInfo | str, path: PathLike[str] | str | None = None, pwd: bytes | None = None) -> str: if not isinstance(member, zipfile.ZipInfo): member = self.getinfo(member) @@ -137,7 +139,7 @@ class ZipFileWithUnixPermissions(zipfile.ZipFile): return extracted # For Python 3.x - def _extract_member(self, member: ZipInfo, targetpath, pwd) -> str: + def _extract_member(self, member: ZipInfo, targetpath: PathLike[str] | str, pwd: bytes | None) -> str: if int(sys.version_info[0]) >= 3: if not isinstance(member, zipfile.ZipInfo): member = self.getinfo(member) @@ -154,7 +156,7 @@ class ZipFileWithUnixPermissions(zipfile.ZipFile): return super(ZipFileWithUnixPermissions, self)._extract_member(member, targetpath, pwd) -def extract(src, dst, movedir=None, remove=True) -> None: +def extract(src: str, dst: str, movedir: PathLike[str] | str | None = None, remove: bool = True) -> None: assert src.endswith(".zip") ZipFileWithUnixPermissions(src).extractall(dst) @@ -169,7 +171,7 @@ def extract(src, dst, movedir=None, remove=True) -> None: os.remove(src) -def check_hash(filename, expected, algorithm) -> None: +def check_hash(filename: str, expected: str, algorithm: str) -> None: hasher = hashlib.new(algorithm) with open(filename, "rb") as f: while True: @@ -182,11 +184,11 @@ def check_hash(filename, expected, algorithm) -> None: sys.exit(1) -def get_default_cache_dir(topdir) -> str: +def get_default_cache_dir(topdir: str) -> str: return os.environ.get("SERVO_CACHE_DIR", os.path.join(topdir, ".servo")) -def append_paths_to_env(env: Dict[str, str], key: str, paths: Union[str, List[str]]) -> None: +def append_paths_to_env(env: dict[str, str], key: str, paths: Union[str, list[str]]) -> None: if isinstance(paths, list): paths = os.pathsep.join(paths) @@ -198,7 +200,7 @@ def append_paths_to_env(env: Dict[str, str], key: str, paths: Union[str, List[st env[key] = new_value -def prepend_paths_to_env(env: Dict[str, str], key: str, paths: Union[str, List[str]]) -> None: +def prepend_paths_to_env(env: dict[str, str], key: str, paths: Union[str, list[str]]) -> None: if isinstance(paths, list): paths = os.pathsep.join(paths) diff --git a/python/servo/visual_studio.py b/python/servo/visual_studio.py index 6a615b531c6..f6e1ad3d8a8 100644 --- a/python/servo/visual_studio.py +++ b/python/servo/visual_studio.py @@ -7,12 +7,15 @@ # option. This file may not be copied, modified, or distributed # except according to those terms. +from __future__ import annotations + import dataclasses import json import os import subprocess import sys -from typing import Generator, List, Optional +from typing import Optional +from collections.abc import Generator COMPATIBLE_MSVC_VERSIONS = { "2019": "16.0", @@ -30,7 +33,7 @@ class VisualStudioInstallation: installation_path: str vc_install_path: str - def __lt__(self, other) -> bool: + def __lt__(self, other: VisualStudioInstallation) -> bool: return self.version_number < other.version_number @@ -108,7 +111,7 @@ def find_compatible_msvc_with_environment_variables() -> Optional[VisualStudioIn ) -def find_msvc_installations() -> List[VisualStudioInstallation]: +def find_msvc_installations() -> list[VisualStudioInstallation]: # First try to find Visual Studio via `vswhere.exe` and in well-known paths. installations = list(find_compatible_msvc_with_vswhere()) installations.extend(find_compatible_msvc_with_path())