Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 61 additions & 35 deletions boot-qemu.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env python3
# pylint: disable=invalid-name

from __future__ import annotations

import contextlib
import os
import platform
Expand All @@ -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',
Expand Down Expand Up @@ -70,17 +74,19 @@ 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
self._qemu_path: Path = utils.UNINIT_PATH

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
Expand All @@ -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
# <kernel_dir>/.config
Expand All @@ -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')
Expand All @@ -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
)
Expand All @@ -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
)
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -898,15 +918,17 @@ 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:
runner.memory = args.memory

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
Expand All @@ -921,3 +943,7 @@ def parse_arguments():
runner.timeout = args.timeout

runner.run()


if __name__ == '__main__':
main()
22 changes: 13 additions & 9 deletions buildroot/rebuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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

Expand All @@ -183,3 +183,7 @@ def parse_arguments():

if args.release:
release_images()


if __name__ == '__main__':
main()
8 changes: 5 additions & 3 deletions ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Loading
Loading