Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve UpgradeDependencyVersion to update dependency version whose value is defined via a property from the parent POM. #4214

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,8 @@ public ResolvedDependency findDependency(Xml.Tag tag) {

@Nullable
public ResolvedManagedDependency findManagedDependency(Xml.Tag tag) {
String groupId = getResolutionResult().getPom().getValue(tag.getChildValue("groupId").orElse(getResolutionResult().getPom().getGroupId()));
String groupId = getResolutionResult().getPom().getValue(tag.getChildValue("groupId")
.orElse(getResolutionResult().getPom().getGroupId()));
String artifactId = getResolutionResult().getPom().getValue(tag.getChildValue("artifactId").orElse(""));
String classifier = getResolutionResult().getPom().getValue(tag.getChildValue("classifier").orElse(null));
if (groupId != null && artifactId != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.openrewrite.xml.XPathMatcher;
import org.openrewrite.xml.tree.Xml;

import java.nio.file.Path;
import java.util.*;

import static java.util.Collections.emptyList;
Expand All @@ -48,7 +49,7 @@
*/
@Value
@EqualsAndHashCode(callSuper = false)
public class UpgradeDependencyVersion extends ScanningRecipe<Set<GroupArtifact>> {
public class UpgradeDependencyVersion extends ScanningRecipe<UpgradeDependencyVersion.Accumulator> {
@EqualsAndHashCode.Exclude
transient MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this);

Expand Down Expand Up @@ -125,35 +126,104 @@ public String getDescription() {
}

@Override
public Set<GroupArtifact> getInitialValue(ExecutionContext ctx) {
return new HashSet<>();
public Accumulator getInitialValue(ExecutionContext ctx) {
return new Accumulator();
}

@Override
public TreeVisitor<?, ExecutionContext> getScanner(Set<GroupArtifact> projectArtifacts) {
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator accumulator) {
return new MavenIsoVisitor<ExecutionContext>() {
private final VersionComparator versionComparator =
requireNonNull(Semver.validate(newVersion, versionPattern).getValue());

@Override
public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
ResolvedPom pom = getResolutionResult().getPom();
projectArtifacts.add(new GroupArtifact(pom.getGroupId(), pom.getArtifactId()));
return document;
accumulator.projectArtifacts.add(new GroupArtifact(pom.getGroupId(), pom.getArtifactId()));
return super.visitDocument(document, ctx);
}

@Override
public Xml.Tag visitTag(final Xml.Tag tag, final ExecutionContext ctx) {
if (isDependencyTag(groupId, artifactId)) {
ResolvedDependency d = findDependency(tag);
if (d != null && d.getRepository() != null) {
// if the resolved dependency exists AND it does not represent an artifact that was parsed
// as a source file, attempt to find a new version.
try {
String newerVersion = findNewerVersion(d.getVersion(),
() -> downloadMetadata(d.getGroupId(), d.getArtifactId(), ctx), versionComparator, ctx);
if (newerVersion != null) {
Optional<Xml.Tag> version = tag.getChild("version");
if (version.isPresent()) {
String requestedVersion = d.getRequested().getVersion();
if (requestedVersion != null && requestedVersion.startsWith("${") &&
!implicitlyDefinedVersionProperties.contains(requestedVersion)) {
String propertyName = requestedVersion.substring(2, requestedVersion.length() - 1);
if (!getResolutionResult().getPom().getRequested().getProperties().containsKey(propertyName)) {
storeParentPomProperty(getResolutionResult().getParent(), propertyName, newerVersion);
}
}
}
}
} catch (MavenDownloadingException e) {
return e.warn(tag);
}
}
}
return super.visitTag(tag, ctx);
}

/**
* Recursively look for a parent POM that's still part of the sources, which contains the version property.
* If found, store the property in the accumulator, such that we can update that source file later.
* @param currentMavenResolutionResult the current Maven resolution result parent to search for the property
* @param propertyName the name of the property to update, if found in any the parent pom source file
* @param newerVersion the resolved newer version that any matching parent pom property should be updated to
*/
private void storeParentPomProperty(
@Nullable MavenResolutionResult currentMavenResolutionResult, String propertyName, String newerVersion) {
if (currentMavenResolutionResult == null) {
return; // No parent contained the property; might then be in the same source file, or an import BOM
}
Pom pom = currentMavenResolutionResult.getPom().getRequested();
if (pom.getSourcePath() == null) {
return; // Not a source file, so nothing to update
}
if (pom.getProperties().containsKey(propertyName)) {
accumulator.pomProperties.add(new PomProperty(pom.getSourcePath(), propertyName, newerVersion));
return; // Property found, so no further searching is needed
}
storeParentPomProperty(currentMavenResolutionResult.getParent(), propertyName, newerVersion);
}
};
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor(Set<GroupArtifact> projectArtifacts) {
VersionComparator versionComparator = Semver.validate(newVersion, versionPattern).getValue();
assert versionComparator != null;

public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator accumulator) {
return new MavenIsoVisitor<ExecutionContext>() {
private final XPathMatcher PROJECT_MATCHER = new XPathMatcher("/project");
private final VersionComparator versionComparator =
requireNonNull(Semver.validate(newVersion, versionPattern).getValue());

@Override
public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
Xml.Tag t = super.visitTag(tag, ctx);
try {
if (isDependencyTag(groupId, artifactId)) {
if (isPropertyTag()) {
Path pomSourcePath = getResolutionResult().getPom().getRequested().getSourcePath();
for (PomProperty pomProperty : accumulator.pomProperties) {
if (pomProperty.pomFilePath.equals(pomSourcePath) &&
pomProperty.propertyName.equals(tag.getName())) {
Optional<String> value = tag.getValue();
if (!value.isPresent() || !value.get().equals(pomProperty.propertyValue)) {
doAfterVisit(new ChangeTagValueVisitor<>(tag, pomProperty.propertyValue));
maybeUpdateModel();
}
break;
}
}
} else if (isDependencyTag(groupId, artifactId)) {
t = upgradeDependency(ctx, t);
} else if (isManagedDependencyTag(groupId, artifactId)) {
if (isManagedDependencyImportTag(groupId, artifactId)) {
Expand Down Expand Up @@ -220,7 +290,10 @@ private Xml.Tag upgradeDependency(ExecutionContext ctx, Xml.Tag t) throws MavenD
if (version.isPresent()) {
String requestedVersion = d.getRequested().getVersion();
if (requestedVersion != null && requestedVersion.startsWith("${") && !implicitlyDefinedVersionProperties.contains(requestedVersion)) {
doAfterVisit(new ChangePropertyValue(requestedVersion.substring(2, requestedVersion.length() - 1), newerVersion, overrideManagedVersion, false).getVisitor());
String propertyName = requestedVersion.substring(2, requestedVersion.length() - 1);
if (getResolutionResult().getPom().getRequested().getProperties().containsKey(propertyName)) {
doAfterVisit(new ChangePropertyValue(propertyName, newerVersion, overrideManagedVersion, false).getVisitor());
}
} else {
t = (Xml.Tag) new ChangeTagValueVisitor<>(version.get(), newerVersion).visitNonNull(t, 0, getCursor().getParentOrThrow());
}
Expand Down Expand Up @@ -257,7 +330,7 @@ private TreeVisitor<Xml, ExecutionContext> upgradeManagedDependency(Xml.Tag tag,
String artifactId = managedDependency.getArtifactId();
String version = managedDependency.getVersion();
if (version != null &&
!projectArtifacts.contains(new GroupArtifact(groupId, artifactId)) &&
!accumulator.projectArtifacts.contains(new GroupArtifact(groupId, artifactId)) &&
matchesGlob(groupId, UpgradeDependencyVersion.this.groupId) &&
matchesGlob(artifactId, UpgradeDependencyVersion.this.artifactId)) {
return upgradeVersion(ctx, t, managedDependency.getRequested().getVersion(), groupId, artifactId, version);
Expand All @@ -267,7 +340,7 @@ private TreeVisitor<Xml, ExecutionContext> upgradeManagedDependency(Xml.Tag tag,
if (dm.getBomGav() != null) {
String group = getResolutionResult().getPom().getValue(tag.getChildValue("groupId").orElse(getResolutionResult().getPom().getGroupId()));
String artifactId = getResolutionResult().getPom().getValue(tag.getChildValue("artifactId").orElse(""));
if (!projectArtifacts.contains(new GroupArtifact(group, artifactId))) {
if (!accumulator.projectArtifacts.contains(new GroupArtifact(group, artifactId))) {
ResolvedGroupArtifactVersion bom = dm.getBomGav();
if (Objects.equals(group, bom.getGroupId()) &&
Objects.equals(artifactId, bom.getArtifactId())) {
Expand Down Expand Up @@ -327,34 +400,55 @@ public TreeVisitor<Xml, ExecutionContext> upgradeVersion(ExecutionContext ctx, X
}

@Nullable
private String findNewerVersion(String groupId, String artifactId, String version, ExecutionContext ctx) throws MavenDownloadingException {
String finalVersion = !Semver.isVersion(version) ? "0.0.0" : version;
private String findNewerVersion(String groupId, String artifactId, String version, ExecutionContext ctx)
throws MavenDownloadingException {
return UpgradeDependencyVersion.this.findNewerVersion(
version, () -> downloadMetadata(groupId, artifactId, ctx), versionComparator, ctx);
}
};
}

// in the case of "latest.patch", a new version can only be derived if the
// current version is a semantic version
if (versionComparator instanceof LatestPatch && !versionComparator.isValid(finalVersion, finalVersion)) {
return null;
}
@Nullable
private String findNewerVersion(
String version, MavenMetadataFailures.MavenMetadataDownloader download, VersionComparator versionComparator, ExecutionContext ctx) throws MavenDownloadingException {
String finalVersion = !Semver.isVersion(version) ? "0.0.0" : version;

// in the case of "latest.patch", a new version can only be derived if the
// current version is a semantic version
if (versionComparator instanceof LatestPatch && !versionComparator.isValid(finalVersion, finalVersion)) {
return null;
}

try {
MavenMetadata mavenMetadata = metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, ctx));
List<String> versions = new ArrayList<>();
for (String v : mavenMetadata.getVersioning().getVersions()) {
if (versionComparator.isValid(finalVersion, v)) {
versions.add(v);
}
}
// handle upgrades from non semver versions like "org.springframework.cloud:spring-cloud-dependencies:Camden.SR5"
if (!Semver.isVersion(finalVersion) && !versions.isEmpty()) {
versions.sort(versionComparator);
return versions.get(versions.size() - 1);
}
return versionComparator.upgrade(finalVersion, versions).orElse(null);
} catch (IllegalStateException e) {
// this can happen when we encounter exotic versions
return null;
try {
MavenMetadata mavenMetadata = metadataFailures.insertRows(ctx, download);
List<String> versions = new ArrayList<>();
for (String v : mavenMetadata.getVersioning().getVersions()) {
if (versionComparator.isValid(finalVersion, v)) {
versions.add(v);
}
}
};
// handle upgrades from non semver versions like "org.springframework.cloud:spring-cloud-dependencies:Camden.SR5"
if (!Semver.isVersion(finalVersion) && !versions.isEmpty()) {
versions.sort(versionComparator);
return versions.get(versions.size() - 1);
}
return versionComparator.upgrade(finalVersion, versions).orElse(null);
} catch (IllegalStateException e) {
// this can happen when we encounter exotic versions
return null;
}
}

@Value
public static class Accumulator {
Set<GroupArtifact> projectArtifacts = new HashSet<>();
Set<PomProperty> pomProperties = new HashSet<>();
}

@Value
public static class PomProperty {
Path pomFilePath;
String propertyName;
String propertyValue;
}
}
Loading