From f6da493b7c77f0d47f8a2717153d0106088be57d 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-compose 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 | 88 ++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100755 hooks/image/70-flatpak-appstream-catalog diff --git a/.editorconfig b/.editorconfig index 5fcf8bcf..bb6ec010 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,52-ek-content-cache,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,52-ek-content-cache,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..8fa9c2be 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-common 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..ac20ce29 --- /dev/null +++ b/hooks/image/70-flatpak-appstream-catalog @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 + +# Construct AppStream XML for preinstalled apps + +import glob +import logging +import os +import subprocess + +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}') + + # 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. + # + # TODO: Switch to appstreamcli validate-relax once we are on Bookworm and can + # use appstreamcli compose. + 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. + # TODO: Switch to appstreamcli compose once we are on Bookworm + 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()