diff --git a/.gitignore b/.gitignore index c276f529..a9dc789a 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,4 @@ system-helper/org.freedesktop.Flatpak.rules flatpak-bwrap py-compile libglnx-config.h +subprojects/.wraplock diff --git a/doc/flatpak-builder.xml b/doc/flatpak-builder.xml index 4e3564f2..ff2d0fa7 100644 --- a/doc/flatpak-builder.xml +++ b/doc/flatpak-builder.xml @@ -641,6 +641,18 @@ + + + + + Set the AppStream compose image format used for + generated icons and screenshots. Accepted values + are png and jxl. + jxl requires AppStream version >= 1.2.0. + Defaults to png if unspecified. + + + diff --git a/src/builder-context.c b/src/builder-context.c index 8ecb0fb6..2272a3d8 100644 --- a/src/builder-context.c +++ b/src/builder-context.c @@ -93,6 +93,7 @@ struct BuilderContext BuilderAsUrlPolicy as_url_policy; BuilderSourceDateEpochMode source_date_epoch_mode; + BuilderAsImageFormat as_image_format; }; typedef struct @@ -1310,6 +1311,19 @@ builder_context_get_as_url_policy (BuilderContext *self) return self->as_url_policy; } +void +builder_context_set_as_image_format (BuilderContext *self, + BuilderAsImageFormat format) +{ + self->as_image_format = format; +} + +BuilderAsImageFormat +builder_context_get_as_image_format (BuilderContext *self) +{ + return self->as_image_format; +} + gboolean builder_context_resolve_source_uri (BuilderContext *self, const char *url, diff --git a/src/builder-context.h b/src/builder-context.h index fa892843..28f42ed0 100644 --- a/src/builder-context.h +++ b/src/builder-context.h @@ -40,6 +40,10 @@ typedef enum { BUILDER_SOURCE_DATE_EPOCH_UNSET, } BuilderSourceDateEpochMode; +typedef enum { + BUILDER_AS_IMAGE_FORMAT_PNG = 0, + BUILDER_AS_IMAGE_FORMAT_JXL, +} BuilderAsImageFormat; /* Same as SOUP_HTTP_URI_FLAGS, means all possible flags for http uris */ @@ -213,6 +217,10 @@ void builder_context_set_as_url_policy (BuilderContext *self, BuilderAsUrlPolicy policy); BuilderAsUrlPolicy builder_context_get_as_url_policy (BuilderContext *self); +void builder_context_set_as_image_format (BuilderContext *self, + BuilderAsImageFormat format); +BuilderAsImageFormat builder_context_get_as_image_format (BuilderContext *self); + G_DEFINE_AUTOPTR_CLEANUP_FUNC (BuilderContext, g_object_unref) G_END_DECLS diff --git a/src/builder-main.c b/src/builder-main.c index 9b4f36db..7a1f5451 100644 --- a/src/builder-main.c +++ b/src/builder-main.c @@ -94,6 +94,7 @@ static gboolean opt_log_system_bus; static gboolean opt_yes; static gint64 opt_source_date_epoch = SOURCE_DATE_EPOCH_DEFAULT; static gchar *opt_as_url_policy = NULL; +static gchar *opt_as_image_format = NULL; static GOptionEntry entries[] = { { "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Print debug information during command processing", NULL }, @@ -151,6 +152,7 @@ static GOptionEntry entries[] = { { "no-shallow-clone", 0, 0, G_OPTION_ARG_NONE, &opt_no_shallow_clone, "Don't use shallow clones when mirroring git repos", NULL }, { "override-source-date-epoch", 0, 0, G_OPTION_ARG_INT64, &opt_source_date_epoch, "Use this timestamp to perform the build, instead of the last modification time of the manifest.", NULL }, { "compose-url-policy", 0, 0, G_OPTION_ARG_STRING, &opt_as_url_policy, "Set the AppStream compose URL policy to either 'partial' (default) or 'full'", "POLICY" }, + { "compose-image-format", 0, 0, G_OPTION_ARG_STRING, &opt_as_image_format, "Set the AppStream compose image format to either 'png' (default) or 'jxl'", "FORMAT" }, { NULL } }; @@ -641,6 +643,28 @@ main (int argc, builder_context_set_as_url_policy (build_context, policy); } + { + BuilderAsImageFormat image_format = BUILDER_AS_IMAGE_FORMAT_PNG; + + if (opt_as_image_format == NULL || g_strcmp0 (opt_as_image_format, "png") == 0) + image_format = BUILDER_AS_IMAGE_FORMAT_PNG; + else if (g_strcmp0 (opt_as_image_format, "jxl") == 0) + image_format = BUILDER_AS_IMAGE_FORMAT_JXL; + else + { + g_printerr ("Invalid value for --compose-image-format: %s\n", opt_as_image_format); + return 1; + } + + if (image_format == BUILDER_AS_IMAGE_FORMAT_JXL && !appstream_has_version (1, 2, 0)) + { + g_printerr ("AppStream version >= 1.2.0 required for 'jxl' compose image format\n"); + return 1; + } + + builder_context_set_as_image_format (build_context, image_format); + } + git_init_email (); if (!builder_context_create_state_dir (build_context, &error)) diff --git a/src/builder-manifest.c b/src/builder-manifest.c index 472b96e4..ccf045e3 100644 --- a/src/builder-manifest.c +++ b/src/builder-manifest.c @@ -2436,6 +2436,7 @@ cmpstringp (const void *p1, const void *p2) static gboolean appstreamcli_compose (GError **error, BuilderAsUrlPolicy as_url_policy, + BuilderAsImageFormat as_image_format, ...) { g_autoptr(GPtrArray) args = NULL; @@ -2449,7 +2450,22 @@ appstreamcli_compose (GError **error, if (as_url_policy == BUILDER_AS_URL_POLICY_FULL) g_ptr_array_add (args, g_strdup ("--no-partial-urls")); - va_start (ap, as_url_policy); + /* For AppStream >= 1.2.0 we need to explicitly pass --image-format, + since JXL is now the default and we want to keep producing + PNG unless the user asked for JXL. */ + if (appstream_has_version (1, 2, 0)) + { + g_ptr_array_add (args, g_strdup ("--image-format")); + g_ptr_array_add (args, g_strdup (as_image_format == BUILDER_AS_IMAGE_FORMAT_JXL ? "jxl" : "png")); + } + else + { + /* For AppStream < 1.2.0, --image-format doesn't exist and PNG is + the only supported format. The JXL choice is gated in main + on having >= 1.2.0 */ + } + + va_start (ap, as_image_format); while ((arg = va_arg (ap, const gchar *))) g_ptr_array_add (args, g_strdup (arg)); g_ptr_array_add (args, NULL); @@ -3100,6 +3116,7 @@ builder_manifest_cleanup (BuilderManifest *self, const char *opt_mirror_screenshots_url = builder_context_get_opt_mirror_screenshots_url (context); gboolean opt_export_only = builder_context_get_opt_export_only (context); BuilderAsUrlPolicy as_url_policy = builder_context_get_as_url_policy (context); + BuilderAsImageFormat as_image_format = builder_context_get_as_image_format (context); if (opt_mirror_screenshots_url && !opt_export_only) { @@ -3112,6 +3129,7 @@ builder_manifest_cleanup (BuilderManifest *self, g_print ("Saving screenshots in %s\n", flatpak_file_get_path_cached (media_dir)); if (!appstreamcli_compose (error, as_url_policy, + as_image_format, "--prefix=/", origin, arg_base_url, @@ -3129,6 +3147,7 @@ builder_manifest_cleanup (BuilderManifest *self, g_print ("Running appstreamcli compose\n"); if (!appstreamcli_compose (error, as_url_policy, + as_image_format, "--prefix=/", origin, result_root_arg, diff --git a/tests/libtest.sh b/tests/libtest.sh index e84ed18c..b610d699 100644 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -167,6 +167,14 @@ assert_file_has_content () { fi } +assert_file_has_content_extended () { + if ! grep -q -E -e "$2" "$1"; then + sed -e 's/^/# /' < "$1" >&2 + echo 1>&2 "File '$1' doesn't match extended regexp '$2'" + exit 1 + fi +} + assert_symlink_has_content () { if ! readlink "$1" | grep -q -e "$2"; then readlink "$1" |sed -e 's/^/# /' >&2 diff --git a/tests/meson.build b/tests/meson.build index c37369ee..fc1290bc 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -27,6 +27,7 @@ test_names = [ 'test-builder-locale-migration', 'test-build-subj', 'test-sandboxed-mode-source-resolution', + 'test-appstream', ] tap_test = find_program( diff --git a/tests/test-appstream.sh b/tests/test-appstream.sh new file mode 100755 index 00000000..fed1f937 --- /dev/null +++ b/tests/test-appstream.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# +# Copyright (C) 2026 Boudhayan Bhattacharya +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +skip_without_fuse + +echo "1..3" + +setup_repo +install_repo +setup_sdk_repo +install_sdk_repo + +cd $TEST_DATA_DIR/ + +cp $(dirname $0)/org.flatpak_builder.gui.desktop . +cp $(dirname $0)/org.flatpak_builder.gui.metainfo.xml . +cp $(dirname $0)/org.test.Hello-256.png . +cp $(dirname $0)/org.flatpak.appstream_media.json . + +# test compose partial url policy +APPDIR=builddir_sc \ +run_build \ + --mirror-screenshots-url=https://example.org/media \ + --state-dir .fp-compose-url-policy-partial \ + --compose-url-policy=partial \ + org.flatpak.appstream_media.json +# we test for the icon tag instead of screenshot +# the former works offline the latter does not +gzip -cdq builddir_sc/files/share/app-info/xmls/org.flatpak.appstream_media.xml.gz \ + > appstream.xml +assert_file_has_content_extended appstream.xml '>org/flatpak/appstream_media/[^/]+/icons/128x128/org.flatpak.appstream_media.png' + +echo "ok compose partial url policy" + +# test compose full url policy +if appstream_has_version 0 16 3; then + APPDIR=builddir_sc \ + run_build \ + --mirror-screenshots-url=https://example.org/media \ + --state-dir .fp-compose-url-policy-full \ + --compose-url-policy=full \ + org.flatpak.appstream_media.json + + gzip -cdq builddir_sc/files/share/app-info/xmls/org.flatpak.appstream_media.xml.gz \ + > appstream.xml + assert_file_has_content_extended appstream.xml '>https://example.org/media/org/flatpak/appstream_media/[^/]+/icons/128x128/org.flatpak.appstream_media.png' + + echo "ok compose full url policy" +else + echo "ok # Skip AppStream < 0.16.3" +fi + +# test compose jxl image format +if appstream_has_version 1 2 0; then + APPDIR=builddir_jxl \ + run_build \ + --mirror-screenshots-url=https://example.org/media \ + --state-dir .fp-compose-image-format-jxl \ + --compose-url-policy=full \ + --compose-image-format=jxl \ + org.flatpak.appstream_media.json + + gzip -cdq builddir_jxl/files/share/app-info/xmls/org.flatpak.appstream_media.xml.gz \ + > appstream.xml + assert_file_has_content_extended appstream.xml '>https://example.org/media/org/flatpak/appstream_media/[^/]+/icons/128x128/org.flatpak.appstream_media.jxl' + + echo "ok compose jxl image format" +else + echo "ok # Skip AppStream < 1.2.0" +fi diff --git a/tests/test-builder.sh b/tests/test-builder.sh index 3bd640e0..755fdf2c 100755 --- a/tests/test-builder.sh +++ b/tests/test-builder.sh @@ -23,7 +23,7 @@ set -euo pipefail skip_without_fuse -echo "1..13" +echo "1..11" setup_repo install_repo @@ -50,12 +50,8 @@ cp $(dirname $0)/Hello-desktop.appdata.xml . cp $(dirname $0)/org.test.Hello.desktop . cp $(dirname $0)/org.test.Hello.xml . cp $(dirname $0)/org.test.Hello.appdata.xml . -cp $(dirname $0)/org.flatpak_builder.gui.desktop . cp $(dirname $0)/org.flatpak_builder.gui.json . -cp $(dirname $0)/org.flatpak_builder.gui.metainfo.xml . cp $(dirname $0)/org.test.Hello.png . -cp $(dirname $0)/org.test.Hello-256.png . -cp $(dirname $0)/org.flatpak.appstream_media.json . cp $(dirname $0)/org.flatpak.install_test.json . cp $(dirname $0)/test-locale-cleanup.json . mkdir include1 @@ -168,35 +164,6 @@ find outdir_sc -path "*/icons/64x64/org.test.Hello.png" -type f | grep -q . echo "ok screenshot ref commit" -# test compose partial url policy -APPDIR=builddir_sc \ -run_build \ - --mirror-screenshots-url=https://example.org/media \ - --state-dir .fp-compose-url-policy-partial \ - --compose-url-policy=partial \ - org.flatpak.appstream_media.json -# we test for the icon tag instead of screenshot -# the former works offline the latter does not -gzip -cdq builddir_sc/files/share/app-info/xmls/org.flatpak.appstream_media.xml.gz|grep -Eq '>org/flatpak/appstream_media/[^/]+/icons/128x128/org.flatpak.appstream_media.png' - -echo "ok compose partial url policy" - -# test compose full url policy -if appstream_has_version 0 16 3; then - APPDIR=builddir_sc \ - run_build \ - --mirror-screenshots-url=https://example.org/media \ - --state-dir .fp-compose-url-policy-full \ - --compose-url-policy=full \ - org.flatpak.appstream_media.json - - gzip -cdq builddir_sc/files/share/app-info/xmls/org.flatpak.appstream_media.xml.gz|grep -Eq '>https://example.org/media/org/flatpak/appstream_media/[^/]+/icons/128x128/org.flatpak.appstream_media.png' - - echo "ok compose full url policy" -else - echo "ok # Skip AppStream < 0.16.3" -fi - # test install APPDIR=builddir run_build --user --install org.flatpak.install_test.json