diff --git a/src/distro/distro.py b/src/distro/distro.py index 78ccdfa..77e2c68 100755 --- a/src/distro/distro.py +++ b/src/distro/distro.py @@ -127,6 +127,7 @@ class InfoDict(TypedDict): "SuSE-release", "altlinux-release", "arch-release", + "armbian-release", "base-release", "centos-release", "fedora-release", @@ -905,6 +906,9 @@ def version(self, pretty: bool = False, best: bool = False) -> str: elif self.id() == "debian" or "debian" in self.like().split(): # On Debian-like, add debian_version file content to candidates list. versions.append(self._debian_version) + if self._distro_release_info.get("id") == "armbian": + # On Armbian, add version from armbian-release file to candidates list. + versions.append(self._armbian_version) version = "" if best: # This algorithm uses the last version in priority order that has @@ -1200,7 +1204,7 @@ def _uname_info(self) -> Dict[str, str]: try: cmd = ("uname", "-rs") stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) - except OSError: + except (OSError, subprocess.CalledProcessError): return {} content = self._to_str(stdout).splitlines() return self._parse_uname_content(content) @@ -1225,6 +1229,16 @@ def _debian_version(self) -> str: except FileNotFoundError: return "" + @cached_property + def _armbian_version(self) -> str: + try: + with open( + os.path.join(self.etc_dir, "armbian-release"), encoding="ascii" + ) as fp: + return self._parse_os_release_content(fp).get("version", "") + except FileNotFoundError: + return "" + @staticmethod def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]: if not lines: @@ -1302,6 +1316,14 @@ def _distro_release_info(self) -> Dict[str, str]: if match is not None: distro_info["id"] = match.group(1) + # Armbian release files are not standard as they start with a + # comment. Manually set name if it has not been inferred. + if distro_info["id"] == "armbian" and distro_info.get( + "name", "" + ).startswith("#"): + distro_info["name"] = "Armbian" + distro_info["version_id"] = self._armbian_version + # CloudLinux < 7: manually enrich info with proper id. if "cloudlinux" in distro_info.get("name", "").lower(): distro_info["id"] = "cloudlinux" diff --git a/tests/resources/distros/armbian/etc/armbian-release b/tests/resources/distros/armbian/etc/armbian-release new file mode 100644 index 0000000..342a882 --- /dev/null +++ b/tests/resources/distros/armbian/etc/armbian-release @@ -0,0 +1,14 @@ +# PLEASE DO NOT EDIT THIS FILE +BOARD=nanopim4v2 +BOARD_NAME="NanoPi M4V2" +BOARDFAMILY=rk3399 +BUILD_REPOSITORY_URL=https://github.com/armbian/build +BUILD_REPOSITORY_COMMIT=1a8daf0 +VERSION=23.02.2 +LINUXFAMILY=rockchip64 +ARCH=arm64 +IMAGE_TYPE=nightly +BOARD_TYPE=conf +INITRD_ARCH=arm64 +KERNEL_IMAGE_TYPE=Image +BRANCH=current \ No newline at end of file diff --git a/tests/resources/distros/armbian/etc/os-release b/tests/resources/distros/armbian/etc/os-release new file mode 120000 index 0000000..c4c75b4 --- /dev/null +++ b/tests/resources/distros/armbian/etc/os-release @@ -0,0 +1 @@ +../usr/lib/os-release \ No newline at end of file diff --git a/tests/resources/distros/armbian/usr/lib/os-release b/tests/resources/distros/armbian/usr/lib/os-release new file mode 100644 index 0000000..9b5419d --- /dev/null +++ b/tests/resources/distros/armbian/usr/lib/os-release @@ -0,0 +1,9 @@ +PRETTY_NAME="Debian GNU/Linux 10 (buster)" +NAME="Debian GNU/Linux" +VERSION_ID="10" +VERSION="10 (buster)" +VERSION_CODENAME=buster +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" diff --git a/tests/test_distro.py b/tests/test_distro.py index 2114d90..78c173e 100644 --- a/tests/test_distro.py +++ b/tests/test_distro.py @@ -1919,6 +1919,28 @@ def test_altlinux10_release(self) -> None: } self._test_release_file_info("altlinux-release", desired_info) + def test_armbian_release(self) -> None: + desired_outcome = { + "id": "debian", + "codename": "buster", + "name": "Debian GNU/Linux", + "pretty_name": "Debian GNU/Linux 10 (buster)", + "like": "", + "version": "10", + "pretty_version": "10 (buster)", + "best_version": "23.02.2", + "major_version": "10", + "minor_version": "", + } + self._test_outcome(desired_outcome) + + desired_info = { + "id": "armbian", + "name": "Armbian", + "version_id": "23.02.2", + } + self._test_release_file_info("armbian-release", desired_info) + def _bad_os_listdir(path: str = ".") -> NoReturn: """This function is used by TestOverallWithEtcNotReadable to simulate @@ -2362,6 +2384,12 @@ def test_repr(self) -> None: repr_str = repr(distro._distro) assert "LinuxDistribution" in repr_str for attr in MODULE_DISTRO.__dict__.keys(): - if attr in ("root_dir", "etc_dir", "usr_lib_dir", "_debian_version"): + if attr in ( + "root_dir", + "etc_dir", + "usr_lib_dir", + "_debian_version", + "_armbian_version", + ): continue assert f"{attr}=" in repr_str