This commit is contained in:
Sam Boyer 2016-06-06 12:50:20 -04:00
Родитель 987ae4d8ca
Коммит 75b26ab337
6 изменённых файлов: 95 добавлений и 70 удалений

Просмотреть файл

@ -112,7 +112,7 @@ func TestProjectManagerInit(t *testing.T) {
// Two birds, one stone - make sure the internal ProjectManager vlist cache
// works by asking for the versions again, and do it through smcache to
// ensure its sorting works, as well.
smc := &smAdapter{
smc := &bridge{
sm: sm,
vlists: make(map[ProjectName][]Version),
}

Просмотреть файл

@ -41,7 +41,7 @@ func (s *solver) satisfiable(pa ProjectAtom) error {
// the constraints established by the current solution.
func (s *solver) checkAtomAllowable(pa ProjectAtom) error {
constraint := s.sel.getConstraint(pa.Ident)
if s.sm.matches(pa.Ident, constraint, pa.Version) {
if s.b.matches(pa.Ident, constraint, pa.Version) {
return nil
}
// TODO collect constraint failure reason
@ -49,7 +49,7 @@ func (s *solver) checkAtomAllowable(pa ProjectAtom) error {
deps := s.sel.getDependenciesOn(pa.Ident)
var failparent []Dependency
for _, dep := range deps {
if !s.sm.matches(pa.Ident, dep.Dep.Constraint, pa.Version) {
if !s.b.matches(pa.Ident, dep.Dep.Constraint, pa.Version) {
s.fail(dep.Depender.Ident)
failparent = append(failparent, dep)
}
@ -71,7 +71,7 @@ func (s *solver) checkDepsConstraintsAllowable(pa ProjectAtom, dep ProjectDep) e
constraint := s.sel.getConstraint(dep.Ident)
// Ensure the constraint expressed by the dep has at least some possible
// intersection with the intersection of existing constraints.
if s.sm.matchesAny(dep.Ident, constraint, dep.Constraint) {
if s.b.matchesAny(dep.Ident, constraint, dep.Constraint) {
return nil
}
@ -80,7 +80,7 @@ func (s *solver) checkDepsConstraintsAllowable(pa ProjectAtom, dep ProjectDep) e
var failsib []Dependency
var nofailsib []Dependency
for _, sibling := range siblings {
if !s.sm.matchesAny(dep.Ident, sibling.Dep.Constraint, dep.Constraint) {
if !s.b.matchesAny(dep.Ident, sibling.Dep.Constraint, dep.Constraint) {
s.fail(sibling.Depender.Ident)
failsib = append(failsib, sibling)
} else {
@ -103,7 +103,7 @@ func (s *solver) checkDepsConstraintsAllowable(pa ProjectAtom, dep ProjectDep) e
// selected.
func (s *solver) checkDepsDisallowsSelected(pa ProjectAtom, dep ProjectDep) error {
selected, exists := s.sel.selected(dep.Ident)
if exists && !s.sm.matches(dep.Ident, dep.Constraint, selected.Version) {
if exists && !s.b.matches(dep.Ident, dep.Constraint, selected.Version) {
s.fail(dep.Ident)
err := &constraintNotAllowedFailure{

Просмотреть файл

@ -3,7 +3,7 @@ package vsolver
type selection struct {
projects []ProjectAtom
deps map[ProjectIdentifier][]Dependency
sm *smAdapter
sm *bridge
}
func (s *selection) getDependenciesOn(id ProjectIdentifier) []Dependency {

Просмотреть файл

@ -3,7 +3,7 @@ package vsolver
import "sort"
// sourceBridges provide an adapter to SourceManagers that tailor operations
// for a particular solve run
// for a single solve run.
type sourceBridge interface {
getProjectInfo(pa ProjectAtom) (ProjectInfo, error)
listVersions(id ProjectIdentifier) ([]Version, error)
@ -14,6 +14,8 @@ type sourceBridge interface {
matches(id ProjectIdentifier, c Constraint, v Version) bool
matchesAny(id ProjectIdentifier, c1, c2 Constraint) bool
intersect(id ProjectIdentifier, c1, c2 Constraint) Constraint
listExternal(n ProjectIdentifier, v Version) ([]string, error)
computeRootReach(path string) ([]string, error)
}
// smAdapter is an adapter and around a proper SourceManager.
@ -31,12 +33,14 @@ type sourceBridge interface {
// Finally, it provides authoritative version/constraint operations, ensuring
// that any possible approach to a match - even those not literally encoded in
// the inputs - is achieved.
type smAdapter struct {
type bridge struct {
// The underlying, adapted-to SourceManager
sm SourceManager
// Direction to sort the version list. False indicates sorting for upgrades;
// true for downgrades.
sortdown bool
// Map of project root name to their available version list. This cache is
// layered on top of the proper SourceManager's cache; the only difference
// is that this keeps the versions sorted in the direction required by the
@ -44,11 +48,11 @@ type smAdapter struct {
vlists map[ProjectName][]Version
}
func (c *smAdapter) getProjectInfo(pa ProjectAtom) (ProjectInfo, error) {
return c.sm.GetProjectInfo(ProjectName(pa.Ident.netName()), pa.Version)
func (b *bridge) getProjectInfo(pa ProjectAtom) (ProjectInfo, error) {
return b.sm.GetProjectInfo(ProjectName(pa.Ident.netName()), pa.Version)
}
func (c *smAdapter) key(id ProjectIdentifier) ProjectName {
func (b *bridge) key(id ProjectIdentifier) ProjectName {
k := ProjectName(id.NetworkName)
if k == "" {
k = id.LocalName
@ -57,41 +61,41 @@ func (c *smAdapter) key(id ProjectIdentifier) ProjectName {
return k
}
func (c *smAdapter) listVersions(id ProjectIdentifier) ([]Version, error) {
k := c.key(id)
func (b *bridge) listVersions(id ProjectIdentifier) ([]Version, error) {
k := b.key(id)
if vl, exists := c.vlists[k]; exists {
if vl, exists := b.vlists[k]; exists {
return vl, nil
}
vl, err := c.sm.ListVersions(k)
vl, err := b.sm.ListVersions(k)
// TODO cache errors, too?
if err != nil {
return nil, err
}
if c.sortdown {
if b.sortdown {
sort.Sort(downgradeVersionSorter(vl))
} else {
sort.Sort(upgradeVersionSorter(vl))
}
c.vlists[k] = vl
b.vlists[k] = vl
return vl, nil
}
func (c *smAdapter) repoExists(id ProjectIdentifier) (bool, error) {
k := c.key(id)
return c.sm.RepoExists(k)
func (b *bridge) repoExists(id ProjectIdentifier) (bool, error) {
k := b.key(id)
return b.sm.RepoExists(k)
}
func (c *smAdapter) vendorCodeExists(id ProjectIdentifier) (bool, error) {
k := c.key(id)
return c.sm.VendorCodeExists(k)
func (b *bridge) vendorCodeExists(id ProjectIdentifier) (bool, error) {
k := b.key(id)
return b.sm.VendorCodeExists(k)
}
func (c *smAdapter) pairVersion(id ProjectIdentifier, v UnpairedVersion) PairedVersion {
vl, err := c.listVersions(id)
func (b *bridge) pairVersion(id ProjectIdentifier, v UnpairedVersion) PairedVersion {
vl, err := b.listVersions(id)
if err != nil {
return nil
}
@ -108,8 +112,8 @@ func (c *smAdapter) pairVersion(id ProjectIdentifier, v UnpairedVersion) PairedV
return nil
}
func (c *smAdapter) pairRevision(id ProjectIdentifier, r Revision) []Version {
vl, err := c.listVersions(id)
func (b *bridge) pairRevision(id ProjectIdentifier, r Revision) []Version {
vl, err := b.listVersions(id)
if err != nil {
return nil
}
@ -131,7 +135,7 @@ func (c *smAdapter) pairRevision(id ProjectIdentifier, r Revision) []Version {
// constraint. If that basic check fails and the provided version is incomplete
// (e.g. an unpaired version or bare revision), it will attempt to gather more
// information on one or the other and re-perform the comparison.
func (c *smAdapter) matches(id ProjectIdentifier, c2 Constraint, v Version) bool {
func (b *bridge) matches(id ProjectIdentifier, c2 Constraint, v Version) bool {
if c2.Matches(v) {
return true
}
@ -149,7 +153,7 @@ func (c *smAdapter) matches(id ProjectIdentifier, c2 Constraint, v Version) bool
case UnpairedVersion:
// Only way paired and unpaired could match is if they share an
// underlying rev
pv := c.pairVersion(id, tc)
pv := b.pairVersion(id, tc)
if pv == nil {
return false
}
@ -157,7 +161,7 @@ func (c *smAdapter) matches(id ProjectIdentifier, c2 Constraint, v Version) bool
case semverConstraint:
// Have to check all the possible versions for that rev to see if
// any match the semver constraint
for _, pv := range c.pairRevision(id, tv.Underlying()) {
for _, pv := range b.pairRevision(id, tv.Underlying()) {
if tc.Matches(pv) {
return true
}
@ -173,7 +177,7 @@ func (c *smAdapter) matches(id ProjectIdentifier, c2 Constraint, v Version) bool
case UnpairedVersion:
// Only way paired and unpaired could match is if they share an
// underlying rev
pv := c.pairVersion(id, tc)
pv := b.pairVersion(id, tc)
if pv == nil {
return false
}
@ -181,7 +185,7 @@ func (c *smAdapter) matches(id ProjectIdentifier, c2 Constraint, v Version) bool
case semverConstraint:
// Have to check all the possible versions for the rev to see if
// any match the semver constraint
for _, pv := range c.pairRevision(id, tv) {
for _, pv := range b.pairRevision(id, tv) {
if tc.Matches(pv) {
return true
}
@ -199,19 +203,19 @@ func (c *smAdapter) matches(id ProjectIdentifier, c2 Constraint, v Version) bool
case Revision, PairedVersion:
// Easy case for both - just pair the uv and see if it matches the revision
// constraint
pv := c.pairVersion(id, tv)
pv := b.pairVersion(id, tv)
if pv == nil {
return false
}
return tc.Matches(pv)
case UnpairedVersion:
// Both are unpaired versions. See if they share an underlying rev.
pv := c.pairVersion(id, tv)
pv := b.pairVersion(id, tv)
if pv == nil {
return false
}
pc := c.pairVersion(id, tc)
pc := b.pairVersion(id, tc)
if pc == nil {
return false
}
@ -220,12 +224,12 @@ func (c *smAdapter) matches(id ProjectIdentifier, c2 Constraint, v Version) bool
case semverConstraint:
// semverConstraint can't ever match a rev, but we do need to check
// if any other versions corresponding to this rev work.
pv := c.pairVersion(id, tv)
pv := b.pairVersion(id, tv)
if pv == nil {
return false
}
for _, ttv := range c.pairRevision(id, pv.Underlying()) {
for _, ttv := range b.pairRevision(id, pv.Underlying()) {
if c2.Matches(ttv) {
return true
}
@ -240,7 +244,7 @@ func (c *smAdapter) matches(id ProjectIdentifier, c2 Constraint, v Version) bool
}
// matchesAny is the authoritative version of Constraint.MatchesAny.
func (c *smAdapter) matchesAny(id ProjectIdentifier, c1, c2 Constraint) bool {
func (b *bridge) matchesAny(id ProjectIdentifier, c1, c2 Constraint) bool {
if c1.MatchesAny(c2) {
return true
}
@ -249,13 +253,13 @@ func (c *smAdapter) matchesAny(id ProjectIdentifier, c1, c2 Constraint) bool {
// more easily understood.
var uc1, uc2 Constraint
if v1, ok := c1.(Version); ok {
uc1 = c.vtu(id, v1)
uc1 = b.vtu(id, v1)
} else {
uc1 = c1
}
if v2, ok := c2.(Version); ok {
uc2 = c.vtu(id, v2)
uc2 = b.vtu(id, v2)
} else {
uc2 = c2
}
@ -264,7 +268,7 @@ func (c *smAdapter) matchesAny(id ProjectIdentifier, c1, c2 Constraint) bool {
}
// intersect is the authoritative version of Constraint.Intersect.
func (c *smAdapter) intersect(id ProjectIdentifier, c1, c2 Constraint) Constraint {
func (b *bridge) intersect(id ProjectIdentifier, c1, c2 Constraint) Constraint {
rc := c1.Intersect(c2)
if rc != none {
return rc
@ -274,13 +278,13 @@ func (c *smAdapter) intersect(id ProjectIdentifier, c1, c2 Constraint) Constrain
// more easily understood.
var uc1, uc2 Constraint
if v1, ok := c1.(Version); ok {
uc1 = c.vtu(id, v1)
uc1 = b.vtu(id, v1)
} else {
uc1 = c1
}
if v2, ok := c2.(Version); ok {
uc2 = c.vtu(id, v2)
uc2 = b.vtu(id, v2)
} else {
uc2 = c2
}
@ -293,29 +297,50 @@ func (c *smAdapter) intersect(id ProjectIdentifier, c1, c2 Constraint) Constrain
// This union may (and typically will) end up being nothing more than the single
// input version, but creating a versionTypeUnion guarantees that 'local'
// constraint checks (direct method calls) are authoritative.
func (c *smAdapter) vtu(id ProjectIdentifier, v Version) versionTypeUnion {
func (b *bridge) vtu(id ProjectIdentifier, v Version) versionTypeUnion {
switch tv := v.(type) {
case Revision:
return versionTypeUnion(c.pairRevision(id, tv))
return versionTypeUnion(b.pairRevision(id, tv))
case PairedVersion:
return versionTypeUnion(c.pairRevision(id, tv.Underlying()))
return versionTypeUnion(b.pairRevision(id, tv.Underlying()))
case UnpairedVersion:
pv := c.pairVersion(id, tv)
pv := b.pairVersion(id, tv)
if pv == nil {
return versionTypeUnion{tv}
}
return versionTypeUnion(c.pairRevision(id, pv.Underlying()))
return versionTypeUnion(b.pairRevision(id, pv.Underlying()))
}
return nil
}
// computeRootReach is a specialized, less stringent version of listExternal
// that allows for a bit of fuzziness in the source inputs.
//
// Specifically, we need to:
// - Analyze test-type files as well as typical source files
// - Make a best-effort attempt even if the code doesn't compile
// - Include main packages in the analysis
//
// Perhaps most important is that we don't want to have the results of this
// analysis be in any permanent cache, and we want to read directly from our
// potentially messy root project source location on disk. Together, this means
// that we can't ask the real SourceManager to do it.
func (b *bridge) computeRootReach(path string) ([]string, error) {
// TODO i now cannot remember the reasons why i thought being less stringent
// in the analysis was OK. so, for now, we just compute list of
// externally-touched packages.
return listExternalDeps(path, path, true)
}
// versionTypeUnion represents a set of versions that are, within the scope of
// this solve operation, equivalent. The simple case here is just a pair (normal
// version plus its underlying revision), but if a tag or branch point at the
// same rev, then they are equivalent - but only for the duration of this
// solve.
// this solver run, equivalent.
//
// The simple case here is just a pair - a normal version plus its underlying
// revision - but if a tag or branch point at the same rev, then we consider
// them equivalent. Again, however, this equivalency is short-lived; it must be
// re-assessed during every solver run.
//
// The union members are treated as being OR'd together: all constraint
// operations attempt each member, and will take the most open/optimistic

Просмотреть файл

@ -68,7 +68,7 @@ type SolveOpts struct {
func NewSolver(sm SourceManager, l *log.Logger) Solver {
return &solver{
sm: &smAdapter{sm: sm},
b: &bridge{sm: sm},
tl: l,
}
}
@ -88,11 +88,11 @@ type solver struct {
// Logger used exclusively for trace output, if the trace option is set.
tl *log.Logger
// An adapter around a standard SourceManager. The adapter does some local
// A bridge to the standard SourceManager. The adapter does some local
// caching of pre-sorted version lists, as well as translation between the
// full-on ProjectIdentifiers that the solver deals with and the simplified
// names a SourceManager operates on.
sm *smAdapter
b *bridge
// The list of projects currently "selected" - that is, they have passed all
// satisfiability checks, and are part of the current solution.
@ -159,8 +159,8 @@ func (s *solver) Solve(opts SolveOpts) (Result, error) {
//}
// Init/reset the smAdapter
s.sm.sortdown = opts.Downgrade
s.sm.vlists = make(map[ProjectName][]Version)
s.b.sortdown = opts.Downgrade
s.b.vlists = make(map[ProjectName][]Version)
s.o = opts
@ -190,7 +190,7 @@ func (s *solver) Solve(opts SolveOpts) (Result, error) {
// Initialize queues
s.sel = &selection{
deps: make(map[ProjectIdentifier][]Dependency),
sm: s.sm,
sm: s.b,
}
s.unsel = &unselected{
sl: make([]ProjectIdentifier, 0),
@ -279,15 +279,15 @@ func (s *solver) solve() ([]ProjectAtom, error) {
func (s *solver) createVersionQueue(id ProjectIdentifier) (*versionQueue, error) {
// If on the root package, there's no queue to make
if id.LocalName == s.rm.Name() {
return newVersionQueue(id, nilpa, s.sm)
return newVersionQueue(id, nilpa, s.b)
}
exists, err := s.sm.repoExists(id)
exists, err := s.b.repoExists(id)
if err != nil {
return nil, err
}
if !exists {
exists, err = s.sm.vendorCodeExists(id)
exists, err = s.b.vendorCodeExists(id)
if err != nil {
return nil, err
}
@ -309,7 +309,7 @@ func (s *solver) createVersionQueue(id ProjectIdentifier) (*versionQueue, error)
}
}
q, err := newVersionQueue(id, lockv, s.sm)
q, err := newVersionQueue(id, lockv, s.b)
if err != nil {
// TODO this particular err case needs to be improved to be ONLY for cases
// where there's absolutely nothing findable about a given project name
@ -378,7 +378,7 @@ func (s *solver) getLockVersionIfValid(id ProjectIdentifier) (ProjectAtom, error
// to be found and attempted in the repository. If it's only in vendor,
// though, then we have to try to use what's in the lock, because that's
// the only version we'll be able to get.
if exist, _ := s.sm.repoExists(id); exist {
if exist, _ := s.b.repoExists(id); exist {
return nilpa, nil
}
@ -405,7 +405,7 @@ func (s *solver) getLockVersionIfValid(id ProjectIdentifier) (ProjectAtom, error
if tv, ok := v.(Revision); ok {
// If we only have a revision from the root's lock, allow matching
// against other versions that have that revision
for _, pv := range s.sm.pairRevision(id, tv) {
for _, pv := range s.b.pairRevision(id, tv) {
if constraint.Matches(pv) {
v = pv
found = true
@ -450,7 +450,7 @@ func (s *solver) getDependenciesOf(pa ProjectAtom) ([]ProjectDep, error) {
if s.rm.Name() == pa.Ident.LocalName {
deps = append(s.rm.GetDependencies(), s.rm.GetDevDependencies()...)
} else {
info, err := s.sm.getProjectInfo(pa)
info, err := s.b.getProjectInfo(pa)
if err != nil {
// TODO revisit this once a decision is made about better-formed errors;
// question is, do we expect the fetcher to pass back simple errors, or
@ -579,8 +579,8 @@ func (s *solver) unselectedComparator(i, j int) bool {
// We can safely ignore an err from ListVersions here because, if there is
// an actual problem, it'll be noted and handled somewhere else saner in the
// solving algorithm.
ivl, _ := s.sm.listVersions(iname)
jvl, _ := s.sm.listVersions(jname)
ivl, _ := s.b.listVersions(iname)
jvl, _ := s.b.listVersions(jname)
iv, jv := len(ivl), len(jvl)
// Packages with fewer versions to pick from are less likely to benefit from

Просмотреть файл

@ -14,12 +14,12 @@ type versionQueue struct {
id ProjectIdentifier
pi []Version
fails []failedVersion
sm *smAdapter
sm *bridge
failed bool
hasLock, allLoaded bool
}
func newVersionQueue(id ProjectIdentifier, lockv ProjectAtom, sm *smAdapter) (*versionQueue, error) {
func newVersionQueue(id ProjectIdentifier, lockv ProjectAtom, sm *bridge) (*versionQueue, error) {
vq := &versionQueue{
id: id,
sm: sm,