From adab8898ff608eb0b404d0709ad266af38b94e18 Mon Sep 17 00:00:00 2001 From: Shannon Pamperl Date: Wed, 10 Jul 2024 18:27:19 -0500 Subject: [PATCH] Add Gradle Enterprise to Develocity 3.17+ migration recipe for Gradle --- .../MigrateGradleEnterpriseToDevelocity.java | 258 ++++++++++++++++++ ...grateGradleEnterpriseToDevelocityTest.java | 146 ++++++++++ 2 files changed, 404 insertions(+) create mode 100644 rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/MigrateGradleEnterpriseToDevelocity.java create mode 100644 rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/MigrateGradleEnterpriseToDevelocityTest.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/MigrateGradleEnterpriseToDevelocity.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/MigrateGradleEnterpriseToDevelocity.java new file mode 100644 index 000000000000..20d72c62869a --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/MigrateGradleEnterpriseToDevelocity.java @@ -0,0 +1,258 @@ +package org.openrewrite.gradle.plugins; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.gradle.GradleParser; +import org.openrewrite.gradle.IsSettingsGradle; +import org.openrewrite.groovy.GroovyIsoVisitor; +import org.openrewrite.groovy.tree.G; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.style.IntelliJ; +import org.openrewrite.java.style.TabsAndIndentsStyle; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; +import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; + +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static java.util.Collections.singletonList; + +@Value +@EqualsAndHashCode(callSuper = false) +public class MigrateGradleEnterpriseToDevelocity extends Recipe { + @Option(displayName = "Plugin 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 = "3.x", + required = false) + @Nullable + String version; + + @Override + public String getDisplayName() { + return "Migrate from Gradle Enterprise to Develocity"; + } + + @Override + public String getDescription() { + return "Migrate from the Gradle Enterprise Gradle plugin to the Develocity Gradle plugin."; + } + + @Override + public Validated validate() { + Validated validated = super.validate(); + if (version != null) { + VersionComparator versionComparator = Objects.requireNonNull(Semver.validate("[3.17,)", null).getValue()); + validated = validated.and(Validated.test("version", "Version must be 3.17+", version, v -> versionComparator.isValid(null, v))); + } + return validated; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + new IsSettingsGradle<>(), + new GroovyIsoVisitor() { + @Override + public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { + G.CompilationUnit g = cu; + g = (G.CompilationUnit) new ChangePlugin("com.gradle.enterprise", "com.gradle.develocity", version).getVisitor() + .visitNonNull(g, ctx); + g = (G.CompilationUnit) new ChangePluginVersion("com.gradle.common-custom-user-data-gradle-plugin", "2.x", null).getVisitor() + .visitNonNull(g, ctx); + g = (G.CompilationUnit) new MigrateConfigurationVisitor().visitNonNull(g, ctx); + return g; + } + } + ); + } + + private static class MigrateConfigurationVisitor extends GroovyIsoVisitor { + @Override + public @Nullable J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + if (m.getSimpleName().equals("gradleEnterprise") && m.getArguments().size() == 1 && m.getArguments().get(0) instanceof J.Lambda) { + return m.withName(m.getName().withSimpleName("develocity")); + } + + if (m.getSimpleName().startsWith("publishAlways") && withinMethodInvocations(Arrays.asList("gradleEnterprise", "buildScan"))) { + if (m.getSimpleName().equals("publishAlways") && noArguments(m.getArguments())) { + // As of 3.17+, `publishAlways` is the default, so it is recommended to not configure anything + return null; + } + + if (m.getSimpleName().equals("publishAlwaysIf")) { + J.MethodInvocation publishingTemplate = develocityPublishAlwaysIfDsl(getIndent(getCursor().firstEnclosing(G.CompilationUnit.class)), ctx); + if (publishingTemplate == null) { + return m; + } + + return publishingTemplate.withArguments(ListUtils.mapFirst(publishingTemplate.getArguments(), arg -> { + if (arg instanceof J.Lambda) { + J.Lambda lambda = (J.Lambda) arg; + J.Block block = (J.Block) lambda.getBody(); + return lambda.withBody(block.withStatements(ListUtils.mapFirst(block.getStatements(), s -> { + if (s instanceof J.Return) { + J.Return _return = (J.Return) s; + return _return.withExpression(m.getArguments().get(0)); + } + return s; + }))); + } + return arg; + })); + } + } + + if (m.getSimpleName().startsWith("publishOnFailure") && withinMethodInvocations(Arrays.asList("gradleEnterprise", "buildScan"))) { + J.MethodInvocation publishingTemplate = develocityPublishOnFailureIfDsl(getIndent(getCursor().firstEnclosing(G.CompilationUnit.class)), ctx); + if (publishingTemplate == null) { + return m; + } + + if (m.getSimpleName().equals("publishOnFailure") && noArguments(m.getArguments())) { + return publishingTemplate; + } + + if (m.getSimpleName().equals("publishOnFailureIf") && m.getArguments().size() == 1 && m.getArguments().get(0) instanceof J.Binary) { + return publishingTemplate.withArguments(ListUtils.mapFirst(publishingTemplate.getArguments(), arg -> { + if (arg instanceof J.Lambda) { + J.Lambda lambda = (J.Lambda) arg; + J.Block block = (J.Block) lambda.getBody(); + return lambda.withBody(block.withStatements(ListUtils.mapFirst(block.getStatements(), s -> { + if (s instanceof J.Return && ((J.Return) s).getExpression() instanceof J.Unary) { + J.Return _return = (J.Return) s; + return _return.withExpression(new J.Binary( + Tree.randomId(), + Space.EMPTY, + Markers.EMPTY, + _return.getExpression(), + JLeftPadded.build(J.Binary.Type.And).withBefore(Space.SINGLE_SPACE), + Space.formatFirstPrefix(m.getArguments(), Space.SINGLE_SPACE).get(0), + JavaType.Primitive.Boolean + )); + } + return s; + }))); + } + return arg; + })); + } + } + + return m; + } + + @Override + public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ctx) { + J.Assignment a = super.visitAssignment(assignment, ctx); + + if (a.getVariable() instanceof J.Identifier && ((J.Identifier) a.getVariable()).getSimpleName().equals("taskInputFiles") && withinMethodInvocations(Arrays.asList("gradleEnterprise", "buildScan", "capture"))) { + return a.withVariable(((J.Identifier) a.getVariable()).withSimpleName("fileFingerprints")); + } + + return a; + } + + private boolean noArguments(List arguments) { + return arguments.isEmpty() || (arguments.size() == 1 && arguments.get(0) instanceof J.Empty); + } + + private boolean withinMethodInvocations(List methods) { + Cursor current = getCursor().getParent(); + for (int i = methods.size() - 1; i >= 0; i--) { + current = findMethodInvocation(current); + if (current == null) { + return false; + } + + if (!((J.MethodInvocation) current.getValue()).getSimpleName().equals(methods.get(i))) { + return false; + } + + current = current.getParent(); + } + + return true; + } + + private @Nullable Cursor findMethodInvocation(@Nullable Cursor start) { + if (start == null) { + return null; + } + + Cursor current = start; + while (current.getParent() != null) { + if (current.getValue() instanceof J.MethodInvocation) { + return current; + } + + current = current.getParent(); + } + + return null; + } + + @Nullable + private J.MethodInvocation develocityPublishAlwaysIfDsl(String indent, ExecutionContext ctx) { + StringBuilder ge = new StringBuilder("\ndevelocity {\n"); + ge.append(indent).append("buildScan {\n"); + ge.append(indent).append(indent).append("publishing.onlyIf { true }\n"); + ge.append(indent).append("}\n"); + ge.append("}\n"); + + G.CompilationUnit cu = GradleParser.builder().build() + .parseInputs(singletonList( + Parser.Input.fromString(Paths.get("settings.gradle"), ge.toString())), null, ctx) + .map(G.CompilationUnit.class::cast) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle")); + + J.MethodInvocation develocity = (J.MethodInvocation) cu.getStatements().get(0); + J.MethodInvocation buildScan = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) develocity.getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); + return (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) buildScan.getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); + } + + @Nullable + private J.MethodInvocation develocityPublishOnFailureIfDsl(String indent, ExecutionContext ctx) { + StringBuilder ge = new StringBuilder("\ndevelocity {\n"); + ge.append(indent).append("buildScan {\n"); + ge.append(indent).append(indent).append("publishing.onlyIf { !it.buildResult.failures.empty }\n"); + ge.append(indent).append("}\n"); + ge.append("}\n"); + + G.CompilationUnit cu = GradleParser.builder().build() + .parseInputs(singletonList( + Parser.Input.fromString(Paths.get("settings.gradle"), ge.toString())), null, ctx) + .map(G.CompilationUnit.class::cast) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Could not parse as Gradle")); + + J.MethodInvocation develocity = (J.MethodInvocation) cu.getStatements().get(0); + J.MethodInvocation buildScan = (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) develocity.getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); + return (J.MethodInvocation) ((J.Return) ((J.Block) ((J.Lambda) buildScan.getArguments().get(0)).getBody()).getStatements().get(0)).getExpression(); + } + + private String getIndent(G.CompilationUnit cu) { + TabsAndIndentsStyle style = cu.getStyle(TabsAndIndentsStyle.class, IntelliJ.tabsAndIndents()); + if (style.getUseTabCharacter()) { + return "\t"; + } else { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < style.getIndentSize(); i++) { + sb.append(" "); + } + return sb.toString(); + } + } + } +} diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/MigrateGradleEnterpriseToDevelocityTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/MigrateGradleEnterpriseToDevelocityTest.java new file mode 100644 index 000000000000..e5cbdbf7f475 --- /dev/null +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/plugins/MigrateGradleEnterpriseToDevelocityTest.java @@ -0,0 +1,146 @@ +package org.openrewrite.gradle.plugins; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.junit.jupiter.api.Assertions.*; +import static org.openrewrite.gradle.Assertions.settingsGradle; +import static org.openrewrite.gradle.toolingapi.Assertions.withToolingApi; + +class MigrateGradleEnterpriseToDevelocityTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new MigrateGradleEnterpriseToDevelocity("3.17.x")) + .beforeRecipe(withToolingApi()); + } + + @Test + void migrate() { + rewriteRun( + settingsGradle( + """ + plugins { + id 'com.gradle.enterprise' version '3.16' + } + gradleEnterprise { + server = 'https://ge.sam.com/' + allowUntrustedServer = true + buildScan { + publishAlways() + uploadInBackground = true + capture { + taskInputFiles = true + } + } + } + """, + """ + plugins { + id 'com.gradle.develocity' version '3.17.5' + } + develocity { + server = 'https://ge.sam.com/' + allowUntrustedServer = true + buildScan { + uploadInBackground = true + capture { + fileFingerprints = true + } + } + } + """ + ) + ); + } + + @Test + void publishAlwaysIf() { + rewriteRun( + settingsGradle( + """ + plugins { + id 'com.gradle.enterprise' version '3.16' + } + gradleEnterprise { + server = 'https://ge.sam.com/' + buildScan { + publishAlwaysIf(System.getenv("CI") != null) + } + } + """, + """ + plugins { + id 'com.gradle.develocity' version '3.17.5' + } + develocity { + server = 'https://ge.sam.com/' + buildScan { + publishing.onlyIf { System.getenv("CI") != null } + } + } + """ + ) + ); + } + + @Test + void publishOnFailure() { + rewriteRun( + settingsGradle( + """ + plugins { + id 'com.gradle.enterprise' version '3.16' + } + gradleEnterprise { + server = 'https://ge.sam.com/' + buildScan { + publishOnFailure() + } + } + """, + """ + plugins { + id 'com.gradle.develocity' version '3.17.5' + } + develocity { + server = 'https://ge.sam.com/' + buildScan { + publishing.onlyIf { !it.buildResult.failures.empty } + } + } + """ + ) + ); + } + + @Test + void publishOnFailureIf() { + rewriteRun( + settingsGradle( + """ + plugins { + id 'com.gradle.enterprise' version '3.16' + } + gradleEnterprise { + server = 'https://ge.sam.com/' + buildScan { + publishOnFailureIf(System.getenv("CI") != null) + } + } + """, + """ + plugins { + id 'com.gradle.develocity' version '3.17.5' + } + develocity { + server = 'https://ge.sam.com/' + buildScan { + publishing.onlyIf { !it.buildResult.failures.empty && System.getenv("CI") != null } + } + } + """ + ) + ); + } +} \ No newline at end of file