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))