parse: deviceinfo: make Deviceinfo a class (MR 2252)

Introduce a Deviceinfo class and use it rather than the dictionary. This
gives us sweet sweet autocomplete, and lays the foundation for having a
proper deviceinfo validator in the future.

Additionally, continue refactoring out args...

Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
This commit is contained in:
Caleb Connolly 2024-06-06 15:05:59 +02:00 committed by Oliver Smith
parent b51d31acab
commit 97bd8b96ec
No known key found for this signature in database
GPG key ID: 5AE7F5513E0885CB
27 changed files with 372 additions and 288 deletions

View file

@ -1,7 +1,8 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import copy
from typing import Dict
from pathlib import Path
from typing import Dict, Optional
from pmb.core import get_context
from pmb.helpers import logging
import os
@ -9,77 +10,12 @@ import pmb.config
import pmb.helpers.other
import pmb.helpers.devices
def sanity_check(info, path):
# Resolve path for more readable error messages
path = os.path.realpath(path)
# Legacy errors
if "flash_methods" in info:
raise RuntimeError("deviceinfo_flash_methods has been renamed to"
" deviceinfo_flash_method. Please adjust your"
" deviceinfo file: " + path)
if "external_disk" in info or "external_disk_install" in info:
raise RuntimeError("Instead of deviceinfo_external_disk and"
" deviceinfo_external_disk_install, please use the"
" new variable deviceinfo_external_storage in your"
" deviceinfo file: " + path)
if "msm_refresher" in info:
raise RuntimeError("It is enough to specify 'msm-fb-refresher' in the"
" depends of your device's package now. Please"
" delete the deviceinfo_msm_refresher line in: " +
path)
if "flash_fastboot_vendor_id" in info:
raise RuntimeError("Fastboot doesn't allow specifying the vendor ID"
" anymore (#1830). Try removing the"
" 'deviceinfo_flash_fastboot_vendor_id' line in: " +
path + " (if you are sure that you need this, then"
" we can probably bring it back to fastboot, just"
" let us know in the postmarketOS issues!)")
if "nonfree" in info:
raise RuntimeError("deviceinfo_nonfree is unused. "
"Please delete it in: " + path)
if "dev_keyboard" in info:
raise RuntimeError("deviceinfo_dev_keyboard is unused. "
"Please delete it in: " + path)
if "date" in info:
raise RuntimeError("deviceinfo_date was replaced by deviceinfo_year. "
"Set it to the release year in: " + path)
# "codename" is required
codename = os.path.basename(os.path.dirname(path))
if codename.startswith("device-"):
codename = codename[7:]
if "codename" not in info or info["codename"] != codename:
raise RuntimeError(f"Please add 'deviceinfo_codename=\"{codename}\"' "
f"to: {path}")
# "chassis" is required
chassis_types = pmb.config.deviceinfo_chassis_types
if "chassis" not in info or not info["chassis"]:
logging.info("NOTE: the most commonly used chassis types in"
" postmarketOS are 'handset' (for phones) and 'tablet'.")
raise RuntimeError(f"Please add 'deviceinfo_chassis' to: {path}")
# "arch" is required
if "arch" not in info or not info["arch"]:
raise RuntimeError(f"Please add 'deviceinfo_arch' to: {path}")
arch = info["arch"]
if (arch != pmb.config.arch_native and
arch not in pmb.config.build_device_architectures):
raise ValueError("Arch '" + arch + "' is not available in"
" postmarketOS. If you would like to add it, see:"
" <https://postmarketos.org/newarch>")
# "chassis" validation
chassis_type = info["chassis"]
if chassis_type not in chassis_types:
raise RuntimeError(f"Unknown chassis type '{chassis_type}', should"
f" be one of {', '.join(chassis_types)}. Fix this"
f" and try again: {path}")
# FIXME: It feels weird to handle this at parse time.
# we should instead have the Deviceinfo object store
# the attributes for all kernels and require the user
# to specify which one they're using.
# Basically: treat Deviceinfo as a standalone type that
# doesn't need to traverse pmaports.
def _parse_kernel_suffix(info, device, kernel):
"""
Remove the kernel suffix (as selected in 'pmbootstrap init') from
@ -108,7 +44,7 @@ def _parse_kernel_suffix(info, device, kernel):
ret = copy.copy(info)
suffix_kernel = kernel.replace("-", "_")
for key in pmb.config.deviceinfo_attributes:
for key in Deviceinfo.__annotations__.keys():
key_kernel = f"{key}_{suffix_kernel}"
if key_kernel not in ret:
continue
@ -121,8 +57,7 @@ def _parse_kernel_suffix(info, device, kernel):
return ret
# FIXME (#2324): Make deviceinfo a type! (class!!!)
def deviceinfo(device=None, kernel=None) -> Dict[str, str]:
def deviceinfo(device=None, kernel=None) -> "Deviceinfo":
"""
:param device: defaults to args.device
:param kernel: defaults to args.kernel
@ -150,25 +85,183 @@ def deviceinfo(device=None, kernel=None) -> Dict[str, str]:
" start a new device port or to choose another device. It may have"
" been renamed, see <https://postmarketos.org/renamed>")
ret = {}
with open(path) as handle:
for line in handle:
if not line.startswith("deviceinfo_"):
continue
if "=" not in line:
raise SyntaxError(f"{path}: No '=' found:\n\t{line}")
split = line.split("=", 1)
key = split[0][len("deviceinfo_"):]
value = split[1].replace("\"", "").replace("\n", "")
ret[key] = value
di = Deviceinfo(path, kernel)
# Assign empty string as default
for key in pmb.config.deviceinfo_attributes:
if key not in ret:
ret[key] = ""
pmb.helpers.other.cache["deviceinfo"][device] = di
return di
ret = _parse_kernel_suffix(ret, device, kernel)
sanity_check(ret, path)
class Deviceinfo:
"""Variables from deviceinfo. Reference: <https://postmarketos.org/deviceinfo>
Many of these are unused in pmbootstrap, and still more that are described
on the wiki are missing. Eventually this class and associated code should
be moved to a separate library and become the authoritative source of truth
for the deviceinfo format."""
path: Path
# general
format_version: str
name: str
manufacturer: str
codename: str
year: str
dtb: str
arch: str
pmb.helpers.other.cache["deviceinfo"][device] = ret
return ret
# device
chassis: str
keyboard: Optional[str] = ""
external_storage: Optional[str] = ""
dev_touchscreen: Optional[str] = ""
dev_touchscreen_calibration: Optional[str] = ""
append_dtb: Optional[str] = ""
# bootloader
flash_method: Optional[str] = ""
boot_filesystem: Optional[str] = ""
# flash
flash_heimdall_partition_kernel: Optional[str] = ""
flash_heimdall_partition_initfs: Optional[str] = ""
flash_heimdall_partition_rootfs: Optional[str] = ""
flash_heimdall_partition_system: Optional[str] = "" # deprecated
flash_heimdall_partition_vbmeta: Optional[str] = ""
flash_heimdall_partition_dtbo: Optional[str] = ""
flash_fastboot_partition_kernel: Optional[str] = ""
flash_fastboot_partition_rootfs: Optional[str] = ""
flash_fastboot_partition_system: Optional[str] = "" # deprecated
flash_fastboot_partition_vbmeta: Optional[str] = ""
flash_fastboot_partition_dtbo: Optional[str] = ""
flash_rk_partition_kernel: Optional[str] = ""
flash_rk_partition_rootfs: Optional[str] = ""
flash_rk_partition_system: Optional[str] = "" # deprecated
flash_mtkclient_partition_kernel: Optional[str] = ""
flash_mtkclient_partition_rootfs: Optional[str] = ""
flash_mtkclient_partition_vbmeta: Optional[str] = ""
flash_mtkclient_partition_dtbo: Optional[str] = ""
generate_legacy_uboot_initfs: Optional[str] = ""
kernel_cmdline: Optional[str] = ""
generate_bootimg: Optional[str] = ""
header_version: Optional[str] = ""
bootimg_qcdt: Optional[str] = ""
bootimg_mtk_mkimage: Optional[str] = "" # deprecated
bootimg_mtk_label_kernel: Optional[str] = ""
bootimg_mtk_label_ramdisk: Optional[str] = ""
bootimg_dtb_second: Optional[str] = ""
bootimg_custom_args: Optional[str] = ""
flash_offset_base: Optional[str] = ""
flash_offset_dtb: Optional[str] = ""
flash_offset_kernel: Optional[str] = ""
flash_offset_ramdisk: Optional[str] = ""
flash_offset_second: Optional[str] = ""
flash_offset_tags: Optional[str] = ""
flash_pagesize: Optional[str] = ""
flash_fastboot_max_size: Optional[str] = ""
flash_sparse: Optional[str] = ""
flash_sparse_samsung_format: Optional[str] = ""
rootfs_image_sector_size: Optional[str] = ""
sd_embed_firmware: Optional[str] = ""
sd_embed_firmware_step_size: Optional[str] = ""
partition_blacklist: Optional[str] = ""
boot_part_start: Optional[str] = ""
partition_type: Optional[str] = ""
root_filesystem: Optional[str] = ""
flash_kernel_on_update: Optional[str] = ""
cgpt_kpart: Optional[str] = ""
cgpt_kpart_start: Optional[str] = ""
cgpt_kpart_size: Optional[str] = ""
# weston
weston_pixman_type: Optional[str] = ""
# keymaps
keymaps: Optional[str] = ""
@staticmethod
def __validate(info: Dict[str, str], path: Path):
# Resolve path for more readable error messages
path = path.resolve()
# Legacy errors
if "flash_methods" in info:
raise RuntimeError("deviceinfo_flash_methods has been renamed to"
" deviceinfo_flash_method. Please adjust your"
f" deviceinfo file: {path}")
if "external_disk" in info or "external_disk_install" in info:
raise RuntimeError("Instead of deviceinfo_external_disk and"
" deviceinfo_external_disk_install, please use the"
" new variable deviceinfo_external_storage in your"
f" deviceinfo file: {path}")
if "msm_refresher" in info:
raise RuntimeError("It is enough to specify 'msm-fb-refresher' in the"
" depends of your device's package now. Please"
" delete the deviceinfo_msm_refresher line in: "
f"{path}")
if "flash_fastboot_vendor_id" in info:
raise RuntimeError("Fastboot doesn't allow specifying the vendor ID"
" anymore (#1830). Try removing the"
" 'deviceinfo_flash_fastboot_vendor_id' line in: "
f"{path} (if you are sure that you need this, then"
" we can probably bring it back to fastboot, just"
" let us know in the postmarketOS issues!)")
if "nonfree" in info:
raise RuntimeError("deviceinfo_nonfree is unused. "
f"Please delete it in: {path}")
if "dev_keyboard" in info:
raise RuntimeError("deviceinfo_dev_keyboard is unused. "
f"Please delete it in: {path}")
if "date" in info:
raise RuntimeError("deviceinfo_date was replaced by deviceinfo_year. "
f"Set it to the release year in: {path}")
# "codename" is required
codename = os.path.basename(os.path.dirname(path))[7:]
if "codename" not in info or info["codename"] != codename:
raise RuntimeError(f"Please add 'deviceinfo_codename=\"{codename}\"' "
f"to: {path}")
# "chassis" is required
chassis_types = pmb.config.deviceinfo_chassis_types
if "chassis" not in info or not info["chassis"]:
logging.info("NOTE: the most commonly used chassis types in"
" postmarketOS are 'handset' (for phones) and 'tablet'.")
raise RuntimeError(f"Please add 'deviceinfo_chassis' to: {path}")
# "arch" is required
if "arch" not in info or not info["arch"]:
raise RuntimeError(f"Please add 'deviceinfo_arch' to: {path}")
arch = info["arch"]
if (arch != pmb.config.arch_native and
arch not in pmb.config.build_device_architectures):
raise ValueError("Arch '" + arch + "' is not available in"
" postmarketOS. If you would like to add it, see:"
" <https://postmarketos.org/newarch>")
# "chassis" validation
chassis_type = info["chassis"]
if chassis_type not in chassis_types:
raise RuntimeError(f"Unknown chassis type '{chassis_type}', should"
f" be one of {', '.join(chassis_types)}. Fix this"
f" and try again: {path}")
def __init__(self, path: Path, kernel: Optional[str] = None):
ret = {}
with open(path) as handle:
for line in handle:
if not line.startswith("deviceinfo_"):
continue
if "=" not in line:
raise SyntaxError(f"{path}: No '=' found:\n\t{line}")
split = line.split("=", 1)
key = split[0][len("deviceinfo_"):]
value = split[1].replace("\"", "").replace("\n", "")
ret[key] = value
ret = _parse_kernel_suffix(ret, ret["codename"], kernel)
Deviceinfo.__validate(ret, path)
for key, value in ret.items():
# FIXME: something to turn on and fix in the future
# if key not in Deviceinfo.__annotations__.keys():
# logging.warning(f"deviceinfo: {key} is not a known attribute")
setattr(self, key, value)