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