From 0a928ba9f44658f2444bce1ab570bcff6ea4d6fb Mon Sep 17 00:00:00 2001 From: Jo Date: Thu, 22 Jan 2026 16:46:55 +0100 Subject: [PATCH] feat(yay): use dyalpm for alpm usage (#2769) * squash: dyalpm integration * fix vercmp * fix linting --- clean_test.go | 18 +- cmd.go | 2 +- go.mod | 5 +- go.sum | 8 +- pkg/db/executor.go | 6 +- pkg/db/ialpm/alpm.go | 196 ++--- pkg/db/ialpm/alpm_test.go | 30 +- pkg/db/ialpm/high_level.go | 6 +- pkg/db/mock/executor.go | 6 +- pkg/db/mock/repo.go | 137 +++- pkg/dep/dep_graph.go | 35 +- pkg/dep/dep_graph_bench_test.go | 577 +++++++++++++ pkg/dep/dep_graph_rpc_test.go | 814 +++++++++++++++++++ pkg/dep/dep_graph_test.go | 384 ++++++++- pkg/dep/testdata/aws-cli-git.json | 3 + pkg/dep/testdata/clion.json | 7 + pkg/dep/testdata/gstreamer-git.json | 5 + pkg/dep/testdata/liri-desktop-git.json | 8 + pkg/dep/testdata/mesa-git.json | 3 + pkg/dep/testdata/nx.json | 6 + pkg/dep/testdata/python-pydantic.json | 3 + pkg/dep/testdata/samsung-unified-driver.json | 6 + pkg/dep/topo/dep.go | 2 +- pkg/download/utils_test.go | 37 +- pkg/query/aur_warnings.go | 6 +- pkg/query/metric.go | 4 +- pkg/query/metric_test.go | 5 +- pkg/query/query_builder.go | 12 +- pkg/query/types.go | 4 +- pkg/query/version_diff.go | 4 +- pkg/upgrade/service.go | 2 +- pkg/upgrade/service_test.go | 2 +- pkg/upgrade/sources_test.go | 26 +- pkg/vcs/mock.go | 4 +- pkg/vcs/vcs.go | 6 +- print_test.go | 10 +- query.go | 10 +- sync_test.go | 22 +- 38 files changed, 2133 insertions(+), 288 deletions(-) create mode 100644 pkg/dep/dep_graph_bench_test.go create mode 100644 pkg/dep/dep_graph_rpc_test.go create mode 100644 pkg/dep/testdata/aws-cli-git.json create mode 100644 pkg/dep/testdata/clion.json create mode 100644 pkg/dep/testdata/gstreamer-git.json create mode 100644 pkg/dep/testdata/liri-desktop-git.json create mode 100644 pkg/dep/testdata/mesa-git.json create mode 100644 pkg/dep/testdata/nx.json create mode 100644 pkg/dep/testdata/python-pydantic.json create mode 100644 pkg/dep/testdata/samsung-unified-driver.json diff --git a/clean_test.go b/clean_test.go index a2bb8d12..66d460f5 100644 --- a/clean_test.go +++ b/clean_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" pacmanconf "github.com/Morganamilo/go-pacmanconf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -47,7 +47,7 @@ func TestCleanHanging(t *testing.T) { } dbExc := &mock.DBExecutor{ - PackageOptionalDependsFn: func(i alpm.IPackage) []alpm.Depend { + PackageOptionalDependsFn: func(i alpm.Package) []alpm.Depend { if i.Name() == "linux" { return []alpm.Depend{ { @@ -58,8 +58,8 @@ func TestCleanHanging(t *testing.T) { return []alpm.Depend{} }, - PackageProvidesFn: func(p alpm.IPackage) []alpm.Depend { return []alpm.Depend{} }, - PackageDependsFn: func(p alpm.IPackage) []alpm.Depend { return []alpm.Depend{} }, + PackageProvidesFn: func(p alpm.Package) []alpm.Depend { return []alpm.Depend{} }, + PackageDependsFn: func(p alpm.Package) []alpm.Depend { return []alpm.Depend{} }, LocalPackagesFn: func() []mock.IPackage { return []mock.IPackage{ &mock.Package{ @@ -143,7 +143,7 @@ func TestIntegrationCleanAUR(t *testing.T) { } dbExc := &mock.DBExecutor{ - PackageOptionalDependsFn: func(i alpm.IPackage) []alpm.Depend { + PackageOptionalDependsFn: func(i alpm.Package) []alpm.Depend { if i.Name() == "linux" { return []alpm.Depend{ { @@ -154,10 +154,10 @@ func TestIntegrationCleanAUR(t *testing.T) { return []alpm.Depend{} }, - PackageProvidesFn: func(p alpm.IPackage) []alpm.Depend { return []alpm.Depend{} }, - PackageDependsFn: func(p alpm.IPackage) []alpm.Depend { return []alpm.Depend{} }, - InstalledRemotePackagesFn: func() map[string]alpm.IPackage { - return map[string]alpm.IPackage{ + PackageProvidesFn: func(p alpm.Package) []alpm.Depend { return []alpm.Depend{} }, + PackageDependsFn: func(p alpm.Package) []alpm.Depend { return []alpm.Depend{} }, + InstalledRemotePackagesFn: func() map[string]alpm.Package { + return map[string]alpm.Package{ "zoom": &mock.Package{ PName: "zoom", PVersion: "6.5.8-1", diff --git a/cmd.go b/cmd.go index 0959cf74..bbcfdc62 100644 --- a/cmd.go +++ b/cmd.go @@ -7,7 +7,7 @@ import ( "net/http" "strings" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/completion" diff --git a/go.mod b/go.mod index fca34a50..09fef992 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/Jguer/yay/v12 require ( github.com/Jguer/aur v1.3.0 - github.com/Jguer/go-alpm/v2 v2.3.1 + github.com/Jguer/dyalpm v0.1.1 github.com/Jguer/votar v1.0.0 github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5 github.com/Morganamilo/go-srcinfo v1.0.0 @@ -20,6 +20,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/itchyny/gojq v0.12.18 // indirect @@ -30,4 +31,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.25 +go 1.25.5 diff --git a/go.sum b/go.sum index f15efc27..0d31d055 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ github.com/Jguer/aur v1.3.0 h1:skdjp/P9kB75TBaJmn9PKK/kCeA9QsgjdUrORZ3gldU= github.com/Jguer/aur v1.3.0/go.mod h1:F8Awo+WKzTxlXtNOO4pDQjMkePLZ+oMSbu+1fKLTTLo= -github.com/Jguer/go-alpm/v2 v2.3.1 h1:6xgyL28aI4E4gng/cH+kvCnpa95f4Qgmddmh8A7iTGk= -github.com/Jguer/go-alpm/v2 v2.3.1/go.mod h1:lfe8gSe83F/KERaQvEfrSqQ4n+8bES+ZIyKWR/gm3MI= +github.com/Jguer/dyalpm v0.1.0 h1:cGajPBZvjZmCG1B1hJmFdNwLoezrIqfiOaAOURM+Kc4= +github.com/Jguer/dyalpm v0.1.0/go.mod h1:eUPJQ/zSclJKTxOPihpspulI+S8WQNsxHJoIBiBgogw= +github.com/Jguer/dyalpm v0.1.1 h1:38JkmJuHIGXVZedXIDGz/nhVcn8DtMk4zM+GRGipC7w= +github.com/Jguer/dyalpm v0.1.1/go.mod h1:eUPJQ/zSclJKTxOPihpspulI+S8WQNsxHJoIBiBgogw= github.com/Jguer/votar v1.0.0 h1:drPYpV5Py5BeAQS8xezmT6uCEfLzotNjLf5yfmlHKTg= github.com/Jguer/votar v1.0.0/go.mod h1:rc6vgVlTqNjI4nAnPbDTbdxw/N7kXkbB8BcUDjeFbYQ= github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5 h1:TMscPjkb1ThXN32LuFY5bEYIcXZx3YlwzhS1GxNpn/c= @@ -19,6 +21,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/pkg/db/executor.go b/pkg/db/executor.go index a77d17f7..bab5b26b 100644 --- a/pkg/db/executor.go +++ b/pkg/db/executor.go @@ -3,13 +3,13 @@ package db import ( "time" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/text" ) type ( - IPackage = alpm.IPackage + IPackage = alpm.Package Depend = alpm.Depend ) @@ -30,7 +30,7 @@ type Upgrade struct { } type SyncUpgrade struct { - Package alpm.IPackage + Package alpm.Package LocalVersion string Reason alpm.PkgReason } diff --git a/pkg/db/ialpm/alpm.go b/pkg/db/ialpm/alpm.go index dffc7230..27fd5895 100644 --- a/pkg/db/ialpm/alpm.go +++ b/pkg/db/ialpm/alpm.go @@ -7,7 +7,7 @@ import ( "strconv" "time" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" pacmanconf "github.com/Morganamilo/go-pacmanconf" "github.com/leonelquinteros/gotext" @@ -17,15 +17,15 @@ import ( ) type AlpmExecutor struct { - handle *alpm.Handle - localDB alpm.IDB - syncDB alpm.IDBList - syncDBsCache []alpm.IDB + handle alpm.Handle + localDB alpm.Database + syncDB []alpm.Database + syncDBsCache []alpm.Database conf *pacmanconf.Config log *text.Logger installedRemotePkgNames []string - installedRemotePkgMap map[string]alpm.IPackage + installedRemotePkgMap map[string]alpm.Package installedSyncPkgNames []string } @@ -34,7 +34,7 @@ func NewExecutor(pacmanConf *pacmanconf.Config, logger *text.Logger) (*AlpmExecu handle: nil, localDB: nil, syncDB: nil, - syncDBsCache: []alpm.IDB{}, + syncDBsCache: []alpm.Database{}, conf: pacmanConf, log: logger, installedRemotePkgNames: nil, @@ -85,7 +85,7 @@ func toUsage(usages []string) alpm.Usage { return ret } -func configureAlpm(pacmanConf *pacmanconf.Config, alpmHandle *alpm.Handle) error { +func configureAlpm(pacmanConf *pacmanconf.Config, alpmHandle alpm.Handle) error { for _, repo := range pacmanConf.Repos { // TODO: set SigLevel alpmDB, err := alpmHandle.RegisterSyncDB(repo.Name, 0) @@ -93,8 +93,12 @@ func configureAlpm(pacmanConf *pacmanconf.Config, alpmHandle *alpm.Handle) error return err } - alpmDB.SetServers(repo.Servers) - alpmDB.SetUsage(toUsage(repo.Usage)) + if err := alpmDB.SetServers(repo.Servers); err != nil { + return err + } + if err := alpmDB.SetUsage(int(toUsage(repo.Usage))); err != nil { + return err + } } if err := alpmHandle.SetCacheDirs(pacmanConf.CacheDir); err != nil { @@ -171,7 +175,7 @@ func (ae *AlpmExecutor) questionCallback() func(question alpm.QuestionAny) { size := 0 - _ = qp.Providers(ae.handle).ForEach(func(pkg alpm.IPackage) error { + _ = qp.Providers(ae.handle).ForEach(func(pkg alpm.Package) error { size++ return nil }) @@ -182,7 +186,7 @@ func (ae *AlpmExecutor) questionCallback() func(question alpm.QuestionAny) { var dbName string - _ = qp.Providers(ae.handle).ForEach(func(pkg alpm.IPackage) error { + _ = qp.Providers(ae.handle).ForEach(func(pkg alpm.Package) error { thisDB := pkg.DB().Name() if dbName != thisDB { @@ -251,7 +255,9 @@ func (ae *AlpmExecutor) RefreshHandle() error { return errConf } - alpmSetQuestionCallback(alpmHandle, ae.questionCallback()) + if err := alpmSetQuestionCallback(alpmHandle, ae.questionCallback()); err != nil { + return err + } alpmSetLogCallback(alpmHandle, ae.logCallback()) ae.handle = alpmHandle ae.syncDBsCache = nil @@ -275,11 +281,7 @@ func (ae *AlpmExecutor) LocalSatisfierExists(pkgName string) bool { } func (ae *AlpmExecutor) SyncSatisfierExists(pkgName string) bool { - if _, err := ae.syncDB.FindSatisfier(pkgName); err != nil { - return false - } - - return true + return ae.SyncSatisfier(pkgName) != nil } func (ae *AlpmExecutor) IsCorrectVersionInstalled(pkgName, versionRequired string) bool { @@ -291,74 +293,63 @@ func (ae *AlpmExecutor) IsCorrectVersionInstalled(pkgName, versionRequired strin return alpmPackage.Version() == versionRequired } -func (ae *AlpmExecutor) SyncSatisfier(pkgName string) alpm.IPackage { - foundPkg, err := ae.syncDB.FindSatisfier(pkgName) +func (ae *AlpmExecutor) SyncSatisfier(pkgName string) alpm.Package { + dbs := ae.syncDBs() + if len(dbs) == 0 { + return nil + } + // Use FindDBSatisfier across sync databases + dbSlice := make([]alpm.Database, len(dbs)) + copy(dbSlice, dbs) + return ae.handle.FindDBSatisfier(dbSlice, pkgName) +} + +func (ae *AlpmExecutor) PackagesFromGroup(groupName string) []alpm.Package { + pkgs, err := ae.handle.FindGroupPkgs(ae.syncDBs(), groupName) if err != nil { return nil } - - return foundPkg + return pkgs } -func (ae *AlpmExecutor) PackagesFromGroup(groupName string) []alpm.IPackage { - groupPackages := []alpm.IPackage{} - _ = ae.syncDB.FindGroupPkgs(groupName).ForEach(func(pkg alpm.IPackage) error { - groupPackages = append(groupPackages, pkg) - - return nil - }) - - return groupPackages -} - -func (ae *AlpmExecutor) PackagesFromGroupAndDB(groupName, dbName string) ([]alpm.IPackage, error) { - singleDBList, err := ae.handle.SyncDBListByDBName(dbName) +func (ae *AlpmExecutor) PackagesFromGroupAndDB(groupName, dbName string) ([]alpm.Package, error) { + singleDBs, err := ae.handle.SyncDBListByDBName(dbName) if err != nil { return nil, err } - - groupPackages := []alpm.IPackage{} - _ = singleDBList.FindGroupPkgs(groupName).ForEach(func(pkg alpm.IPackage) error { - groupPackages = append(groupPackages, pkg) - - return nil - }) - - return groupPackages, nil + return ae.handle.FindGroupPkgs(singleDBs, groupName) } -func (ae *AlpmExecutor) LocalPackages() []alpm.IPackage { - localPackages := []alpm.IPackage{} - _ = ae.localDB.PkgCache().ForEach(func(pkg alpm.IPackage) error { +func (ae *AlpmExecutor) LocalPackages() []alpm.Package { + localPackages := []alpm.Package{} + _ = ae.localDB.PkgCache().ForEach(func(pkg alpm.Package) error { localPackages = append(localPackages, pkg) return nil }) - return localPackages } // SyncPackages searches SyncDB for packages or returns all packages if no search param is given. -func (ae *AlpmExecutor) SyncPackages(pkgNames ...string) []alpm.IPackage { - repoPackages := []alpm.IPackage{} - _ = ae.syncDB.ForEach(func(alpmDB alpm.IDB) error { +func (ae *AlpmExecutor) SyncPackages(pkgNames ...string) []alpm.Package { + repoPackages := []alpm.Package{} + for _, alpmDB := range ae.syncDBs() { if len(pkgNames) == 0 { - _ = alpmDB.PkgCache().ForEach(func(pkg alpm.IPackage) error { - repoPackages = append(repoPackages, pkg) - return nil - }) - } else { - _ = alpmDB.Search(pkgNames).ForEach(func(pkg alpm.IPackage) error { + _ = alpmDB.PkgCache().ForEach(func(pkg alpm.Package) error { repoPackages = append(repoPackages, pkg) return nil }) + continue } - return nil - }) + _ = alpmDB.Search(pkgNames).ForEach(func(pkg alpm.Package) error { + repoPackages = append(repoPackages, pkg) + return nil + }) + } return repoPackages } -func (ae *AlpmExecutor) LocalPackage(pkgName string) alpm.IPackage { +func (ae *AlpmExecutor) LocalPackage(pkgName string) alpm.Package { pkg := ae.localDB.Pkg(pkgName) if pkg == nil { return nil @@ -367,15 +358,15 @@ func (ae *AlpmExecutor) LocalPackage(pkgName string) alpm.IPackage { return pkg } -func (ae *AlpmExecutor) syncDBs() []alpm.IDB { +func (ae *AlpmExecutor) syncDBs() []alpm.Database { if ae.syncDBsCache == nil { - ae.syncDBsCache = ae.syncDB.Slice() + ae.syncDBsCache = ae.syncDB } return ae.syncDBsCache } -func (ae *AlpmExecutor) SyncPackage(pkgName string) alpm.IPackage { +func (ae *AlpmExecutor) SyncPackage(pkgName string) alpm.Package { for _, db := range ae.syncDBs() { if dbPkg := db.Pkg(pkgName); dbPkg != nil { return dbPkg @@ -385,7 +376,7 @@ func (ae *AlpmExecutor) SyncPackage(pkgName string) alpm.IPackage { return nil } -func (ae *AlpmExecutor) SyncPackageFromDB(pkgName, dbName string) alpm.IPackage { +func (ae *AlpmExecutor) SyncPackageFromDB(pkgName, dbName string) alpm.Package { singleDB, err := ae.handle.SyncDBByName(dbName) if err != nil { return nil @@ -394,38 +385,34 @@ func (ae *AlpmExecutor) SyncPackageFromDB(pkgName, dbName string) alpm.IPackage return singleDB.Pkg(pkgName) } -func (ae *AlpmExecutor) SatisfierFromDB(pkgName, dbName string) (alpm.IPackage, error) { - singleDBList, err := ae.handle.SyncDBListByDBName(dbName) +func (ae *AlpmExecutor) SatisfierFromDB(pkgName, dbName string) (alpm.Package, error) { + singleDBs, err := ae.handle.SyncDBListByDBName(dbName) if err != nil { return nil, err } - foundPkg, err := singleDBList.FindSatisfier(pkgName) - if err != nil { + foundPkg := ae.handle.FindDBSatisfier(singleDBs, pkgName) + if foundPkg == nil { return nil, nil } return foundPkg, nil } -func (ae *AlpmExecutor) PackageDepends(pkg alpm.IPackage) []alpm.Depend { - alpmPackage := pkg.(*alpm.Package) - return alpmPackage.Depends().Slice() +func (ae *AlpmExecutor) PackageDepends(pkg alpm.Package) []alpm.Depend { + return pkg.Depends() } -func (ae *AlpmExecutor) PackageOptionalDepends(pkg alpm.IPackage) []alpm.Depend { - alpmPackage := pkg.(*alpm.Package) - return alpmPackage.OptionalDepends().Slice() +func (ae *AlpmExecutor) PackageOptionalDepends(pkg alpm.Package) []alpm.Depend { + return pkg.OptionalDepends() } -func (ae *AlpmExecutor) PackageProvides(pkg alpm.IPackage) []alpm.Depend { - alpmPackage := pkg.(*alpm.Package) - return alpmPackage.Provides().Slice() +func (ae *AlpmExecutor) PackageProvides(pkg alpm.Package) []alpm.Depend { + return pkg.Provides() } -func (ae *AlpmExecutor) PackageGroups(pkg alpm.IPackage) []string { - alpmPackage := pkg.(*alpm.Package) - return alpmPackage.Groups().Slice() +func (ae *AlpmExecutor) PackageGroups(pkg alpm.Package) []string { + return pkg.Groups() } // upRepo gathers local packages and checks if they have new versions. @@ -453,7 +440,7 @@ func (ae *AlpmExecutor) SyncUpgrades(enableDowngrade bool) ( return ups, err } - _ = ae.handle.TransGetAdd().ForEach(func(pkg alpm.IPackage) error { + _ = ae.handle.TransGetAdd().ForEach(func(pkg alpm.Package) error { localVer := "-" reason := alpm.PkgReasonExplicit @@ -474,29 +461,22 @@ func (ae *AlpmExecutor) SyncUpgrades(enableDowngrade bool) ( return ups, errReturn } -func (ae *AlpmExecutor) BiggestPackages() []alpm.IPackage { - localPackages := []alpm.IPackage{} - _ = ae.localDB.PkgCache().SortBySize().ForEach(func(pkg alpm.IPackage) error { - localPackages = append(localPackages, pkg) - return nil - }) - - return localPackages +func (ae *AlpmExecutor) BiggestPackages() []alpm.Package { + return append([]alpm.Package{}, ae.localDB.PkgCache().SortBySize()...) } func (ae *AlpmExecutor) LastBuildTime() time.Time { var lastTime time.Time - _ = ae.syncDB.ForEach(func(db alpm.IDB) error { - _ = db.PkgCache().ForEach(func(pkg alpm.IPackage) error { + for _, db := range ae.syncDBs() { + _ = db.PkgCache().ForEach(func(pkg alpm.Package) error { thisTime := pkg.BuildDate() if thisTime.After(lastTime) { lastTime = thisTime } return nil }) - return nil - }) + } return lastTime } @@ -510,34 +490,32 @@ func (ae *AlpmExecutor) Cleanup() { } func (ae *AlpmExecutor) Repos() (repos []string) { - _ = ae.syncDB.ForEach(func(db alpm.IDB) error { + for _, db := range ae.syncDBs() { repos = append(repos, db.Name()) - return nil - }) + } return } -func alpmSetArchitecture(alpmHandle *alpm.Handle, arch []string) error { +func alpmSetArchitecture(alpmHandle alpm.Handle, arch []string) error { return alpmHandle.SetArchitectures(arch) } func (ae *AlpmExecutor) AlpmArchitectures() ([]string, error) { - architectures, err := ae.handle.GetArchitectures() + architectures, err := ae.handle.Architectures() - return architectures.Slice(), err + return architectures, err } -func alpmSetLogCallback(alpmHandle *alpm.Handle, cb func(alpm.LogLevel, string)) { - alpmHandle.SetLogCallback(func(ctx any, lvl alpm.LogLevel, msg string) { - cbo := ctx.(func(alpm.LogLevel, string)) - cbo(lvl, msg) - }, cb) +func alpmSetLogCallback(alpmHandle alpm.Handle, cb func(alpm.LogLevel, string)) { + // dyalpm uses a different callback mechanism - log callback not easily supported + // due to va_list in libalpm. Skip setting log callback. + _ = alpmHandle + _ = cb } -func alpmSetQuestionCallback(alpmHandle *alpm.Handle, cb func(alpm.QuestionAny)) { - alpmHandle.SetQuestionCallback(func(ctx any, q alpm.QuestionAny) { - cbo := ctx.(func(alpm.QuestionAny)) - cbo(q) - }, cb) +func alpmSetQuestionCallback(alpmHandle alpm.Handle, cb func(alpm.QuestionAny)) error { + return alpmHandle.SetQuestionCallbackFunc(func(q alpm.Question) { + cb(alpm.QuestionAny{Question: q}) + }) } diff --git a/pkg/db/ialpm/alpm_test.go b/pkg/db/ialpm/alpm_test.go index 87884d71..3f301bd3 100644 --- a/pkg/db/ialpm/alpm_test.go +++ b/pkg/db/ialpm/alpm_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/Morganamilo/go-pacmanconf" "github.com/stretchr/testify/assert" @@ -60,29 +60,29 @@ func TestAlpmExecutor(t *testing.T) { h := aExec.handle assert.NotNil(t, h) - root, err := h.Root() + root := h.Root() assert.Nil(t, err) assert.Equal(t, "/", root) - dbPath, err := h.DBPath() + dbPath := h.DBPath() assert.Nil(t, err) assert.Equal(t, "/var/lib/pacman/", dbPath) cache, err := h.CacheDirs() assert.Nil(t, err) - assert.Equal(t, []string{"/cachedir/", "/another/"}, cache.Slice()) + assert.Equal(t, []string{"/cachedir/", "/another/"}, cache) - log, err := h.LogFile() + log := h.LogFile() assert.Nil(t, err) assert.Equal(t, "/logfile", log) - gpg, err := h.GPGDir() + gpg := h.GPGDir() assert.Nil(t, err) assert.Equal(t, "/gpgdir/", gpg) hook, err := h.HookDirs() assert.Nil(t, err) - assert.Equal(t, []string{"/usr/share/libalpm/hooks/", "/hookdir/"}, hook.Slice()) + assert.Equal(t, []string{"/usr/share/libalpm/hooks/", "/hookdir/"}, hook) arch, err := alpmTestGetArch(h) assert.Nil(t, err) @@ -90,27 +90,27 @@ func TestAlpmExecutor(t *testing.T) { ignorePkg, err := h.IgnorePkgs() assert.Nil(t, err) - assert.Equal(t, []string{"ignore", "this", "package"}, ignorePkg.Slice()) + assert.Equal(t, []string{"ignore", "this", "package"}, ignorePkg) ignoreGroup, err := h.IgnoreGroups() assert.Nil(t, err) - assert.Equal(t, []string{"ignore", "this", "group"}, ignoreGroup.Slice()) + assert.Equal(t, []string{"ignore", "this", "group"}, ignoreGroup) noUp, err := h.NoUpgrades() assert.Nil(t, err) - assert.Equal(t, []string{"noupgrade"}, noUp.Slice()) + assert.Equal(t, []string{"noupgrade"}, noUp) noEx, err := h.NoExtracts() assert.Nil(t, err) - assert.Equal(t, []string{"noextract"}, noEx.Slice()) + assert.Equal(t, []string{"noextract"}, noEx) - check, err := h.CheckSpace() + check := h.CheckSpace() assert.Nil(t, err) assert.Equal(t, true, check) } -func alpmTestGetArch(h *alpm.Handle) ([]string, error) { - architectures, err := h.GetArchitectures() +func alpmTestGetArch(h alpm.Handle) ([]string, error) { + architectures, err := h.Architectures() - return architectures.Slice(), err + return architectures, err } diff --git a/pkg/db/ialpm/high_level.go b/pkg/db/ialpm/high_level.go index 54aeee03..3f9b4cba 100644 --- a/pkg/db/ialpm/high_level.go +++ b/pkg/db/ialpm/high_level.go @@ -1,7 +1,7 @@ package ialpm import ( - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/text" ) @@ -9,7 +9,7 @@ import ( // GetPackageNamesBySource returns package names with and without correspondence in SyncDBS respectively. func (ae *AlpmExecutor) getPackageNamesBySource() { if ae.installedRemotePkgMap == nil { - ae.installedRemotePkgMap = map[string]alpm.IPackage{} + ae.installedRemotePkgMap = map[string]alpm.Package{} } for _, localpkg := range ae.LocalPackages() { pkgName := localpkg.Name() @@ -25,7 +25,7 @@ func (ae *AlpmExecutor) getPackageNamesBySource() { "sync_len", len(ae.installedSyncPkgNames), "remote_len", len(ae.installedRemotePkgNames)) } -func (ae *AlpmExecutor) InstalledRemotePackages() map[string]alpm.IPackage { +func (ae *AlpmExecutor) InstalledRemotePackages() map[string]alpm.Package { if ae.installedRemotePkgMap == nil { ae.getPackageNamesBySource() } diff --git a/pkg/db/mock/executor.go b/pkg/db/mock/executor.go index be205b70..c4bd7e45 100644 --- a/pkg/db/mock/executor.go +++ b/pkg/db/mock/executor.go @@ -6,11 +6,11 @@ import ( "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/text" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" ) type ( - IPackage = alpm.IPackage + IPackage = alpm.Package Depend = alpm.Depend Upgrade = db.Upgrade ) @@ -25,7 +25,7 @@ type DBExecutor struct { LocalPackagesFn func() []IPackage LocalSatisfierExistsFn func(string) bool PackageDependsFn func(IPackage) []Depend - PackageOptionalDependsFn func(alpm.IPackage) []alpm.Depend + PackageOptionalDependsFn func(alpm.Package) []alpm.Depend PackageProvidesFn func(IPackage) []Depend PackagesFromGroupFn func(string) []IPackage PackagesFromGroupAndDBFn func(string, string) ([]IPackage, error) diff --git a/pkg/db/mock/repo.go b/pkg/db/mock/repo.go index ce4ddadd..83ccecb6 100644 --- a/pkg/db/mock/repo.go +++ b/pkg/db/mock/repo.go @@ -1,29 +1,15 @@ package mock import ( + "io" "time" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" ) +// DependList is a lightweight helper for test fixtures. type DependList struct { - Depends []Depend -} - -func (d DependList) Slice() []alpm.Depend { - return d.Depends -} - -func (d DependList) ForEach(f func(*alpm.Depend) error) error { - for i := range d.Depends { - dep := &d.Depends[i] - err := f(dep) - if err != nil { - return err - } - } - - return nil + Depends []alpm.Depend } type Package struct { @@ -37,12 +23,12 @@ type Package struct { PSize int64 PVersion string PReason alpm.PkgReason - PDepends alpm.IDependList - PProvides alpm.IDependList + PDepends DependList + PProvides DependList PArchitecture string } -var _ alpm.IPackage = (*Package)(nil) +var _ alpm.Package = (*Package)(nil) func (p *Package) Base() string { return p.PBase @@ -52,7 +38,7 @@ func (p *Package) BuildDate() time.Time { return p.PBuildDate } -func (p *Package) DB() alpm.IDB { +func (p *Package) DB() alpm.Database { return p.PDB } @@ -102,35 +88,32 @@ func (p *Package) Architecture() string { } // Backup returns a list of package backups. -func (p *Package) Backup() alpm.BackupList { +func (p *Package) Backup() []alpm.Backup { panic("not implemented") } // Conflicts returns the conflicts of the package as a DependList. -func (p *Package) Conflicts() alpm.IDependList { +func (p *Package) Conflicts() []alpm.Depend { panic("not implemented") } // Depends returns the package's dependency list. -func (p *Package) Depends() alpm.IDependList { - if p.PDepends != nil { - return p.PDepends - } - return alpm.DependList{} +func (p *Package) Depends() []alpm.Depend { + return p.PDepends.Depends } // Depends returns the package's optional dependency list. -func (p *Package) OptionalDepends() alpm.IDependList { +func (p *Package) OptionalDepends() []alpm.Depend { panic("not implemented") } // Depends returns the package's check dependency list. -func (p *Package) CheckDepends() alpm.IDependList { +func (p *Package) CheckDepends() []alpm.Depend { panic("not implemented") } // Depends returns the package's make dependency list. -func (p *Package) MakeDepends() alpm.IDependList { +func (p *Package) MakeDepends() []alpm.Depend { panic("not implemented") } @@ -145,7 +128,7 @@ func (p *Package) ContainsFile(path string) (alpm.File, error) { } // Groups returns the groups the package belongs to. -func (p *Package) Groups() alpm.StringList { +func (p *Package) Groups() []string { panic("not implemented") } @@ -155,7 +138,7 @@ func (p *Package) InstallDate() time.Time { } // Licenses returns the package license list. -func (p *Package) Licenses() alpm.StringList { +func (p *Package) Licenses() []string { panic("not implemented") } @@ -170,11 +153,8 @@ func (p *Package) Packager() string { } // Provides returns DependList of packages provides by package. -func (p *Package) Provides() alpm.IDependList { - if p.PProvides == nil { - return alpm.DependList{} - } - return p.PProvides +func (p *Package) Provides() []alpm.Depend { + return p.PProvides.Depends } // Origin returns package origin. @@ -183,7 +163,7 @@ func (p *Package) Origin() alpm.PkgFrom { } // Replaces returns a DependList with the packages this package replaces. -func (p *Package) Replaces() alpm.IDependList { +func (p *Package) Replaces() []alpm.Depend { panic("not implemented") } @@ -193,19 +173,19 @@ func (p *Package) URL() string { } // ComputeRequiredBy returns the names of reverse dependencies of a package. -func (p *Package) ComputeRequiredBy() []string { +func (p *Package) ComputeRequiredBy() ([]string, error) { panic("not implemented") } // ComputeOptionalFor returns the names of packages that optionally // require the given package. -func (p *Package) ComputeOptionalFor() []string { +func (p *Package) ComputeOptionalFor() ([]string, error) { panic("not implemented") } // SyncNewVersion checks if there is a new version of the // package in a given DBlist. -func (p *Package) SyncNewVersion(l alpm.IDBList) alpm.IPackage { +func (p *Package) SyncNewVersion(dbs []alpm.Database) alpm.Package { panic("not implemented") } @@ -213,8 +193,57 @@ func (p *Package) Type() string { panic("not implemented") } +func (p *Package) CheckMD5Sum() error { + panic("not implemented") +} + +func (p *Package) CheckPGPSignature() (alpm.SigList, error) { + panic("not implemented") +} + +func (p *Package) Contains(path string) bool { + panic("not implemented") +} + +func (p *Package) Free() error { + return nil +} + +// New methods required by dyalpm refactoring +func (p *Package) HasScriptlet() bool { + return false +} + +func (p *Package) DownloadSize() int64 { + return 0 +} + +func (p *Package) NativeHandle() alpm.Handle { + return nil +} + +func (p *Package) Sig() string { + return "" +} + +func (p *Package) PkgValidation() alpm.PkgValidation { + return alpm.PkgValidationUnknown +} + +func (p *Package) XData() string { + return "" +} + +func (p *Package) Changelog() (io.ReadCloser, error) { + return nil, nil +} + +func (p *Package) SyncGetNewVersion(dbsSync []alpm.Database) alpm.Package { + return nil +} + type DB struct { - alpm.IDB + alpm.Database name string } @@ -225,3 +254,23 @@ func NewDB(name string) *DB { func (d *DB) Name() string { return d.name } + +func (d *DB) Pkg(name string) alpm.Package { + return nil +} + +func (d *DB) PkgCache() alpm.PackageIterator { + return alpm.PackageIterator{} +} + +func (d *DB) Search(needles []string) alpm.PackageIterator { + return alpm.PackageIterator{} +} + +func (d *DB) SetServers(servers []string) error { + return nil +} + +func (d *DB) SetUsage(usage int) error { + return nil +} diff --git a/pkg/dep/dep_graph.go b/pkg/dep/dep_graph.go index eba7aa95..2d57c83d 100644 --- a/pkg/dep/dep_graph.go +++ b/pkg/dep/dep_graph.go @@ -7,7 +7,7 @@ import ( "strings" aurc "github.com/Jguer/aur" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" gosrc "github.com/Morganamilo/go-srcinfo" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" @@ -38,7 +38,7 @@ func (i *InstallInfo) String() string { } type ( - Reason uint + Reason int Source int ) @@ -311,18 +311,19 @@ func (g *Grapher) addDepNodes(ctx context.Context, pkg *aur.Pkg, graph *topo.Gra func (g *Grapher) GraphSyncPkg(ctx context.Context, graph *topo.Graph[string, *InstallInfo], - pkg alpm.IPackage, upgradeInfo *db.SyncUpgrade, + pkg alpm.Package, upgradeInfo *db.SyncUpgrade, ) *topo.Graph[string, *InstallInfo] { if graph == nil { graph = NewGraph() } graph.AddNode(pkg.Name()) - _ = pkg.Provides().ForEach(func(p *alpm.Depend) error { + provides := pkg.Provides() + for i := range provides { + p := &provides[i] g.logger.Debugln(pkg.Name() + " provides: " + p.String()) graph.AddProvides(p.Name, p, pkg.Name()) - return nil - }) + } dbName := pkg.DB().Name() info := &InstallInfo{ @@ -512,16 +513,25 @@ func (g *Grapher) findDepsFromAUR(ctx context.Context, for i := range aurPkgs { pkg := &aurPkgs[i] - if deps.Contains(pkg.Name) { - g.providerCache[pkg.Name] = append(g.providerCache[pkg.Name], *pkg) + // Cache by the full depString (including version) for each dep whose name matches + for _, depString := range deps.ToSlice() { + depName, _, _ := splitDep(depString) + if depName == pkg.Name { + g.providerCache[depString] = append(g.providerCache[depString], *pkg) + } } for _, val := range pkg.Provides { if val == pkg.Name { continue } - if deps.Contains(val) { - g.providerCache[val] = append(g.providerCache[val], *pkg) + // Also check provides against versioned deps + provideName, _, _ := splitDep(val) + for _, depString := range deps.ToSlice() { + depName, _, _ := splitDep(depString) + if depName == provideName { + g.providerCache[depString] = append(g.providerCache[depString], *pkg) + } } } } @@ -595,7 +605,6 @@ func (g *Grapher) ValidateAndSetNodeInfo(graph *topo.Graph[string, *InstallInfo] if info.Value.Reason < nodeInfo.Value.Reason { return // refuse to downgrade reason } - if info.Value.Upgrade { return // refuse to overwrite an upgrade } @@ -685,7 +694,7 @@ func (g *Grapher) addNodes( }, }) - if newDeps := alpmPkg.Depends().Slice(); len(newDeps) != 0 && g.fullGraph { + if newDeps := alpmPkg.Depends(); len(newDeps) != 0 && g.fullGraph { newDepsSlice := make([]string, 0, len(newDeps)) for _, newDep := range newDeps { newDepsSlice = append(newDepsSlice, newDep.Name) @@ -862,7 +871,7 @@ func archStringToString(alpmArches []string, archString []gosrc.ArchString) []st func aurDepModToAlpmDep(mod string) alpm.DepMod { switch mod { case "=": - return alpm.DepModEq + return alpm.DepModEQ case ">=": return alpm.DepModGE case "<=": diff --git a/pkg/dep/dep_graph_bench_test.go b/pkg/dep/dep_graph_bench_test.go new file mode 100644 index 00000000..41d41890 --- /dev/null +++ b/pkg/dep/dep_graph_bench_test.go @@ -0,0 +1,577 @@ +//go:build !integration +// +build !integration + +package dep + +import ( + "context" + "io" + "os" + "testing" + + aurc "github.com/Jguer/aur" + alpm "github.com/Jguer/dyalpm" + "github.com/stretchr/testify/require" + + "github.com/Jguer/yay/v12/pkg/db/mock" + mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" + aur "github.com/Jguer/yay/v12/pkg/query" + "github.com/Jguer/yay/v12/pkg/text" +) + +// benchCase represents a single benchmark scenario with expected results for validation. +type benchCase struct { + name string + targets []string + expectedLayers []map[string]*InstallInfo + noDeps bool + noCheckDeps bool +} + +// newBenchMockDB creates a mock DB executor for benchmarking gstreamer-git scenarios. +func newBenchMockDB() *mock.DBExecutor { + return &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "gstreamer-git", "gst-plugins-base-libs-git", "gst-plugins-good-git", + "gstreamer-git=1.24.0.r37-1", "gst-plugins-base-libs-git=1.24.0.r37-1": + return nil + case "libxml2": + return &mock.Package{ + PName: "libxml2", + PVersion: "2.12.0-1", + PDB: mock.NewDB("core"), + } + case "glib2": + return &mock.Package{ + PName: "glib2", + PVersion: "2.78.0-1", + PDB: mock.NewDB("core"), + } + case "orc": + return &mock.Package{ + PName: "orc", + PVersion: "0.4.34-1", + PDB: mock.NewDB("extra"), + } + case "libxv": + return &mock.Package{ + PName: "libxv", + PVersion: "1.0.12-1", + PDB: mock.NewDB("extra"), + } + case "iso-codes": + return &mock.Package{ + PName: "iso-codes", + PVersion: "4.15.0-1", + PDB: mock.NewDB("extra"), + } + case "libpulse": + return &mock.Package{ + PName: "libpulse", + PVersion: "16.1-1", + PDB: mock.NewDB("extra"), + } + case "wavpack": + return &mock.Package{ + PName: "wavpack", + PVersion: "5.6.0-1", + PDB: mock.NewDB("extra"), + } + } + return nil + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "gstreamer-git", "gstreamer-git=1.24.0.r37-1", + "gst-plugins-base-libs-git", "gst-plugins-base-libs-git=1.24.0.r37-1", + "gst-plugins-good-git": + return false + case "libxml2", "glib2", "orc", "libxv", "iso-codes", "libpulse", "wavpack", + "git", "meson", "ninja", "llvm", "clang": + return true + } + return true + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } +} + +// newBenchMockAUR creates a mock AUR client for benchmarking gstreamer-git scenarios. +func newBenchMockAUR(t testing.TB) *mockaur.MockAUR { + return &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + if len(query.Needles) > 0 { + for _, needle := range query.Needles { + if needle == "gstreamer-git" || needle == "gst-plugins-base-libs-git" || needle == "gst-plugins-good-git" { + gstFn := getFromFile(t, "testdata/gstreamer-git.json") + return gstFn(ctx, query) + } + } + } + return []aur.Pkg{}, nil + }} +} + +// newJellyfinMockDB creates a mock DB for jellyfin scenarios. +func newJellyfinMockDB() *mock.DBExecutor { + return &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "jellyfin": + return nil + case "dotnet-runtime-6.0": + return &mock.Package{ + PName: "dotnet-runtime-6.0", + PBase: "dotnet-runtime-6.0", + PVersion: "6.0.100-1", + PDB: mock.NewDB("community"), + } + case "dotnet-sdk-6.0": + return &mock.Package{ + PName: "dotnet-sdk-6.0", + PBase: "dotnet-sdk-6.0", + PVersion: "6.0.100-1", + PDB: mock.NewDB("community"), + } + } + return nil + }, + PackagesFromGroupFn: func(string) []mock.IPackage { return nil }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "dotnet-sdk-6.0", "dotnet-runtime-6.0", "jellyfin-server=10.8.8", "jellyfin-web=10.8.8": + return false + } + return true + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } +} + +// newJellyfinMockAUR creates a mock AUR for jellyfin scenarios. +func newJellyfinMockAUR(t testing.TB) *mockaur.MockAUR { + return &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + if len(query.Needles) == 0 { + return []aur.Pkg{}, nil + } + switch query.Needles[0] { + case "jellyfin": + return getFromFile(t, "testdata/jellyfin.json")(ctx, query) + case "jellyfin-web": + return getFromFile(t, "testdata/jellyfin-web.json")(ctx, query) + case "jellyfin-server": + return getFromFile(t, "testdata/jellyfin-server.json")(ctx, query) + } + return []aur.Pkg{}, nil + }} +} + +// newCephMockDB creates a mock DB for ceph scenarios with providers. +func newCephMockDB() *mock.DBExecutor { + return &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "ceph-bin", "ceph-libs-bin", "ceph", "ceph-libs", "ceph-libs=17.2.6-2": + return nil + } + return nil + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "ceph-libs", "ceph-libs=17.2.6-2": + return false + case "dep1", "dep2", "dep3", "makedep1", "makedep2", "checkdep1": + return true + } + return true + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } +} + +// newCephMockAUR creates a mock AUR for ceph scenarios. +func newCephMockAUR() *mockaur.MockAUR { + mockPkgs := map[string]aur.Pkg{ + "ceph-bin": { + Name: "ceph-bin", + PackageBase: "ceph-bin", + Version: "17.2.6-2", + Depends: []string{"ceph-libs=17.2.6-2", "dep1"}, + Provides: []string{"ceph=17.2.6-2"}, + }, + "ceph-libs-bin": { + Name: "ceph-libs-bin", + PackageBase: "ceph-bin", + Version: "17.2.6-2", + Depends: []string{"dep1", "dep2"}, + Provides: []string{"ceph-libs=17.2.6-2"}, + }, + "ceph": { + Name: "ceph", + PackageBase: "ceph", + Version: "17.2.6-2", + Depends: []string{"ceph-libs=17.2.6-2", "dep1"}, + MakeDepends: []string{"makedep1"}, + CheckDepends: []string{"checkdep1"}, + Provides: []string{"ceph=17.2.6-2"}, + }, + "ceph-libs": { + Name: "ceph-libs", + PackageBase: "ceph", + Version: "17.2.6-2", + Depends: []string{"dep1", "dep2", "dep3"}, + MakeDepends: []string{"makedep1", "makedep2"}, + CheckDepends: []string{"checkdep1"}, + Provides: []string{"ceph-libs=17.2.6-2"}, + }, + } + + return &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + pkgs := []aur.Pkg{} + for _, needle := range query.Needles { + if pkg, ok := mockPkgs[needle]; ok { + pkgs = append(pkgs, pkg) + } + } + return pkgs, nil + }} +} + +// newAndroidSDKMockDB creates a mock DB for android-sdk scenarios. +func newAndroidSDKMockDB() *mock.DBExecutor { + return &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "android-sdk": + return nil + case "jdk11-openjdk": + return &mock.Package{ + PName: "jdk11-openjdk", + PVersion: "11.0.12.u7-1", + PDB: mock.NewDB("community"), + PProvides: mock.DependList{ + Depends: []alpm.Depend{ + {Name: "java-environment", Version: "11", Mod: alpm.DepModEQ}, + {Name: "java-environment-openjdk", Version: "11", Mod: alpm.DepModEQ}, + {Name: "jdk11-openjdk", Version: "11.0.19.u7-1", Mod: alpm.DepModEQ}, + }, + }, + } + } + return nil + }, + PackagesFromGroupFn: func(string) []mock.IPackage { return nil }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "java-environment": + return false + case "libxtst", "fontconfig", "freetype2", "lib32-gcc-libs", "lib32-glibc", + "libx11", "libxext", "libxrender", "zlib", "gcc-libs": + return true + } + return true + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } +} + +// newAndroidSDKMockAUR creates a mock AUR for android-sdk scenarios. +func newAndroidSDKMockAUR(t testing.TB) *mockaur.MockAUR { + return &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + if len(query.Needles) > 0 && query.Needles[0] == "android-sdk" { + return getFromFile(t, "testdata/android-sdk.json")(ctx, query) + } + return []aur.Pkg{}, nil + }} +} + +// verifyLayers checks that the actual layers match expected layers. +func verifyLayers(t testing.TB, expected, actual []map[string]*InstallInfo) { + t.Helper() + require.Equal(t, len(expected), len(actual), "layer count mismatch") + for i := range expected { + require.Equal(t, len(expected[i]), len(actual[i]), "layer %d package count mismatch", i) + for name, expectedInfo := range expected[i] { + actualInfo, ok := actual[i][name] + require.True(t, ok, "missing package %s in layer %d", name, i) + require.Equal(t, expectedInfo.Source, actualInfo.Source, "source mismatch for %s", name) + require.Equal(t, expectedInfo.Reason, actualInfo.Reason, "reason mismatch for %s", name) + require.Equal(t, expectedInfo.Version, actualInfo.Version, "version mismatch for %s", name) + if expectedInfo.AURBase != nil { + require.NotNil(t, actualInfo.AURBase, "AURBase should not be nil for %s", name) + require.Equal(t, *expectedInfo.AURBase, *actualInfo.AURBase, "AURBase mismatch for %s", name) + } + if expectedInfo.SyncDBName != nil { + require.NotNil(t, actualInfo.SyncDBName, "SyncDBName should not be nil for %s", name) + require.Equal(t, *expectedInfo.SyncDBName, *actualInfo.SyncDBName, "SyncDBName mismatch for %s", name) + } + } + } +} + +// BenchmarkGraphFromTargets_GstreamerGit benchmarks dependency graph construction +// for the gstreamer-git split package scenario. +func BenchmarkGraphFromTargets_GstreamerGit(b *testing.B) { + cases := []benchCase{ + { + name: "SingleTarget", + targets: []string{"gst-plugins-good-git"}, + expectedLayers: []map[string]*InstallInfo{ + {"gst-plugins-good-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, + {"gst-plugins-base-libs-git": {Source: AUR, Reason: Dep, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, + {"gstreamer-git": {Source: AUR, Reason: Dep, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, + }, + }, + { + name: "TwoTargets", + targets: []string{"gstreamer-git", "gst-plugins-good-git"}, + expectedLayers: []map[string]*InstallInfo{ + {"gst-plugins-good-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, + {"gst-plugins-base-libs-git": {Source: AUR, Reason: Dep, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, + {"gstreamer-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, + }, + }, + { + name: "AllThreeExplicit", + targets: []string{"gstreamer-git", "gst-plugins-base-libs-git", "gst-plugins-good-git"}, + expectedLayers: []map[string]*InstallInfo{ + {"gst-plugins-good-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, + {"gst-plugins-base-libs-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, + {"gstreamer-git": {Source: AUR, Reason: Explicit, Version: "1.24.0.r37-1", AURBase: ptrString("gstreamer-git")}}, + }, + }, + } + + for _, tc := range cases { + b.Run(tc.name, func(b *testing.B) { + mockDB := newBenchMockDB() + mockAUR := newBenchMockAUR(b) + logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") + g := NewGrapher(mockDB, mockAUR, false, true, tc.noDeps, tc.noCheckDeps, false, logger) + + // Verify correctness once before benchmarking + graph, err := g.GraphFromTargets(context.Background(), nil, tc.targets) + require.NoError(b, err) + layers := graph.TopoSortedLayers(nil) + verifyLayers(b, tc.expectedLayers, layers) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = g.GraphFromTargets(context.Background(), nil, tc.targets) + } + }) + } +} + +// BenchmarkGraphFromTargets_Jellyfin benchmarks dependency graph construction +// for the jellyfin package with mixed AUR/sync dependencies. +func BenchmarkGraphFromTargets_Jellyfin(b *testing.B) { + cases := []benchCase{ + { + name: "NoDeps", + targets: []string{"jellyfin"}, + noDeps: true, + noCheckDeps: false, + expectedLayers: []map[string]*InstallInfo{ + {"jellyfin": {Source: AUR, Reason: Explicit, Version: "10.8.8-1", AURBase: ptrString("jellyfin")}}, + {"dotnet-sdk-6.0": {Source: Sync, Reason: MakeDep, Version: "6.0.100-1", SyncDBName: ptrString("community")}}, + }, + }, + { + name: "WithDeps", + targets: []string{"jellyfin"}, + noDeps: false, + noCheckDeps: false, + expectedLayers: []map[string]*InstallInfo{ + {"jellyfin": {Source: AUR, Reason: Explicit, Version: "10.8.8-1", AURBase: ptrString("jellyfin")}}, + { + "jellyfin-web": {Source: AUR, Reason: Dep, Version: "10.8.8-1", AURBase: ptrString("jellyfin")}, + "jellyfin-server": {Source: AUR, Reason: Dep, Version: "10.8.8-1", AURBase: ptrString("jellyfin")}, + }, + { + "dotnet-sdk-6.0": {Source: Sync, Reason: MakeDep, Version: "6.0.100-1", SyncDBName: ptrString("community")}, + "dotnet-runtime-6.0": {Source: Sync, Reason: Dep, Version: "6.0.100-1", SyncDBName: ptrString("community")}, + }, + }, + }, + } + + for _, tc := range cases { + b.Run(tc.name, func(b *testing.B) { + mockDB := newJellyfinMockDB() + mockAUR := newJellyfinMockAUR(b) + logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") + g := NewGrapher(mockDB, mockAUR, false, true, tc.noDeps, tc.noCheckDeps, false, logger) + + // Verify correctness once before benchmarking + graph, err := g.GraphFromTargets(context.Background(), nil, tc.targets) + require.NoError(b, err) + layers := graph.TopoSortedLayers(nil) + verifyLayers(b, tc.expectedLayers, layers) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = g.GraphFromTargets(context.Background(), nil, tc.targets) + } + }) + } +} + +// BenchmarkGraphFromTargets_CephProvides benchmarks dependency graph construction +// for packages with virtual provides (ceph-bin provides ceph-libs). +func BenchmarkGraphFromTargets_CephProvides(b *testing.B) { + cases := []benchCase{ + { + name: "CephBinWithLibsBin", + targets: []string{"ceph-bin", "ceph-libs-bin"}, + expectedLayers: []map[string]*InstallInfo{ + {"ceph-bin": {Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph-bin")}}, + {"ceph-libs-bin": {Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph-bin")}}, + }, + }, + { + name: "CephOnly", + targets: []string{"ceph"}, + expectedLayers: []map[string]*InstallInfo{ + {"ceph": {Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph")}}, + {"ceph-libs": {Source: AUR, Reason: Dep, Version: "17.2.6-2", AURBase: ptrString("ceph")}}, + }, + }, + { + name: "CephBinOnly", + targets: []string{"ceph-bin"}, + expectedLayers: []map[string]*InstallInfo{ + {"ceph-bin": {Source: AUR, Reason: Explicit, Version: "17.2.6-2", AURBase: ptrString("ceph-bin")}}, + {"ceph-libs": {Source: AUR, Reason: Dep, Version: "17.2.6-2", AURBase: ptrString("ceph")}}, + }, + }, + } + + for _, tc := range cases { + b.Run(tc.name, func(b *testing.B) { + mockDB := newCephMockDB() + mockAUR := newCephMockAUR() + logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") + g := NewGrapher(mockDB, mockAUR, false, true, tc.noDeps, tc.noCheckDeps, false, logger) + + // Verify correctness once before benchmarking + graph, err := g.GraphFromTargets(context.Background(), nil, tc.targets) + require.NoError(b, err) + layers := graph.TopoSortedLayers(nil) + verifyLayers(b, tc.expectedLayers, layers) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = g.GraphFromTargets(context.Background(), nil, tc.targets) + } + }) + } +} + +// BenchmarkGraphFromTargets_AndroidSDK benchmarks dependency graph construction +// for packages with explicit sync dependencies providing virtual packages. +func BenchmarkGraphFromTargets_AndroidSDK(b *testing.B) { + tc := benchCase{ + name: "WithJDK", + targets: []string{"android-sdk", "jdk11-openjdk"}, + expectedLayers: []map[string]*InstallInfo{ + {"android-sdk": {Source: AUR, Reason: Explicit, Version: "26.1.1-2", AURBase: ptrString("android-sdk")}}, + {"jdk11-openjdk": {Source: Sync, Reason: Explicit, Version: "11.0.12.u7-1", SyncDBName: ptrString("community")}}, + }, + } + + mockDB := newAndroidSDKMockDB() + mockAUR := newAndroidSDKMockAUR(b) + logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") + g := NewGrapher(mockDB, mockAUR, false, true, tc.noDeps, tc.noCheckDeps, false, logger) + + // Verify correctness once before benchmarking + graph, err := g.GraphFromTargets(context.Background(), nil, tc.targets) + require.NoError(b, err) + layers := graph.TopoSortedLayers(nil) + verifyLayers(b, tc.expectedLayers, layers) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = g.GraphFromTargets(context.Background(), nil, tc.targets) + } +} + +// BenchmarkTopoSortedLayers benchmarks the topological sort operation on pre-built graphs. +func BenchmarkTopoSortedLayers(b *testing.B) { + b.Run("GstreamerGit", func(b *testing.B) { + mockDB := newBenchMockDB() + mockAUR := newBenchMockAUR(b) + logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, logger) + + graph, err := g.GraphFromTargets(context.Background(), nil, []string{"gst-plugins-good-git"}) + require.NoError(b, err) + + // Verify correctness + layers := graph.TopoSortedLayers(nil) + require.Len(b, layers, 3) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = graph.TopoSortedLayers(nil) + } + }) + + b.Run("Jellyfin", func(b *testing.B) { + mockDB := newJellyfinMockDB() + mockAUR := newJellyfinMockAUR(b) + logger := text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test") + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, logger) + + graph, err := g.GraphFromTargets(context.Background(), nil, []string{"jellyfin"}) + require.NoError(b, err) + + // Verify correctness + layers := graph.TopoSortedLayers(nil) + require.Len(b, layers, 3) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = graph.TopoSortedLayers(nil) + } + }) +} + +// BenchmarkNewGraph benchmarks graph creation overhead. +func BenchmarkNewGraph(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = NewGraph() + } +} + +// BenchmarkGraphDependOn benchmarks adding dependency edges. +func BenchmarkGraphDependOn(b *testing.B) { + b.Run("SmallGraph", func(b *testing.B) { + for i := 0; i < b.N; i++ { + graph := NewGraph() + _ = graph.DependOn("pkg1", "pkg2") + _ = graph.DependOn("pkg2", "pkg3") + _ = graph.DependOn("pkg3", "pkg4") + } + }) + + b.Run("MediumGraph", func(b *testing.B) { + for i := 0; i < b.N; i++ { + graph := NewGraph() + // Create a chain of 20 dependencies + for j := 0; j < 20; j++ { + _ = graph.DependOn( + "pkg"+string(rune('A'+j)), + "pkg"+string(rune('A'+j+1)), + ) + } + } + }) +} diff --git a/pkg/dep/dep_graph_rpc_test.go b/pkg/dep/dep_graph_rpc_test.go new file mode 100644 index 00000000..6952cddd --- /dev/null +++ b/pkg/dep/dep_graph_rpc_test.go @@ -0,0 +1,814 @@ +//go:build !integration +// +build !integration + +// Package dep provides tests for tree resolution and parsing using RPC/.SRCINFO metadata +// instead of PKGBUILD parsing. These tests validate: +// - Reliable parser: Ability to handle complex packages using provided metadata +// - Reliable solver: Ability to correctly solve and build complex dependency chains +// - Split packages: Ability to correctly build and install split packages +package dep + +import ( + "context" + "fmt" + "io" + "os" + "testing" + + aurc "github.com/Jguer/aur" + "github.com/stretchr/testify/require" + + "github.com/Jguer/yay/v12/pkg/db/mock" + mockaur "github.com/Jguer/yay/v12/pkg/dep/mock" + aur "github.com/Jguer/yay/v12/pkg/query" + "github.com/Jguer/yay/v12/pkg/text" +) + +// TestGrapher_ReliableParser_AWSCliGit tests the reliable parsing capability +// for complex packages like aws-cli-git that have many dependencies. +// This validates that the RPC metadata is correctly parsed and dependencies +// are properly resolved without needing PKGBUILD parsing. +func TestGrapher_ReliableParser_AWSCliGit(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "aws-cli-git": + return nil + case "python": + return &mock.Package{PName: "python", PVersion: "3.11.0-1", PDB: mock.NewDB("core")} + case "python-botocore>=1.19.35", "python-botocore": + return &mock.Package{PName: "python-botocore", PVersion: "1.29.0-1", PDB: mock.NewDB("extra")} + case "python-docutils>=0.10", "python-docutils": + return &mock.Package{PName: "python-docutils", PVersion: "0.19-1", PDB: mock.NewDB("extra")} + case "python-rsa>=3.1.2", "python-rsa": + return &mock.Package{PName: "python-rsa", PVersion: "4.9-1", PDB: mock.NewDB("extra")} + case "python-s3transfer>=0.3.0", "python-s3transfer": + return &mock.Package{PName: "python-s3transfer", PVersion: "0.6.0-1", PDB: mock.NewDB("extra")} + case "python-yaml>=3.10", "python-yaml": + return &mock.Package{PName: "python-yaml", PVersion: "6.0-1", PDB: mock.NewDB("extra")} + case "python-colorama>=0.2.5", "python-colorama": + return &mock.Package{PName: "python-colorama", PVersion: "0.4.6-1", PDB: mock.NewDB("extra")} + case "python-tox>=2.3.1", "python-tox": + return &mock.Package{PName: "python-tox", PVersion: "4.0.0-1", PDB: mock.NewDB("extra")} + case "python-nose>=1.3.7", "python-nose": + return &mock.Package{PName: "python-nose", PVersion: "1.3.7-1", PDB: mock.NewDB("extra")} + case "python-mock>=1.3.0", "python-mock": + return &mock.Package{PName: "python-mock", PVersion: "5.0.0-1", PDB: mock.NewDB("extra")} + case "python-wheel>=0.24.0", "python-wheel": + return &mock.Package{PName: "python-wheel", PVersion: "0.38.0-1", PDB: mock.NewDB("extra")} + case "python-dateutil>=2.1", "python-dateutil": + return &mock.Package{PName: "python-dateutil", PVersion: "2.8.2-1", PDB: mock.NewDB("extra")} + case "python-sphinx>=1.1.3", "python-sphinx": + return &mock.Package{PName: "python-sphinx", PVersion: "6.0.0-1", PDB: mock.NewDB("extra")} + case "python-distribute": + return &mock.Package{PName: "python-distribute", PVersion: "0.7.3-1", PDB: mock.NewDB("extra")} + case "git": + return &mock.Package{PName: "git", PVersion: "2.39.0-1", PDB: mock.NewDB("extra")} + } + panic("implement me " + s) + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "aws-cli-git": + return false + case "python", "python-botocore>=1.19.35", "python-docutils>=0.10", + "python-rsa>=3.1.2", "python-s3transfer>=0.3.0", "python-yaml>=3.10", + "python-colorama>=0.2.5", "python-tox>=2.3.1", "python-nose>=1.3.7", + "python-mock>=1.3.0", "python-wheel>=0.24.0", "python-dateutil>=2.1", + "python-sphinx>=1.1.3", "python-distribute", "git": + return true + } + panic("implement me " + s) + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + if len(query.Needles) > 0 && query.Needles[0] == "aws-cli-git" { + awsFn := getFromFile(t, "testdata/aws-cli-git.json") + return awsFn(ctx, query) + } + return []aur.Pkg{}, nil + }} + + t.Run("parses aws-cli-git with all its dependencies", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"aws-cli-git"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + require.NotEmpty(t, layers) + require.Contains(t, layers[0], "aws-cli-git") + require.Equal(t, "1.27.145.r11217.g5885ee4dc-1", layers[0]["aws-cli-git"].Version) + require.Equal(t, "aws-cli-git", *layers[0]["aws-cli-git"].AURBase) + require.Equal(t, Explicit, layers[0]["aws-cli-git"].Reason) + require.Equal(t, AUR, layers[0]["aws-cli-git"].Source) + }) + + t.Run("validates provides field for aws-cli", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"aws-cli-git"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + require.NotEmpty(t, layers) + require.Contains(t, layers[0], "aws-cli-git") + }) +} + +// TestGrapher_ReliableSolver_LiriDesktopGit tests the dependency solver +// with complex dependency chains like liri-desktop-git metapackage. +func TestGrapher_ReliableSolver_LiriDesktopGit(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "liri-desktop-git", "liri-shell-git", "liri-settings-git", + "libliri-git", "fluid-git", "liri-cmake-shared-git": + return nil + case "qt5-declarative": + return &mock.Package{PName: "qt5-declarative", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} + case "qt5-quickcontrols2": + return &mock.Package{PName: "qt5-quickcontrols2", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} + case "qt5-svg": + return &mock.Package{PName: "qt5-svg", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} + case "qt5-graphicaleffects": + return &mock.Package{PName: "qt5-graphicaleffects", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} + case "qt5-wayland": + return &mock.Package{PName: "qt5-wayland", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} + case "wayland": + return &mock.Package{PName: "wayland", PVersion: "1.22.0-1", PDB: mock.NewDB("extra")} + case "cmake": + return &mock.Package{PName: "cmake", PVersion: "3.28.0-1", PDB: mock.NewDB("extra")} + case "qt5-tools": + return &mock.Package{PName: "qt5-tools", PVersion: "5.15.10-1", PDB: mock.NewDB("extra")} + case "git": + return &mock.Package{PName: "git", PVersion: "2.43.0-1", PDB: mock.NewDB("extra")} + } + panic("implement me " + s) + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "liri-desktop-git", "liri-shell-git", "liri-settings-git", + "libliri-git", "fluid-git": + return false + case "liri-cmake-shared-git": // makedepend from AUR + return false + case "qt5-declarative", "qt5-quickcontrols2", "qt5-svg", + "qt5-graphicaleffects", "qt5-wayland", "wayland", + "cmake", "qt5-tools", "git": + return true + } + panic("implement me " + s) + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + for _, needle := range query.Needles { + switch needle { + case "liri-desktop-git", "liri-shell-git", "liri-settings-git", + "libliri-git", "fluid-git", "liri-cmake-shared-git": + liriFn := getFromFile(t, "testdata/liri-desktop-git.json") + return liriFn(ctx, query) + } + } + return []aur.Pkg{}, nil + }} + + t.Run("liri-desktop-git pulls all dependencies", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"liri-desktop-git"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + totalPkgs := 0 + for _, layer := range layers { + totalPkgs += len(layer) + } + // 6 packages: liri-desktop-git + 4 deps + liri-cmake-shared-git (makedep) + require.Equal(t, 6, totalPkgs) + require.Contains(t, layers[0], "liri-desktop-git") + require.Equal(t, Explicit, layers[0]["liri-desktop-git"].Reason) + }) + + t.Run("complex dependency chain resolves in correct order", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"liri-desktop-git"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + require.Contains(t, layers[0], "liri-desktop-git") + + allPkgs := make(map[string]bool) + for _, layer := range layers { + for pkg := range layer { + allPkgs[pkg] = true + } + } + require.True(t, allPkgs["liri-desktop-git"]) + require.True(t, allPkgs["liri-shell-git"]) + require.True(t, allPkgs["liri-settings-git"]) + require.True(t, allPkgs["fluid-git"]) + require.True(t, allPkgs["libliri-git"]) + require.True(t, allPkgs["liri-cmake-shared-git"]) // makedepend + }) +} + +// TestGrapher_SplitPackages_Clion tests split packages where multiple packages +// come from the same package base, ensuring no rebuilding or reinstalling multiple times. +func TestGrapher_SplitPackages_Clion(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb": + return nil + case "libdbusmenu-glib": + return &mock.Package{PName: "libdbusmenu-glib", PVersion: "16.04.0-5", PDB: mock.NewDB("extra")} + case "rsync": + return &mock.Package{PName: "rsync", PVersion: "3.2.7-1", PDB: mock.NewDB("extra")} + case "glibc": + return &mock.Package{PName: "glibc", PVersion: "2.38-1", PDB: mock.NewDB("core")} + case "gcc-libs": + return &mock.Package{PName: "gcc-libs", PVersion: "13.2.1-1", PDB: mock.NewDB("core")} + case "python": + return &mock.Package{PName: "python", PVersion: "3.11.0-1", PDB: mock.NewDB("core")} + } + panic("implement me " + s) + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb": + return false + case "libdbusmenu-glib", "rsync", "glibc", "gcc-libs", "python": + return true + } + panic("implement me " + s) + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + for _, needle := range query.Needles { + switch needle { + case "clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb": + clionFn := getFromFile(t, "testdata/clion.json") + return clionFn(ctx, query) + } + } + return []aur.Pkg{}, nil + }} + + installInfos := map[string]*InstallInfo{ + "clion exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, + "clion-jre exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, + "clion-cmake exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, + "clion-gdb exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, + "clion-lldb exp": {Source: AUR, Reason: Explicit, Version: "2025.3.1.1-1", AURBase: ptrString("clion")}, + } + + tests := []struct { + name string + targets []string + wantLayers []map[string]*InstallInfo + }{ + { + name: "single clion package", + targets: []string{"clion"}, + wantLayers: []map[string]*InstallInfo{ + {"clion": installInfos["clion exp"]}, + }, + }, + { + name: "clion with clion-jre", + targets: []string{"clion", "clion-jre"}, + wantLayers: []map[string]*InstallInfo{ + {"clion": installInfos["clion exp"], "clion-jre": installInfos["clion-jre exp"]}, + }, + }, + { + name: "all clion packages from same base", + targets: []string{"clion", "clion-jre", "clion-cmake", "clion-gdb", "clion-lldb"}, + wantLayers: []map[string]*InstallInfo{ + { + "clion": installInfos["clion exp"], + "clion-jre": installInfos["clion-jre exp"], + "clion-cmake": installInfos["clion-cmake exp"], + "clion-gdb": installInfos["clion-gdb exp"], + "clion-lldb": installInfos["clion-lldb exp"], + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + require.EqualValues(t, tt.wantLayers, layers, layers) + }) + } + + t.Run("packages from same base share AURBase", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, + []string{"clion", "clion-jre", "clion-cmake"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + require.Len(t, layers, 1) + for _, info := range layers[0] { + require.NotNil(t, info.AURBase) + require.Equal(t, "clion", *info.AURBase) + } + }) +} + +// TestGrapher_SplitPackages_SamsungUnifiedDriver tests split packages where +// packages depend on another package from the same package base. +func TestGrapher_SplitPackages_SamsungUnifiedDriver(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "samsung-unified-driver", "samsung-unified-driver-common", + "samsung-unified-driver-printer", "samsung-unified-driver-scanner": + return nil + case "cups": + return &mock.Package{PName: "cups", PVersion: "2.4.2-1", PDB: mock.NewDB("extra")} + case "ghostscript": + return &mock.Package{PName: "ghostscript", PVersion: "10.02.0-1", PDB: mock.NewDB("extra")} + case "libxml2-legacy": + return &mock.Package{PName: "libxml2-legacy", PVersion: "2.10.3-1", PDB: mock.NewDB("extra")} + case "libusb-compat": + return &mock.Package{PName: "libusb-compat", PVersion: "0.1.8-1", PDB: mock.NewDB("extra")} + case "sane": + return &mock.Package{PName: "sane", PVersion: "1.2.1-1", PDB: mock.NewDB("extra")} + } + panic("implement me " + s) + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "samsung-unified-driver", "samsung-unified-driver-common", + "samsung-unified-driver-printer", "samsung-unified-driver-scanner": + return false + case "cups", "ghostscript", "libxml2-legacy", "libusb-compat", "sane": + return true + } + panic("implement me " + s) + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + for _, needle := range query.Needles { + switch needle { + case "samsung-unified-driver", "samsung-unified-driver-common", + "samsung-unified-driver-printer", "samsung-unified-driver-scanner": + samsungFn := getFromFile(t, "testdata/samsung-unified-driver.json") + return samsungFn(ctx, query) + } + } + return []aur.Pkg{}, nil + }} + + tests := []struct { + name string + targets []string + wantPkgCount int + wantContains []string + wantNotContain []string + }{ + { + name: "metapackage samsung-unified-driver pulls printer and scanner", + targets: []string{"samsung-unified-driver"}, + wantPkgCount: 4, + wantContains: []string{ + "samsung-unified-driver", + "samsung-unified-driver-printer", + "samsung-unified-driver-scanner", + "samsung-unified-driver-common", + }, + }, + { + name: "printer alone pulls common", + targets: []string{"samsung-unified-driver-printer"}, + wantPkgCount: 2, + wantContains: []string{"samsung-unified-driver-printer", "samsung-unified-driver-common"}, + wantNotContain: []string{"samsung-unified-driver-scanner"}, + }, + { + name: "scanner alone pulls common", + targets: []string{"samsung-unified-driver-scanner"}, + wantPkgCount: 2, + wantContains: []string{"samsung-unified-driver-scanner", "samsung-unified-driver-common"}, + wantNotContain: []string{"samsung-unified-driver-printer"}, + }, + { + name: "common alone", + targets: []string{"samsung-unified-driver-common"}, + wantPkgCount: 1, + wantContains: []string{"samsung-unified-driver-common"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + allPkgs := make(map[string]bool) + for _, layer := range layers { + for pkg := range layer { + allPkgs[pkg] = true + } + } + require.Equal(t, tt.wantPkgCount, len(allPkgs)) + + for _, pkg := range tt.wantContains { + require.True(t, allPkgs[pkg], "expected package %s not found", pkg) + } + + for _, pkg := range tt.wantNotContain { + require.False(t, allPkgs[pkg], "unexpected package %s found", pkg) + } + }) + } + + t.Run("split package internal deps resolved correctly", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"samsung-unified-driver"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + lastLayer := layers[len(layers)-1] + require.Contains(t, lastLayer, "samsung-unified-driver-common") + + for _, layer := range layers { + for _, info := range layer { + require.NotNil(t, info.AURBase) + require.Equal(t, "samsung-unified-driver", *info.AURBase) + } + } + }) +} + +// TestGrapher_SplitPackages_NX tests independent split packages like nxproxy and nxagent. +func TestGrapher_SplitPackages_NX(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "nxproxy", "nxagent", "nx-x11", "libxcomp": + return nil + case "libjpeg-turbo": + return &mock.Package{PName: "libjpeg-turbo", PVersion: "2.1.5-1", PDB: mock.NewDB("extra")} + case "libpng": + return &mock.Package{PName: "libpng", PVersion: "1.6.39-1", PDB: mock.NewDB("extra")} + case "gcc-libs": + return &mock.Package{PName: "gcc-libs", PVersion: "13.2.1-1", PDB: mock.NewDB("core")} + case "libxml2": + return &mock.Package{PName: "libxml2", PVersion: "2.11.0-1", PDB: mock.NewDB("extra")} + case "xkeyboard-config": + return &mock.Package{PName: "xkeyboard-config", PVersion: "2.38-1", PDB: mock.NewDB("extra")} + case "xorg-xkbcomp": + return &mock.Package{PName: "xorg-xkbcomp", PVersion: "1.4.6-1", PDB: mock.NewDB("extra")} + case "libxfont2": + return &mock.Package{PName: "libxfont2", PVersion: "2.0.6-1", PDB: mock.NewDB("extra")} + case "libxinerama": + return &mock.Package{PName: "libxinerama", PVersion: "1.1.5-1", PDB: mock.NewDB("extra")} + case "xorg-font-util": + return &mock.Package{PName: "xorg-font-util", PVersion: "1.4.0-1", PDB: mock.NewDB("extra")} + case "pixman": + return &mock.Package{PName: "pixman", PVersion: "0.42.2-1", PDB: mock.NewDB("extra")} + case "libxrandr": + return &mock.Package{PName: "libxrandr", PVersion: "1.5.3-1", PDB: mock.NewDB("extra")} + case "libxtst": + return &mock.Package{PName: "libxtst", PVersion: "1.2.4-1", PDB: mock.NewDB("extra")} + case "libxcomposite": + return &mock.Package{PName: "libxcomposite", PVersion: "0.4.6-1", PDB: mock.NewDB("extra")} + case "libxpm": + return &mock.Package{PName: "libxpm", PVersion: "3.5.16-1", PDB: mock.NewDB("extra")} + case "libxdamage": + return &mock.Package{PName: "libxdamage", PVersion: "1.1.6-1", PDB: mock.NewDB("extra")} + case "libtirpc": + return &mock.Package{PName: "libtirpc", PVersion: "1.3.3-1", PDB: mock.NewDB("extra")} + case "xorgproto": + return &mock.Package{PName: "xorgproto", PVersion: "2023.2-1", PDB: mock.NewDB("extra")} + case "imake": + return &mock.Package{PName: "imake", PVersion: "1.0.9-1", PDB: mock.NewDB("extra")} + } + panic("implement me " + s) + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "nxproxy", "nxagent", "nx-x11", "libxcomp": + return false + case "libjpeg-turbo", "libpng", "gcc-libs", "libxml2", + "xkeyboard-config", "xorg-xkbcomp", "libxfont2", "libxinerama", + "xorg-font-util", "pixman", "libxrandr", "libxtst", + "libxcomposite", "libxpm", "libxdamage", "libtirpc", + "xorgproto", "imake": + return true + } + panic("implement me " + s) + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + for _, needle := range query.Needles { + switch needle { + case "nxproxy", "nxagent", "nx-x11", "libxcomp": + nxFn := getFromFile(t, "testdata/nx.json") + return nxFn(ctx, query) + } + } + return []aur.Pkg{}, nil + }} + + installInfos := map[string]*InstallInfo{ + "nxproxy exp": {Source: AUR, Reason: Explicit, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, + "nxagent exp": {Source: AUR, Reason: Explicit, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, + "nx-x11 dep": {Source: AUR, Reason: Dep, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, + "libxcomp dep": {Source: AUR, Reason: Dep, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, + "libxcomp exp": {Source: AUR, Reason: Explicit, Version: "3.5.99.27-3", AURBase: ptrString("nx")}, + } + + tests := []struct { + name string + targets []string + wantLayers []map[string]*InstallInfo + }{ + { + name: "nxproxy independently", + targets: []string{"nxproxy"}, + wantLayers: []map[string]*InstallInfo{ + {"nxproxy": installInfos["nxproxy exp"]}, + {"libxcomp": installInfos["libxcomp dep"]}, + }, + }, + { + name: "nxagent independently - has more deps", + targets: []string{"nxagent"}, + wantLayers: []map[string]*InstallInfo{ + {"nxagent": installInfos["nxagent exp"]}, + {"nx-x11": installInfos["nx-x11 dep"]}, + {"libxcomp": installInfos["libxcomp dep"]}, + }, + }, + { + name: "both nxproxy and nxagent", + targets: []string{"nxproxy", "nxagent"}, + wantLayers: []map[string]*InstallInfo{ + {"nxproxy": installInfos["nxproxy exp"], "nxagent": installInfos["nxagent exp"]}, + {"nx-x11": installInfos["nx-x11 dep"]}, + {"libxcomp": installInfos["libxcomp dep"]}, + }, + }, + { + name: "libxcomp independently", + targets: []string{"libxcomp"}, + wantLayers: []map[string]*InstallInfo{ + {"libxcomp": installInfos["libxcomp exp"]}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + require.EqualValues(t, tt.wantLayers, layers, layers) + }) + } + + t.Run("split packages share AURBase but can be installed independently", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + + got1, err := g.GraphFromTargets(context.Background(), nil, []string{"nxproxy"}) + require.NoError(t, err) + layers1 := got1.TopoSortedLayers(nil) + + got2, err := g.GraphFromTargets(context.Background(), nil, []string{"nxagent"}) + require.NoError(t, err) + layers2 := got2.TopoSortedLayers(nil) + + require.Equal(t, "nx", *layers1[0]["nxproxy"].AURBase) + require.Equal(t, "nx", *layers2[0]["nxagent"].AURBase) + + totalPkgs1 := 0 + for _, layer := range layers1 { + totalPkgs1 += len(layer) + } + totalPkgs2 := 0 + for _, layer := range layers2 { + totalPkgs2 += len(layer) + } + + require.Equal(t, 2, totalPkgs1) + require.Equal(t, 3, totalPkgs2) + }) +} + +// TestGrapher_SplitPackages_ReversedOrder tests that split packages resolve +// correctly regardless of the order they are specified in. +func TestGrapher_SplitPackages_ReversedOrder(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "nxproxy", "nxagent", "nx-x11", "libxcomp": + return nil + default: + return &mock.Package{PName: s, PVersion: "1.0.0-1", PDB: mock.NewDB("extra")} + } + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "nxproxy", "nxagent", "nx-x11", "libxcomp": + return false + default: + return true + } + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + for _, needle := range query.Needles { + switch needle { + case "nxproxy", "nxagent", "nx-x11", "libxcomp": + nxFn := getFromFile(t, "testdata/nx.json") + return nxFn(ctx, query) + } + } + return []aur.Pkg{}, nil + }} + + t.Run("nxproxy nxagent order", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"nxproxy", "nxagent"}) + require.NoError(t, err) + layers1 := got.TopoSortedLayers(nil) + + require.Contains(t, layers1[0], "nxproxy") + require.Contains(t, layers1[0], "nxagent") + }) + + t.Run("nxagent nxproxy reversed order", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"nxagent", "nxproxy"}) + require.NoError(t, err) + layers2 := got.TopoSortedLayers(nil) + + require.Contains(t, layers2[0], "nxproxy") + require.Contains(t, layers2[0], "nxagent") + }) +} + +// TestGrapher_MultipleInstallInfo ensures that when the same package appears as +// both explicit target and dependency, the explicit reason takes precedence. +func TestGrapher_MultipleInstallInfo(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "samsung-unified-driver", "samsung-unified-driver-common", + "samsung-unified-driver-printer", "samsung-unified-driver-scanner": + return nil + default: + return &mock.Package{PName: s, PVersion: "1.0.0-1", PDB: mock.NewDB("extra")} + } + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "samsung-unified-driver", "samsung-unified-driver-common", + "samsung-unified-driver-printer", "samsung-unified-driver-scanner": + return false + default: + return true + } + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + for _, needle := range query.Needles { + switch needle { + case "samsung-unified-driver", "samsung-unified-driver-common", + "samsung-unified-driver-printer", "samsung-unified-driver-scanner": + samsungFn := getFromFile(t, "testdata/samsung-unified-driver.json") + return samsungFn(ctx, query) + } + } + return []aur.Pkg{}, nil + }} + + t.Run("explicit target takes precedence over dependency", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + + got, err := g.GraphFromTargets(context.Background(), nil, + []string{"samsung-unified-driver", "samsung-unified-driver-common"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + found := false + for _, layer := range layers { + if info, ok := layer["samsung-unified-driver-common"]; ok { + found = true + require.Equal(t, Explicit, info.Reason, + "explicit target should have Explicit reason, not Dep") + } + } + require.True(t, found, "samsung-unified-driver-common should be in layers") + }) +} + +// TestGrapher_VersionedDependencies tests proper handling of versioned dependencies. +func TestGrapher_VersionedDependencies(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "versioned-pkg", "dep-pkg", "dep-pkg>=2.0.0": + return nil + } + panic(fmt.Sprintf("implement me: %s", s)) + }, + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "versioned-pkg", "dep-pkg", "dep-pkg>=2.0.0": + return false + } + return true + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + mockPkgs := map[string]aur.Pkg{ + "versioned-pkg": { + Name: "versioned-pkg", + PackageBase: "versioned-pkg", + Version: "1.0.0-1", + Depends: []string{"dep-pkg>=2.0.0"}, + }, + "dep-pkg": { + Name: "dep-pkg", + PackageBase: "dep-pkg", + Version: "2.5.0-1", + }, + } + + pkgs := []aur.Pkg{} + for _, needle := range query.Needles { + if pkg, ok := mockPkgs[needle]; ok { + pkgs = append(pkgs, pkg) + } + } + return pkgs, nil + }} + + t.Run("versioned dependency satisfied by higher version", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"versioned-pkg"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + require.Len(t, layers, 2) + require.Contains(t, layers[0], "versioned-pkg") + require.Contains(t, layers[1], "dep-pkg") + require.Equal(t, "2.5.0-1", layers[1]["dep-pkg"].Version) + }) +} diff --git a/pkg/dep/dep_graph_test.go b/pkg/dep/dep_graph_test.go index cf460cf7..e8ebddd4 100644 --- a/pkg/dep/dep_graph_test.go +++ b/pkg/dep/dep_graph_test.go @@ -14,7 +14,7 @@ import ( "testing" aurc "github.com/Jguer/aur" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" mapset "github.com/deckarep/golang-set/v2" "github.com/stretchr/testify/require" @@ -29,7 +29,8 @@ func ptrString(s string) *string { return &s } -func getFromFile(t *testing.T, filePath string) mockaur.GetFunc { +func getFromFile(t testing.TB, filePath string) mockaur.GetFunc { + t.Helper() f, err := os.Open(filePath) require.NoError(t, err) @@ -254,9 +255,9 @@ func TestGrapher_GraphProvides_androidsdk(t *testing.T) { PDB: mock.NewDB("community"), PProvides: mock.DependList{ Depends: []alpm.Depend{ - {Name: "java-environment", Version: "11", Mod: alpm.DepModEq}, - {Name: "java-environment-openjdk", Version: "11", Mod: alpm.DepModEq}, - {Name: "jdk11-openjdk", Version: "11.0.19.u7-1", Mod: alpm.DepModEq}, + {Name: "java-environment", Version: "11", Mod: alpm.DepModEQ}, + {Name: "java-environment-openjdk", Version: "11", Mod: alpm.DepModEQ}, + {Name: "jdk11-openjdk", Version: "11.0.19.u7-1", Mod: alpm.DepModEQ}, }, }, } @@ -904,3 +905,376 @@ func TestGrapher_GraphFromTargets_TargetNotFound(t *testing.T) { }, layers, layers) }) } + +// TestGrapher_GraphFromAUR_SplitPkgInternalDeps tests split packages where +// packages from the same base depend on each other (like gstreamer-git). +func TestGrapher_GraphFromAUR_SplitPkgInternalDeps(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + // AUR packages and versioned AUR deps return nil + case "gstreamer-git", "gst-plugins-base-libs-git", "gst-plugins-good-git", + "gstreamer-git=1.24.0.r37-1", "gst-plugins-base-libs-git=1.24.0.r37-1": + return nil + case "libxml2": + return &mock.Package{ + PName: "libxml2", + PVersion: "2.12.0-1", + PDB: mock.NewDB("core"), + } + case "glib2": + return &mock.Package{ + PName: "glib2", + PVersion: "2.78.0-1", + PDB: mock.NewDB("core"), + } + case "orc": + return &mock.Package{ + PName: "orc", + PVersion: "0.4.34-1", + PDB: mock.NewDB("extra"), + } + case "libxv": + return &mock.Package{ + PName: "libxv", + PVersion: "1.0.12-1", + PDB: mock.NewDB("extra"), + } + case "iso-codes": + return &mock.Package{ + PName: "iso-codes", + PVersion: "4.15.0-1", + PDB: mock.NewDB("extra"), + } + case "libpulse": + return &mock.Package{ + PName: "libpulse", + PVersion: "16.1-1", + PDB: mock.NewDB("extra"), + } + case "wavpack": + return &mock.Package{ + PName: "wavpack", + PVersion: "5.6.0-1", + PDB: mock.NewDB("extra"), + } + } + + panic("implement me " + s) + }, + + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "gstreamer-git", "gstreamer-git=1.24.0.r37-1", + "gst-plugins-base-libs-git", "gst-plugins-base-libs-git=1.24.0.r37-1", + "gst-plugins-good-git": + return false + case "libxml2", "glib2", "orc", "libxv", "iso-codes", "libpulse", "wavpack", + "git", "meson", "ninja": // makedepends + return true + } + + panic("implement me " + s) + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + if len(query.Needles) > 0 { + for _, needle := range query.Needles { + if needle == "gstreamer-git" || needle == "gst-plugins-base-libs-git" || needle == "gst-plugins-good-git" { + gstFn := getFromFile(t, "testdata/gstreamer-git.json") + return gstFn(ctx, query) + } + } + } + + return []aur.Pkg{}, nil // Return empty for unknown packages + }} + + installInfos := map[string]*InstallInfo{ + "gstreamer-git exp": { + Source: AUR, + Reason: Explicit, + Version: "1.24.0.r37-1", + AURBase: ptrString("gstreamer-git"), + }, + "gstreamer-git dep": { + Source: AUR, + Reason: Dep, + Version: "1.24.0.r37-1", + AURBase: ptrString("gstreamer-git"), + }, + "gst-plugins-base-libs-git exp": { + Source: AUR, + Reason: Explicit, + Version: "1.24.0.r37-1", + AURBase: ptrString("gstreamer-git"), + }, + "gst-plugins-base-libs-git dep": { + Source: AUR, + Reason: Dep, + Version: "1.24.0.r37-1", + AURBase: ptrString("gstreamer-git"), + }, + "gst-plugins-good-git exp": { + Source: AUR, + Reason: Explicit, + Version: "1.24.0.r37-1", + AURBase: ptrString("gstreamer-git"), + }, + } + + tests := []struct { + name string + targets []string + wantLayers []map[string]*InstallInfo + wantErr bool + }{ + { + name: "gst-plugins-good-git pulls in base libs and gstreamer", + targets: []string{"gst-plugins-good-git"}, + wantLayers: []map[string]*InstallInfo{ + {"gst-plugins-good-git": installInfos["gst-plugins-good-git exp"]}, + {"gst-plugins-base-libs-git": installInfos["gst-plugins-base-libs-git dep"]}, + {"gstreamer-git": installInfos["gstreamer-git dep"]}, + }, + wantErr: false, + }, + { + name: "gst-plugins-base-libs-git pulls in gstreamer", + targets: []string{"gst-plugins-base-libs-git"}, + wantLayers: []map[string]*InstallInfo{ + {"gst-plugins-base-libs-git": installInfos["gst-plugins-base-libs-git exp"]}, + {"gstreamer-git": installInfos["gstreamer-git dep"]}, + }, + wantErr: false, + }, + { + name: "explicit gstreamer-git with gst-plugins-good-git", + targets: []string{"gstreamer-git", "gst-plugins-good-git"}, + wantLayers: []map[string]*InstallInfo{ + {"gst-plugins-good-git": installInfos["gst-plugins-good-git exp"]}, + {"gst-plugins-base-libs-git": installInfos["gst-plugins-base-libs-git dep"]}, + {"gstreamer-git": installInfos["gstreamer-git exp"]}, + }, + wantErr: false, + }, + { + name: "all three packages explicitly", + targets: []string{"gstreamer-git", "gst-plugins-base-libs-git", "gst-plugins-good-git"}, + wantLayers: []map[string]*InstallInfo{ + {"gst-plugins-good-git": installInfos["gst-plugins-good-git exp"]}, + {"gst-plugins-base-libs-git": installInfos["gst-plugins-base-libs-git exp"]}, + {"gstreamer-git": installInfos["gstreamer-git exp"]}, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, + false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, tt.targets) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + require.EqualValues(t, tt.wantLayers, layers, layers) + }) + } +} + +// TestGrapher_GraphFromAUR_CheckDeps tests packages with CheckDepends. +func TestGrapher_GraphFromAUR_CheckDeps(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "python-pydantic": + return nil + case "python": + return &mock.Package{ + PName: "python", + PVersion: "3.11.0-1", + PDB: mock.NewDB("core"), + } + case "python-typing-extensions": + return &mock.Package{ + PName: "python-typing-extensions", + PVersion: "4.8.0-1", + PDB: mock.NewDB("extra"), + } + case "python-build": + return &mock.Package{ + PName: "python-build", + PVersion: "1.0.0-1", + PDB: mock.NewDB("extra"), + } + case "python-installer": + return &mock.Package{ + PName: "python-installer", + PVersion: "0.7.0-1", + PDB: mock.NewDB("extra"), + } + case "python-pytest": + return &mock.Package{ + PName: "python-pytest", + PVersion: "7.4.0-1", + PDB: mock.NewDB("extra"), + } + case "python-pytest-mock": + return &mock.Package{ + PName: "python-pytest-mock", + PVersion: "3.11.0-1", + PDB: mock.NewDB("extra"), + } + } + + panic("implement me " + s) + }, + + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "python-pydantic", + "python-pytest", "python-pytest-mock": // check deps not installed + return false + case "python", "python-typing-extensions", "python-build", "python-installer": + return true + } + + panic("implement me " + s) + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + if len(query.Needles) > 0 && query.Needles[0] == "python-pydantic" { + pydanticFn := getFromFile(t, "testdata/python-pydantic.json") + return pydanticFn(ctx, query) + } + + return []aur.Pkg{}, nil // Return empty for unknown packages + }} + + t.Run("with check deps enabled", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, + false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"python-pydantic"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + // Should have the main package and its check deps + require.Len(t, layers, 2) + require.Contains(t, layers[0], "python-pydantic") + // Check deps should be in the second layer + require.Contains(t, layers[1], "python-pytest") + require.Contains(t, layers[1], "python-pytest-mock") + }) + + t.Run("with check deps disabled", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, + false, true, false, true, false, // noCheckDeps = true + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"python-pydantic"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + // Should only have the main package (no check deps) + require.Len(t, layers, 1) + require.Contains(t, layers[0], "python-pydantic") + }) +} + +// TestGrapher_GraphFromAUR_VirtualProvides tests packages that provide virtual packages +// (like mesa-git providing vulkan-driver, opengl-driver). +func TestGrapher_GraphFromAUR_VirtualProvides(t *testing.T) { + mockDB := &mock.DBExecutor{ + SyncPackageFn: func(string) mock.IPackage { return nil }, + PackagesFromGroupFn: func(string) []mock.IPackage { return []mock.IPackage{} }, + SyncSatisfierFn: func(s string) mock.IPackage { + switch s { + case "mesa-git": + return nil + case "libdrm": + return &mock.Package{ + PName: "libdrm", + PVersion: "2.4.117-1", + PDB: mock.NewDB("core"), + } + case "vulkan-icd-loader": + return &mock.Package{ + PName: "vulkan-icd-loader", + PVersion: "1.3.268-1", + PDB: mock.NewDB("extra"), + } + case "vulkan-radeon": + return &mock.Package{ + PName: "vulkan-radeon", + PVersion: "23.3.0-1", + PDB: mock.NewDB("extra"), + PProvides: mock.DependList{ + Depends: []alpm.Depend{ + {Name: "vulkan-driver", Version: "", Mod: alpm.DepModAny}, + }, + }, + } + } + + // Most mesa deps are already installed + switch s { + case "libxxf86vm", "libxdamage", "libxshmfence", "libelf", "libunwind", + "libglvnd", "wayland", "lm_sensors", "zstd", "expat": + return &mock.Package{ + PName: s, + PVersion: "1.0.0-1", + PDB: mock.NewDB("extra"), + } + } + + panic("implement me " + s) + }, + + LocalSatisfierExistsFn: func(s string) bool { + switch s { + case "mesa-git", "vulkan-driver", "opengl-driver": + return false + case "libdrm", "libxxf86vm", "libxdamage", "libxshmfence", "libelf", + "libunwind", "libglvnd", "wayland", "lm_sensors", "vulkan-icd-loader", + "zstd", "expat", + "git", "meson", "ninja", "llvm", "clang": // makedepends + return true + } + + panic("implement me " + s) + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + mockAUR := &mockaur.MockAUR{GetFn: func(ctx context.Context, query *aurc.Query) ([]aur.Pkg, error) { + if len(query.Needles) > 0 && query.Needles[0] == "mesa-git" { + mesaFn := getFromFile(t, "testdata/mesa-git.json") + return mesaFn(ctx, query) + } + + return []aur.Pkg{}, nil // Return empty for unknown packages + }} + + t.Run("mesa-git provides vulkan-driver and opengl-driver", func(t *testing.T) { + g := NewGrapher(mockDB, mockAUR, + false, true, false, false, false, + text.NewLogger(io.Discard, io.Discard, &os.File{}, true, "test")) + got, err := g.GraphFromTargets(context.Background(), nil, []string{"mesa-git"}) + require.NoError(t, err) + layers := got.TopoSortedLayers(nil) + + require.Len(t, layers, 1) + require.Contains(t, layers[0], "mesa-git") + require.Equal(t, "24.0.0.r1234-1", layers[0]["mesa-git"].Version) + require.Equal(t, "mesa-git", *layers[0]["mesa-git"].AURBase) + }) +} diff --git a/pkg/dep/testdata/aws-cli-git.json b/pkg/dep/testdata/aws-cli-git.json new file mode 100644 index 00000000..54ea5b23 --- /dev/null +++ b/pkg/dep/testdata/aws-cli-git.json @@ -0,0 +1,3 @@ +[ + {"ID":1266542,"Name":"aws-cli-git","PackageBaseID":75029,"PackageBase":"aws-cli-git","Version":"1.27.145.r11217.g5885ee4dc-1","Description":"Universal Command Line Interface for Amazon Web Services awscli","URL":"https://github.com/aws/aws-cli","NumVotes":8,"Popularity":0.0,"OutOfDate":1710077383,"Maintainer":null,"Submitter":"flaccid","FirstSubmitted":1383449501,"LastModified":1685666227,"URLPath":"/cgit/aur.git/snapshot/aws-cli-git.tar.gz","Depends":["python","python-botocore>=1.19.35","python-docutils>=0.10","python-rsa>=3.1.2","python-s3transfer>=0.3.0","python-yaml>=3.10","python-colorama>=0.2.5","python-tox>=2.3.1","python-nose>=1.3.7","python-mock>=1.3.0","python-wheel>=0.24.0","python-dateutil>=2.1","python-sphinx>=1.1.3"],"MakeDepends":["python","python-distribute","git"],"Conflicts":["python2-aws-cli","python-aws-cli","aws-cli"],"Provides":["aws-cli=1.27.145"],"License":["Apache"]} +] diff --git a/pkg/dep/testdata/clion.json b/pkg/dep/testdata/clion.json new file mode 100644 index 00000000..d27567c6 --- /dev/null +++ b/pkg/dep/testdata/clion.json @@ -0,0 +1,7 @@ +[ + {"ID":1937213,"Name":"clion","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"Cross-platform IDE for C and C++ from JetBrains.","URL":"http://www.jetbrains.com/clion","NumVotes":239,"Popularity":1.760009,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["libdbusmenu-glib"],"MakeDepends":["rsync"],"OptDepends":["clion-jre","clion-cmake","clion-gdb","clion-lldb","java-runtime","cmake","gdb","lldb","gcc","clang","gtest","python","doxygen"],"License":["custom"]}, + {"ID":1937214,"Name":"clion-jre","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"JBR (JetBrains Runtime) for CLion - aass/JVM bundle based on OpenJDK","URL":"http://www.jetbrains.com/clion","NumVotes":50,"Popularity":0.5,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["glibc","gcc-libs"],"MakeDepends":["rsync"],"Provides":["java-runtime"],"License":["custom"]}, + {"ID":1937215,"Name":"clion-cmake","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"CMake for CLion IDE","URL":"http://www.jetbrains.com/clion","NumVotes":30,"Popularity":0.3,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["glibc"],"MakeDepends":["rsync"],"Provides":["cmake"],"Conflicts":["cmake"],"License":["custom"]}, + {"ID":1937216,"Name":"clion-gdb","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"GDB for CLion IDE","URL":"http://www.jetbrains.com/clion","NumVotes":25,"Popularity":0.25,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["glibc","python"],"MakeDepends":["rsync"],"Provides":["gdb"],"Conflicts":["gdb"],"License":["custom"]}, + {"ID":1937217,"Name":"clion-lldb","PackageBaseID":92202,"PackageBase":"clion","Version":"2025.3.1.1-1","Description":"LLDB for CLion IDE","URL":"http://www.jetbrains.com/clion","NumVotes":20,"Popularity":0.2,"OutOfDate":null,"Maintainer":"Zrax","Submitter":"rdoursenaud","FirstSubmitted":1429046635,"LastModified":1768325120,"URLPath":"/cgit/aur.git/snapshot/clion.tar.gz","Depends":["glibc","python"],"MakeDepends":["rsync"],"Provides":["lldb"],"Conflicts":["lldb"],"License":["custom"]} +] diff --git a/pkg/dep/testdata/gstreamer-git.json b/pkg/dep/testdata/gstreamer-git.json new file mode 100644 index 00000000..440fd2f2 --- /dev/null +++ b/pkg/dep/testdata/gstreamer-git.json @@ -0,0 +1,5 @@ +[ + {"ID":1000001,"Name":"gstreamer-git","PackageBaseID":100001,"PackageBase":"gstreamer-git","Version":"1.24.0.r37-1","Description":"GStreamer open-source multimedia framework core library (git version)","URL":"https://gstreamer.freedesktop.org/","NumVotes":10,"Popularity":0.5,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/gstreamer-git.tar.gz","Depends":["libxml2","glib2"],"MakeDepends":["git","meson","ninja"],"Provides":["gstreamer=1.24.0"],"Conflicts":["gstreamer"],"License":["LGPL"]}, + {"ID":1000002,"Name":"gst-plugins-base-libs-git","PackageBaseID":100001,"PackageBase":"gstreamer-git","Version":"1.24.0.r37-1","Description":"GStreamer base plugins libraries (git version)","URL":"https://gstreamer.freedesktop.org/","NumVotes":10,"Popularity":0.5,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/gstreamer-git.tar.gz","Depends":["gstreamer-git=1.24.0.r37-1","orc","libxv","iso-codes"],"MakeDepends":["git","meson","ninja"],"Provides":["gst-plugins-base-libs=1.24.0"],"Conflicts":["gst-plugins-base-libs"],"License":["LGPL"]}, + {"ID":1000003,"Name":"gst-plugins-good-git","PackageBaseID":100001,"PackageBase":"gstreamer-git","Version":"1.24.0.r37-1","Description":"GStreamer good plugins (git version)","URL":"https://gstreamer.freedesktop.org/","NumVotes":10,"Popularity":0.5,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/gstreamer-git.tar.gz","Depends":["gst-plugins-base-libs-git=1.24.0.r37-1","libpulse","wavpack"],"MakeDepends":["git","meson","ninja"],"Provides":["gst-plugins-good=1.24.0"],"Conflicts":["gst-plugins-good"],"License":["LGPL"]} +] diff --git a/pkg/dep/testdata/liri-desktop-git.json b/pkg/dep/testdata/liri-desktop-git.json new file mode 100644 index 00000000..fc9c3f85 --- /dev/null +++ b/pkg/dep/testdata/liri-desktop-git.json @@ -0,0 +1,8 @@ +[ + {"ID":2000001,"Name":"liri-desktop-git","PackageBaseID":200001,"PackageBase":"liri-desktop-git","Version":"0.1.0.r50-1","Description":"Liri Desktop metapackage","URL":"https://liri.io","NumVotes":10,"Popularity":0.1,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/liri-desktop-git.tar.gz","Depends":["liri-shell-git","liri-settings-git","fluid-git","libliri-git"],"MakeDepends":["git"],"License":["GPL3"]}, + {"ID":2000002,"Name":"liri-shell-git","PackageBaseID":200002,"PackageBase":"liri-shell-git","Version":"0.9.0.r100-1","Description":"Liri Shell - Wayland compositor","URL":"https://liri.io","NumVotes":8,"Popularity":0.08,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/liri-shell-git.tar.gz","Depends":["fluid-git","libliri-git","qt5-wayland","wayland"],"MakeDepends":["git","liri-cmake-shared-git","qt5-tools"],"License":["GPL3"]}, + {"ID":2000003,"Name":"liri-settings-git","PackageBaseID":200003,"PackageBase":"liri-settings-git","Version":"0.9.0.r50-1","Description":"Liri Settings","URL":"https://liri.io","NumVotes":6,"Popularity":0.06,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/liri-settings-git.tar.gz","Depends":["fluid-git","libliri-git","qt5-declarative"],"MakeDepends":["git","liri-cmake-shared-git","qt5-tools"],"License":["GPL3"]}, + {"ID":1191231,"Name":"libliri-git","PackageBaseID":122343,"PackageBase":"libliri-git","Version":"r183.5ebe982-1","Description":"Utilities for Liri Quick applications","URL":"https://liri.io","NumVotes":3,"Popularity":0.0,"OutOfDate":1751932023,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/libliri-git.tar.gz","Depends":["qt5-declarative"],"MakeDepends":["git","liri-cmake-shared-git"],"Conflicts":["libliri"],"Provides":["libliri"],"Replaces":["libliri"],"License":["LGPL3"]}, + {"ID":2000004,"Name":"fluid-git","PackageBaseID":200004,"PackageBase":"fluid-git","Version":"1.3.0.r50-1","Description":"Fluid - Material Design for Qt Quick","URL":"https://liri.io","NumVotes":15,"Popularity":0.15,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/fluid-git.tar.gz","Depends":["qt5-declarative","qt5-quickcontrols2","qt5-svg","qt5-graphicaleffects"],"MakeDepends":["git","liri-cmake-shared-git","qt5-tools"],"Conflicts":["fluid"],"Provides":["fluid"],"License":["MPL2"]}, + {"ID":2000005,"Name":"liri-cmake-shared-git","PackageBaseID":200005,"PackageBase":"liri-cmake-shared-git","Version":"1.0.0.r20-1","Description":"Shared CMake functions and macros for Liri","URL":"https://liri.io","NumVotes":5,"Popularity":0.05,"OutOfDate":null,"Maintainer":"plfiorini","Submitter":"plfiorini","FirstSubmitted":1494775706,"LastModified":1672746933,"URLPath":"/cgit/aur.git/snapshot/liri-cmake-shared-git.tar.gz","Depends":["cmake"],"MakeDepends":["git"],"Conflicts":["liri-cmake-shared"],"Provides":["liri-cmake-shared"],"License":["BSD"]} +] diff --git a/pkg/dep/testdata/mesa-git.json b/pkg/dep/testdata/mesa-git.json new file mode 100644 index 00000000..a27c4bb8 --- /dev/null +++ b/pkg/dep/testdata/mesa-git.json @@ -0,0 +1,3 @@ +[ + {"ID":3000001,"Name":"mesa-git","PackageBaseID":300001,"PackageBase":"mesa-git","Version":"24.0.0.r1234-1","Description":"Open-source implementation of the OpenGL specification (git version)","URL":"https://www.mesa3d.org/","NumVotes":100,"Popularity":3.0,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/mesa-git.tar.gz","Depends":["libdrm","libxxf86vm","libxdamage","libxshmfence","libelf","libunwind","libglvnd","wayland","lm_sensors","vulkan-icd-loader","zstd","expat"],"MakeDepends":["git","meson","ninja","llvm","clang"],"Provides":["mesa=24.0.0","vulkan-intel=24.0.0","vulkan-radeon=24.0.0","vulkan-driver","opengl-driver","mesa-libgl"],"Conflicts":["mesa","vulkan-intel","vulkan-radeon","mesa-libgl"],"License":["MIT"]} +] diff --git a/pkg/dep/testdata/nx.json b/pkg/dep/testdata/nx.json new file mode 100644 index 00000000..cdc53e9b --- /dev/null +++ b/pkg/dep/testdata/nx.json @@ -0,0 +1,6 @@ +[ + {"ID":1313798,"Name":"libxcomp","PackageBaseID":177611,"PackageBase":"nx","Version":"3.5.99.27-3","Description":"NX X compression library","URL":"https://arctica-project.org","NumVotes":4,"Popularity":1e-6,"OutOfDate":null,"Maintainer":"harrietobrien","Submitter":"arojas","FirstSubmitted":1648839853,"LastModified":1693834449,"URLPath":"/cgit/aur.git/snapshot/nx.tar.gz","Depends":["libjpeg-turbo","libpng","gcc-libs"],"MakeDepends":["libjpeg-turbo","libpng","gcc-libs","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","xorgproto","imake"],"License":["GPL"]}, + {"ID":1313799,"Name":"nxproxy","PackageBaseID":177611,"PackageBase":"nx","Version":"3.5.99.27-3","Description":"NX proxy","URL":"https://arctica-project.org","NumVotes":4,"Popularity":1e-6,"OutOfDate":null,"Maintainer":"harrietobrien","Submitter":"arojas","FirstSubmitted":1648839853,"LastModified":1693834449,"URLPath":"/cgit/aur.git/snapshot/nx.tar.gz","Depends":["libxcomp"],"MakeDepends":["libjpeg-turbo","libpng","gcc-libs","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","xorgproto","imake"],"License":["GPL"]}, + {"ID":1313800,"Name":"nx-x11","PackageBaseID":177611,"PackageBase":"nx","Version":"3.5.99.27-3","Description":"NX-X11 lib for the NX framework","URL":"https://arctica-project.org","NumVotes":4,"Popularity":1e-6,"OutOfDate":null,"Maintainer":"harrietobrien","Submitter":"arojas","FirstSubmitted":1648839853,"LastModified":1693834449,"URLPath":"/cgit/aur.git/snapshot/nx.tar.gz","Depends":["libxcomp"],"MakeDepends":["libjpeg-turbo","libpng","gcc-libs","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","xorgproto","imake"],"License":["GPL"]}, + {"ID":1313801,"Name":"nxagent","PackageBaseID":177611,"PackageBase":"nx","Version":"3.5.99.27-3","Description":"NX X server based on Xnest","URL":"https://arctica-project.org","NumVotes":4,"Popularity":1e-6,"OutOfDate":null,"Maintainer":"harrietobrien","Submitter":"arojas","FirstSubmitted":1648839853,"LastModified":1693834449,"URLPath":"/cgit/aur.git/snapshot/nx.tar.gz","Depends":["nx-x11","libxcomp","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","libtirpc"],"MakeDepends":["libjpeg-turbo","libpng","gcc-libs","libxml2","xkeyboard-config","xorg-xkbcomp","libxfont2","libxinerama","xorg-font-util","pixman","libxrandr","libxtst","libxcomposite","libxpm","libxdamage","xorgproto","imake"],"Conflicts":["nx-xcompext"],"License":["GPL"]} +] diff --git a/pkg/dep/testdata/python-pydantic.json b/pkg/dep/testdata/python-pydantic.json new file mode 100644 index 00000000..a47e24fd --- /dev/null +++ b/pkg/dep/testdata/python-pydantic.json @@ -0,0 +1,3 @@ +[ + {"ID":2000001,"Name":"python-pydantic","PackageBaseID":200001,"PackageBase":"python-pydantic","Version":"2.5.0-1","Description":"Data validation using Python type hints","URL":"https://github.com/pydantic/pydantic","NumVotes":50,"Popularity":2.0,"OutOfDate":null,"Maintainer":"testmaint","Submitter":"testmaint","FirstSubmitted":1600000000,"LastModified":1700000000,"URLPath":"/cgit/aur.git/snapshot/python-pydantic.tar.gz","Depends":["python","python-typing-extensions"],"MakeDepends":["python-build","python-installer"],"CheckDepends":["python-pytest","python-pytest-mock"],"License":["MIT"]} +] diff --git a/pkg/dep/testdata/samsung-unified-driver.json b/pkg/dep/testdata/samsung-unified-driver.json new file mode 100644 index 00000000..393c4d97 --- /dev/null +++ b/pkg/dep/testdata/samsung-unified-driver.json @@ -0,0 +1,6 @@ +[ + {"ID":1734381,"Name":"samsung-unified-driver","PackageBaseID":44188,"PackageBase":"samsung-unified-driver","Version":"1.00.39-11","Description":"Samsung Unified Linux Driver for printers and scanners.","URL":"http://www.samsung.com","NumVotes":227,"Popularity":0.119974,"OutOfDate":null,"Maintainer":"christoph.gysin","Submitter":"ptb","FirstSubmitted":1291593695,"LastModified":1747833725,"URLPath":"/cgit/aur.git/snapshot/samsung-unified-driver.tar.gz","Depends":["samsung-unified-driver-printer","samsung-unified-driver-scanner"],"License":["custom:samsung"]}, + {"ID":1734378,"Name":"samsung-unified-driver-common","PackageBaseID":44188,"PackageBase":"samsung-unified-driver","Version":"1.00.39-11","Description":"Samsung Unified Linux Driver (common files)","URL":"http://www.samsung.com","NumVotes":227,"Popularity":0.119974,"OutOfDate":null,"Maintainer":"christoph.gysin","Submitter":"ptb","FirstSubmitted":1291593695,"LastModified":1747833725,"URLPath":"/cgit/aur.git/snapshot/samsung-unified-driver-common.tar.gz","License":["custom:samsung"]}, + {"ID":1734379,"Name":"samsung-unified-driver-printer","PackageBaseID":44188,"PackageBase":"samsung-unified-driver","Version":"1.00.39-11","Description":"Samsung Unified Linux Driver for printers.","URL":"http://www.samsung.com","NumVotes":227,"Popularity":0.119974,"OutOfDate":null,"Maintainer":"christoph.gysin","Submitter":"ptb","FirstSubmitted":1291593695,"LastModified":1747833725,"URLPath":"/cgit/aur.git/snapshot/samsung-unified-driver-printer.tar.gz","Depends":["samsung-unified-driver-common","cups","ghostscript"],"License":["custom:samsung"]}, + {"ID":1734380,"Name":"samsung-unified-driver-scanner","PackageBaseID":44188,"PackageBase":"samsung-unified-driver","Version":"1.00.39-11","Description":"Samsung Unified Linux Driver for scanners.","URL":"http://www.samsung.com","NumVotes":227,"Popularity":0.119974,"OutOfDate":null,"Maintainer":"christoph.gysin","Submitter":"ptb","FirstSubmitted":1291593695,"LastModified":1747833725,"URLPath":"/cgit/aur.git/snapshot/samsung-unified-driver-scanner.tar.gz","Depends":["samsung-unified-driver-common","libxml2-legacy","libusb-compat","sane"],"License":["custom:samsung"]} +] diff --git a/pkg/dep/topo/dep.go b/pkg/dep/topo/dep.go index 65944980..c3bff5b6 100644 --- a/pkg/dep/topo/dep.go +++ b/pkg/dep/topo/dep.go @@ -5,7 +5,7 @@ import ( "maps" "strings" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" ) type ( diff --git a/pkg/download/utils_test.go b/pkg/download/utils_test.go index 43f9dc0f..11cb7d6a 100644 --- a/pkg/download/utils_test.go +++ b/pkg/download/utils_test.go @@ -10,9 +10,10 @@ import ( "github.com/stretchr/testify/assert" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/db" + "github.com/Jguer/yay/v12/pkg/db/mock" "github.com/Jguer/yay/v12/pkg/settings/exe" ) @@ -53,14 +54,12 @@ func (c *testGitBuilder) Capture(cmd *exec.Cmd) (stdout, stderr string, err erro type ( testDB struct { - alpm.IDB + alpm.Database name string } testPackage struct { - db.IPackage - name string - base string - db *testDB + *mock.Package + db *testDB } testDBSearcher struct { absPackagesDB map[string]string @@ -78,24 +77,18 @@ func (d *testDB) Name() string { return d.name } -func (p *testPackage) Name() string { - return p.name -} - -func (p *testPackage) Base() string { - return p.base -} - -func (p *testPackage) DB() alpm.IDB { +func (p *testPackage) DB() alpm.Database { return p.db } func (d *testDBSearcher) SyncPackage(name string) db.IPackage { if v, ok := d.absPackagesDB[name]; ok { return &testPackage{ - name: name, - base: name, - db: &testDB{name: v}, + Package: &mock.Package{ + PName: name, + PBase: name, + }, + db: &testDB{name: v}, } } @@ -105,9 +98,11 @@ func (d *testDBSearcher) SyncPackage(name string) db.IPackage { func (d *testDBSearcher) SyncPackageFromDB(name string, db string) db.IPackage { if v, ok := d.absPackagesDB[name]; ok && v == db { return &testPackage{ - name: name, - base: name, - db: &testDB{name: v}, + Package: &mock.Package{ + PName: name, + PBase: name, + }, + db: &testDB{name: v}, } } diff --git a/pkg/query/aur_warnings.go b/pkg/query/aur_warnings.go index cbfe1cac..69b44732 100644 --- a/pkg/query/aur_warnings.go +++ b/pkg/query/aur_warnings.go @@ -6,7 +6,7 @@ import ( "github.com/leonelquinteros/gotext" "github.com/Jguer/aur" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/db" "github.com/Jguer/yay/v12/pkg/text" @@ -25,7 +25,7 @@ func NewWarnings(logger *text.Logger) *AURWarnings { return &AURWarnings{log: logger} } -func (warnings *AURWarnings) AddToWarnings(remote map[string]alpm.IPackage, aurPkg *aur.Pkg) { +func (warnings *AURWarnings) AddToWarnings(remote map[string]alpm.Package, aurPkg *aur.Pkg) { name := aurPkg.Name pkg, ok := remote[name] if !ok { @@ -53,7 +53,7 @@ func (warnings *AURWarnings) AddToWarnings(remote map[string]alpm.IPackage, aurP } func (warnings *AURWarnings) CalculateMissing(remoteNames []string, - remote map[string]alpm.IPackage, aurData map[string]*aur.Pkg, + remote map[string]alpm.Package, aurData map[string]*aur.Pkg, ) { for _, name := range remoteNames { if _, ok := aurData[name]; !ok && !remote[name].ShouldIgnore() { diff --git a/pkg/query/metric.go b/pkg/query/metric.go index e858a3c4..8fe2c6c9 100644 --- a/pkg/query/metric.go +++ b/pkg/query/metric.go @@ -41,7 +41,7 @@ func (a *abstractResults) GetMetric(pkg *abstractResult) float64 { // slightly overweight sync sources by always giving them max popularity popularity := 1.0 - if pkg.source == sourceAUR { + if pkg.source == "aur" { popularity = a.aurSortByMetric(pkg) } @@ -66,7 +66,7 @@ func (a *abstractResults) separateSourceScore(source string, score float64) floa } // AUR is always lowest priority - if source == sourceAUR { + if source == "aur" { return 0 } diff --git a/pkg/query/metric_test.go b/pkg/query/metric_test.go index 710ff966..3ea5b6cf 100644 --- a/pkg/query/metric_test.go +++ b/pkg/query/metric_test.go @@ -27,7 +27,7 @@ func TestSeparateSourceScore_UsesRepoOrderEvenlyDistributed(t *testing.T) { assert.InDelta(t, 31.6, a.separateSourceScore("extra", sim), delta) assert.InDelta(t, 18.3, a.separateSourceScore("community", sim), delta) assert.InDelta(t, 5.0, a.separateSourceScore("multilib", sim), delta) - assert.Equal(t, 0.0, a.separateSourceScore(sourceAUR, sim)) + assert.Equal(t, 0.0, a.separateSourceScore("aur", sim)) }) t.Run("arch arm repos (core/extra/alarm/aur)", func(t *testing.T) { @@ -41,7 +41,6 @@ func TestSeparateSourceScore_UsesRepoOrderEvenlyDistributed(t *testing.T) { assert.InDelta(t, 45.0, a.separateSourceScore("core", sim), delta) assert.InDelta(t, 31.6, a.separateSourceScore("extra", sim), delta) assert.InDelta(t, 18.3, a.separateSourceScore("alarm", sim), delta) - assert.InDelta(t, 5.0, a.separateSourceScore("aur", sim), delta) - assert.Equal(t, 0.0, a.separateSourceScore(sourceAUR, sim)) + assert.Equal(t, 0.0, a.separateSourceScore("aur", sim)) }) } diff --git a/pkg/query/query_builder.go b/pkg/query/query_builder.go index 8eacde2f..0fb6d0db 100644 --- a/pkg/query/query_builder.go +++ b/pkg/query/query_builder.go @@ -9,7 +9,7 @@ import ( "unicode" "github.com/Jguer/aur" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/adrg/strutil" "github.com/adrg/strutil/metrics" mapset "github.com/deckarep/golang-set/v2" @@ -21,8 +21,6 @@ import ( "github.com/Jguer/yay/v12/pkg/text" ) -const sourceAUR = "AUR" - type SearchVerbosity int // Verbosity settings for search. @@ -198,7 +196,7 @@ func (s *SourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor } sortableResults.sortByFunc = sortableResults.GetSortFunc(s.sortBy, s.bottomUp) - var repoResults []alpm.IPackage + var repoResults []alpm.Package if s.targetMode.AtLeastRepo() { repoResults = dbExecutor.SyncPackages(pkgS...) @@ -210,7 +208,7 @@ func (s *SourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor s.queryMap[dbName][repoResults[i].Name()] = repoResults[i] - rawProvides := repoResults[i].Provides().Slice() + rawProvides := repoResults[i].Provides() provides := make([]string, len(rawProvides)) for j := range rawProvides { @@ -234,7 +232,7 @@ func (s *SourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor if s.targetMode.AtLeastAUR() { var aurResults []aur.Pkg aurResults, aurErr = queryAUR(ctx, s.aurClient, pkgS, s.searchBy) - dbName := sourceAUR + dbName := "aur" for i := range aurResults { if s.queryMap[dbName] == nil { @@ -297,7 +295,7 @@ func (s *SourceQueryBuilder) Results(dbExecutor db.Executor, verboseSearch Searc switch pPkg := pkg.(type) { case aur.Pkg: toPrint += aurPkgSearchString(&pPkg, dbExecutor, s.singleLineResults) - case alpm.IPackage: + case alpm.Package: toPrint += syncPkgSearchString(pPkg, dbExecutor, s.singleLineResults) } diff --git a/pkg/query/types.go b/pkg/query/types.go index 8992f492..3fd5829f 100644 --- a/pkg/query/types.go +++ b/pkg/query/types.go @@ -5,7 +5,7 @@ import ( "strconv" "github.com/Jguer/aur" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/leonelquinteros/gotext" "github.com/Jguer/yay/v12/pkg/db" @@ -86,7 +86,7 @@ func aurPkgSearchString( } // PrintSearch receives a RepoSearch type and outputs pretty text. -func syncPkgSearchString(pkg alpm.IPackage, dbExecutor db.Executor, singleLineResults bool) string { +func syncPkgSearchString(pkg alpm.Package, dbExecutor db.Executor, singleLineResults bool) string { linkText := text.Bold(text.ColorHash(pkg.DB().Name())) + "/" + text.Bold(pkg.Name()) toPrint := text.CreateRepoLink(pkg.DB().Name(), pkg.Architecture(), pkg.Name(), linkText) + " " + text.Cyan(pkg.Version()) + diff --git a/pkg/query/version_diff.go b/pkg/query/version_diff.go index 61c465cd..12baefd6 100644 --- a/pkg/query/version_diff.go +++ b/pkg/query/version_diff.go @@ -6,7 +6,7 @@ import ( "github.com/Jguer/yay/v12/pkg/text" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" ) func GetVersionDiff(oldVersion, newVersion string) (left, right string) { @@ -74,6 +74,6 @@ func isDevelName(name string) bool { return strings.Contains(name, "-always-") } -func isDevelPackage(pkg alpm.IPackage) bool { +func isDevelPackage(pkg alpm.Package) bool { return isDevelName(pkg.Name()) || isDevelName(pkg.Base()) } diff --git a/pkg/upgrade/service.go b/pkg/upgrade/service.go index 839dec50..760182de 100644 --- a/pkg/upgrade/service.go +++ b/pkg/upgrade/service.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/Jguer/aur" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" diff --git a/pkg/upgrade/service_test.go b/pkg/upgrade/service_test.go index d3800162..67c1be63 100644 --- a/pkg/upgrade/service_test.go +++ b/pkg/upgrade/service_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/Jguer/aur" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/pkg/upgrade/sources_test.go b/pkg/upgrade/sources_test.go index ebc63448..088e3e2f 100644 --- a/pkg/upgrade/sources_test.go +++ b/pkg/upgrade/sources_test.go @@ -14,7 +14,7 @@ import ( aur "github.com/Jguer/aur" "github.com/stretchr/testify/assert" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/Jguer/yay/v12/pkg/db/mock" "github.com/Jguer/yay/v12/pkg/text" @@ -25,7 +25,7 @@ func Test_upAUR(t *testing.T) { t.Parallel() type args struct { - remote map[string]alpm.IPackage + remote map[string]alpm.Package aurdata map[string]*aur.Pkg timeUpdate bool enableDowngrade bool @@ -38,7 +38,7 @@ func Test_upAUR(t *testing.T) { { name: "No Updates", args: args{ - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, "local_pkg": &mock.Package{PName: "local_pkg", PVersion: "1.1.0"}, "ignored": &mock.Package{PName: "ignored", PVersion: "1.0.0", PShouldIgnore: true}, @@ -54,7 +54,7 @@ func Test_upAUR(t *testing.T) { { name: "Simple Update", args: args{ - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "2.1.0", Name: "hello"}}, @@ -65,7 +65,7 @@ func Test_upAUR(t *testing.T) { { name: "Downgrade", args: args{ - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "1.0.0", Name: "hello"}}, @@ -77,7 +77,7 @@ func Test_upAUR(t *testing.T) { { name: "Downgrade Disabled", args: args{ - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "1.0.0", Name: "hello"}}, @@ -90,7 +90,7 @@ func Test_upAUR(t *testing.T) { name: "Mixed Updates Downgrades", args: args{ enableDowngrade: true, - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "up": &mock.Package{PName: "up", PVersion: "2.0.0"}, "same": &mock.Package{PName: "same", PVersion: "3.0.0"}, "down": &mock.Package{PName: "down", PVersion: "1.1.0"}, @@ -112,7 +112,7 @@ func Test_upAUR(t *testing.T) { { name: "Time Update", args: args{ - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0", PBuildDate: time.Now()}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "2.0.0", Name: "hello", LastModified: int(time.Now().AddDate(0, 0, 2).Unix())}}, @@ -138,7 +138,7 @@ func Test_upDevel(t *testing.T) { t.Parallel() type args struct { - remote map[string]alpm.IPackage + remote map[string]alpm.Package aurdata map[string]*aur.Pkg cached vcs.Store } @@ -152,7 +152,7 @@ func Test_upDevel(t *testing.T) { name: "No Updates", args: args{ cached: &vcs.Mock{}, - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, "local_pkg": &mock.Package{PName: "local_pkg", PVersion: "1.1.0"}, "ignored": &mock.Package{PName: "ignored", PVersion: "1.0.0", PShouldIgnore: true}, @@ -171,7 +171,7 @@ func Test_upDevel(t *testing.T) { cached: &vcs.Mock{ ToUpgradeReturn: []string{"hello", "hello4"}, }, - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, "hello2": &mock.Package{PName: "hello2", PVersion: "3.0.0"}, "hello4": &mock.Package{PName: "hello4", PVersion: "4.0.0"}, @@ -204,7 +204,7 @@ func Test_upDevel(t *testing.T) { finalLen: 1, args: args{ cached: &vcs.Mock{ToUpgradeReturn: []string{}}, - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0"}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "2.0.0", Name: "hello"}}, @@ -218,7 +218,7 @@ func Test_upDevel(t *testing.T) { cached: &vcs.Mock{ ToUpgradeReturn: []string{"hello"}, }, - remote: map[string]alpm.IPackage{ + remote: map[string]alpm.Package{ "hello": &mock.Package{PName: "hello", PVersion: "2.0.0", PShouldIgnore: true}, }, aurdata: map[string]*aur.Pkg{"hello": {Version: "2.0.0", Name: "hello"}}, diff --git a/pkg/vcs/mock.go b/pkg/vcs/mock.go index 9f51fa7f..6d4b1a67 100644 --- a/pkg/vcs/mock.go +++ b/pkg/vcs/mock.go @@ -4,7 +4,7 @@ import ( "context" "slices" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" gosrc "github.com/Morganamilo/go-srcinfo" ) @@ -31,5 +31,5 @@ func (m *Mock) Load() error { return nil } -func (m *Mock) CleanOrphans(pkgs map[string]alpm.IPackage) { +func (m *Mock) CleanOrphans(pkgs map[string]alpm.Package) { } diff --git a/pkg/vcs/vcs.go b/pkg/vcs/vcs.go index 32978603..ba9eb61e 100644 --- a/pkg/vcs/vcs.go +++ b/pkg/vcs/vcs.go @@ -12,7 +12,7 @@ import ( "sync" "time" - "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" gosrc "github.com/Morganamilo/go-srcinfo" "github.com/leonelquinteros/gotext" @@ -30,7 +30,7 @@ type Store interface { // RemovePackages removes the VCS info of the packages given as arg if they exist. RemovePackages(pkgs []string) // Clean orphaned VCS info. - CleanOrphans(pkgs map[string]alpm.IPackage) + CleanOrphans(pkgs map[string]alpm.Package) // Load loads the VCS info from disk. Load() error // Save saves the VCS info to disk. @@ -315,7 +315,7 @@ func (v *InfoStore) Load() error { return nil } -func (v *InfoStore) CleanOrphans(pkgs map[string]alpm.IPackage) { +func (v *InfoStore) CleanOrphans(pkgs map[string]alpm.Package) { missing := make([]string, 0) for pkgName := range v.OriginsByPackage { diff --git a/print_test.go b/print_test.go index f7b5ee2c..fc6b02bd 100644 --- a/print_test.go +++ b/print_test.go @@ -12,7 +12,7 @@ import ( "testing" "github.com/Jguer/aur" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -48,8 +48,8 @@ func TestPrintUpdateList(t *testing.T) { ReposFn: func() []string { return []string{"core"} }, - InstalledRemotePackagesFn: func() map[string]alpm.IPackage { - return map[string]alpm.IPackage{ + InstalledRemotePackagesFn: func() map[string]alpm.Package { + return map[string]alpm.Package{ "vosk-api": &mock.Package{ PName: "vosk-api", PVersion: "0.3.43-1", @@ -108,8 +108,8 @@ func TestPrintUpdateList(t *testing.T) { ReposFn: func() []string { return []string{"core"} }, - InstalledRemotePackagesFn: func() map[string]alpm.IPackage { - return map[string]alpm.IPackage{ + InstalledRemotePackagesFn: func() map[string]alpm.Package { + return map[string]alpm.Package{ "vosk-api": &mock.Package{ PName: "vosk-api", PVersion: "0.3.43-1", diff --git a/query.go b/query.go index 1f10e2f6..86af9891 100644 --- a/query.go +++ b/query.go @@ -7,7 +7,7 @@ import ( "path/filepath" aur "github.com/Jguer/aur" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" mapset "github.com/deckarep/golang-set/v2" "github.com/leonelquinteros/gotext" @@ -242,7 +242,13 @@ func hangingPackages(removeOptional bool, dbExecutor db.Executor) (hanging []str func getFolderSize(path string) (size int64) { _ = filepath.WalkDir(path, func(p string, entry fs.DirEntry, err error) error { - info, _ := entry.Info() + if err != nil || entry == nil { + return nil + } + info, infoErr := entry.Info() + if infoErr != nil || info == nil { + return nil + } size += info.Size() return nil }) diff --git a/sync_test.go b/sync_test.go index f0a18d08..0e2dd3ca 100644 --- a/sync_test.go +++ b/sync_test.go @@ -15,7 +15,7 @@ import ( "testing" "github.com/Jguer/aur" - alpm "github.com/Jguer/go-alpm/v2" + alpm "github.com/Jguer/dyalpm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -84,8 +84,8 @@ func TestSyncUpgrade(t *testing.T) { ReposFn: func() []string { return []string{"core"} }, - InstalledRemotePackagesFn: func() map[string]alpm.IPackage { - return map[string]alpm.IPackage{} + InstalledRemotePackagesFn: func() map[string]alpm.Package { + return map[string]alpm.Package{} }, InstalledRemotePackageNamesFn: func() []string { return []string{} @@ -196,8 +196,8 @@ func TestSyncUpgrade_IgnoreAll(t *testing.T) { ReposFn: func() []string { return []string{"core"} }, - InstalledRemotePackagesFn: func() map[string]alpm.IPackage { - return map[string]alpm.IPackage{} + InstalledRemotePackagesFn: func() map[string]alpm.Package { + return map[string]alpm.Package{} }, InstalledRemotePackageNamesFn: func() []string { return []string{} @@ -307,8 +307,8 @@ func TestSyncUpgrade_IgnoreOne(t *testing.T) { ReposFn: func() []string { return []string{"core"} }, - InstalledRemotePackagesFn: func() map[string]alpm.IPackage { - return map[string]alpm.IPackage{} + InstalledRemotePackagesFn: func() map[string]alpm.Package { + return map[string]alpm.Package{} }, InstalledRemotePackageNamesFn: func() []string { return []string{} @@ -481,8 +481,8 @@ pkgname = python-vosk SyncSatisfierFn: func(s string) mock.IPackage { return nil }, - InstalledRemotePackagesFn: func() map[string]alpm.IPackage { - return map[string]alpm.IPackage{ + InstalledRemotePackagesFn: func() map[string]alpm.Package { + return map[string]alpm.Package{ "vosk-api": &mock.Package{ PName: "vosk-api", PVersion: "0.3.43-1", @@ -681,8 +681,8 @@ func TestSyncUpgrade_NoCombinedUpgrade(t *testing.T) { ReposFn: func() []string { return []string{"core"} }, - InstalledRemotePackagesFn: func() map[string]alpm.IPackage { - return map[string]alpm.IPackage{} + InstalledRemotePackagesFn: func() map[string]alpm.Package { + return map[string]alpm.Package{} }, InstalledRemotePackageNamesFn: func() []string { return []string{}