From 2f371015c473676e212342edbd6d98c11a26a3b4 Mon Sep 17 00:00:00 2001 From: purian23 Date: Mon, 26 Oct 2026 22:56:23 -0400 Subject: [PATCH] sysupdate(flatpak): list updates when one needs an uninstalled runtime Fixes #3950 Port 2.0 (cherry picked from commit a7bd6a8e06ba57d467ff21924e6f7cbcd68ee8ef) --- .../server/sysupdate/backend_flatpak.go | 20 +++++---- .../server/sysupdate/backend_flatpak_test.go | 45 +++++++++++++++++++ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/core/internal/server/sysupdate/backend_flatpak.go b/core/internal/server/sysupdate/backend_flatpak.go index e2b14d76d..e3f4d3eac 100644 --- a/core/internal/server/sysupdate/backend_flatpak.go +++ b/core/internal/server/sysupdate/backend_flatpak.go @@ -3,6 +3,7 @@ package sysupdate import ( "context" "errors" + "fmt" "os/exec" "strings" ) @@ -21,18 +22,21 @@ func (flatpakBackend) RunsInTerminal() bool { return false } func (flatpakBackend) IsAvailable(_ context.Context) bool { return commandExists("flatpak") } func (flatpakBackend) CheckUpdates(ctx context.Context) ([]Package, error) { - // Run `flatpak update` - cmd := exec.CommandContext(ctx, "flatpak", "update") + // Without --no-deps an update needing an uninstalled runtime prompts first, and declining aborts before the table prints. + cmd := exec.CommandContext(ctx, "flatpak", "update", "--no-deps") cmd.Stdin = strings.NewReader("n\nn\n") // decline up to 2 installation prompts out, err := cmd.Output() - if err != nil { - if exitErr, ok := errors.AsType[*exec.ExitError](err); ok && exitErr.ExitCode() == 1 && len(out) > 0 { - } else if len(out) == 0 { - return nil, err + pkgs := parseFlatpakUpdateOutput(string(out), flatpakInstalled(ctx)) + // Declining the final prompt also exits non-zero, so only a failure without a table is an error. + if err != nil && len(pkgs) == 0 { + if exitErr, ok := errors.AsType[*exec.ExitError](err); ok { + if msg := lastNonEmptyLine(string(exitErr.Stderr)); msg != "" { + return nil, fmt.Errorf("flatpak update: %s", msg) + } } + return nil, err } - installed := flatpakInstalled(ctx) - return parseFlatpakUpdateOutput(string(out), installed), nil + return pkgs, nil } type flatpakInstalledEntry struct { diff --git a/core/internal/server/sysupdate/backend_flatpak_test.go b/core/internal/server/sysupdate/backend_flatpak_test.go index d5fd80a14..7e0680d01 100644 --- a/core/internal/server/sysupdate/backend_flatpak_test.go +++ b/core/internal/server/sysupdate/backend_flatpak_test.go @@ -2,6 +2,7 @@ package sysupdate import ( "reflect" + "strings" "testing" ) @@ -100,3 +101,47 @@ func TestParseFlatpakUpdateOutput(t *testing.T) { }) } } + +func TestFlatpakCheckUpdates(t *testing.T) { + const runtimePrompt = `[ "$1" = list ] && exit 0 +for a in "$@"; do + if [ "$a" = --no-deps ]; then + printf ' 1.\tVesktop\tdev.vencord.Vesktop\tstable\tu\tflathub\t< 130 MB\n\nProceed with these changes? [Y/n]: ' + exit 1 + fi +done +printf 'Required runtime for dev.vencord.Vesktop/x86_64/stable found in remote flathub\nDo you want to install it? [Y/n]: ' +echo 'error: The application dev.vencord.Vesktop/x86_64/stable requires the runtime org.freedesktop.Platform/x86_64/26.08 which is not installed' >&2 +exit 1` + + for _, tt := range []struct { + name string + script string + wantRef string + wantErr string + }{ + {name: "update needing a new runtime", script: runtimePrompt, wantRef: "dev.vencord.Vesktop//stable"}, + { + name: "failure without a table", + script: "[ \"$1\" = list ] && exit 0\necho 'Looking for updates…'\necho 'error: Unable to load summary from remote flathub' >&2\nexit 1", + wantErr: "Unable to load summary", + }, + } { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + t.Setenv("PATH", dir) + writeUpdateExecutable(t, dir, "flatpak", tt.script) + + pkgs, err := flatpakBackend{}.CheckUpdates(t.Context()) + if tt.wantErr != "" { + if err == nil || !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("err = %v, want %q (a failed check must not read as up to date)", err, tt.wantErr) + } + return + } + if err != nil || len(pkgs) != 1 || pkgs[0].Ref != tt.wantRef { + t.Errorf("pkgs = %#v, err = %v, want %s", pkgs, err, tt.wantRef) + } + }) + } +}