github.com/tiagovtristao/plz@v13.4.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 if a.Type == "" { 71 a.Type = "jar" 72 } 73 r.Lock() 74 defer r.Unlock() 75 if pom := r.pom(a); pom != nil { 76 return pom, false 77 } 78 // Override an empty version with a suggestion if we're going to create it. 79 if a.Version == "" { 80 a.SetVersion(a.SoftVersion) 81 } 82 pom := &PomXML{Artifact: *a} 83 r.poms[pom.unversioned] = append(r.poms[pom.unversioned], pom) 84 return pom, true 85 } 86 87 func (r *Resolver) pom(a *Artifact) *PomXML { 88 poms := r.poms[a.unversioned] 89 log.Debug("Resolving %s:%s: found %d candidates", a.GroupID, a.ArtifactID, len(poms)) 90 for _, pom := range poms { 91 if pom.SoftVersion != "" && a.SoftVersion == "" { 92 // Unfortunately we can't reuse these if we downloaded as a 'soft' version and 93 // we have a request for a real one now; otherwise we might end up using the 94 // latest because of this 'soft' guess and never fetch the requested one, but 95 // that depends on the order we run the two in which isn't always determinate. 96 continue 97 } 98 if a.Version == "" || pom.ParsedVersion.Matches(&a.ParsedVersion) { 99 log.Debug("Retrieving pom %s for %s", pom.Artifact, a) 100 return pom 101 } 102 } 103 return nil 104 } 105 106 // Submit adds this dependency to the queue for future resolution. 107 func (r *Resolver) Submit(dep *pomDependency) { 108 atomic.AddInt64(&r.liveTasks, 1) 109 r.tasks.Put(dep) 110 } 111 112 // process continually reads tasks from the queue and resolves them. 113 func (r *Resolver) process() { 114 for { 115 t, err := r.tasks.Get(1) 116 if err != nil { 117 log.Fatalf("%s", err) 118 } 119 dep := t[0].(*pomDependency) 120 log.Debug("beginning resolution of %s", dep.Artifact) 121 dep.Resolve(r.fetch) 122 count := atomic.AddInt64(&r.liveTasks, -1) 123 log.Debug("processed %s, %d tasks remaining", dep.Artifact, count) 124 if count <= 0 { 125 log.Debug("all tasks done, stopping") 126 break 127 } 128 } 129 } 130 131 // Mediate performs recursive dependency version mediation for all artifacts. 132 // Note that this is only done within each artifact, i.e. technically solving 133 // this properly is a fairly hard problem, and we avoid that by just attempting 134 // to solve within each individual artifact. 135 func (r *Resolver) Mediate() { 136 // All can be done in parallel 137 var wg sync.WaitGroup 138 for _, poms := range r.poms { 139 if len(poms) > 1 { // clearly unnecessary otherwise 140 wg.Add(1) 141 go func(poms []*PomXML) { 142 r.mediate(poms) 143 wg.Done() 144 }(poms) 145 } 146 } 147 wg.Wait() 148 } 149 150 func (r *Resolver) mediate(poms []*PomXML) { 151 // strip out parents which we don't need to worry about 152 nonParents := make([]*PomXML, 0, len(poms)) 153 for _, pom := range poms { 154 if !pom.isParent { 155 nonParents = append(nonParents, pom) 156 } 157 } 158 // Mediation might not be needed any more. 159 if len(nonParents) < 2 { 160 return 161 } 162 poms = nonParents 163 164 // Sort it so things are deterministic later 165 sort.Slice(poms, func(i, j int) bool { 166 return poms[i].ParsedVersion.LessThan(&poms[j].ParsedVersion) 167 }) 168 169 // Reduce these to just hard deps (any soft versions we assumed, so we don't care about) 170 hard := make([]*PomXML, 0, len(poms)) 171 for _, pom := range poms { 172 if pom.SoftVersion == "" { 173 hard = append(hard, pom) 174 } 175 } 176 if len(hard) == 1 { 177 // Only one hard dep, must be that 178 r.updateDeps(poms, hard[0]) 179 return 180 } else if len(hard) == 0 { 181 // No hard deps, can just use the first one we find 182 r.updateDeps(poms, poms[0]) 183 return 184 } 185 // Walk over once and calculate the intersection of all required versions 186 ver := hard[0].OriginalArtifact.ParsedVersion 187 for _, pom := range hard[1:] { 188 if !ver.Intersect(&pom.OriginalArtifact.ParsedVersion) { 189 // TODO(peterebden): Should really give some more detail here about what we can't satisfy 190 log.Fatalf("Unsatisfiable version constraints for %s:%s: %s", pom.GroupID, pom.ArtifactID, strings.Join(r.allVersions(hard), " ")) 191 } 192 } 193 // Find the first one that satisfies this version & use that. 194 for _, pom := range hard { 195 if pom.ParsedVersion.Matches(&ver) { 196 r.updateDeps(poms, pom) 197 return 198 } 199 } 200 log.Fatalf("Failed to find a suitable version for %s:%s from %s", poms[0].GroupID, poms[0].ArtifactID, hard) 201 } 202 203 // updateDeps updates all the dependencies to point to one particular artifact. 204 func (r *Resolver) updateDeps(poms []*PomXML, winner *PomXML) { 205 for _, pom := range poms { 206 for _, dependor := range pom.Dependors { 207 for _, dep := range dependor.Dependencies.Dependency { 208 if dep.GroupID == winner.GroupID && dep.ArtifactID == winner.ArtifactID { 209 dep.Pom = winner 210 } 211 } 212 } 213 } 214 } 215 216 // allVersions returns all the version descriptors in the given set of poms. 217 func (r *Resolver) allVersions(poms []*PomXML) []string { 218 ret := make([]string, len(poms)) 219 for i, pom := range poms { 220 ret[i] = pom.OriginalArtifact.ParsedVersion.Raw 221 } 222 return ret 223 }