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