diff --git a/plugin/src/main/java/org/openrewrite/gradle/AbstractRewriteTask.java b/plugin/src/main/java/org/openrewrite/gradle/AbstractRewriteTask.java index 1638e9d43..abc66163e 100755 --- a/plugin/src/main/java/org/openrewrite/gradle/AbstractRewriteTask.java +++ b/plugin/src/main/java/org/openrewrite/gradle/AbstractRewriteTask.java @@ -16,8 +16,9 @@ package org.openrewrite.gradle; import org.gradle.api.DefaultTask; +import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.ProjectLayout; -import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.options.Option; @@ -30,16 +31,18 @@ import java.util.List; import java.util.Set; -import static java.util.Collections.emptySet; import static java.util.stream.Collectors.toSet; public abstract class AbstractRewriteTask extends DefaultTask { - protected @Nullable Provider> resolvedDependencies; protected boolean dumpGcActivity; protected @Nullable GradleProjectParser gpp; protected @Nullable RewriteExtension extension; + private final ConfigurableFileCollection resolvedDependencies; + private final ConfigurableFileCollection projectClasspath; protected AbstractRewriteTask() { + resolvedDependencies = getProject().files(); + projectClasspath = getProject().files(); if (GradleVersion.current().compareTo(GradleVersion.version("7.4")) >= 0) { notCompatibleWithConfigurationCache("org.openrewrite.rewrite needs to parse the whole project"); } @@ -51,10 +54,20 @@ public T setExtension(RewriteExtension extension return (T) this; } - public T setResolvedDependencies(Provider> resolvedDependencies) { - this.resolvedDependencies = resolvedDependencies; - //noinspection unchecked - return (T) this; + @Classpath + public ConfigurableFileCollection getResolvedDependencies() { + return resolvedDependencies; + } + + /** + * The project's runtime classpaths, declared as an input so that Gradle + * knows to build dependent project jars before running rewrite tasks. + * This is separate from {@link #getResolvedDependencies()} to avoid polluting the + * rewrite recipe classpath with project dependencies. + */ + @Classpath + public ConfigurableFileCollection getProjectClasspath() { + return projectClasspath; } @Option(description = "Dump GC activity related to parsing.", option = "dumpGcActivity") @@ -78,14 +91,7 @@ protected T getProjectParser() { if (extension == null) { throw new IllegalArgumentException("Must configure extension"); } - if (resolvedDependencies == null) { - throw new IllegalArgumentException("Must configure resolvedDependencies"); - } - Set deps = resolvedDependencies.getOrNull(); - if (deps == null) { - deps = emptySet(); - } - Set classpath = deps.stream() + Set classpath = getResolvedDependencies().getFiles().stream() .map(File::toPath) .collect(toSet()); gpp = new DelegatingProjectParser(getProject(), extension, classpath); diff --git a/plugin/src/main/java/org/openrewrite/gradle/RewritePlugin.java b/plugin/src/main/java/org/openrewrite/gradle/RewritePlugin.java index ac96a088f..82d7ed136 100644 --- a/plugin/src/main/java/org/openrewrite/gradle/RewritePlugin.java +++ b/plugin/src/main/java/org/openrewrite/gradle/RewritePlugin.java @@ -29,14 +29,13 @@ import org.gradle.api.plugins.JvmEcosystemPlugin; import org.gradle.api.plugins.quality.CheckstyleExtension; import org.gradle.api.plugins.quality.CheckstylePlugin; -import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; -import org.jspecify.annotations.Nullable; import java.io.File; import java.lang.reflect.Method; import java.util.*; +import java.util.concurrent.Callable; import static org.gradle.api.attributes.Bundling.BUNDLING_ATTRIBUTE; import static org.gradle.api.attributes.java.TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE; @@ -51,9 +50,6 @@ @SuppressWarnings("unused") public class RewritePlugin implements Plugin { - @Nullable - private Set resolvedDependencies; - @Override public void apply(Project project) { boolean isRootProject = project == project.getRootProject(); @@ -70,24 +66,31 @@ public void apply(Project project) { Configuration rewriteConf = project.getConfigurations().maybeCreate("rewrite"); rewriteConf.setCanBeConsumed(false); - Provider> resolvedDependenciesProvider = project.provider(() -> getResolvedDependencies(project, extension, rewriteConf)); + Callable detachedConf = new Callable() { + private Configuration cached; + + @Override + public Configuration call() { + if (cached == null) { + cached = createDetachedConfiguration(project, extension, rewriteConf); + } + return cached; + } + }; TaskProvider rewriteRun = project.getTasks().register("rewriteRun", RewriteRunTask.class, task -> { task.setExtension(extension); - task.setResolvedDependencies(resolvedDependenciesProvider); - task.dependsOn(rewriteConf); + task.getResolvedDependencies().from(detachedConf); }); TaskProvider rewriteDryRun = project.getTasks().register("rewriteDryRun", RewriteDryRunTask.class, task -> { task.setExtension(extension); - task.setResolvedDependencies(resolvedDependenciesProvider); - task.dependsOn(rewriteConf); + task.getResolvedDependencies().from(detachedConf); }); TaskProvider rewriteDiscover = project.getTasks().register("rewriteDiscover", RewriteDiscoverTask.class, task -> { task.setExtension(extension); - task.setResolvedDependencies(resolvedDependenciesProvider); - task.dependsOn(rewriteConf); + task.getResolvedDependencies().from(detachedConf); }); if (isRootProject) { @@ -137,8 +140,12 @@ private static void configureProject(Project project, RewriteExtension extension sourceSets.all(sourceSet -> { // This is intended to ensure that any Groovy/Kotlin/etc. and dependent project sources are available TaskProvider compileTask = project.getTasks().named(sourceSet.getCompileJavaTaskName()); - rewriteRun.configure(task -> task.dependsOn(compileTask)); - rewriteDryRun.configure(task -> task.dependsOn(compileTask)); + for (TaskProvider taskProvider : Arrays.asList(rewriteRun, rewriteDryRun)) { + taskProvider.configure(task -> { + task.dependsOn(compileTask); + task.getProjectClasspath().from(sourceSet.getRuntimeClasspath()); + }); + } }); // Detect SourceSets which overlap other sourceSets and disable the compilation task of the overlapping @@ -164,40 +171,37 @@ private static void configureProject(Project project, RewriteExtension extension }); } - private Set getResolvedDependencies(Project project, RewriteExtension extension, Configuration rewriteConf) { - if (resolvedDependencies == null) { - // Avoid Stream.concat here pending https://github.com/gradle/gradle/issues/33152 - List dependencies = new ArrayList<>(); - dependencies.addAll(knownRewriteDependencies(extension, project.getDependencies())); - dependencies.addAll(rewriteConf.getDependencies()); - // By using a detached configuration, we separate this dependency resolution from the rest of the project's - // configuration. This also means that Gradle has no criteria with which to select between variants of - // dependencies which expose differing capabilities. So those must be manually configured - Configuration detachedConf = project.getConfigurations().detachedConfiguration(dependencies.toArray(new Dependency[0])); - - try { - ObjectFactory objectFactory = project.getObjects(); - detachedConf.attributes(attributes -> { - // Adapted from org.gradle.api.plugins.jvm.internal.DefaultJvmEcosystemAttributesDetails - attributes.attribute(Category.CATEGORY_ATTRIBUTE, objectFactory.named(Category.class, Category.LIBRARY)); - attributes.attribute(Usage.USAGE_ATTRIBUTE, objectFactory.named(Usage.class, Usage.JAVA_RUNTIME)); - attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objectFactory.named(LibraryElements.class, LibraryElements.JAR)); - attributes.attribute(BUNDLING_ATTRIBUTE, objectFactory.named(Bundling.class, Bundling.EXTERNAL)); - try { - attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objectFactory.named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM)); - } catch (NoClassDefFoundError e) { - // Old versions of Gradle don't have the class TargetJvmEnvironment and that's OK, we can always - // try this attribute instead - attributes.attribute(Attribute.of("org.gradle.jvm.environment", String.class), "standard-jvm"); - } - }); - } catch (NoClassDefFoundError e) { - // Old versions of Gradle don't have all of these attributes and that's OK - } + private static Configuration createDetachedConfiguration(Project project, RewriteExtension extension, Configuration rewriteConf) { + // Avoid Stream.concat here pending https://github.com/gradle/gradle/issues/33152 + List dependencies = new ArrayList<>(); + dependencies.addAll(knownRewriteDependencies(extension, project.getDependencies())); + dependencies.addAll(rewriteConf.getDependencies()); + // By using a detached configuration, we separate this dependency resolution from the rest of the project's + // configuration. This also means that Gradle has no criteria with which to select between variants of + // dependencies which expose differing capabilities. So those must be manually configured + Configuration detachedConf = project.getConfigurations().detachedConfiguration(dependencies.toArray(new Dependency[0])); - resolvedDependencies = detachedConf.resolve(); + try { + ObjectFactory objectFactory = project.getObjects(); + detachedConf.attributes(attributes -> { + // Adapted from org.gradle.api.plugins.jvm.internal.DefaultJvmEcosystemAttributesDetails + attributes.attribute(Category.CATEGORY_ATTRIBUTE, objectFactory.named(Category.class, Category.LIBRARY)); + attributes.attribute(Usage.USAGE_ATTRIBUTE, objectFactory.named(Usage.class, Usage.JAVA_RUNTIME)); + attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objectFactory.named(LibraryElements.class, LibraryElements.JAR)); + attributes.attribute(BUNDLING_ATTRIBUTE, objectFactory.named(Bundling.class, Bundling.EXTERNAL)); + try { + attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objectFactory.named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM)); + } catch (NoClassDefFoundError e) { + // Old versions of Gradle don't have the class TargetJvmEnvironment and that's OK, we can always + // try this attribute instead + attributes.attribute(Attribute.of("org.gradle.jvm.environment", String.class), "standard-jvm"); + } + }); + } catch (NoClassDefFoundError e) { + // Old versions of Gradle don't have all of these attributes and that's OK } - return resolvedDependencies; + + return detachedConf; } private static Collection knownRewriteDependencies(RewriteExtension extension, DependencyHandler deps) {