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  }