Skip to content

Commit f73e403

Browse files
committed
Add SplitArtifacts=metainfo
GNOME Software parses .metainfo.xml files to provide nice metadata for artifacts it downloads/updates. Follow the spec and add parameter for it, filling the content from os-release. https://www.freedesktop.org/software/appstream/docs/sect-Metadata-OS.html
1 parent bae79ab commit f73e403

File tree

4 files changed

+118
-3
lines changed

4 files changed

+118
-3
lines changed

mkosi/__init__.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from contextlib import AbstractContextManager
2727
from pathlib import Path
2828
from typing import Any, Optional, Union, cast
29+
from urllib.parse import urlparse
2930

3031
from mkosi.archive import can_extract_tar, extract_tar, make_cpio, make_tar
3132
from mkosi.bootloader import (
@@ -385,9 +386,68 @@ def configure_os_release(context: Context) -> None:
385386

386387
newosrelease.rename(osrelease)
387388

389+
osrelease_content = read_env_file(osrelease)
390+
388391
if ArtifactOutput.os_release in context.config.split_artifacts:
389392
shutil.copy(osrelease, context.staging / context.config.output_split_os_release)
390393

394+
# https://www.freedesktop.org/software/appstream/docs/sect-Metadata-OS.html
395+
osid = context.config.appstream_id or osrelease_content.get("ID", "linux")
396+
name = context.config.appstream_name or osrelease_content.get("NAME") or context.config.image_id or osid
397+
if context.config.appstream_description:
398+
description = context.config.appstream_description
399+
elif "PRETTY_NAME" in osrelease_content:
400+
description = f"{osrelease_content.get("PRETTY_NAME")} built with mkosi"
401+
else:
402+
description = "Image built with mkosi"
403+
icon = context.config.appstream_icon or "https://brand.systemd.io/assets/svg/systemd-logomark.svg"
404+
if context.config.appstream_homepage:
405+
home_url = context.config.appstream_homepage
406+
elif "HOME_URL" in osrelease_content:
407+
home_url = osrelease_content.get("HOME_URL")
408+
else:
409+
home_url = None
410+
if home_url:
411+
url = urlparse(home_url)
412+
if not url.netloc:
413+
home_url = None
414+
if context.config.appstream_id:
415+
id = context.config.appstream_id
416+
elif home_url:
417+
netloc = url.netloc.split(".")
418+
netloc.reverse()
419+
netloc.remove("www")
420+
id = ".".join(netloc)
421+
id += f".{osid}"
422+
else:
423+
id = osid
424+
timestamp = (
425+
datetime.datetime.fromtimestamp(context.config.source_date_epoch, tz=datetime.timezone.utc)
426+
if context.config.source_date_epoch is not None
427+
else datetime.datetime.now(tz=datetime.timezone.utc)
428+
).isoformat()
429+
430+
metainfo = '<?xml version="1.0" encoding="UTF-8"?>\n'
431+
metainfo += '<component type="operating-system">\n'
432+
metainfo += f" <id>{id}</id>\n"
433+
metainfo += f" <name>{name}</name>\n"
434+
metainfo += f" <summary>{osid} image built with mkosi</summary>\n"
435+
metainfo += f" <description><p>{description}</p></description>\n"
436+
if home_url:
437+
metainfo += f' <url type="homepage">{home_url}</url>\n'
438+
if icon:
439+
metainfo += f' <icon type="remote">{icon}</icon>\n'
440+
metainfo += " <metadata_license>FSFAP</metadata_license>\n"
441+
metainfo += " <releases>\n"
442+
metainfo += f' <release version="{context.config.image_version}" date="{timestamp}" type="development">\n' # noqa E501
443+
metainfo += " <description></description>\n"
444+
metainfo += " </release>\n"
445+
metainfo += " </releases>\n"
446+
metainfo += "</component>\n"
447+
(context.root / f"/usr/share/metainfo/{id}.metainfo.xml").write_text(metainfo)
448+
if ArtifactOutput.metainfo in context.config.split_artifacts:
449+
(context.staging / context.config.output_split_appstream_metainfo).write_text(metainfo)
450+
391451

392452
def configure_extension_release(context: Context) -> None:
393453
if context.config.output_format not in (OutputFormat.sysext, OutputFormat.confext):

mkosi/config.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ class ArtifactOutput(StrEnum):
575575
pcrs = enum.auto()
576576
roothash = enum.auto()
577577
os_release = enum.auto()
578+
metainfo = enum.auto()
578579

579580
@staticmethod
580581
def compat_no() -> list["ArtifactOutput"]:
@@ -1986,6 +1987,13 @@ class Config:
19861987
machine: Optional[str]
19871988
forward_journal: Optional[Path]
19881989

1990+
appstream_id: Optional[str]
1991+
appstream_name: Optional[str]
1992+
appstream_summary: Optional[str]
1993+
appstream_description: Optional[str]
1994+
appstream_url: Optional[str]
1995+
appstream_icon: Optional[str]
1996+
19891997
vmm: Vmm
19901998
console: ConsoleMode
19911999
cpus: int
@@ -2132,6 +2140,10 @@ def output_split_roothash(self) -> str:
21322140
def output_split_os_release(self) -> str:
21332141
return f"{self.output}.osrelease"
21342142

2143+
@property
2144+
def output_split_appstream_metainfo(self) -> str:
2145+
return f"{self.output}.metainfo.xml"
2146+
21352147
@property
21362148
def output_nspawn_settings(self) -> str:
21372149
return f"{self.output}.nspawn"
@@ -2173,6 +2185,7 @@ def outputs(self) -> list[str]:
21732185
self.output_split_pcrs,
21742186
self.output_split_roothash,
21752187
self.output_split_os_release,
2188+
self.output_split_appstream_metainfo,
21762189
self.output_nspawn_settings,
21772190
self.output_checksum,
21782191
self.output_signature,
@@ -3977,6 +3990,42 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple
39773990
default=ConfigFeature.auto,
39783991
help="Run systemd-storagetm as part of the serve verb",
39793992
),
3993+
ConfigSetting(
3994+
dest="appstream_id",
3995+
section="Content",
3996+
parse=config_parse_string,
3997+
help="'id' field for Appstream metainfo file",
3998+
),
3999+
ConfigSetting(
4000+
dest="appstream_name",
4001+
section="Content",
4002+
parse=config_parse_string,
4003+
help="'name' field for Appstream metainfo file",
4004+
),
4005+
ConfigSetting(
4006+
dest="appstream_summary",
4007+
section="Content",
4008+
parse=config_parse_string,
4009+
help="'summary' field for Appstream metainfo file",
4010+
),
4011+
ConfigSetting(
4012+
dest="appstream_description",
4013+
section="Content",
4014+
parse=config_parse_string,
4015+
help="'description' field for Appstream metainfo file",
4016+
),
4017+
ConfigSetting(
4018+
dest="appstream_url",
4019+
section="Content",
4020+
parse=config_parse_string,
4021+
help="'url' homepage field for Appstream metainfo file",
4022+
),
4023+
ConfigSetting(
4024+
dest="appstream_icon",
4025+
section="Content",
4026+
parse=config_parse_string,
4027+
help="'icon' URL field for Appstream metainfo file",
4028+
),
39804029
]
39814030
SETTINGS_LOOKUP_BY_NAME = {name: s for s in SETTINGS for name in [s.name, *s.compat_names]}
39824031
SETTINGS_LOOKUP_BY_DEST = {s.dest: s for s in SETTINGS}
@@ -5033,6 +5082,12 @@ def summary(config: Config) -> str:
50335082
Make Initrd: {yes_no(config.make_initrd)}
50345083
SSH: {yes_no(config.ssh)}
50355084
SELinux Relabel: {config.selinux_relabel}
5085+
Appstream Metainfo ID: {none_to_none(config.appstream_id)}
5086+
Appstream Metainfo Name: {none_to_none(config.appstream_name)}
5087+
Appstream Metainfo Summary: {none_to_none(config.appstream_summary)}
5088+
Appstream Metainfo Description: {none_to_none(config.appstream_description)}
5089+
Appstream Metainfo URL: {none_to_none(config.appstream_url)}
5090+
Appstream Icon: {none_to_none(config.appstream_icon)}
50365091
"""
50375092

50385093
if config.output_format.is_extension_or_portable_image() or config.output_format in (

mkosi/resources/man/mkosi.1.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,8 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
624624
`SplitArtifacts=`, `--split-artifacts=`
625625
: The artifact types to split out of the final image. A comma-delimited
626626
list consisting of `uki`, `kernel`, `initrd`, `os-release`, `prcs`, `partitions`,
627-
`roothash` and `tar`. When building a bootable image `kernel` and `initrd`
628-
correspond to their artifact found in the image (or in the UKI),
627+
`appstream-metainfo`, `roothash` and `tar`. When building a bootable image `kernel`
628+
and `initrd` correspond to their artifact found in the image (or in the UKI),
629629
while `uki` copies out the entire UKI. If `pcrs` is specified, a JSON
630630
file containing the pre-calculated TPM2 digests is written out, according
631631
to the [UKI specification](https://uapi-group.org/specifications/specs/unified_kernel_image/#json-format-for-pcrsig),

mkosi/resources/mkosi-obs/mkosi.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ LocalMirror=file:///.build.binaries/
1414
[Output]
1515
OutputDirectory=
1616
Checksum=yes
17-
SplitArtifacts=pcrs,roothash
17+
SplitArtifacts=pcrs,roothash,appstream-metainfo
1818
CompressOutput=zstd
1919

2020
[Validation]

0 commit comments

Comments
 (0)