github.phpd.cn/thought-machine/please@v12.2.0+incompatible/tools/please_maven/maven/resolver.go (about) 1 package maven 2 3 import ( 4 "sort" 5 "strings" 6 "sync" 7 "sync/atomic" 8 9 "github.com/Workiva/go-datastructures/queue" 10 ) 11 12 // A Resolver resolves Maven artifacts into specific versions. 13 // Ultimately we should really be using a proper SAT solver here but we're 14 // not rushing it in favour of forcing manual disambiguation (and the majority 15 // of things going wrong are due to Maven madness anyway). 16 type Resolver struct { 17 sync.Mutex 18 // Contains all the poms we've fetched. 19 // Note that these are keyed by a subset of the artifact struct, so we 20 // can do version-independent lookups. 21 poms map[unversioned][]*PomXML 22 // Reference to a thing that fetches for us. 23 fetch *Fetch 24 // Task queue that prioritises upcoming tasks. 25 tasks *queue.PriorityQueue 26 // Count of live tasks. 27 liveTasks int64 28 } 29 30 // NewResolver constructs and returns a new Resolver instance. 31 func NewResolver(f *Fetch) *Resolver { 32 return &Resolver{ 33 poms: map[unversioned][]*PomXML{}, 34 fetch: f, 35 tasks: queue.NewPriorityQueue(100, false), 36 } 37 } 38 39 // Run runs the given number of worker threads until everything is resolved. 40 func (r *Resolver) Run(artifacts []Artifact, concurrency int) { 41 // Kick off original artifacts 42 for _, a := range artifacts { 43 r.Submit(&pomDependency{Artifact: a}) 44 } 45 46 // We use this channel as a slightly overblown semaphore; when any one 47 // of the goroutines finishes, we're done. At least one will return but 48 // not necessarily more than that. 49 ch := make(chan bool, concurrency) 50 for i := 0; i < concurrency; i++ { 51 go func() { 52 r.process() 53 ch <- true 54 }() 55 } 56 <-ch 57 } 58 59 // Pom returns a pom for an artifact. The version doesn't have to be specified exactly. 60 // If one doesn't currently exist it will return nil. 61 func (r *Resolver) Pom(a *Artifact) *PomXML { 62 r.Lock() 63 defer r.Unlock() 64 return r.pom(a) 65 } 66 67 // CreatePom returns a pom for an artifact. If a suitable match doesn't exist, a new one 68 // will be created. The second return value is true if a new one was created. 69 func (r *Resolver) CreatePom(a *Artifact) (*PomXML, bool) { 70 r.Lock() 71 defer r.Unlock() 72 if pom := r.pom(a); pom != nil { 73 return pom, false 74 } 75 // Override an empty version with a suggestion if we're going to create it. 76 if a.Version == "" { 77 a.SetVersion(a.SoftVersion) 78 } 79 pom := &PomXML{Artifact: *a} 80 r.poms[pom.unversioned] = append(r.poms[pom.unversioned], pom) 81 return pom, true 82 } 83 84 func (r *Resolver) pom(a *Artifact) *PomXML { 85 poms := r.poms[a.unversioned] 86 log.Debug("Resolving %s:%s: found %d candidates", a.GroupID, a.ArtifactID, len(poms)) 87 for _, pom := range poms { 88 if pom.SoftVersion != "" && a.SoftVersion == "" { 89 // Unfortunately we can't reuse these if we downloaded as a 'soft' version and 90 // we have a request for a real one now; otherwise we might end up using the 91 // latest because of this 'soft' guess and never fetch the requested one, but 92 // that depends on the order we run the two in which isn't always determinate. 93 continue 94 } 95 if a.Version == "" || pom.ParsedVersion.Matches(&a.ParsedVersion) { 96 log.Debug("Retrieving pom %s for %s", pom.Artifact, a) 97 return pom 98 } 99 } 100 return nil 101 } 102 103 // Submit adds this dependency to the queue for future resolution. 104 func (r *Resolver) Submit(dep *pomDependency) { 105 atomic.AddInt64(&r.liveTasks, 1) 106 r.tasks.Put(dep) 107 } 108 109 // process continually reads tasks from the queue and resolves them. 110 func (r *Resolver) process() { 111 for { 112 t, err := r.tasks.Get(1) 113 if err != nil { 114 log.Fatalf("%s", err) 115 } 116 dep := t[0].(*pomDependency) 117 log.Debug("beginning resolution of %s", dep.Artifact) 118 dep.Resolve(r.fetch) 119 count := atomic.AddInt64(&r.liveTasks, -1) 120 log.Debug("processed %s, %d tasks remaining", dep.Artifact, count) 121 if count <= 0 { 122 log.Debug("all tasks done, stopping") 123 break 124 } 125 } 126 } 127 128 // Mediate performs recursive dependency version mediation for all artifacts. 129 // Note that this is only done within each artifact, i.e. technically solving 130 // this properly is a fairly hard problem, and we avoid that by just attempting 131 // to solve within each individual artifact. 132 func (r *Resolver) Mediate() { 133 // All can be done in parallel 134 var wg sync.WaitGroup 135 for _, poms := range r.poms { 136 if len(poms) > 1 { // clearly unnecessary otherwise 137 wg.Add(1) 138 go func(poms []*PomXML) { 139 r.mediate(poms) 140 wg.Done() 141 }(poms) 142 } 143 } 144 wg.Wait() 145 } 146 147 func (r *Resolver) mediate(poms []*PomXML) { 148 // strip out parents which we don't need to worry about 149 nonParents := make([]*PomXML, 0, len(poms)) 150 for _, pom := range poms { 151 if !pom.isParent { 152 nonParents = append(nonParents, pom) 153 } 154 } 155 // Mediation might not be needed any more. 156 if len(nonParents) < 2 { 157 return 158 } 159 poms = nonParents 160 161 // Sort it so things are deterministic later 162 sort.Slice(poms, func(i, j int) bool { 163 return poms[i].ParsedVersion.LessThan(&poms[j].ParsedVersion) 164 }) 165 166 // Reduce these to just hard deps (any soft versions we assumed, so we don't care about) 167 hard := make([]*PomXML, 0, len(poms)) 168 for _, pom := range poms { 169 if pom.SoftVersion == "" { 170 hard = append(hard, pom) 171 } 172 } 173 if len(hard) == 1 { 174 // Only one hard dep, must be that 175 r.updateDeps(poms, hard[0]) 176 return 177 } else if len(hard) == 0 { 178 // No hard deps, can just use the first one we find 179 r.updateDeps(poms, poms[0]) 180 return 181 } 182 // Walk over once and calculate the intersection of all required versions 183 ver := hard[0].OriginalArtifact.ParsedVersion 184 for _, pom := range hard[1:] { 185 if !ver.Intersect(&pom.OriginalArtifact.ParsedVersion) { 186 // TODO(peterebden): Should really give some more detail here about what we can't satisfy 187 log.Fatalf("Unsatisfiable version constraints for %s:%s: %s", pom.GroupID, pom.ArtifactID, strings.Join(r.allVersions(hard), " ")) 188 } 189 } 190 // Find the first one that satisfies this version & use that. 191 for _, pom := range hard { 192 if pom.ParsedVersion.Matches(&ver) { 193 r.updateDeps(poms, pom) 194 return 195 } 196 } 197 log.Fatalf("Failed to find a suitable version for %s:%s from %s", poms[0].GroupID, poms[0].ArtifactID, hard) 198 } 199 200 // updateDeps updates all the dependencies to point to one particular artifact. 201 func (r *Resolver) updateDeps(poms []*PomXML, winner *PomXML) { 202 for _, pom := range poms { 203 for _, dependor := range pom.Dependors { 204 for _, dep := range dependor.Dependencies.Dependency { 205 if dep.GroupID == winner.GroupID && dep.ArtifactID == winner.ArtifactID { 206 dep.Pom = winner 207 } 208 } 209 } 210 } 211 } 212 213 // allVersions returns all the version descriptors in the given set of poms. 214 func (r *Resolver) allVersions(poms []*PomXML) []string { 215 ret := make([]string, len(poms)) 216 for i, pom := range poms { 217 ret[i] = pom.OriginalArtifact.ParsedVersion.Raw 218 } 219 return ret 220 }