diff --git a/boot-qemu.py b/boot-qemu.py index 959fdc1..a82771b 100755 --- a/boot-qemu.py +++ b/boot-qemu.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # pylint: disable=invalid-name +from __future__ import annotations + import contextlib import os import platform @@ -10,12 +12,14 @@ import subprocess import sys from argparse import ArgumentParser -from collections.abc import Sequence from pathlib import Path -from typing import Any, Union +from typing import TYPE_CHECKING, Any import utils +if TYPE_CHECKING: + from collections.abc import Sequence + SUPPORTED_ARCHES = [ 'arm', 'arm32_v5', @@ -70,7 +74,7 @@ def __init__(self) -> None: self._initrd_arch: str = '' self._kvm_cpu: list[str] = ['host'] self._qemu_arch: str = '' - self._qemu_args: list[Union[Path, str]] = [ + self._qemu_args: list[Path | str] = [ '-display', 'none', '-nodefaults', ] # fmt: off @@ -78,9 +82,11 @@ def __init__(self) -> None: def _find_dtb(self) -> Path: if not self._dtbs: - raise RuntimeError('No dtbs set?') + msg = 'No dtbs set?' + raise RuntimeError(msg) if self.kernel == utils.UNINIT_PATH: - raise RuntimeError('Cannot locate dtb without kernel') + msg = 'Cannot locate dtb without kernel' + raise RuntimeError(msg) # If we are in a boot folder, look for them in the dts folder in it. # Otherwise, assume there is a 'dtbs' folder in the same folder as the @@ -90,13 +96,13 @@ def _find_dtb(self) -> Path: if (dtb := Path(self.kernel.parent, dtb_dir, dtb_loc)).exists(): return dtb - raise FileNotFoundError( - f"dtb is required for booting but it could not be found at expected locations ('{self._dtbs}')" - ) + msg = f"dtb is required for booting but it could not be found at expected locations ('{self._dtbs}')" + raise FileNotFoundError(msg) def _get_default_smp_value(self) -> int: if self.kernel_dir == utils.UNINIT_PATH: - raise RuntimeError('No kernel build folder specified?') + msg = 'No kernel build folder specified?' + raise RuntimeError(msg) # If kernel_dir is the kernel source, the configuration will be at # /.config @@ -118,18 +124,21 @@ def _get_default_smp_value(self) -> int: # Use the minimum of the number of usable processers for the script or # CONFIG_NR_CPUS. if not (usable_cpus := os.cpu_count()): - raise RuntimeError('Could not determine number of CPUs?') + msg = 'Could not determine number of CPUs?' + raise RuntimeError(msg) return min(usable_cpus, config_nr_cpus) def _get_kernel_ver_tuple(self, decomp_prog: str) -> tuple[int, ...]: if self.kernel == utils.UNINIT_PATH: - raise RuntimeError('No kernel set?') + msg = 'No kernel set?' + raise RuntimeError(msg) utils.check_cmd(decomp_prog) if decomp_prog == 'gzip': decomp_cmd = [decomp_prog, '-c', '-d', self.kernel] else: - raise RuntimeError(f"Unsupported decompression program ('{decomp_prog}')?") + msg = f"Unsupported decompression program ('{decomp_prog}')?" + raise RuntimeError(msg) decomp = subprocess.run(decomp_cmd, capture_output=True, check=True) utils.check_cmd('strings') @@ -141,13 +150,15 @@ def _get_kernel_ver_tuple(self, decomp_prog: str) -> tuple[int, ...]: r'^Linux version (\d+\.\d+\.\d+)', strings_stdout, flags=re.MULTILINE ) ): - raise RuntimeError(f"Could not find Linux version in {self.kernel}?") + msg = f"Could not find Linux version in {self.kernel}?" + raise RuntimeError(msg) return tuple(int(x) for x in match.groups()[0].split('.')) def _get_qemu_ver_string(self) -> str: if self._qemu_path == utils.UNINIT_PATH: - raise RuntimeError('No path to QEMU set?') + msg = 'No path to QEMU set?' + raise RuntimeError(msg) qemu_ver = subprocess.run( [self._qemu_path, '--version'], capture_output=True, check=True, text=True ) @@ -156,17 +167,20 @@ def _get_qemu_ver_string(self) -> str: def _get_qemu_ver_tuple(self) -> tuple[int, ...]: qemu_ver_string = self._get_qemu_ver_string() if not (match := re.search(r'version (\d+\.\d+.\d+)', qemu_ver_string)): - raise RuntimeError('Could not find QEMU version?') + msg = 'Could not find QEMU version?' + raise RuntimeError(msg) return tuple(int(x) for x in match.groups()[0].split('.')) - def _have_dev_kvm_access(self) -> bool: + @staticmethod + def _have_dev_kvm_access() -> bool: return os.access('/dev/kvm', os.R_OK | os.W_OK) def _prepare_initrd(self) -> Path: if self.initrd != utils.UNINIT_PATH: return self.initrd if not self._initrd_arch: - raise RuntimeError('No initrd architecture specified?') + msg = 'No initrd architecture specified?' + raise RuntimeError(msg) return utils.prepare_initrd( self._initrd_arch, gh_json_file=self.gh_json_file, modules=self.modules ) @@ -247,9 +261,11 @@ def _set_kernel_vars(self) -> None: return if self.kernel_dir == utils.UNINIT_PATH: - raise RuntimeError('No kernel image or kernel build folder specified?') + msg = 'No kernel image or kernel build folder specified?' + raise RuntimeError(msg) if self._default_kernel_path == utils.UNINIT_PATH: - raise RuntimeError('No default kernel path specified?') + msg = 'No default kernel path specified?' + raise RuntimeError(msg) possible_kernel_locations = { Path(self._default_kernel_path), # default (kbuild) @@ -261,10 +277,12 @@ def _set_qemu_path(self) -> None: if self._qemu_path != utils.UNINIT_PATH: return # already found and set if not self._qemu_arch: - raise RuntimeError('No QEMU architecture set?') + msg = 'No QEMU architecture set?' + raise RuntimeError(msg) qemu_bin = f"qemu-system-{self._qemu_arch}" if not (qemu_path := shutil.which(qemu_bin)): - raise RuntimeError(f'{qemu_bin} could not be found on your system?') + msg = f'{qemu_bin} could not be found on your system?' + raise RuntimeError(msg) self._qemu_path = Path(qemu_path) def run(self) -> None: @@ -321,7 +339,7 @@ def run(self) -> None: class ARMEFIQEMURunner(QEMURunner): - def _setup_efi(self, possible_locations: Sequence[Union[Path, str]]) -> None: + def _setup_efi(self, possible_locations: Sequence[Path | str]) -> None: # Sizing the images to 64M is recommended by "Prepare the firmware" section at # https://mirrors.edge.kernel.org/pub/linux/kernel/people/will/docs/qemu/qemu-arm64-howto.html efi_img_size = 64 * 1024 * 1024 # 64M @@ -716,10 +734,12 @@ def guess_arch(kernel_arg: Path) -> str: # Note: 'required=False' just to provide our own exception. vmlinux_locations = ['vmlinux', '../../../vmlinux'] if not (vmlinux := utils.find_first_file(kernel_dir, vmlinux_locations, required=False)): - raise RuntimeError('Architecture was not provided and vmlinux could not be found!') + msg = 'Architecture was not provided and vmlinux could not be found!' + raise RuntimeError(msg) if not (file := shutil.which('file')): - raise RuntimeError("Architecture was not provided and 'file' is not installed!") + msg = "Architecture was not provided and 'file' is not installed!" + raise RuntimeError(msg) # Get output of file file_out = subprocess.run( @@ -754,14 +774,12 @@ def guess_arch(kernel_arg: Path) -> str: for string, value in file_rosetta.items(): if string in file_out: if value == 'ambiguous': - raise RuntimeError( - f"'{string}' found in '{file_out}' but the architecture is ambiguous, please explicitly specify it via '-a'!" - ) + msg = f"'{string}' found in '{file_out}' but the architecture is ambiguous, please explicitly specify it via '-a'!" + raise RuntimeError(msg) return value - raise RuntimeError( - f"Architecture could not be deduced from '{file_out}', please explicitly specify it via '-a' or add support for it to guess_arch()!" - ) + msg = f"Architecture could not be deduced from '{file_out}', please explicitly specify it via '-a' or add support for it to guess_arch()!" + raise RuntimeError(msg) def parse_arguments(): @@ -840,11 +858,12 @@ def parse_arguments(): return parser.parse_args() -if __name__ == '__main__': +def main(): args = parse_arguments() if not (kernel_location := Path(args.kernel_location).resolve()).exists(): - raise FileNotFoundError(f"Supplied kernel location ('{kernel_location}') does not exist!") + msg = f"Supplied kernel location ('{kernel_location}') does not exist!" + raise FileNotFoundError(msg) if not (arch := args.architecture): arch = guess_arch(kernel_location) @@ -874,8 +893,9 @@ def parse_arguments(): if kernel_location.is_file(): if args.gdb and kernel_location.name != 'vmlinux': + msg = 'Debugging with gdb requires a kernel build folder to locate vmlinux' raise RuntimeError( - 'Debugging with gdb requires a kernel build folder to locate vmlinux', + msg, ) runner.kernel = kernel_location else: @@ -898,7 +918,8 @@ def parse_arguments(): if args.initrd: if not (initrd := Path(args.initrd).resolve()).exists(): - raise FileNotFoundError(f"Supplied initrd ('{initrd}') does not exist?") + msg = f"Supplied initrd ('{initrd}') does not exist?" + raise FileNotFoundError(msg) runner.initrd = initrd if args.memory: @@ -906,7 +927,8 @@ def parse_arguments(): if args.modules: if not (modules := Path(args.modules).resolve()).exists(): - raise FileNotFoundError(f"Supplied modules .cpio ('{modules}') does not exist?") + msg = f"Supplied modules .cpio ('{modules}') does not exist?" + raise FileNotFoundError(msg) if not args.memory: utils.yellow('Memory not specified, the default may be too small for modules...') runner.modules = modules @@ -921,3 +943,7 @@ def parse_arguments(): runner.timeout = args.timeout runner.run() + + +if __name__ == '__main__': + main() diff --git a/buildroot/rebuild.py b/buildroot/rebuild.py index a11be27..5981886 100755 --- a/buildroot/rebuild.py +++ b/buildroot/rebuild.py @@ -64,7 +64,8 @@ def build_image(architecture, edit_config, savedefconfig): for image in images: if not image.exists(): - raise FileNotFoundError(f"{image} could not be found! Did the build error?") + msg = f"{image} could not be found! Did the build error?" + raise FileNotFoundError(msg) zstd_cmd = [ 'zstd', '-f', @@ -112,16 +113,14 @@ def download_and_extract_buildroot(): try: subprocess.run(patch_cmd, check=True) except subprocess.CalledProcessError as err: - raise RuntimeError( - f"{patch} did not apply to Buildroot {BUILDROOT_VERSION}, does it need to be updated?" - ) from err + msg = f"{patch} did not apply to Buildroot {BUILDROOT_VERSION}, does it need to be updated?" + raise RuntimeError(msg) from err def release_images(): if not shutil.which('gh'): - raise RuntimeError( - "Could not find GitHub CLI ('gh') on your system, please install it to do releases!" - ) + msg = "Could not find GitHub CLI ('gh') on your system, please install it to do releases!" + raise RuntimeError(msg) gh_cmd = [ 'gh', @@ -169,11 +168,12 @@ def parse_arguments(): return parser.parse_args() -if __name__ == '__main__': +def main(): args = parse_arguments() if not shutil.which('zstd'): - raise RuntimeError('zstd could not be found on your system, please install it!') + msg = 'zstd could not be found on your system, please install it!' + raise RuntimeError(msg) architectures = SUPPORTED_ARCHES if 'all' in args.architectures else args.architectures @@ -183,3 +183,7 @@ def parse_arguments(): if args.release: release_images() + + +if __name__ == '__main__': + main() diff --git a/ruff.toml b/ruff.toml index ab0ab0f..d3149fe 100644 --- a/ruff.toml +++ b/ruff.toml @@ -6,13 +6,14 @@ quote-style = 'preserve' # https://docs.astral.sh/ruff/rules/ [lint] -select = [ +extend-select = [ 'A', # flake8-builtins 'ARG', # flake8-unused-arguments 'B', # flake8-bugbear 'C4', # flake8-comprehensions - 'DTZ', # flake8-datetimes + 'DTZ', # flake8-datetimez 'E', # pycodestyle + 'EM', # flake8-errmsg 'F', # pyflakes 'FURB', # refurb 'I', # isort @@ -38,7 +39,8 @@ ignore = [ 'PLR0913', # too-many-arguments 'PLR0915', # too-many-statements 'PLR2004', # magic-value-comparison + 'S404', # suspicious-subprocess-import 'S603', # subprocess-without-shell-equals-true 'S607', # start-process-with-partial-path - 'TRY003', # raise-vanilla-args ] +preview = true diff --git a/utils.py b/utils.py index 3a662f2..ecd0749 100755 --- a/utils.py +++ b/utils.py @@ -1,13 +1,17 @@ #!/usr/bin/env python3 +from __future__ import annotations + import json import os import shutil import subprocess import sys -from collections.abc import Iterable from pathlib import Path -from typing import Any, NoReturn, Optional, Union +from typing import TYPE_CHECKING, Any, NoReturn + +if TYPE_CHECKING: + from collections.abc import Iterable BOOT_UTILS = Path(__file__).resolve().parent UNINIT_PATH = Path('/uninitialized') @@ -65,12 +69,13 @@ def download_initrd(gh_json, local_dest): return - raise RuntimeError(f"Failed to find {remote_file} in downloads of {url}?") + msg = f"Failed to find {remote_file} in downloads of {url}?" + raise RuntimeError(msg) def find_first_file( relative_root: Path, - possible_files: Iterable[Union[Path, str]], + possible_files: Iterable[Path | str], required: bool = True, ) -> Path: """ @@ -94,15 +99,14 @@ def find_first_file( return full_path if required: files_str = "', '".join([str(elem) for elem in possible_files]) + msg = f"No files from list ('{files_str}') could be found within '{relative_root}'!" raise FileNotFoundError( - f"No files from list ('{files_str}') could be found within '{relative_root}'!", + msg, ) return UNINIT_PATH -def get_full_kernel_path( - kernel_location: Union[Path, str], image: str, arch: Optional[str] = None -) -> Path: +def get_full_kernel_path(kernel_location: Path | str, image: str, arch: str | None = None) -> Path: """ Get the full path to a kernel image based on the architecture and image name if necessary. @@ -122,7 +126,7 @@ def get_full_kernel_path( kernel = kernel_location # If the image is an uncompressed vmlinux or a UML image, it is in the # root of the build folder - elif image in ("vmlinux", "linux"): + elif image in {"vmlinux", "linux"}: kernel = kernel_location.joinpath(image) # Otherwise, it is in the architecture's boot directory elif arch: @@ -160,7 +164,8 @@ def get_gh_json(endpoint: str) -> dict[str, Any]: try: curl_out = subprocess.run(curl_cmd, capture_output=True, check=True, text=True).stdout except subprocess.CalledProcessError as err: - raise RuntimeError(f"Failed to query GitHub API at {endpoint}: {err.stderr}") from err + msg = f"Failed to query GitHub API at {endpoint}: {err.stderr}" + raise RuntimeError(msg) from err return json.loads(curl_out) @@ -178,8 +183,8 @@ def green(string: str) -> None: def prepare_initrd( architecture: str, rootfs_format: str = 'cpio', - gh_json_file: Optional[Path] = None, - modules: Optional[Path] = None, + gh_json_file: Path | None = None, + modules: Path | None = None, ) -> Path: """ Returns a decompressed initial ramdisk. @@ -195,7 +200,8 @@ def prepare_initrd( # querying the GitHub API at all. if gh_json_file and gh_json_file != UNINIT_PATH: if not gh_json_file.exists(): - raise FileNotFoundError(f"Provided GitHub JSON file ('{gh_json_file}') does not exist!") + msg = f"Provided GitHub JSON file ('{gh_json_file}') does not exist!" + raise FileNotFoundError(msg) gh_json_rel = json.loads(gh_json_file.read_text(encoding='utf-8')) else: # Make sure that the current user is not rate limited by GitHub, @@ -209,10 +215,11 @@ def prepare_initrd( gh_json_rel = get_gh_json(f"https://api.github.com/repos/{REPO}/releases/latest") elif not src.exists(): limit = gh_json_rl['resources']['core']['limit'] - raise RuntimeError( + msg = ( f"Cannot query GitHub API for latest images release due to rate limit (remaining: {remaining}, limit: {limit}) and {src} does not exist already! " 'Download it manually or supply a GitHub personal access token via the GITHUB_TOKEN environment variable to make an authenticated GitHub API request.' ) + raise RuntimeError(msg) # Download the ramdisk if it is not already downloaded if not src.exists(): @@ -237,9 +244,8 @@ def prepare_initrd( cpio_sig = bytes([0x30, 0x37, 0x30, 0x37, 0x30, 0x31]) with modules.open('rb') as module_file: if module_file.read(6) != cpio_sig: - raise RuntimeError( - f"{modules} does not have cpio magic bytes, was it generated with the 'modules-cpio-pkg' target?" - ) + msg = f"{modules} does not have cpio magic bytes, was it generated with the 'modules-cpio-pkg' target?" + raise RuntimeError(msg) (new_dst := dst.parent.joinpath('rootfs-modules.cpio')).unlink(missing_ok=True) with ( @@ -249,7 +255,8 @@ def prepare_initrd( new_dst.open('xb') as dst_file, ): if not proc.stdout: - raise RuntimeError('cat stdout is None?') + msg = 'cat stdout is None?' + raise RuntimeError(msg) dst_file.write(proc.stdout.read()) dst = new_dst