From e41f4ed5a19276cfbb81dd7d1c16988b904676c7 Mon Sep 17 00:00:00 2001 From: John-Michael Mulesa Date: Tue, 21 Apr 2026 16:18:35 -0400 Subject: [PATCH 1/7] refactor: update FindRepoLatest to prioritize repo priority, version, and architecture with lock support. The FindRepoLatest function in googet/client/client.go has been rewritten to change the package selection logic. Previously, it would find the latest version within the first architecture in the provided list (archs) that had any matching packages. This meant a newer version in a lower-preference architecture would be missed. The new logic collects all possible package matches across all repos and supported architectures. These matches are then sorted based on: 1. Repo Priority (highest first) 2. Package Version (newest first) 3. Architecture Preference (based on the order in the archs slice, e.g., native-arch, compatible-arch, noarch). --- cli/install/install.go | 11 +- cli/latest/latest.go | 41 ++++-- cli/update/update.go | 25 ++-- client/client.go | 115 ++++++++++------ client/client_test.go | 283 ++++++++++++++++++++++++++++++++++++++- download/download.go | 2 +- goolib/goospec.go | 1 + install/install.go | 35 ++++- system/system_windows.go | 16 +-- 9 files changed, 441 insertions(+), 88 deletions(-) diff --git a/cli/install/install.go b/cli/install/install.go index b6c53b4..127e5ce 100644 --- a/cli/install/install.go +++ b/cli/install/install.go @@ -198,7 +198,14 @@ func (i *installer) installFromRepo(ctx context.Context, name string, archs []st if pi.Ver == "" { var err error var spec *goolib.PkgSpec - if spec, _, pi.Arch, err = client.FindRepoLatest(pi, i.repoMap, archs); err != nil { + installedPkgs, err := i.db.FetchPkgs(pi.Name) + var installedArch string + var isLocked bool + if err == nil && len(installedPkgs) > 0 { + installedArch = installedPkgs[0].PackageSpec.Arch + isLocked = installedPkgs[0].PackageSpec.LockArch + } + if spec, _, pi.Arch, err = client.FindRepoLatest(pi, i.repoMap, archs, installedArch, isLocked); err != nil { return fmt.Errorf("can't resolve version for package %q: %v", pi.Name, err) } pi.Ver = spec.Version @@ -262,7 +269,7 @@ func (i *installer) reinstall(ctx context.Context, pi goolib.PackageInfo, ps cli } func (i *installer) enumerateDeps(pi goolib.PackageInfo, r string, archs []string, dryRun bool) (*bytes.Buffer, error) { - dl, err := install.ListDeps(pi, i.repoMap, r, archs) + dl, err := install.ListDeps(pi, i.repoMap, r, archs, i.db) if err != nil { return nil, fmt.Errorf("error listing dependencies for %s.%s.%s: %v", pi.Name, pi.Arch, pi.Ver, err) } diff --git a/cli/latest/latest.go b/cli/latest/latest.go index 0830fbf..ea5e718 100644 --- a/cli/latest/latest.go +++ b/cli/latest/latest.go @@ -87,8 +87,34 @@ func (cmd *latestCmd) Execute(ctx context.Context, flags *flag.FlagSet, _ ...int return subcommands.ExitFailure } + var installedArch string + var isLocked bool + var state client.GooGetState + + if cmd.compare { + db, err := googetdb.NewDB(settings.DBFile()) + if err != nil { + logger.Errorf("Failed to open database: %v", err) + return subcommands.ExitFailure + } + defer db.Close() + + state, err = db.FetchPkgs("") + if err != nil { + logger.Errorf("Failed fetching installed packages: %v", err) + return subcommands.ExitFailure + } + for _, p := range state { + if p.PackageSpec != nil && p.PackageSpec.Name == pi.Name { + installedArch = p.PackageSpec.Arch + isLocked = p.PackageSpec.LockArch + break + } + } + } + rm := downloader.AvailableVersions(ctx, repos, settings.CacheDir(), settings.CacheLife) - spec, _, a, err := client.FindRepoLatest(pi, rm, settings.Archs) + spec, _, a, err := client.FindRepoLatest(pi, rm, settings.Archs, installedArch, isLocked) if err != nil { logger.Errorf("Failed to find package: %v", err) return subcommands.ExitFailure @@ -98,19 +124,6 @@ func (cmd *latestCmd) Execute(ctx context.Context, flags *flag.FlagSet, _ ...int fmt.Println(v) return subcommands.ExitSuccess } - - db, err := googetdb.NewDB(settings.DBFile()) - if err != nil { - logger.Errorf("Failed to open database: %v", err) - return subcommands.ExitFailure - } - defer db.Close() - - state, err := db.FetchPkgs("") - if err != nil { - logger.Errorf("Failed fetching installed packages: %v", err) - return subcommands.ExitFailure - } pi.Arch = a var ver string pkgFound := false diff --git a/cli/update/update.go b/cli/update/update.go index 1883e4f..d025fbe 100644 --- a/cli/update/update.go +++ b/cli/update/update.go @@ -92,7 +92,7 @@ func (cmd *updateCmd) Execute(ctx context.Context, _ *flag.FlagSet, _ ...interfa } rm := downloader.AvailableVersions(ctx, repos, cache, settings.CacheLife) - ud := updates(state.PackageMap(), rm) + ud := updates(state, rm) if ud == nil { fmt.Println("No updates available for any installed packages.") return subcommands.ExitSuccess @@ -126,43 +126,46 @@ func (cmd *updateCmd) Execute(ctx context.Context, _ *flag.FlagSet, _ ...interfa return exitCode } -func updates(pm client.PackageMap, rm client.RepoMap) []goolib.PackageInfo { +func updates(state client.GooGetState, rm client.RepoMap) []goolib.PackageInfo { fmt.Println("Searching for available updates...") var ud []goolib.PackageInfo - for p, ver := range pm { - pi := goolib.PkgNameSplit(p) - spec, r, _, err := client.FindRepoLatest(pi, rm, settings.Archs) + for _, p := range state { + if p.PackageSpec == nil { + continue + } + pi := goolib.PackageInfo{Name: p.PackageSpec.Name, Arch: p.PackageSpec.Arch, Ver: p.PackageSpec.Version} + spec, r, _, err := client.FindRepoLatest(pi, rm, settings.Archs, p.PackageSpec.Arch, p.PackageSpec.LockArch) if err != nil { // This error is because this installed package is not available in a repo. logger.Info(err) continue } - c, err := goolib.ComparePriorityVersion(rm[r].Priority, spec.Version, priority.Default, ver) + c, err := goolib.ComparePriorityVersion(rm[r].Priority, spec.Version, priority.Default, p.PackageSpec.Version) if err != nil { logger.Error(err) continue } if c < 1 { - logger.Infof("%s - highest priority version already installed", p) + logger.Infof("%s.%s - highest priority version already installed", p.PackageSpec.Name, p.PackageSpec.Arch) continue } // The versions might actually be the same even though the priorities are different, // so do another check to skip reinstall of the same version. - c, err = goolib.Compare(spec.Version, ver) + c, err = goolib.Compare(spec.Version, p.PackageSpec.Version) if err != nil { logger.Error(err) continue } if c == 0 { - logger.Infof("%s - same version installed", p) + logger.Infof("%s.%s - same version installed", p.PackageSpec.Name, p.PackageSpec.Arch) continue } op := "Upgrade" if c == -1 { op = "Downgrade" } - fmt.Printf(" %s, %s --> %s from %s\n", p, ver, spec.Version, r) - logger.Infof("%s for package %s, %s installed and %s available from %s.", op, p, ver, spec.Version, r) + fmt.Printf(" %s.%s, %s --> %s from %s\n", p.PackageSpec.Name, p.PackageSpec.Arch, p.PackageSpec.Version, spec.Version, r) + logger.Infof("%s for package %s.%s, %s installed and %s available from %s.", op, p.PackageSpec.Name, p.PackageSpec.Arch, p.PackageSpec.Version, spec.Version, r) ud = append(ud, goolib.PackageInfo{Name: pi.Name, Arch: pi.Arch, Ver: spec.Version}) } return ud diff --git a/client/client.go b/client/client.go index c8ab0e6..c1c51fa 100644 --- a/client/client.go +++ b/client/client.go @@ -27,6 +27,7 @@ import ( "net/url" "os" "path/filepath" + "sort" "strings" "time" @@ -357,8 +358,7 @@ func decode(index io.ReadCloser, ct, url, cf string) ([]goolib.RepoSpec, error) return nil, err } - // The .url files aren't used by googet but help developers and the - // curious figure out which file belongs to which repo/URL. + // The .url files aren't used by googet but help identify which file belongs to which repo/URL. mf := fmt.Sprintf("%s.url", strings.TrimSuffix(cf, filepath.Ext(cf))) if err = ioutil.WriteFile(mf, []byte(url), 0644); err != nil { logger.Errorf("Failed to write '%s': %v", mf, err) @@ -407,62 +407,101 @@ func latest(psm map[string][]*goolib.PkgSpec, rm RepoMap) (*goolib.PkgSpec, stri // FindRepoLatest returns the latest version of a package along with its repo and arch. // It checks both direct name matches and "Provides" entries. -// The archs are searched in order; if a matching package is found for any arch, it is -// returned immediately even if a later arch might have a later version. -func FindRepoLatest(pi goolib.PackageInfo, rm RepoMap, archs []string) (*goolib.PkgSpec, string, string, error) { +// The search order is: +// 1. Repo Priority (High > Low) +// 2. Version (New > Old) +// 3. Architecture Preference (as defined by archs slice order) +func FindRepoLatest(pi goolib.PackageInfo, rm RepoMap, archs []string, installedArch string, isLocked bool) (*goolib.PkgSpec, string, string, error) { name := pi.Name if pi.Arch != "" { archs = []string{pi.Arch} name = fmt.Sprintf("%s.%s", pi.Name, pi.Arch) } - for _, a := range archs { - psmDirect := make(map[string][]*goolib.PkgSpec) - psmProvides := make(map[string][]*goolib.PkgSpec) + archPref := make(map[string]int) + for i, a := range archs { + archPref[a] = i + } - for u, r := range rm { - for _, p := range r.Packages { - ps := p.PackageSpec - if ps.Arch != a { - continue - } + type candidate struct { + spec *goolib.PkgSpec + repo string + priority priority.Value + } + var directCandidates []candidate + var providesCandidates []candidate - // Check exact match - if ps.Name == pi.Name { - if satisfiesVersion(ps.Version, pi.Ver) { - psmDirect[u] = append(psmDirect[u], ps) - } - // Skip checking Provides if the package itself is a direct match. - continue + for u, r := range rm { + for _, p := range r.Packages { + ps := p.PackageSpec + + if _, ok := archPref[ps.Arch]; !ok { + continue + } + + if ps.Name == pi.Name { + if satisfiesVersion(ps.Version, pi.Ver) { + directCandidates = append(directCandidates, candidate{ps, u, r.Priority}) } + continue + } - // Check provides - for _, prov := range ps.Provides { - if SatisfiesProvider(prov, pi.Name, pi.Ver) { - psmProvides[u] = append(psmProvides[u], ps) - break - } + for _, prov := range ps.Provides { + if SatisfiesProvider(prov, pi.Name, pi.Ver) { + providesCandidates = append(providesCandidates, candidate{ps, u, r.Priority}) + break } } } + } - // Prioritize direct package matches over virtual package providers. - if len(psmDirect) > 0 { - pkg, repo := latest(psmDirect, rm) - if pkg != nil { - return pkg, repo, a, nil + sortFunc := func(list []candidate) func(i, j int) bool { + return func(i, j int) bool { + if list[i].priority != list[j].priority { + return list[i].priority > list[j].priority } + cmp, err := goolib.Compare(list[i].spec.Version, list[j].spec.Version) + if err != nil { + logger.Errorf("Error comparing package versions: %v", err) + return false // maintain order in case of error + } + if cmp != 0 { + return cmp > 0 + } + return archPref[list[i].spec.Arch] < archPref[list[j].spec.Arch] } + } - // If no direct matches, check providers. - // Note: This matches Arch behavior (prefer real package). - if len(psmProvides) > 0 { - pkg, repo := latest(psmProvides, rm) - if pkg != nil { - return pkg, repo, a, nil + filterAndReturn := func(list []candidate) (*goolib.PkgSpec, string, string, error) { + if len(list) == 0 { + return nil, "", "", fmt.Errorf("no package found") + } + sort.Slice(list, sortFunc(list)) + for _, cand := range list { + if isLocked && cand.spec.Arch != installedArch { + if cand.spec.LockArch { + continue // Ignore this candidate + } } + return cand.spec, cand.repo, cand.spec.Arch, nil + } + return nil, "", "", fmt.Errorf("no package found satisfying lock conditions") + } + + if len(directCandidates) > 0 { + spec, repo, arch, err := filterAndReturn(directCandidates) + if err == nil { + return spec, repo, arch, nil + } + } + + if len(providesCandidates) > 0 { + spec, repo, arch, err := filterAndReturn(providesCandidates) + if err == nil { + return spec, repo, arch, nil } } + return nil, "", "", fmt.Errorf("no package found satisfying %s in any repo", name) } diff --git a/client/client_test.go b/client/client_test.go index 7a134c6..39a1248 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -173,8 +173,8 @@ func TestFindRepoLatest(t *testing.T) { {PackageSpec: &goolib.PkgSpec{Name: "bar_pkg", Version: "2.3.0@1", Arch: "noarch"}}, }}, }, - wantVersion: "1.2.3@4", - wantArch: "noarch", + wantVersion: "3.0.0@1", + wantArch: "arm64", wantRepo: "foo_repo", }, { @@ -234,9 +234,215 @@ func TestFindRepoLatest(t *testing.T) { wantArch: "noarch", wantRepo: "high_priority_repo", }, + { + desc: "version priority over arch", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"noarch", "x86_64"}, + rm: RepoMap{ + "foo_repo": Repo{Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "noarch"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0@1", Arch: "x86_64"}}, + }}, + }, + wantVersion: "2.0.0@1", + wantArch: "x86_64", + wantRepo: "foo_repo", + }, + { + desc: "priority wins over version", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"noarch"}, + rm: RepoMap{ + "high_pri": Repo{ + Priority: 1000, + Packages: []goolib.RepoSpec{{PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "noarch"}}}, + }, + "low_pri": Repo{ + Priority: 500, + Packages: []goolib.RepoSpec{{PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0@1", Arch: "noarch"}}}, + }, + }, + wantVersion: "1.0.0@1", + wantArch: "noarch", + wantRepo: "high_pri", + }, + { + desc: "version wins over arch", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"x86_64", "x86_32"}, + rm: RepoMap{ + "repo": Repo{ + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_64"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0@1", Arch: "x86_32"}}, + }, + }, + }, + wantVersion: "2.0.0@1", + wantArch: "x86_32", + wantRepo: "repo", + }, + { + desc: "arch wins tie", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"x86_64", "x86_32"}, + rm: RepoMap{ + "repo": Repo{ + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_64"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_32"}}, + }, + }, + }, + wantVersion: "1.0.0@1", + wantArch: "x86_64", + wantRepo: "repo", + }, + { + desc: "cross arch upgrade", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"x86_32", "x86_64"}, // Prefer 32-bit + rm: RepoMap{ + "repo": Repo{ + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_32"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0@1", Arch: "x86_64"}}, + }, + }, + }, + wantVersion: "2.0.0@1", + wantArch: "x86_64", + wantRepo: "repo", + }, + { + desc: "complex mix", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"x86_64", "x86_32"}, + rm: RepoMap{ + "high_pri": Repo{ + Priority: 1000, + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_64"}}, + }, + }, + "med_pri": Repo{ + Priority: 500, + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "3.0.0@1", Arch: "x86_64"}}, + }, + }, + "low_pri": Repo{ // Should win if version was primary + Priority: 100, + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "4.0.0@1", Arch: "x86_64"}}, + }, + }, + }, + wantVersion: "1.0.0@1", + wantArch: "x86_64", + wantRepo: "high_pri", + }, + { + desc: "system_windows amd64 default preference", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"x86_64", "x86_32", "noarch"}, + rm: RepoMap{ + "repo": Repo{ + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "noarch"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_64"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_32"}}, + }, + }, + }, + wantVersion: "1.0.0@1", + wantArch: "x86_64", + wantRepo: "repo", + }, + { + desc: "system_windows amd64 version override", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"x86_64", "x86_32", "noarch"}, + rm: RepoMap{ + "repo": Repo{ + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "noarch"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_64"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0@1", Arch: "x86_32"}}, + }, + }, + }, + wantVersion: "2.0.0@1", + wantArch: "x86_32", + wantRepo: "repo", + }, + { + desc: "system_windows arm64 default preference", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"arm64", "x86_64", "x86_32", "noarch"}, + rm: RepoMap{ + "repo": Repo{ + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "arm64"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_64"}}, + }, + }, + }, + wantVersion: "1.0.0@1", + wantArch: "arm64", + wantRepo: "repo", + }, + { + desc: "system_windows arm64 version override", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"arm64", "x86_64", "x86_32", "noarch"}, + rm: RepoMap{ + "repo": Repo{ + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "arm64"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0@1", Arch: "x86_64"}}, + }, + }, + }, + wantVersion: "2.0.0@1", + wantArch: "x86_64", + wantRepo: "repo", + }, + { + desc: "system_windows 386 default preference", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"x86_32", "noarch"}, + rm: RepoMap{ + "repo": Repo{ + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "noarch"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "x86_32"}}, + }, + }, + }, + wantVersion: "1.0.0@1", + wantArch: "x86_32", + wantRepo: "repo", + }, + { + desc: "system_windows 386 version override", + pi: goolib.PackageInfo{Name: "foo_pkg"}, + archs: []string{"x86_32", "noarch"}, + rm: RepoMap{ + "repo": Repo{ + Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0@1", Arch: "noarch"}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0@1", Arch: "x86_32"}}, + }, + }, + }, + wantVersion: "2.0.0@1", + wantArch: "x86_32", + wantRepo: "repo", + }, } { t.Run(tt.desc, func(t *testing.T) { - gotSpec, gotRepo, gotArch, err := FindRepoLatest(tt.pi, tt.rm, tt.archs) + gotSpec, gotRepo, gotArch, err := FindRepoLatest(tt.pi, tt.rm, tt.archs, "", false) if err != nil && !tt.wantErr { t.Fatalf("FindRepoLatest(%v, %v, %v) failed: %v", tt.pi, tt.rm, tt.archs, err) } else if err == nil && tt.wantErr { @@ -490,7 +696,7 @@ func TestFindRepoLatest_Provides(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - spec, _, _, err := FindRepoLatest(tt.pi, rm, []string{"noarch"}) + spec, _, _, err := FindRepoLatest(tt.pi, rm, []string{"noarch"}, "", false) if tt.wantError { if err == nil { t.Errorf("FindRepoLatest(%v) wanted error, got nil", tt.pi) @@ -542,7 +748,7 @@ func TestFindRepoLatest_Priority(t *testing.T) { } pi := goolib.PackageInfo{Name: "real_pkg", Arch: "noarch"} - spec, _, _, err := FindRepoLatest(pi, rm, []string{"noarch"}) + spec, _, _, err := FindRepoLatest(pi, rm, []string{"noarch"}, "", false) if err != nil { t.Fatalf("FindRepoLatest failed: %v", err) } @@ -554,3 +760,70 @@ func TestFindRepoLatest_Priority(t *testing.T) { t.Errorf("Expected version '1.0.0', got '%s'", spec.Version) } } + +func TestFindRepoLatest_LockArch(t *testing.T) { + tests := []struct { + name string + installedArch string + isLocked bool + rm RepoMap + wantVersion string + wantArch string + }{ + { + name: "locked, ignore newer locked cross-arch", + installedArch: "noarch", + isLocked: true, + rm: RepoMap{ + "repo1": Repo{Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0", Arch: "x86_64", LockArch: true}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0", Arch: "noarch"}}, + }}, + }, + wantVersion: "1.0.0", + wantArch: "noarch", + }, + { + name: "locked, accept newer unlocked cross-arch", + installedArch: "noarch", + isLocked: true, + rm: RepoMap{ + "repo1": Repo{Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0", Arch: "x86_64", LockArch: true}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "3.0.0", Arch: "x86_64", LockArch: false}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0", Arch: "noarch"}}, + }}, + }, + wantVersion: "3.0.0", + wantArch: "x86_64", + }, + { + name: "unlocked, take newest", + installedArch: "noarch", + isLocked: false, + rm: RepoMap{ + "repo1": Repo{Packages: []goolib.RepoSpec{ + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "2.0.0", Arch: "x86_64", LockArch: true}}, + {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0", Arch: "noarch"}}, + }}, + }, + wantVersion: "2.0.0", + wantArch: "x86_64", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + spec, _, _, err := FindRepoLatest(goolib.PackageInfo{Name: "foo_pkg"}, tt.rm, []string{"noarch", "x86_64"}, tt.installedArch, tt.isLocked) + if err != nil { + t.Fatalf("FindRepoLatest failed: %v", err) + } + if spec.Version != tt.wantVersion { + t.Errorf("got version %q, want %q", spec.Version, tt.wantVersion) + } + if spec.Arch != tt.wantArch { + t.Errorf("got arch %q, want %q", spec.Arch, tt.wantArch) + } + }) + } +} diff --git a/download/download.go b/download/download.go index 6c8b2b9..7690879 100644 --- a/download/download.go +++ b/download/download.go @@ -151,7 +151,7 @@ func FromRepo(ctx context.Context, rs goolib.RepoSpec, repo, dir string, downloa // Latest downloads the latest available version of a package. func Latest(ctx context.Context, name, dir string, rm client.RepoMap, archs []string, downloader *client.Downloader) (string, string, error) { - spec, repo, arch, err := client.FindRepoLatest(goolib.PackageInfo{Name: name, Arch: "", Ver: ""}, rm, archs) + spec, repo, arch, err := client.FindRepoLatest(goolib.PackageInfo{Name: name, Arch: "", Ver: ""}, rm, archs, "", false) if err != nil { return "", "", err } diff --git a/goolib/goospec.go b/goolib/goospec.go index 7a54209..ef49d83 100644 --- a/goolib/goospec.go +++ b/goolib/goospec.go @@ -79,6 +79,7 @@ type PkgSpec struct { Name string Version string Arch string + LockArch bool `json:",omitempty"` ReleaseNotes []string `json:",omitempty"` Description string `json:",omitempty"` License string `json:",omitempty"` diff --git a/install/install.go b/install/install.go index 98c02ef..9a83a54 100644 --- a/install/install.go +++ b/install/install.go @@ -145,7 +145,14 @@ func installDeps(ctx context.Context, ps *goolib.PkgSpec, cache string, rm clien continue } - spec, repo, _, err := client.FindRepoLatest(goolib.PackageInfo{Name: pi.Name, Arch: pi.Arch, Ver: ver}, rm, archs) + installedPkgs, err := db.FetchPkgs(pi.Name) + var installedArch string + var isLocked bool + if err == nil && len(installedPkgs) > 0 { + installedArch = installedPkgs[0].PackageSpec.Arch + isLocked = installedPkgs[0].PackageSpec.LockArch + } + spec, repo, _, err := client.FindRepoLatest(goolib.PackageInfo{Name: pi.Name, Arch: pi.Arch, Ver: ver}, rm, archs, installedArch, isLocked) if err != nil { return err } @@ -167,7 +174,14 @@ func FromRepo(ctx context.Context, pi goolib.PackageInfo, repo, cache string, rm // If no version is specified, resolve the latest version handling both // direct matches and providers. if pi.Ver == "" { - spec, repoURL, _, err := client.FindRepoLatest(pi, rm, archs) + installedPkgs, err := db.FetchPkgs(pi.Name) + var installedArch string + var isLocked bool + if err == nil && len(installedPkgs) > 0 { + installedArch = installedPkgs[0].PackageSpec.Arch + isLocked = installedPkgs[0].PackageSpec.LockArch + } + spec, repoURL, _, err := client.FindRepoLatest(pi, rm, archs, installedArch, isLocked) if err != nil { return err } @@ -579,7 +593,7 @@ func installPkg(pkg string, ps *goolib.PkgSpec, dbOnly, force bool, db *googetdb return insFiles, nil } -func listDeps(pi goolib.PackageInfo, rm client.RepoMap, repo string, dl []goolib.PackageInfo, archs []string) ([]goolib.PackageInfo, error) { +func listDeps(pi goolib.PackageInfo, rm client.RepoMap, repo string, dl []goolib.PackageInfo, archs []string, db *googetdb.GooDB) ([]goolib.PackageInfo, error) { rs, err := client.FindRepoSpec(pi, rm[repo]) if err != nil { return nil, err @@ -587,7 +601,14 @@ func listDeps(pi goolib.PackageInfo, rm client.RepoMap, repo string, dl []goolib dl = append(dl, pi) for d, v := range rs.PackageSpec.PkgDependencies { di := goolib.PkgNameSplit(d) - spec, repo, arch, err := client.FindRepoLatest(di, rm, archs) + installedPkgs, err := db.FetchPkgs(di.Name) + var installedArch string + var isLocked bool + if err == nil && len(installedPkgs) > 0 { + installedArch = installedPkgs[0].PackageSpec.Arch + isLocked = installedPkgs[0].PackageSpec.LockArch + } + spec, repo, arch, err := client.FindRepoLatest(di, rm, archs, installedArch, isLocked) di.Arch = arch if err != nil { return nil, fmt.Errorf("cannot resolve dependency %s.%s.%s: %v", di.Name, di.Arch, di.Ver, err) @@ -600,7 +621,7 @@ func listDeps(pi goolib.PackageInfo, rm client.RepoMap, repo string, dl []goolib return nil, fmt.Errorf("cannot resolve dependency, %s.%s version %s or greater not installed and not available in any repo", pi.Name, pi.Arch, pi.Ver) } di.Ver = spec.Version - dl, err = listDeps(di, rm, repo, dl, archs) + dl, err = listDeps(di, rm, repo, dl, archs, db) if err != nil { return nil, err } @@ -609,7 +630,7 @@ func listDeps(pi goolib.PackageInfo, rm client.RepoMap, repo string, dl []goolib } // ListDeps returns a list of dependencies and subdependancies for a package. -func ListDeps(pi goolib.PackageInfo, rm client.RepoMap, repo string, archs []string) ([]goolib.PackageInfo, error) { +func ListDeps(pi goolib.PackageInfo, rm client.RepoMap, repo string, archs []string, db *googetdb.GooDB) ([]goolib.PackageInfo, error) { logger.Infof("Building dependency list for %s.%s.%s", pi.Name, pi.Arch, pi.Ver) - return listDeps(pi, rm, repo, nil, archs) + return listDeps(pi, rm, repo, nil, archs, db) } diff --git a/system/system_windows.go b/system/system_windows.go index 362dc14..d31c16f 100644 --- a/system/system_windows.go +++ b/system/system_windows.go @@ -404,20 +404,16 @@ func InstallableArchs() ([]string, error) { case runtime.GOARCH == "386": // Check if this is indeed a 32bit system. aw, err := width() - if err != nil { - return []string{"noarch", "x86_32"}, nil - } - if int(aw) == 32 { - return []string{"noarch", "x86_32"}, nil + if err != nil || int(aw) == 32 { + return []string{"x86_32", "noarch"}, nil } - return []string{"noarch", "x86_64", "x86_32"}, nil + return []string{"x86_64", "x86_32", "noarch"}, nil case runtime.GOARCH == "amd64": - // TODO: Add check for 32bit support, make sure it works with servers and client OS's. - return []string{"noarch", "x86_32", "x86_64"}, nil + return []string{"x86_64", "x86_32", "noarch"}, nil case runtime.GOARCH == "arm": - return []string{"noarch", "arm"}, nil + return []string{"arm", "noarch"}, nil case runtime.GOARCH == "arm64": - return []string{"noarch", "x86_32", "x86_64", "arm64"}, nil + return []string{"arm64", "x86_64", "x86_32", "noarch"}, nil default: return nil, fmt.Errorf("runtime %s not supported", runtime.GOARCH) } From 10c4f782aae650cd50a59d77c8a073dddcdd79b9 Mon Sep 17 00:00:00 2001 From: John-Michael Mulesa Date: Tue, 21 Apr 2026 16:44:17 -0400 Subject: [PATCH 2/7] fix fmt --- client/client_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index 39a1248..ff21404 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -780,8 +780,8 @@ func TestFindRepoLatest_LockArch(t *testing.T) { {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0", Arch: "noarch"}}, }}, }, - wantVersion: "1.0.0", - wantArch: "noarch", + wantVersion: "1.0.0", + wantArch: "noarch", }, { name: "locked, accept newer unlocked cross-arch", @@ -794,8 +794,8 @@ func TestFindRepoLatest_LockArch(t *testing.T) { {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0", Arch: "noarch"}}, }}, }, - wantVersion: "3.0.0", - wantArch: "x86_64", + wantVersion: "3.0.0", + wantArch: "x86_64", }, { name: "unlocked, take newest", @@ -807,8 +807,8 @@ func TestFindRepoLatest_LockArch(t *testing.T) { {PackageSpec: &goolib.PkgSpec{Name: "foo_pkg", Version: "1.0.0", Arch: "noarch"}}, }}, }, - wantVersion: "2.0.0", - wantArch: "x86_64", + wantVersion: "2.0.0", + wantArch: "x86_64", }, } From 32bb6e55fc63b783aab3271b573e84f068314a34 Mon Sep 17 00:00:00 2001 From: John-Michael Mulesa Date: Wed, 22 Apr 2026 10:03:23 -0400 Subject: [PATCH 3/7] refactor: update TestUpdates to use client.GooGetState instead of client.PackageMap --- cli/update/update_test.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/cli/update/update_test.go b/cli/update/update_test.go index 48c1476..d5e654e 100644 --- a/cli/update/update_test.go +++ b/cli/update/update_test.go @@ -29,16 +29,16 @@ func captureStdout(f func()) string { func TestUpdates(t *testing.T) { for _, tc := range []struct { - name string - pm client.PackageMap - rm client.RepoMap - want []goolib.PackageInfo + name string + state client.GooGetState + rm client.RepoMap + want []goolib.PackageInfo }{ { name: "upgrade to later version", - pm: client.PackageMap{ - "foo.x86_32": "1.0", - "bar.x86_32": "2.0", + state: client.GooGetState{ + {PackageSpec: &goolib.PkgSpec{Name: "foo", Version: "1.0", Arch: "x86_32"}}, + {PackageSpec: &goolib.PkgSpec{Name: "bar", Version: "2.0", Arch: "x86_32"}}, }, rm: client.RepoMap{ "stable": client.Repo{ @@ -53,9 +53,9 @@ func TestUpdates(t *testing.T) { }, { name: "rollback to earlier version", - pm: client.PackageMap{ - "foo.x86_32": "2.0", - "bar.x86_32": "2.0", + state: client.GooGetState{ + {PackageSpec: &goolib.PkgSpec{Name: "foo", Version: "2.0", Arch: "x86_32"}}, + {PackageSpec: &goolib.PkgSpec{Name: "bar", Version: "2.0", Arch: "x86_32"}}, }, rm: client.RepoMap{ "stable": client.Repo{ @@ -76,8 +76,8 @@ func TestUpdates(t *testing.T) { }, { name: "no change if rollback version already installed", - pm: client.PackageMap{ - "foo.x86_32": "1.0", + state: client.GooGetState{ + {PackageSpec: &goolib.PkgSpec{Name: "foo", Version: "1.0", Arch: "x86_32"}}, }, rm: client.RepoMap{ "stable": client.Repo{ @@ -98,7 +98,9 @@ func TestUpdates(t *testing.T) { }, { name: "no updates available", - pm: client.PackageMap{"foo.x86_32": "1.0"}, + state: client.GooGetState{ + {PackageSpec: &goolib.PkgSpec{Name: "foo", Version: "1.0", Arch: "x86_32"}}, + }, rm: client.RepoMap{ "stable": client.Repo{ Priority: priority.Default, @@ -116,11 +118,11 @@ func TestUpdates(t *testing.T) { var pi []goolib.PackageInfo captureStdout(func() { - pi = updates(tc.pm, tc.rm) + pi = updates(tc.state, tc.rm) }) if diff := cmp.Diff(pi, tc.want); diff != "" { - t.Errorf("updates(%v, %v) got unexpected diff (-got +want):\n%v", tc.pm, tc.rm, diff) + t.Errorf("updates(%v, %v) got unexpected diff (-got +want):\n%v", tc.state, tc.rm, diff) } }) } From 9aa616c0e3e66ed66151bdb9a603f5599ec3d00c Mon Sep 17 00:00:00 2001 From: John-Michael Mulesa Date: Wed, 22 Apr 2026 10:06:12 -0400 Subject: [PATCH 4/7] fix: remove version from package info during update lookup to allow latest version retrieval --- cli/update/update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/update/update.go b/cli/update/update.go index d025fbe..e4e42e2 100644 --- a/cli/update/update.go +++ b/cli/update/update.go @@ -133,7 +133,7 @@ func updates(state client.GooGetState, rm client.RepoMap) []goolib.PackageInfo { if p.PackageSpec == nil { continue } - pi := goolib.PackageInfo{Name: p.PackageSpec.Name, Arch: p.PackageSpec.Arch, Ver: p.PackageSpec.Version} + pi := goolib.PackageInfo{Name: p.PackageSpec.Name, Arch: p.PackageSpec.Arch} spec, r, _, err := client.FindRepoLatest(pi, rm, settings.Archs, p.PackageSpec.Arch, p.PackageSpec.LockArch) if err != nil { // This error is because this installed package is not available in a repo. From 997ae927a6ecf5f78d695872aa899e5d56ffcb0f Mon Sep 17 00:00:00 2001 From: John-Michael Mulesa Date: Wed, 22 Apr 2026 10:13:26 -0400 Subject: [PATCH 5/7] chore: bump googet version to 3.3.0 and update release notes --- googet.goospec | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/googet.goospec b/googet.goospec index 8fbf3ba..d71ca3c 100644 --- a/googet.goospec +++ b/googet.goospec @@ -1,4 +1,4 @@ -{{$version := "3.2.1@0" -}} +{{$version := "3.3.0@0" -}} { "name": "googet", "version": "{{$version}}", @@ -15,7 +15,9 @@ "path": "install.ps1" }, "releaseNotes": [ - "3.2.1 - Refactor: Update GooDB.FetchPkg to accept goolib.PackageInfo.", + "3.3.0 - Refactor: Update FindRepoLatest to prioritize repo priority, version, and architecture with lock support.", + "3.3.0 - Refactor: Add file ownership conflict checks to prevent overwrites.", + "3.2.1 - Refactor: Update GooDB.FetchPkg to accept goolib.PackageInfo.", "3.2.0 - Add Provides functionality and field to the GooGet PkgSpec.", "3.1.0 - Introduce a dry_run flag for update, install, and remove subcommands.", "3.0.0 - Replace googet state file with sqlite database. Add json output for installed command.", From 5f90456b9df7d2cab6be991cf1666164bf937c9c Mon Sep 17 00:00:00 2001 From: John-Michael Mulesa Date: Wed, 22 Apr 2026 15:37:57 -0400 Subject: [PATCH 6/7] refactor: consolidate installed package state retrieval into InstalledLockState and optimize candidate sorting using slices.SortFunc --- cli/install/install.go | 9 +++---- cli/latest/latest.go | 23 +++-------------- client/client.go | 53 ++++++++++++++++++---------------------- googetdb/googetdb.go | 14 +++++++++++ goolib/goospec.go | 2 ++ install/install.go | 27 +++++++------------- system/system_windows.go | 3 ++- 7 files changed, 58 insertions(+), 73 deletions(-) diff --git a/cli/install/install.go b/cli/install/install.go index 127e5ce..b0e4000 100644 --- a/cli/install/install.go +++ b/cli/install/install.go @@ -198,12 +198,9 @@ func (i *installer) installFromRepo(ctx context.Context, name string, archs []st if pi.Ver == "" { var err error var spec *goolib.PkgSpec - installedPkgs, err := i.db.FetchPkgs(pi.Name) - var installedArch string - var isLocked bool - if err == nil && len(installedPkgs) > 0 { - installedArch = installedPkgs[0].PackageSpec.Arch - isLocked = installedPkgs[0].PackageSpec.LockArch + installedArch, isLocked, _, _, err := i.db.InstalledLockState(pi.Name) + if err != nil { + logger.Infof("Error fetching installed package state: %v, proceeding without lock", err) } if spec, _, pi.Arch, err = client.FindRepoLatest(pi, i.repoMap, archs, installedArch, isLocked); err != nil { return fmt.Errorf("can't resolve version for package %q: %v", pi.Name, err) diff --git a/cli/latest/latest.go b/cli/latest/latest.go index ea5e718..2735f4c 100644 --- a/cli/latest/latest.go +++ b/cli/latest/latest.go @@ -89,7 +89,8 @@ func (cmd *latestCmd) Execute(ctx context.Context, flags *flag.FlagSet, _ ...int var installedArch string var isLocked bool - var state client.GooGetState + var ver string + var pkgFound bool if cmd.compare { db, err := googetdb.NewDB(settings.DBFile()) @@ -99,18 +100,11 @@ func (cmd *latestCmd) Execute(ctx context.Context, flags *flag.FlagSet, _ ...int } defer db.Close() - state, err = db.FetchPkgs("") + installedArch, isLocked, ver, pkgFound, err = db.InstalledLockState(pi.Name) if err != nil { - logger.Errorf("Failed fetching installed packages: %v", err) + logger.Errorf("Failed fetching installed package state: %v", err) return subcommands.ExitFailure } - for _, p := range state { - if p.PackageSpec != nil && p.PackageSpec.Name == pi.Name { - installedArch = p.PackageSpec.Arch - isLocked = p.PackageSpec.LockArch - break - } - } } rm := downloader.AvailableVersions(ctx, repos, settings.CacheDir(), settings.CacheLife) @@ -125,15 +119,6 @@ func (cmd *latestCmd) Execute(ctx context.Context, flags *flag.FlagSet, _ ...int return subcommands.ExitSuccess } pi.Arch = a - var ver string - pkgFound := false - for _, p := range state { - if p.Match(pi) { - ver = p.PackageSpec.Version - pkgFound = true - break - } - } status := packageStatus{ PackageName: pi.Name, diff --git a/client/client.go b/client/client.go index c1c51fa..80ec7ba 100644 --- a/client/client.go +++ b/client/client.go @@ -27,7 +27,7 @@ import ( "net/url" "os" "path/filepath" - "sort" + "slices" "strings" "time" @@ -455,48 +455,43 @@ func FindRepoLatest(pi goolib.PackageInfo, rm RepoMap, archs []string, installed } } - sortFunc := func(list []candidate) func(i, j int) bool { - return func(i, j int) bool { - if list[i].priority != list[j].priority { - return list[i].priority > list[j].priority - } - cmp, err := goolib.Compare(list[i].spec.Version, list[j].spec.Version) - if err != nil { - logger.Errorf("Error comparing package versions: %v", err) - return false // maintain order in case of error - } - if cmp != 0 { - return cmp > 0 - } - return archPref[list[i].spec.Arch] < archPref[list[j].spec.Arch] + cmpFunc := func(a, b candidate) int { + c, err := goolib.ComparePriorityVersion(a.priority, a.spec.Version, b.priority, b.spec.Version) + if err != nil { + logger.Errorf("Error comparing priority/version: %v", err) + return 0 + } + if c != 0 { + return -c // reverse for descending order + } + if archPref[a.spec.Arch] < archPref[b.spec.Arch] { + return -1 + } + if archPref[a.spec.Arch] > archPref[b.spec.Arch] { + return 1 } + return 0 } - filterAndReturn := func(list []candidate) (*goolib.PkgSpec, string, string, error) { + bestCandidate := func(list []candidate) (*goolib.PkgSpec, string, string, error) { if len(list) == 0 { return nil, "", "", fmt.Errorf("no package found") } - sort.Slice(list, sortFunc(list)) + slices.SortFunc(list, cmpFunc) for _, cand := range list { - if isLocked && cand.spec.Arch != installedArch { - if cand.spec.LockArch { - continue // Ignore this candidate - } + if isLocked && cand.spec.Arch != installedArch && cand.spec.LockArch { + continue // Ignore this candidate } return cand.spec, cand.repo, cand.spec.Arch, nil } return nil, "", "", fmt.Errorf("no package found satisfying lock conditions") } - if len(directCandidates) > 0 { - spec, repo, arch, err := filterAndReturn(directCandidates) - if err == nil { - return spec, repo, arch, nil + for _, cands := range [][]candidate{directCandidates, providesCandidates} { + if len(cands) == 0 { + continue } - } - - if len(providesCandidates) > 0 { - spec, repo, arch, err := filterAndReturn(providesCandidates) + spec, repo, arch, err := bestCandidate(cands) if err == nil { return spec, repo, arch, nil } diff --git a/googetdb/googetdb.go b/googetdb/googetdb.go index e74b1b2..5ec0547 100644 --- a/googetdb/googetdb.go +++ b/googetdb/googetdb.go @@ -214,6 +214,20 @@ func (g *GooDB) FetchPkgs(pkgName string) (client.GooGetState, error) { return state, nil } +// InstalledLockState returns the architecture, lock status, and version of the installed package. +// If the package is not installed, it returns empty string and false. +func (g *GooDB) InstalledLockState(pkgName string) (installedArch string, isLocked bool, ver string, pkgFound bool, err error) { + pkgs, err := g.FetchPkgs(pkgName) + if err != nil { + return "", false, "", false, err + } + if len(pkgs) == 0 { + return "", false, "", false, nil + } + // Assume only one arch is installed, or pick the first one. + return pkgs[0].PackageSpec.Arch, pkgs[0].PackageSpec.LockArch, pkgs[0].PackageSpec.Version, true, nil +} + // readState reads the JSON installed package state from the given path, // retrying with a .bak extension if the first read fails. // diff --git a/goolib/goospec.go b/goolib/goospec.go index 24e4eff..15937b8 100644 --- a/goolib/goospec.go +++ b/goolib/goospec.go @@ -79,6 +79,8 @@ type PkgSpec struct { Name string Version string Arch string + // LockArch, if true, prevents upgrading an installed package to a different architecture + // unless the newer candidate version sets LockArch to false (or leaves it default). LockArch bool `json:",omitempty"` ReleaseNotes []string `json:",omitempty"` Description string `json:",omitempty"` diff --git a/install/install.go b/install/install.go index 9a83a54..5b95038 100644 --- a/install/install.go +++ b/install/install.go @@ -145,12 +145,9 @@ func installDeps(ctx context.Context, ps *goolib.PkgSpec, cache string, rm clien continue } - installedPkgs, err := db.FetchPkgs(pi.Name) - var installedArch string - var isLocked bool - if err == nil && len(installedPkgs) > 0 { - installedArch = installedPkgs[0].PackageSpec.Arch - isLocked = installedPkgs[0].PackageSpec.LockArch + installedArch, isLocked, _, _, err := db.InstalledLockState(pi.Name) + if err != nil { + logger.Infof("Error fetching installed package state: %v, proceeding without lock", err) } spec, repo, _, err := client.FindRepoLatest(goolib.PackageInfo{Name: pi.Name, Arch: pi.Arch, Ver: ver}, rm, archs, installedArch, isLocked) if err != nil { @@ -174,12 +171,9 @@ func FromRepo(ctx context.Context, pi goolib.PackageInfo, repo, cache string, rm // If no version is specified, resolve the latest version handling both // direct matches and providers. if pi.Ver == "" { - installedPkgs, err := db.FetchPkgs(pi.Name) - var installedArch string - var isLocked bool - if err == nil && len(installedPkgs) > 0 { - installedArch = installedPkgs[0].PackageSpec.Arch - isLocked = installedPkgs[0].PackageSpec.LockArch + installedArch, isLocked, _, _, err := db.InstalledLockState(pi.Name) + if err != nil { + logger.Infof("Error fetching installed package state: %v, proceeding without lock", err) } spec, repoURL, _, err := client.FindRepoLatest(pi, rm, archs, installedArch, isLocked) if err != nil { @@ -601,12 +595,9 @@ func listDeps(pi goolib.PackageInfo, rm client.RepoMap, repo string, dl []goolib dl = append(dl, pi) for d, v := range rs.PackageSpec.PkgDependencies { di := goolib.PkgNameSplit(d) - installedPkgs, err := db.FetchPkgs(di.Name) - var installedArch string - var isLocked bool - if err == nil && len(installedPkgs) > 0 { - installedArch = installedPkgs[0].PackageSpec.Arch - isLocked = installedPkgs[0].PackageSpec.LockArch + installedArch, isLocked, _, _, err := db.InstalledLockState(di.Name) + if err != nil { + logger.Infof("Error fetching installed package state: %v, proceeding without lock", err) } spec, repo, arch, err := client.FindRepoLatest(di, rm, archs, installedArch, isLocked) di.Arch = arch diff --git a/system/system_windows.go b/system/system_windows.go index d31c16f..ce28313 100644 --- a/system/system_windows.go +++ b/system/system_windows.go @@ -397,7 +397,8 @@ func width() (int, error) { return int(p[0].AddressWidth), nil } -// InstallableArchs returns a slice of archs supported by this machine. +// InstallableArchs returns a slice of archs supported by this machine, +// ordered by preference (native architecture first, followed by compatible architectures, and lastly "noarch"). // WMI errors are logged but not returned. func InstallableArchs() ([]string, error) { switch { From 16e11a6475211a8a619d4c88511fce7e19a43c2b Mon Sep 17 00:00:00 2001 From: John-Michael Mulesa Date: Wed, 22 Apr 2026 15:40:28 -0400 Subject: [PATCH 7/7] fix fmt --- goolib/goospec.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/goolib/goospec.go b/goolib/goospec.go index 15937b8..7f8e29b 100644 --- a/goolib/goospec.go +++ b/goolib/goospec.go @@ -76,9 +76,9 @@ var validArch = []string{"noarch", "x86_64", "x86_32", "arm", "arm64"} // PkgSpec is an individual package specification. type PkgSpec struct { - Name string - Version string - Arch string + Name string + Version string + Arch string // LockArch, if true, prevents upgrading an installed package to a different architecture // unless the newer candidate version sets LockArch to false (or leaves it default). LockArch bool `json:",omitempty"`