diff --git a/packages/device.py b/packages/device.py new file mode 100644 index 0000000..f9f4568 --- /dev/null +++ b/packages/device.py @@ -0,0 +1,118 @@ +import logging +import os + +from typing import Optional + +from config import config +from constants import Arch, ARCHES +from config.scheme import DataClass, munchclass +from .pkgbuild import discover_pkgbuilds, _pkgbuilds_cache, Pkgbuild, parse_pkgbuild + +DEVICE_DEPRECATIONS = { + "oneplus-enchilada": "sdm845-oneplus-enchilada", + "oneplus-fajita": "sdm845-oneplus-fajita", + "xiaomi-beryllium-ebbg": "sdm845-sdm845-xiaomi-beryllium-ebbg", + "xiaomi-beryllium-tianma": "sdm845-sdm845-xiaomi-tianma", + "bq-paella": "msm8916-bq-paella", +} + + +@munchclass() +class Device(DataClass): + name: str + arch: Arch + package: Pkgbuild + + def parse_deviceinfo(self): + pass + + +def check_devicepkg_name(name: str, log_level: Optional[int] = None): + valid = True + if not name.startswith('device-'): + valid = False + if log_level is not None: + logging.log(log_level, f'invalid device package name "{name}": doesn\'t start with "device-"') + if name.endswith('-common'): + valid = False + if log_level is not None: + logging.log(log_level, f'invalid device package name "{name}": ends with "-common"') + return valid + + +def parse_device_pkg(pkgbuild: Pkgbuild) -> Device: + if len(pkgbuild.arches) != 1: + raise Exception(f"{pkgbuild.name}: Device package must have exactly one arch, but has {pkgbuild.arches}") + arch = pkgbuild.arches[0] + if arch == 'any' or arch not in ARCHES: + raise Exception(f'unknown arch for device package: {arch}') + if pkgbuild.repo != 'device': + logging.warning(f'device package {pkgbuild.name} is in unexpected repo "{pkgbuild.repo}", expected "device"') + name = pkgbuild.name + prefix = 'device-' + if name.startswith(prefix): + name = name[len(prefix):] + return Device(name=name, arch=arch, package=pkgbuild) + + +_device_cache: dict[str, Device] = {} +_device_cache_populated: bool = False + + +def get_devices(pkgbuilds: Optional[dict[str, Pkgbuild]] = None, lazy: bool = True) -> dict[str, Device]: + global _device_cache, _device_cache_populated + use_cache = _device_cache_populated and lazy + if not use_cache: + if not pkgbuilds: + pkgbuilds = discover_pkgbuilds(lazy=lazy) + _device_cache.clear() + for pkgbuild in pkgbuilds.values(): + if not (pkgbuild.repo == 'device' and check_devicepkg_name(pkgbuild.name, log_level=None)): + continue + dev = parse_device_pkg(pkgbuild) + _device_cache[dev.name] = dev + _device_cache_populated = True + return _device_cache.copy() + + +def get_device(name: str, pkgbuilds: Optional[dict[str, Pkgbuild]] = None, lazy: bool = True, scan_all=False) -> Device: + global _device_cache, _device_cache_populated + assert lazy or pkgbuilds + if name in DEVICE_DEPRECATIONS: + warning = f"Deprecated device {name}" + replacement = DEVICE_DEPRECATIONS[name] + if replacement: + warning += (f': Device has been renamed to {replacement}! Please adjust your profile config!\n' + 'This will become an error in a future version!') + name = replacement + logging.warning(warning) + if lazy and name in _device_cache: + return _device_cache[name] + if scan_all: + devices = get_devices(pkgbuilds=pkgbuilds, lazy=lazy) + if name not in devices: + raise Exception(f'Unknown device {name}!') + return devices[name] + else: + pkgname = f'device-{name}' + if pkgbuilds: + if pkgname not in pkgbuilds: + raise Exception(f'Unknown device {name}!') + pkgbuild = pkgbuilds[pkgname] + else: + if lazy and pkgname in _pkgbuilds_cache: + pkgbuild = _pkgbuilds_cache[pkgname] + else: + relative_path = os.path.join('device', pkgname) + assert os.path.exists(os.path.join(config.get_path('pkgbuilds'), relative_path)) + pkgbuild = [p for p in parse_pkgbuild(relative_path, _config=config) if p.name == pkgname][0] + _pkgbuilds_cache[pkgname] = pkgbuild + device = parse_device_pkg(pkgbuild) + if lazy: + _device_cache[name] = device + return device + + +def get_profile_device(profile_name: Optional[str] = None, hint_or_set_arch: bool = False): + profile = config.enforce_profile_device_set(profile_name, hint_or_set_arch=hint_or_set_arch) + return get_device(profile.device) diff --git a/packages/test_device.py b/packages/test_device.py new file mode 100644 index 0000000..309dfb6 --- /dev/null +++ b/packages/test_device.py @@ -0,0 +1,91 @@ +import pytest + +import os + +from copy import copy + +from config import ConfigStateHolder, config +from .pkgbuild import init_pkgbuilds, discover_pkgbuilds, Pkgbuild, parse_pkgbuild +from .device import Device, DEVICE_DEPRECATIONS, get_device, get_devices, parse_device_pkg, check_devicepkg_name + + +@pytest.fixture(scope='session') +def initialise_pkgbuilds_dir() -> ConfigStateHolder: + config.try_load_file() + init_pkgbuilds(interactive=False) + return config + + +@pytest.fixture() +def pkgbuilds_dir(initialise_pkgbuilds_dir: ConfigStateHolder) -> str: + global config + config = initialise_pkgbuilds_dir + return config.get_path('pkgbuilds') + + +@pytest.fixture(scope='session') +def pkgbuilds_repo_cached(initialise_pkgbuilds_dir) -> dict[str, Pkgbuild]: + return discover_pkgbuilds() + + +@pytest.fixture() +def pkgbuilds_repo(pkgbuilds_dir, pkgbuilds_repo_cached): + # use pkgbuilds_dir to ensure global config gets overriden, can't be done from session scope fixtures + return pkgbuilds_repo_cached + + +ONEPLUS_ENCHILADA = 'sdm845-oneplus-enchilada' +ONEPLUS_ENCHILADA_PKG = f'device-{ONEPLUS_ENCHILADA}' + + +@pytest.fixture(scope='session') +def enchilada_pkgbuild(initialise_pkgbuilds_dir): + config.try_load_file() + return parse_pkgbuild(os.path.join('device', ONEPLUS_ENCHILADA_PKG))[0] + + +def validate_oneplus_enchilada(d: Device): + assert d + assert d.arch == 'aarch64' + assert d.package and d.package.name == ONEPLUS_ENCHILADA_PKG + + +def test_get_device(): + name = ONEPLUS_ENCHILADA + d = get_device(name) + validate_oneplus_enchilada(d) + + +def test_get_device_deprecated(): + name = 'oneplus-enchilada' + assert name in DEVICE_DEPRECATIONS + d = get_device(name) + # currently redirects to correct package, need to change this test when changed to an exception + validate_oneplus_enchilada(d) + + +def test_parse_device_pkg_enchilada(enchilada_pkgbuild): + validate_oneplus_enchilada(parse_device_pkg(enchilada_pkgbuild)) + + +def test_parse_device_pkg_malformed_arch(enchilada_pkgbuild): + enchilada_pkgbuild = copy(enchilada_pkgbuild) + enchilada_pkgbuild.arches.append('x86_64') + with pytest.raises(Exception): + parse_device_pkg(enchilada_pkgbuild) + + +def test_discover_packages_and_warm_cache_sorry_takes_long(pkgbuilds_repo): + # mostly used to warm up the cache in a user-visible way + assert pkgbuilds_repo + assert ONEPLUS_ENCHILADA_PKG in pkgbuilds_repo + + +def test_get_devices(pkgbuilds_repo: dict[str, Pkgbuild]): + d = get_devices(pkgbuilds_repo) + assert d + assert ONEPLUS_ENCHILADA in d + for p in d.values(): + check_devicepkg_name(p.package.name) + assert 'sdm845-oneplus-common' not in d + validate_oneplus_enchilada(d[ONEPLUS_ENCHILADA])