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:
Sam Boyer 2016-03-15 13:07:16 -04:00
Родитель 36ad5dcbf3
Коммит dd31877cd9
2 изменённых файлов: 167 добавлений и 102 удалений

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

@ -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
Просмотреть файл

@ -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) {}