From 553aed8b5676521e1275ece822f2d76918b90500 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Thu, 24 Aug 2023 12:12:54 -0400 Subject: [PATCH] Ensure support for custom plugin source set with various buckets New source set don't typically create the API, compile only API, API elements and runtime elements buckets. If this is the case, we will optionally create the buckets to the best of our ability. Despite the fact that core Gradle development plugin allow to customize the plugin source set, the plugin was not coded for that purpose. As leader and Gradle expert, we write our extension plugins as if it was possible, but we are most certainly sure they didn't intended to be the case. Users still have up until after project evaluation to create the missing buckets. Signed-off-by: Daniel Lacasse --- ...evelopmentDependenciesFunctionalTests.java | 398 +++++++++++++----- .../testers/DependencyWiringTester.java | 50 +++ ...evelopmentTestSuiteDependenciesTester.java | 65 ++- .../GradlePluginDevelopmentDependencies.java | 1 + ...nDevelopmentDependenciesExtensionRule.java | 128 +++++- 5 files changed, 530 insertions(+), 112 deletions(-) create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/DependencyWiringTester.java diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentDependenciesFunctionalTests.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentDependenciesFunctionalTests.java index 7af4c12c..4cde881b 100644 --- a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentDependenciesFunctionalTests.java +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentDependenciesFunctionalTests.java @@ -7,6 +7,7 @@ import dev.gradleplugins.runnerkit.GradleExecutor; import dev.gradleplugins.runnerkit.GradleRunner; import dev.gradleplugins.testers.DependencyBucketTester; +import dev.gradleplugins.testers.DependencyWiringTester; import dev.gradleplugins.testers.EnforcedPlatformDependencyModifierTester; import dev.gradleplugins.testers.GradleApiDependencyTester; import dev.gradleplugins.testers.GradlePluginDependencyTester; @@ -14,6 +15,7 @@ import dev.gradleplugins.testers.ProjectDependencyTester; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; @@ -21,6 +23,7 @@ import static dev.gradleplugins.buildscript.syntax.Syntax.groovyDsl; import static dev.gradleplugins.buildscript.syntax.Syntax.literal; +import static dev.gradleplugins.buildscript.syntax.Syntax.not; class GradlePluginDevelopmentDependenciesFunctionalTests { @@ -139,118 +142,303 @@ public GradleBuildFile buildFile() { } } - @Nested - class ApiDependencyBucketTest extends DependencyBucketTester { - @Override - public GradleRunner runner() { - return runner; - } - - @Override - public ExpressionBuilder bucketDsl() { - return literal("gradlePlugin.dependencies.api"); - } - - @Override - public GradleBuildFile buildFile() { - return buildFile; - } - - @Override - public GradleSettingsFile settingsFile() { - return settingsFile; - } - } - - @Nested - class ImplementationDependencyBucketTest extends DependencyBucketTester { - @Override - public GradleRunner runner() { - return runner; - } - - @Override - public ExpressionBuilder bucketDsl() { - return literal("gradlePlugin.dependencies.implementation"); - } - - @Override - public GradleBuildFile buildFile() { - return buildFile; - } - - @Override - public GradleSettingsFile settingsFile() { - return settingsFile; - } - } - - @Nested - class CompileOnlyDependencyBucketTest extends DependencyBucketTester { - @Override - public GradleRunner runner() { - return runner; - } - @Override - public ExpressionBuilder bucketDsl() { - return literal("gradlePlugin.dependencies.compileOnly"); - } - - @Override - public GradleBuildFile buildFile() { - return buildFile; - } - - @Override - public GradleSettingsFile settingsFile() { - return settingsFile; + abstract class DependencyBucketsTester { + @Nested + class ApiDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { + @Override + public GradleRunner runner() { + return runner; + } + + public ExpressionBuilder sourceSetDsl() { + return literal("gradlePlugin.pluginSourceSet"); + } + + @Override + public ExpressionBuilder bucketDsl() { + return literal("gradlePlugin.dependencies.api"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public GradleSettingsFile settingsFile() { + return settingsFile; + } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName"))); + } + + @Test + void testApiElementsWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("apiElementsConfigurationName"))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName"))); + } + + @Test + void testRuntimeElementsWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("runtimeElementsConfigurationName"))); + } + } + + @Nested + class CompileOnlyApiDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { + @Override + public GradleRunner runner() { + return runner; + } + + public ExpressionBuilder sourceSetDsl() { + return literal("gradlePlugin.pluginSourceSet"); + } + + @Override + public ExpressionBuilder bucketDsl() { + return literal("gradlePlugin.dependencies.compileOnlyApi"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public GradleSettingsFile settingsFile() { + return settingsFile; + } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName"))); + } + + @Test + void testApiElementsWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("apiElementsConfigurationName"))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName")))); + } + + @Test + void testRuntimeElementsWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("runtimeElementsConfigurationName")))); + } + } + + @Nested + class ImplementationDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { + @Override + public GradleRunner runner() { + return runner; + } + + public ExpressionBuilder sourceSetDsl() { + return literal("gradlePlugin.pluginSourceSet"); + } + + @Override + public ExpressionBuilder bucketDsl() { + return literal("gradlePlugin.dependencies.implementation"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public GradleSettingsFile settingsFile() { + return settingsFile; + } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName"))); + } + + @Test + void testApiElementsWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("apiElementsConfigurationName")))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName"))); + } + + @Test + void testRuntimeElementsWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("runtimeElementsConfigurationName"))); + } + } + + @Nested + class CompileOnlyDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { + @Override + public GradleRunner runner() { + return runner; + } + + public ExpressionBuilder sourceSetDsl() { + return literal("gradlePlugin.pluginSourceSet"); + } + + @Override + public ExpressionBuilder bucketDsl() { + return literal("gradlePlugin.dependencies.compileOnly"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public GradleSettingsFile settingsFile() { + return settingsFile; + } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName"))); + } + + @Test + void testApiElementsWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("apiElementsConfigurationName")))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName")))); + } + + @Test + void testRuntimeElementsWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("runtimeElementsConfigurationName")))); + } + } + + @Nested + class RuntimeOnlyDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { + @Override + public GradleRunner runner() { + return runner; + } + + public ExpressionBuilder sourceSetDsl() { + return literal("gradlePlugin.pluginSourceSet"); + } + + @Override + public ExpressionBuilder bucketDsl() { + return literal("gradlePlugin.dependencies.runtimeOnly"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public GradleSettingsFile settingsFile() { + return settingsFile; + } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName")))); + } + + @Test + void testApiElementsWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("apiElementsConfigurationName")))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName"))); + } + + @Test + void testRuntimeElementsWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("runtimeElementsConfigurationName"))); + } + } + + @Nested + class AnnotationProcessorDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { + @Override + public GradleRunner runner() { + return runner; + } + + public ExpressionBuilder sourceSetDsl() { + return literal("gradlePlugin.pluginSourceSet"); + } + + @Override + public ExpressionBuilder bucketDsl() { + return literal("gradlePlugin.dependencies.annotationProcessor"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public GradleSettingsFile settingsFile() { + return settingsFile; + } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName")))); + } + + @Test + void testApiElementsWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("apiElementsConfigurationName")))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName")))); + } + + @Test + void testRuntimeElementsWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("runtimeElementsConfigurationName")))); + } } } @Nested - class RuntimeOnlyDependencyBucketTest extends DependencyBucketTester { - @Override - public GradleRunner runner() { - return runner; - } - - @Override - public ExpressionBuilder bucketDsl() { - return literal("gradlePlugin.dependencies.runtimeOnly"); - } - - @Override - public GradleBuildFile buildFile() { - return buildFile; - } - - @Override - public GradleSettingsFile settingsFile() { - return settingsFile; - } - } + class DefaultPluginSourceSetTest extends DependencyBucketsTester {} @Nested - class AnnotationProcessorDependencyBucketTest extends DependencyBucketTester { - @Override - public GradleRunner runner() { - return runner; - } - - @Override - public ExpressionBuilder bucketDsl() { - return literal("gradlePlugin.dependencies.annotationProcessor"); - } - - @Override - public GradleBuildFile buildFile() { - return buildFile; - } - - @Override - public GradleSettingsFile settingsFile() { - return settingsFile; + class CustomPluginSourceSetTest extends DependencyBucketsTester { + @BeforeEach + void givenCustomPluginSourceSet() { + buildFile.append(groovyDsl( + "gradlePlugin {", + " pluginSourceSet(sourceSets.create('customMain'))", + "}" + )); } } } diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/DependencyWiringTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/DependencyWiringTester.java new file mode 100644 index 00000000..d0b1f696 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/DependencyWiringTester.java @@ -0,0 +1,50 @@ +package dev.gradleplugins.testers; + +import dev.gradleplugins.buildscript.GradleDsl; +import dev.gradleplugins.buildscript.ast.ExpressionBuilder; +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.runnerkit.GradleRunner; + +import static dev.gradleplugins.buildscript.syntax.Syntax.groovyDsl; +import static dev.gradleplugins.buildscript.syntax.Syntax.string; + +public interface DependencyWiringTester { + ExpressionBuilder bucketDsl(); + + GradleBuildFile buildFile(); + + GradleRunner runner(); + + default void assertBucketDependencyIs(Expression expression) { + buildFile().append(bucketDsl().call("add", string("com.example:foo:1.0"))); + buildFile().append(groovyDsl( + "Set allDependencies(Configuration configuration) {", + " return configuration.allDependencies.collect {", + " if (it instanceof ExternalModuleDependency) {", + " return \"${it.group}:${it.name}:${it.version}\".toString()", + " } else if (it instanceof ProjectDependency) {", + " return it.projectPath", + " } else if (it instanceof SelfResolvingDependency) {", + " return it.targetComponentId?.displayName ?: ''", + " } else {", + " throw new RuntimeException()", + " }", + " }", + "}" + )); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert " + expression.toString(GradleDsl.GROOVY), + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + default Expression containedIn(Expression configurationName) { + return groovyDsl("allDependencies(configurations[" + configurationName.toString(GradleDsl.GROOVY) + "]).contains('com.example:foo:1.0')"); + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDevelopmentTestSuiteDependenciesTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDevelopmentTestSuiteDependenciesTester.java index 8f96afc5..c08a61c1 100644 --- a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDevelopmentTestSuiteDependenciesTester.java +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDevelopmentTestSuiteDependenciesTester.java @@ -11,6 +11,7 @@ import static dev.gradleplugins.buildscript.ast.expressions.AssignmentExpression.assign; import static dev.gradleplugins.buildscript.ast.expressions.VariableDeclarationExpression.val; import static dev.gradleplugins.buildscript.syntax.Syntax.groovyDsl; +import static dev.gradleplugins.buildscript.syntax.Syntax.not; import static dev.gradleplugins.buildscript.syntax.Syntax.string; public abstract class GradlePluginDevelopmentTestSuiteDependenciesTester { @@ -150,12 +151,16 @@ public GradleSettingsFile settingsFile() { } @Nested - class ImplementationDependencyBucketTest extends DependencyBucketTester { + class ImplementationDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { @Override public GradleRunner runner() { return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); } + public ExpressionBuilder sourceSetDsl() { + return testSuiteDsl().dot("sourceSet.get()"); + } + @Override public ExpressionBuilder bucketDsl() { return testSuiteDsl().dot("dependencies.implementation"); @@ -170,15 +175,29 @@ public GradleBuildFile buildFile() { public GradleSettingsFile settingsFile() { return GradlePluginDevelopmentTestSuiteDependenciesTester.this.settingsFile(); } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName"))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName"))); + } } @Nested - class CompileOnlyDependencyBucketTest extends DependencyBucketTester { + class CompileOnlyDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { @Override public GradleRunner runner() { return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); } + public ExpressionBuilder sourceSetDsl() { + return testSuiteDsl().dot("sourceSet.get()"); + } + @Override public ExpressionBuilder bucketDsl() { return testSuiteDsl().dot("dependencies.compileOnly"); @@ -193,15 +212,29 @@ public GradleBuildFile buildFile() { public GradleSettingsFile settingsFile() { return GradlePluginDevelopmentTestSuiteDependenciesTester.this.settingsFile(); } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName"))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName")))); + } } @Nested - class RuntimeOnlyDependencyBucketTest extends DependencyBucketTester { + class RuntimeOnlyDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { @Override public GradleRunner runner() { return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); } + public ExpressionBuilder sourceSetDsl() { + return testSuiteDsl().dot("sourceSet.get()"); + } + @Override public ExpressionBuilder bucketDsl() { return testSuiteDsl().dot("dependencies.runtimeOnly"); @@ -216,15 +249,29 @@ public GradleBuildFile buildFile() { public GradleSettingsFile settingsFile() { return GradlePluginDevelopmentTestSuiteDependenciesTester.this.settingsFile(); } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName")))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName"))); + } } @Nested - class AnnotationProcessorDependencyBucketTest extends DependencyBucketTester { + class AnnotationProcessorDependencyBucketTest extends DependencyBucketTester implements DependencyWiringTester { @Override public GradleRunner runner() { return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); } + public ExpressionBuilder sourceSetDsl() { + return testSuiteDsl().dot("sourceSet.get()"); + } + @Override public ExpressionBuilder bucketDsl() { return testSuiteDsl().dot("dependencies.annotationProcessor"); @@ -239,6 +286,16 @@ public GradleBuildFile buildFile() { public GradleSettingsFile settingsFile() { return GradlePluginDevelopmentTestSuiteDependenciesTester.this.settingsFile(); } + + @Test + void testCompileClasspathWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("compileClasspathConfigurationName")))); + } + + @Test + void testRuntimeClasspathWiring() { + assertBucketDependencyIs(not(containedIn(sourceSetDsl().dot("runtimeClasspathConfigurationName")))); + } } @Nested diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencies.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencies.java index 861a2bc3..001d19f6 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencies.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencies.java @@ -11,6 +11,7 @@ public interface GradlePluginDevelopmentDependencies extends GradlePluginDevelop GradlePluginDevelopmentDependencyBucket getApi(); GradlePluginDevelopmentDependencyBucket getImplementation(); GradlePluginDevelopmentDependencyBucket getCompileOnly(); + GradlePluginDevelopmentDependencyBucket getCompileOnlyApi(); GradlePluginDevelopmentDependencyBucket getRuntimeOnly(); GradlePluginDevelopmentDependencyBucket getAnnotationProcessor(); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RegisterGradlePluginDevelopmentDependenciesExtensionRule.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RegisterGradlePluginDevelopmentDependenciesExtensionRule.java index b5c3eae8..ae8d35f2 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RegisterGradlePluginDevelopmentDependenciesExtensionRule.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RegisterGradlePluginDevelopmentDependenciesExtensionRule.java @@ -3,7 +3,6 @@ import dev.gradleplugins.GradlePluginDevelopmentDependencies; import dev.gradleplugins.GradlePluginDevelopmentDependencyBucket; import dev.gradleplugins.GradlePluginDevelopmentDependencyModifiers; -import dev.gradleplugins.internal.DefaultDependencyBucket; import dev.gradleplugins.internal.DefaultDependencyBucketFactory; import dev.gradleplugins.internal.DependencyBucketFactory; import dev.gradleplugins.internal.DependencyFactory; @@ -12,21 +11,42 @@ import dev.gradleplugins.internal.runtime.dsl.GroovyHelper; import org.codehaus.groovy.runtime.MethodClosure; import org.gradle.api.Action; +import org.gradle.api.JavaVersion; import org.gradle.api.Project; +import org.gradle.api.Transformer; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ExternalModuleDependency; -import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.ProjectDependency; +import org.gradle.api.attributes.Bundling; +import org.gradle.api.attributes.Category; +import org.gradle.api.attributes.LibraryElements; +import org.gradle.api.attributes.Usage; import org.gradle.api.plugins.ExtensionAware; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.compile.CompileOptions; +import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.plugin.devel.GradlePluginDevelopmentExtension; -import org.jetbrains.annotations.NotNull; import javax.inject.Inject; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import static dev.gradleplugins.internal.DefaultDependencyBucket.pluginSourceSet; +import static org.gradle.api.attributes.Bundling.BUNDLING_ATTRIBUTE; +import static org.gradle.api.attributes.Bundling.EXTERNAL; +import static org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE; +import static org.gradle.api.attributes.Category.LIBRARY; +import static org.gradle.api.attributes.LibraryElements.JAR; +import static org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE; +import static org.gradle.api.attributes.Usage.JAVA_RUNTIME; +import static org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE; +import static org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE; public final class RegisterGradlePluginDevelopmentDependenciesExtensionRule implements Action { @Override @@ -44,9 +64,105 @@ public void execute(Project project) { GroovyHelper.instance().addNewInstanceMethod(dependenciesExtension, "enforcedPlatform", new MethodClosure(dependenciesExtension.getEnforcedPlatform(), "modify")); ((ExtensionAware) extension).getExtensions().add(GradlePluginDevelopmentDependencies.class, "dependencies", dependenciesExtension); + + // Shim missing configurations + project.afterEvaluate(__ -> { + final SourceSet sourceSet = extension.getPluginSourceSet();; + + Configuration api = project.getConfigurations().findByName(sourceSet.getApiConfigurationName()); + if (api == null) { + api = project.getConfigurations().create(sourceSet.getApiConfigurationName()); + api.setDescription("API dependencies for " + sourceSet + "."); + api.setCanBeResolved(false); + api.setCanBeConsumed(false); + project.getConfigurations().getByName(sourceSet.getImplementationConfigurationName()).extendsFrom(api); + } + + Configuration compileOnlyApi = project.getConfigurations().findByName(compileOnlyApiConfigurationName(sourceSet)); + if (compileOnlyApi == null) { + compileOnlyApi = project.getConfigurations().create(compileOnlyApiConfigurationName(sourceSet)); + compileOnlyApi.setDescription("Compile only dependencies for " + sourceSet + "."); + compileOnlyApi.setCanBeResolved(false); + compileOnlyApi.setCanBeConsumed(false); + project.getConfigurations().getByName(sourceSet.getCompileOnlyConfigurationName()).extendsFrom(compileOnlyApi); + } + + Configuration apiElements = project.getConfigurations().findByName(sourceSet.getApiElementsConfigurationName()); + if (apiElements == null) { + apiElements = project.getConfigurations().create(sourceSet.getApiElementsConfigurationName()); + apiElements.setDescription("API elements for " + sourceSet + "."); + apiElements.setCanBeResolved(false); + apiElements.setCanBeConsumed(true); + apiElements.attributes(it -> { + it.attribute(USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_API)); + it.attribute(CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, LIBRARY)); + it.attribute(TARGET_JVM_VERSION_ATTRIBUTE, project.getTasks().named(sourceSet.getCompileJavaTaskName(), JavaCompile.class).flatMap(toMajorVersion(project)).get()); + it.attribute(BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, EXTERNAL)); + it.attribute(LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, JAR)); + }); + } + + Configuration runtimeElements = project.getConfigurations().findByName(sourceSet.getRuntimeElementsConfigurationName()); + if (runtimeElements == null) { + runtimeElements = project.getConfigurations().create(sourceSet.getRuntimeElementsConfigurationName()); + runtimeElements.setDescription("Runtime elements for " + sourceSet + "."); + runtimeElements.setCanBeResolved(false); + runtimeElements.setCanBeConsumed(true); + runtimeElements.attributes(it -> { + it.attribute(USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, JAVA_RUNTIME)); + it.attribute(CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, LIBRARY)); + it.attribute(TARGET_JVM_VERSION_ATTRIBUTE, project.getTasks().named(sourceSet.getCompileJavaTaskName(), JavaCompile.class).flatMap(toMajorVersion(project)).get()); + it.attribute(BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, EXTERNAL)); + it.attribute(LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, JAR)); + }); + } + + if (!extension.getPluginSourceSet().getName().equals("main")) { + apiElements.extendsFrom(api); + apiElements.extendsFrom(compileOnlyApi); + + runtimeElements.extendsFrom(project.getConfigurations().getByName(extension.getPluginSourceSet().getImplementationConfigurationName())); + runtimeElements.extendsFrom(project.getConfigurations().getByName(extension.getPluginSourceSet().getRuntimeOnlyConfigurationName())); + } + }); + }); + } + + private static Transformer, JavaCompile> toMajorVersion(Project project) { + return task -> getReleaseOption(project, task.getOptions()) + .orElse(getReleaseFlag(project, task.getOptions().getCompilerArgs())) + .orElse(project.provider(() -> Integer.parseInt(JavaVersion.toVersion(task.getTargetCompatibility()).getMajorVersion()))); + } + + private static Provider getReleaseOption(Project project, CompileOptions options) { + try { + final Method getRelease = options.getClass().getDeclaredMethod("getRelease"); + + @SuppressWarnings("unchecked") + final Provider result = (Provider) getRelease.invoke(options); + return result; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + return project.provider(() -> null); + } + } + + private static Provider getReleaseFlag(Project project, List compilerArgs) { + return project.provider(() -> { + int flagIndex = compilerArgs.indexOf("--release"); + if (flagIndex != -1 && flagIndex + 1 < compilerArgs.size()) { + return Integer.parseInt(String.valueOf(compilerArgs.get(flagIndex + 1))); + } + return null; }); } + private static String compileOnlyApiConfigurationName(SourceSet sourceSet) { + if (sourceSet.getName().equals("main")) { + return "compileOnlyApi"; + } + return sourceSet.getName() + "CompileOnlyApi"; + } + private static void gradlePlugin(Project project, Action action) { action.execute((GradlePluginDevelopmentExtension) project.getExtensions().getByName("gradlePlugin")); } @@ -66,6 +182,7 @@ public DefaultGradlePluginDevelopmentDependencies(Project project, DependencyBuc this.project = project; add(dependencyBucketFactory.create("api")); add(dependencyBucketFactory.create("implementation")); + add(dependencyBucketFactory.create("compileOnlyApi")); add(dependencyBucketFactory.create("compileOnly")); add(dependencyBucketFactory.create("runtimeOnly")); add(dependencyBucketFactory.create("annotationProcessor")); @@ -85,6 +202,11 @@ public GradlePluginDevelopmentDependencyBucket getImplementation() { return dependencyBuckets.get("implementation"); } + @Override + public GradlePluginDevelopmentDependencyBucket getCompileOnlyApi() { + return dependencyBuckets.get("compileOnlyApi"); + } + @Override public GradlePluginDevelopmentDependencyBucket getCompileOnly() { return dependencyBuckets.get("compileOnly");