diff --git a/common/flatpak-transaction.c b/common/flatpak-transaction.c index 141f8505..8fbceedc 100644 --- a/common/flatpak-transaction.c +++ b/common/flatpak-transaction.c @@ -1857,18 +1857,15 @@ run_operation_before (FlatpakTransactionOperation *op, } static gboolean -add_related (FlatpakTransaction *self, - FlatpakTransactionOperation *op, - GError **error) +op_get_related (FlatpakTransaction *self, + FlatpakTransactionOperation *op, + GPtrArray **out_related, + GError **error) { FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); g_autoptr(FlatpakRemoteState) state = NULL; g_autoptr(GPtrArray) related = NULL; - g_autoptr(GError) local_error = NULL; - int i; - - if (priv->disable_related) - return TRUE; + g_autoptr(GError) related_error = NULL; if (op->kind != FLATPAK_TRANSACTION_OPERATION_UNINSTALL) { @@ -1885,15 +1882,37 @@ add_related (FlatpakTransaction *self, if (transaction_is_local_only (self, op->kind)) related = flatpak_dir_find_local_related_for_metadata (priv->dir, op->ref, op->remote, op->resolved_metakey, - NULL, &local_error); + NULL, &related_error); else related = flatpak_dir_find_remote_related_for_metadata (priv->dir, state, op->ref, op->resolved_metakey, - NULL, &local_error); + NULL, &related_error); + + if (related_error != NULL) + g_message (_("Warning: Problem looking for related refs: %s"), related_error->message); + + if (out_related) + *out_related = g_steal_pointer (&related); + + return TRUE; +} + +static gboolean +add_related (FlatpakTransaction *self, + FlatpakTransactionOperation *op, + GError **error) +{ + FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); + g_autoptr(GPtrArray) related = NULL; + int i; + + if (priv->disable_related) + return TRUE; + + if (!op_get_related (self, op, &related, error)) + return FALSE; + if (related == NULL) - { - g_message (_("Warning: Problem looking for related refs: %s"), local_error->message); - return TRUE; - } + return TRUE; if (op->kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) { @@ -1985,20 +2004,13 @@ find_runtime_remote (FlatpakTransaction *self, return NULL; } - -static gboolean -add_deps (FlatpakTransaction *self, - FlatpakTransactionOperation *op, - GError **error) +static char * +op_get_runtime_ref (FlatpakTransactionOperation *op) { - FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); g_autofree char *runtime_ref = NULL; - g_autofree char *full_runtime_ref = NULL; - g_autofree char *runtime_remote = NULL; - FlatpakTransactionOperation *runtime_op = NULL; if (!op->resolved_metakey) - return TRUE; + return NULL; /* Generally only app needs runtimes dependencies, not dependencies because you don't run extensions directly. However if the extension has extra data (and doesn't define NoRuntime) its also needed so we can run the @@ -2010,9 +2022,27 @@ add_deps (FlatpakTransaction *self, runtime_ref = g_key_file_get_string (op->resolved_metakey, "ExtensionOf", "runtime", NULL); if (runtime_ref == NULL) + return NULL; + + return g_strconcat ("runtime/", runtime_ref, NULL); +} + +static gboolean +add_deps (FlatpakTransaction *self, + FlatpakTransactionOperation *op, + GError **error) +{ + FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); + g_autofree char *full_runtime_ref = NULL; + g_autofree char *runtime_remote = NULL; + FlatpakTransactionOperation *runtime_op = NULL; + + if (!op->resolved_metakey) return TRUE; - full_runtime_ref = g_strconcat ("runtime/", runtime_ref, NULL); + full_runtime_ref = op_get_runtime_ref (op); + if (full_runtime_ref == NULL) + return TRUE; runtime_op = flatpak_transaction_get_last_op_for_ref (self, full_runtime_ref); @@ -2545,7 +2575,7 @@ emit_op_done (FlatpakTransaction *self, } static GBytes * -load_deployed_metadata (FlatpakTransaction *self, const char *ref, char **out_commit) +load_deployed_metadata (FlatpakTransaction *self, const char *ref, char **out_commit, char **out_remote) { FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); g_autoptr(GFile) deploy_dir = NULL; @@ -2557,14 +2587,17 @@ load_deployed_metadata (FlatpakTransaction *self, const char *ref, char **out_co if (deploy_dir == NULL) return NULL; - if (out_commit) + if (out_commit || out_remote) { g_autoptr(GBytes) deploy_data = NULL; deploy_data = flatpak_load_deploy_data (deploy_dir, ref, FLATPAK_DEPLOY_VERSION_ANY, NULL, NULL); if (deploy_data == NULL) return NULL; - *out_commit = g_strdup (flatpak_deploy_data_get_commit (deploy_data)); + if (out_commit) + *out_commit = g_strdup (flatpak_deploy_data_get_commit (deploy_data)); + if (out_remote) + *out_remote = g_strdup (flatpak_deploy_data_get_origin (deploy_data)); } metadata_file = g_file_get_child (deploy_dir, "metadata"); @@ -2649,7 +2682,7 @@ resolve_op_end (FlatpakTransaction *self, { g_autoptr(GBytes) old_metadata_bytes = NULL; - old_metadata_bytes = load_deployed_metadata (self, op->ref, NULL); + old_metadata_bytes = load_deployed_metadata (self, op->ref, NULL, NULL); mark_op_resolved (op, checksum, sideload_path, metadata_bytes, old_metadata_bytes); emit_eol_and_maybe_skip (self, op); } @@ -2781,7 +2814,7 @@ resolve_ops (FlatpakTransaction *self, { /* We resolve to the deployed metadata, because we need it to uninstall related ops */ - metadata_bytes = load_deployed_metadata (self, op->ref, &checksum); + metadata_bytes = load_deployed_metadata (self, op->ref, &checksum, NULL); mark_op_resolved (op, checksum, NULL, metadata_bytes, NULL); continue; } @@ -4081,6 +4114,325 @@ flatpak_transaction_normalize_ops (FlatpakTransaction *self) } } +static GPtrArray * +find_related_from_deploy (FlatpakTransaction *self, + const char *ref, + char **out_remote) +{ + FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); + g_autoptr(GBytes) metadata_bytes = NULL; + g_autoptr(GKeyFile) metakey = NULL; + g_autofree char *checksum = NULL; + g_autofree char *remote = NULL; + g_autoptr(GError) related_error = NULL; + g_autoptr(GPtrArray) related = NULL; + + metadata_bytes = load_deployed_metadata (self, ref, &checksum, &remote); + if (metadata_bytes == NULL) + return NULL; + + if (out_remote) + *out_remote = g_strdup (remote); + + metakey = g_key_file_new (); + if (!g_key_file_load_from_bytes (metakey, metadata_bytes, G_KEY_FILE_NONE, NULL)) + { + g_message ("Warning: Failed to parse metadata for %s\n", ref); + return NULL; + } + + related = flatpak_dir_find_local_related_for_metadata (priv->dir, ref, + remote, metakey, + NULL, &related_error); + if (related_error != NULL) + g_message (_("Warning: Problem looking for related refs: %s"), related_error->message); + + return g_steal_pointer (&related); +} + +static gboolean +prune_maybe_unused_list (FlatpakTransaction *self, + GHashTable *maybe_unused_runtimes, + GHashTable *metadata_injection, + GPtrArray *to_be_excluded, + GCancellable *cancellable, + GError **error) +{ + FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); + g_auto(GStrv) unused_refs = NULL; + const char * const *to_be_excluded_strv = NULL; + + if (to_be_excluded->len > 0) + { + g_ptr_array_add (to_be_excluded, NULL); + to_be_excluded_strv = (const char * const *) to_be_excluded->pdata; + } + + unused_refs = flatpak_dir_list_unused_refs_with_options (priv->dir, + NULL, /* arch */ + metadata_injection, + to_be_excluded_strv, + TRUE, /* filter_by_eol */ + cancellable, error); + if (unused_refs == NULL) + return FALSE; + + GLNX_HASH_TABLE_FOREACH_IT (maybe_unused_runtimes, hashiter, const char *, runtime, const char *, remote) + { + if (!g_strv_contains ((const gchar * const *)unused_refs, runtime)) + g_hash_table_iter_remove (&hashiter); + } + + return TRUE; +} + +static gboolean +populate_maybe_unused_list (FlatpakTransaction *self, + GHashTable *maybe_unused_runtimes, + GCancellable *cancellable, + GError **error) +{ + FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); + g_auto(GStrv) unused_refs = NULL; + + unused_refs = flatpak_dir_list_unused_refs_with_options (priv->dir, + NULL, /* arch */ + NULL, /* metadata_injection */ + NULL, /* refs_to_exclude */ + TRUE, /* filter_by_eol */ + cancellable, error); + if (unused_refs == NULL) + return FALSE; + + for (char **iter = unused_refs; iter && *iter; iter++) + g_hash_table_replace (maybe_unused_runtimes, g_strdup (*iter), NULL); + + return TRUE; +} + +static gboolean +add_uninstall_unused_ops (FlatpakTransaction *self, + GCancellable *cancellable, + GError **error) +{ + FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); + g_autoptr(GHashTable) maybe_unused_runtimes = NULL; + g_autoptr(GHashTable) newly_used_runtimes = NULL; + g_autoptr(GHashTable) metadata_injection = NULL; + g_autoptr(GPtrArray) to_be_excluded = NULL; + g_autoptr(GPtrArray) run_after_ops = NULL; + GList *l, *next; + int i; + + if (priv->disable_deps) + return TRUE; + + /* This is the set of runtimes which are no longer needed by something in the + * transaction (an uninstall or update). The values are either the relevant + * remote name or %NULL. */ + maybe_unused_runtimes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + /* This is the set of runtimes being used by an install or update operation + * in the transaction. */ + newly_used_runtimes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + /* This is a mapping from refs to #GKeyFile metadata objects, for each ref + * being installed or updated by the transaction. This will allows us to + * calculate what dependencies will be used after those operations are + * executed. For example an app update may drop an extension point and + * thereby make an installed extension become unused. */ + metadata_injection = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + + /* This is the set of runtimes and apps scheduled for uninstallation and + * which are therefore excluded when calculating used refs. */ + to_be_excluded = g_ptr_array_new (); + + /* These are the set of operations which may need to be executed before the + * uninstall operations added by this function. */ + run_after_ops = g_ptr_array_new (); + + for (l = priv->ops; l != NULL; l = next) + { + FlatpakTransactionOperation *op = l->data; + FlatpakTransactionOperationType op_type = flatpak_transaction_operation_get_operation_type (op); + g_autofree char *runtime_ref = NULL; + g_autoptr(GBytes) deploy_data = NULL; + + next = l->next; + + if (op->skip) + continue; + + g_assert (op_type == FLATPAK_TRANSACTION_OPERATION_UNINSTALL || + op_type == FLATPAK_TRANSACTION_OPERATION_INSTALL || + op_type == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE || + op_type == FLATPAK_TRANSACTION_OPERATION_UPDATE); + + if (op_type == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) + g_ptr_array_add (to_be_excluded, op->ref); + else if (op->resolved_metakey) + g_hash_table_insert (metadata_injection, op->ref, op->resolved_metakey); + + if ((op_type == FLATPAK_TRANSACTION_OPERATION_INSTALL || + op_type == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE) && + g_str_has_prefix (op->ref, "runtime/")) + g_hash_table_add (newly_used_runtimes, g_strdup (op->ref)); + + if ((op_type == FLATPAK_TRANSACTION_OPERATION_INSTALL || + op_type == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE) && + !priv->disable_related) + { + g_autoptr(GPtrArray) related = NULL; + if (!op_get_related (self, op, &related, error)) + return FALSE; + + for (i = 0; related && i < related->len; i++) + { + FlatpakRelated *rel = g_ptr_array_index (related, i); + if (rel->delete) + g_hash_table_add (newly_used_runtimes, g_strdup (rel->ref)); + } + } + + runtime_ref = op_get_runtime_ref (op); + if (runtime_ref != NULL) + { + g_autoptr(GPtrArray) runtime_related = NULL; + g_autofree char *runtime_remote = NULL; + + if (op_type == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) + { + runtime_related = find_related_from_deploy (self, runtime_ref, &runtime_remote); + } + else + { + FlatpakTransactionOperation *runtime_op = NULL; + runtime_op = flatpak_transaction_get_last_op_for_ref (self, runtime_ref); + if (runtime_op != NULL) + { + runtime_remote = g_strdup (runtime_op->remote); + if (!priv->disable_related && !op_get_related (self, runtime_op, &runtime_related, error)) + return FALSE; + } + else + runtime_related = find_related_from_deploy (self, runtime_ref, &runtime_remote); + } + + if (op_type == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) + { + g_hash_table_replace (maybe_unused_runtimes, g_strdup (runtime_ref), g_strdup (runtime_remote)); + g_ptr_array_add (run_after_ops, op); + } + else + g_hash_table_add (newly_used_runtimes, g_strdup (runtime_ref)); + + if (!priv->disable_related) + { + for (i = 0; runtime_related && i < runtime_related->len; i++) + { + FlatpakRelated *rel = g_ptr_array_index (runtime_related, i); + + if (op_type == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) + { + if (rel->delete) + g_hash_table_replace (maybe_unused_runtimes, g_strdup (rel->ref), g_strdup (runtime_remote)); + } + else + g_hash_table_add (newly_used_runtimes, g_strdup (rel->ref)); + } + } + } + + if (op_type == FLATPAK_TRANSACTION_OPERATION_UPDATE && + dir_ref_is_installed (priv->dir, op->ref, NULL, &deploy_data)) + { + g_autofree char *full_previous_runtime = NULL; + g_autofree char *prev_runtime_remote = NULL; + g_autoptr(GPtrArray) runtime_related = NULL; + g_autoptr(GPtrArray) ref_related = NULL; + + g_ptr_array_add (run_after_ops, op); + + /* The related refs of the op might now be unused */ + if (!priv->disable_related) + { + ref_related = find_related_from_deploy (self, op->ref, NULL); + for (i = 0; ref_related && i < ref_related->len; i++) + { + FlatpakRelated *rel = g_ptr_array_index (ref_related, i); + if (rel->delete) + g_hash_table_replace (maybe_unused_runtimes, g_strdup (rel->ref), g_strdup (op->remote)); + } + } + + /* If the op is changing to a different runtime, the previous one and + * its related refs might now be unused */ + const char *previous_runtime = flatpak_deploy_data_get_runtime (deploy_data); + + if (previous_runtime == NULL || *previous_runtime == '\0') + continue; + + full_previous_runtime = g_strconcat ("runtime/", previous_runtime, NULL); + if (g_strcmp0 (full_previous_runtime, runtime_ref) == 0) + continue; + + runtime_related = find_related_from_deploy (self, full_previous_runtime, &prev_runtime_remote); + + g_hash_table_replace (maybe_unused_runtimes, + g_strdup (full_previous_runtime), + g_strdup (prev_runtime_remote)); + + if (priv->disable_related) + continue; + + for (i = 0; runtime_related && i < runtime_related->len; i++) + { + FlatpakRelated *rel = g_ptr_array_index (runtime_related, i); + if (rel->delete) + g_hash_table_replace (maybe_unused_runtimes, g_strdup (rel->ref), g_strdup (prev_runtime_remote)); + } + } + } + + /* Subtract newly_used_runtimes from maybe_unused_runtimes */ + GLNX_HASH_TABLE_FOREACH (newly_used_runtimes, const char *, runtime_ref) + g_hash_table_remove (maybe_unused_runtimes, runtime_ref); + + if (g_hash_table_size (maybe_unused_runtimes) == 0) + return TRUE; + + /* Check which things in maybe_unused_runtimes will be unused after the + * ops in the transaction are executed. Note that + * maybe_unused_runtimes and to_be_excluded are modified in this helper */ + if (!prune_maybe_unused_list (self, maybe_unused_runtimes, metadata_injection, to_be_excluded, + cancellable, error)) + return FALSE; + + /* Schedule each unused runtime to be uninstalled */ + GLNX_HASH_TABLE_FOREACH_KV (maybe_unused_runtimes, const char *, runtime_ref, const char *, remote) + { + g_autofree char *resolved_remote = NULL; + FlatpakTransactionOperation *unused_op = NULL; + + if (remote == NULL) + g_assert (dir_ref_is_installed (priv->dir, runtime_ref, &resolved_remote, NULL)); + else + resolved_remote = g_strdup (remote); + + unused_op = flatpak_transaction_add_op (self, resolved_remote, runtime_ref, + NULL, NULL, NULL, NULL, + FLATPAK_TRANSACTION_OPERATION_UNINSTALL); + + for (i = 0; i < run_after_ops->len; i++) + { + FlatpakTransactionOperation *op = g_ptr_array_index (run_after_ops, i); + run_operation_before (op, unused_op, 1); + } + } + + return TRUE; +} + static gboolean flatpak_transaction_real_run (FlatpakTransaction *self, GCancellable *cancellable, @@ -4191,11 +4543,20 @@ flatpak_transaction_real_run (FlatpakTransaction *self, return FALSE; } - sort_ops (self); - /* Ensure the operation kind is normalized and not no-op */ flatpak_transaction_normalize_ops (self); + /* Add uninstall ops for things that are made unused by this transaction (and + * which match a heuristic). We don't need to do another round of + * resolve_all_ops() since uninstalls don't require that. */ + if (!add_uninstall_unused_ops (self, cancellable, error)) + { + g_assert (error == NULL || *error != NULL); + return FALSE; + } + + sort_ops (self); + g_signal_emit (self, signals[READY], 0, &ready_res); if (!ready_res) return flatpak_fail_error (error, FLATPAK_ERROR_ABORTED, _("Aborted by user")); diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h index ebbf7759..1b3bad30 100644 --- a/common/flatpak-utils-private.h +++ b/common/flatpak-utils-private.h @@ -322,6 +322,33 @@ g_key_file_load_from_bytes (GKeyFile *key_file, } #endif +#if !GLIB_CHECK_VERSION (2, 54, 0) +static inline gboolean +g_ptr_array_find_with_equal_func (GPtrArray *haystack, + gconstpointer needle, + GEqualFunc equal_func, + guint *index_) +{ + guint i; + + g_return_val_if_fail (haystack != NULL, FALSE); + + if (equal_func == NULL) + equal_func = g_direct_equal; + + for (i = 0; i < haystack->len; i++) + { + if (equal_func (g_ptr_array_index (haystack, i), needle)) + { + if (index_ != NULL) + *index_ = i; + return TRUE; + } + } + + return FALSE; +} +#endif #if !GLIB_CHECK_VERSION (2, 56, 0) GDateTime *flatpak_g_date_time_new_from_iso8601 (const gchar *text, diff --git a/tests/libtest.sh b/tests/libtest.sh index acb2095b..6e784cd6 100644 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -273,7 +273,9 @@ make_runtime () { GPGARGS="$4" RUNTIME_REF="runtime/org.test.Platform/$(flatpak --default-arch)/${BRANCH}" - if [ -f ${test_builddir}/runtime-repo/${RUNTIME_REF} ]; then + if [ ! -z "${SRC_RUNTIME_REPO:-}" ]; then + RUNTIME_REPO=repos/${SRC_RUNTIME_REPO} + elif [ -f ${test_builddir}/runtime-repo/${RUNTIME_REF} ]; then RUNTIME_REPO=${test_builddir}/runtime-repo else RUNTIME_REPO=${TEST_DATA_DIR}/runtime-repo @@ -379,6 +381,20 @@ make_updated_app () { update_repo $REPONAME "${COLLECTION_ID}" } +make_updated_runtime () { + REPONAME=${1:-test} + if [ x${USE_COLLECTIONS_IN_SERVER-} == xyes ] ; then + COLLECTION_ID=${2:-org.test.Collection.${REPONAME}} + else + COLLECTION_ID="" + fi + BRANCH=${3:-master} + TEXT=${4:-UPDATED} + + GPGARGS="${GPGARGS:-${FL_GPGARGS}}" $(dirname $0)/make-test-runtime.sh repos/${REPONAME} org.test.Platform "${BRANCH}" "${COLLECTION_ID}" "${TEXT}" > /dev/null + update_repo $REPONAME "${COLLECTION_ID}" +} + setup_sdk_repo () { REPONAME=${1:-test} if [ x${USE_COLLECTIONS_IN_SERVER-} == xyes ] ; then diff --git a/tests/make-test-runtime.sh b/tests/make-test-runtime.sh index af289625..39e0b1a4 100755 --- a/tests/make-test-runtime.sh +++ b/tests/make-test-runtime.sh @@ -97,5 +97,5 @@ else fi mkdir -p repos -flatpak build-export ${collection_args} --disable-sandbox --runtime ${GPGARGS-} ${REPO} ${DIR} ${BRANCH} +flatpak build-export ${collection_args} --disable-sandbox --runtime ${GPGARGS-} ${EXPORT_ARGS-} ${REPO} ${DIR} ${BRANCH} rm -rf ${DIR} diff --git a/tests/test-repo.sh b/tests/test-repo.sh index 06624de8..86647005 100644 --- a/tests/test-repo.sh +++ b/tests/test-repo.sh @@ -24,7 +24,7 @@ set -euo pipefail skip_without_bwrap skip_revokefs_without_fuse -echo "1..38" +echo "1..40" #Regular repo setup_repo @@ -424,11 +424,57 @@ ${FLATPAK} ${U} uninstall -y org.test.NewHello org.test.Platform ok "eol-rebase" +# Remove any pin of the runtime from an earlier test +${FLATPAK} ${U} pin --remove runtime/org.test.Platform/$ARCH/master 2>/dev/null || true + +EXPORT_ARGS="--end-of-life=Reason3" make_updated_runtime + +${FLATPAK} ${U} install -y test-repo org.test.Hello +${FLATPAK} ${U} list -d > list-log +assert_file_has_content list-log "org\.test\.Hello" +assert_file_has_content list-log "org\.test\.Platform" + +${FLATPAK} ${U} uninstall -y org.test.Hello + +${FLATPAK} ${U} list -d -a > list-log +assert_not_file_has_content list-log "org\.test\.Hello" +assert_not_file_has_content list-log "org\.test\.Platform" +assert_not_file_has_content list-log "org\.test\.Platform.Locale" + +ok "eol runtime uninstalled with app" + +${FLATPAK} ${U} install -y test-repo org.test.Hello + +${FLATPAK} ${U} info org.test.Platform > info-log +assert_file_has_content info-log "End-of-life: Reason3" + +assert_has_dir $FL_DIR/runtime/org.test.Platform/$ARCH/master/active/files + +# Update the app to a different runtime branch +make_updated_runtime "" "" "mainline" "" +make_updated_app "" "" "" "UPDATED99" "" "mainline" + +${FLATPAK} ${U} update -y org.test.Hello + +# The previous runtime should have been removed during the update +assert_has_dir $FL_DIR/runtime/org.test.Platform/$ARCH/mainline/active/files +assert_not_has_dir $FL_DIR/runtime/org.test.Platform/$ARCH/master/active/files + +# Revert things for future tests +EXPORT_ARGS="" make_updated_runtime +make_updated_app "" "" "" "UPDATED100" "" "master" +${FLATPAK} ${U} uninstall -y --all +ostree refs --repo=repos/test --delete runtime/org.test.Platform/$ARCH/mainline +ostree refs --repo=repos/test --delete runtime/org.test.Platform.Locale/$ARCH/mainline +update_repo + +ok "eol runtime uninstalled on app update to different runtime" + ${FLATPAK} ${U} install -y test-repo org.test.Platform port=$(cat httpd-port) UPDATE_REPO_ARGS="--redirect-url=http://127.0.0.1:${port}/test-gpg3 --gpg-import=${FL_GPG_HOMEDIR2}/pubring.gpg" update_repo -GPGPUBKEY="${FL_GPG_HOMEDIR2}/pubring.gpg" GPGARGS="${FL_GPGARGS2}" setup_repo_no_add test-gpg3 org.test.Collection.test master +SRC_RUNTIME_REPO="test" GPGPUBKEY="${FL_GPG_HOMEDIR2}/pubring.gpg" GPGARGS="${FL_GPGARGS2}" setup_repo_no_add test-gpg3 org.test.Collection.test master ${FLATPAK} ${U} update -y org.test.Platform # Ensure we have the new uri diff --git a/tests/testlibrary.c b/tests/testlibrary.c index 50d5f03b..a903d261 100644 --- a/tests/testlibrary.c +++ b/tests/testlibrary.c @@ -4334,6 +4334,88 @@ test_installation_unused_refs_excludes_pins (void) g_assert_cmpint (refs->len, ==, 0); } +static void +test_installation_unused_refs_across_installations (void) +{ + g_autoptr(FlatpakInstallation) system_inst = NULL; + g_autoptr(FlatpakInstallation) user_inst = NULL; + g_autoptr(FlatpakTransaction) transaction = NULL; + g_autoptr(GPtrArray) refs = NULL; + g_autoptr(GError) error = NULL; + g_autofree char *runtime = NULL; + g_autofree char *app = NULL; + FlatpakInstalledRef *unused_ref; + gboolean res; + + runtime = g_strdup_printf ("runtime/org.test.Platform/%s/master", + flatpak_get_default_arch ()); + app = g_strdup_printf ("app/org.test.Hello/%s/master", + flatpak_get_default_arch ()); + + system_inst = flatpak_installation_new_system (NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (system_inst); + + user_inst = flatpak_installation_new_user (NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (user_inst); + + empty_installation (system_inst); + empty_installation (user_inst); + + add_remote_system ("test-runtime-only", NULL); + + transaction = flatpak_transaction_new_for_installation (system_inst, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (transaction); + + res = flatpak_transaction_add_install (transaction, "test-runtime-only-repo", runtime, NULL, &error); + g_assert_no_error (error); + g_assert_true (res); + + res = flatpak_transaction_run (transaction, NULL, &error); + g_assert_no_error (error); + g_assert_true (res); + g_clear_object (&transaction); + + /* Undo the pinning that happened as a side effect of the install */ + const char *argv[] = { "flatpak", "pin", "--system", "--remove", runtime, NULL }; + run_test_subprocess ((char **) argv, RUN_TEST_SUBPROCESS_DEFAULT); + flatpak_installation_drop_caches (system_inst, NULL, &error); + g_assert_no_error (error); + + /* The runtime should show as unused */ + refs = flatpak_installation_list_unused_refs (system_inst, NULL, NULL, &error); + g_assert_nonnull (refs); + g_assert_no_error (error); + g_assert_cmpint (refs->len, ==, 1); + unused_ref = g_ptr_array_index (refs, 0); + g_assert_cmpstr (flatpak_ref_get_name (FLATPAK_REF (unused_ref)), ==, "org.test.Platform"); + g_clear_pointer (&refs, g_ptr_array_unref); + + /* Install an app in the user installation that uses the runtime in the + * system installation */ + transaction = flatpak_transaction_new_for_installation (user_inst, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (transaction); + + flatpak_transaction_add_dependency_source (transaction, system_inst); + + res = flatpak_transaction_add_install (transaction, repo_name, app, NULL, &error); + g_assert_no_error (error); + g_assert_true (res); + + res = flatpak_transaction_run (transaction, NULL, &error); + g_assert_no_error (error); + g_assert_true (res); + + /* Now the runtime should be used */ + refs = flatpak_installation_list_unused_refs (system_inst, NULL, NULL, &error); + g_assert_nonnull (refs); + g_assert_no_error (error); + g_assert_cmpint (refs->len, ==, 0); +} + int main (int argc, char *argv[]) { @@ -4385,6 +4467,7 @@ main (int argc, char *argv[]) g_test_add_func ("/library/installation-no-interaction", test_installation_no_interaction); g_test_add_func ("/library/installation-unused-refs", test_installation_unused_refs); g_test_add_func ("/library/installation-unused-refs-excludes-pins", test_installation_unused_refs_excludes_pins); + g_test_add_func ("/library/installation-unused-refs-across-installations", test_installation_unused_refs_across_installations); global_setup ();