diff --git a/pmb/aportgen/__init__.py b/pmb/aportgen/__init__.py index e8b0dcec..9c614290 100644 --- a/pmb/aportgen/__init__.py +++ b/pmb/aportgen/__init__.py @@ -35,7 +35,7 @@ def get_cross_package_arches(pkgname: str) -> str: return "x86_64" -def properties(pkgname): +def properties(pkgname: str) -> tuple[str, str, AportGenEntry]: """ Get the `pmb.config.aportgen` properties for the aport generator, based on the pkgname prefix. diff --git a/pmb/aportgen/device.py b/pmb/aportgen/device.py index 950db3db..e72bbc1f 100644 --- a/pmb/aportgen/device.py +++ b/pmb/aportgen/device.py @@ -13,7 +13,7 @@ import pmb.parse.apkindex import pmb.parse -def ask_for_architecture(): +def ask_for_architecture() -> Arch: architectures = [str(a) for a in Arch.supported()] # Don't show armhf, new ports shouldn't use this architecture if "armhf" in architectures: @@ -32,12 +32,12 @@ def ask_for_architecture(): ) -def ask_for_manufacturer(): +def ask_for_manufacturer() -> str: logging.info("Who produced the device (e.g. LG)?") return pmb.helpers.cli.ask("Manufacturer", None, None, False) -def ask_for_name(manufacturer): +def ask_for_name(manufacturer: str) -> str: logging.info("What is the official name (e.g. Google Nexus 5)?") ret = pmb.helpers.cli.ask("Name", None, None, False) @@ -47,13 +47,13 @@ def ask_for_name(manufacturer): return ret -def ask_for_year(): +def ask_for_year() -> str: # Regex from https://stackoverflow.com/a/12240826 logging.info("In what year was the device released (e.g. 2012)?") return pmb.helpers.cli.ask("Year", None, None, False, validation_regex=r"^[1-9]\d{3,}$") -def ask_for_chassis(): +def ask_for_chassis() -> str: types = pmb.config.deviceinfo_chassis_types logging.info("What type of device is it?") @@ -69,7 +69,7 @@ def ask_for_external_storage() -> bool: ) -def ask_for_flash_method(): +def ask_for_flash_method() -> str: while True: logging.info("Which flash method does the device support?") method = pmb.helpers.cli.ask( @@ -100,7 +100,7 @@ def ask_for_flash_method(): ) -def ask_for_bootimg(): +def ask_for_bootimg() -> Bootimg | None: logging.info( "You can analyze a known working boot.img file to" " automatically fill out the flasher information for your" diff --git a/pmb/build/checksum.py b/pmb/build/checksum.py index 18417e6b..660514bf 100644 --- a/pmb/build/checksum.py +++ b/pmb/build/checksum.py @@ -11,7 +11,7 @@ import pmb.helpers.pmaports from pmb.core import Chroot -def update(pkgname): +def update(pkgname: str) -> None: """Fetch all sources and update the checksums in the APKBUILD.""" pmb.build.init_abuild_minimal() pmb.build.copy_to_buildpath(pkgname, no_override=True) @@ -24,7 +24,7 @@ def update(pkgname): pmb.helpers.run.user(["cp", source, target]) -def verify(pkgname): +def verify(pkgname: str) -> None: """Fetch all sources and verify their checksums.""" pmb.build.init_abuild_minimal() pmb.build.copy_to_buildpath(pkgname) diff --git a/pmb/build/envkernel.py b/pmb/build/envkernel.py index 8f0c1462..78fbc729 100644 --- a/pmb/build/envkernel.py +++ b/pmb/build/envkernel.py @@ -21,7 +21,7 @@ from pmb.core import Chroot from pmb.core.context import get_context -def match_kbuild_out(word): +def match_kbuild_out(word: str) -> str | None: """Look for paths in the following formats: "//arch//boot" "//include/config/kernel.release" @@ -55,7 +55,7 @@ def match_kbuild_out(word): return "" if out_dir is None else out_dir.strip("/") -def find_kbuild_output_dir(function_body): +def find_kbuild_output_dir(function_body: list[str]) -> str: """Guess what the kernel build output directory is. Parses each line of the function word by word, looking for paths which diff --git a/pmb/build/newapkbuild.py b/pmb/build/newapkbuild.py index 4cb44994..168126a9 100644 --- a/pmb/build/newapkbuild.py +++ b/pmb/build/newapkbuild.py @@ -3,6 +3,7 @@ import os from pmb.core.pkgrepo import pkgrepo_default_path from pmb.helpers import logging +from pmb.types import PathString from pathlib import Path import pmb.chroot import pmb.helpers.cli @@ -12,7 +13,7 @@ import pmb.build from pmb.core import Chroot -def newapkbuild(folder, args_passed, force=False): +def newapkbuild(folder: PathString, args_passed: list[str], force: bool = False) -> None: # Initialize build environment and build folder pmb.build.init() pmb.chroot.init(Chroot.native()) diff --git a/pmb/chroot/apk.py b/pmb/chroot/apk.py index 34188714..4733965e 100644 --- a/pmb/chroot/apk.py +++ b/pmb/chroot/apk.py @@ -58,7 +58,7 @@ def check_min_version(chroot: Chroot = Chroot.native()) -> None: ) -def packages_split_to_add_del(packages): +def packages_split_to_add_del(packages: list[str]) -> tuple[list[str], list[str]]: """ Sort packages into "to_add" and "to_del" lists depending on their pkgname starting with an exclamation mark. diff --git a/pmb/chroot/init.py b/pmb/chroot/init.py index b36c2c27..38fad31f 100644 --- a/pmb/chroot/init.py +++ b/pmb/chroot/init.py @@ -59,7 +59,7 @@ def mark_in_chroot(chroot: Chroot = Chroot.native()) -> None: pmb.helpers.run.root(["touch", in_chroot_file]) -def init_keys(): +def init_keys() -> None: """ All Alpine and postmarketOS repository keys are shipped with pmbootstrap. Copy them into $WORK/config_apk_keys, which gets mounted inside the various diff --git a/pmb/chroot/initfs.py b/pmb/chroot/initfs.py index 6bbdbba0..0f8b591f 100644 --- a/pmb/chroot/initfs.py +++ b/pmb/chroot/initfs.py @@ -77,7 +77,7 @@ def extract(flavor: str | None, chroot: Chroot, extra: bool = False) -> Path: return outside -def ls(flavor, suffix, extra=False): +def ls(flavor: str | None, suffix: Chroot, extra: bool = False) -> None: tmp = "/tmp/initfs-extracted" if extra: tmp = "/tmp/initfs-extra-extracted" diff --git a/pmb/chroot/shutdown.py b/pmb/chroot/shutdown.py index 4dff3dfc..5971b9f7 100644 --- a/pmb/chroot/shutdown.py +++ b/pmb/chroot/shutdown.py @@ -12,7 +12,7 @@ from pmb.core import Chroot, ChrootType from pmb.core.context import get_context -def kill_adb(): +def kill_adb() -> None: """ Kill adb daemon if it's running. """ @@ -22,7 +22,7 @@ def kill_adb(): pmb.chroot.root(["adb", "-P", str(port), "kill-server"]) -def kill_sccache(): +def kill_sccache() -> None: """ Kill sccache daemon if it's running. Unlike ccache it automatically spawns a daemon when you call it and exits after some time of inactivity. @@ -59,7 +59,7 @@ def shutdown_cryptsetup_device(name: str) -> None: raise RuntimeError("Failed to parse 'cryptsetup status' output!") -def shutdown(only_install_related=False): +def shutdown(only_install_related: bool = False) -> None: # Stop daemons kill_adb() kill_sccache() diff --git a/pmb/chroot/zap.py b/pmb/chroot/zap.py index 813dcdfc..00348193 100644 --- a/pmb/chroot/zap.py +++ b/pmb/chroot/zap.py @@ -173,7 +173,7 @@ def zap_pkgs_local_mismatch(confirm: bool = True, dry: bool = False) -> None: pmb.build.other.index_repo() -def zap_pkgs_online_mismatch(confirm=True, dry=False): +def zap_pkgs_online_mismatch(confirm: bool = True, dry: bool = False) -> None: # Check whether we need to do anything paths = list(get_context().config.work.glob("cache_apk_*")) if not len(paths): diff --git a/pmb/ci/__init__.py b/pmb/ci/__init__.py index 8d6c9cd0..c8404346 100644 --- a/pmb/ci/__init__.py +++ b/pmb/ci/__init__.py @@ -5,13 +5,20 @@ import glob from pmb.helpers import logging import os from pathlib import Path +from typing import Any, TypedDict import pmb.chroot from pmb.types import Env, PmbArgs import pmb.helpers.cli from pmb.core import Chroot -def get_ci_scripts(topdir): +class CiScriptDescriptor(TypedDict): + description: str + options: list[str] + artifacts: str | None + + +def get_ci_scripts(topdir: Path) -> dict[str, CiScriptDescriptor]: """Find 'pmbootstrap ci'-compatible scripts inside a git repository, and parse their metadata (description, options). The reference is at: https://postmarketos.org/pmb-ci @@ -21,7 +28,7 @@ def get_ci_scripts(topdir): :returns: a dict of CI scripts found in the git repository, e.g. {"ruff": {"description": "lint all python scripts", "options": []}, ...} """ - ret = {} + ret: dict[str, CiScriptDescriptor] = {} for script in glob.glob(f"{topdir}/.ci/*.sh"): is_pmb_ci_script = False description = "" @@ -61,7 +68,7 @@ def get_ci_scripts(topdir): return ret -def sort_scripts_by_speed(scripts): +def sort_scripts_by_speed(scripts: dict[str, CiScriptDescriptor]) -> dict[str, CiScriptDescriptor]: """Order the scripts, so fast scripts run before slow scripts. Whether a script is fast or not is determined by the '# Options: slow' comment in the file. @@ -88,7 +95,9 @@ def sort_scripts_by_speed(scripts): return ret -def ask_which_scripts_to_run(scripts_available): +def ask_which_scripts_to_run( + scripts_available: dict[str, CiScriptDescriptor], +) -> dict[str, CiScriptDescriptor]: """Display an interactive prompt about which of the scripts the user wishes to run, or all of them. @@ -117,7 +126,7 @@ def ask_which_scripts_to_run(scripts_available): return ret -def copy_git_repo_to_chroot(topdir): +def copy_git_repo_to_chroot(topdir: Path) -> None: """Create a tarball of the git repo (including unstaged changes and new files) and extract it in chroot_native. @@ -142,7 +151,7 @@ def copy_git_repo_to_chroot(topdir): pmb.chroot.user(["tar", "-xf", "/tmp/git.tar.gz"], working_dir=ci_dir) -def run_scripts(topdir, scripts): +def run_scripts(topdir: Path, scripts: dict[str, CiScriptDescriptor]) -> None: """Run one of the given scripts after another, either natively or in a chroot. Display a progress message and stop on error (without printing a python stack trace). diff --git a/pmb/commands/base.py b/pmb/commands/base.py index 42662c96..0f9bbcd6 100644 --- a/pmb/commands/base.py +++ b/pmb/commands/base.py @@ -5,6 +5,6 @@ class Command: """Base class for pmbootstrap commands.""" - def run(self): + def run(self) -> None: """Run the command.""" raise NotImplementedError() diff --git a/pmb/commands/index.py b/pmb/commands/index.py index fe2af2fb..acffaa56 100644 --- a/pmb/commands/index.py +++ b/pmb/commands/index.py @@ -7,8 +7,8 @@ import pmb.build.other class Index(commands.Command): - def __init__(self): + def __init__(self) -> None: pass - def run(self): + def run(self) -> None: pmb.build.other.index_repo() diff --git a/pmb/commands/kconfig.py b/pmb/commands/kconfig.py index 94bce869..465ee5d3 100644 --- a/pmb/commands/kconfig.py +++ b/pmb/commands/kconfig.py @@ -96,6 +96,6 @@ class KConfigMigrate(commands.Command): self.pkgname_list = [pkgname] if isinstance(pkgname, str) else pkgname self.arch = arch - def run(self): + def run(self) -> None: for pkgname in self.pkgname_list: pmb.build.kconfig.migrate_config(pkgname, self.arch) diff --git a/pmb/commands/repo_bootstrap.py b/pmb/commands/repo_bootstrap.py index 7bb6a2e4..dffed1f1 100644 --- a/pmb/commands/repo_bootstrap.py +++ b/pmb/commands/repo_bootstrap.py @@ -26,7 +26,7 @@ class RepoBootstrap(commands.Command): progress_total: int = 0 progress_step: str - def check_repo_arg(self): + def check_repo_arg(self) -> None: cfg = pmb.config.pmaports.read_config_repos() if self.repo in cfg: @@ -79,7 +79,7 @@ class RepoBootstrap(commands.Command): if self.arch.cpu_emulation_required(): self.progress_total += len(steps) - def log_progress(self, msg): + def log_progress(self, msg: str) -> None: percent = int(100 * self.progress_done / self.progress_total) logging.info(f"*** {percent}% [{self.progress_step}] {msg} ***") @@ -130,7 +130,7 @@ class RepoBootstrap(commands.Command): self.log_progress("bootstrap complete!") - def check_existing_pkgs(self): + def check_existing_pkgs(self) -> None: channel = pmb.config.pmaports.read_config()["channel"] path = self.context.config.work / "packages" / channel / self.arch @@ -169,7 +169,7 @@ class RepoBootstrap(commands.Command): return ret - def run(self): # noqa: F821 + def run(self) -> None: # noqa: F821 self.check_existing_pkgs() steps = self.get_steps() diff --git a/pmb/commands/shutdown.py b/pmb/commands/shutdown.py index 8f531be2..8df0895d 100644 --- a/pmb/commands/shutdown.py +++ b/pmb/commands/shutdown.py @@ -7,8 +7,8 @@ import pmb.chroot class Shutdown(commands.Command): - def __init__(self): + def __init__(self) -> None: pass - def run(self): + def run(self) -> None: pmb.chroot.shutdown() diff --git a/pmb/commands/test.py b/pmb/commands/test.py index 940b7b90..b06d5512 100644 --- a/pmb/commands/test.py +++ b/pmb/commands/test.py @@ -12,7 +12,7 @@ import time """Various internal test commands for performance testing and debugging.""" -def apkindex_parse_all(): +def apkindex_parse_all() -> None: indexes = pmb.helpers.repo.apkindex_files(Arch.native()) pkgs = 0 @@ -26,9 +26,9 @@ def apkindex_parse_all(): class Test(commands.Command): - def __init__(self, action: str): + def __init__(self, action: str) -> None: self.action = action - def run(self): + def run(self) -> None: if self.action == "apkindex_parse_all": apkindex_parse_all() diff --git a/pmb/config/workdir.py b/pmb/config/workdir.py index 1d4842aa..37871971 100644 --- a/pmb/config/workdir.py +++ b/pmb/config/workdir.py @@ -129,7 +129,7 @@ def chroot_check_channel(chroot: Chroot) -> bool: return False -def clean(): +def clean() -> bool | None: """Remove obsolete data data from workdir.cfg. :returns: None if workdir does not exist, diff --git a/pmb/core/config.py b/pmb/core/config.py index 6c53216d..d30efb1d 100644 --- a/pmb/core/config.py +++ b/pmb/core/config.py @@ -83,7 +83,7 @@ class Config: providers: dict[str, str] = {} - def __init__(self): + def __init__(self) -> None: # Make sure we aren't modifying the class defaults for key in Config.__annotations__.keys(): setattr(self, key, deepcopy(Config.get_default(key))) diff --git a/pmb/helpers/apk.py b/pmb/helpers/apk.py index 13172f18..29bf1c81 100644 --- a/pmb/helpers/apk.py +++ b/pmb/helpers/apk.py @@ -116,7 +116,7 @@ def _create_command_with_progress(command, fifo): return ["sh", "-c", command_flat] -def _compute_progress(line): +def _compute_progress(line: str) -> float: """Compute the progress as a number between 0 and 1. :param line: line as read from the progress fifo @@ -277,7 +277,7 @@ def cache_clean(arch: Arch) -> None: pmb.helpers.run.root(_command) -def check_outdated(version_installed, action_msg): +def check_outdated(version_installed: str, action_msg: str) -> None: """Check if the provided alpine version is outdated. This depends on the alpine mirrordir (edge, v3.12, ...) related to currently checked out diff --git a/pmb/helpers/apk_static.py b/pmb/helpers/apk_static.py index 763f045b..6bc4115d 100644 --- a/pmb/helpers/apk_static.py +++ b/pmb/helpers/apk_static.py @@ -7,6 +7,7 @@ import shutil import tarfile import tempfile import stat +from pathlib import Path import pmb.helpers.apk import pmb.helpers.run @@ -19,7 +20,7 @@ import pmb.parse.version from pmb.core.context import get_context -def read_signature_info(tar): +def read_signature_info(tar: tarfile.TarFile) -> tuple[str, str]: """ Find various information about the signature that was used to sign /sbin/apk.static inside the archive (not to be confused with the normal apk @@ -107,7 +108,7 @@ def verify_signature(files, sigkey_path): ) -def extract(version, apk_path): +def extract(version: str, apk_path: Path) -> None: """ Extract everything to temporary locations, verify signatures and reported versions. When everything is right, move the extracted apk.static to the @@ -147,7 +148,7 @@ def extract(version, apk_path): shutil.move(temp_path, target_path) -def download(file): +def download(file: str) -> Path: """ Download a single file from an Alpine mirror. """ diff --git a/pmb/helpers/cli.py b/pmb/helpers/cli.py index e24031be..3246f401 100644 --- a/pmb/helpers/cli.py +++ b/pmb/helpers/cli.py @@ -6,6 +6,8 @@ import os import re import readline import sys +from collections.abc import KeysView +from typing import Any import pmb.config from pmb.core.context import get_context @@ -14,7 +16,7 @@ from pmb.core.context import get_context class ReadlineTabCompleter: """Store intermediate state for completer function.""" - def __init__(self, options: list[str]) -> None: + def __init__(self, options: KeysView[str] | dict[str, Any] | list[str]) -> None: """:param options: list of possible completions.""" self.options = sorted(options) self.matches: list[str] = [] @@ -38,13 +40,13 @@ class ReadlineTabCompleter: def ask( - question="Continue?", - choices=["y", "n"], - default="n", - lowercase_answer=True, - validation_regex=None, - complete=None, -): + question: str = "Continue?", + choices: list[str] | None = ["y", "n"], + default: int | str | None = "n", + lowercase_answer: bool | None = True, + validation_regex: str | None = None, + complete: KeysView[str] | dict[str, Any] | list[str] | None = None, +) -> str: """Ask a question on the terminal. :param question: display prompt diff --git a/pmb/helpers/file.py b/pmb/helpers/file.py index 858ce74e..ddc20cc9 100644 --- a/pmb/helpers/file.py +++ b/pmb/helpers/file.py @@ -84,7 +84,7 @@ def is_up_to_date(path_sources, path_target=None, lastmod_target=None): return lastmod_target >= lastmod_source -def is_older_than(path, seconds): +def is_older_than(path: Path, seconds: int) -> bool: """Check if a single file is older than a given amount of seconds.""" if not os.path.exists(path): return True diff --git a/pmb/helpers/package.py b/pmb/helpers/package.py index 835d2876..4a720031 100644 --- a/pmb/helpers/package.py +++ b/pmb/helpers/package.py @@ -20,7 +20,7 @@ import pmb.helpers.pmaports import pmb.helpers.repo -def remove_operators(package): +def remove_operators(package: str) -> str: for operator in [">", ">=", "<=", "=", "<", "~"]: if operator in package: package = package.split(operator)[0] diff --git a/pmb/helpers/pmaports.py b/pmb/helpers/pmaports.py index 3ea6e08a..24932f9c 100644 --- a/pmb/helpers/pmaports.py +++ b/pmb/helpers/pmaports.py @@ -375,7 +375,7 @@ def get_channel_new(channel: str) -> str: return channel -def require_bootstrap_error(repo, arch, trigger_str): +def require_bootstrap_error(repo: str, arch: Arch, trigger_str: str) -> None: """ Tell the user that they need to do repo_bootstrap, with some context. @@ -392,7 +392,7 @@ def require_bootstrap_error(repo, arch, trigger_str): ) -def require_bootstrap(arch, trigger_str): +def require_bootstrap(arch: Arch, trigger_str: str) -> None: """ Check if repo_bootstrap was done, if any is needed. diff --git a/pmb/helpers/repo_missing.py b/pmb/helpers/repo_missing.py index 1692c768..e34fb7fb 100644 --- a/pmb/helpers/repo_missing.py +++ b/pmb/helpers/repo_missing.py @@ -1,16 +1,16 @@ # Copyright 2023 Oliver Smith # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Any from pmb.core.arch import Arch from pmb.helpers import logging +from pmb.types import Apkbuild import pmb.build import pmb.helpers.package import pmb.helpers.pmaports -def filter_missing_packages(arch, pkgnames): +def filter_missing_packages(arch: Arch, pkgnames: list[str]) -> list[str]: """Create a subset of pkgnames with missing or outdated binary packages. :param arch: architecture (e.g. "armhf") @@ -27,7 +27,7 @@ def filter_missing_packages(arch, pkgnames): return ret -def filter_aport_packages(pkgnames): +def filter_aport_packages(pkgnames: list[str]) -> list[str]: """Create a subset of pkgnames where each one has an aport. :param pkgnames: list of package names (e.g. ["hello-world", "test12"]) @@ -40,7 +40,7 @@ def filter_aport_packages(pkgnames): return ret -def filter_arch_packages(arch, pkgnames): +def filter_arch_packages(arch: Arch, pkgnames: list[str]) -> list[str]: """Create a subset of pkgnames with packages removed that can not be built for a certain arch. :param arch: architecture (e.g. "armhf") @@ -54,7 +54,7 @@ def filter_arch_packages(arch, pkgnames): return ret -def get_relevant_packages(arch, pkgname=None, built=False): +def get_relevant_packages(arch: Arch, pkgname: str | None = None, built: bool = False) -> list[str]: """Get all packages that can be built for the architecture in question. :param arch: architecture (e.g. "armhf") @@ -92,7 +92,7 @@ def get_relevant_packages(arch, pkgname=None, built=False): return ret -def generate_output_format(arch: Arch, pkgnames: list[str]) -> list[dict[str, Any]]: +def generate_output_format(arch: Arch, pkgnames: list[str]) -> list[Apkbuild]: """Generate the detailed output format. :param arch: architecture diff --git a/pmb/helpers/run_core.py b/pmb/helpers/run_core.py index 3dabf486..ab8a8d94 100644 --- a/pmb/helpers/run_core.py +++ b/pmb/helpers/run_core.py @@ -72,7 +72,7 @@ def sanity_checks( raise RuntimeError("Can't use output_return with output: " + output) -def background(cmd, working_dir=None): +def background(cmd: str, working_dir: PathString | None = None) -> subprocess.Popen: """Run a subprocess in background and redirect its output to the log.""" ret = subprocess.Popen( cmd, stdout=pmb.helpers.logging.logfd, stderr=pmb.helpers.logging.logfd, cwd=working_dir @@ -81,7 +81,7 @@ def background(cmd, working_dir=None): return ret -def pipe(cmd, working_dir=None): +def pipe(cmd: str, working_dir: PathString | None = None) -> subprocess.Popen: """Run a subprocess in background and redirect its output to a pipe.""" ret = subprocess.Popen( cmd, @@ -156,7 +156,9 @@ def pipe_read( return -def kill_process_tree(pid, ppids, sudo): +# FIXME: The docstring claims that ppids should be a list of "process ID tuples", but in practice it +# gets called with a list of string lists for the ppids argument. +def kill_process_tree(pid: int | str, ppids: list[list[str]], sudo: bool) -> None: """Recursively kill a pid and its child processes. :param pid: process id that will be killed @@ -173,7 +175,7 @@ def kill_process_tree(pid, ppids, sudo): kill_process_tree(child_pid, ppids, sudo) -def kill_command(pid, sudo): +def kill_command(pid: int, sudo: bool) -> None: """Kill a command process and recursively kill its child processes. :param pid: process id that will be killed @@ -268,7 +270,7 @@ def foreground_pipe( return (process.returncode, b"".join(output_buffer).decode("utf-8")) -def foreground_tui(cmd, working_dir=None): +def foreground_tui(cmd: str, working_dir: PathString | None = None) -> int: """Run a subprocess in foreground without redirecting any of its output. This is the only way text-based user interfaces (ncurses programs like @@ -279,7 +281,7 @@ def foreground_tui(cmd, working_dir=None): return process.wait() -def check_return_code(code, log_message): +def check_return_code(code: int, log_message: str) -> None: """Check the return code of a command. :param code: exit code to check @@ -298,7 +300,7 @@ def check_return_code(code, log_message): raise RuntimeError(f"Command failed (exit code {str(code)}): " + log_message) -def sudo_timer_iterate(): +def sudo_timer_iterate() -> None: """Run sudo -v and schedule a new timer to repeat the same.""" if pmb.config.which_sudo() == "sudo": subprocess.Popen(["sudo", "-v"]).wait() @@ -310,7 +312,7 @@ def sudo_timer_iterate(): timer.start() -def sudo_timer_start(): +def sudo_timer_start() -> None: """Start a timer to call sudo -v periodically, so that the password is only needed once.""" if "sudo_timer_active" in pmb.helpers.other.cache: return @@ -319,7 +321,7 @@ def sudo_timer_start(): sudo_timer_iterate() -def add_proxy_env_vars(env): +def add_proxy_env_vars(env: Env) -> None: """Add proxy environment variables from host to the environment of the command we are running. :param env: dict of environment variables, it will be extended with all of the proxy env vars diff --git a/pmb/helpers/ui.py b/pmb/helpers/ui.py index 5eeb88d5..c56229f3 100644 --- a/pmb/helpers/ui.py +++ b/pmb/helpers/ui.py @@ -1,6 +1,7 @@ # Copyright 2023 Clayton Craft # SPDX-License-Identifier: GPL-3.0-or-later import os +from pmb.core.arch import Arch from pmb.core.pkgrepo import pkgrepo_iglob from pmb.types import WithExtraRepos import pmb.helpers.pmaports @@ -8,7 +9,7 @@ import pmb.helpers.package import pmb.parse -def list_ui(arch): +def list_ui(arch: Arch) -> list[tuple[str, str]]: """Get all UIs, for which aports are available with their description. :param arch: device architecture, for which the UIs must be available diff --git a/pmb/install/_install.py b/pmb/install/_install.py index dd9381d3..5d196b9f 100644 --- a/pmb/install/_install.py +++ b/pmb/install/_install.py @@ -673,7 +673,7 @@ def write_cgpt_kpart(args: PmbArgs, layout: PartitionLayout, suffix: Chroot) -> pmb.chroot.root(["dd", f"if={filename}", f"of=/dev/installp{layout['kernel']}"]) -def sanity_check_boot_size(): +def sanity_check_boot_size() -> None: default = Config().boot_size config = get_context().config if int(config.boot_size) >= int(default): diff --git a/pmb/install/format.py b/pmb/install/format.py index 72e2bc73..c0276d24 100644 --- a/pmb/install/format.py +++ b/pmb/install/format.py @@ -6,7 +6,7 @@ from pmb.core import Chroot from pmb.types import PartitionLayout, PmbArgs, PathString -def install_fsprogs(filesystem): +def install_fsprogs(filesystem: str) -> None: """Install the package required to format a specific filesystem.""" fsprogs = pmb.config.filesystems.get(filesystem) if not fsprogs: diff --git a/pmb/meta/__init__.py b/pmb/meta/__init__.py index e3c3594b..75f23693 100644 --- a/pmb/meta/__init__.py +++ b/pmb/meta/__init__.py @@ -12,7 +12,7 @@ FuncReturn = TypeVar("FuncReturn") class Wrapper(Generic[FuncArgs, FuncReturn]): - def __init__(self, cache: "Cache", func: Callable[[FuncArgs], FuncReturn]): + def __init__(self, cache: "Cache", func: Callable[[FuncArgs], FuncReturn]) -> None: self.cache = cache self.func = func self.disabled = False @@ -47,12 +47,12 @@ class Wrapper(Generic[FuncArgs, FuncReturn]): return self.cache.cache[key] - def cache_clear(self): + def cache_clear(self) -> None: self.cache.clear() self.misses = 0 self.hits = 0 - def cache_disable(self): + def cache_disable(self) -> None: self.disabled = True @@ -64,7 +64,7 @@ class Cache: function is called with the given value. For example, in pmb.build._package we never want to use the cached result when called with force=True.""" - def __init__(self, *args, cache_deepcopy=False, **kwargs): + def __init__(self, *args: str, cache_deepcopy: bool = False, **kwargs: Any) -> None: for a in args: if not isinstance(a, str): raise ValueError(f"Cache key must be a string, not {type(a)}") @@ -72,7 +72,7 @@ class Cache: if len(args) != len(set(args)): raise ValueError("Duplicate cache key properties") - self.cache = {} + self.cache: dict[str, Any] = {} self.params = args self.kwargs = kwargs self.cache_deepcopy = cache_deepcopy @@ -144,5 +144,5 @@ class Cache: # FIXME: Once PEP-695 generics are in we shouldn't need this. return Wrapper(self, func) - def clear(self): + def clear(self) -> None: self.cache.clear() diff --git a/pmb/parse/arguments.py b/pmb/parse/arguments.py index c6eecd02..efdcee52 100644 --- a/pmb/parse/arguments.py +++ b/pmb/parse/arguments.py @@ -29,7 +29,9 @@ import pmb.helpers.pmaports """ -def toggle_other_boolean_flags(*other_destinations, value=True): +def toggle_other_boolean_flags( + *other_destinations: str, value: bool = True +) -> type[argparse.Action]: """Group several argparse flags to one. Sets multiple other_destination to value. @@ -52,12 +54,12 @@ def toggle_other_boolean_flags(*other_destinations, value=True): return SetOtherDestinationsAction -def type_ondev_cp(val): +def type_ondev_cp(val: str) -> list[str]: """Parse and validate arguments to 'pmbootstrap install --ondev --cp'. :param val: 'HOST_SRC:CHROOT_DEST' string - :returns: (HOST_SRC, CHROOT_DEST) + :returns: [HOST_SRC, CHROOT_DEST] """ ret = val.split(":") @@ -75,7 +77,7 @@ def type_ondev_cp(val): return ret -def arguments_install(subparser): +def arguments_install(subparser: argparse._SubParsersAction) -> None: ret = subparser.add_parser( "install", help="set up device specific chroot and install to SD card or image file" ) @@ -266,7 +268,7 @@ def arguments_install(subparser): ) -def arguments_export(subparser): +def arguments_export(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser( "export", help="create convenience symlinks" @@ -297,7 +299,7 @@ def arguments_export(subparser): return ret -def arguments_sideload(subparser): +def arguments_sideload(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser( "sideload", help="Push packages to a running phone connected over usb or wifi" ) @@ -325,7 +327,7 @@ def arguments_sideload(subparser): return ret -def arguments_flasher(subparser): +def arguments_flasher(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser("flasher", help="flash something to the target device") ret.add_argument("--method", help="override flash method", dest="flash_method", default=None) sub = ret.add_subparsers(dest="action_flasher") @@ -431,7 +433,7 @@ def arguments_flasher(subparser): return ret -def arguments_initfs(subparser): +def arguments_initfs(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser("initfs", help="do something with the initramfs") sub = ret.add_subparsers(dest="action_initfs") @@ -457,7 +459,7 @@ def arguments_initfs(subparser): return ret -def arguments_qemu(subparser): +def arguments_qemu(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser("qemu") ret.add_argument("--cmdline", help="override kernel commandline") ret.add_argument( @@ -549,7 +551,7 @@ def arguments_qemu(subparser): return ret -def arguments_pkgrel_bump(subparser): +def arguments_pkgrel_bump(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser( "pkgrel_bump", help="increase the pkgrel to" @@ -577,7 +579,7 @@ def arguments_pkgrel_bump(subparser): return ret -def arguments_pkgver_bump(subparser): +def arguments_pkgver_bump(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser( "pkgver_bump", help="increase the pkgver and reset pkgrel to 0. useful when dealing with metapackages.", @@ -587,7 +589,7 @@ def arguments_pkgver_bump(subparser): return ret -def arguments_aportupgrade(subparser): +def arguments_aportupgrade(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser( "aportupgrade", help="check for outdated packages that need upgrading" ) @@ -609,7 +611,7 @@ def arguments_aportupgrade(subparser): return ret -def arguments_newapkbuild(subparser): +def arguments_newapkbuild(subparser: argparse._SubParsersAction) -> None: """ Wrapper for Alpine's "newapkbuild" command. @@ -651,7 +653,7 @@ def arguments_newapkbuild(subparser): ) -def arguments_kconfig(subparser): +def arguments_kconfig(subparser: argparse._SubParsersAction) -> None: # Allowed architectures arch_choices = Arch.supported() @@ -726,7 +728,7 @@ def arguments_repo_bootstrap(subparser): return ret -def arguments_repo_missing(subparser): +def arguments_repo_missing(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser("repo_missing") package = ret.add_argument( "package", nargs="?", help="only look at a specific package and its dependencies" @@ -745,23 +747,23 @@ def arguments_repo_missing(subparser): return ret -def arguments_lint(subparser): +def arguments_lint(subparser: argparse._SubParsersAction) -> None: lint = subparser.add_parser("lint", help="run quality checks on pmaports (required to pass CI)") add_packages_arg(lint, nargs="*") -def arguments_test(subparser): +def arguments_test(subparser: argparse._SubParsersAction) -> None: test = subparser.add_parser("test", help="Internal pmbootstrap test tools") sub = test.add_subparsers(dest="action_test", required=True) sub.add_parser("apkindex_parse_all", help="parse all APKINDEX files") -def arguments_status(subparser): +def arguments_status(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser("status", help="show a config and pmaports overview") return ret -def arguments_netboot(subparser): +def arguments_netboot(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser("netboot", help="launch nbd server with pmOS rootfs") sub = ret.add_subparsers(dest="action_netboot") sub.required = True @@ -772,7 +774,7 @@ def arguments_netboot(subparser): return ret -def arguments_ci(subparser): +def arguments_ci(subparser: argparse._SubParsersAction) -> argparse.ArgumentParser: ret = subparser.add_parser( "ci", help="run continuous integration scripts locally of git repo in current directory", @@ -1294,7 +1296,7 @@ def get_parser(): return parser -def arguments(): +def arguments() -> PmbArgs: args: PmbArgs = get_parser().parse_args() if getattr(args, "fork_alpine_retain_branch", False): diff --git a/pmb/parse/binfmt_info.py b/pmb/parse/binfmt_info.py index 4a1d6dc0..96c866e1 100644 --- a/pmb/parse/binfmt_info.py +++ b/pmb/parse/binfmt_info.py @@ -7,7 +7,8 @@ import pmb.config # Return: {magic: ..., mask: ...} -def binfmt_info(arch_qemu): +# FIXME: Maybe this should use Arch instead of str. +def binfmt_info(arch_qemu: str) -> dict[str, str]: # Parse the info file full = {} info = pmb.config.pmb_src / "pmb/data/qemu-user-binfmt.txt" diff --git a/pmb/parse/kconfig.py b/pmb/parse/kconfig.py index be6bf093..f663dead 100644 --- a/pmb/parse/kconfig.py +++ b/pmb/parse/kconfig.py @@ -4,6 +4,7 @@ from pathlib import Path from pmb.helpers import logging import re import os +from typing import Literal, overload import pmb.build import pmb.config @@ -14,7 +15,7 @@ from pmb.helpers.exceptions import NonBugError from pmb.types import PathString -def is_set(config, option): +def is_set(config: str, option: str) -> bool: """ Check, whether a boolean or tristate option is enabled either as builtin or module. @@ -26,7 +27,7 @@ def is_set(config, option): return re.search("^CONFIG_" + option + "=[ym]$", config, re.M) is not None -def is_set_str(config, option, string): +def is_set_str(config: str, option: str, string: str) -> bool: """ Check, whether a config option contains a string as value. @@ -42,7 +43,7 @@ def is_set_str(config, option, string): return False -def is_in_array(config, option, string): +def is_in_array(config: str, option: str, string: str) -> bool: """ Check, whether a config option contains string as an array element @@ -59,7 +60,14 @@ def is_in_array(config, option, string): return False -def check_option(component, details, config, config_path, option, option_value): +def check_option( + component: str, + details: bool, + config: str, + config_path: PathString, + option: str, + option_value: bool | str | list[str], +) -> bool: """ Check, whether one kernel config option has a given value. @@ -72,7 +80,7 @@ def check_option(component, details, config, config_path, option, option_value): :returns: True if the check passed, False otherwise """ - def warn_ret_false(should_str): + def warn_ret_false(should_str: str) -> bool: config_name = os.path.basename(config_path) if details: logging.warning( @@ -107,8 +115,14 @@ def check_option(component, details, config, config_path, option, option_value): def check_config_options_set( - config, config_path, config_arch, options, component, pkgver, details=False -): + config: str, + config_path: PathString, + config_arch: str, # TODO: Replace with Arch type? + options: dict[str, dict], + component: str, + pkgver: str, + details: bool = False, +) -> bool: """ Check, whether all the kernel config passes all rules of one component. @@ -195,7 +209,27 @@ def check_config( return all(ret) -def check(pkgname, components_list=[], details=False, must_exist=True): +@overload +def check( + pkgname: str, + components_list: list[str] = ..., + details: bool = ..., + must_exist: Literal[False] = ..., +) -> bool | None: ... + + +@overload +def check( + pkgname: str, + components_list: list[str] = ..., + details: bool = ..., + must_exist: Literal[True] = ..., +) -> bool: ... + + +def check( + pkgname: str, components_list: list[str] = [], details: bool = False, must_exist: bool = True +) -> bool | None: """ Check for necessary kernel config options in a package. @@ -263,7 +297,8 @@ def check(pkgname, components_list=[], details=False, must_exist=True): return ret -def extract_arch(config_path): +# TODO: Make this use the Arch type probably +def extract_arch(config_path: PathString) -> str: # Extract the architecture out of the config with open(config_path) as f: config = f.read() @@ -283,7 +318,7 @@ def extract_arch(config_path): return "unknown" -def extract_version(config_path): +def extract_version(config_path: PathString) -> str: # Try to extract the version string out of the comment header with open(config_path) as f: # Read the first 3 lines of the file and get the third line only diff --git a/pmb/parse/version.py b/pmb/parse/version.py index c7840453..4a727a1b 100644 --- a/pmb/parse/version.py +++ b/pmb/parse/version.py @@ -10,7 +10,7 @@ https://git.alpinelinux.org/cgit/apk-tools/tree/src/version.c """ -def token_value(string): +def token_value(string: str) -> int: """ Return the associated value for a given token string (we parse through the version string one token at a time). @@ -35,7 +35,7 @@ def token_value(string): return order[string] -def next_token(previous, rest): +def next_token(previous: str, rest: str) -> tuple[str, str]: """ Parse the next token in the rest of the version string, we're currently looking at. @@ -90,7 +90,7 @@ def next_token(previous, rest): return (next, rest) -def parse_suffix(rest): +def parse_suffix(rest: str) -> tuple[str, int, bool]: """ Cut off the suffix of rest (which is now at the beginning of the rest variable, but regarding the whole version string, it is a @@ -188,7 +188,7 @@ def get_token(previous, rest): return (next, value, rest) -def validate(version): +def validate(version: str) -> bool: """ Check whether one version string is valid. @@ -277,7 +277,7 @@ Convenience functions below are not modeled after apk's version.c. """ -def check_string(a_version, rule): +def check_string(a_version: str, rule: str) -> bool: """ Compare a version against a check string. This is used in "pmbootstrap kconfig check", to only require certain options if the pkgver is in a diff --git a/pmb/qemu/run.py b/pmb/qemu/run.py index be12183e..6f16e8e7 100644 --- a/pmb/qemu/run.py +++ b/pmb/qemu/run.py @@ -287,7 +287,7 @@ def command_qemu( return (command, env) -def resize_image(img_size_new, img_path): +def resize_image(img_size_new: str, img_path: Path) -> None: """ Truncates an image to a specific size. The value must be larger than the current image size, and it must be specified in MiB or GiB units (powers of