зеркало из https://github.com/golang/dep.git
Refactor VersionQueue
Mostly good now, but still have the "exist"-ness thing to be handled in the PackageManager (soon to be SourceManager).
This commit is contained in:
Родитель
36ad5dcbf3
Коммит
dd31877cd9
92
solver.go
92
solver.go
|
@ -3,8 +3,6 @@ package vsolver
|
|||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
)
|
||||
|
||||
type SolveFailure uint
|
||||
|
@ -27,9 +25,9 @@ type solver struct {
|
|||
latest map[ProjectIdentifier]struct{}
|
||||
sel *selection
|
||||
unsel *unselected
|
||||
versions []*VersionQueue
|
||||
rs Spec
|
||||
rl Lock
|
||||
versions []*VersionQueue
|
||||
}
|
||||
|
||||
func (s *solver) Solve(rootSpec Spec, rootLock Lock, toUpgrade []ProjectIdentifier) Result {
|
||||
|
@ -68,6 +66,7 @@ func (s *solver) doSolve() ([]ProjectID, error) {
|
|||
// backtracking succeeded, move to the next unselected ref
|
||||
continue
|
||||
}
|
||||
// TODO handle failures, lolzies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +74,7 @@ func (s *solver) doSolve() ([]ProjectID, error) {
|
|||
func (s *solver) createVersionQueue(ref ProjectIdentifier) (*VersionQueue, error) {
|
||||
// If on the root package, there's no queue to make
|
||||
if ref == s.rs.ID {
|
||||
return NewVersionQueue(ref, nil, nil), nil
|
||||
return NewVersionQueue(ref, nil, s.pf)
|
||||
}
|
||||
|
||||
if !s.pf.ProjectExists(ref) {
|
||||
|
@ -92,26 +91,29 @@ func (s *solver) createVersionQueue(ref ProjectIdentifier) (*VersionQueue, error
|
|||
return nil, err // pass it straight back up
|
||||
}
|
||||
|
||||
// TODO probably use an actual container/list
|
||||
// TODO should probably just make the fetcher return semver already, and
|
||||
// update ProjectID to suit
|
||||
var list []*ProjectID
|
||||
for _, pi := range versions {
|
||||
_, err := semver.NewVersion(pi.Version)
|
||||
if err != nil {
|
||||
// couldn't parse version; moving on
|
||||
// TODO log this at all? would be info/debug-type, at best
|
||||
continue
|
||||
}
|
||||
// this is the lockv, push it to the front
|
||||
if lockv.Version == pi.Version {
|
||||
list = append([]*ProjectID{&pi}, list...)
|
||||
} else {
|
||||
list = append(list, &pi)
|
||||
}
|
||||
//var list []*ProjectID
|
||||
//for _, pi := range versions {
|
||||
//_, err := semver.NewVersion(pi.Version)
|
||||
//if err != nil {
|
||||
//// couldn't parse version; moving on
|
||||
//// TODO log this at all? would be info/debug-type, at best
|
||||
//continue
|
||||
//}
|
||||
//// this is the lockv, push it to the front
|
||||
//if lockv.Version == pi.Version {
|
||||
//list = append([]*ProjectID{&pi}, list...)
|
||||
//} else {
|
||||
//list = append(list, &pi)
|
||||
//}
|
||||
//}
|
||||
|
||||
q, err := NewVersionQueue(ref, lockv, s.pf)
|
||||
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
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := NewVersionQueue(ref, s.checkVersion, list)
|
||||
return q, s.findValidVersion(q)
|
||||
}
|
||||
|
||||
|
@ -124,6 +126,7 @@ func (s *solver) findValidVersion(q *VersionQueue) error {
|
|||
panic("version queue is empty, should not happen")
|
||||
}
|
||||
|
||||
// TODO worth adding an isEmpty()-type method to VersionQueue?
|
||||
for {
|
||||
err = s.checkVersion(q.current())
|
||||
if err == nil {
|
||||
|
@ -131,7 +134,11 @@ func (s *solver) findValidVersion(q *VersionQueue) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
q.next()
|
||||
err = q.advance()
|
||||
if err != nil {
|
||||
// Error on advance; have to bail out
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s.fail(s.sel.getDependenciesOn(q.current().ID)[0].Depender.ID)
|
||||
|
@ -156,18 +163,18 @@ func (s *solver) getLockVersionIfValid(ref ProjectIdentifier) *ProjectID {
|
|||
return nil
|
||||
}
|
||||
|
||||
// createProjectRevisionIterator creates an iterator that retrieves metadata for
|
||||
// a given ref, one version at a time.
|
||||
func (s *solver) createProjectRevisionIterator(ref ProjectIdentifier, lockv *ProjectID) (*projectRevisionIterator, error) {
|
||||
|
||||
// TODO keep track of all the available revs, as reported by the pf?
|
||||
return &projectRevisionIterator{
|
||||
cur: lockv,
|
||||
haslock: lockv == nil,
|
||||
ref: ref,
|
||||
pf: s.pf,
|
||||
}, nil
|
||||
}
|
||||
// getAllowedVersions retrieves an ordered list of versions from the source manager for
|
||||
// the given identifier. It returns an error if the named project does not exist.
|
||||
//
|
||||
// ...REALLY NOT NECESSARY, VERSIONQUEUE CAN JUST DO IT DIRECTLY?
|
||||
//
|
||||
//func (s *solver) getAllowedVersions(ref ProjectIdentifier) (ids []*ProjectID, err error) {
|
||||
//ids, err = s.pf.ListVersions(ref)
|
||||
//if err != nil {
|
||||
//// TODO ...more err handling here?
|
||||
//return nil, err
|
||||
//}
|
||||
//}
|
||||
|
||||
func (s *solver) checkVersion(pi *ProjectID) error {
|
||||
if pi == nil {
|
||||
|
@ -354,18 +361,3 @@ func (s *solver) fail(id ProjectIdentifier) {
|
|||
func (s *solver) choose(id ProjectID) {
|
||||
|
||||
}
|
||||
|
||||
type projectRevisionIterator struct {
|
||||
cur *ProjectID
|
||||
haslock bool
|
||||
ref ProjectIdentifier
|
||||
pf PackageFetcher
|
||||
}
|
||||
|
||||
func (pri *projectRevisionIterator) next() bool {
|
||||
// TODO pull the next item from the pf and put it into the current item
|
||||
}
|
||||
|
||||
func (pri *projectRevisionIterator) current() *ProjectID {
|
||||
return pri.cur
|
||||
}
|
||||
|
|
177
types.go
177
types.go
|
@ -1,7 +1,5 @@
|
|||
package vsolver
|
||||
|
||||
import "container/list"
|
||||
|
||||
// The type of the version - branch, revision, or version
|
||||
type VersionType uint8
|
||||
|
||||
|
@ -35,6 +33,72 @@ const (
|
|||
FromCache InfoLevel = 1 << iota
|
||||
)
|
||||
|
||||
// ProjectExistence values represent the extent to which a project "exists."
|
||||
type ProjectExistence uint8
|
||||
|
||||
const (
|
||||
// DoesNotExist indicates that a particular project URI cannot be located,
|
||||
// at any level. It is represented as 1, rather than 0, to differentiate it
|
||||
// from the zero-value (which is ExistenceUnknown).
|
||||
DoesNotExist ProjectExistence = 1 << iota
|
||||
|
||||
// ExistsInLock indicates that a project exists (i.e., is mentioned in) a
|
||||
// lock file.
|
||||
// TODO not sure if it makes sense to have this IF it's just the source
|
||||
// manager's responsibility for putting this together - the implication is
|
||||
// that this is the root lock file, right?
|
||||
ExistsInLock
|
||||
|
||||
// ExistsInVendor indicates that a project exists in a vendor directory at
|
||||
// the predictable location based on import path. It does NOT imply, much
|
||||
// less guarantee, any of the following:
|
||||
// - That the code at the expected location under vendor is at the version
|
||||
// given in a lock file
|
||||
// - That the code at the expected location under vendor is from the
|
||||
// expected upstream project at all
|
||||
// - That, if this flag is not present, the project does not exist at some
|
||||
// unexpected/nested location under vendor
|
||||
// - That the full repository history is available. In fact, the
|
||||
// assumption should be that if only this flag is on, the full repository
|
||||
// history is likely not available locally
|
||||
//
|
||||
// In short, the information encoded in this flag should in no way be
|
||||
// construed as exhaustive.
|
||||
ExistsInVendor
|
||||
|
||||
// ExistsInCache indicates that a project exists on-disk in the local cache.
|
||||
// It does not guarantee that an upstream exists, thus it cannot imply
|
||||
// that the cache is at all correct - up-to-date, or even of the expected
|
||||
// upstream project repository.
|
||||
//
|
||||
// Additionally, this refers only to the existence of the local repository
|
||||
// itself; it says nothing about the existence or completeness of the
|
||||
// separate metadata cache.
|
||||
ExistsInCache
|
||||
|
||||
// ExistsUpstream indicates that a project repository was locatable at the
|
||||
// path provided by a project's URI (a base import path).
|
||||
ExistsUpstream
|
||||
|
||||
// Indicates that the upstream project, in addition to existing, is also
|
||||
// accessible.
|
||||
//
|
||||
// Different hosting providers treat unauthorized access differently:
|
||||
// GitHub, for example, returns 404 (or the equivalent) when attempting unauthorized
|
||||
// access, whereas BitBucket returns 403 (or 302 login redirect). Thus,
|
||||
// while the ExistsUpstream and UpstreamAccessible bits should always only
|
||||
// be on or off together when interacting with Github, it is possible that a
|
||||
// BitBucket provider might have ExistsUpstream, but not UpstreamAccessible.
|
||||
//
|
||||
// For most purposes, non-existence and inaccessibility are treated the
|
||||
// same, but clearly delineating the two allows slightly improved UX.
|
||||
UpstreamAccessible
|
||||
|
||||
// The zero value; indicates that no work has yet been done to determine the
|
||||
// existence level of a project.
|
||||
ExistenceUnknown ProjectExistence = 0
|
||||
)
|
||||
|
||||
type DepSpec struct {
|
||||
Identifier, VersionSpec string
|
||||
}
|
||||
|
@ -115,73 +179,82 @@ type Result struct {
|
|||
}
|
||||
|
||||
type VersionQueue struct {
|
||||
l *list.List
|
||||
rt VersionType
|
||||
ref ProjectIdentifier
|
||||
pi []*ProjectID
|
||||
failed bool
|
||||
vf func(*ProjectID) error
|
||||
//pri *projectRevisionIterator
|
||||
//pf PackageFetcher // vQ may need to grab specific version info, at times
|
||||
ref ProjectIdentifier
|
||||
pi []*ProjectID
|
||||
failed bool
|
||||
hasLock, allLoaded bool
|
||||
pf PackageFetcher
|
||||
//avf func(ProjectIdentifier) ([]*ProjectID, error)
|
||||
}
|
||||
|
||||
func NewVersionQueue(ref ProjectIdentifier, validator func(*ProjectID) error, pi []*ProjectID) *VersionQueue {
|
||||
return &VersionQueue{
|
||||
//func NewVersionQueue(ref ProjectIdentifier, lockv *ProjectID, avf func(ProjectIdentifier, *ProjectID) []*ProjectID) (*VersionQueue, error) {
|
||||
func NewVersionQueue(ref ProjectIdentifier, lockv *ProjectID, pf PackageFetcher) (*VersionQueue, error) {
|
||||
vq = &VersionQueue{
|
||||
ref: ref,
|
||||
//pri: pri,
|
||||
vf: validator,
|
||||
//avf: avf,
|
||||
pf: pf,
|
||||
}
|
||||
|
||||
if lockv != nil {
|
||||
vq.hasLock = true
|
||||
vq.pi = append(vq.pi, lockv)
|
||||
} else {
|
||||
var err error
|
||||
//vq.pi, err = vq.avf(vq.ref, nil)
|
||||
// TODO should probably just make the fetcher return semver already, and
|
||||
// update ProjectID to suit
|
||||
vq.pi, err = vq.pf.ListVersions(vq.ref)
|
||||
if err != nil {
|
||||
// TODO pushing this error this early entails that we
|
||||
// unconditionally deep scan (e.g. vendor), as well as hitting the
|
||||
// network.
|
||||
return nil, err
|
||||
}
|
||||
vq.allLoaded = true
|
||||
}
|
||||
|
||||
return vq, nil
|
||||
}
|
||||
|
||||
func (vq *VersionQueue) current() *ProjectID {
|
||||
if len(vq.pi > 0) {
|
||||
return vq.pi[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vq *VersionQueue) next() bool {
|
||||
func (vq *VersionQueue) advance() (err error) {
|
||||
// The current version may have failed, but the next one hasn't
|
||||
vq.failed = false
|
||||
|
||||
// TODO ordering of queue for highest/lowest version choice logic - do it
|
||||
// internally here, or is it better elsewhere?
|
||||
for k, pi := range vq.pi {
|
||||
err := vq.vf(pi)
|
||||
if err == nil {
|
||||
vq.pi = vq.pi[k:]
|
||||
return true
|
||||
if !vq.allLoaded {
|
||||
// Can only get here if no lock was initially provided, so we know we
|
||||
// should have that
|
||||
lockv := vq.pi[0]
|
||||
|
||||
//vq.pi, err = vq.avf(vq.ref)
|
||||
vq.pi, err = vq.pf.ListVersions(vq.ref)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// search for and remove locked version
|
||||
// TODO should be able to avoid O(n) here each time...if it matters
|
||||
for k, pi := range vq.pi {
|
||||
if pi == lockv {
|
||||
// GC-safe deletion for slice w/pointer elements
|
||||
vq.pi, vq.pi[len(vq.pi)-1] = append(vq.pi[:k], vq.pi[k+1:]...), nil
|
||||
}
|
||||
}
|
||||
// TODO keep this err somewhere?
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
if len(vq.pi > 0) {
|
||||
vq.pi = vq.pi[1:]
|
||||
}
|
||||
|
||||
func (vq *VersionQueue) Back() *ProjectID {
|
||||
return vq.l.Back().Value.(*ProjectID)
|
||||
// normal end of queue. we don't error; it's left to the caller to infer an
|
||||
// empty queue w/a subsequent call to current(), which will return nil.
|
||||
// TODO this approach kinda...sucks
|
||||
return
|
||||
}
|
||||
|
||||
func (vq *VersionQueue) Front() *ProjectID {
|
||||
return vq.l.Front().Value.(*ProjectID)
|
||||
}
|
||||
func (vq *VersionQueue) Len() int {
|
||||
return vq.l.Len()
|
||||
}
|
||||
func (vq *VersionQueue) InsertAfter(v, mark *ProjectID) bool {
|
||||
return nil != vq.l.InsertAfter(v, *list.Element{Value: mark})
|
||||
}
|
||||
|
||||
func (vq *VersionQueue) InsertBefore(v, mark *ProjectID) bool {
|
||||
return nil != vq.l.InsertBefore(v, *list.Element{Value: mark})
|
||||
}
|
||||
|
||||
func (vq *VersionQueue) MoveAfter(v, mark *ProjectID) {}
|
||||
func (vq *VersionQueue) MoveBefore(v, mark *ProjectID) {}
|
||||
func (vq *VersionQueue) MoveToBack(v *ProjectID) {}
|
||||
func (vq *VersionQueue) MoveToFront(v *ProjectID) {}
|
||||
func (vq *VersionQueue) PushBack(v *ProjectID) {}
|
||||
func (vq *VersionQueue) PushFront(v *ProjectID) {}
|
||||
func (vq *VersionQueue) Remove(v *ProjectID) bool {}
|
||||
|
||||
//func (vq *VersionQueue) PushBackList(other *VersionQueue) {}
|
||||
//func (vq *VersionQueue) PushFrontList(other *VersionQueue) {}
|
||||
|
|
Загрузка…
Ссылка в новой задаче