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  }