Skip to content
11 changes: 9 additions & 2 deletions cli/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
41 changes: 27 additions & 14 deletions cli/latest/latest.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,34 @@ 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 state client.GooGetState
Comment thread
jm2 marked this conversation as resolved.
Outdated

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 {
Comment thread
jm2 marked this conversation as resolved.
Outdated
installedArch = p.PackageSpec.Arch
Comment thread
jm2 marked this conversation as resolved.
Outdated
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
Expand All @@ -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
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, 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
Expand Down
115 changes: 77 additions & 38 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"
"sort"
"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,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 {
Comment thread
jm2 marked this conversation as resolved.
Outdated
return func(i, j int) bool {
if list[i].priority != list[j].priority {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this duplicating the logic of ComparePriorityVersion? If you need a descending sort, just flip the values or sort ascending and reverse. Alternately use an ascending sort and iterate over the result with slices.Backward.

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) {
Comment thread
jm2 marked this conversation as resolved.
Outdated
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 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combine nested ifs into the equivalent

if isLocked && cand.spec.Arch != installedArch && cand.spec.LockArch { ... }

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 {
Comment thread
jm2 marked this conversation as resolved.
Outdated
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)
}

Expand Down
Loading
Loading