Skip to content

Commit d2ee7db

Browse files
committed
Add a DistributionRelease object
1 parent a0b4e1a commit d2ee7db

20 files changed

+262
-106
lines changed

mkosi/__init__.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
yes_no,
8585
)
8686
from mkosi.context import Context
87-
from mkosi.distributions import Distribution, detect_distribution
87+
from mkosi.distributions import Distribution, DistributionRelease, detect_distribution
8888
from mkosi.documentation import show_docs
8989
from mkosi.installer import clean_package_manager_metadata
9090
from mkosi.kmod import gen_required_kernel_modules, loaded_modules, process_kernel_modules
@@ -595,7 +595,7 @@ def run_configure_scripts(config: Config) -> Config:
595595

596596
env = dict(
597597
DISTRIBUTION=str(config.distribution),
598-
RELEASE=config.release,
598+
RELEASE=str(config.release),
599599
ARCHITECTURE=str(config.architecture),
600600
QEMU_ARCHITECTURE=config.architecture.to_qemu(),
601601
DISTRIBUTION_ARCHITECTURE=config.distribution.architecture(config.architecture),
@@ -641,7 +641,7 @@ def run_sync_scripts(config: Config) -> None:
641641

642642
env = dict(
643643
DISTRIBUTION=str(config.distribution),
644-
RELEASE=config.release,
644+
RELEASE=str(config.release),
645645
ARCHITECTURE=str(config.architecture),
646646
DISTRIBUTION_ARCHITECTURE=config.distribution.architecture(config.architecture),
647647
SRCDIR="/work/src",
@@ -754,7 +754,7 @@ def run_prepare_scripts(context: Context, build: bool) -> None:
754754

755755
env = dict(
756756
DISTRIBUTION=str(context.config.distribution),
757-
RELEASE=context.config.release,
757+
RELEASE=str(context.config.release),
758758
ARCHITECTURE=str(context.config.architecture),
759759
DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture),
760760
BUILDROOT="/buildroot",
@@ -822,7 +822,7 @@ def run_build_scripts(context: Context) -> None:
822822

823823
env = dict(
824824
DISTRIBUTION=str(context.config.distribution),
825-
RELEASE=context.config.release,
825+
RELEASE=str(context.config.release),
826826
ARCHITECTURE=str(context.config.architecture),
827827
DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture),
828828
BUILDROOT="/buildroot",
@@ -897,7 +897,7 @@ def run_postinst_scripts(context: Context) -> None:
897897

898898
env = dict(
899899
DISTRIBUTION=str(context.config.distribution),
900-
RELEASE=context.config.release,
900+
RELEASE=str(context.config.release),
901901
ARCHITECTURE=str(context.config.architecture),
902902
DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture),
903903
BUILDROOT="/buildroot",
@@ -966,7 +966,7 @@ def run_finalize_scripts(context: Context) -> None:
966966

967967
env = dict(
968968
DISTRIBUTION=str(context.config.distribution),
969-
RELEASE=context.config.release,
969+
RELEASE=str(context.config.release),
970970
ARCHITECTURE=str(context.config.architecture),
971971
DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture),
972972
BUILDROOT="/buildroot",
@@ -1035,7 +1035,7 @@ def run_postoutput_scripts(context: Context) -> None:
10351035

10361036
env = dict(
10371037
DISTRIBUTION=str(context.config.distribution),
1038-
RELEASE=context.config.release,
1038+
RELEASE=str(context.config.release),
10391039
ARCHITECTURE=str(context.config.architecture),
10401040
DISTRIBUTION_ARCHITECTURE=context.config.distribution.architecture(context.config.architecture),
10411041
SRCDIR="/work/src",
@@ -3408,12 +3408,14 @@ def make_image(
34083408
cmdline += ["--definitions", workdir(d)]
34093409
opts += ["--ro-bind", d, workdir(d)]
34103410

3411-
def can_orphan_file(distribution: Optional[Distribution], release: Optional[str]) -> bool:
3411+
def can_orphan_file(
3412+
distribution: Optional[Distribution], release: Optional[DistributionRelease]
3413+
) -> bool:
34123414
if distribution is None:
34133415
return True
34143416

34153417
return not (
3416-
(distribution == Distribution.centos and release and GenericVersion(release) == 9)
3418+
(distribution == Distribution.centos and release and release == 9)
34173419
or (distribution == Distribution.ubuntu and release == "jammy")
34183420
)
34193421

@@ -4149,7 +4151,7 @@ def run_sandbox(args: Args, config: Config) -> None:
41494151
if hd:
41504152
env |= {"MKOSI_HOST_DISTRIBUTION": str(hd)}
41514153
if hr:
4152-
env |= {"MKOSI_HOST_RELEASE": hr}
4154+
env |= {"MKOSI_HOST_RELEASE": str(hr)}
41534155
if config.tools() != Path("/"):
41544156
env |= {"MKOSI_DEFAULT_TOOLS_TREE_PATH": os.fspath(config.tools())}
41554157

@@ -4580,7 +4582,7 @@ def run_clean_scripts(config: Config) -> None:
45804582

45814583
env = dict(
45824584
DISTRIBUTION=str(config.distribution),
4583-
RELEASE=config.release,
4585+
RELEASE=str(config.release),
45844586
ARCHITECTURE=str(config.architecture),
45854587
DISTRIBUTION_ARCHITECTURE=config.distribution.architecture(config.architecture),
45864588
SRCDIR="/work/src",

mkosi/config.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from pathlib import Path
3131
from typing import Any, Callable, ClassVar, Generic, Optional, Protocol, TypeVar, Union, cast
3232

33-
from mkosi.distributions import Distribution, detect_distribution
33+
from mkosi.distributions import Distribution, DistributionRelease, detect_distribution
3434
from mkosi.log import ARG_DEBUG, ARG_DEBUG_SANDBOX, ARG_DEBUG_SHELL, complete_step, die
3535
from mkosi.pager import page
3636
from mkosi.run import SandboxProtocol, find_binary, nosandbox, run, sandbox_cmd, workdir
@@ -989,20 +989,21 @@ def config_default_distribution(namespace: dict[str, Any]) -> Distribution:
989989
return detected
990990

991991

992-
def config_default_release(namespace: dict[str, Any]) -> str:
992+
def config_default_release(namespace: dict[str, Any]) -> DistributionRelease:
993993
hd: Optional[Distribution]
994-
hr: Optional[str]
994+
hr: Optional[DistributionRelease]
995995

996996
if (d := os.getenv("MKOSI_HOST_DISTRIBUTION")) and (r := os.getenv("MKOSI_HOST_RELEASE")):
997-
hd, hr = Distribution(d), r
997+
hd = Distribution(d)
998+
hr = hd.parse_release(r)
998999
else:
9991000
hd, hr = detect_distribution()
10001001

10011002
# If the configured distribution matches the host distribution, use the same release as the host.
10021003
if namespace["distribution"] == hd and hr is not None:
10031004
return hr
10041005

1005-
return cast(str, namespace["distribution"].default_release())
1006+
return cast(DistributionRelease, namespace["distribution"].default_release())
10061007

10071008

10081009
def config_default_tools_tree_distribution(namespace: dict[str, Any]) -> Distribution:
@@ -1918,7 +1919,7 @@ class Config:
19181919
pass_environment: list[str]
19191920

19201921
distribution: Distribution
1921-
release: str
1922+
release: DistributionRelease
19221923
architecture: Architecture
19231924
mirror: Optional[str]
19241925
local_mirror: Optional[str]
@@ -2095,6 +2096,9 @@ class Config:
20952096

20962097
image: str
20972098

2099+
def __post_init__(self) -> None:
2100+
object.__setattr__(self, "release", self.distribution.installer().parse_release(str(self.release)))
2101+
20982102
def finalize_environment(self) -> dict[str, str]:
20992103
env = {
21002104
"SYSTEMD_TMPFILES_FORCE_SUBVOL": "0",
@@ -2328,7 +2332,7 @@ def expand_key_specifiers(self, key: str) -> str:
23282332
specifiers = {
23292333
"&": "&",
23302334
"d": str(self.distribution),
2331-
"r": self.release,
2335+
"r": str(self.release),
23322336
"a": str(self.architecture),
23332337
"i": self.image_id or "",
23342338
"v": self.image_version or "",
@@ -5589,6 +5593,8 @@ def default(self, o: Any) -> Any:
55895593
return str(o)
55905594
elif isinstance(o, GenericVersion):
55915595
return str(o)
5596+
elif isinstance(o, DistributionRelease):
5597+
return str(o)
55925598
elif isinstance(o, os.PathLike):
55935599
return os.fspath(o)
55945600
elif isinstance(o, uuid.UUID):

mkosi/distributions/__init__.py

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# SPDX-License-Identifier: LGPL-2.1-or-later
22

33
import enum
4+
import functools
45
import importlib
56
import urllib.parse
67
from pathlib import Path
78
from typing import TYPE_CHECKING, Optional, cast
89

910
from mkosi.util import StrEnum, read_env_file
11+
from mkosi.versioncomp import GenericVersion
1012

1113
if TYPE_CHECKING:
1214
from mkosi.config import Architecture, Config
@@ -21,7 +23,66 @@ class PackageType(StrEnum):
2123
pkg = enum.auto()
2224

2325

26+
@functools.total_ordering
27+
class DistributionRelease:
28+
def __init__(self, release: str, *, releasemap: Optional[dict[str, tuple[str, str]]] = None) -> None:
29+
self.releasemap = {} if releasemap is None else releasemap
30+
self._release = release
31+
32+
def __str__(self) -> str:
33+
return self.releasemap.get(self._release, (None, self._release))[1]
34+
35+
def __fspath__(self) -> str:
36+
return str(self)
37+
38+
def __repr__(self) -> str:
39+
return f"DistributionRelease('{self._release}')"
40+
41+
def major(self) -> str:
42+
return self._release.partition(".")[0]
43+
44+
def minor(self) -> str:
45+
return self._release.partition(".")[1]
46+
47+
def isnumeric(self) -> bool:
48+
return self._release.isdigit()
49+
50+
def _construct_versions(self, other: object) -> tuple[GenericVersion, Optional[GenericVersion]]:
51+
v1 = GenericVersion(self.releasemap.get(self._release.lower(), (self._release.lower(),))[0])
52+
53+
if isinstance(other, DistributionRelease):
54+
if self.releasemap == other.releasemap:
55+
v2 = GenericVersion(
56+
other.releasemap.get(other._release.lower(), (other._release.lower(),))[0]
57+
)
58+
else:
59+
v2 = None
60+
elif isinstance(other, GenericVersion):
61+
v2 = GenericVersion(self.releasemap.get(str(other), (str(other),))[0])
62+
elif isinstance(other, (str, int)):
63+
v2 = GenericVersion(self.releasemap.get(str(other), (str(other),))[0])
64+
else:
65+
raise ValueError(f"{other} not a DistributionRelease, str or int")
66+
67+
return v1, v2
68+
69+
def __eq__(self, other: object) -> bool:
70+
v1, v2 = self._construct_versions(other)
71+
if v2 is None:
72+
return False
73+
return v1 == v2
74+
75+
def __lt__(self, other: object) -> bool:
76+
v1, v2 = self._construct_versions(other)
77+
if v2 is None:
78+
return False
79+
return v1 < v2
80+
81+
2482
class DistributionInstaller:
83+
_default_release: str = ""
84+
_releasemap: dict[str, tuple[str, str]] = {}
85+
2586
@classmethod
2687
def pretty_name(cls) -> str:
2788
raise NotImplementedError
@@ -55,8 +116,12 @@ def package_type(cls) -> PackageType:
55116
return PackageType.none
56117

57118
@classmethod
58-
def default_release(cls) -> str:
59-
return ""
119+
def default_release(cls) -> DistributionRelease:
120+
return DistributionRelease(cls._default_release, releasemap=cls._releasemap)
121+
122+
@classmethod
123+
def parse_release(cls, release: str) -> DistributionRelease:
124+
return DistributionRelease(release, releasemap=cls._releasemap)
60125

61126
@classmethod
62127
def default_tools_tree_distribution(cls) -> Optional["Distribution"]:
@@ -112,6 +177,9 @@ def is_rpm_distribution(self) -> bool:
112177
Distribution.alma,
113178
)
114179

180+
def parse_release(self, release: str) -> DistributionRelease:
181+
return self.installer().parse_release(release)
182+
115183
def pretty_name(self) -> str:
116184
return self.installer().pretty_name()
117185

@@ -136,7 +204,7 @@ def architecture(self, arch: "Architecture") -> str:
136204
def package_type(self) -> PackageType:
137205
return self.installer().package_type()
138206

139-
def default_release(self) -> str:
207+
def default_release(self) -> DistributionRelease:
140208
return self.installer().default_release()
141209

142210
def default_tools_tree_distribution(self) -> "Distribution":
@@ -156,7 +224,9 @@ def installer(self) -> type[DistributionInstaller]:
156224
return cast(type[DistributionInstaller], installer)
157225

158226

159-
def detect_distribution(root: Path = Path("/")) -> tuple[Optional[Distribution], Optional[str]]:
227+
def detect_distribution(
228+
root: Path = Path("/"),
229+
) -> tuple[Optional[Distribution], Optional[DistributionRelease]]:
160230
try:
161231
os_release = read_env_file(root / "etc/os-release")
162232
except FileNotFoundError:
@@ -183,7 +253,10 @@ def detect_distribution(root: Path = Path("/")) -> tuple[Optional[Distribution],
183253
if d and d.is_apt_distribution() and version_codename:
184254
version_id = version_codename
185255

186-
return d, version_id
256+
if d and version_id:
257+
return d, d.parse_release(version_id)
258+
259+
return d, DistributionRelease(version_id) if version_id is not None else None
187260

188261

189262
def join_mirror(mirror: str, link: str) -> str:

mkosi/distributions/alma.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ def pretty_name(cls) -> str:
1212

1313
@classmethod
1414
def gpgurls(cls, context: Context) -> tuple[str, ...]:
15-
major = cls.major_release(context.config)
1615
return (
1716
find_rpm_gpgkey(
1817
context,
19-
f"RPM-GPG-KEY-AlmaLinux-{major}",
20-
f"https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-{major}",
18+
f"RPM-GPG-KEY-AlmaLinux-{context.config.release.major}",
19+
f"https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-{context.config.release.major}",
2120
),
2221
)
2322

mkosi/distributions/arch.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
from mkosi.config import Architecture, Config
99
from mkosi.context import Context
1010
from mkosi.curl import curl
11-
from mkosi.distributions import DistributionInstaller, PackageType
11+
from mkosi.distributions import DistributionInstaller, DistributionRelease, PackageType
1212
from mkosi.installer.pacman import Pacman, PacmanRepository
1313
from mkosi.log import complete_step, die
1414

1515

1616
class Installer(DistributionInstaller):
17+
_default_release = "rolling"
18+
1719
@classmethod
1820
def pretty_name(cls) -> str:
1921
return "Arch Linux"
@@ -27,8 +29,8 @@ def package_type(cls) -> PackageType:
2729
return PackageType.pkg
2830

2931
@classmethod
30-
def default_release(cls) -> str:
31-
return "rolling"
32+
def parse_release(cls, release: str) -> DistributionRelease:
33+
return DistributionRelease("rolling")
3234

3335
@classmethod
3436
def package_manager(cls, config: "Config") -> type[Pacman]:

mkosi/distributions/azure.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@
1414

1515

1616
class Installer(fedora.Installer):
17+
_default_release = "3.0"
18+
1719
@classmethod
1820
def pretty_name(cls) -> str:
1921
return "Azure Linux"
2022

21-
@classmethod
22-
def default_release(cls) -> str:
23-
return "3.0"
24-
2523
@classmethod
2624
def filesystem(cls) -> str:
2725
return "ext4"
@@ -50,7 +48,7 @@ def repositories(cls, context: Context) -> Iterable[RpmRepository]:
5048
return
5149

5250
mirror = context.config.mirror or "https://packages.microsoft.com/azurelinux"
53-
url = join_mirror(mirror, context.config.release)
51+
url = join_mirror(mirror, str(context.config.release))
5452

5553
for repo in ("base", "extended", "ms-oss", "ms-non-oss", "cloud-native", "nvidia"):
5654
yield RpmRepository(

0 commit comments

Comments
 (0)