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