diff --git a/debian/list.go b/debian/list.go index 189448a9..5b767376 100644 --- a/debian/list.go +++ b/debian/list.go @@ -65,11 +65,13 @@ const ( // VerifyDependencies looks for missing dependencies in package list. // -// Analysis would be peformed for each architecture +// Analysis would be peformed for each architecture, in specified sources func (l *PackageList) VerifyDependencies(options int, architectures []string, sources *PackageIndexedList) ([]Dependency, error) { - missing := make([]Dependency, 0, 100) + missing := make([]Dependency, 0, 128) for _, arch := range architectures { + cache := make(map[string]bool, 2048) + for _, p := range l.packages { if !p.MatchesArchitecture(arch) { continue @@ -83,8 +85,17 @@ func (l *PackageList) VerifyDependencies(options int, architectures []string, so dep.Architecture = arch - if sources.Search(dep) != nil { + hash := dep.Hash() + _, ok := cache[hash] + if ok { + continue + } + + if sources.Search(dep) == nil { missing = append(missing, dep) + cache[hash] = false + } else { + cache[hash] = true } } } @@ -154,6 +165,49 @@ func (l *PackageIndexedList) Append(pl *PackageList) { // Search searches package index for specified package func (l *PackageIndexedList) Search(dep Dependency) *Package { + if dep.Relation == VersionDontCare { + for _, p := range l.providesList[dep.Pkg] { + if p.MatchesArchitecture(dep.Architecture) { + return p + } + } + } + + i := sort.Search(len(l.packages), func(j int) bool { return l.packages[j].Name >= dep.Pkg }) + + for i < len(l.packages) && l.packages[i].Name == dep.Pkg { + p := l.packages[i] + if p.MatchesArchitecture(dep.Architecture) { + if dep.Relation == VersionDontCare { + return p + } + + r := CompareVersions(p.Version, dep.Version) + switch dep.Relation { + case VersionEqual: + if r == 0 { + return p + } + case VersionLess: + if r < 0 { + return p + } + case VersionGreater: + if r > 0 { + return p + } + case VersionLessOrEqual: + if r <= 0 { + return p + } + case VersionGreaterOrEqual: + if r >= 0 { + return p + } + } + } + i++ + } return nil } diff --git a/debian/list_test.go b/debian/list_test.go index 5eb15904..fa686006 100644 --- a/debian/list_test.go +++ b/debian/list_test.go @@ -119,3 +119,79 @@ func (s *PackageListSuite) TestPackageRefListForeach(c *C) { c.Check(err, Equals, e) } + +type PackageIndexedListSuite struct { + packages []*Package + pl *PackageList + list *PackageIndexedList +} + +var _ = Suite(&PackageIndexedListSuite{}) + +func (s *PackageIndexedListSuite) SetUpTest(c *C) { + s.pl = NewPackageList() + s.packages = []*Package{ + &Package{Name: "lib", Version: "1.0", Architecture: "i386", PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"mail-agent"}}, + &Package{Name: "dpkg", Version: "1.7", Architecture: "i386"}, + &Package{Name: "data", Version: "1.1~bp1", Architecture: "all", PreDepends: []string{"dpkg (>= 1.6)"}}, + &Package{Name: "app", Version: "1.1~bp1", Architecture: "i386", PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}, + &Package{Name: "mailer", Version: "3.5.8", Architecture: "i386", Provides: "mail-agent"}, + &Package{Name: "app", Version: "1.1~bp1", Architecture: "amd64", PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}, + &Package{Name: "app", Version: "1.1~bp1", Architecture: "arm", PreDepends: []string{"dpkg (>= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}, + &Package{Name: "app", Version: "1.0", Architecture: "s390", PreDepends: []string{"dpkg >= 1.6)"}, Depends: []string{"lib (>> 0.9)", "data (>= 1.0)"}}, + &Package{Name: "aa", Version: "2.0-1", Architecture: "i386", PreDepends: []string{"dpkg (>= 1.6)"}}, + &Package{Name: "dpkg", Version: "1.6.1-3", Architecture: "amd64"}, + } + for _, p := range s.packages { + s.pl.Add(p) + } + + s.list = NewPackageIndexedList() + s.list.Append(s.pl) + s.list.PrepareIndex() +} + +func (s *PackageIndexedListSuite) TestIndex(c *C) { + c.Check(len(s.list.providesList), Equals, 1) + c.Check(len(s.list.providesList["mail-agent"]), Equals, 1) + c.Check(s.list.packages[0], Equals, s.packages[8]) +} + +func (s *PackageIndexedListSuite) TestSearch(c *C) { + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app"}), Equals, s.packages[3]) + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "mail-agent"}), Equals, s.packages[4]) + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "puppy"}), IsNil) + + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp1"}), Equals, s.packages[3]) + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionEqual, Version: "1.1~bp2"}), IsNil) + + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1"}), Equals, s.packages[3]) + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLess, Version: "1.1~~"}), IsNil) + + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1"}), Equals, s.packages[3]) + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~bp1"}), Equals, s.packages[3]) + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionLessOrEqual, Version: "1.1~~"}), IsNil) + + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.0"}), Equals, s.packages[3]) + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreater, Version: "1.2"}), IsNil) + + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.0"}), Equals, s.packages[3]) + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.1~bp1"}), Equals, s.packages[3]) + c.Check(s.list.Search(Dependency{Architecture: "i386", Pkg: "app", Relation: VersionGreaterOrEqual, Version: "1.2"}), IsNil) +} + +func (s *PackageIndexedListSuite) TestVerifyDependencies(c *C) { + missing, err := s.pl.VerifyDependencies(0, []string{"i386"}, s.list) + + c.Check(err, IsNil) + c.Check(missing, DeepEquals, []Dependency{}) + + missing, err = s.pl.VerifyDependencies(0, []string{"i386", "amd64"}, s.list) + + c.Check(err, IsNil) + c.Check(missing, DeepEquals, []Dependency{Dependency{Pkg: "lib", Relation: VersionGreater, Version: "0.9", Architecture: "amd64"}}) + + _, err = s.pl.VerifyDependencies(0, []string{"i386", "amd64", "s390"}, s.list) + + c.Check(err, ErrorMatches, "unable to process package app-1.0_s390:.*") +} diff --git a/debian/version.go b/debian/version.go index b3203f56..74996725 100644 --- a/debian/version.go +++ b/debian/version.go @@ -188,6 +188,11 @@ type Dependency struct { Architecture string } +// Hash calculates some predefined unique ID of Dependency +func (d *Dependency) Hash() string { + return fmt.Sprintf("%s:%s:%d:%s", d.Architecture, d.Pkg, d.Relation, d.Version) +} + // parseDependency parses dependency in format "pkg (>= 1.35)" into parts func parseDependency(dep string) (d Dependency, err error) { if !strings.HasSuffix(dep, ")") {