github.com/golang/dep@v0.5.4/gps/bridge.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package gps 6 7 import ( 8 "fmt" 9 "os" 10 "path/filepath" 11 "sync/atomic" 12 13 "github.com/golang/dep/gps/pkgtree" 14 ) 15 16 // sourceBridge is an adapter to SourceManagers that tailor operations for a 17 // single solve run. 18 type sourceBridge interface { 19 // sourceBridge includes many methods from the SourceManager interface. 20 SourceExists(ProjectIdentifier) (bool, error) 21 SyncSourceFor(ProjectIdentifier) error 22 RevisionPresentIn(ProjectIdentifier, Revision) (bool, error) 23 ListPackages(ProjectIdentifier, Version) (pkgtree.PackageTree, error) 24 GetManifestAndLock(ProjectIdentifier, Version, ProjectAnalyzer) (Manifest, Lock, error) 25 ExportProject(ProjectIdentifier, Version, string) error 26 DeduceProjectRoot(ip string) (ProjectRoot, error) 27 28 listVersions(ProjectIdentifier) ([]Version, error) 29 verifyRootDir(path string) error 30 vendorCodeExists(ProjectIdentifier) (bool, error) 31 breakLock() 32 } 33 34 // bridge is an adapter around a proper SourceManager. It provides localized 35 // caching that's tailored to the requirements of a particular solve run. 36 // 37 // Finally, it provides authoritative version/constraint operations, ensuring 38 // that any possible approach to a match - even those not literally encoded in 39 // the inputs - is achieved. 40 type bridge struct { 41 // The underlying, adapted-to SourceManager 42 sm SourceManager 43 44 // The solver which we're assisting. 45 // 46 // The link between solver and bridge is circular, which is typically a bit 47 // awkward, but the bridge needs access to so many of the input arguments 48 // held by the solver that it ends up being easier and saner to do this. 49 s *solver 50 51 // Map of project root name to their available version list. This cache is 52 // layered on top of the proper SourceManager's cache; the only difference 53 // is that this keeps the versions sorted in the direction required by the 54 // current solve run. 55 vlists map[ProjectIdentifier][]Version 56 57 // Indicates whether lock breaking has already been run 58 lockbroken int32 59 60 // Whether to sort version lists for downgrade. 61 down bool 62 63 // The cancellation context provided to the solver. Threading it through the 64 // various solver methods is needlessly verbose so long as we maintain the 65 // lifetime guarantees that a solver can only be run once. 66 // TODO(sdboyer) uncomment this and thread it through SourceManager methods 67 //ctx context.Context 68 } 69 70 // mkBridge creates a bridge 71 func mkBridge(s *solver, sm SourceManager, down bool) *bridge { 72 return &bridge{ 73 sm: sm, 74 s: s, 75 down: down, 76 vlists: make(map[ProjectIdentifier][]Version), 77 } 78 } 79 80 func (b *bridge) GetManifestAndLock(id ProjectIdentifier, v Version, an ProjectAnalyzer) (Manifest, Lock, error) { 81 if b.s.rd.isRoot(id.ProjectRoot) { 82 return b.s.rd.rm, b.s.rd.rl, nil 83 } 84 85 b.s.mtr.push("b-gmal") 86 m, l, e := b.sm.GetManifestAndLock(id, v, an) 87 b.s.mtr.pop() 88 return m, l, e 89 } 90 91 func (b *bridge) listVersions(id ProjectIdentifier) ([]Version, error) { 92 if vl, exists := b.vlists[id]; exists { 93 return vl, nil 94 } 95 96 b.s.mtr.push("b-list-versions") 97 pvl, err := b.sm.ListVersions(id) 98 if err != nil { 99 b.s.mtr.pop() 100 return nil, err 101 } 102 103 vl := hidePair(pvl) 104 if b.down { 105 SortForDowngrade(vl) 106 } else { 107 SortForUpgrade(vl) 108 } 109 110 b.vlists[id] = vl 111 b.s.mtr.pop() 112 return vl, nil 113 } 114 115 func (b *bridge) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) { 116 b.s.mtr.push("b-rev-present-in") 117 i, e := b.sm.RevisionPresentIn(id, r) 118 b.s.mtr.pop() 119 return i, e 120 } 121 122 func (b *bridge) SourceExists(id ProjectIdentifier) (bool, error) { 123 b.s.mtr.push("b-source-exists") 124 i, e := b.sm.SourceExists(id) 125 b.s.mtr.pop() 126 return i, e 127 } 128 129 func (b *bridge) vendorCodeExists(id ProjectIdentifier) (bool, error) { 130 fi, err := os.Stat(filepath.Join(b.s.rd.dir, "vendor", string(id.ProjectRoot))) 131 if err != nil { 132 return false, err 133 } else if fi.IsDir() { 134 return true, nil 135 } 136 137 return false, nil 138 } 139 140 // listPackages lists all the packages contained within the given project at a 141 // particular version. 142 // 143 // The root project is handled separately, as the source manager isn't 144 // responsible for that code. 145 func (b *bridge) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) { 146 if b.s.rd.isRoot(id.ProjectRoot) { 147 return b.s.rd.rpt, nil 148 } 149 150 b.s.mtr.push("b-list-pkgs") 151 pt, err := b.sm.ListPackages(id, v) 152 b.s.mtr.pop() 153 return pt, err 154 } 155 156 func (b *bridge) ExportProject(id ProjectIdentifier, v Version, path string) error { 157 panic("bridge should never be used to ExportProject") 158 } 159 160 // verifyRoot ensures that the provided path to the project root is in good 161 // working condition. This check is made only once, at the beginning of a solve 162 // run. 163 func (b *bridge) verifyRootDir(path string) error { 164 if fi, err := os.Stat(path); err != nil { 165 return badOptsFailure(fmt.Sprintf("could not read project root (%s): %s", path, err)) 166 } else if !fi.IsDir() { 167 return badOptsFailure(fmt.Sprintf("project root (%s) is a file, not a directory", path)) 168 } 169 170 return nil 171 } 172 173 func (b *bridge) DeduceProjectRoot(ip string) (ProjectRoot, error) { 174 b.s.mtr.push("b-deduce-proj-root") 175 pr, e := b.sm.DeduceProjectRoot(ip) 176 b.s.mtr.pop() 177 return pr, e 178 } 179 180 // breakLock is called when the solver has to break a version recorded in the 181 // lock file. It prefetches all the projects in the solver's lock, so that the 182 // information is already on hand if/when the solver needs it. 183 // 184 // Projects that have already been selected are skipped, as it's generally unlikely that the 185 // solver will have to backtrack through and fully populate their version queues. 186 func (b *bridge) breakLock() { 187 // No real conceivable circumstance in which multiple calls are made to 188 // this, but being that this is the entrance point to a bunch of async work, 189 // protect it with an atomic CAS in case things change in the future. 190 // 191 // We avoid using a sync.Once here, as there's no reason for other callers 192 // to block until completion. 193 if !atomic.CompareAndSwapInt32(&b.lockbroken, 0, 1) { 194 return 195 } 196 197 for _, lp := range b.s.rd.rl.Projects() { 198 if _, is := b.s.sel.selected(lp.Ident()); !is { 199 pi, v := lp.Ident(), lp.Version() 200 go func() { 201 // Sync first 202 b.sm.SyncSourceFor(pi) 203 // Preload the package info for the locked version, too, as 204 // we're more likely to need that 205 b.sm.ListPackages(pi, v) 206 }() 207 } 208 } 209 } 210 211 func (b *bridge) SyncSourceFor(id ProjectIdentifier) error { 212 // we don't track metrics here b/c this is often called in its own goroutine 213 // by the solver, and the metrics design is for wall time on a single thread 214 return b.sm.SyncSourceFor(id) 215 }