pmbootstrap-meow/pmb/helpers/devices.py
Newbyte 7b78bcfcf9
pmb: Also apply ext4 metadata_csum workaround for archived ports
Ports in the archived category may be downstream ports. While mainline
ports also are allowed, archived ports should generally not be relied on
anyway so the loss of ext4 reliability on those shouldn't matter.

Fixes 89a80265de

Part-of: https://gitlab.postmarketos.org/postmarketOS/pmbootstrap/-/merge_requests/2640
2025-07-07 15:08:54 +02:00

205 lines
6.9 KiB
Python

# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations
import os
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
import pmb.config
from pmb.core.pkgrepo import pkgrepo_glob_one, pkgrepo_iglob
from pmb.helpers import logging
import pmb.helpers.pmaports
def find_path(codename: str, file: str = "") -> Path | None:
"""Find path to device APKBUILD under `device/*/device-`.
:param codename: device codename
:param file: file to look for (e.g. APKBUILD or deviceinfo), may be empty
:returns: path to APKBUILD
"""
g = pkgrepo_glob_one(f"device/*/device-{codename}/{file}")
if not g:
return None
return g
# TODO: This could be simplified using StrEnum once we stop supporting Python 3.10.
class DeviceCategory(Enum):
"""Enum for representing a specific device category."""
ARCHIVED = "archived"
DOWNSTREAM = "downstream"
TESTING = "testing"
COMMUNITY = "community"
MAIN = "main"
@staticmethod
def shown() -> list[DeviceCategory]:
"""Get a list of all device categories that typically are visible, in order of "best" to
"worst".
:returns: List of all non-hidden device categories.
"""
return [
DeviceCategory.MAIN,
DeviceCategory.COMMUNITY,
DeviceCategory.TESTING,
DeviceCategory.DOWNSTREAM,
]
def allows_downstream_ports(self) -> bool:
"""Check whether a given category is allowed to contain downstream ports. This does not
necessarily mean that it exclusively contains downstream ports.
:returns: True, if the category allows downstream ports, False if only allows mainline ports.
"""
match self:
case DeviceCategory.ARCHIVED | DeviceCategory.DOWNSTREAM:
return True
case DeviceCategory.TESTING | DeviceCategory.COMMUNITY | DeviceCategory.MAIN:
return False
case _:
raise AssertionError
def explain(self) -> str:
"""Provide an explanation of a given category.
:returns: String explaining the given category.
"""
match self:
case DeviceCategory.ARCHIVED:
return "ports that have a better alternative available"
case DeviceCategory.DOWNSTREAM:
return "ports that use a downstream kernel — very limited functionality. Not recommended"
case DeviceCategory.TESTING:
return 'anything from "just boots in some sense" to almost fully functioning ports'
case DeviceCategory.COMMUNITY:
return "often mostly usable, but may lack important functionality"
case DeviceCategory.MAIN:
return "ports where mostly everything works"
case _:
raise AssertionError
def color(self) -> str:
"""Returns the color associated with the given device category.
:returns: ANSI escape sequence for the color associated with the given device category."""
styles = pmb.config.styles
match self:
case DeviceCategory.ARCHIVED:
return styles["RED"]
case DeviceCategory.DOWNSTREAM:
return styles["YELLOW"]
case DeviceCategory.TESTING:
return styles["GREEN"]
case DeviceCategory.COMMUNITY:
return styles["BLUE"]
case DeviceCategory.MAIN:
return styles["MAGENTA"]
case _:
raise AssertionError
def __str__(self) -> str:
return self.value
@dataclass(frozen=True)
class DeviceEntry:
codename: str
category: DeviceCategory
def codename_without_vendor(self) -> str:
return self.codename.split("-", 1)[1]
def __str__(self) -> str:
"""Remove "vendor-" prefix from device codename and add category."""
styles = pmb.config.styles
return f"{self.category.color()}{self.codename_without_vendor()}{styles['END']} ({self.category})"
def list_codenames(vendor: str) -> list[DeviceEntry]:
"""Get all devices for which aports are available.
:param vendor: Vendor name to choose devices from.
:returns: ["first-device", "second-device", ...]}
"""
ret: list[DeviceEntry] = []
for path in pkgrepo_iglob(f"device/*/device-{vendor}-*"):
codename = os.path.basename(path).split("-", 1)[1]
# Ensure we don't crash on unknown device categories.
try:
category = get_device_category_by_directory_path(path)
except RuntimeError as exception:
logging.warning("WARNING: %s: %s", codename, exception)
continue
# Get rid of ports inside of hidden device categories.
if category not in DeviceCategory.shown():
continue
ret.append(DeviceEntry(codename, category))
return ret
def list_vendors() -> set[str]:
"""Get all device vendors, for which aports are available.
:returns: {"vendor1", "vendor2", ...}
"""
ret = set()
for path in pkgrepo_iglob("device/*/device-*"):
vendor = path.name.split("-", 2)[1]
ret.add(vendor)
return ret
def get_device_category_by_apkbuild_path(apkbuild_path: Path) -> DeviceCategory:
"""Get the category of a device based on the path to its APKBUILD inside of pmaports.
This will fail to determine the device category from out-of-tree APKBUILDs.
:apkbuild_path: Path to an APKBUILD within pmaports for a particular device.
:returns: The device category of the provided device APKBUILD.
"""
# Path is something like this:
# .../device/community/device-samsung-m0/APKBUILD
# ↑ ↑ parent 1
# | parent 2
category_str = apkbuild_path.parent.parent.name
try:
device_category = DeviceCategory(category_str)
except ValueError as exception:
raise RuntimeError(f'Unknown device category "{category_str}"') from exception
return device_category
def get_device_category_by_directory_path(device_directory: Path) -> DeviceCategory:
"""Get the category of a device based on the path to its directory inside of pmaports.
:device_directory: Path to the device package directory for a particular device.
:returns: The device category of the provided device directory.
"""
device_apkbuild_path = device_directory / "APKBUILD"
return get_device_category_by_apkbuild_path(device_apkbuild_path)
def get_device_category_by_name(device_name: str) -> DeviceCategory:
"""Get the category of a device based on its name.
:device_name: Name of a particular device to determine the category of.
Format should be "vendor-codename".
:returns: The device category of the provided device name.
"""
device_directory = pmb.helpers.pmaports.find(f"device-{device_name}")
return get_device_category_by_directory_path(device_directory)