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