From ab18d4835bc726b4f65db69b87c03aea72d63e07 Mon Sep 17 00:00:00 2001 From: 5hir0kur0 <12101162+5hir0kur0@users.noreply.github.com> Date: Thu, 18 Jul 2024 11:50:26 +0900 Subject: [PATCH] Support version relation in `Provides` entries --- deb/list.go | 24 ++++++++++------- deb/package.go | 73 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/deb/list.go b/deb/list.go index 77a03a64..51c9ba7e 100644 --- a/deb/list.go +++ b/deb/list.go @@ -145,7 +145,7 @@ func (l *PackageList) Add(p *Package) error { l.packages[key] = p if l.indexed { - for _, provides := range p.Provides { + for _, provides := range p.ProvidedPackages() { l.providesIndex[provides] = append(l.providesIndex[provides], p) } @@ -215,7 +215,7 @@ func (l *PackageList) Append(pl *PackageList) error { func (l *PackageList) Remove(p *Package) { delete(l.packages, l.keyFunc(p)) if l.indexed { - for _, provides := range p.Provides { + for _, provides := range p.ProvidedPackages() { for i, pkg := range l.providesIndex[provides] { if pkg.Equals(p) { // remove l.ProvidesIndex[provides][i] w/o preserving order @@ -419,7 +419,7 @@ func (l *PackageList) PrepareIndex() { l.packagesIndex[i] = p i++ - for _, provides := range p.Provides { + for _, provides := range p.ProvidedPackages() { l.providesIndex[provides] = append(l.providesIndex[provides], p) } } @@ -472,21 +472,25 @@ func (l *PackageList) Search(dep Dependency, allMatches bool) (searchResults []* searchResults = append(searchResults, p) if !allMatches { - break + return } } i++ } - if dep.Relation == VersionDontCare { - for _, p := range l.providesIndex[dep.Pkg] { - if dep.Architecture == "" || p.MatchesArchitecture(dep.Architecture) { + providers, ok := l.providesIndex[dep.Pkg] + if !ok { + return + } + for _, p := range providers { + if dep.Architecture == "" || p.MatchesArchitecture(dep.Architecture) { + if p.MatchesDependency(dep) { searchResults = append(searchResults, p) + } - if !allMatches { - break - } + if !allMatches { + return } } } diff --git a/deb/package.go b/deb/package.go index 2957b7f8..75553e8f 100644 --- a/deb/package.go +++ b/deb/package.go @@ -3,6 +3,7 @@ package deb import ( gocontext "context" "encoding/json" + "errors" "fmt" "path/filepath" "strconv" @@ -312,6 +313,23 @@ func (p *Package) GetField(name string) string { } } +// ProvidedPackages returns just the package names of the provided packages (without version numbers such as +// `(= 1.2.3)`). +func (p *Package) ProvidedPackages() []string { + result := make([]string, len(p.Provides)) + for i, provided := range p.Provides { + providedDep, err := ParseDependency(provided) + if err != nil { + // Should never happen, but I included this, so it definitely has the old behavior in case there is no + // special syntax. + result[i] = provided + } else { + result[i] = providedDep.Pkg + } + } + return result +} + // MatchesArchitecture checks whether packages matches specified architecture func (p *Package) MatchesArchitecture(arch string) bool { if p.Architecture == ArchitectureAll && arch != ArchitectureSource { @@ -321,24 +339,57 @@ func (p *Package) MatchesArchitecture(arch string) bool { return p.Architecture == arch } +// providesDependency checks if the package `Provide:`s the dependency, assuming that the architecture matches. +// If the `Provides:` entry includes a version number, it will be considered when checking the dependency. +func (p *Package) providesDependency(dep Dependency) (bool, error) { + var errs []error // won't cause an allocation in case of no errors + for _, provided := range p.Provides { + providedDep, err := ParseDependency(provided) + if err != nil { + errs = append(errs, err) + } + // The only relation allowed here is `=`. + // > The relations allowed are [...]. The exception is the Provides field, for which only = is allowed. + // > [...] + // > A Provides field may contain version numbers, and such a version number will be considered when + // > considering a dependency on or conflict with the virtual package name. + // -- https://www.debian.org/doc/debian-policy/ch-relationships.html + switch providedDep.Relation { + case VersionDontCare: + if providedDep.Pkg == dep.Pkg { + return true, nil + } + case VersionEqual: + providedVersion := providedDep.Version + if providedDep.Pkg == dep.Pkg && versionSatisfiesDependency(providedVersion, dep) { + return true, nil + } + default: + errs = append(errs, fmt.Errorf("unsupported relation in Provides: %s", providedDep.String())) + } + } + return false, errors.Join(errs...) +} + // MatchesDependency checks whether package matches specified dependency func (p *Package) MatchesDependency(dep Dependency) bool { if dep.Architecture != "" && !p.MatchesArchitecture(dep.Architecture) { return false } - if dep.Relation == VersionDontCare { - if utils.StrSliceHasItem(p.Provides, dep.Pkg) { - return true - } - return dep.Pkg == p.Name + if providesDep, _ := p.providesDependency(dep); providesDep { // Is ignoring errors the right thing to do here? + return true } if dep.Pkg != p.Name { return false } - r := CompareVersions(p.Version, dep.Version) + return versionSatisfiesDependency(p.Version, dep) +} + +func versionSatisfiesDependency(version string, dep Dependency) bool { + r := CompareVersions(version, dep.Version) switch dep.Relation { case VersionEqual: @@ -352,13 +403,15 @@ func (p *Package) MatchesDependency(dep Dependency) bool { case VersionGreaterOrEqual: return r >= 0 case VersionPatternMatch: - matched, err := filepath.Match(dep.Version, p.Version) + matched, err := filepath.Match(dep.Version, version) return err == nil && matched case VersionRegexp: - return dep.Regexp.FindStringIndex(p.Version) != nil + return dep.Regexp.FindStringIndex(version) != nil + case VersionDontCare: + return true + default: + panic(fmt.Sprintf("unknown relation: %d", dep.Relation)) } - - panic("unknown relation") } // GetName returns package name