Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cli/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,11 @@ 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 {
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)
}
pi.Ver = spec.Version
Expand Down Expand Up @@ -262,7 +266,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)
}
Expand Down
44 changes: 21 additions & 23 deletions cli/latest/latest.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,28 @@ func (cmd *latestCmd) Execute(ctx context.Context, flags *flag.FlagSet, _ ...int
return subcommands.ExitFailure
}

var installedArch string
Comment thread
jm2 marked this conversation as resolved.
var isLocked bool
var ver string
var pkgFound bool

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()

installedArch, isLocked, ver, pkgFound, err = db.InstalledLockState(pi.Name)
if err != nil {
logger.Errorf("Failed fetching installed package state: %v", err)
return subcommands.ExitFailure
}
}

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
Expand All @@ -98,29 +118,7 @@ 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
for _, p := range state {
if p.Match(pi) {
ver = p.PackageSpec.Version
pkgFound = true
break
}
}

status := packageStatus{
PackageName: pi.Name,
Expand Down
25 changes: 14 additions & 11 deletions cli/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
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
Expand Down
32 changes: 17 additions & 15 deletions cli/update/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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{
Expand All @@ -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{
Expand All @@ -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,
Expand All @@ -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)
}
})
}
Expand Down
112 changes: 73 additions & 39 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"net/url"
"os"
"path/filepath"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -407,62 +407,96 @@ 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
}
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
}

// 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
bestCandidate := func(list []candidate) (*goolib.PkgSpec, string, string, error) {
if len(list) == 0 {
return nil, "", "", fmt.Errorf("no package found")
}
slices.SortFunc(list, cmpFunc)
for _, cand := range list {
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")
}

for _, cands := range [][]candidate{directCandidates, providesCandidates} {
if len(cands) == 0 {
continue
}
spec, repo, arch, err := bestCandidate(cands)
if err == nil {
return spec, repo, arch, nil
}
}

return nil, "", "", fmt.Errorf("no package found satisfying %s in any repo", name)
}

Expand Down
Loading
Loading