github.phpd.cn/thought-machine/please@v12.2.0+incompatible/tools/please_maven/maven/pom.go (about)

     1  package maven
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/xml"
     6  	"fmt"
     7  	"io"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/Workiva/go-datastructures/queue"
    14  	"github.com/jessevdk/go-flags"
    15  	"gopkg.in/op/go-logging.v1"
    16  )
    17  
    18  var log = logging.MustGetLogger("maven")
    19  
    20  type unversioned struct {
    21  	GroupID    string `xml:"groupId"`
    22  	ArtifactID string `xml:"artifactId"`
    23  	Type       string `xml:"type"`
    24  }
    25  
    26  // An Artifact is a model of a Maven artifact.
    27  type Artifact struct {
    28  	unversioned
    29  	// Raw version as found in XML
    30  	Version string `xml:"version"`
    31  	// A full-blown Maven version spec. If the version is not parseable (which is allowed
    32  	// to happen :( ) then we just use Version to interpret it as a string.
    33  	ParsedVersion Version
    34  	// Trailing specifier e.g. "@aar"
    35  	Specifier string
    36  	isParent  bool
    37  	// A "soft version", for dependencies that don't have one specified and we want to
    38  	// provide a hint about what to do in that case.
    39  	SoftVersion string
    40  	// somewhat awkward, we use this to pass through excluded artifacts from above.
    41  	Exclusions []Artifact `xml:"exclusions>exclusion"`
    42  }
    43  
    44  // GroupPath returns the group ID as a path.
    45  func (a *Artifact) GroupPath() string {
    46  	return strings.Replace(a.GroupID, ".", "/", -1)
    47  }
    48  
    49  // MetadataPath returns the path to the metadata XML file for this artifact.
    50  func (a *Artifact) MetadataPath() string {
    51  	return a.GroupPath() + "/" + a.ArtifactID + "/maven-metadata.xml"
    52  }
    53  
    54  // Path returns the path to an artifact that we'd download.
    55  func (a *Artifact) Path(suffix string) string {
    56  	return a.GroupPath() + "/" + a.ArtifactID + "/" + a.ParsedVersion.Path + "/" + a.ArtifactID + "-" + a.ParsedVersion.Path + suffix
    57  }
    58  
    59  // PomPath returns the path to the pom.xml for this artifact.
    60  func (a *Artifact) PomPath() string {
    61  	return a.Path(".pom")
    62  }
    63  
    64  // SourcePath returns the path to the sources jar for this artifact.
    65  func (a *Artifact) SourcePath() string {
    66  	return a.Path("-sources.jar")
    67  }
    68  
    69  // String prints this artifact as a Maven identifier (i.e. GroupID:ArtifactID:Version)
    70  func (a Artifact) String() string {
    71  	s := a.GroupID + ":" + a.ArtifactID + ":" + a.ParsedVersion.Path
    72  	if a.Type != "" && a.Type != "jar" {
    73  		s += "@" + a.Type
    74  	}
    75  	return s
    76  }
    77  
    78  // FromID loads this artifact from a Maven id.
    79  func (a *Artifact) FromID(id string) error {
    80  	split := strings.Split(id, ":")
    81  	if len(split) != 3 {
    82  		return fmt.Errorf("Invalid Maven artifact id %s; must be in the form group:artifact:version", id)
    83  	}
    84  	a.GroupID = split[0]
    85  	a.ArtifactID = split[1]
    86  	a.Version = split[2]
    87  	if index := strings.IndexRune(a.Version, '@'); index != -1 {
    88  		if t := a.Version[index+1:]; t != "jar" {
    89  			a.Type = t
    90  			a.Version = a.Version[:index]
    91  		}
    92  	}
    93  	a.ParsedVersion.Unmarshal(a.Version)
    94  	return nil
    95  }
    96  
    97  // SetVersion updates the version on this artifact.
    98  func (a *Artifact) SetVersion(ver string) {
    99  	a.ParsedVersion.Unmarshal(ver)
   100  	a.Version = a.ParsedVersion.Path
   101  }
   102  
   103  // UnmarshalFlag implements the flags.Unmarshaler interface.
   104  // This lets us use Artifact instances directly as flags.
   105  func (a *Artifact) UnmarshalFlag(value string) error {
   106  	if err := a.FromID(value); err != nil {
   107  		return &flags.Error{Type: flags.ErrMarshal, Message: err.Error()}
   108  	}
   109  	return nil
   110  }
   111  
   112  // IsExcluded returns true if the given artifact is in this one's list of exclusions.
   113  func (a *Artifact) IsExcluded(a2 *Artifact) bool {
   114  	for _, excl := range a.Exclusions {
   115  		if excl.GroupID == a2.GroupID && excl.ArtifactID == a2.ArtifactID {
   116  			return true
   117  		}
   118  	}
   119  	return false
   120  }
   121  
   122  type pomProperty struct {
   123  	XMLName xml.Name
   124  	Value   string `xml:",chardata"`
   125  }
   126  
   127  // A PomXML models a Maven pom.xml and its contents.
   128  type PomXML struct {
   129  	Artifact
   130  	sync.Mutex
   131  	OriginalArtifact     Artifact
   132  	Dependencies         pomDependencies `xml:"dependencies"`
   133  	DependencyManagement struct {
   134  		Dependencies pomDependencies `xml:"dependencies"`
   135  	} `xml:"dependencyManagement"`
   136  	Properties struct {
   137  		Property []pomProperty `xml:",any"`
   138  	} `xml:"properties"`
   139  	Licences struct {
   140  		Licence []struct {
   141  			Name string `xml:"name"`
   142  		} `xml:"license"`
   143  	} `xml:"licenses"`
   144  	Parent        Artifact `xml:"parent"`
   145  	PropertiesMap map[string]string
   146  	Dependors     []*PomXML
   147  	HasSources    bool
   148  }
   149  
   150  type pomDependency struct {
   151  	Artifact
   152  	Pom      *PomXML
   153  	Dependor *PomXML
   154  	Scope    string `xml:"scope"`
   155  	Optional bool   `xml:"optional"`
   156  }
   157  
   158  type pomDependencies struct {
   159  	Dependency []*pomDependency `xml:"dependency"`
   160  }
   161  
   162  // A MetadataXML models a Maven metadata.xml file and its contents.
   163  type MetadataXML struct {
   164  	Version    string `xml:"version"`
   165  	Versioning struct {
   166  		Latest   string `xml:"latest"`
   167  		Release  string `xml:"release"`
   168  		Versions struct {
   169  			Version []string `xml:"version"`
   170  		} `xml:"versions"`
   171  	} `xml:"versioning"`
   172  	Group, Artifact string
   173  }
   174  
   175  // LatestVersion returns the latest available version of a package
   176  func (metadata *MetadataXML) LatestVersion() string {
   177  	if metadata.Versioning.Release != "" {
   178  		return metadata.Versioning.Release
   179  	} else if metadata.Versioning.Latest != "" {
   180  		log.Warning("No release version for %s:%s, using latest", metadata.Group, metadata.Artifact)
   181  		return metadata.Versioning.Latest
   182  	} else if metadata.Version != "" {
   183  		log.Warning("No release version for %s:%s", metadata.Group, metadata.Artifact)
   184  		return metadata.Version
   185  	}
   186  	log.Fatalf("Can't find a version for %s:%s", metadata.Group, metadata.Artifact)
   187  	return ""
   188  }
   189  
   190  // HasVersion returns true if the given package has the specified version.
   191  func (metadata *MetadataXML) HasVersion(version string) bool {
   192  	for _, v := range metadata.Versioning.Versions.Version {
   193  		if v == version {
   194  			return true
   195  		}
   196  	}
   197  	return false
   198  }
   199  
   200  // Unmarshal reads a metadata object from raw XML. It dies on any error.
   201  func (metadata *MetadataXML) Unmarshal(content []byte) {
   202  	if err := xml.Unmarshal(content, metadata); err != nil {
   203  		log.Fatalf("Error parsing metadata XML: %s\n", err)
   204  	}
   205  }
   206  
   207  // AddProperty adds a property (typically from a parent or wherever), without overwriting.
   208  func (pom *PomXML) AddProperty(property pomProperty) {
   209  	if _, present := pom.PropertiesMap[property.XMLName.Local]; !present {
   210  		pom.PropertiesMap[property.XMLName.Local] = property.Value
   211  		pom.Properties.Property = append(pom.Properties.Property, property)
   212  	}
   213  }
   214  
   215  // replaceVariables a Maven variable in the given string.
   216  func (pom *PomXML) replaceVariables(s string) string {
   217  	if strings.HasPrefix(s, "${") {
   218  		if prop, present := pom.PropertiesMap[s[2:len(s)-1]]; !present {
   219  			log.Fatalf("Failed property lookup %s: %s\n", s, pom.PropertiesMap)
   220  		} else if strings.HasPrefix(prop, "${") && prop != s {
   221  			// Some property values can themselves be more properties...
   222  			return pom.replaceVariables(prop)
   223  		} else {
   224  			return prop
   225  		}
   226  	}
   227  	return s
   228  }
   229  
   230  // Unmarshal parses a downloaded pom.xml. This is of course less trivial than you would hope.
   231  func (pom *PomXML) Unmarshal(f *Fetch, response []byte) {
   232  	pom.OriginalArtifact = pom.Artifact // Keep a copy of this for later
   233  	decoder := xml.NewDecoder(bytes.NewReader(response))
   234  	// This is not beautiful; it assumes all inputs are utf-8 compatible, essentially, in order to handle
   235  	// ISO-8859-1 inputs. Possibly we should use a real conversion although it's a little unclear what the
   236  	// suggested way of doing that or packages to use are.
   237  	decoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) { return input, nil }
   238  	if err := decoder.Decode(pom); err != nil {
   239  		log.Fatalf("Error parsing XML response: %s\n", err)
   240  	}
   241  	// Clean up strings in case they have spaces
   242  	pom.GroupID = strings.TrimSpace(pom.GroupID)
   243  	pom.ArtifactID = strings.TrimSpace(pom.ArtifactID)
   244  	pom.SetVersion(strings.TrimSpace(pom.Version))
   245  	for i, licence := range pom.Licences.Licence {
   246  		pom.Licences.Licence[i].Name = strings.TrimSpace(licence.Name)
   247  	}
   248  	// Handle properties nonsense, because of course it doesn't work this out for us...
   249  	pom.PropertiesMap = map[string]string{}
   250  	for _, prop := range pom.Properties.Property {
   251  		pom.PropertiesMap[prop.XMLName.Local] = prop.Value
   252  	}
   253  	// There are also some properties that aren't described by the above - "project" is a bit magic.
   254  	pom.PropertiesMap["groupId"] = pom.GroupID
   255  	pom.PropertiesMap["artifactId"] = pom.ArtifactID
   256  	pom.PropertiesMap["version"] = pom.Version
   257  	pom.PropertiesMap["project.groupId"] = pom.GroupID
   258  	pom.PropertiesMap["project.version"] = pom.Version
   259  	if pom.Parent.ArtifactID != "" {
   260  		if pom.Parent.GroupID == pom.GroupID && pom.Parent.ArtifactID == pom.ArtifactID {
   261  			log.Fatalf("Circular dependency: %s:%s:%s specifies itself as its own parent", pom.GroupID, pom.ArtifactID, pom.Version)
   262  		}
   263  		// Must inherit variables from the parent.
   264  		pom.Parent.isParent = true
   265  		pom.Parent.ParsedVersion.Unmarshal(pom.Parent.Version)
   266  		parent := f.Pom(&pom.Parent)
   267  		for _, prop := range parent.Properties.Property {
   268  			pom.AddProperty(prop)
   269  		}
   270  		if len(pom.Licences.Licence) == 0 {
   271  			pom.Licences.Licence = parent.Licences.Licence
   272  		}
   273  	}
   274  	pom.Version = pom.replaceVariables(pom.Version)
   275  	// Arbitrarily, some pom files have this different structure with the extra "dependencyManagement" level.
   276  	pom.Dependencies.Dependency = append(pom.Dependencies.Dependency, pom.DependencyManagement.Dependencies.Dependency...)
   277  	pom.Dependencies.Dependency = pom.stripTestDependencies(f, pom.Dependencies.Dependency)
   278  	if !pom.isParent { // Don't fetch dependencies of parents, that just gets silly.
   279  		pom.HasSources = f.HasSources(&pom.Artifact)
   280  		for _, dep := range pom.Dependencies.Dependency {
   281  			pom.handleDependency(f, dep)
   282  		}
   283  	}
   284  }
   285  
   286  // stripTestDependencies removes all deps that have test scope or otherwise wouldn't be included.
   287  func (pom *PomXML) stripTestDependencies(f *Fetch, deps []*pomDependency) []*pomDependency {
   288  	ret := make([]*pomDependency, 0, len(deps))
   289  	for _, dep := range deps {
   290  		if dep.Scope == "test" || dep.Scope == "system" {
   291  			log.Info("Not fetching %s:%s (dep of %s) because of scope", dep.GroupID, dep.ArtifactID, pom.Artifact)
   292  			continue
   293  		}
   294  		if dep.Optional && !f.ShouldInclude(dep.ArtifactID) {
   295  			log.Info("Not fetching optional dependency %s:%s (of %s)", dep.GroupID, dep.ArtifactID, pom.Artifact)
   296  			continue
   297  		}
   298  		ret = append(ret, dep)
   299  	}
   300  	return ret
   301  }
   302  
   303  func (pom *PomXML) handleDependency(f *Fetch, dep *pomDependency) {
   304  	dep.GroupID = pom.replaceVariables(dep.GroupID)
   305  	dep.ArtifactID = pom.replaceVariables(dep.ArtifactID)
   306  	dep.SetVersion(pom.replaceVariables(dep.Version))
   307  	dep.Dependor = pom
   308  	if f.IsExcluded(dep.ArtifactID) {
   309  		log.Info("Not fetching %s, is excluded by command-line parameter", dep.Artifact)
   310  		return
   311  	} else if pom.OriginalArtifact.IsExcluded(&dep.Artifact) {
   312  		log.Info("Not fetching %s, is excluded by its parent", dep.Artifact)
   313  		return
   314  	}
   315  	log.Info("Fetching %s (depended on by %s)", dep.Artifact, pom.Artifact)
   316  	f.Resolver.Submit(dep)
   317  }
   318  
   319  func (dep *pomDependency) Resolve(f *Fetch) {
   320  	if dep.Version == "" {
   321  		// If no version is specified, we can take any version that we've already found.
   322  		if pom := f.Resolver.Pom(&dep.Artifact); pom != nil {
   323  			dep.Pom = pom
   324  			return
   325  		}
   326  
   327  		// Not 100% sure what the logic should really be here; for example, jacoco
   328  		// seems to leave these underspecified and expects the same version, but other
   329  		// things seem to expect the latest. Most likely it is some complex resolution
   330  		// logic, but we'll take a stab at the same if the group matches and the same
   331  		// version exists, otherwise we'll take the latest.
   332  		if metadata := f.Metadata(&dep.Artifact); dep.GroupID == dep.Dependor.GroupID && metadata.HasVersion(dep.Dependor.Version) {
   333  			dep.SoftVersion = dep.Dependor.Version
   334  		} else {
   335  			dep.SoftVersion = metadata.LatestVersion()
   336  		}
   337  	}
   338  	dep.Pom = f.Pom(&dep.Artifact)
   339  	if dep.Dependor != nil {
   340  		dep.Pom.Lock()
   341  		defer dep.Pom.Unlock()
   342  		dep.Pom.Dependors = append(dep.Pom.Dependors, dep.Dependor)
   343  	}
   344  }
   345  
   346  // AllDependencies returns all the dependencies for this package.
   347  func (pom *PomXML) AllDependencies() []*PomXML {
   348  	deps := make([]*PomXML, 0, len(pom.Dependencies.Dependency))
   349  	for _, dep := range pom.Dependencies.Dependency {
   350  		if dep.Pom != nil {
   351  			deps = append(deps, dep.Pom)
   352  		}
   353  	}
   354  	return deps
   355  }
   356  
   357  // AllLicences returns all the licences for this package.
   358  func (pom *PomXML) AllLicences() []string {
   359  	licences := make([]string, len(pom.Licences.Licence))
   360  	for i, licence := range pom.Licences.Licence {
   361  		licences[i] = licence.Name
   362  	}
   363  	return licences
   364  }
   365  
   366  // Compare implements the queue.Item interface to define the order we resolve dependencies in.
   367  func (dep *pomDependency) Compare(item queue.Item) int {
   368  	dep2 := item.(*pomDependency)
   369  	// Primarily we order by versions; if the version is not given, it comes after one that does.
   370  	if dep.Version == "" && dep2.Version != "" {
   371  		return 1
   372  	} else if dep.Version != "" && dep2.Version == "" {
   373  		return -1
   374  	}
   375  	id1 := dep.String()
   376  	id2 := dep2.String()
   377  	if id1 < id2 {
   378  		return -1
   379  	} else if id1 > id2 {
   380  		return 1
   381  	}
   382  	return 0
   383  }
   384  
   385  // A Version is a Maven version spec (see https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm),
   386  // including range reference info (https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm)
   387  // The above is pretty light on detail unfortunately (like how do you know the difference between a BuildNumber
   388  // and a Qualifier) so we really are taking a bit of a guess here.
   389  // If only semver had existed back then...
   390  //
   391  // Note that we don't (yet?) support broken ranges like (,1.0],[1.2,).
   392  type Version struct {
   393  	Min, Max  VersionPart
   394  	Raw, Path string
   395  	Parsed    bool
   396  }
   397  
   398  // A VersionPart forms part of a Version; it can be either an upper or lower bound.
   399  type VersionPart struct {
   400  	Qualifier                 string
   401  	Major, Minor, Incremental int
   402  	Inclusive                 bool
   403  	Set                       bool
   404  }
   405  
   406  // Unmarshal parses a Version from a raw string.
   407  // Errors are not reported since literally anything can appear in a Maven version specifier;
   408  // an input like "thirty-five ham and cheese sandwiches" is simply treated as a string.
   409  func (v *Version) Unmarshal(in string) {
   410  	v.Raw = in                      // Always.
   411  	v.Path = strings.Trim(in, "[]") // needs more thought.
   412  	// Try to match the simple single versions first.
   413  	if submatches := singleVersionRegex.FindStringSubmatch(in); len(submatches) == 7 {
   414  		// Special case for no specifiers; that indicates >=
   415  		if submatches[1] == "[" || (submatches[1] == "" && submatches[6] == "") {
   416  			v.Min = versionPart(submatches[2:6], true)
   417  			v.Max.Major = 9999 // arbitrarily large
   418  			v.Parsed = true
   419  		}
   420  		if submatches[6] == "]" {
   421  			v.Max = versionPart(submatches[2:6], true)
   422  			v.Parsed = true
   423  		}
   424  	} else if submatches := doubleVersionRegex.FindStringSubmatch(in); len(submatches) == 11 {
   425  		v.Min = versionPart(submatches[2:6], submatches[1] == "[")
   426  		v.Max = versionPart(submatches[6:10], submatches[10] == "]")
   427  		v.Parsed = true
   428  	}
   429  }
   430  
   431  // Matches returns true if this version matches the spec given by ver.
   432  // Note that this is not symmetric; if this version is 1.0 and ver is <= 2.0, this is true;
   433  // conversely it is false if this is 2.0 and ver is <= 1.0.
   434  // It further treats this version as exact using its Min attribute, since that's roughly how Maven does it.
   435  func (v *Version) Matches(ver *Version) bool {
   436  	if v.Parsed || ver.Parsed {
   437  		return (v.Min.LessThan(ver.Max) && v.Min.GreaterThan(ver.Min))
   438  	}
   439  	// If we fail to parse it, they are treated as strings.
   440  	return strings.Trim(v.Raw, "[]") == strings.Trim(ver.Raw, "[]") || v.Raw >= ver.Raw
   441  }
   442  
   443  // LessThan returns true if this version is less than the given version.
   444  func (v *Version) LessThan(ver *Version) bool {
   445  	if v.Parsed {
   446  		return v.Min.LessThan(ver.Min)
   447  	}
   448  	return v.Raw < ver.Raw
   449  }
   450  
   451  // Intersect reduces v to the intersection of itself and v2.
   452  // It returns true if the resulting version is still conceptually satisfiable.
   453  func (v *Version) Intersect(v2 *Version) bool {
   454  	if !v.Parsed || !v2.Parsed {
   455  		// Fallback logic; one or both versions aren't parsed, so we do string comparisons.
   456  		if strings.Trim(v.Raw, "[]") == strings.Trim(v2.Raw, "[]") {
   457  			return true // If they're identical they always intersect.
   458  		} else if strings.HasPrefix(v.Raw, "[") || strings.HasPrefix(v2.Raw, "[") {
   459  			return false // No intersection if one specified an exact version
   460  		} else if v2.Raw > v.Raw {
   461  			*v = *v2
   462  		}
   463  		return true // still OK, we take the highest of the two.
   464  	}
   465  	if v2.Min.Set && v2.Min.GreaterThan(v.Min) {
   466  		v.Min = v2.Min
   467  	}
   468  	if v2.Max.Set && v2.Max.LessThan(v.Max) {
   469  		v.Max = v2.Max
   470  	}
   471  	return v.Min.LessThan(v.Max)
   472  }
   473  
   474  // Equals returns true if the two versions are equal.
   475  func (v1 VersionPart) Equals(v2 VersionPart) bool {
   476  	return v1.Major == v2.Major && v1.Minor == v2.Minor && v1.Incremental == v2.Incremental && v1.Qualifier == v2.Qualifier
   477  }
   478  
   479  // LessThan returns true if v1 < v2 (or <= if v2.Inclusive)
   480  func (v1 VersionPart) LessThan(v2 VersionPart) bool {
   481  	return v1.Major < v2.Major ||
   482  		(v1.Major == v2.Major && v1.Minor < v2.Minor) ||
   483  		(v1.Major == v2.Major && v1.Minor == v2.Minor && v1.Incremental < v2.Incremental) ||
   484  		(v1.Major == v2.Major && v1.Minor == v2.Minor && v1.Incremental == v2.Incremental && v1.Qualifier < v2.Qualifier) ||
   485  		(v2.Inclusive && v1.Equals(v2))
   486  }
   487  
   488  // GreaterThan returns true if v1 > v2 (or >= if v2.Inclusive)
   489  func (v1 VersionPart) GreaterThan(v2 VersionPart) bool {
   490  	return v1.Major > v2.Major ||
   491  		(v1.Major == v2.Major && v1.Minor > v2.Minor) ||
   492  		(v1.Major == v2.Major && v1.Minor == v2.Minor && v1.Incremental > v2.Incremental) ||
   493  		(v1.Major == v2.Major && v1.Minor == v2.Minor && v1.Incremental == v2.Incremental && v1.Qualifier > v2.Qualifier) ||
   494  		(v2.Inclusive && v1.Equals(v2))
   495  }
   496  
   497  // versionPart returns a new VersionPart given some raw strings.
   498  func versionPart(parts []string, inclusive bool) VersionPart {
   499  	v := VersionPart{
   500  		Major:     mustInt(parts[0]),
   501  		Qualifier: parts[3],
   502  		Inclusive: inclusive,
   503  		Set:       true,
   504  	}
   505  	if parts[1] != "" {
   506  		v.Minor = mustInt(parts[1])
   507  	}
   508  	if parts[2] != "" {
   509  		v.Incremental = mustInt(parts[2])
   510  	}
   511  	return v
   512  }
   513  
   514  func mustInt(in string) int {
   515  	i, err := strconv.Atoi(in)
   516  	if err != nil {
   517  		log.Fatalf("Bad version number: %s", err)
   518  	}
   519  	return i
   520  }
   521  
   522  const versionRegex = `([0-9]+)(?:\.([0-9]+))?(?:\.([0-9]+))?(-[^\]\]]+)?`
   523  
   524  var singleVersionRegex = regexp.MustCompile(fmt.Sprintf(`^(\[|\(,)?%s(\]|,\))?$`, versionRegex))
   525  var doubleVersionRegex = regexp.MustCompile(fmt.Sprintf(`^(\[|\()%s,%s(\]|\))$`, versionRegex, versionRegex))