From e9abef8b900000a8c5272f37b75092dd07de820f Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Fri, 10 Nov 2023 15:09:05 +0000 Subject: [PATCH] Export appstream catalogue for preinstalled Flatpak apps Although we have added the name and summary for each app to the JSON manifest, there is no substitute for having all the data. XML compresses very well! It is better to capture this at build time rather than fetching it on the fly later because apps can change or disappear, but we would like to be able to describe exactly what is in any given image. librsvg2-bin is required for apps which only ship an SVG file, because appstream-compose really, really wants to export 64px and 128px bitmaps for each app. https://phabricator.endlessm.com/T35013 --- .editorconfig | 2 +- .flake8 | 1 + config/defaults.ini | 3 + hooks/image/70-flatpak-appstream-catalog | 84 ++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100755 hooks/image/70-flatpak-appstream-catalog diff --git a/.editorconfig b/.editorconfig index ea5a6474..e2160bc6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,6 +14,6 @@ indent_style = space indent_size = 2 # Python -[{*.py,eos-image-builder,run-build,helpers/{assemble-manifest,fetch-remote-collection-id,generate-ovf-files,kill-chroot-procs,kolibri-pick-content-from-channel,mutable-path,packages-manifest},hooks/{content/{50-flatpak,50-kolibri-content},image/{50-flatpak.chroot,62-kolibri-options,70-flatpak-manifest,70-ostree-manifest}}}] +[{*.py,eos-image-builder,run-build,helpers/{assemble-manifest,fetch-remote-collection-id,generate-ovf-files,kill-chroot-procs,kolibri-pick-content-from-channel,mutable-path,packages-manifest},hooks/{content/{50-flatpak,50-kolibri-content},image/{50-flatpak.chroot,62-kolibri-options,70-flatpak-appstream-catalog,70-flatpak-manifest,70-ostree-manifest}}}] indent_size = 4 max_line_length = 88 diff --git a/.flake8 b/.flake8 index 493ae6c5..20c31f82 100644 --- a/.flake8 +++ b/.flake8 @@ -17,6 +17,7 @@ filename = ./hooks/content/50-kolibri-content, ./hooks/image/50-flatpak.chroot, ./hooks/image/62-kolibri-options, + ./hooks/image/70-flatpak-appstream-catalog, ./hooks/image/70-flatpak-manifest, ./hooks/image/70-ostree-manifest, ./run-build diff --git a/config/defaults.ini b/config/defaults.ini index e4ad61dc..21dc671e 100644 --- a/config/defaults.ini +++ b/config/defaults.ini @@ -65,6 +65,7 @@ base = bullseye # Packages to install in the buildroot packages_add = + appstream-util attr awscli ca-certificates @@ -88,6 +89,7 @@ packages_add = json-glib-tools jq libfsapfs-utils + librsvg2-bin mount openssh-client ostree @@ -145,6 +147,7 @@ hooks_add = 62-kolibri-automatic-provision 62-kolibri-options 63-icon-grid + 70-flatpak-appstream-catalog 70-flatpak-manifest 70-ostree-manifest 80-ldconfig-aux-cache.chroot diff --git a/hooks/image/70-flatpak-appstream-catalog b/hooks/image/70-flatpak-appstream-catalog new file mode 100755 index 00000000..b8f1ace1 --- /dev/null +++ b/hooks/image/70-flatpak-appstream-catalog @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +# Construct AppStream XML for preinstalled apps + +import glob +import logging +import os +import subprocess +import sys +from xml.etree import ElementTree + +logging.basicConfig( + level=logging.INFO, + format='+ %(asctime)s %(levelname)s %(name)s: %(message)s', + datefmt='%H:%M:%S' +) +logger = logging.getLogger(os.path.basename(__file__)) + +def main(): + root = os.path.join(os.environ['OSTREE_VAR'], 'lib/flatpak/exports') + + # Find the metainfo files to include in the AppStream. + ids = [] + for metainfo in glob.glob(os.path.join(root, 'share/metainfo/*.xml')): + # Everything here should end in .metainfo.xml or .appdata.xml, but + # let's be sure. + if not (metainfo.endswith('.metainfo.xml') or + metainfo.endswith('.appdata.xml')): + raise Exception(f'Unexpected metainfo file {metainfo}') + + # FIXME: It's kind of bad to reject these files here because they're obviously + # valid enough for Flathub to have published them. Reasons that appstream-util + # 0.7.18-1+deb11u1 considers appdata to be invalid include: + # + # - Using a attribute from a more recent version of the spec + # (dozens of apps, including GIMP) + # + # - A
  • element contains a nested element (Pods; OK this is not a + # preinstalled app, but still) + # + # - elements are not in order (FileRoller) + # + # None of these are reasons to exclude the app from the catalogue but there is + # no way to tell the tool to ignore them: if we pass these files to + # appstream-compose it will fail. + cmd = ('appstream-util', 'validate-relax', '--nonet', metainfo) + proc = subprocess.run(cmd) + if proc.returncode != 0: + logger.warning('Skipping %s, considered invalid by appstream-util', metainfo) + continue + + # Convert the filename to an "app-id" as expected by + # appstream-compose. This isn't necessarily the app ID but the stem + # of the metainfo XML file without .metainfo.xml or .appdata.xml. + # Most of the time these are the same. + metainfo_id = os.path.basename(metainfo) + if metainfo_id.endswith('.metainfo.xml'): + metainfo_id = metainfo_id.rpartition('.metainfo.xml')[0] + else: + metainfo_id = metainfo_id.rpartition('.appdata.xml')[0] + logger.info('Including metainfo ID %s', metainfo_id) + ids.append(metainfo_id) + + # Exit if there aren't any desktop metainfo files. + if len(ids) == 0: + logger.info('No metainfo files') + return + + # Compose the AppStream XML. + cmd = [ + 'appstream-compose', + f'--origin={os.environ["EIB_OUTVERSION"]}', + f'--prefix={root}', + f'--output-dir={os.environ["EIB_OUTDIR"]}', + # We don't want to export the icons but we can't tell + # appstream-compose not to do so... + f'--icons-dir={os.environ["EIB_TMPDIR"]}/appstream-compose-icons', + ] + cmd += ids + logger.info('Composing AppStream XML: %s', ' '.join(cmd)) + subprocess.run(cmd, check=True) + +if __name__ == '__main__': + main()