diff --git a/src/python/jw/pkg/App.py b/src/python/jw/pkg/App.py index e188d805..3dc7fa8a 100644 --- a/src/python/jw/pkg/App.py +++ b/src/python/jw/pkg/App.py @@ -3,6 +3,8 @@ # This source code file is a merge of various build tools and a horrible mess. # +from __future__ import annotations + from typing import TypeAlias import os, sys, argparse, pwd, re @@ -210,7 +212,10 @@ class App(Base): # -- Members without default values self.__opt_os: str|None = None self.__top_name: str|None = None + self.__os_release: str|None = None self.__distro_id: str|None = None + self.__distro_name: str|None = None + self.__distro_codename: str|None = None self.__os_cascade: list[str]|None = None self.__res_cache = ResultCache() self.__topdir: str|None = None @@ -253,16 +258,95 @@ class App(Base): return self.__projs_root @property - def distro_id(self): - if self.__distro_id is None: + def os_release(self) -> str: + if self.__os_release is None: os_release = '/etc/os-release' with open(os_release, 'r') as file: - m = re.search(r'^\s*ID\s*=\s*("?)([^"\n]+)\1\s*$', file.read(), re.MULTILINE) - if m is None: - raise Exception(f'Could not read "ID=" from "{os_release}"') - self.__distro_id = m.group(2) + self.__os_release = file.read() + return self.__os_release + + def os_release_field(self, key: str, throw: bool=False) -> str: + m = re.search(r'^\s*' + key + r'\s*=\s*("?)([^"\n]+)\1\s*$', self.os_release, re.MULTILINE) + if m is None: + if throw: + raise Exception(f'Could not read "{key}=" from /etc/os-release') + return None + return m.group(2) + + @property + def distro_name(self) -> str: + if self.__distro_name is None: + self.__distro_name = self.os_release_field('NAME', throw=True) + return self.__distro_name + + @property + def distro_id(self) -> str: + if self.__distro_id is None: + val = self.os_release_field('ID', throw=True) + match val: + case 'opensuse-tumbleweed': + self.__distro_id = 'opensuse' + case 'kali': + self.__distro_id = 'kali' + case _: + self.__distro_id = val return self.__distro_id + @property + def distro_codename(self) -> str: + match self.distro_id: + case 'opensuse': + self.__distro_codename = \ + self.os_release_field('ID', throw=True).split('-')[1] + case 'kali': + self.__distro_codename = \ + self.os_release_field('VERSION_CODENAME', throw=True).split('-')[1] + case _: + self.__distro_codename = \ + self.os_release_field('VERSION_CODENAME', throw=True) + return self.__distro_codename + + @property + def distro_cascade(self) -> str: + return ' '.join(self.os_cascade()) + + @property + def distro_gnu_triplet(self) -> str: + + import sysconfig + import shutil + import subprocess + + # Best: GNU host triplet Python was built for + for key in ("HOST_GNU_TYPE", "BUILD_GNU_TYPE"): # BUILD_GNU_TYPE can exist too + ret = sysconfig.get_config_var(key) + if isinstance(ret, str) and ret: + return ret + + # Common on Debian/Ubuntu: multiarch component (often looks like a triplet) + ret = sysconfig.get_config_var("MULTIARCH") + if isinstance(ret, str) and ret: + return ret + + # Sometimes exposed (privately) by CPython + ret = getattr(sys.implementation, "_multiarch", None) + if isinstance(ret, str) and ret: + return ret + + # Last resort: ask the system compiler + for cc in ("gcc", "cc", "clang"): + path = shutil.which(cc) + if not path: + continue + try: + ret = subprocess.check_output([path, "-dumpmachine"], text=True, stderr=subprocess.DEVNULL).strip() + if ret: + return ret + except Exception: + pass + + raise RuntimeError('Failed to get GNU triplet from running machine') + def find_dir(self, name: str, search_subdirs: list[str]=[], search_absdirs: list[str]=[], pretty: bool=True): return self.__find_dir(name, search_subdirs, search_absdirs, pretty)