From 225d8b30a01c03b07b8a00612ddc6dd002ad6fd0 Mon Sep 17 00:00:00 2001 From: Newbyte Date: Tue, 29 Oct 2024 23:06:59 +0100 Subject: [PATCH] pmb: Add lots of type hints (MR 2464) --- pmb/aportgen/core.py | 2 +- pmb/aportgen/device.py | 8 +- pmb/aportgen/gcc.py | 2 +- pmb/aportgen/linux.py | 4 +- pmb/build/_package.py | 29 ++++--- pmb/build/autodetect.py | 11 ++- pmb/build/backend.py | 28 ++++--- pmb/build/envkernel.py | 12 +-- pmb/build/init.py | 7 +- pmb/build/kconfig.py | 20 +++-- pmb/build/other.py | 14 ++-- pmb/chroot/apk.py | 15 ++-- pmb/chroot/binfmt.py | 6 +- pmb/chroot/init.py | 8 +- pmb/chroot/initfs.py | 6 +- pmb/chroot/initfs_hooks.py | 12 +-- pmb/chroot/mount.py | 12 +-- pmb/chroot/other.py | 2 +- pmb/chroot/run.py | 142 ++++++++++++++++++++++++++------- pmb/chroot/shutdown.py | 2 +- pmb/chroot/zap.py | 22 ++--- pmb/ci/__init__.py | 4 +- pmb/commands/__init__.py | 2 +- pmb/commands/repo_bootstrap.py | 2 +- pmb/config/file.py | 4 +- pmb/config/init.py | 6 +- pmb/config/other.py | 4 +- pmb/config/pmaports.py | 22 ++--- pmb/config/workdir.py | 4 +- pmb/core/config.py | 2 +- pmb/core/context.py | 2 +- pmb/core/pkgrepo.py | 8 +- pmb/export/frontend.py | 2 +- pmb/export/odin.py | 2 +- pmb/export/symlinks.py | 2 +- pmb/flasher/init.py | 2 +- pmb/flasher/run.py | 2 +- pmb/flasher/variables.py | 2 +- pmb/helpers/apk.py | 7 +- pmb/helpers/aportupgrade.py | 37 +++++---- pmb/helpers/devices.py | 4 +- pmb/helpers/file.py | 8 +- pmb/helpers/frontend.py | 20 +++-- pmb/helpers/git.py | 8 +- pmb/helpers/lint.py | 2 +- pmb/helpers/logging.py | 28 +++---- pmb/helpers/mount.py | 3 +- pmb/helpers/other.py | 12 +-- pmb/helpers/pkgrel_bump.py | 2 +- pmb/helpers/pmaports.py | 14 ++-- pmb/helpers/repo.py | 8 +- pmb/helpers/run.py | 61 +++++++++++--- pmb/helpers/run_core.py | 10 ++- pmb/helpers/status.py | 2 +- pmb/helpers/toml.py | 4 +- pmb/install/_install.py | 86 ++++++++++---------- pmb/install/blockdevice.py | 12 ++- pmb/install/format.py | 22 +++-- pmb/install/partition.py | 7 +- pmb/install/recovery.py | 4 +- pmb/meta/__init__.py | 6 +- pmb/netboot/__init__.py | 2 +- pmb/parse/_apkbuild.py | 11 +-- pmb/parse/bootimg.py | 12 +-- pmb/parse/depends.py | 2 +- pmb/parse/deviceinfo.py | 4 +- pmb/parse/kconfig.py | 14 +++- pmb/parse/version.py | 2 +- pmb/qemu/run.py | 25 ++++-- pmb/sideload/__init__.py | 11 ++- pmb/types.py | 5 ++ 71 files changed, 566 insertions(+), 325 deletions(-) diff --git a/pmb/aportgen/core.py b/pmb/aportgen/core.py index 09f074f9..16db7a82 100644 --- a/pmb/aportgen/core.py +++ b/pmb/aportgen/core.py @@ -161,7 +161,7 @@ def rewrite( handle.truncate() -def get_upstream_aport(pkgname: str, arch: Arch | None = None, retain_branch: bool = False): +def get_upstream_aport(pkgname: str, arch: Arch | None = None, retain_branch: bool = False) -> Path: """ Perform a git checkout of Alpine's aports and get the path to the aport. diff --git a/pmb/aportgen/device.py b/pmb/aportgen/device.py index 268de250..197abb0c 100644 --- a/pmb/aportgen/device.py +++ b/pmb/aportgen/device.py @@ -193,8 +193,8 @@ def generate_deviceinfo( chassis: str, has_external_storage: bool, flash_method: str, - bootimg=None, -): + bootimg: Bootimg | None = None, +) -> None: codename = "-".join(pkgname.split("-")[1:]) external_storage = "true" if has_external_storage else "false" # Note: New variables must be added to pmb/config/__init__.py as well @@ -281,7 +281,7 @@ def generate_modules_initfs() -> None: handle.write(line.lstrip() + "\n") -def generate_apkbuild(pkgname: str, name: str, arch: Arch, flash_method: str): +def generate_apkbuild(pkgname: str, name: str, arch: Arch, flash_method: str) -> None: # Dependencies depends = ["postmarketos-base", "linux-" + "-".join(pkgname.split("-")[1:])] if flash_method in ["fastboot", "heimdall-bootimg"]: @@ -331,7 +331,7 @@ def generate_apkbuild(pkgname: str, name: str, arch: Arch, flash_method: str): handle.write(line[8:].replace(" " * 4, "\t") + "\n") -def generate(pkgname: str): +def generate(pkgname: str) -> None: arch = ask_for_architecture() manufacturer = ask_for_manufacturer() name = ask_for_name(manufacturer) diff --git a/pmb/aportgen/gcc.py b/pmb/aportgen/gcc.py index 3d8cb6bd..5d31b33f 100644 --- a/pmb/aportgen/gcc.py +++ b/pmb/aportgen/gcc.py @@ -8,7 +8,7 @@ import pmb.helpers.git import pmb.helpers.run -def generate(pkgname: str): +def generate(pkgname: str) -> None: # Copy original aport prefix = pkgname.split("-")[0] arch = Arch.from_str(pkgname.split("-")[1]) diff --git a/pmb/aportgen/linux.py b/pmb/aportgen/linux.py index e6b81fa3..f7724766 100644 --- a/pmb/aportgen/linux.py +++ b/pmb/aportgen/linux.py @@ -7,7 +7,7 @@ import pmb.aportgen.core import pmb.parse.apkindex -def generate_apkbuild(pkgname: str, deviceinfo: Deviceinfo, patches: list[str]): +def generate_apkbuild(pkgname: str, deviceinfo: Deviceinfo, patches: list[str]) -> None: device = "-".join(pkgname.split("-")[1:]) carch = deviceinfo.arch.kernel() @@ -117,7 +117,7 @@ def generate_apkbuild(pkgname: str, deviceinfo: Deviceinfo, patches: list[str]): hndl.write(line[8:].replace(" " * 4, "\t") + "\n") -def generate(pkgname: str): +def generate(pkgname: str) -> None: device = "-".join(pkgname.split("-")[1:]) deviceinfo = pmb.parse.deviceinfo(device) work = get_context().config.work diff --git a/pmb/build/_package.py b/pmb/build/_package.py index 11d22633..389096af 100644 --- a/pmb/build/_package.py +++ b/pmb/build/_package.py @@ -30,7 +30,7 @@ from pmb.core import Chroot from pmb.core.context import get_context -def check_build_for_arch(pkgname: str, arch: Arch): +def check_build_for_arch(pkgname: str, arch: Arch) -> bool: """Check if pmaport can be built or exists as binary for a specific arch. :returns: * True when it can be built @@ -67,7 +67,7 @@ def check_build_for_arch(pkgname: str, arch: Arch): raise RuntimeError(f"Can't build '{pkgname}' for architecture {arch}") -def get_depends(context: Context, apkbuild): +def get_depends(context: Context, apkbuild: dict[str, Any]) -> list[str]: """Alpine's abuild always builds/installs the "depends" and "makedepends" of a package before building it. @@ -95,7 +95,7 @@ def get_depends(context: Context, apkbuild): return ret -def get_pkgver(original_pkgver: str, original_source=False): +def get_pkgver(original_pkgver: str, original_source: bool = False) -> str: """Get the original pkgver when using the original source. Otherwise, get the pkgver with an appended suffix of current date and time. @@ -122,7 +122,14 @@ def output_path(arch: Arch, pkgname: str, pkgver: str, pkgrel: str) -> Path: return arch / f"{pkgname}-{pkgver}-r{pkgrel}.apk" -def finish(apkbuild, channel, arch, output: Path, chroot: Chroot, strict=False): +def finish( + apkbuild: dict[str, Any], + channel: str, + arch: Arch, + output: Path, + chroot: Chroot, + strict: bool = False, +) -> None: """Various finishing tasks that need to be done after a build.""" # Verify output file out_dir = get_context().config.work / "packages" / channel @@ -212,7 +219,9 @@ class BuildQueueItem(TypedDict): chroot: Chroot -def has_cyclical_dependency(unmet_deps: dict[str, list[str]], item: BuildQueueItem, dep: str): +def has_cyclical_dependency( + unmet_deps: dict[str, list[str]], item: BuildQueueItem, dep: str +) -> bool: pkgnames = [item["name"]] + list(item["apkbuild"]["subpackages"].keys()) for pkgname in pkgnames: @@ -245,7 +254,7 @@ def prioritise_build_queue(disarray: list[BuildQueueItem]) -> list[BuildQueueIte all_pkgnames.append(item["name"]) all_pkgnames += item["apkbuild"]["subpackages"].keys() - def queue_item(item: BuildQueueItem): + def queue_item(item: BuildQueueItem) -> None: queue.append(item) disarray.remove(item) all_pkgnames.remove(item["name"]) @@ -443,10 +452,10 @@ def packages( context: Context, pkgnames: list[str], arch: Arch | None = None, - force=False, - strict=False, - src=None, - bootstrap_stage=BootstrapStage.NONE, + force: bool = False, + strict: bool = False, + src: str | None = None, + bootstrap_stage: int = BootstrapStage.NONE, log_callback: Callable | None = None, ) -> list[str]: """ diff --git a/pmb/build/autodetect.py b/pmb/build/autodetect.py index 12766cab..85fd9957 100644 --- a/pmb/build/autodetect.py +++ b/pmb/build/autodetect.py @@ -3,7 +3,6 @@ from pathlib import Path from pmb.core.arch import Arch from pmb.helpers import logging -from typing import Any import pmb.config import pmb.chroot.apk @@ -11,11 +10,11 @@ import pmb.helpers.pmaports from pmb.core import Chroot from pmb.core.context import get_context from pmb.meta import Cache -from pmb.types import CrossCompileType +from pmb.types import Apkbuild, CrossCompileType # FIXME (#2324): type hint Arch -def arch_from_deviceinfo(pkgname, aport: Path) -> Arch | None: +def arch_from_deviceinfo(pkgname: str, aport: Path) -> Arch | None: """ The device- packages are noarch packages. But it only makes sense to build them for the device's architecture, which is specified in the deviceinfo @@ -39,7 +38,7 @@ def arch_from_deviceinfo(pkgname, aport: Path) -> Arch | None: @Cache("package") -def arch(package: str | dict[str, Any]) -> Arch: +def arch(package: str | Apkbuild) -> Arch: """ Find a good default in case the user did not specify for which architecture a package should be built. @@ -84,7 +83,7 @@ def arch(package: str | dict[str, Any]) -> Arch: return Arch.native() -def chroot(apkbuild: dict[str, str], arch: Arch) -> Chroot: +def chroot(apkbuild: Apkbuild, arch: Arch) -> Chroot: if arch == Arch.native(): return Chroot.native() @@ -94,7 +93,7 @@ def chroot(apkbuild: dict[str, str], arch: Arch) -> Chroot: return Chroot.buildroot(arch) -def crosscompile(apkbuild, arch: Arch) -> CrossCompileType: +def crosscompile(apkbuild: Apkbuild, arch: Arch) -> CrossCompileType: """Decide the type of compilation necessary to build a given APKBUILD.""" if not get_context().cross: return None diff --git a/pmb/build/backend.py b/pmb/build/backend.py index eaa04763..7957e510 100644 --- a/pmb/build/backend.py +++ b/pmb/build/backend.py @@ -1,6 +1,5 @@ import enum from pathlib import Path -from typing import Any from pmb.core.pkgrepo import pkgrepo_paths import pmb.helpers.run import pmb.chroot @@ -9,6 +8,7 @@ from pmb.core import Context from pmb.core.arch import Arch from pmb.core.chroot import Chroot from pmb.helpers import logging +from pmb.types import Apkbuild, CrossCompileType, Env class BootstrapStage(enum.IntEnum): @@ -21,7 +21,9 @@ class BootstrapStage(enum.IntEnum): # We don't need explicit representations of the other numbers. -def override_source(apkbuild, pkgver, src, chroot: Chroot = Chroot.native()): +def override_source( + apkbuild: Apkbuild, pkgver: str, src: str | None, chroot: Chroot = Chroot.native() +) -> None: """Mount local source inside chroot and append new functions (prepare() etc.) to the APKBUILD to make it use the local source. """ @@ -132,7 +134,7 @@ def mount_pmaports(chroot: Chroot = Chroot.native()) -> dict[str, Path]: return dest_paths -def link_to_git_dir(chroot: Chroot): +def link_to_git_dir(chroot: Chroot) -> None: """Make ``/home/pmos/build/.git`` point to the .git dir from pmaports.git, with a symlink so abuild does not fail (#1841). @@ -158,7 +160,7 @@ def link_to_git_dir(chroot: Chroot): pmb.chroot.user(["ln", "-sf", dest_paths["pmaports"] / ".git", "/home/pmos/build/.git"], chroot) -def handle_csum_failure(apkbuild, chroot: Chroot): +def handle_csum_failure(apkbuild: Apkbuild, chroot: Chroot) -> None: csum_fail_path = chroot / "tmp/apkbuild_verify_failed" if not csum_fail_path.exists(): return @@ -181,17 +183,17 @@ def handle_csum_failure(apkbuild, chroot: Chroot): def run_abuild( context: Context, - apkbuild: dict[str, Any], + apkbuild: Apkbuild, pkgver: str, - channel, + channel: str, arch: Arch, - strict=False, - force=False, - cross=None, + strict: bool = False, + force: bool = False, + cross: CrossCompileType = None, suffix: Chroot = Chroot.native(), - src=None, - bootstrap_stage=BootstrapStage.NONE, -): + src: str | None = None, + bootstrap_stage: int = BootstrapStage.NONE, +) -> None: """ Set up all environment variables and construct the abuild command (all depending on the cross-compiler method and target architecture), copy @@ -234,7 +236,7 @@ def run_abuild( ) # Environment variables - env = {"CARCH": arch, "SUDO_APK": "abuild-apk --no-progress"} + env: Env = {"CARCH": str(arch), "SUDO_APK": "abuild-apk --no-progress"} if cross == "native": hostspec = arch.alpine_triple() env["CROSS_COMPILE"] = hostspec + "-" diff --git a/pmb/build/envkernel.py b/pmb/build/envkernel.py index 5ed21061..8c56d016 100644 --- a/pmb/build/envkernel.py +++ b/pmb/build/envkernel.py @@ -12,7 +12,7 @@ import pmb.aportgen.core import pmb.build import pmb.build.autodetect import pmb.chroot -from pmb.types import PathString, PmbArgs +from pmb.types import Env, PathString, PmbArgs import pmb.helpers import pmb.helpers.mount import pmb.helpers.pmaports @@ -97,7 +97,7 @@ def find_kbuild_output_dir(function_body): ) -def modify_apkbuild(pkgname: str, aport: Path): +def modify_apkbuild(pkgname: str, aport: Path) -> None: """Modify kernel APKBUILD to package build output from envkernel.sh.""" work = get_context().config.work apkbuild_path = aport / "APKBUILD" @@ -119,7 +119,9 @@ def modify_apkbuild(pkgname: str, aport: Path): pmb.aportgen.core.rewrite(pkgname, apkbuild_path, fields=fields) -def run_abuild(context: Context, pkgname: str, arch: Arch, apkbuild_path: Path, kbuild_out): +def run_abuild( + context: Context, pkgname: str, arch: Arch, apkbuild_path: Path, kbuild_out: str +) -> None: """ Prepare build environment and run abuild. @@ -190,7 +192,7 @@ def run_abuild(context: Context, pkgname: str, arch: Arch, apkbuild_path: Path, pmb.helpers.run.root(cmd) # Create the apk package - env = { + env: Env = { "CARCH": str(arch), "CHOST": str(arch), "CBUILD": str(Arch.native()), @@ -211,7 +213,7 @@ def run_abuild(context: Context, pkgname: str, arch: Arch, apkbuild_path: Path, pmb.chroot.root(["rm", build_path / "src"]) -def package_kernel(args: PmbArgs): +def package_kernel(args: PmbArgs) -> None: """Frontend for 'pmbootstrap build --envkernel': creates a package from envkernel output.""" pkgname = args.packages[0] if len(args.packages) > 1 or not pkgname.startswith("linux-"): diff --git a/pmb/build/init.py b/pmb/build/init.py index f7d77b19..3089aa1d 100644 --- a/pmb/build/init.py +++ b/pmb/build/init.py @@ -13,9 +13,10 @@ import pmb.chroot.apk import pmb.helpers.run from pmb.core import Chroot from pmb.core.context import get_context +from pmb.types import CrossCompileType -def init_abuild_minimal(chroot: Chroot = Chroot.native(), build_pkgs: list[str] = []): +def init_abuild_minimal(chroot: Chroot = Chroot.native(), build_pkgs: list[str] = []) -> None: """Initialize a minimal chroot with abuild where one can do 'abuild checksum'.""" marker = chroot / "tmp/pmb_chroot_abuild_init_done" if os.path.exists(marker): @@ -111,7 +112,9 @@ def init(chroot: Chroot = Chroot.native()) -> bool: return True -def init_compiler(context: Context, depends, cross, arch: Arch): +def init_compiler( + context: Context, depends: list[str], cross: CrossCompileType, arch: Arch +) -> None: arch_str = str(arch) cross_pkgs = ["ccache-cross-symlinks", "abuild"] if "gcc4" in depends: diff --git a/pmb/build/kconfig.py b/pmb/build/kconfig.py index 96279706..a4a8e957 100644 --- a/pmb/build/kconfig.py +++ b/pmb/build/kconfig.py @@ -18,6 +18,7 @@ import pmb.helpers.pmaports import pmb.helpers.run import pmb.parse from pmb.core import Chroot +from pmb.types import Apkbuild, Env class KConfigUI(enum.Enum): @@ -45,7 +46,7 @@ class KConfigUI(enum.Enum): return self.value -def get_arch(apkbuild: dict[str, Any]) -> Arch: +def get_arch(apkbuild: Apkbuild) -> Arch: """Take the architecture from the APKBUILD or complain if it's ambiguous. This function only gets called if --arch is not set. @@ -76,7 +77,7 @@ def get_arch(apkbuild: dict[str, Any]) -> Arch: return Arch.from_str(apkbuild["arch"][0]) -def get_outputdir(pkgname: str, apkbuild: dict[str, Any]) -> Path: +def get_outputdir(pkgname: str, apkbuild: Apkbuild) -> Path: """Get the folder for the kernel compilation output. For most APKBUILDs, this is $builddir. But some older ones still use @@ -121,7 +122,7 @@ def get_outputdir(pkgname: str, apkbuild: dict[str, Any]) -> Path: ) -def extract_and_patch_sources(pkgname: str, arch) -> None: +def extract_and_patch_sources(pkgname: str, arch: Arch) -> None: pmb.build.copy_to_buildpath(pkgname) logging.info("(native) extract kernel source") pmb.chroot.user(["abuild", "unpack"], working_dir=Path("/home/pmos/build")) @@ -130,11 +131,18 @@ def extract_and_patch_sources(pkgname: str, arch) -> None: ["abuild", "prepare"], working_dir=Path("/home/pmos/build"), output="interactive", - env={"CARCH": arch}, + env={"CARCH": str(arch)}, ) -def _make(chroot: pmb.core.Chroot, make_command: str, env, pkgname, arch, apkbuild) -> None: +def _make( + chroot: pmb.core.Chroot, + make_command: str, + env: Env, + pkgname: str, + arch: Arch, + apkbuild: Apkbuild, +) -> None: aport = pmb.helpers.pmaports.find(pkgname) outputdir = get_outputdir(pkgname, apkbuild) @@ -155,7 +163,7 @@ def _make(chroot: pmb.core.Chroot, make_command: str, env, pkgname, arch, apkbui pmb.build.checksum.update(pkgname) -def _init(pkgname: str, arch: Arch | None) -> tuple[str, Arch, Any, Chroot, dict[str, str]]: +def _init(pkgname: str, arch: Arch | None) -> tuple[str, Arch, Any, Chroot, Env]: """ :returns: pkgname, arch, apkbuild, chroot, env """ diff --git a/pmb/build/other.py b/pmb/build/other.py index 26c4b684..ef157a78 100644 --- a/pmb/build/other.py +++ b/pmb/build/other.py @@ -4,6 +4,7 @@ import enum from pmb.helpers import logging import os from pathlib import Path +from typing import Any import shlex import datetime @@ -17,10 +18,13 @@ import pmb.helpers.run import pmb.parse.apkindex import pmb.parse.version from pmb.core import Chroot +from pmb.core.arch import Arch from pmb.core.context import get_context -def copy_to_buildpath(package, chroot: Chroot = Chroot.native(), no_override: bool = False): +def copy_to_buildpath( + package: str, chroot: Chroot = Chroot.native(), no_override: bool = False +) -> None: # Sanity check aport = pmb.helpers.pmaports.find(package) if not os.path.exists(aport / "APKBUILD"): @@ -49,7 +53,7 @@ def copy_to_buildpath(package, chroot: Chroot = Chroot.native(), no_override: bo pmb.chroot.root(["chown", "-R", "pmos:pmos", "/home/pmos/build"], chroot) -def abuild_overrides(apkbuild: Path): +def abuild_overrides(apkbuild: Path) -> None: """Override some abuild functions by patching the APKBUILD file.""" if apkbuild.is_relative_to(get_context().config.work / "cache_git"): @@ -77,7 +81,7 @@ class BuildStatus(enum.Enum): return self in [BuildStatus.OUTDATED, BuildStatus.NEW] -def get_status(arch, apkbuild) -> BuildStatus: +def get_status(arch: Arch | None, apkbuild: dict[str, Any]) -> BuildStatus: """Check if the package has already been built. Compared to abuild's check, this check also works for different architectures. @@ -170,7 +174,7 @@ def index_repo(arch=None): pmb.parse.apkindex.clear_cache(path / "APKINDEX.tar.gz") -def configure_abuild(chroot: Chroot, verify=False): +def configure_abuild(chroot: Chroot, verify: bool = False) -> None: """Set the correct JOBS count in ``abuild.conf``. :param verify: internally used to test if changing the config has worked. @@ -198,7 +202,7 @@ def configure_abuild(chroot: Chroot, verify=False): pmb.chroot.root(["sed", "-i", f"$ a\\{prefix}{jobs}", "/etc/abuild.conf"], chroot) -def configure_ccache(chroot: Chroot = Chroot.native(), verify=False): +def configure_ccache(chroot: Chroot = Chroot.native(), verify: bool = False) -> None: """Set the maximum ccache size. :param verify: internally used to test if changing the config has worked. diff --git a/pmb/chroot/apk.py b/pmb/chroot/apk.py index 3eb66a84..cfad6820 100644 --- a/pmb/chroot/apk.py +++ b/pmb/chroot/apk.py @@ -32,8 +32,11 @@ from pmb.helpers.exceptions import NonBugError @Cache("chroot", "user_repository", mirrors_exclude=[]) def update_repository_list( - chroot: Chroot, user_repository=False, mirrors_exclude: list[str] = [], check=False -): + chroot: Chroot, + user_repository: bool = False, + mirrors_exclude: list[str] = [], + check: bool = False, +) -> None: """ Update /etc/apk/repositories, if it is outdated (when the user changed the --mirror-alpine or --mirror-pmOS parameters). @@ -79,7 +82,7 @@ def update_repository_list( @Cache("chroot") -def check_min_version(chroot: Chroot = Chroot.native()): +def check_min_version(chroot: Chroot = Chroot.native()) -> None: """ Check the minimum apk version, before running it the first time in the current session (lifetime of one pmbootstrap call). @@ -179,7 +182,9 @@ def packages_get_locally_built_apks(package_list: list[str], arch: Arch) -> list # FIXME: list[Sequence[PathString]] weirdness # mypy: disable-error-code="operator" -def install_run_apk(to_add: list[str], to_add_local: list[Path], to_del: list[str], chroot: Chroot): +def install_run_apk( + to_add: list[str], to_add_local: list[Path], to_del: list[str], chroot: Chroot +) -> None: """ Run apk to add packages, and ensure only the desired packages get explicitly marked as installed. @@ -248,7 +253,7 @@ def install_run_apk(to_add: list[str], to_add_local: list[Path], to_del: list[st pmb.chroot.root(["apk", "--no-progress"] + command, chroot) -def install(packages, chroot: Chroot, build=True, quiet: bool = False): +def install(packages: list[str], chroot: Chroot, build: bool = True, quiet: bool = False) -> None: """ Install packages from pmbootstrap's local package index or the pmOS/Alpine binary package mirrors. Iterate over all dependencies recursively, and diff --git a/pmb/chroot/binfmt.py b/pmb/chroot/binfmt.py index 608f814b..43628033 100644 --- a/pmb/chroot/binfmt.py +++ b/pmb/chroot/binfmt.py @@ -11,11 +11,11 @@ import pmb.parse import pmb.chroot.apk -def is_registered(arch_qemu: Arch): +def is_registered(arch_qemu: Arch) -> bool: return os.path.exists(f"/proc/sys/fs/binfmt_misc/qemu-{arch_qemu}") -def register(arch: Arch): +def register(arch: Arch) -> None: """ Get arch, magic, mask. """ @@ -56,7 +56,7 @@ def register(arch: Arch): pmb.helpers.run.root(["sh", "-c", 'echo "' + code + '" > ' + register]) -def unregister(arch: Arch): +def unregister(arch: Arch) -> None: arch_qemu = arch.qemu() binfmt_file = "/proc/sys/fs/binfmt_misc/qemu-" + arch_qemu if not os.path.exists(binfmt_file): diff --git a/pmb/chroot/init.py b/pmb/chroot/init.py index 32b825ce..1bd6323d 100644 --- a/pmb/chroot/init.py +++ b/pmb/chroot/init.py @@ -30,7 +30,7 @@ class UsrMerge(enum.Enum): OFF = 2 -def copy_resolv_conf(chroot: Chroot): +def copy_resolv_conf(chroot: Chroot) -> None: """ Use pythons super fast file compare function (due to caching) and copy the /etc/resolv.conf to the chroot, in case it is @@ -46,7 +46,7 @@ def copy_resolv_conf(chroot: Chroot): pmb.helpers.run.root(["touch", resolv_path]) -def mark_in_chroot(chroot: Chroot = Chroot.native()): +def mark_in_chroot(chroot: Chroot = Chroot.native()) -> None: """ Touch a flag so we can know when we're running in chroot (and don't accidentally flash partitions on our host). This marker @@ -74,7 +74,7 @@ def init_keys(): pmb.helpers.run.root(["cp", key, target]) -def init_usr_merge(chroot: Chroot): +def init_usr_merge(chroot: Chroot) -> None: logging.info(f"({chroot}) merge /usr") script = f"{pmb.config.pmb_src}/pmb/data/merge-usr.sh" pmb.helpers.run.root(["sh", "-e", script, "CALLED_FROM_PMB", chroot.path]) @@ -104,7 +104,7 @@ def warn_if_chroots_outdated(): @Cache("chroot") -def init(chroot: Chroot, usr_merge=UsrMerge.AUTO): +def init(chroot: Chroot, usr_merge: UsrMerge = UsrMerge.AUTO) -> None: """ Initialize a chroot by copying the resolv.conf and updating /etc/apk/repositories. If /bin/sh is missing, create the chroot from diff --git a/pmb/chroot/initfs.py b/pmb/chroot/initfs.py index 6eb1455c..6bbdbba0 100644 --- a/pmb/chroot/initfs.py +++ b/pmb/chroot/initfs.py @@ -12,7 +12,7 @@ from pmb.core import Chroot from pmb.core.context import get_context -def build(flavor, chroot: Chroot): +def build(flavor: str | None, chroot: Chroot) -> None: # Update mkinitfs and hooks pmb.chroot.apk.install(["postmarketos-mkinitfs"], chroot) pmb.chroot.initfs_hooks.update(chroot) @@ -31,7 +31,7 @@ def build(flavor, chroot: Chroot): pmb.chroot.root(["mkinitfs", "-o", f"/boot/initramfs-{flavor}", release], chroot) -def extract(flavor, chroot: Chroot, extra=False): +def extract(flavor: str | None, chroot: Chroot, extra: bool = False) -> Path: """ Extract the initramfs to /tmp/initfs-extracted or the initramfs-extra to /tmp/initfs-extra-extracted and return the outside extraction path. @@ -86,7 +86,7 @@ def ls(flavor, suffix, extra=False): pmb.chroot.root(["rm", "-r", tmp], suffix) -def frontend(args: PmbArgs): +def frontend(args: PmbArgs) -> None: # Find the appropriate kernel flavor context = get_context() chroot = Chroot.rootfs(context.config.device) diff --git a/pmb/chroot/initfs_hooks.py b/pmb/chroot/initfs_hooks.py index f09c71e9..781138e4 100644 --- a/pmb/chroot/initfs_hooks.py +++ b/pmb/chroot/initfs_hooks.py @@ -9,7 +9,7 @@ import pmb.chroot.apk from pmb.core import Chroot -def list_chroot(suffix: Chroot, remove_prefix=True): +def list_chroot(suffix: Chroot, remove_prefix: bool = True) -> list[str]: ret = [] prefix = pmb.config.initfs_hook_prefix for pkgname in pmb.chroot.apk.installed(suffix).keys(): @@ -21,7 +21,7 @@ def list_chroot(suffix: Chroot, remove_prefix=True): return ret -def list_aports(): +def list_aports() -> list[str]: ret = [] prefix = pmb.config.initfs_hook_prefix for path in pkgrepo_iglob(f"*/{prefix}*"): @@ -29,7 +29,7 @@ def list_aports(): return ret -def ls(suffix: Chroot): +def ls(suffix: Chroot) -> None: hooks_chroot = list_chroot(suffix) hooks_aports = list_aports() @@ -38,7 +38,7 @@ def ls(suffix: Chroot): logging.info(line) -def add(hook, suffix: Chroot): +def add(hook: str, suffix: Chroot) -> None: if hook not in list_aports(): raise RuntimeError( "Invalid hook name!" " Run 'pmbootstrap initfs hook_ls'" " to get a list of all hooks." @@ -47,14 +47,14 @@ def add(hook, suffix: Chroot): pmb.chroot.apk.install([f"{prefix}{hook}"], suffix) -def delete(hook, suffix: Chroot): +def delete(hook: str, suffix: Chroot) -> None: if hook not in list_chroot(suffix): raise RuntimeError("There is no such hook installed!") prefix = pmb.config.initfs_hook_prefix pmb.chroot.root(["apk", "del", f"{prefix}{hook}"], suffix) -def update(suffix: Chroot): +def update(suffix: Chroot) -> None: """ Rebuild and update all hooks that are out of date """ diff --git a/pmb/chroot/mount.py b/pmb/chroot/mount.py index c0bbf603..8644d5c2 100644 --- a/pmb/chroot/mount.py +++ b/pmb/chroot/mount.py @@ -15,7 +15,7 @@ from pmb.core import Chroot from pmb.core.context import get_context -def mount_chroot_image(chroot: Chroot): +def mount_chroot_image(chroot: Chroot) -> None: """Mount an IMAGE type chroot, to modify an existing rootfs image. This doesn't support split images yet!""" # Make sure everything is nicely unmounted just to be super safe @@ -42,7 +42,7 @@ def mount_chroot_image(chroot: Chroot): logging.info(f"({chroot}) mounted {chroot.name}") -def create_device_nodes(chroot: Chroot): +def create_device_nodes(chroot: Chroot) -> None: """ Create device nodes for null, zero, full, random, urandom in the chroot. """ @@ -90,7 +90,7 @@ def create_device_nodes(chroot: Chroot): raise RuntimeError(f"Failed to create device nodes in the '{chroot}' chroot.") -def mount_dev_tmpfs(chroot: Chroot = Chroot.native()): +def mount_dev_tmpfs(chroot: Chroot = Chroot.native()) -> None: """ Mount tmpfs inside the chroot's dev folder to make sure we can create device nodes, even if the filesystem of the work folder does not support @@ -116,7 +116,7 @@ def mount_dev_tmpfs(chroot: Chroot = Chroot.native()): pmb.helpers.run.root(["ln", "-sf", "/proc/self/fd", f"{dev}/"]) -def mount(chroot: Chroot): +def mount(chroot: Chroot) -> None: if chroot.type == ChrootType.IMAGE and not pmb.mount.ismount(chroot.path): mount_chroot_image(chroot) @@ -154,7 +154,7 @@ def mount(chroot: Chroot): ) -def mount_native_into_foreign(chroot: Chroot): +def mount_native_into_foreign(chroot: Chroot) -> None: source = Chroot.native().path target = chroot / "native" pmb.helpers.mount.bind(source, target) @@ -166,7 +166,7 @@ def mount_native_into_foreign(chroot: Chroot): # pmb.helpers.run.root(["ln", "-sf", "/native/usr/bin/pigz", "/usr/local/bin/pigz"]) -def remove_mnt_pmbootstrap(chroot: Chroot): +def remove_mnt_pmbootstrap(chroot: Chroot) -> None: """Safely remove /mnt/pmbootstrap directories from the chroot, without running rm -r as root and potentially removing data inside the mountpoint in case it was still mounted (bug in pmbootstrap, or user diff --git a/pmb/chroot/other.py b/pmb/chroot/other.py index 624da409..249546ab 100644 --- a/pmb/chroot/other.py +++ b/pmb/chroot/other.py @@ -8,7 +8,7 @@ import pmb.install from pmb.core import Chroot -def kernel_flavor_installed(chroot: Chroot, autoinstall=True) -> str | None: +def kernel_flavor_installed(chroot: Chroot, autoinstall: bool = True) -> str | None: """ Get installed kernel flavor. Optionally install the device's kernel beforehand. diff --git a/pmb/chroot/run.py b/pmb/chroot/run.py index 781e538b..dc3345d0 100644 --- a/pmb/chroot/run.py +++ b/pmb/chroot/run.py @@ -3,7 +3,9 @@ import os from pathlib import Path, PurePath import shutil +import subprocess from collections.abc import Sequence +from typing import overload, Literal import pmb.config import pmb.chroot @@ -11,7 +13,14 @@ import pmb.chroot.binfmt import pmb.helpers.run import pmb.helpers.run_core from pmb.core import Chroot -from pmb.types import Env, PathString +from pmb.types import ( + Env, + PathString, + RunOutputType, + RunOutputTypeDefault, + RunOutputTypePopen, + RunReturnType, +) def executables_absolute_path(): @@ -35,13 +44,13 @@ def rootm( cmds: Sequence[Sequence[PathString]], chroot: Chroot = Chroot.native(), working_dir: PurePath = PurePath("/"), - output="log", - output_return=False, - check=None, - env={}, - disable_timeout=False, - add_proxy_env_vars=True, -): + output: RunOutputType = "log", + output_return: bool = False, + check: bool | None = None, + env: Env = {}, + disable_timeout: bool = False, + add_proxy_env_vars: bool = True, +) -> RunReturnType: """ Run a list of commands inside a chroot as root. @@ -110,17 +119,59 @@ def rootm( ) +@overload +def root( + cmds: Sequence[PathString], + chroot: Chroot = ..., + working_dir: PurePath = ..., + output: RunOutputTypePopen = ..., + output_return: Literal[False] = ..., + check: bool | None = ..., + env: Env = ..., + disable_timeout: bool = ..., + add_proxy_env_vars: bool = ..., +) -> subprocess.Popen: ... + + +@overload +def root( + cmds: Sequence[PathString], + chroot: Chroot = ..., + working_dir: PurePath = ..., + output: RunOutputTypeDefault = ..., + output_return: Literal[False] = ..., + check: bool | None = ..., + env: Env = ..., + disable_timeout: bool = ..., + add_proxy_env_vars: bool = ..., +) -> int: ... + + +@overload +def root( + cmds: Sequence[PathString], + chroot: Chroot = ..., + working_dir: PurePath = ..., + output: RunOutputType = ..., + output_return: Literal[True] = ..., + check: bool | None = ..., + env: Env = ..., + disable_timeout: bool = ..., + add_proxy_env_vars: bool = ..., +) -> str: ... + + def root( cmds: Sequence[PathString], chroot: Chroot = Chroot.native(), working_dir: PurePath = PurePath("/"), - output="log", - output_return=False, - check=None, - env={}, - disable_timeout=False, - add_proxy_env_vars=True, -): + output: RunOutputType = "log", + output_return: bool = False, + check: bool | None = None, + env: Env = {}, + disable_timeout: bool = False, + add_proxy_env_vars: bool = True, +) -> RunReturnType: return rootm( [cmds], chroot, @@ -138,11 +189,11 @@ def userm( cmds: Sequence[Sequence[PathString]], chroot: Chroot = Chroot.native(), working_dir: Path = Path("/"), - output="log", - output_return=False, - check=None, - env={}, -): + output: RunOutputType = "log", + output_return: bool = False, + check: bool | None = None, + env: Env = {}, +) -> RunReturnType: """ Run a command inside a chroot as "user". We always use the BusyBox implementation of 'su', because other implementations may override the PATH @@ -162,24 +213,61 @@ def userm( flat_cmd = pmb.helpers.run_core.flat_cmd(cmds, env=env) cmd = ["busybox", "su", "pmos", "-c", flat_cmd] - return pmb.chroot.root( + # Can't figure out why this one fails :( + return pmb.chroot.root( # type: ignore[call-overload] cmd, chroot, working_dir, output, output_return, check, {}, add_proxy_env_vars=False ) +@overload +def user( + cmd: Sequence[PathString], + chroot: Chroot = ..., + working_dir: Path = ..., + output: RunOutputTypePopen = ..., + output_return: Literal[False] = ..., + check: bool | None = ..., + env: Env = ..., +) -> subprocess.Popen: ... + + +@overload +def user( + cmd: Sequence[PathString], + chroot: Chroot = ..., + working_dir: Path = ..., + output: RunOutputTypeDefault = ..., + output_return: Literal[False] = ..., + check: bool | None = ..., + env: Env = ..., +) -> int: ... + + +@overload +def user( + cmd: Sequence[PathString], + chroot: Chroot = ..., + working_dir: Path = ..., + output: RunOutputType = ..., + output_return: Literal[True] = ..., + check: bool | None = ..., + env: Env = ..., +) -> str: ... + + def user( cmd: Sequence[PathString], chroot: Chroot = Chroot.native(), working_dir: Path = Path("/"), - output="log", - output_return=False, - check=None, - env={}, -): + output: RunOutputType = "log", + output_return: bool = False, + check: bool | None = None, + env: Env = {}, +) -> RunReturnType: return userm([cmd], chroot, working_dir, output, output_return, check, env) -def exists(username, chroot: Chroot = Chroot.native()): +def exists(username: str, chroot: Chroot = Chroot.native()) -> bool: """ Checks if username exists in the system diff --git a/pmb/chroot/shutdown.py b/pmb/chroot/shutdown.py index b4d7eedf..4dff3dfc 100644 --- a/pmb/chroot/shutdown.py +++ b/pmb/chroot/shutdown.py @@ -33,7 +33,7 @@ def kill_sccache(): pmb.chroot.root(["sccache", "--stop-server"]) -def shutdown_cryptsetup_device(name: str): +def shutdown_cryptsetup_device(name: str) -> None: """ :param name: cryptsetup device name, usually "pm_crypt" in pmbootstrap """ diff --git a/pmb/chroot/zap.py b/pmb/chroot/zap.py index e288496d..c909472b 100644 --- a/pmb/chroot/zap.py +++ b/pmb/chroot/zap.py @@ -20,7 +20,7 @@ from pmb.core import Chroot from pmb.core.context import get_context -def del_chroot(path: Path, confirm=True, dry=False): +def del_chroot(path: Path, confirm: bool = True, dry: bool = False) -> None: if confirm and not pmb.helpers.cli.confirm(f"Remove {path}?"): return if dry: @@ -37,16 +37,16 @@ def del_chroot(path: Path, confirm=True, dry=False): def zap( - confirm=True, - dry=False, - pkgs_local=False, - http=False, - pkgs_local_mismatch=False, - pkgs_online_mismatch=False, - distfiles=False, - rust=False, - netboot=False, -): + confirm: bool = True, + dry: bool = False, + pkgs_local: bool = False, + http: bool = False, + pkgs_local_mismatch: bool = False, + pkgs_online_mismatch: bool = False, + distfiles: bool = False, + rust: bool = False, + netboot: bool = False, +) -> None: """ Shutdown everything inside the chroots (e.g. adb), umount everything and then safely remove folders from the work-directory. diff --git a/pmb/ci/__init__.py b/pmb/ci/__init__.py index 9e1cf9f8..8d6c9cd0 100644 --- a/pmb/ci/__init__.py +++ b/pmb/ci/__init__.py @@ -6,7 +6,7 @@ from pmb.helpers import logging import os from pathlib import Path import pmb.chroot -from pmb.types import PmbArgs +from pmb.types import Env, PmbArgs import pmb.helpers.cli from pmb.core import Chroot @@ -176,7 +176,7 @@ def run_scripts(topdir, scripts): copy_git_repo_to_chroot(topdir) repo_copied = True - env = {"TESTUSER": "pmos"} + env: Env = {"TESTUSER": "pmos"} rc = pmb.chroot.root( [script_path], check=False, env=env, working_dir=Path("/home/pmos/ci"), output="tui" ) diff --git a/pmb/commands/__init__.py b/pmb/commands/__init__.py index 3aec50a1..01acefa2 100644 --- a/pmb/commands/__init__.py +++ b/pmb/commands/__init__.py @@ -55,7 +55,7 @@ unmigrated_commands = [ ] -def run_command(args: PmbArgs): +def run_command(args: PmbArgs) -> None: # Handle deprecated command format if args.action in unmigrated_commands: getattr(frontend, args.action)(args) diff --git a/pmb/commands/repo_bootstrap.py b/pmb/commands/repo_bootstrap.py index 1101567a..32a611d5 100644 --- a/pmb/commands/repo_bootstrap.py +++ b/pmb/commands/repo_bootstrap.py @@ -114,7 +114,7 @@ class RepoBootstrap(commands.Command): bootstrap_stage = int(step.split("bootstrap_", 1)[1]) - def log_wrapper(pkg: BuildQueueItem): + def log_wrapper(pkg: BuildQueueItem) -> None: self.log_progress(f"building {pkg['name']}") packages = self.get_packages(bootstrap_line) diff --git a/pmb/config/file.py b/pmb/config/file.py index 4a27ef3d..4eba7162 100644 --- a/pmb/config/file.py +++ b/pmb/config/file.py @@ -50,7 +50,7 @@ def load(path: Path) -> Config: return config -def serialize(config: Config, skip_defaults=True) -> configparser.ConfigParser: +def serialize(config: Config, skip_defaults: bool = True) -> configparser.ConfigParser: """Serialize the config object into a ConfigParser to write it out in the pmbootstrap_v3.cfg INI format. @@ -91,7 +91,7 @@ def serialize(config: Config, skip_defaults=True) -> configparser.ConfigParser: # FIXME: we should have distinct Config and ConfigFile types -def save(output: Path, config: Config): +def save(output: Path, config: Config) -> None: """Save the config object to the specified path. IMPORTANT: The global config (available via get_context().config) diff --git a/pmb/config/init.py b/pmb/config/init.py index 2da3624e..4f4be192 100644 --- a/pmb/config/init.py +++ b/pmb/config/init.py @@ -210,7 +210,7 @@ def ask_for_ui(deviceinfo: Deviceinfo) -> str: ) -def ask_for_ui_extras(config: Config, ui): +def ask_for_ui_extras(config: Config, ui: str) -> bool: apkbuild = pmb.helpers.pmaports.get( f"postmarketos-ui-{ui}", subpackages=False, must_exist=False ) @@ -226,7 +226,7 @@ def ask_for_ui_extras(config: Config, ui): return pmb.helpers.cli.confirm("Enable this package?", default=config.ui_extras) -def ask_for_systemd(config: Config, ui): +def ask_for_systemd(config: Config, ui: str) -> SystemdConfig: if "systemd" not in pmb.config.pmaports.read_config_repos(): return config.systemd @@ -292,7 +292,7 @@ def ask_for_timezone() -> str: return "GMT" -def ask_for_provider_select(apkbuild, providers_cfg) -> None: +def ask_for_provider_select(apkbuild: dict[str, Any], providers_cfg: dict[str, str]) -> None: """Ask for selectable providers that are specified using "_pmb_select" in a APKBUILD. :param apkbuild: the APKBUILD with the _pmb_select diff --git a/pmb/config/other.py b/pmb/config/other.py index d740fea9..4fc1d38f 100644 --- a/pmb/config/other.py +++ b/pmb/config/other.py @@ -8,7 +8,7 @@ from pmb.meta import Cache @Cache() -def is_systemd_selected(config: Config): +def is_systemd_selected(config: Config) -> bool: if "systemd" not in pmb.config.pmaports.read_config_repos(): return False if pmb.helpers.ui.check_option(config.ui, "pmb:systemd-never", skip_extra_repos=True): @@ -20,7 +20,7 @@ def is_systemd_selected(config: Config): return pmb.helpers.ui.check_option(config.ui, "pmb:systemd", skip_extra_repos=True) -def systemd_selected_str(config: Config): +def systemd_selected_str(config: Config) -> tuple[str, str]: if "systemd" not in pmb.config.pmaports.read_config_repos(): return "no", "not supported by pmaports branch" if pmb.helpers.ui.check_option(config.ui, "pmb:systemd-never"): diff --git a/pmb/config/pmaports.py b/pmb/config/pmaports.py index e56dc3fe..7b427465 100644 --- a/pmb/config/pmaports.py +++ b/pmb/config/pmaports.py @@ -6,6 +6,7 @@ from pmb.core.pkgrepo import pkgrepo_default_path, pkgrepo_paths, pkgrepo_relati from pmb.helpers import logging import os import sys +from typing import cast, Any import pmb.config from pmb.meta import Cache @@ -14,7 +15,7 @@ import pmb.helpers.pmaports import pmb.parse.version -def clone(): +def clone() -> None: logging.info( "Setting up the native chroot and cloning the package build" " recipes (pmaports)..." ) @@ -23,7 +24,7 @@ def clone(): pmb.helpers.git.clone("pmaports") -def check_version_pmaports(real): +def check_version_pmaports(real: str) -> None: # Compare versions min = pmb.config.pmaports_min_version if pmb.parse.version.compare(real, min) >= 0: @@ -41,7 +42,7 @@ def check_version_pmaports(real): raise RuntimeError("Run 'pmbootstrap pull' to update your pmaports.") -def check_version_pmbootstrap(min_ver): +def check_version_pmbootstrap(min_ver: str) -> None: # Compare versions real = pmb.__version__ if pmb.parse.version.compare(real, min_ver) >= 0: @@ -71,7 +72,7 @@ def check_version_pmbootstrap(min_ver): @Cache() -def read_config_repos(): +def read_config_repos() -> dict[str, configparser.SectionProxy]: """Read the sections starting with "repo:" from pmaports.cfg.""" cfg = configparser.ConfigParser() @@ -88,7 +89,7 @@ def read_config_repos(): @Cache("aports") -def read_config(aports: Path | None = None): +def read_config(aports: Path | None = None) -> dict[str, Any]: """Read and verify pmaports.cfg. If aports is not specified and systemd is enabled, the returned channel will be the systemd one (e.g. systemd-edge instead of edge) @@ -128,7 +129,8 @@ def read_config(aports: Path | None = None): if systemd: ret["channel"] = "systemd-" + ret["channel"] - return ret + # FIXME: This is a hack to work around python/typeshed issue #12919 + return cast(dict[str, Any], ret) def all_channels() -> list[str]: @@ -141,7 +143,7 @@ def all_channels() -> list[str]: return list(ret) -def read_config_channel(): +def read_config_channel() -> dict[str, str]: """Get the properties of the currently active channel in pmaports.git. As specified in channels.cfg (https://postmarketos.org/channels.cfg). @@ -180,13 +182,13 @@ def read_config_channel(): ) -def init(): +def init() -> None: if not os.path.exists(pkgrepo_default_path()): clone() read_config() -def switch_to_channel_branch(channel_new): +def switch_to_channel_branch(channel_new: str) -> bool: """Checkout the channel's branch in pmaports.git. :channel_new: channel name (e.g. "edge", "v21.03") @@ -236,7 +238,7 @@ def switch_to_channel_branch(channel_new): return True -def install_githooks(): +def install_githooks() -> None: aports = pkgrepo_default_path() hooks_dir = aports / ".githooks" if not hooks_dir.exists(): diff --git a/pmb/config/workdir.py b/pmb/config/workdir.py index 1d8f17de..1d4842aa 100644 --- a/pmb/config/workdir.py +++ b/pmb/config/workdir.py @@ -17,7 +17,7 @@ from pmb.core.context import get_context from pmb.helpers import logging -def chroot_save_init(suffix: Chroot): +def chroot_save_init(suffix: Chroot) -> None: """Save the chroot initialization data in $WORK/workdir.cfg.""" # Read existing cfg cfg = configparser.ConfigParser() @@ -48,7 +48,7 @@ def chroots_outdated() -> list[Chroot]: ... def chroots_outdated(chroot: Chroot) -> bool: ... -def chroots_outdated(chroot: Chroot | None = None): +def chroots_outdated(chroot: Chroot | None = None) -> bool | list[Chroot]: """Check if init dates from workdir.cfg indicate that any chroot is outdated. diff --git a/pmb/core/config.py b/pmb/core/config.py index 49827ac4..2a1ce1fc 100644 --- a/pmb/core/config.py +++ b/pmb/core/config.py @@ -107,7 +107,7 @@ class Config: else: raise ValueError(f"Invalid dotted key: {dotted_key}") - def __setattr__(self, key: str, value: Any): + def __setattr__(self, key: str, value: Any) -> None: """Allow for setattr() to be used with a dotted key to set nested dictionaries (e.g. "mirrors.alpine").""" keys = key.split(".") diff --git a/pmb/core/context.py b/pmb/core/context.py index fd555faf..434f50e1 100644 --- a/pmb/core/context.py +++ b/pmb/core/context.py @@ -56,7 +56,7 @@ def get_context(allow_failure: bool = False) -> Context: return __context -def set_context(context: Context): +def set_context(context: Context) -> None: """Set global runtime context.""" global __context diff --git a/pmb/core/pkgrepo.py b/pmb/core/pkgrepo.py index a1af27aa..b4935c3d 100644 --- a/pmb/core/pkgrepo.py +++ b/pmb/core/pkgrepo.py @@ -9,7 +9,7 @@ from pmb.meta import Cache @Cache(skip_extras=False) -def pkgrepo_paths(skip_extras=False) -> list[Path]: +def pkgrepo_paths(skip_extras: bool = False) -> list[Path]: config = get_context().config paths = list(map(lambda x: Path(x), config.aports)) if not paths: @@ -32,7 +32,7 @@ def pkgrepo_default_path() -> Path: return pkgrepo_paths(skip_extras=True)[0] -def pkgrepo_names(skip_exras=False) -> list[str]: +def pkgrepo_names(skip_exras: bool = False) -> list[str]: """ Return a list of all the package repository names. """ @@ -78,7 +78,7 @@ def pkgrepo_glob_one(path: str) -> Path | None: return None -def pkgrepo_iglob(path: str, recursive=False) -> Generator[Path, None, None]: +def pkgrepo_iglob(path: str, recursive: bool = False) -> Generator[Path, None, None]: """ Yield each matching glob over each aports repository. """ @@ -91,7 +91,7 @@ def pkgrepo_iglob(path: str, recursive=False) -> Generator[Path, None, None]: yield pdir -def pkgrepo_iter_package_dirs(skip_extra_repos=False) -> Generator[Path, None, None]: +def pkgrepo_iter_package_dirs(skip_extra_repos: bool = False) -> Generator[Path, None, None]: """ Yield each matching glob over each aports repository. Detect duplicates within the same aports repository but otherwise diff --git a/pmb/export/frontend.py b/pmb/export/frontend.py index aa5cb5fb..8012357c 100644 --- a/pmb/export/frontend.py +++ b/pmb/export/frontend.py @@ -10,7 +10,7 @@ import pmb.export from pmb.core import Chroot, ChrootType -def frontend(args: PmbArgs): # FIXME: ARGS_REFACTOR +def frontend(args: PmbArgs) -> None: # FIXME: ARGS_REFACTOR config = get_context().config # Create the export folder target = args.export_folder diff --git a/pmb/export/odin.py b/pmb/export/odin.py index afa18f57..2526bd41 100644 --- a/pmb/export/odin.py +++ b/pmb/export/odin.py @@ -11,7 +11,7 @@ import pmb.helpers.file from pmb.core import Chroot, ChrootType -def odin(device: str, flavor, folder: Path): +def odin(device: str, flavor: str, folder: Path) -> None: """ Create Odin flashable tar file with kernel and initramfs for devices configured with the flasher method 'heimdall-isorec' diff --git a/pmb/export/symlinks.py b/pmb/export/symlinks.py index ca6019ad..cdb9bb1f 100644 --- a/pmb/export/symlinks.py +++ b/pmb/export/symlinks.py @@ -13,7 +13,7 @@ import pmb.helpers.file from pmb.core import Chroot, ChrootType -def symlinks(flavor, folder: Path): +def symlinks(flavor: str, folder: Path) -> None: """ Create convenience symlinks to the rootfs and boot files. """ diff --git a/pmb/flasher/init.py b/pmb/flasher/init.py index 87b028f9..bfbc7ac2 100644 --- a/pmb/flasher/init.py +++ b/pmb/flasher/init.py @@ -42,7 +42,7 @@ def install_depends(method: str) -> None: pmb.chroot.apk.install(depends, Chroot.native()) -def init(device: str, method: str): +def init(device: str, method: str) -> None: install_depends(method) # Mount folders from host system diff --git a/pmb/flasher/run.py b/pmb/flasher/run.py index b8eaef0d..c5bb8a64 100644 --- a/pmb/flasher/run.py +++ b/pmb/flasher/run.py @@ -6,7 +6,7 @@ import pmb.chroot.initfs import pmb.helpers.args -def check_partition_blacklist(deviceinfo: Deviceinfo, key, value): +def check_partition_blacklist(deviceinfo: Deviceinfo, key: str, value: str) -> None: if not key.startswith("$PARTITION_"): return diff --git a/pmb/flasher/variables.py b/pmb/flasher/variables.py index b2f976e1..563e5088 100644 --- a/pmb/flasher/variables.py +++ b/pmb/flasher/variables.py @@ -12,7 +12,7 @@ def variables( no_reboot: bool | None, partition: str | None, resume: bool | None, -) -> dict[str, str]: +) -> dict[str, str | None]: device = get_context().config.device deviceinfo = pmb.parse.deviceinfo() _cmdline = deviceinfo.kernel_cmdline or "" diff --git a/pmb/helpers/apk.py b/pmb/helpers/apk.py index ace74e43..05286651 100644 --- a/pmb/helpers/apk.py +++ b/pmb/helpers/apk.py @@ -66,7 +66,7 @@ def _compute_progress(line): return cur / tot if tot > 0 else 0 -def apk_with_progress(command: Sequence[PathString], chroot: Chroot | None = None): +def apk_with_progress(command: Sequence[PathString], chroot: Chroot | None = None) -> None: """Run an apk subcommand while printing a progress bar to STDOUT. :param command: apk subcommand in list form @@ -86,7 +86,10 @@ def apk_with_progress(command: Sequence[PathString], chroot: Chroot | None = Non with pmb.helpers.run.root(["cat", fifo], output="pipe") as p_cat: with pmb.helpers.run.root(command_with_progress, output="background") as p_apk: while p_apk.poll() is None: - line = p_cat.stdout.readline().decode("utf-8") + p_cat_stdout = p_cat.stdout + if p_cat_stdout is None: + raise RuntimeError("cat process had no stdout?") + line = p_cat_stdout.readline().decode("utf-8") progress = _compute_progress(line) pmb.helpers.cli.progress_print(progress) pmb.helpers.cli.progress_flush() diff --git a/pmb/helpers/aportupgrade.py b/pmb/helpers/aportupgrade.py index 52d160a1..88fcf923 100644 --- a/pmb/helpers/aportupgrade.py +++ b/pmb/helpers/aportupgrade.py @@ -6,12 +6,19 @@ from pmb.helpers import logging import os import re import urllib.parse +from typing import TypedDict -from pmb.types import PmbArgs +from pmb.types import Apkbuild, PmbArgs import pmb.helpers.file import pmb.helpers.http import pmb.helpers.pmaports + +class PackageVersionInfo(TypedDict): + sha: str + date: datetime.datetime + + req_headers: dict[str, str] = {} req_headers_github: dict[str, str] = {} @@ -47,7 +54,7 @@ def init_req_headers() -> None: ) -def get_package_version_info_github(repo_name: str, ref: str | None): +def get_package_version_info_github(repo_name: str, ref: str | None) -> PackageVersionInfo: logging.debug(f"Trying GitHub repository: {repo_name}") # Get the URL argument to request a special ref, if needed @@ -63,13 +70,15 @@ def get_package_version_info_github(repo_name: str, ref: str | None): commit_date = latest_commit["commit"]["committer"]["date"] # Extract the time from the field date = datetime.datetime.strptime(commit_date, "%Y-%m-%dT%H:%M:%SZ") - return { - "sha": latest_commit["sha"], - "date": date, - } + return PackageVersionInfo( + sha=latest_commit["sha"], + date=date, + ) -def get_package_version_info_gitlab(gitlab_host: str, repo_name: str, ref: str | None): +def get_package_version_info_gitlab( + gitlab_host: str, repo_name: str, ref: str | None +) -> PackageVersionInfo: logging.debug(f"Trying GitLab repository: {repo_name}") repo_name_safe = urllib.parse.quote(repo_name, safe="") @@ -89,13 +98,13 @@ def get_package_version_info_gitlab(gitlab_host: str, repo_name: str, ref: str | # Extract the time from the field # 2019-10-14T09:32:00.000Z / 2019-12-27T07:58:53.000-05:00 date = datetime.datetime.strptime(commit_date, "%Y-%m-%dT%H:%M:%S.000%z") - return { - "sha": latest_commit["id"], - "date": date, - } + return PackageVersionInfo( + sha=latest_commit["id"], + date=date, + ) -def upgrade_git_package(args: PmbArgs, pkgname: str, package) -> None: +def upgrade_git_package(args: PmbArgs, pkgname: str, package: Apkbuild) -> None: """Update _commit/pkgver/pkgrel in a git-APKBUILD (or pretend to do it if args.dry is set). :param pkgname: the package name @@ -169,7 +178,7 @@ def upgrade_git_package(args: PmbArgs, pkgname: str, package) -> None: return -def upgrade_stable_package(args: PmbArgs, pkgname: str, package) -> None: +def upgrade_stable_package(args: PmbArgs, pkgname: str, package: Apkbuild) -> None: """ Update _commit/pkgver/pkgrel in an APKBUILD (or pretend to do it if args.dry is set). @@ -254,7 +263,7 @@ def upgrade_stable_package(args: PmbArgs, pkgname: str, package) -> None: return -def upgrade(args: PmbArgs, pkgname, git=True, stable=True) -> None: +def upgrade(args: PmbArgs, pkgname: str, git: bool = True, stable: bool = True) -> None: """Find new versions of a single package and upgrade it. :param pkgname: the name of the package diff --git a/pmb/helpers/devices.py b/pmb/helpers/devices.py index e62a6647..5469ad8d 100644 --- a/pmb/helpers/devices.py +++ b/pmb/helpers/devices.py @@ -5,7 +5,7 @@ from pathlib import Path from pmb.core.pkgrepo import pkgrepo_glob_one, pkgrepo_iglob -def find_path(codename: str, file="") -> Path | None: +def find_path(codename: str, file: str = "") -> Path | None: """Find path to device APKBUILD under `device/*/device-`. :param codename: device codename @@ -36,7 +36,7 @@ def list_codenames(vendor=None, archived=True): return ret -def list_vendors(): +def list_vendors() -> set[str]: """Get all device vendors, for which aports are available. :returns: {"vendor1", "vendor2", ...} diff --git a/pmb/helpers/file.py b/pmb/helpers/file.py index d25ebbf7..858ce74e 100644 --- a/pmb/helpers/file.py +++ b/pmb/helpers/file.py @@ -10,7 +10,7 @@ import pmb.helpers.run import pmb.helpers.pmaports -def replace(path: Path, old: str, new: str): +def replace(path: Path, old: str, new: str) -> None: text = "" with path.open("r", encoding="utf-8") as handle: text = handle.read() @@ -21,7 +21,9 @@ def replace(path: Path, old: str, new: str): handle.write(text) -def replace_apkbuild(args: PmbArgs, pkgname, key, new, in_quotes=False): +def replace_apkbuild( + args: PmbArgs, pkgname: str, key: str, new: int | str, in_quotes: bool = False +) -> None: """Replace one key=value line in an APKBUILD and verify it afterwards. :param pkgname: package name, e.g. "hello-world" @@ -90,7 +92,7 @@ def is_older_than(path, seconds): return lastmod + seconds < time.time() -def symlink(file: Path, link: Path): +def symlink(file: Path, link: Path) -> None: """Check if the symlink is already present, otherwise create it.""" if os.path.exists(link): if os.path.islink(link) and os.path.realpath(os.readlink(link)) == os.path.realpath(file): diff --git a/pmb/helpers/frontend.py b/pmb/helpers/frontend.py index aac0649e..b67c585c 100644 --- a/pmb/helpers/frontend.py +++ b/pmb/helpers/frontend.py @@ -7,7 +7,7 @@ from pmb.helpers import logging import os from pathlib import Path import sys -from typing import Any, NoReturn +from typing import cast, Any, NoReturn import pmb.aportgen import pmb.build @@ -19,7 +19,7 @@ import pmb.chroot.other import pmb.ci import pmb.config from pmb.core import Config -from pmb.types import PmbArgs +from pmb.types import Env, PmbArgs, RunOutputType import pmb.export import pmb.flasher import pmb.helpers.aportupgrade @@ -43,7 +43,7 @@ from pmb.core import ChrootType, Chroot from pmb.core.context import get_context -def _parse_flavor(device: str, autoinstall=True) -> str: +def _parse_flavor(device: str, autoinstall: bool = True) -> str: """Verify the flavor argument if specified, or return a default value. :param autoinstall: make sure that at least one kernel flavor is installed @@ -189,10 +189,13 @@ def chroot(args: PmbArgs) -> None: pmb.chroot.init(chroot) # Xauthority - env = {} + env: Env = {} if args.xauth: pmb.chroot.other.copy_xauthority(chroot) - env["DISPLAY"] = os.environ.get("DISPLAY") + x11_display = os.environ.get("DISPLAY") + if x11_display is None: + raise AssertionError("$DISPLAY was unset despite that it should be set at this point") + env["DISPLAY"] = x11_display env["XAUTHORITY"] = "/home/pmos/.Xauthority" # Install blockdevice @@ -210,13 +213,16 @@ def chroot(args: PmbArgs) -> None: pmb.chroot.apk.update_repository_list(chroot, user_repository=True) + # TODO: Maybe this could be done better. + output_type = cast(RunOutputType, args.output) + # Run the command as user/root if user: logging.info(f"({chroot}) % su pmos -c '" + " ".join(args.command) + "'") - pmb.chroot.user(args.command, chroot, output=args.output, env=env) + pmb.chroot.user(args.command, chroot, output=output_type, env=env) else: logging.info(f"({chroot}) % " + " ".join(args.command)) - pmb.chroot.root(args.command, chroot, output=args.output, env=env) + pmb.chroot.root(args.command, chroot, output=output_type, env=env) def config(args: PmbArgs) -> None: diff --git a/pmb/helpers/git.py b/pmb/helpers/git.py index ce8361cb..4c69a2e4 100644 --- a/pmb/helpers/git.py +++ b/pmb/helpers/git.py @@ -65,7 +65,7 @@ def clone(name_repo: str) -> None: open(fetch_head, "w").close() -def rev_parse(path: Path, revision="HEAD", extra_args: list = []) -> str: +def rev_parse(path: Path, revision: str = "HEAD", extra_args: list = []) -> str: """Run "git rev-parse" in a specific repository dir. :param path: to the git repository @@ -79,7 +79,7 @@ def rev_parse(path: Path, revision="HEAD", extra_args: list = []) -> str: return rev.rstrip() -def can_fast_forward(path, branch_upstream, branch="HEAD") -> bool: +def can_fast_forward(path: Path, branch_upstream: str, branch: str = "HEAD") -> bool: command = ["git", "merge-base", "--is-ancestor", branch, branch_upstream] ret = pmb.helpers.run.user(command, path, check=False) if ret == 0: @@ -244,7 +244,7 @@ def parse_channels_cfg(aports: Path) -> dict: return ret -def branch_looks_official(repo: Path, branch) -> bool: +def branch_looks_official(repo: Path, branch: str) -> bool: """Check if a given branch follows the patterns of official branches in pmaports or aports. @@ -344,7 +344,7 @@ def get_topdir(repo: Path) -> Path: return Path(res.strip()) -def get_files(repo: Path): +def get_files(repo: Path) -> list[str]: """Get all files inside a git repository, that are either already in the git tree or are not in gitignore. Do not list deleted files. To be used for creating a tarball of the git repository. diff --git a/pmb/helpers/lint.py b/pmb/helpers/lint.py index 4da48bc6..78856ed7 100644 --- a/pmb/helpers/lint.py +++ b/pmb/helpers/lint.py @@ -45,7 +45,7 @@ def get_custom_valid_options() -> list[str]: # FIXME: dest_paths[repo], repo expected to be a Literal. # We should really make Config.mirrors not a TypedDict. # mypy: disable-error-code="index" -def check(pkgnames: Sequence[str]): +def check(pkgnames: Sequence[str]) -> None: """Run apkbuild-lint on the supplied packages. :param pkgnames: Names of the packages to lint diff --git a/pmb/helpers/logging.py b/pmb/helpers/logging.py index 525133fb..e25d39e2 100644 --- a/pmb/helpers/logging.py +++ b/pmb/helpers/logging.py @@ -4,7 +4,7 @@ import logging import os from pathlib import Path import sys -from typing import Final, TextIO +from typing import Any, Final, TextIO import pmb.config from pmb.meta import Cache @@ -24,7 +24,7 @@ VERBOSE: Final[int] = 5 class log_handler(logging.StreamHandler): """Write to stdout and to the already opened log file.""" - def __init__(self, details_to_stdout: bool = False, quiet: bool = False): + def __init__(self, details_to_stdout: bool = False, quiet: bool = False) -> None: super().__init__() self.details_to_stdout = details_to_stdout self.quiet = False @@ -90,7 +90,7 @@ class log_handler(logging.StreamHandler): self.handleError(record) -def add_verbose_log_level(): +def add_verbose_log_level() -> None: """Add a new log level "verbose", which is below "debug". Also monkeypatch logging, so it can be used with logging.verbose(). @@ -112,7 +112,7 @@ def add_verbose_log_level(): ) -def init(logfile: Path, verbose: bool, details_to_stdout: bool = False): +def init(logfile: Path, verbose: bool, details_to_stdout: bool = False) -> None: """Set log format and add the log file descriptor to logfd, add the verbose log level.""" global logfd @@ -153,7 +153,7 @@ def init(logfile: Path, verbose: bool, details_to_stdout: bool = False): logging.debug(f"$ pmbootstrap {' '.join(sys.argv)}") -def disable(): +def disable() -> None: logger = logging.getLogger() logger.disabled = True @@ -162,38 +162,38 @@ def disable(): # by not calling the (undefined) logging.verbose() function. -def critical(msg: object, *args, **kwargs): +def critical(msg: object, *args: str, **kwargs: Any) -> None: logging.critical(msg, *args, **kwargs) -def fatal(msg: object, *args, **kwargs): +def fatal(msg: object, *args: str, **kwargs: Any) -> None: logging.fatal(msg, *args, **kwargs) -def error(msg: object, *args, **kwargs): +def error(msg: object, *args: str, **kwargs: Any) -> None: logging.error(msg, *args, **kwargs) -def warning(msg: object, *args, **kwargs): +def warning(msg: object, *args: str, **kwargs: Any) -> None: logging.warning(msg, *args, **kwargs) @Cache("msg") -def warn_once(msg: str): +def warn_once(msg: str) -> None: logging.warning(msg) -def info(msg: object, *args, **kwargs): +def info(msg: object, *args: str, **kwargs: Any) -> None: logging.info(msg, *args, **kwargs) -def debug(msg: object, *args, **kwargs): +def debug(msg: object, *args: str, **kwargs: Any) -> None: logging.debug(msg, *args, **kwargs) -def verbose(msg: object, *args, **kwargs): +def verbose(msg: object, *args: str, **kwargs: Any) -> None: logging.verbose(msg, *args, **kwargs) # type: ignore[attr-defined] -def log(level: int, msg: object, *args, **kwargs): +def log(level: int, msg: object, *args: str, **kwargs: Any) -> None: logging.log(level, msg, *args, **kwargs) diff --git a/pmb/helpers/mount.py b/pmb/helpers/mount.py index 003e715c..ddb6f82c 100644 --- a/pmb/helpers/mount.py +++ b/pmb/helpers/mount.py @@ -4,6 +4,7 @@ import os from pathlib import Path, PurePath import pmb.helpers from pmb.core import Chroot +from pmb.types import PathString import pmb.helpers.run @@ -24,7 +25,7 @@ def ismount(folder: Path) -> bool: def bind( - source: Path, destination: Path, create_folders: bool = True, umount: bool = False + source: PathString, destination: Path, create_folders: bool = True, umount: bool = False ) -> None: """Mount --bind a folder and create necessary directory structure. diff --git a/pmb/helpers/other.py b/pmb/helpers/other.py index f084302d..a47e87b7 100644 --- a/pmb/helpers/other.py +++ b/pmb/helpers/other.py @@ -15,7 +15,7 @@ from typing import Any from pmb.helpers.exceptions import NonBugError -def folder_size(path: Path): +def folder_size(path: Path) -> int: """Run `du` to calculate the size of a folder. (this is less code and faster than doing the same task in pure Python) @@ -32,7 +32,7 @@ def folder_size(path: Path): return ret -def check_grsec(): +def check_grsec() -> None: """Check if the current kernel is based on the grsec patchset. Also check if the chroot_deny_chmod option is enabled. @@ -47,7 +47,7 @@ def check_grsec(): ) -def check_binfmt_misc(): +def check_binfmt_misc() -> None: """Check if the 'binfmt_misc' module is loaded. This is done by checking, if /proc/sys/fs/binfmt_misc/ exists. @@ -72,13 +72,13 @@ def check_binfmt_misc(): raise RuntimeError(f"Failed to set up binfmt_misc, see: {link}") -def migrate_success(work: Path, version): +def migrate_success(work: Path, version: int) -> None: logging.info("Migration to version " + str(version) + " done") with open(work / "version", "w") as handle: handle.write(str(version) + "\n") -def migrate_work_folder(): +def migrate_work_folder() -> None: # Read current version context = get_context() current = 0 @@ -182,7 +182,7 @@ def normalize_hostname(hostname: str) -> str: return hostname -def validate_hostname(hostname): +def validate_hostname(hostname: str) -> bool: """Check whether the string is a valid hostname. Check is performed according to diff --git a/pmb/helpers/pkgrel_bump.py b/pmb/helpers/pkgrel_bump.py index c917dab5..be968768 100644 --- a/pmb/helpers/pkgrel_bump.py +++ b/pmb/helpers/pkgrel_bump.py @@ -20,7 +20,7 @@ class BumpType(Enum): def package( - pkgname: str, reason="", dry: bool = False, bump_type: BumpType = BumpType.PKGREL + pkgname: str, reason: str = "", dry: bool = False, bump_type: BumpType = BumpType.PKGREL ) -> None: """Increase the pkgrel or pkgver in the APKBUILD of a specific package. diff --git a/pmb/helpers/pmaports.py b/pmb/helpers/pmaports.py index 2deb0678..a5e00810 100644 --- a/pmb/helpers/pmaports.py +++ b/pmb/helpers/pmaports.py @@ -18,7 +18,7 @@ from pmb.meta import Cache import pmb.parse -def _find_apkbuilds(skip_extra_repos=False) -> dict[str, Path]: +def _find_apkbuilds(skip_extra_repos: bool = False) -> dict[str, Path]: # Try to get a cached result first (we assume that the aports don't change # in one pmbootstrap call) apkbuilds = pmb.helpers.other.cache.get("pmb.helpers.pmaports.apkbuilds") @@ -50,7 +50,7 @@ def get_list() -> list[str]: return list(_find_apkbuilds().keys()) -def guess_main_dev(subpkgname) -> Path | None: +def guess_main_dev(subpkgname: str) -> Path | None: """Check if a package without "-dev" at the end exists in pmaports or not, and log the appropriate message. Don't call this function directly, use guess_main() instead. @@ -76,7 +76,7 @@ def guess_main_dev(subpkgname) -> Path | None: return None -def guess_main(subpkgname) -> Path | None: +def guess_main(subpkgname: str) -> Path | None: """Find the main package by assuming it is a prefix of the subpkgname. We do that, because in some APKBUILDs the subpkgname="" variable gets @@ -243,7 +243,9 @@ def get_with_path( return None, None -def get(pkgname, must_exist=True, subpackages=True, skip_extra_repos=False) -> dict[str, Any]: +def get( + pkgname: str, must_exist: bool = True, subpackages: bool = True, skip_extra_repos: bool = False +) -> dict[str, Any]: return get_with_path(pkgname, must_exist, subpackages, skip_extra_repos)[1] @@ -273,7 +275,7 @@ def find_providers(provide: str, default: list[str]) -> list[tuple[Any, Any]]: return sorted(providers.items(), reverse=True, key=lambda p: p[1].get("provider_priority", 0)) -def get_repo(pkgname) -> str | None: +def get_repo(pkgname: str) -> str | None: """Get the repository folder of an aport. :pkgname: package name @@ -289,7 +291,7 @@ def get_repo(pkgname) -> str | None: return None -def check_arches(arches, arch: Arch): +def check_arches(arches: list[str], arch: Arch) -> bool: """Check if building for a certain arch is allowed. :param arches: list of all supported arches, as it can be found in the diff --git a/pmb/helpers/repo.py b/pmb/helpers/repo.py index a4ab5d37..be61b1ee 100644 --- a/pmb/helpers/repo.py +++ b/pmb/helpers/repo.py @@ -54,7 +54,7 @@ def apkindex_hash(url: str, length: int = 8) -> Path: # FIXME: make config.mirrors a normal dict # mypy: disable-error-code="literal-required" @Cache("user_repository", "mirrors_exclude") -def urls(user_repository=False, mirrors_exclude: list[str] = []): +def urls(user_repository: bool = False, mirrors_exclude: list[str] = []) -> list[str]: """Get a list of repository URLs, as they are in /etc/apk/repositories. :param user_repository: add /mnt/pmbootstrap/packages @@ -118,7 +118,7 @@ def urls(user_repository=False, mirrors_exclude: list[str] = []): def apkindex_files( - arch: Arch | None = None, user_repository=True, exclude_mirrors: list[str] = [] + arch: Arch | None = None, user_repository: bool = True, exclude_mirrors: list[str] = [] ) -> list[Path]: """Get a list of outside paths to all resolved APKINDEX.tar.gz files for a specific arch. @@ -144,7 +144,7 @@ def apkindex_files( @Cache("arch", force=False) -def update(arch: Arch | None = None, force=False, existing_only=False): +def update(arch: Arch | None = None, force: bool = False, existing_only: bool = False) -> bool: """Download the APKINDEX files for all URLs depending on the architectures. :param arch: * one Alpine architecture name ("x86_64", "armhf", ...) @@ -223,7 +223,7 @@ def update(arch: Arch | None = None, force=False, existing_only=False): return True -def alpine_apkindex_path(repo="main", arch: Arch | None = None): +def alpine_apkindex_path(repo: str = "main", arch: Arch | None = None) -> Path: """Get the path to a specific Alpine APKINDEX file on disk and download it if necessary. :param repo: Alpine repository name (e.g. "main") diff --git a/pmb/helpers/run.py b/pmb/helpers/run.py index 527bb489..222fad5a 100644 --- a/pmb/helpers/run.py +++ b/pmb/helpers/run.py @@ -6,18 +6,26 @@ import subprocess from pmb.core.arch import Arch import pmb.helpers.run_core from collections.abc import Sequence -from pmb.types import Env, PathString +from typing import overload, Literal +from pmb.types import ( + Env, + PathString, + RunOutputType, + RunOutputTypeDefault, + RunOutputTypePopen, + RunReturnType, +) def user( cmd: Sequence[PathString], working_dir: Path | None = None, - output: str = "log", + output: RunOutputType = "log", output_return: bool = False, check: bool | None = None, env: Env = {}, sudo: bool = False, -) -> str | int | subprocess.Popen: +) -> RunReturnType: """ Run a command on the host system as user. @@ -56,7 +64,7 @@ def user( def user_output( cmd: Sequence[PathString], working_dir: Path | None = None, - output: str = "log", + output: RunOutputType = "log", check: bool | None = None, env: Env = {}, sudo: bool = False, @@ -68,14 +76,47 @@ def user_output( return ret +@overload def root( cmd: Sequence[PathString], - working_dir=None, - output="log", - output_return=False, - check=None, - env={}, -): + working_dir: Path | None = ..., + output: RunOutputTypePopen = ..., + output_return: Literal[False] = ..., + check: bool | None = ..., + env: Env = ..., +) -> subprocess.Popen: ... + + +@overload +def root( + cmd: Sequence[PathString], + working_dir: Path | None = ..., + output: RunOutputTypeDefault = ..., + output_return: Literal[False] = ..., + check: bool | None = ..., + env: Env = ..., +) -> int: ... + + +@overload +def root( + cmd: Sequence[PathString], + working_dir: Path | None = ..., + output: RunOutputType = ..., + output_return: Literal[True] = ..., + check: bool | None = ..., + env: Env = ..., +) -> str: ... + + +def root( + cmd: Sequence[PathString], + working_dir: Path | None = None, + output: RunOutputType = "log", + output_return: bool = False, + check: bool | None = None, + env: Env = {}, +) -> RunReturnType: """Run a command on the host system as root, with sudo or doas. :param env: dict of environment variables to be passed to the command, e.g. diff --git a/pmb/helpers/run_core.py b/pmb/helpers/run_core.py index 0bd3a5eb..d025ad73 100644 --- a/pmb/helpers/run_core.py +++ b/pmb/helpers/run_core.py @@ -3,7 +3,7 @@ import fcntl from pmb.core.context import get_context from pmb.core.arch import Arch -from pmb.types import PathString, Env +from pmb.types import PathString, Env, RunOutputType from pmb.helpers import logging import os from pathlib import Path @@ -22,7 +22,9 @@ import pmb.helpers.run called by core(). """ -def flat_cmd(cmds: Sequence[Sequence[PathString]], working_dir: Path | None = None, env: Env = {}): +def flat_cmd( + cmds: Sequence[Sequence[PathString]], working_dir: Path | None = None, env: Env = {} +) -> str: """Convert a shell command passed as list into a flat shell string with proper escaping. :param cmds: list of commands as list, e.g. ["echo", "string with spaces"] @@ -53,7 +55,9 @@ def flat_cmd(cmds: Sequence[Sequence[PathString]], working_dir: Path | None = No return ret -def sanity_checks(output="log", output_return=False, check=None): +def sanity_checks( + output: RunOutputType = "log", output_return: bool = False, check: bool | None = None +) -> None: """Raise an exception if the parameters passed to core() don't make sense. (all parameters are described in core() below). diff --git a/pmb/helpers/status.py b/pmb/helpers/status.py index de986243..4201a5da 100644 --- a/pmb/helpers/status.py +++ b/pmb/helpers/status.py @@ -9,7 +9,7 @@ from pmb.types import PmbArgs from pmb.core.context import get_context -def print_status_line(key: str, value: str): +def print_status_line(key: str, value: str) -> None: styles = pmb.config.styles key = f"{styles['GREEN']}{key}{styles['END']}:" padding = 17 diff --git a/pmb/helpers/toml.py b/pmb/helpers/toml.py index be3ee98f..ce07812a 100644 --- a/pmb/helpers/toml.py +++ b/pmb/helpers/toml.py @@ -1,5 +1,7 @@ # Copyright 2024 Oliver Smith # SPDX-License-Identifier: GPL-3.0-or-later +from pathlib import Path + from pmb.meta import Cache from pmb.helpers.exceptions import NonBugError @@ -12,7 +14,7 @@ except ImportError: @Cache("path") -def load_toml_file(path) -> dict: +def load_toml_file(path: Path) -> dict: """Read a toml file into a dict and show the path on error.""" with open(path, mode="rb") as f: try: diff --git a/pmb/install/_install.py b/pmb/install/_install.py index d0201ea0..b5edb2b6 100644 --- a/pmb/install/_install.py +++ b/pmb/install/_install.py @@ -38,7 +38,7 @@ get_recommends_visited: list[str] = [] get_selected_providers_visited: list[str] = [] -def get_subpartitions_size(chroot: Chroot): +def get_subpartitions_size(chroot: Chroot) -> tuple[int, int]: """ Calculate the size of the boot and root subpartition. @@ -84,7 +84,7 @@ def get_nonfree_packages(device): return ret -def get_kernel_package(config: Config): +def get_kernel_package(config: Config) -> list[str]: """ Get the device's kernel subpackage based on the user's choice in "pmbootstrap init". @@ -110,7 +110,7 @@ def get_kernel_package(config: Config): return ["device-" + config.device + "-kernel-" + config.kernel] -def copy_files_from_chroot(args: PmbArgs, chroot: Chroot): +def copy_files_from_chroot(args: PmbArgs, chroot: Chroot) -> None: """ Copy all files from the rootfs chroot to /mnt/install, except for the home folder (because /home will contain some empty @@ -155,7 +155,7 @@ def copy_files_from_chroot(args: PmbArgs, chroot: Chroot): pmb.chroot.root(["cp", "-a"] + folders + ["/mnt/install/"], working_dir=mountpoint) -def create_home_from_skel(filesystem: str, user: str): +def create_home_from_skel(filesystem: str, user: str) -> None: """ Create /home/{user} from /etc/skel """ @@ -172,7 +172,7 @@ def create_home_from_skel(filesystem: str, user: str): pmb.helpers.run.root(["chown", "-R", "10000", home]) -def configure_apk(args: PmbArgs): +def configure_apk(args: PmbArgs) -> None: """ Copy over all official keys, and the keys used to compile local packages (unless --no-local-pkgs is set). Then copy the corresponding APKINDEX files @@ -204,7 +204,7 @@ def configure_apk(args: PmbArgs): pmb.helpers.run.user(["cat", rootfs / "etc/apk/repositories"]) -def set_user(config: Config): +def set_user(config: Config) -> None: """ Create user with UID 10000 if it doesn't exist. Usually the ID for the first user created is 1000, but higher ID is @@ -228,7 +228,7 @@ def set_user(config: Config): pmb.chroot.root(["addgroup", config.user, group], chroot) -def setup_login_chpasswd_user_from_arg(args: PmbArgs, user: str, chroot: Chroot): +def setup_login_chpasswd_user_from_arg(args: PmbArgs, user: str, chroot: Chroot) -> None: """ Set the user's password from what the user passed as --password. Make an effort to not have the password end up in the log file by writing it to @@ -251,7 +251,7 @@ def setup_login_chpasswd_user_from_arg(args: PmbArgs, user: str, chroot: Chroot) os.unlink(path_outside) -def is_root_locked(chroot: Chroot): +def is_root_locked(chroot: Chroot) -> bool: """ Figure out from /etc/shadow if root is already locked. The output of this is stored in the log, so use grep to only log the line for root, not the @@ -265,7 +265,7 @@ def is_root_locked(chroot: Chroot): return shadow_root.startswith("root:!:") -def setup_login(args: PmbArgs, config: Config, chroot: Chroot): +def setup_login(args: PmbArgs, config: Config, chroot: Chroot) -> None: """ Loop until the password for user has been set successfully, and disable root login. @@ -294,7 +294,7 @@ def setup_login(args: PmbArgs, config: Config, chroot: Chroot): pmb.chroot.root(["passwd", "-l", "root"], chroot) -def copy_ssh_keys(config: Config): +def copy_ssh_keys(config: Config) -> None: """ If requested, copy user's SSH public keys to the device if they exist """ @@ -327,7 +327,7 @@ def copy_ssh_keys(config: Config): pmb.helpers.run.root(["chown", "-R", "10000:10000", target]) -def setup_keymap(config: Config): +def setup_keymap(config: Config) -> None: """ Set the keymap with the setup-keymap utility if the device requires it """ @@ -369,7 +369,7 @@ def setup_keymap(config: Config): logging.info("NOTE: No valid keymap specified for device") -def setup_timezone(chroot: Chroot, timezone: str): +def setup_timezone(chroot: Chroot, timezone: str) -> None: # We don't care about the arch since it's built for all! alpine_conf = pmb.helpers.package.get("alpine-conf", Arch.native()) version = alpine_conf.version.split("-r")[0] @@ -387,7 +387,7 @@ def setup_timezone(chroot: Chroot, timezone: str): pmb.chroot.root(setup_tz_cmd, chroot) -def setup_hostname(device: str, hostname: str | None): +def setup_hostname(device: str, hostname: str | None) -> None: """ Set the hostname and update localhost address in /etc/hosts """ @@ -417,7 +417,7 @@ def setup_hostname(device: str, hostname: str | None): pmb.chroot.root(["sed", "-i", "-e", regex, "/etc/hosts"], suffix) -def setup_appstream(offline: bool, chroot: Chroot): +def setup_appstream(offline: bool, chroot: Chroot) -> None: """ If alpine-appstream-downloader has been downloaded, execute it to have update AppStream data on new installs @@ -444,7 +444,7 @@ def setup_appstream(offline: bool, chroot: Chroot): ) -def disable_sshd(chroot: Chroot): +def disable_sshd(chroot: Chroot) -> None: # check=False: rc-update doesn't exit with 0 if already disabled pmb.chroot.root(["rc-update", "del", "sshd", "default"], chroot, check=False) @@ -456,7 +456,7 @@ def disable_sshd(chroot: Chroot): raise RuntimeError(f"Failed to disable sshd service: {sshd_files}") -def print_sshd_info(args: PmbArgs): +def print_sshd_info(args: PmbArgs) -> None: logging.info("") # make the note stand out logging.info("*** SSH DAEMON INFORMATION ***") @@ -480,7 +480,7 @@ def print_sshd_info(args: PmbArgs): logging.info("More info: https://postmarketos.org/ondev-debug") -def disable_firewall(chroot: Chroot): +def disable_firewall(chroot: Chroot) -> None: # check=False: rc-update doesn't exit with 0 if already disabled pmb.chroot.root(["rc-update", "del", "nftables", "default"], chroot, check=False) @@ -492,7 +492,7 @@ def disable_firewall(chroot: Chroot): raise RuntimeError(f"Failed to disable firewall: {nftables_files}") -def print_firewall_info(disabled: bool, arch: Arch): +def print_firewall_info(disabled: bool, arch: Arch) -> None: pmaports_cfg = pmb.config.pmaports.read_config() pmaports_ok = pmaports_cfg.get("supported_firewall", None) == "nftables" @@ -532,7 +532,7 @@ def print_firewall_info(disabled: bool, arch: Arch): logging.info("For more information: https://postmarketos.org/firewall") -def generate_binary_list(args: PmbArgs, chroot: Chroot, step): +def generate_binary_list(args: PmbArgs, chroot: Chroot, step: int) -> list[tuple[str, int]]: """ Perform three checks prior to writing binaries to disk: 1) that binaries exist, 2) that binaries do not extend into the first partition, 3) that @@ -587,7 +587,7 @@ def generate_binary_list(args: PmbArgs, chroot: Chroot, step): return binary_list -def embed_firmware(args: PmbArgs, suffix: Chroot): +def embed_firmware(args: PmbArgs, suffix: Chroot) -> None: """ This method will embed firmware, located at /usr/share, that are specified by the "sd_embed_firmware" deviceinfo parameter into the SD card image @@ -626,7 +626,7 @@ def embed_firmware(args: PmbArgs, suffix: Chroot): ) -def write_cgpt_kpart(args: PmbArgs, layout, suffix: Chroot): +def write_cgpt_kpart(args: PmbArgs, layout: PartitionLayout, suffix: Chroot) -> None: """ Write the kernel to the ChromeOS kernel partition. @@ -658,7 +658,7 @@ def sanity_check_boot_size(): sys.exit(1) -def sanity_check_disk(args: PmbArgs): +def sanity_check_disk(args: PmbArgs) -> None: device = args.disk device_name = os.path.basename(device) if not os.path.exists(device): @@ -670,7 +670,7 @@ def sanity_check_disk(args: PmbArgs): raise RuntimeError(f"{device} is read-only, maybe a locked SD card?") -def sanity_check_disk_size(args: PmbArgs): +def sanity_check_disk_size(args: PmbArgs) -> None: device = args.disk devpath = os.path.realpath(device) sysfs = "/sys/class/block/{}/size".format(devpath.replace("/dev/", "")) @@ -697,13 +697,13 @@ def sanity_check_disk_size(args: PmbArgs): raise RuntimeError("Aborted.") -def get_ondev_pkgver(args: PmbArgs): +def get_ondev_pkgver(args: PmbArgs) -> str: arch = pmb.parse.deviceinfo().arch package = pmb.helpers.package.get("postmarketos-ondev", arch) return package.version.split("-r")[0] -def sanity_check_ondev_version(args: PmbArgs): +def sanity_check_ondev_version(args: PmbArgs) -> None: ver_pkg = get_ondev_pkgver(args) ver_min = pmb.config.ondev_min_version if pmb.parse.version.compare(ver_pkg, ver_min) == -1: @@ -715,7 +715,7 @@ def sanity_check_ondev_version(args: PmbArgs): ) -def get_partition_layout(reserve, kernel): +def get_partition_layout(reserve: bool | int, kernel: bool) -> PartitionLayout: """ :param reserve: create an empty partition between root and boot (pma#463) :param kernel: create a separate kernel partition before all other @@ -741,7 +741,7 @@ def get_partition_layout(reserve, kernel): return ret -def get_uuid(args: PmbArgs, partition: Path): +def get_uuid(args: PmbArgs, partition: Path) -> str: """ Get UUID of a partition @@ -760,7 +760,7 @@ def get_uuid(args: PmbArgs, partition: Path): ).rstrip() -def create_crypttab(args: PmbArgs, layout, chroot: Chroot): +def create_crypttab(args: PmbArgs, layout: PartitionLayout, chroot: Chroot) -> None: """ Create /etc/crypttab config @@ -776,7 +776,7 @@ def create_crypttab(args: PmbArgs, layout, chroot: Chroot): pmb.chroot.root(["mv", "/tmp/crypttab", "/etc/crypttab"], chroot) -def create_fstab(args: PmbArgs, layout, chroot: Chroot): +def create_fstab(args: PmbArgs, layout: PartitionLayout, chroot: Chroot) -> None: """ Create /etc/fstab config @@ -832,15 +832,15 @@ def create_fstab(args: PmbArgs, layout, chroot: Chroot): def install_system_image( args: PmbArgs, - size_reserve, + size_reserve: int, chroot: Chroot, - step, - steps, - boot_label="pmOS_boot", - root_label="pmOS_root", - split=False, + step: int, + steps: int, + boot_label: str = "pmOS_boot", + root_label: str = "pmOS_root", + split: bool = False, disk: Path | None = None, -): +) -> None: """ :param size_reserve: empty partition between root and boot in MiB (pma#463) :param suffix: the chroot suffix, where the rootfs that will be installed @@ -944,7 +944,7 @@ def install_system_image( pmb.chroot.user(["mv", "-f", sys_image_patched, sys_image], working_dir=workdir) -def print_flash_info(device: str, deviceinfo: Deviceinfo, split: bool, have_disk: bool): +def print_flash_info(device: str, deviceinfo: Deviceinfo, split: bool, have_disk: bool) -> None: """Print flashing information, based on the deviceinfo data and the pmbootstrap arguments.""" logging.info("") # make the note stand out @@ -1042,7 +1042,7 @@ def print_flash_info(device: str, deviceinfo: Deviceinfo, split: bool, have_disk ) -def install_recovery_zip(args: PmbArgs, device: str, arch: Arch, steps): +def install_recovery_zip(args: PmbArgs, device: str, arch: Arch, steps: int) -> None: logging.info(f"*** ({steps}/{steps}) CREATING RECOVERY-FLASHABLE ZIP ***") chroot = Chroot(ChrootType.BUILDROOT, arch) mount_device_rootfs(Chroot.rootfs(device), chroot) @@ -1054,7 +1054,7 @@ def install_recovery_zip(args: PmbArgs, device: str, arch: Arch, steps): logging.info("https://postmarketos.org/recoveryzip") -def install_on_device_installer(args: PmbArgs, step, steps): +def install_on_device_installer(args: PmbArgs, step: int, steps: int) -> None: # Generate the rootfs image config = get_context().config if not args.ondev_no_rootfs: @@ -1136,7 +1136,7 @@ def install_on_device_installer(args: PmbArgs, step, steps): ) -def get_selected_providers(args: PmbArgs, packages): +def get_selected_providers(args: PmbArgs, packages: list[str]) -> list[str]: """ Look through the specified packages and see which providers were selected in "pmbootstrap init". Install those as extra packages to select them @@ -1182,7 +1182,7 @@ def get_selected_providers(args: PmbArgs, packages): return ret -def get_recommends(args: PmbArgs, packages) -> Sequence[str]: +def get_recommends(args: PmbArgs, packages: list[str]) -> Sequence[str]: """ Look through the specified packages and collect additional packages specified under _pmb_recommends in them. This is recursive, so it will dive @@ -1240,7 +1240,7 @@ def get_recommends(args: PmbArgs, packages) -> Sequence[str]: return ret -def create_device_rootfs(args: PmbArgs, step, steps): +def create_device_rootfs(args: PmbArgs, step: int, steps: int) -> None: # list all packages to be installed (including the ones specified by --add) # and upgrade the installed packages/apkindexes context = get_context() @@ -1348,7 +1348,7 @@ def create_device_rootfs(args: PmbArgs, step, steps): disable_firewall(chroot) -def install(args: PmbArgs): +def install(args: PmbArgs) -> None: device = get_context().config.device chroot = Chroot(ChrootType.ROOTFS, device) deviceinfo = pmb.parse.deviceinfo() diff --git a/pmb/install/blockdevice.py b/pmb/install/blockdevice.py index c3e2868b..ef68c834 100644 --- a/pmb/install/blockdevice.py +++ b/pmb/install/blockdevice.py @@ -12,7 +12,7 @@ from pmb.core import Chroot from pmb.core.context import get_context -def previous_install(path: Path): +def previous_install(path: Path) -> bool: """ Search the disk for possible existence of a previous installation of pmOS. We temporarily mount the possible pmOS_boot partition as @@ -39,7 +39,7 @@ def previous_install(path: Path): return "pmOS_boot" in label -def mount_disk(path: Path): +def mount_disk(path: Path) -> None: """ :param path: path to disk block device (e.g. /dev/mmcblk0) """ @@ -61,7 +61,9 @@ def mount_disk(path: Path): raise RuntimeError("Aborted.") -def create_and_mount_image(args: PmbArgs, size_boot, size_root, size_reserve, split=False): +def create_and_mount_image( + args: PmbArgs, size_boot: int, size_root: int, size_reserve: int, split: bool = False +) -> None: """ Create a new image file, and mount it as /dev/install. @@ -121,7 +123,9 @@ def create_and_mount_image(args: PmbArgs, size_boot, size_root, size_reserve, sp pmb.helpers.mount.bind_file(device, Chroot.native() / mount_point) -def create(args: PmbArgs, size_boot, size_root, size_reserve, split, disk: Path | None): +def create( + args: PmbArgs, size_boot: int, size_root: int, size_reserve: int, split: bool, disk: Path | None +) -> None: """ Create /dev/install (the "install blockdevice"). diff --git a/pmb/install/format.py b/pmb/install/format.py index d560a325..72e2bc73 100644 --- a/pmb/install/format.py +++ b/pmb/install/format.py @@ -3,7 +3,7 @@ from pmb.helpers import logging import pmb.chroot from pmb.core import Chroot -from pmb.types import PmbArgs +from pmb.types import PartitionLayout, PmbArgs, PathString def install_fsprogs(filesystem): @@ -14,7 +14,7 @@ def install_fsprogs(filesystem): pmb.chroot.apk.install([fsprogs], Chroot.native()) -def format_and_mount_boot(args: PmbArgs, device, boot_label): +def format_and_mount_boot(args: PmbArgs, device: str, boot_label: str) -> None: """ :param device: boot partition on install block device (e.g. /dev/installp1) :param boot_label: label of the root partition (e.g. "pmOS_boot") @@ -40,7 +40,7 @@ def format_and_mount_boot(args: PmbArgs, device, boot_label): pmb.chroot.root(["mount", device, mountpoint]) -def format_luks_root(args: PmbArgs, device): +def format_luks_root(args: PmbArgs, device: str) -> None: """ :param device: root partition on install block device (e.g. /dev/installp2) """ @@ -72,7 +72,7 @@ def format_luks_root(args: PmbArgs, device): raise RuntimeError("Failed to open cryptdevice!") -def get_root_filesystem(args: PmbArgs): +def get_root_filesystem(args: PmbArgs) -> str: ret = args.filesystem or pmb.parse.deviceinfo().root_filesystem or "ext4" pmaports_cfg = pmb.config.pmaports.read_config() @@ -90,7 +90,7 @@ def get_root_filesystem(args: PmbArgs): return ret -def prepare_btrfs_subvolumes(args: PmbArgs, device, mountpoint): +def prepare_btrfs_subvolumes(args: PmbArgs, device: str, mountpoint: str) -> None: """ Create separate subvolumes if root filesystem is btrfs. This lets us do snapshots and rollbacks of relevant parts @@ -143,7 +143,9 @@ def prepare_btrfs_subvolumes(args: PmbArgs, device, mountpoint): pmb.chroot.root(["chattr", "+C", f"{mountpoint}/var"]) -def format_and_mount_root(args: PmbArgs, device, root_label, disk): +def format_and_mount_root( + args: PmbArgs, device: str, root_label: str, disk: PathString | None +) -> None: """ :param device: root partition on install block device (e.g. /dev/installp2) :param root_label: label of the root partition (e.g. "pmOS_root") @@ -185,7 +187,13 @@ def format_and_mount_root(args: PmbArgs, device, root_label, disk): prepare_btrfs_subvolumes(args, device, mountpoint) -def format(args: PmbArgs, layout, boot_label, root_label, disk): +def format( + args: PmbArgs, + layout: PartitionLayout, + boot_label: str, + root_label: str, + disk: PathString | None, +) -> None: """ :param layout: partition layout from get_partition_layout() :param boot_label: label of the boot partition (e.g. "pmOS_boot") diff --git a/pmb/install/partition.py b/pmb/install/partition.py index 52f05155..785a4abf 100644 --- a/pmb/install/partition.py +++ b/pmb/install/partition.py @@ -8,12 +8,13 @@ import pmb.chroot import pmb.config import pmb.install.losetup from pmb.core import Chroot +from pmb.types import PartitionLayout import pmb.core.dps # FIXME (#2324): this function drops disk to a string because it's easier # to manipulate, this is probably bad. -def partitions_mount(device: str, layout, disk: Path | None): +def partitions_mount(device: str, layout: PartitionLayout, disk: Path | None) -> None: """ Mount blockdevices of partitions inside native chroot :param layout: partition layout from get_partition_layout() @@ -61,7 +62,7 @@ def partitions_mount(device: str, layout, disk: Path | None): pmb.helpers.mount.bind_file(source, target) -def partition(layout, size_boot, size_reserve): +def partition(layout: PartitionLayout, size_boot: int, size_reserve: int) -> None: """ Partition /dev/install and create /dev/install{p1,p2,p3}: * /dev/installp1: boot @@ -122,7 +123,7 @@ def partition(layout, size_boot, size_reserve): pmb.chroot.root(["parted", "-s", "/dev/install"] + command, check=False) -def partition_cgpt(layout, size_boot, size_reserve): +def partition_cgpt(layout: PartitionLayout, size_boot: int, size_reserve: int) -> None: """ This function does similar functionality to partition(), but this one is for ChromeOS devices which use special GPT. We don't follow diff --git a/pmb/install/recovery.py b/pmb/install/recovery.py index 794ebec4..01527656 100644 --- a/pmb/install/recovery.py +++ b/pmb/install/recovery.py @@ -12,7 +12,7 @@ import pmb.flasher import pmb.helpers.frontend -def create_zip(args: PmbArgs, chroot: Chroot, device: str): +def create_zip(args: PmbArgs, chroot: Chroot, device: str) -> None: """ Create android recovery compatible installer zip. """ @@ -51,7 +51,7 @@ def create_zip(args: PmbArgs, chroot: Chroot, device: str): raise AssertionError("Partitions should not be None at this point") # Create config file for the recovery installer - options = { + options: dict[str, bool | str] = { "DEVICE": device, "FLASH_KERNEL": args.recovery_flash_kernel, "ISOREC": method == "heimdall-isorec", diff --git a/pmb/meta/__init__.py b/pmb/meta/__init__.py index 64f1bddf..e3c3594b 100644 --- a/pmb/meta/__init__.py +++ b/pmb/meta/__init__.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import copy -from typing import Generic, Optional, TypeVar, overload +from typing import Any, Generic, Optional, TypeVar, overload from collections.abc import Callable import inspect @@ -25,7 +25,7 @@ class Wrapper(Generic[FuncArgs, FuncReturn]): # actually end up here. We first check if we have a cached # result and if not then we do the actual function call and # cache it if applicable - def __call__(self, *args, **kwargs) -> FuncReturn: + def __call__(self, *args: Any, **kwargs: Any) -> FuncReturn: if self.disabled: return self.func(*args, **kwargs) @@ -79,7 +79,7 @@ class Cache: # Build the cache key, or return None to not cache in the case where # we only cache when an argument has a specific value - def build_key(self, func: Callable, *args, **kwargs) -> str | None: + def build_key(self, func: Callable, *args: Any, **kwargs: Any) -> str | None: key = "~" # Easy case: cache irrelevant of arguments if not self.params and not self.kwargs: diff --git a/pmb/netboot/__init__.py b/pmb/netboot/__init__.py index 29b98e40..7446b62a 100644 --- a/pmb/netboot/__init__.py +++ b/pmb/netboot/__init__.py @@ -12,7 +12,7 @@ import pmb.helpers.run from pmb.core import Chroot -def start_nbd_server(device: str, replace: bool, ip="172.16.42.2", port=9999): +def start_nbd_server(device: str, replace: bool, ip: str = "172.16.42.2", port: int = 9999) -> None: """ Start nbd server in chroot_native with pmOS rootfs. :param ip: IP address to serve nbd server for diff --git a/pmb/parse/_apkbuild.py b/pmb/parse/_apkbuild.py index 170fbb05..d00faa2f 100644 --- a/pmb/parse/_apkbuild.py +++ b/pmb/parse/_apkbuild.py @@ -3,6 +3,7 @@ # mypy: disable-error-code="attr-defined" from pmb.core.context import get_context from pmb.helpers import logging +from pmb.types import Apkbuild import os from pathlib import Path import re @@ -31,7 +32,7 @@ revar4 = re.compile(r"\${([a-zA-Z_]+[a-zA-Z0-9_]*)#(.*)}") revar5 = re.compile(r"([a-zA-Z_]+[a-zA-Z0-9_]*)=") -def replace_variable(apkbuild, value: str) -> str: +def replace_variable(apkbuild: Apkbuild, value: str) -> str: def log_key_not_found(match): logging.verbose( f"{apkbuild['pkgname']}: key '{match.group(1)}' for" @@ -95,7 +96,7 @@ def replace_variable(apkbuild, value: str) -> str: return value -def function_body(path: Path, func: str): +def function_body(path: Path, func: str) -> list[str]: """ Get the body of a function in an APKBUILD. @@ -120,7 +121,7 @@ def function_body(path: Path, func: str): return func_body -def read_file(path: Path): +def read_file(path: Path) -> list[str]: """ Read an APKBUILD file @@ -321,7 +322,7 @@ def _parse_subpackage(path, lines, apkbuild, subpackages, subpkg): @Cache("path") -def apkbuild(path: Path, check_pkgver=True, check_pkgname=True): +def apkbuild(path: Path, check_pkgver: bool = True, check_pkgname: bool = True) -> Apkbuild: """ Parse relevant information out of the APKBUILD file. This is not meant to be perfect and catch every edge case (for that, a full shell parser @@ -371,7 +372,7 @@ def apkbuild(path: Path, check_pkgver=True, check_pkgname=True): return ret -def kernels(device: str): +def kernels(device: str) -> dict[str, str] | None: """ Get the possible kernels from a device-* APKBUILD. diff --git a/pmb/parse/bootimg.py b/pmb/parse/bootimg.py index 342016d7..350af95b 100644 --- a/pmb/parse/bootimg.py +++ b/pmb/parse/bootimg.py @@ -1,6 +1,8 @@ # Copyright 2023 Oliver Smith # SPDX-License-Identifier: GPL-3.0-or-later import os +from typing import TextIO + from pmb.core.context import get_context from pmb.helpers import logging from pathlib import Path @@ -9,10 +11,10 @@ import pmb.chroot import pmb.chroot.other import pmb.chroot.apk from pmb.core import Chroot -from pmb.types import Bootimg +from pmb.types import Bootimg, PathString -def is_dtb(path) -> bool: +def is_dtb(path: PathString) -> bool: if not os.path.isfile(path): return False with open(path, "rb") as f: @@ -20,7 +22,7 @@ def is_dtb(path) -> bool: return f.read(4) == b"\xd0\x0d\xfe\xed" -def get_mtk_label(path) -> str | None: +def get_mtk_label(path: PathString) -> str | None: """Read the label from the MediaTek header of the kernel or ramdisk inside an extracted boot.img. :param path: to either the kernel or ramdisk extracted from boot.img @@ -52,7 +54,7 @@ def get_mtk_label(path) -> str | None: return label -def get_qcdt_type(path) -> str | None: +def get_qcdt_type(path: PathString) -> str | None: """Get the dt.img type by reading the first four bytes of the file. :param path: to the qcdt image extracted from boot.img :returns: * None: dt.img is of unknown type @@ -195,5 +197,5 @@ def bootimg(path: Path) -> Bootimg: ) -def trim_input(f) -> str: +def trim_input(f: TextIO) -> str: return f.read().replace("\n", "") diff --git a/pmb/parse/depends.py b/pmb/parse/depends.py index 11bf372e..5d09b801 100644 --- a/pmb/parse/depends.py +++ b/pmb/parse/depends.py @@ -10,7 +10,7 @@ from pmb.core.context import get_context def package_provider( - pkgname, pkgnames_install, suffix: Chroot = Chroot.native() + pkgname: str, pkgnames_install: list[str], suffix: Chroot = Chroot.native() ) -> pmb.core.apkindex_block.ApkindexBlock | None: """ :param pkgnames_install: packages to be installed diff --git a/pmb/parse/deviceinfo.py b/pmb/parse/deviceinfo.py index c57797c1..5598dc85 100644 --- a/pmb/parse/deviceinfo.py +++ b/pmb/parse/deviceinfo.py @@ -60,7 +60,7 @@ def _parse_kernel_suffix(info, device, kernel): @Cache("device", "kernel") -def deviceinfo(device=None, kernel=None) -> "Deviceinfo": +def deviceinfo(device: str | None = None, kernel: str | None = None) -> "Deviceinfo": """ :param device: defaults to args.device :param kernel: defaults to args.kernel @@ -170,7 +170,7 @@ class Deviceinfo: keymaps: str | None = "" @staticmethod - def __validate(info: dict[str, str], path: Path): + def __validate(info: dict[str, str], path: Path) -> None: # Resolve path for more readable error messages path = path.resolve() diff --git a/pmb/parse/kconfig.py b/pmb/parse/kconfig.py index d346f93a..1e5fb38e 100644 --- a/pmb/parse/kconfig.py +++ b/pmb/parse/kconfig.py @@ -11,6 +11,7 @@ import pmb.parse import pmb.helpers.pmaports import pmb.parse.kconfigcheck from pmb.helpers.exceptions import NonBugError +from pmb.types import PathString def is_set(config, option): @@ -158,7 +159,14 @@ def check_config_options_set( return ret -def check_config(config_path, config_arch, pkgver, categories: list, details=False): +# TODO: This should probably use Arch and not str for config_arch +def check_config( + config_path: PathString, + config_arch: str, + pkgver: str, + categories: list[str], + details: bool = False, +) -> bool: """ Check, whether one kernel config passes the rules of multiple components. @@ -295,7 +303,9 @@ def extract_version(config_path): return "unknown" -def check_file(config_path, components_list: list[str] = [], details=False): +def check_file( + config_path: PathString, components_list: list[str] = [], details: bool = False +) -> bool: """ Check for necessary kernel config options in a kconfig file. diff --git a/pmb/parse/version.py b/pmb/parse/version.py index 4272a4f2..c7840453 100644 --- a/pmb/parse/version.py +++ b/pmb/parse/version.py @@ -206,7 +206,7 @@ def validate(version): return True -def compare(a_version: str, b_version: str, fuzzy=False): +def compare(a_version: str, b_version: str, fuzzy: bool = False) -> int: """ Compare two versions A and B to find out which one is higher, or if both are equal. diff --git a/pmb/qemu/run.py b/pmb/qemu/run.py index 7d38ee86..38d1e0b9 100644 --- a/pmb/qemu/run.py +++ b/pmb/qemu/run.py @@ -21,13 +21,13 @@ import pmb.chroot.initfs import pmb.config import pmb.config.pmaports import pmb.install.losetup -from pmb.types import PathString, PmbArgs +from pmb.types import Env, PathString, PmbArgs import pmb.helpers.run import pmb.parse.cpuinfo from pmb.core import Chroot, ChrootType -def system_image(device: str): +def system_image(device: str) -> Path: """ Returns path to rootfs for specified device. In case that it doesn't exist, raise and exception explaining how to generate it. @@ -41,7 +41,7 @@ def system_image(device: str): return path -def create_second_storage(args: PmbArgs, device: str): +def create_second_storage(args: PmbArgs, device: str) -> Path: """ Generate a second storage image if it does not exist. @@ -55,7 +55,7 @@ def create_second_storage(args: PmbArgs, device: str): return path -def which_qemu(arch: Arch): +def which_qemu(arch: Arch) -> str: """ Finds the qemu executable or raises an exception otherwise """ @@ -97,7 +97,13 @@ def create_gdk_loader_cache(args: PmbArgs) -> Path: return chroot_native / custom_cache_path -def command_qemu(args: PmbArgs, config: Config, arch: Arch, img_path, img_path_2nd=None): +def command_qemu( + args: PmbArgs, + config: Config, + arch: Arch, + img_path: Path, + img_path_2nd: Path | None = None, +) -> tuple[list[str | Path], Env]: """ Generate the full qemu command with arguments to run postmarketOS """ @@ -139,6 +145,11 @@ def command_qemu(args: PmbArgs, config: Config, arch: Arch, img_path, img_path_2 if not arch.is_native() and ncpus > 8: ncpus = 8 + env: Env + # It might be tempting to use PathString here, but I don't think it makes sense semantically as + # this is not just a list of paths. + command: list[str | Path] + if args.host_qemu: qemu_bin = which_qemu(arch) env = {} @@ -319,7 +330,7 @@ def sigterm_handler(number, frame): ) -def install_depends(args: PmbArgs, arch: Arch): +def install_depends(args: PmbArgs, arch: Arch) -> None: """ Install any necessary qemu dependencies in native chroot """ @@ -358,7 +369,7 @@ def install_depends(args: PmbArgs, arch: Arch): pmb.chroot.apk.install(depends, chroot) -def run(args: PmbArgs): +def run(args: PmbArgs) -> None: """ Run a postmarketOS image in qemu """ diff --git a/pmb/sideload/__init__.py b/pmb/sideload/__init__.py index 032f888a..6595f04d 100644 --- a/pmb/sideload/__init__.py +++ b/pmb/sideload/__init__.py @@ -1,6 +1,7 @@ # Copyright 2023 Martijn Braam # SPDX-License-Identifier: GPL-3.0-or-later import os +from pathlib import Path from typing import Optional from pmb.core.arch import Arch from pmb.helpers import logging @@ -63,7 +64,7 @@ def ssh_find_arch(args: PmbArgs, user: str, host: str, port: str) -> Arch: return alpine_architecture -def ssh_install_apks(args: PmbArgs, user, host, port, paths: list) -> None: +def ssh_install_apks(args: PmbArgs, user: str, host: str, port: str, paths: list[Path]) -> None: """Copy binary packages via SCP and install them via SSH. :param user: target device ssh username :param host: target device ssh hostname @@ -95,7 +96,13 @@ def ssh_install_apks(args: PmbArgs, user, host, port, paths: list) -> None: def sideload( - args: PmbArgs, user: str, host: str, port: str, arch: Arch | None, copy_key: bool, pkgnames + args: PmbArgs, + user: str, + host: str, + port: str, + arch: Arch | None, + copy_key: bool, + pkgnames: list[str], ) -> None: """Build packages if necessary and install them via SSH. diff --git a/pmb/types.py b/pmb/types.py index 0d283eea..cdf3d274 100644 --- a/pmb/types.py +++ b/pmb/types.py @@ -1,6 +1,7 @@ # Copyright 2024 Caleb Connolly # SPDX-License-Identifier: GPL-3.0-or-later +import subprocess from argparse import Namespace from pathlib import Path from typing import Any, Literal, TypedDict @@ -8,6 +9,10 @@ from typing import Any, Literal, TypedDict from pmb.core.arch import Arch CrossCompileType = Literal["native", "crossdirect"] | None +RunOutputTypeDefault = Literal["log", "stdout", "interactive", "tui", "null"] +RunOutputTypePopen = Literal["background", "pipe"] +RunOutputType = RunOutputTypeDefault | RunOutputTypePopen +RunReturnType = str | int | subprocess.Popen PathString = Path | str Env = dict[str, PathString] Apkbuild = dict[str, Any]