Skip to content

Commit

Permalink
Move markers from gradle tooling model into rewrite-gradle, use org.o…
Browse files Browse the repository at this point in the history
…penrewrite.gradle.toolingapi.Assertions#withToolingApi (#4023)

* Extract DependencyVersionSelector
* Move markers from gradle tooling model into rewrite-gradle, use org.openrewrite.gradle.toolingapi.Assertions#withToolingApi
* Prepare for upgrading transitive gradle dependencies
  • Loading branch information
jkschneider authored Feb 20, 2024
1 parent fc4714a commit 3bd8271
Show file tree
Hide file tree
Showing 28 changed files with 1,144 additions and 483 deletions.
4 changes: 3 additions & 1 deletion rewrite-gradle/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ dependencies {
api("org.jetbrains:annotations:latest.release")
compileOnly(project(":rewrite-test"))
implementation(project(":rewrite-properties"))
implementation("org.openrewrite.gradle.tooling:model:$rewriteVersion")

compileOnly("org.codehaus.groovy:groovy:latest.release")
compileOnly(gradleApi())
Expand All @@ -59,6 +58,9 @@ dependencies {
// because gradle-api fatjars this implementation already
exclude("ch.qos.logback", "logback-classic")
}

testImplementation("org.openrewrite.gradle.tooling:model:latest.release")

testImplementation("com.squareup.okhttp3:mockwebserver:4.+")

testRuntimeOnly("org.codehaus.groovy:groovy:latest.release")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import org.openrewrite.maven.internal.MavenPomDownloader;
import org.openrewrite.maven.table.MavenMetadataFailures;
import org.openrewrite.maven.tree.*;
import org.openrewrite.semver.*;
import org.openrewrite.tree.ParseError;

import java.util.*;
Expand Down Expand Up @@ -188,8 +187,8 @@ static G.CompilationUnit addDependency(
}),
newRequested));
if (newGdc.isCanBeResolved() && resolvedGav != null) {
newGdc = newGdc.withResolved(ListUtils.concat(
ListUtils.map(gdc.getResolved(), resolved -> {
newGdc = newGdc.withDirectResolved(ListUtils.concat(
ListUtils.map(gdc.getDirectResolved(), resolved -> {
// Remove any existing dependency with the same group and artifact id
if (Objects.equals(resolved.getGroupId(), resolvedGav.getGroupId()) && Objects.equals(resolved.getArtifactId(), resolvedGav.getArtifactId())) {
return null;
Expand Down Expand Up @@ -231,8 +230,8 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
resolvedVersion = version;
} else {
try {
resolvedVersion = resolveDependencyVersion(groupId, artifactId, "0", version, versionPattern, gp.getMavenRepositories(), metadataFailures, ctx)
.orElse(null);
resolvedVersion = new DependencyVersionSelector(metadataFailures, gp)
.select(new GroupArtifact(groupId, artifactId), configuration, version, versionPattern, ctx);
} catch (MavenDownloadingException e) {
return e.warn(m);
}
Expand Down Expand Up @@ -365,42 +364,4 @@ private DependencyStyle autodetectDependencyStyle(List<Statement> statements) {

return string >= map ? DependencyStyle.String : DependencyStyle.Map;
}

public static Optional<String> resolveDependencyVersion(String groupId, String artifactId, String currentVersion, @Nullable String newVersion, @Nullable String versionPattern,
List<MavenRepository> repositories, @Nullable MavenMetadataFailures metadataFailures, ExecutionContext ctx) throws MavenDownloadingException {
VersionComparator versionComparator = StringUtils.isBlank(newVersion) ?
new LatestRelease(versionPattern) :
requireNonNull(Semver.validate(newVersion, versionPattern).getValue());

Optional<String> version;
if (versionComparator instanceof ExactVersion) {
version = versionComparator.upgrade(currentVersion, singletonList(newVersion));
} else if (versionComparator instanceof LatestPatch && !versionComparator.isValid(currentVersion, currentVersion)) {
// in the case of "latest.patch", a new version can only be derived if the
// current version is a semantic version
return Optional.empty();
} else {
version = findNewerVersion(groupId, artifactId, currentVersion, versionComparator, repositories, metadataFailures, ctx);
}
return version;
}

private static Optional<String> findNewerVersion(String groupId, String artifactId, String version, VersionComparator versionComparator,
List<MavenRepository> repositories, @Nullable MavenMetadataFailures metadataFailures, ExecutionContext ctx) throws MavenDownloadingException {
try {
MavenMetadata mavenMetadata = metadataFailures == null ?
downloadMetadata(groupId, artifactId, repositories, ctx) :
metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, repositories, ctx));
return versionComparator.upgrade(version, mavenMetadata.getVersioning().getVersions());
} catch (IllegalStateException e) {
// this can happen when we encounter exotic versions
return Optional.empty();
}
}

private static MavenMetadata downloadMetadata(String groupId, String artifactId, List<MavenRepository> repositories, ExecutionContext ctx) throws MavenDownloadingException {
return new MavenPomDownloader(ctx)
.downloadMetadata(new GroupArtifact(groupId, artifactId), null,
repositories);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.gradle;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.*;
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
import org.openrewrite.gradle.marker.GradleProject;
import org.openrewrite.gradle.search.FindGradleProject;
import org.openrewrite.groovy.GroovyVisitor;
import org.openrewrite.groovy.tree.G;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.tree.J;
import org.openrewrite.marker.Markup;
import org.openrewrite.maven.MavenDownloadingException;
import org.openrewrite.maven.table.MavenMetadataFailures;
import org.openrewrite.maven.tree.GroupArtifact;
import org.openrewrite.maven.tree.ResolvedDependency;
import org.openrewrite.semver.DependencyMatcher;
import org.openrewrite.semver.Semver;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.util.Collections.singletonList;

@Value
@EqualsAndHashCode(callSuper = false)
public class AddDirectDependencyToUpgradeTransitiveVersion extends Recipe {

@EqualsAndHashCode.Exclude
transient MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this);

@Option(displayName = "Group",
description = "The first part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression.",
example = "com.fasterxml.jackson*")
String groupId;

@Option(displayName = "Artifact",
description = "The second part of a dependency coordinate `com.google.guava:guava:VERSION`. This can be a glob expression.",
example = "jackson-module*")
String artifactId;

@Option(displayName = "Version",
description = "An exact version number or node-style semver selector used to select the version number. " +
"You can also use `latest.release` for the latest available version and `latest.patch` if " +
"the current version is a valid semantic version. For more details, you can look at the documentation " +
"page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors). " +
"Defaults to `latest.release`.",
example = "29.X",
required = false)
@Nullable
String version;

@Option(displayName = "Version pattern",
description = "Allows version selection to be extended beyond the original Node Semver semantics. So for example," +
"Setting 'newVersion' to \"25-29\" can be paired with a metadata pattern of \"-jre\" to select Guava 29.0-jre",
example = "-jre",
required = false)
@Nullable
String versionPattern;

@Override
public String getDisplayName() {
return "Upgrade transitive Gradle dependencies";
}

@Override
public String getDescription() {
return "Upgrades the version of a transitive dependency in a Gradle build file. " +
"There are many ways to do this in Gradle, so the mechanism for upgrading a " +
"transitive dependency must be considered carefully depending on your style " +
"of dependency management.";
}

@Override
public Validated<Object> validate() {
Validated<Object> validated = super.validate();
if (version != null) {
validated = validated.and(Semver.validate(version, versionPattern));
}
return validated;
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
DependencyMatcher dependencyMatcher = new DependencyMatcher(groupId, artifactId, null);
return Preconditions.check(new FindGradleProject(FindGradleProject.SearchCriteria.Marker), new GroovyVisitor<ExecutionContext>() {
GradleProject gradleProject;

@Override
public J visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) {
gradleProject = cu.getMarkers().findFirst(GradleProject.class)
.orElseThrow(() -> new IllegalStateException("Unable to find GradleProject marker."));

Map<GroupArtifact, List<GradleDependencyConfiguration>> toUpdate = new HashMap<>();

DependencyVersionSelector versionSelector = new DependencyVersionSelector(metadataFailures, gradleProject);
for (GradleDependencyConfiguration configuration : gradleProject.getConfigurations()) {
for (ResolvedDependency resolvedDependency : configuration.getResolved()) {
if (resolvedDependency.getDepth() > 0 &&
dependencyMatcher.matches(resolvedDependency.getGroupId(), resolvedDependency.getArtifactId(), resolvedDependency.getVersion())) {

try {
String selected = versionSelector.select(resolvedDependency.getGav(), configuration.getName(),
version, versionPattern, ctx);
if (!resolvedDependency.getVersion().equals(selected)) {
toUpdate.merge(new GroupArtifact(groupId, artifactId), singletonList(configuration), (existing, update) -> {
List<GradleDependencyConfiguration> all = ListUtils.concatAll(existing, update);
all.removeIf(c -> {
for (GradleDependencyConfiguration config : all) {
if (c.allExtendsFrom().contains(config)) {
return true;
}
}
return false;
});
return all;
});
}
} catch (MavenDownloadingException e) {
return Markup.warn(cu, e);
}
}
}
}

return super.visitCompilationUnit(cu, ctx);
}
});
}
}
Loading

0 comments on commit 3bd8271

Please sign in to comment.