mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-01-12 03:21:33 +00:00
438 lines
11 KiB
Go
438 lines
11 KiB
Go
package debian
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/smira/aptly/aptly"
|
|
"github.com/smira/aptly/utils"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Dependency options
|
|
const (
|
|
// DepFollowSource pulls source packages when required
|
|
DepFollowSource = 1 << iota
|
|
// DepFollowSuggests pulls from suggests
|
|
DepFollowSuggests
|
|
// DepFollowRecommends pulls from recommends
|
|
DepFollowRecommends
|
|
// DepFollowAllVariants follows all variants if depends on "a | b"
|
|
DepFollowAllVariants
|
|
// DepFollowBuild pulls build dependencies
|
|
DepFollowBuild
|
|
)
|
|
|
|
// PackageList is list of unique (by key) packages
|
|
//
|
|
// It could be seen as repo snapshot, repo contents, result of filtering,
|
|
// merge, etc.
|
|
//
|
|
// If indexed, PackageList starts supporting searching
|
|
type PackageList struct {
|
|
// Straight list of packages as map
|
|
packages map[string]*Package
|
|
// Has index been prepared?
|
|
indexed bool
|
|
// Indexed list of packages, sorted by name internally
|
|
packagesIndex []*Package
|
|
// Map of packages for each virtual package (provides)
|
|
providesIndex map[string][]*Package
|
|
}
|
|
|
|
// Verify interface
|
|
var (
|
|
_ sort.Interface = &PackageList{}
|
|
)
|
|
|
|
// NewPackageList creates empty package list
|
|
func NewPackageList() *PackageList {
|
|
return &PackageList{packages: make(map[string]*Package, 1000)}
|
|
}
|
|
|
|
// NewPackageListFromRefList loads packages list from PackageRefList
|
|
func NewPackageListFromRefList(reflist *PackageRefList, collection *PackageCollection, progress aptly.Progress) (*PackageList, error) {
|
|
// empty reflist
|
|
if reflist == nil {
|
|
return NewPackageList(), nil
|
|
}
|
|
|
|
result := &PackageList{packages: make(map[string]*Package, reflist.Len())}
|
|
|
|
if progress != nil {
|
|
progress.InitBar(int64(reflist.Len()), false)
|
|
}
|
|
|
|
err := reflist.ForEach(func(key []byte) error {
|
|
p, err2 := collection.ByKey(key)
|
|
if err2 != nil {
|
|
return fmt.Errorf("unable to load package with key %s: %s", key, err2)
|
|
}
|
|
if progress != nil {
|
|
progress.AddBar(1)
|
|
}
|
|
return result.Add(p)
|
|
})
|
|
|
|
if progress != nil {
|
|
progress.ShutdownBar()
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Add appends package to package list, additionally checking for uniqueness
|
|
func (l *PackageList) Add(p *Package) error {
|
|
key := string(p.Key(""))
|
|
existing, ok := l.packages[key]
|
|
if ok {
|
|
if !existing.Equals(p) {
|
|
return fmt.Errorf("conflict in package %s: %#v != %#v", p, existing, p)
|
|
}
|
|
return nil
|
|
}
|
|
l.packages[key] = p
|
|
|
|
if l.indexed {
|
|
for _, provides := range p.Provides {
|
|
l.providesIndex[provides] = append(l.providesIndex[provides], p)
|
|
}
|
|
|
|
i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.packagesIndex[j].Name >= p.Name })
|
|
|
|
// insert p into l.packagesIndex in position i
|
|
l.packagesIndex = append(l.packagesIndex, nil)
|
|
copy(l.packagesIndex[i+1:], l.packagesIndex[i:])
|
|
l.packagesIndex[i] = p
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ForEach calls handler for each package in list
|
|
func (l *PackageList) ForEach(handler func(*Package) error) error {
|
|
var err error
|
|
for _, p := range l.packages {
|
|
err = handler(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Len returns number of packages in the list
|
|
func (l *PackageList) Len() int {
|
|
return len(l.packages)
|
|
}
|
|
|
|
// Append adds content from one package list to another
|
|
func (l *PackageList) Append(pl *PackageList) error {
|
|
if l.indexed {
|
|
panic("Append not supported when indexed")
|
|
}
|
|
for k, p := range pl.packages {
|
|
existing, ok := l.packages[k]
|
|
if ok {
|
|
if !existing.Equals(p) {
|
|
return fmt.Errorf("conflict in package %s: %#v != %#v", p, existing, p)
|
|
}
|
|
} else {
|
|
l.packages[k] = p
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Remove removes package from the list, and updates index when required
|
|
func (l *PackageList) Remove(p *Package) {
|
|
delete(l.packages, string(p.Key("")))
|
|
if l.indexed {
|
|
for _, provides := range p.Provides {
|
|
for i, pkg := range l.providesIndex[provides] {
|
|
if pkg.Equals(p) {
|
|
// remove l.ProvidesIndex[provides][i] w/o preserving order
|
|
l.providesIndex[provides][len(l.providesIndex[provides])-1], l.providesIndex[provides][i], l.providesIndex[provides] =
|
|
nil, l.providesIndex[provides][len(l.providesIndex[provides])-1], l.providesIndex[provides][:len(l.providesIndex[provides])-1]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.packagesIndex[j].Name >= p.Name })
|
|
for i < len(l.packagesIndex) && l.packagesIndex[i].Name == p.Name {
|
|
if l.packagesIndex[i].Equals(p) {
|
|
// remove l.packagesIndex[i] preserving order
|
|
copy(l.packagesIndex[i:], l.packagesIndex[i+1:])
|
|
l.packagesIndex[len(l.packagesIndex)-1] = nil
|
|
l.packagesIndex = l.packagesIndex[:len(l.packagesIndex)-1]
|
|
break
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Architectures returns list of architectures present in packages and flag if source packages are present.
|
|
//
|
|
// If includeSource is true, meta-architecture "source" would be present in the list
|
|
func (l *PackageList) Architectures(includeSource bool) (result []string) {
|
|
result = make([]string, 0, 10)
|
|
for _, pkg := range l.packages {
|
|
if pkg.Architecture != "all" && (pkg.Architecture != "source" || includeSource) && !utils.StrSliceHasItem(result, pkg.Architecture) {
|
|
result = append(result, pkg.Architecture)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// depSliceDeduplicate removes dups in slice of Dependencies
|
|
func depSliceDeduplicate(s []Dependency) []Dependency {
|
|
l := len(s)
|
|
if l < 2 {
|
|
return s
|
|
}
|
|
if l == 2 {
|
|
if s[0] == s[1] {
|
|
return s[0:1]
|
|
}
|
|
return s
|
|
}
|
|
|
|
found := make(map[string]bool, l)
|
|
j := 0
|
|
for i, x := range s {
|
|
h := x.Hash()
|
|
if !found[h] {
|
|
found[h] = true
|
|
s[j] = s[i]
|
|
j++
|
|
}
|
|
}
|
|
|
|
return s[:j]
|
|
}
|
|
|
|
// VerifyDependencies looks for missing dependencies in package list.
|
|
//
|
|
// Analysis would be peformed for each architecture, in specified sources
|
|
func (l *PackageList) VerifyDependencies(options int, architectures []string, sources *PackageList, progress aptly.Progress) ([]Dependency, error) {
|
|
missing := make([]Dependency, 0, 128)
|
|
|
|
if progress != nil {
|
|
progress.InitBar(int64(l.Len())*int64(len(architectures)), false)
|
|
}
|
|
|
|
for _, arch := range architectures {
|
|
cache := make(map[string]bool, 2048)
|
|
|
|
for _, p := range l.packages {
|
|
if progress != nil {
|
|
progress.AddBar(1)
|
|
}
|
|
|
|
if !p.MatchesArchitecture(arch) {
|
|
continue
|
|
}
|
|
|
|
for _, dep := range p.GetDependencies(options) {
|
|
variants, err := ParseDependencyVariants(dep)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to process package %s: %s", p, err)
|
|
}
|
|
|
|
variants = depSliceDeduplicate(variants)
|
|
|
|
variantsMissing := make([]Dependency, 0, len(variants))
|
|
missingCount := 0
|
|
|
|
for _, dep := range variants {
|
|
if dep.Architecture == "" {
|
|
dep.Architecture = arch
|
|
}
|
|
|
|
hash := dep.Hash()
|
|
r, ok := cache[hash]
|
|
if ok {
|
|
if !r {
|
|
missingCount++
|
|
}
|
|
continue
|
|
}
|
|
|
|
if sources.Search(dep) == nil {
|
|
variantsMissing = append(variantsMissing, dep)
|
|
missingCount++
|
|
} else {
|
|
cache[hash] = true
|
|
}
|
|
}
|
|
|
|
if options&DepFollowAllVariants == DepFollowAllVariants {
|
|
missing = append(missing, variantsMissing...)
|
|
for _, dep := range variantsMissing {
|
|
cache[dep.Hash()] = false
|
|
}
|
|
} else {
|
|
if missingCount == len(variants) {
|
|
missing = append(missing, variantsMissing...)
|
|
for _, dep := range variantsMissing {
|
|
cache[dep.Hash()] = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if progress != nil {
|
|
progress.ShutdownBar()
|
|
}
|
|
|
|
return missing, nil
|
|
}
|
|
|
|
// Swap swaps two packages in index
|
|
func (l *PackageList) Swap(i, j int) {
|
|
l.packagesIndex[i], l.packagesIndex[j] = l.packagesIndex[j], l.packagesIndex[i]
|
|
}
|
|
|
|
// Compare compares two names in lexographical order
|
|
func (l *PackageList) Less(i, j int) bool {
|
|
return l.packagesIndex[i].Name < l.packagesIndex[j].Name
|
|
}
|
|
|
|
// PrepareIndex prepares list for indexing
|
|
func (l *PackageList) PrepareIndex() {
|
|
l.packagesIndex = make([]*Package, l.Len())
|
|
l.providesIndex = make(map[string][]*Package, 128)
|
|
|
|
i := 0
|
|
for _, p := range l.packages {
|
|
l.packagesIndex[i] = p
|
|
i++
|
|
|
|
for _, provides := range p.Provides {
|
|
l.providesIndex[provides] = append(l.providesIndex[provides], p)
|
|
}
|
|
}
|
|
|
|
sort.Sort(l)
|
|
|
|
l.indexed = true
|
|
}
|
|
|
|
// Search searches package index for specified package
|
|
func (l *PackageList) Search(dep Dependency) *Package {
|
|
if !l.indexed {
|
|
panic("list not indexed, can't search")
|
|
}
|
|
|
|
if dep.Relation == VersionDontCare {
|
|
for _, p := range l.providesIndex[dep.Pkg] {
|
|
if p.MatchesArchitecture(dep.Architecture) {
|
|
return p
|
|
}
|
|
}
|
|
}
|
|
|
|
i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.packagesIndex[j].Name >= dep.Pkg })
|
|
|
|
for i < len(l.packagesIndex) && l.packagesIndex[i].Name == dep.Pkg {
|
|
p := l.packagesIndex[i]
|
|
if p.MatchesDependency(dep) {
|
|
return p
|
|
}
|
|
|
|
i++
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Filter filters package index by specified queries (ORed together), possibly pulling dependencies
|
|
func (l *PackageList) Filter(queries []string, withDependencies bool, source *PackageList, dependencyOptions int, architecturesList []string) (*PackageList, error) {
|
|
if !l.indexed {
|
|
panic("list not indexed, can't filter")
|
|
}
|
|
|
|
result := NewPackageList()
|
|
|
|
for _, query := range queries {
|
|
isDepQuery := strings.IndexAny(query, " (){}=<>") != -1
|
|
|
|
if !isDepQuery {
|
|
// try to interpret query as package string representation
|
|
|
|
// convert Package.String() to Package.Key()
|
|
i := strings.Index(query, "_")
|
|
if i != -1 {
|
|
pkg, query := query[:i], query[i+1:]
|
|
j := strings.LastIndex(query, "_")
|
|
if j != -1 {
|
|
version, arch := query[:j], query[j+1:]
|
|
p := l.packages["P"+arch+" "+pkg+" "+version]
|
|
if p != nil {
|
|
result.Add(p)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// try as dependency
|
|
dep, err := ParseDependency(query)
|
|
if err != nil {
|
|
if isDepQuery {
|
|
return nil, err
|
|
}
|
|
// parsing failed, but probably that wasn't a dep query
|
|
continue
|
|
}
|
|
|
|
i := sort.Search(len(l.packagesIndex), func(j int) bool { return l.packagesIndex[j].Name >= dep.Pkg })
|
|
|
|
for i < len(l.packagesIndex) && l.packagesIndex[i].Name == dep.Pkg {
|
|
p := l.packagesIndex[i]
|
|
if p.MatchesDependency(dep) {
|
|
result.Add(p)
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
|
|
if withDependencies {
|
|
added := result.Len()
|
|
|
|
dependencySource := NewPackageList()
|
|
dependencySource.Append(source)
|
|
dependencySource.Append(result)
|
|
dependencySource.PrepareIndex()
|
|
|
|
// while some new dependencies were discovered
|
|
for added > 0 {
|
|
added = 0
|
|
|
|
// find missing dependencies
|
|
missing, err := result.VerifyDependencies(dependencyOptions, architecturesList, dependencySource, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// try to satisfy dependencies
|
|
for _, dep := range missing {
|
|
p := l.Search(dep)
|
|
if p != nil {
|
|
result.Add(p)
|
|
dependencySource.Add(p)
|
|
added++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|