From befff0ad861e01946477320e23f099a5b3285260 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 10 Jul 2024 12:24:36 -0400 Subject: [PATCH 1/8] Improve dependencies declarations using buckets Signed-off-by: Daniel Lacasse --- ...evelopmentDependenciesFunctionalTests.java | 444 ++++++++++++++++++ ...pmentFunctionalTestingFunctionalTests.java | 228 +++++++++ ...DevelopmentUnitTestingFunctionalTests.java | 239 ++++++++++ ...tDependenciesExtensionFunctionalTests.java | 88 ++++ .../testers/DependencyBucketTester.java | 131 ++++++ ...orcedPlatformDependencyModifierTester.java | 115 +++++ .../testers/GradleApiDependencyTester.java | 50 ++ .../testers/GradlePluginDependencyTester.java | 35 ++ ...evelopmentTestSuiteDependenciesTester.java | 353 ++++++++++++++ .../GradleTestKitDependencyTester.java | 66 +++ .../PlatformDependencyModifierTester.java | 115 +++++ .../testers/ProjectDependencyTester.java | 57 +++ .../TestFixturesDependencyModifierTester.java | 128 +++++ .../groovy/GroovyDslRuntimeExtensions.groovy | 7 +- .../GradlePluginDevelopmentDependencies.java | 27 ++ ...ePluginDevelopmentDependencyExtension.java | 13 + ...ePluginDevelopmentDependencyModifiers.java | 23 + ...luginDevelopmentTestSuiteDependencies.java | 46 +- .../internal/DependencyFactory.java | 26 + .../EnforcedPlatformDependencyModifier.java | 34 ++ ...dlePluginDevelopmentTestSuiteInternal.java | 173 +++++-- .../internal/PlatformDependencyModifier.java | 34 ++ ...nderTestMetadataConfigurationSupplier.java | 8 + .../TestFixturesDependencyModifier.java | 34 ++ .../GradlePluginDevelopmentBasePlugin.java | 1 + ...adlePluginDevelopmentDependenciesRule.java | 277 +++++++++++ .../ProjectDependenciesExtensionRule.java | 70 +-- .../rules/RepositoriesExtensionRules.java | 24 +- .../internal/runtime/dsl/GroovyHelper.java | 31 ++ .../main/kotlin/DependencyBucketExtensions.kt | 22 + .../kotlin/DependencyModifierExtensions.kt | 13 + ...ules.gradle-plugin-dependencies.properties | 1 + 32 files changed, 2795 insertions(+), 118 deletions(-) create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentDependenciesFunctionalTests.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/ProjectDependenciesExtensionFunctionalTests.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/DependencyBucketTester.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/EnforcedPlatformDependencyModifierTester.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradleApiDependencyTester.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDependencyTester.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDevelopmentTestSuiteDependenciesTester.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradleTestKitDependencyTester.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/PlatformDependencyModifierTester.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/ProjectDependencyTester.java create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/TestFixturesDependencyModifierTester.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencies.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencyModifiers.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/EnforcedPlatformDependencyModifier.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/PlatformDependencyModifier.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/TestFixturesDependencyModifier.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradlePluginDevelopmentDependenciesRule.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/runtime/dsl/GroovyHelper.java create mode 100644 subprojects/gradle-plugin-development/src/main/kotlin/DependencyBucketExtensions.kt create mode 100644 subprojects/gradle-plugin-development/src/main/kotlin/DependencyModifierExtensions.kt create mode 100644 subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-plugin-dependencies.properties 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 new file mode 100644 index 00000000..4cde881b --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentDependenciesFunctionalTests.java @@ -0,0 +1,444 @@ +package dev.gradleplugins; + +import dev.gradleplugins.buildscript.ast.ExpressionBuilder; +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.buildscript.io.GradleSettingsFile; +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; +import dev.gradleplugins.testers.PlatformDependencyModifierTester; +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; + +import java.nio.file.Path; + +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 { + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path testDirectory; + GradleRunner runner = GradleRunner.create(GradleExecutor.gradleTestKit()).withGradleVersion(System.getProperty("dev.gradleplugins.defaultGradleVersion")).withPluginClasspath().inDirectory(() -> testDirectory); + GradleBuildFile buildFile; + GradleSettingsFile settingsFile; + + @BeforeEach + void setup() { + settingsFile = GradleSettingsFile.inDirectory(testDirectory); + buildFile = GradleBuildFile.inDirectory(testDirectory); + buildFile.plugins(it -> { + it.id("dev.gradleplugins.gradle-plugin-base"); + it.id("java-gradle-plugin"); + }); + } + + @Nested + class GradleApiDependencyTest extends GradleApiDependencyTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public Expression gradleApiDsl(String version) { + return groovyDsl("gradlePlugin.dependencies.gradleApi('" + version + "')"); + } + } + + @Nested + class ProjectDependencyTest extends ProjectDependencyTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public GradleSettingsFile settingsFile() { + return settingsFile; + } + + @Override + public Expression projectDsl(String projectPath) { + return groovyDsl("gradlePlugin.dependencies.project('" + projectPath + "')"); + } + + @Override + public Expression projectDsl() { + return groovyDsl("gradlePlugin.dependencies.project()"); + } + } + + @Nested + class GradlePluginDependencyTest extends GradlePluginDependencyTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public Expression gradlePluginDsl(String pluginNotation) { + return groovyDsl("gradlePlugin.dependencies.gradlePlugin('" + pluginNotation + "')"); + } + } + + @Nested + class PlatformDependencyModifierTest extends PlatformDependencyModifierTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public ExpressionBuilder modifierDsl() { + return literal("gradlePlugin.dependencies.platform"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + } + + @Nested + class EnforcedPlatformDependencyModifierTest extends EnforcedPlatformDependencyModifierTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public ExpressionBuilder modifierDsl() { + return literal("gradlePlugin.dependencies.enforcedPlatform"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + } + + + 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 DefaultPluginSourceSetTest extends DependencyBucketsTester {} + + @Nested + 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/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java new file mode 100644 index 00000000..5404a5c7 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java @@ -0,0 +1,228 @@ +package dev.gradleplugins; + +import dev.gradleplugins.buildscript.ast.ExpressionBuilder; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.buildscript.io.GradleSettingsFile; +import dev.gradleplugins.runnerkit.BuildResult; +import dev.gradleplugins.runnerkit.GradleExecutor; +import dev.gradleplugins.runnerkit.GradleRunner; +import dev.gradleplugins.testers.GradlePluginDevelopmentTestSuiteDependenciesTester; +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; + +import java.io.IOException; +import java.nio.file.Path; + +import static dev.gradleplugins.buildscript.blocks.BuildscriptBlock.classpath; +import static dev.gradleplugins.buildscript.blocks.DependencyNotation.files; +import static dev.gradleplugins.buildscript.syntax.Syntax.groovyDsl; +import static dev.gradleplugins.buildscript.syntax.Syntax.literal; +import static dev.gradleplugins.fixtures.runnerkit.BuildResultMatchers.hasFailureCause; +import static dev.gradleplugins.fixtures.runnerkit.BuildResultMatchers.hasFailureDescription; +import static org.hamcrest.MatcherAssert.assertThat; + +class GradlePluginDevelopmentFunctionalTestingFunctionalTests { + @TempDir(cleanup = CleanupMode.ON_SUCCESS) + Path testDirectory; + GradleRunner runner = GradleRunner.create(GradleExecutor.gradleTestKit()).withGradleVersion(System.getProperty("dev.gradleplugins.defaultGradleVersion")).withPluginClasspath().inDirectory(() -> testDirectory); + GradleBuildFile buildFile; + GradleSettingsFile settingsFile; + + @BeforeEach + void givenProject() throws IOException { + settingsFile = GradleSettingsFile.inDirectory(testDirectory); + settingsFile.buildscript(it -> it.dependencies(classpath(files(runner.getPluginClasspath())))); + settingsFile.append(groovyDsl("rootProject.name = 'gradle-plugin'")); + + buildFile = GradleBuildFile.inDirectory(testDirectory); + buildFile.plugins(it -> { + it.id("dev.gradleplugins.gradle-plugin-functional-test"); + it.id("java-gradle-plugin"); + }); + } + + @Test + void hasMainSourceSetAsTestedSourceSetConvention() { + buildFile.append(groovyDsl( + "functionalTest.testedSourceSet = null", + "tasks.register('verify') {", + " doLast {", + " assert functionalTest.testedSourceSet.orNull?.name == 'main'", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void usesDevelPluginSourceSetAsTestedSourceSetConvention() { + buildFile.append(groovyDsl( + "gradlePlugin.pluginSourceSet(sourceSets.create('anotherMain'))", + "functionalTest.testedSourceSet = null", + "tasks.register('verify') {", + " doLast {", + " assert functionalTest.testedSourceSet.orNull?.name == 'anotherMain'", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void disallowChangesToSourceSetProperty() { + buildFile.append(groovyDsl( + "afterEvaluate {", + " functionalTest.sourceSet = null", // expect failure + "}", + "tasks.register('verify')" + )); + + BuildResult result = runner.withTasks("verify").buildAndFail(); + assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); + assertThat(result, hasFailureCause("The value for property 'sourceSet' is final and cannot be changed any further.")); + } + + @Test + void disallowChangesToTestedSourceSetProperty() { + buildFile.append(groovyDsl( + "afterEvaluate {", + " functionalTest.testedSourceSet = null", // expect failure + "}", + "tasks.register('verify')" + )); + + BuildResult result = runner.withTasks("verify").buildAndFail(); + assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); + assertThat(result, hasFailureCause("The value for property 'testedSourceSet' is final and cannot be changed any further.")); + } + + @Test + void disallowChangesToTestingStrategiesProperty() { + buildFile.append(groovyDsl( + "afterEvaluate {", + " functionalTest.testingStrategies = null", // expect failure + "}", + "tasks.register('verify')" + )); + + BuildResult result = runner.withTasks("verify").buildAndFail(); + assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); + assertThat(result, hasFailureCause("The value for property 'testingStrategies' is final and cannot be changed any further.")); + } + + @Test + void returnsTestTasksOnTaskViewElementQuery() { + buildFile.append(groovyDsl( + "functionalTest {", + " testingStrategies = [strategies.coverageForGradleVersion('6.8'), strategies.coverageForGradleVersion('7.1')]", + "}", + "", + "tasks.register('verify') {", + " doLast {", + " assert functionalTest.testTasks.elements.get().every { it.name == \"${functionalTest.name}6.8\" || it.name == \"${functionalTest.name}7.1\" }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void addsPluginUnderTestMetadataAsRuntimeOnlyDependency() { + buildFile.append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert functionalTest.dependencies.runtimeOnly.asConfiguration.get().dependencies.any {", + " it instanceof SelfResolvingDependency && it.files.singleFile.path.endsWith('/pluginUnderTestMetadataFunctionalTest')", + " }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void includesPluginUnderTestMetadataConfigurationDependencies() { + buildFile.append(groovyDsl( + "functionalTest.dependencies.pluginUnderTestMetadata files('my/own/dep.jar')", + "", + "tasks.register('verify') {", + " doLast {", + " assert functionalTest.pluginUnderTestMetadataTask.get().pluginClasspath.any { it.path.endsWith('/my/own/dep.jar') }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void doesNotIncludesSourceSetInDevelTestSourceSets() { + buildFile.append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert !gradlePlugin.testSourceSets.any { it.name == 'functionalTest' }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void hasGradleTestKitImplementationDependencyToLocalVersion() { + buildFile.append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert configurations.functionalTestImplementation.dependencies.any { it instanceof SelfResolvingDependency && it.targetComponentId?.displayName == 'Gradle TestKit' }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void hasGradleTestKitImplementationDependencyToGradleApiVersion() { + buildFile.append(groovyDsl( + "apply plugin: 'dev.gradleplugins.gradle-plugin-base'", + "gradlePlugin.compatibility.gradleApiVersion = '5.6'", + "tasks.register('verify') {", + " doLast {", + " assert configurations.functionalTestImplementation.dependencies.any { 'dev.gradleplugins:gradle-test-kit:5.6' == \"${it.group}:${it.name}:${it.version}\" }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Nested + class DependenciesTest extends GradlePluginDevelopmentTestSuiteDependenciesTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public ExpressionBuilder testSuiteDsl() { + return literal("functionalTest"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public GradleSettingsFile settingsFile() { + return settingsFile; + } + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java new file mode 100644 index 00000000..96290a17 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java @@ -0,0 +1,239 @@ +package dev.gradleplugins; + +import dev.gradleplugins.buildscript.ast.ExpressionBuilder; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.buildscript.io.GradleSettingsFile; +import dev.gradleplugins.runnerkit.BuildResult; +import dev.gradleplugins.runnerkit.GradleExecutor; +import dev.gradleplugins.runnerkit.GradleRunner; +import dev.gradleplugins.testers.GradlePluginDevelopmentTestSuiteDependenciesTester; +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; + +import java.io.IOException; +import java.nio.file.Path; + +import static dev.gradleplugins.buildscript.blocks.BuildscriptBlock.classpath; +import static dev.gradleplugins.buildscript.blocks.DependencyNotation.files; +import static dev.gradleplugins.buildscript.syntax.Syntax.groovyDsl; +import static dev.gradleplugins.buildscript.syntax.Syntax.literal; +import static dev.gradleplugins.fixtures.runnerkit.BuildResultMatchers.hasFailureCause; +import static dev.gradleplugins.fixtures.runnerkit.BuildResultMatchers.hasFailureDescription; +import static org.hamcrest.MatcherAssert.assertThat; + +class GradlePluginDevelopmentUnitTestingFunctionalTests { + @TempDir(cleanup = CleanupMode.ON_SUCCESS) + Path testDirectory; + GradleRunner runner = GradleRunner.create(GradleExecutor.gradleTestKit()).withGradleVersion(System.getProperty("dev.gradleplugins.defaultGradleVersion")).withPluginClasspath().inDirectory(() -> testDirectory); + GradleBuildFile buildFile; + GradleSettingsFile settingsFile; + + @BeforeEach + void givenProject() throws IOException { + settingsFile = GradleSettingsFile.inDirectory(testDirectory); + settingsFile.buildscript(it -> it.dependencies(classpath(files(runner.getPluginClasspath())))); + settingsFile.append(groovyDsl("rootProject.name = 'gradle-plugin'")); + + buildFile = GradleBuildFile.inDirectory(testDirectory); + buildFile.plugins(it -> it.id("dev.gradleplugins.gradle-plugin-unit-test")); + } + + @Test // https://github.com/gradle-plugins/toolbox/issues/65 + void canAddDependenciesBeforeCoreGradleDevelPluginApplied() { + // We need to avoid an implementation that rely on the sourceSet for the component dependencies. + // If we do, the sourceSet can realize/register before we apply core Gradle plugins. + // Those plugins assume the sourceSet does not exist which result in a failure. + buildFile.append(groovyDsl( + "test.dependencies.implementation 'org.junit.jupiter:junit-jupiter:5.8.1'", + "apply plugin: 'java-gradle-plugin'", + "", + "tasks.register('verify') {", + " doLast {", + " assert configurations.testImplementation.dependencies.any { 'org.junit.jupiter:junit-jupiter:5.8.1' == \"${it.group}:${it.name}:${it.version}\" }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Nested + class GivenCorePluginAppliedTest { + @BeforeEach + void givenProject() throws IOException { + buildFile.plugins(it -> it.id("java-gradle-plugin")); + } + + @Test + void hasMainSourceSetAsTestedSourceSetConvention() { + buildFile.append(groovyDsl( + "test.testedSourceSet = null", + "tasks.register('verify') {", + " doLast {", + " assert test.testedSourceSet.orNull?.name == 'main'", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void usesDevelPluginSourceSetAsTestedSourceSetConvention() { + buildFile.append(groovyDsl( + "gradlePlugin.pluginSourceSet(sourceSets.create('anotherMain'))", + "test.testedSourceSet = null", + "tasks.register('verify') {", + " doLast {", + " assert test.testedSourceSet.orNull?.name == 'anotherMain'", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void disallowChangesToSourceSetProperty() { + buildFile.append(groovyDsl( + "afterEvaluate {", + " test.sourceSet = null", // expect failure + "}", + "tasks.register('verify')" + )); + + BuildResult result = runner.withTasks("verify").buildAndFail(); + assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); + assertThat(result, hasFailureCause("The value for property 'sourceSet' is final and cannot be changed any further.")); + } + + @Test + void disallowChangesToTestedSourceSetProperty() { + buildFile.append(groovyDsl( + "afterEvaluate {", + " test.testedSourceSet = null", // expect failure + "}", + "tasks.register('verify')" + )); + + BuildResult result = runner.withTasks("verify").buildAndFail(); + assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); + assertThat(result, hasFailureCause("The value for property 'testedSourceSet' is final and cannot be changed any further.")); + } + + @Test + void disallowChangesToTestingStrategiesProperty() { + buildFile.append(groovyDsl( + "afterEvaluate {", + " test.testingStrategies = null", // expect failure + "}", + "tasks.register('verify')" + )); + + BuildResult result = runner.withTasks("verify").buildAndFail(); + assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); + assertThat(result, hasFailureCause("The value for property 'testingStrategies' is final and cannot be changed any further.")); + } + + @Test + void returnsTestTasksOnTaskViewElementQuery() { + buildFile.append(groovyDsl( + "test {", + " testingStrategies = [strategies.coverageForGradleVersion('6.8'), strategies.coverageForGradleVersion('7.1')]", + "}", + "", + "tasks.register('verify') {", + " doLast {", + " assert test.testTasks.elements.get().every { it.name == 'test6.8' || it.name == 'test7.1' }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void includesPluginUnderTestMetadataConfigurationDependencies() { + buildFile.append(groovyDsl( + "test.dependencies.pluginUnderTestMetadata files('my/own/dep.jar')", + "", + "tasks.register('verify') {", + " doLast {", + " assert test.pluginUnderTestMetadataTask.get().pluginClasspath.any { it.path.endsWith('/my/own/dep.jar') }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void addsPluginUnderTestMetadataAsRuntimeOnlyDependency() { + buildFile.append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert test.dependencies.runtimeOnly.asConfiguration.get().dependencies.any {", + " it instanceof SelfResolvingDependency && it.files.singleFile.path.endsWith('/pluginUnderTestMetadataTest')", + " }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void doesNotIncludesSourceSetInDevelTestSourceSets() { + buildFile.append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert !gradlePlugin.testSourceSets.any { it.name == 'test' }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Test + void hasGradleApiImplementationDependency() { + buildFile.append(groovyDsl( + "apply plugin: 'dev.gradleplugins.gradle-plugin-base'", + "gradlePlugin.compatibility.minimumGradleVersion = '5.6'", + "tasks.register('verify') {", + " doLast {", + " assert configurations.testImplementation.dependencies.any { 'dev.gradleplugins:gradle-api:5.6' == \"${it.group}:${it.name}:${it.version}\" }", + " }", + "}" + )); + + runner.withTasks("verify").build(); + } + + @Nested + class DependenciesTest extends GradlePluginDevelopmentTestSuiteDependenciesTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public ExpressionBuilder testSuiteDsl() { + return literal("test"); + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public GradleSettingsFile settingsFile() { + return settingsFile; + } + } + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/ProjectDependenciesExtensionFunctionalTests.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/ProjectDependenciesExtensionFunctionalTests.java new file mode 100644 index 00000000..eac6cc59 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/ProjectDependenciesExtensionFunctionalTests.java @@ -0,0 +1,88 @@ +package dev.gradleplugins; + +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.runnerkit.GradleExecutor; +import dev.gradleplugins.runnerkit.GradleRunner; +import dev.gradleplugins.testers.GradleApiDependencyTester; +import dev.gradleplugins.testers.GradlePluginDependencyTester; +import dev.gradleplugins.testers.GradleTestKitDependencyTester; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; + +import static dev.gradleplugins.buildscript.syntax.Syntax.groovyDsl; + +class ProjectDependenciesExtensionFunctionalTests { + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path testDirectory; + GradleRunner runner = GradleRunner.create(GradleExecutor.gradleTestKit()).withGradleVersion(System.getProperty("dev.gradleplugins.defaultGradleVersion")).withPluginClasspath().inDirectory(() -> testDirectory); + GradleBuildFile buildFile; + + @BeforeEach + void setup() { + buildFile = GradleBuildFile.inDirectory(testDirectory); + buildFile.plugins(it -> it.id("dev.gradleplugins.gradle-plugin-base")); + } + + @Nested + class GradleApiDependencyTest extends GradleApiDependencyTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public Expression gradleApiDsl(String version) { + return groovyDsl("dependencies.gradleApi('" + version + "')"); + } + } + + @Nested + class GradleTestKitDependencyTest extends GradleTestKitDependencyTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public Expression gradleTestKitDsl(String version) { + return groovyDsl("dependencies.gradleTestKit('" + version + "')"); + } + + @Override + public Expression gradleTestKitDsl() { + return groovyDsl("dependencies.gradleTestKit()"); + } + } + + @Nested + class GradlePluginDependencyTest extends GradlePluginDependencyTester { + @Override + public GradleRunner runner() { + return runner; + } + + @Override + public GradleBuildFile buildFile() { + return buildFile; + } + + @Override + public Expression gradlePluginDsl(String pluginNotation) { + return groovyDsl("dependencies.gradlePlugin('" + pluginNotation + "')"); + } + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/DependencyBucketTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/DependencyBucketTester.java new file mode 100644 index 00000000..63e1be4a --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/DependencyBucketTester.java @@ -0,0 +1,131 @@ +package dev.gradleplugins.testers; + +import dev.gradleplugins.buildscript.ast.ExpressionBuilder; +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.buildscript.io.GradleSettingsFile; +import dev.gradleplugins.runnerkit.GradleRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; + +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.string; + +public abstract class DependencyBucketTester { + public abstract GradleRunner runner(); + + public abstract ExpressionBuilder bucketDsl(); + + public abstract GradleBuildFile buildFile(); + + public abstract GradleSettingsFile settingsFile(); + + private abstract class Tester { + public abstract Expression bucketDsl(Expression dsl); + + @Test + void testGroupArtifactVersionNotation() { + buildFile().append(bucketDsl(string("com.example:foo:1.0"))); + buildFile().append(val("bucketUnderTest", assign(DependencyBucketTester.this.bucketDsl()))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert bucketUnderTest.asConfiguration.get().incoming.dependencies.any {", + " it instanceof ExternalModuleDependency && 'com.example:foo:1.0' == \"${it.group}:${it.name}:${it.version}\"", + " }", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testProjectNotation() throws IOException { + Files.createDirectories(runner().getWorkingDirectory().toPath().resolve("other-project")); + settingsFile().append(groovyDsl("include 'other-project'")); + buildFile().append(bucketDsl(groovyDsl("project(':other-project')"))); + buildFile().append(val("bucketUnderTest", assign(DependencyBucketTester.this.bucketDsl()))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert bucketUnderTest.asConfiguration.get().incoming.dependencies.any {", + " it instanceof ProjectDependency && ':other-project' == it.dependencyProject.path", + " }", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testLocalProjectNotation() { + buildFile().append(bucketDsl(groovyDsl("project"))); + buildFile().append(val("bucketUnderTest", assign(DependencyBucketTester.this.bucketDsl()))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert bucketUnderTest.asConfiguration.get().incoming.dependencies.any {", + " it instanceof ProjectDependency && project.path == it.dependencyProject.path", + " }", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testFileCollectionNotation() { + buildFile().append(bucketDsl(groovyDsl("files('my-path')"))); + buildFile().append(val("bucketUnderTest", assign(DependencyBucketTester.this.bucketDsl()))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert bucketUnderTest.asConfiguration.get().incoming.dependencies.any {", + " it instanceof FileCollectionDependency && [project.file('my-path')] as Set == it.files.files", + " }", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + } + + @Nested + public class GroovyDslTest extends Tester { + @Override + public Expression bucketDsl(Expression dsl) { + return DependencyBucketTester.this.bucketDsl().call(dsl); + } + } + + @Nested + public class KotlinDslTest extends Tester { + @BeforeEach + void useKotlinDsl() { + buildFile().useKotlinDsl(); + } + + @Override + public Expression bucketDsl(Expression dsl) { + return DependencyBucketTester.this.bucketDsl().call(dsl); + } + } + + @Nested + public class AdderTest extends Tester { + @Override + public Expression bucketDsl(Expression dsl) { + return DependencyBucketTester.this.bucketDsl().call("add", dsl); + } + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/EnforcedPlatformDependencyModifierTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/EnforcedPlatformDependencyModifierTester.java new file mode 100644 index 00000000..b63e3265 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/EnforcedPlatformDependencyModifierTester.java @@ -0,0 +1,115 @@ +package dev.gradleplugins.testers; + +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +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.string; + +public abstract class EnforcedPlatformDependencyModifierTester { + public abstract GradleRunner runner(); + + public abstract ExpressionBuilder modifierDsl(); + + public abstract GradleBuildFile buildFile(); + + private abstract class Tester { + public abstract ExpressionBuilder modifierDsl(Expression dsl); + + @Test + void testEnforcedPlatformDependencyModifierOnExternalModuleDependency() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(groovyDsl("dependencies.create('com.example:foo:1.0')"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert !dependencyUnderTest.isEndorsingStrictVersions()", + " assert dependencyUnderTest.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)?.name == 'enforced-platform'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testEnforcedPlatformDependencyModifierOnGroupArtifactVersionNotation() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(string("com.example:foo:1.0"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert !dependencyUnderTest.isEndorsingStrictVersions()", + " assert dependencyUnderTest.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)?.name == 'enforced-platform'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testEnforcedPlatformDependencyModifierOnProjectDependency() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(groovyDsl("dependencies.create(project)"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert !dependencyUnderTest.isEndorsingStrictVersions()", + " assert dependencyUnderTest.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)?.name == 'enforced-platform'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testEnforcedPlatformDependencyModifierOnProjectNotation() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(groovyDsl("project"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert !dependencyUnderTest.isEndorsingStrictVersions()", + " assert dependencyUnderTest.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)?.name == 'enforced-platform'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + } + + @Nested + public class GroovyDslTest extends Tester { + @Override + public ExpressionBuilder modifierDsl(Expression dsl) { + return EnforcedPlatformDependencyModifierTester.this.modifierDsl().call(dsl); + } + } + + @Nested + public class KotlinDslTest extends Tester { + @BeforeEach + void useKotlinDsl() { + buildFile().useKotlinDsl(); + } + + @Override + public ExpressionBuilder modifierDsl(Expression dsl) { + return EnforcedPlatformDependencyModifierTester.this.modifierDsl().call(dsl); + } + } + + @Nested + public class ModifierTest extends Tester { + @Override + public ExpressionBuilder modifierDsl(Expression dsl) { + return EnforcedPlatformDependencyModifierTester.this.modifierDsl().call("modify", dsl); + } + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradleApiDependencyTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradleApiDependencyTester.java new file mode 100644 index 00000000..21cd9ba7 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradleApiDependencyTester.java @@ -0,0 +1,50 @@ +package dev.gradleplugins.testers; + +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.runnerkit.GradleRunner; +import org.junit.jupiter.api.Test; + +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; + +public abstract class GradleApiDependencyTester { + public abstract GradleRunner runner(); + + public abstract GradleBuildFile buildFile(); + + public abstract Expression gradleApiDsl(String version); + + @Test + void testGradleApiDependency() { + buildFile().append(val("dependencyUnderTest", assign(gradleApiDsl("6.3")))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest instanceof ExternalModuleDependency", + " assert dependencyUnderTest.group == 'dev.gradleplugins'", + " assert dependencyUnderTest.name == 'gradle-api'", + " assert dependencyUnderTest.version == '6.3'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testLocalGradleApiDependency() { + buildFile().append(val("dependencyUnderTest", assign(gradleApiDsl("local")))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest instanceof SelfResolvingDependency", + " assert dependencyUnderTest.targetComponentId.displayName == 'Gradle API'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDependencyTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDependencyTester.java new file mode 100644 index 00000000..632718c6 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDependencyTester.java @@ -0,0 +1,35 @@ +package dev.gradleplugins.testers; + +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.runnerkit.GradleRunner; +import org.junit.jupiter.api.Test; + +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; + +public abstract class GradlePluginDependencyTester { + public abstract GradleRunner runner(); + + public abstract GradleBuildFile buildFile(); + + public abstract Expression gradlePluginDsl(String pluginNotation); + + @Test + void testGradlePluginDependency() { + buildFile().append(val("dependencyUnderTest", assign(gradlePluginDsl("dev.gradleplugins.java-gradle-plugin:1.6.1")))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest instanceof ExternalModuleDependency", + " assert dependencyUnderTest.group == 'dev.gradleplugins.java-gradle-plugin'", + " assert dependencyUnderTest.name == 'dev.gradleplugins.java-gradle-plugin.gradle.plugin'", + " assert dependencyUnderTest.version == '1.6.1'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } +} 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 new file mode 100644 index 00000000..373358b4 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradlePluginDevelopmentTestSuiteDependenciesTester.java @@ -0,0 +1,353 @@ +package dev.gradleplugins.testers; + +import dev.gradleplugins.buildscript.ast.ExpressionBuilder; +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.buildscript.io.GradleSettingsFile; +import dev.gradleplugins.runnerkit.GradleRunner; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +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 { + public abstract GradleRunner runner(); + + public abstract ExpressionBuilder testSuiteDsl(); + + public abstract GradleBuildFile buildFile(); + public abstract GradleSettingsFile settingsFile(); + + @Nested + class GradleApiDependencyTest extends GradleApiDependencyTester { + @Override + public GradleRunner runner() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + + @Override + public Expression gradleApiDsl(String version) { + return testSuiteDsl().dot("dependencies.gradleApi").call(string(version)); + } + } + + @Nested + class GradleTestKitDependencyTest extends GradleTestKitDependencyTester { + @Override + public GradleRunner runner() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + + @Override + public Expression gradleTestKitDsl(String version) { + return testSuiteDsl().dot("dependencies.gradleTestKit").call(string(version)); + } + + @Override + public Expression gradleTestKitDsl() { + return testSuiteDsl().dot("dependencies.gradleTestKit()"); + } + } + + @Nested + class ProjectDependencyTest extends ProjectDependencyTester { + @Override + public GradleRunner runner() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + + @Override + public GradleSettingsFile settingsFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.settingsFile(); + } + + @Override + public Expression projectDsl(String projectPath) { + return testSuiteDsl().dot("dependencies.project").call(string(projectPath)); + } + + @Override + public Expression projectDsl() { + return testSuiteDsl().dot("dependencies.project()"); + } + } + + @Nested + class PlatformDependencyModifierTest extends PlatformDependencyModifierTester { + @Override + public GradleRunner runner() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); + } + + @Override + public ExpressionBuilder modifierDsl() { + return testSuiteDsl().dot("dependencies.platform"); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + } + + @Nested + class EnforcedPlatformDependencyModifierTest extends EnforcedPlatformDependencyModifierTester { + @Override + public GradleRunner runner() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); + } + + @Override + public ExpressionBuilder modifierDsl() { + return testSuiteDsl().dot("dependencies.enforcedPlatform"); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + } + + @Nested + class TestFixturesDependencyModifierTest extends TestFixturesDependencyModifierTester { + @Override + public GradleRunner runner() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); + } + + @Override + public ExpressionBuilder modifierDsl() { + return testSuiteDsl().dot("dependencies.testFixtures"); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + + @Override + public GradleSettingsFile settingsFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.settingsFile(); + } + } + + @Nested + 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"); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + + @Override + 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 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"); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + + @Override + 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 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"); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + + @Override + 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 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"); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + + @Override + 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 + class PluginUnderTestDependencyBucketTest extends DependencyBucketTester { + @Override + public GradleRunner runner() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.runner(); + } + + @Override + public ExpressionBuilder bucketDsl() { + return testSuiteDsl().dot("dependencies.pluginUnderTest"); + } + + @Override + public GradleBuildFile buildFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.buildFile(); + } + + @Override + public GradleSettingsFile settingsFile() { + return GradlePluginDevelopmentTestSuiteDependenciesTester.this.settingsFile(); + } + + @Test + void isDeclarableDependencyBucket() { + buildFile().append(val("bucketUnderTest", assign(bucketDsl()))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert !bucketUnderTest.asConfiguration.get().isCanBeConsumed()", + " assert !bucketUnderTest.asConfiguration.get().isCanBeResolved()", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void hasDescription() { + buildFile().append(val("bucketUnderTest", assign(bucketDsl()))); + buildFile().append(val("testSuiteUnderTest", assign(testSuiteDsl()))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert bucketUnderTest.asConfiguration.get().description == \"Plugin under test for ${testSuiteUnderTest.sourceSet.get()}.\"", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradleTestKitDependencyTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradleTestKitDependencyTester.java new file mode 100644 index 00000000..0f44c462 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/GradleTestKitDependencyTester.java @@ -0,0 +1,66 @@ +package dev.gradleplugins.testers; + +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.runnerkit.GradleRunner; +import org.junit.jupiter.api.Test; + +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; + +public abstract class GradleTestKitDependencyTester { + public abstract GradleRunner runner(); + + public abstract GradleBuildFile buildFile(); + + public abstract Expression gradleTestKitDsl(String version); + public abstract Expression gradleTestKitDsl(); + + @Test + void testGradleTestKitDependency() { + buildFile().append(val("dependencyUnderTest", assign(gradleTestKitDsl("6.3")))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest instanceof ExternalModuleDependency", + " assert dependencyUnderTest.group == 'dev.gradleplugins'", + " assert dependencyUnderTest.name == 'gradle-test-kit'", + " assert dependencyUnderTest.version == '6.3'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testLocalGradleTestKitDependency() { + buildFile().append(val("dependencyUnderTest", assign(gradleTestKitDsl("local")))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest instanceof SelfResolvingDependency", + " assert dependencyUnderTest.targetComponentId.displayName == 'Gradle TestKit'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testGradleTestKitLocalDependency() { + buildFile().append(val("dependencyUnderTest", assign(gradleTestKitDsl()))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest instanceof SelfResolvingDependency", + " assert dependencyUnderTest.targetComponentId.displayName == 'Gradle TestKit'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/PlatformDependencyModifierTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/PlatformDependencyModifierTester.java new file mode 100644 index 00000000..ae7046d2 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/PlatformDependencyModifierTester.java @@ -0,0 +1,115 @@ +package dev.gradleplugins.testers; + +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +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.string; + +public abstract class PlatformDependencyModifierTester { + public abstract GradleRunner runner(); + + public abstract ExpressionBuilder modifierDsl(); + + public abstract GradleBuildFile buildFile(); + + private abstract class Tester { + public abstract ExpressionBuilder modifierDsl(Expression dsl); + + @Test + void testPlatformDependencyModifierOnExternalModuleDependency() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(groovyDsl("dependencies.create('com.example:foo:1.0')"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest.isEndorsingStrictVersions()", + " assert dependencyUnderTest.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)?.name == 'platform'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testPlatformDependencyModifierOnGroupArtifactVersionNotation() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(string("com.example:foo:1.0"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest.isEndorsingStrictVersions()", + " assert dependencyUnderTest.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)?.name == 'platform'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testPlatformDependencyModifierOnProjectDependency() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(groovyDsl("dependencies.create(project)"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest.isEndorsingStrictVersions()", + " assert dependencyUnderTest.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)?.name == 'platform'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testPlatformDependencyModifierOnProjectNotation() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(groovyDsl("project"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest.isEndorsingStrictVersions()", + " assert dependencyUnderTest.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE)?.name == 'platform'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + } + + @Nested + public class GroovyDslTest extends Tester { + @Override + public ExpressionBuilder modifierDsl(Expression dsl) { + return PlatformDependencyModifierTester.this.modifierDsl().call(dsl); + } + } + + @Nested + public class KotlinDslTest extends Tester { + @BeforeEach + void useKotlinDsl() { + buildFile().useKotlinDsl(); + } + + @Override + public ExpressionBuilder modifierDsl(Expression dsl) { + return PlatformDependencyModifierTester.this.modifierDsl().call(dsl); + } + } + + @Nested + public class ModifierTest extends Tester { + @Override + public ExpressionBuilder modifierDsl(Expression dsl) { + return PlatformDependencyModifierTester.this.modifierDsl().call("modify", dsl); + } + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/ProjectDependencyTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/ProjectDependencyTester.java new file mode 100644 index 00000000..516f8e82 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/ProjectDependencyTester.java @@ -0,0 +1,57 @@ +package dev.gradleplugins.testers; + +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.buildscript.io.GradleSettingsFile; +import dev.gradleplugins.runnerkit.GradleRunner; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; + +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; + +public abstract class ProjectDependencyTester { + public abstract GradleRunner runner(); + + public abstract GradleBuildFile buildFile(); + + public abstract GradleSettingsFile settingsFile(); + + public abstract Expression projectDsl(String projectPath); + public abstract Expression projectDsl(); + + @Test + void testProjectWithPathDependency() throws IOException { + Files.createDirectories(runner().getWorkingDirectory().toPath().resolve("other-project")); + settingsFile().append(groovyDsl("include 'other-project'")); + buildFile().append(val("dependencyUnderTest", assign(projectDsl(":other-project")))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest instanceof ProjectDependency", + " assert dependencyUnderTest.dependencyProject.path == ':other-project'", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testProjectDependency() { + buildFile().append(val("dependencyUnderTest", assign(projectDsl()))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest instanceof ProjectDependency", + " assert dependencyUnderTest.dependencyProject.path == project.path", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } +} diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/TestFixturesDependencyModifierTester.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/TestFixturesDependencyModifierTester.java new file mode 100644 index 00000000..a94bdc11 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/testers/TestFixturesDependencyModifierTester.java @@ -0,0 +1,128 @@ +package dev.gradleplugins.testers; + +import dev.gradleplugins.buildscript.ast.ExpressionBuilder; +import dev.gradleplugins.buildscript.ast.expressions.Expression; +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.buildscript.io.GradleSettingsFile; +import dev.gradleplugins.runnerkit.GradleRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +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.string; + +public abstract class TestFixturesDependencyModifierTester { + public abstract GradleRunner runner(); + + public abstract ExpressionBuilder modifierDsl(); + + public abstract GradleBuildFile buildFile(); + public abstract GradleSettingsFile settingsFile(); + + private abstract class Tester { + public abstract ExpressionBuilder modifierDsl(Expression dsl); + + @Test + void testTestFixturesDependencyModifierOnExternalModuleDependency() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(groovyDsl("dependencies.create('com.example:foo:1.0')"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest.requestedCapabilities.any {", + " 'com.example:foo-test-fixtures' == \"${it.group}:${it.name}\" && it.version == null", + " }", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testTestFixturesDependencyModifierOnGroupArtifactVersionNotation() { + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(string("com.example:foo:1.0"))))); + buildFile().append(groovyDsl( + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest.requestedCapabilities.any {", + " 'com.example:foo-test-fixtures' == \"${it.group}:${it.name}\" && it.version == null", + " }", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testTestFixturesDependencyModifierOnProjectDependency() { + settingsFile().append(groovyDsl("rootProject.name = 'foo'")); + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(groovyDsl("dependencies.create(project)"))))); + buildFile().append(groovyDsl( + "group = 'com.example'", + "version = '4.2'", + "", + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest.requestedCapabilities.any {", + " 'com.example:foo-test-fixtures:4.2' == \"${it.group}:${it.name}:${it.version}\"", + " }", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + + @Test + void testTestFixturesDependencyModifierOnProjectNotation() { + settingsFile().append(groovyDsl("rootProject.name = 'foo'")); + buildFile().append(val("dependencyUnderTest", assign(modifierDsl(groovyDsl("project"))))); + buildFile().append(groovyDsl( + "group = 'com.example'", + "version = '4.2'", + "tasks.register('verify') {", + " doLast {", + " assert dependencyUnderTest.requestedCapabilities.any {", + " 'com.example:foo-test-fixtures:4.2' == \"${it.group}:${it.name}:${it.version}\"", + " }", + " }", + "}" + )); + + runner().withTasks("verify").build(); + } + } + + @Nested + public class GroovyDslTest extends Tester { + @Override + public ExpressionBuilder modifierDsl(Expression dsl) { + return TestFixturesDependencyModifierTester.this.modifierDsl().call(dsl); + } + } + + @Nested + public class KotlinDslTest extends Tester { + @BeforeEach + void useKotlinDsl() { + buildFile().useKotlinDsl(); + } + + @Override + public ExpressionBuilder modifierDsl(Expression dsl) { + return TestFixturesDependencyModifierTester.this.modifierDsl().call(dsl); + } + } + + @Nested + public class ModifierTest extends Tester { + @Override + public ExpressionBuilder modifierDsl(Expression dsl) { + return TestFixturesDependencyModifierTester.this.modifierDsl().call("modify", dsl); + } + } +} diff --git a/subprojects/gradle-plugin-development/src/main/groovy/dev/gradleplugins/internal/dsl/groovy/GroovyDslRuntimeExtensions.groovy b/subprojects/gradle-plugin-development/src/main/groovy/dev/gradleplugins/internal/dsl/groovy/GroovyDslRuntimeExtensions.groovy index eec6fe36..cf9fde97 100644 --- a/subprojects/gradle-plugin-development/src/main/groovy/dev/gradleplugins/internal/dsl/groovy/GroovyDslRuntimeExtensions.groovy +++ b/subprojects/gradle-plugin-development/src/main/groovy/dev/gradleplugins/internal/dsl/groovy/GroovyDslRuntimeExtensions.groovy @@ -1,10 +1,12 @@ package dev.gradleplugins.internal.dsl.groovy +import dev.gradleplugins.internal.runtime.dsl.GroovyHelper + /** * Helper class to add extension methods to Groovy DSL classes at runtime. * The end result is comparable to the Kotlin extension methods. */ -class GroovyDslRuntimeExtensions { +class GroovyDslRuntimeExtensions extends GroovyHelper { /** * Add an extension methods to an object. * @@ -12,7 +14,8 @@ class GroovyDslRuntimeExtensions { * @param methodName the extension method name * @param methodBody the extension method body */ - static void extendWithMethod(Object self, String methodName, Closure methodBody) { + @Override + void addNewInstanceMethod(Object self, String methodName, Closure methodBody) { self.metaClass."${methodName}" = methodBody } } 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 new file mode 100644 index 00000000..f36cb3ca --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencies.java @@ -0,0 +1,27 @@ +package dev.gradleplugins; + +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.ProjectDependency; +import org.gradle.api.plugins.ExtensionAware; +import org.gradle.plugin.devel.GradlePluginDevelopmentExtension; + +public interface GradlePluginDevelopmentDependencies extends GradlePluginDevelopmentDependencyModifiers.PlatformDependencyModifiers { + GradlePluginDevelopmentDependencyBucket getApi(); + GradlePluginDevelopmentDependencyBucket getImplementation(); + GradlePluginDevelopmentDependencyBucket getCompileOnly(); + GradlePluginDevelopmentDependencyBucket getCompileOnlyApi(); + GradlePluginDevelopmentDependencyBucket getRuntimeOnly(); + GradlePluginDevelopmentDependencyBucket getAnnotationProcessor(); + + Dependency gradleApi(String version); + + ProjectDependency project(String projectPath); + ProjectDependency project(); + + ExternalModuleDependency gradlePlugin(String pluginNotation); + + static GradlePluginDevelopmentDependencies dependencies(GradlePluginDevelopmentExtension extension) { + return (GradlePluginDevelopmentDependencies) ((ExtensionAware) extension).getExtensions().getByName("dependencies"); + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencyExtension.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencyExtension.java index daea187f..5899442d 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencyExtension.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencyExtension.java @@ -1,6 +1,7 @@ package dev.gradleplugins; import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.artifacts.dsl.DependencyHandler; import java.util.Objects; @@ -32,6 +33,8 @@ public interface GradlePluginDevelopmentDependencyExtension { * * @return a dependency instance for the latest Gradle Fixtures, never null */ + // TODO(2.0): Remove this method + @Deprecated Dependency gradleFixtures(); /** @@ -41,6 +44,16 @@ public interface GradlePluginDevelopmentDependencyExtension { */ Dependency gradleRunnerKit(); + /** + * Returns the Gradle plugin's external dependency marked by the specified plugin notation. + * The Gradle plugin marker consist of a published redirection artifact at {@literal :.gradle.plugin:}. + * The plugin notation is a short form notation a-la Maven: {@literal :}. + * + * @param pluginNotation the plugin id and version of the Gradle plugin dependency + * @return a dependency instance to the Gradle Plugin marked by the specified notation, never null + */ + ExternalModuleDependency gradlePlugin(String pluginNotation); + /** * Returns {@link DependencyHandler} extension methods. * diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencyModifiers.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencyModifiers.java new file mode 100644 index 00000000..0f169a57 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentDependencyModifiers.java @@ -0,0 +1,23 @@ +package dev.gradleplugins; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ProjectDependency; + +public interface GradlePluginDevelopmentDependencyModifiers { + interface PlatformDependencyModifiers { + DependencyModifier getPlatform(); + DependencyModifier getEnforcedPlatform(); + } + + interface TestFixturesDependencyModifiers { + DependencyModifier getTestFixtures(); + } + + interface DependencyModifier { + DependencyType modify(DependencyType dependency); + ExternalModuleDependency modify(CharSequence dependencyNotation); + ProjectDependency modify(Project project); + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentTestSuiteDependencies.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentTestSuiteDependencies.java index 43c1c140..ed1888b6 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentTestSuiteDependencies.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentTestSuiteDependencies.java @@ -4,44 +4,84 @@ import org.gradle.api.NamedDomainObjectProvider; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ProjectDependency; -public interface GradlePluginDevelopmentTestSuiteDependencies { +public interface GradlePluginDevelopmentTestSuiteDependencies extends GradlePluginDevelopmentDependencyModifiers.PlatformDependencyModifiers, GradlePluginDevelopmentDependencyModifiers.TestFixturesDependencyModifiers { + // TODO(2.0): Remove this method + // Essentially deprecated, use getImplementation().add(...) void implementation(Object notation); + // TODO(2.0): Remove this method + // Essentially deprecated, use getImplementation().add(...) void implementation(Object notation, Action action); + GradlePluginDevelopmentDependencyBucket getImplementation(); + + // TODO(2.0): Remove this method + // Essentially deprecated, use getCompileOnly().add(...) void compileOnly(Object notation); + GradlePluginDevelopmentDependencyBucket getCompileOnly(); + + // TODO(2.0): Remove this method + // Essentially deprecated, use getRuntimeOnly().add(...) void runtimeOnly(Object notation); + GradlePluginDevelopmentDependencyBucket getRuntimeOnly(); + + // TODO(2.0): Remove this method + // Essentially deprecated, use getAnnotationProcessor().add(...) void annotationProcessor(Object notation); + GradlePluginDevelopmentDependencyBucket getAnnotationProcessor(); + + // TODO(2.0): Remove this method + // Essentially deprecated, use getPluginUnderTest().add(...) void pluginUnderTestMetadata(Object notation); + // TODO(2.0): Remove this method + @Deprecated NamedDomainObjectProvider getPluginUnderTestMetadata(); - Object testFixtures(Object notation); + GradlePluginDevelopmentDependencyBucket getPluginUnderTest(); + + // TODO(2.0): Remove this method + // Essentially deprecated, use getTestFixtures().modify(...) + ModuleDependency testFixtures(Object notation); + + // TODO(2.0): Remove this method + // Essentially deprecated, use getPlatform().modify(...) + ModuleDependency platform(Object notation); - Object platform(Object notation); + ProjectDependency project(String projectPath); + ProjectDependency project(); + // TODO(2.0): Remove this method @Deprecated Object spockFramework(); + // TODO(2.0): Remove this method @Deprecated Object spockFramework(String version); + // TODO(2.0): Remove this method @Deprecated Object gradleFixtures(); + // TODO(2.0): Return SelfResolvingDependency Object gradleTestKit(); + // TODO(2.0): Return Dependency Object gradleTestKit(String version); + // TODO(2.0): Remove this method @Deprecated Object groovy(); + // TODO(2.0): Remove this method @Deprecated Object groovy(String version); + // TODO(2.0): Return Dependency Object gradleApi(String version); } diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/DependencyFactory.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/DependencyFactory.java index 05a51987..ea3cc861 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/DependencyFactory.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/DependencyFactory.java @@ -87,6 +87,32 @@ public ExternalModuleDependency spockFrameworkPlatform(String version) { return (ExternalModuleDependency) dependencies.platform(create("org.spockframework:spock-bom:" + version)); } + public ExternalModuleDependency gradlePlugin(String notation) { + assert notation != null : "'notation' must not be null"; + + // Parsing [:] + String pluginId = null; + String version = null; + { + int index = notation.indexOf(':'); + if (index == -1) { + pluginId = notation; + } else if (notation.indexOf(':', index + 1) != -1) { + throw new RuntimeException("Invalid Gradle plugin notation, please use '' or ':'."); + } else { + pluginId = notation.substring(0, index); + version = notation.substring(index + 1); + } + } + + // Dependency + if (version == null) { + return (ExternalModuleDependency) dependencies.create(pluginId + ":" + pluginId + ".gradle.plugin"); + } else { + return (ExternalModuleDependency) dependencies.create(pluginId + ":" + pluginId + ".gradle.plugin:" + version); + } + } + public static DependencyFactory forProject(Project project) { return new DependencyFactory(project.getDependencies()); } diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/EnforcedPlatformDependencyModifier.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/EnforcedPlatformDependencyModifier.java new file mode 100644 index 00000000..2bd01895 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/EnforcedPlatformDependencyModifier.java @@ -0,0 +1,34 @@ +package dev.gradleplugins.internal; + +import dev.gradleplugins.GradlePluginDevelopmentDependencyModifiers; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ProjectDependency; + +public final class EnforcedPlatformDependencyModifier implements GradlePluginDevelopmentDependencyModifiers.DependencyModifier { + private final Project project; + private final DependencyFactory dependencyFactory; + + public EnforcedPlatformDependencyModifier(Project project) { + this.project = project; + this.dependencyFactory = DependencyFactory.forProject(project); + } + + @Override + public DependencyType modify(DependencyType dependency) { + @SuppressWarnings("unchecked") + final DependencyType result = (DependencyType) project.getDependencies().enforcedPlatform(dependency); + return result; + } + + @Override + public ExternalModuleDependency modify(CharSequence dependencyNotation) { + return modify(dependencyFactory.create(dependencyNotation)); + } + + @Override + public ProjectDependency modify(Project project) { + return modify(dependencyFactory.create(project)); + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradlePluginDevelopmentTestSuiteInternal.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradlePluginDevelopmentTestSuiteInternal.java index 1a156da1..6167ab15 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradlePluginDevelopmentTestSuiteInternal.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradlePluginDevelopmentTestSuiteInternal.java @@ -1,19 +1,24 @@ package dev.gradleplugins.internal; import dev.gradleplugins.GradlePluginDevelopmentDependencyBucket; +import dev.gradleplugins.GradlePluginDevelopmentDependencyModifiers; import dev.gradleplugins.GradlePluginDevelopmentTestSuite; import dev.gradleplugins.GradlePluginDevelopmentTestSuiteDependencies; import dev.gradleplugins.GradlePluginTestingStrategyFactory; import dev.gradleplugins.GradleRuntimeCompatibility; import dev.gradleplugins.TaskView; +import dev.gradleplugins.internal.runtime.dsl.GroovyHelper; +import dev.gradleplugins.internal.util.LocalOrRemoteVersionTransformer; import org.apache.commons.lang3.StringUtils; +import org.codehaus.groovy.runtime.MethodClosure; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectProvider; import org.gradle.api.Project; +import org.gradle.api.Transformer; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ModuleDependency; -import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.component.SoftwareComponent; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.PluginManager; @@ -30,7 +35,10 @@ import javax.inject.Inject; import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.function.Supplier; @@ -60,6 +68,15 @@ public GradlePluginDevelopmentTestSuiteInternal(String name, Project project, Ta this.name = name; this.displayName = GUtil.toWords(name) + "s"; this.dependencies = objects.newInstance(Dependencies.class, project, minimumGradleVersion.orElse(GradleVersion.current().getVersion()).map(GradleRuntimeCompatibility::groovyVersionOf), this); + + // adhoc decoration of the dependencies + dependencies.forEach(dependencyBucket -> { + GroovyHelper.instance().addNewInstanceMethod(dependencies, dependencyBucket.getName(), new MethodClosure(dependencyBucket, "add")); + }); + GroovyHelper.instance().addNewInstanceMethod(dependencies, "platform", new MethodClosure(dependencies.getPlatform(), "modify")); + GroovyHelper.instance().addNewInstanceMethod(dependencies, "enforcedPlatform", new MethodClosure(dependencies.getEnforcedPlatform(), "modify")); + GroovyHelper.instance().addNewInstanceMethod(dependencies, "testFixtures", new MethodClosure(dependencies.getTestFixtures(), "modify")); + this.pluginUnderTestMetadataTask = registerPluginUnderTestMetadataTask(tasks, pluginUnderTestMetadataTaskName(name), displayName); this.testTasks = objects.newInstance(TestTaskView.class, testTaskActions, providers.provider(new FinalizeComponentCallable<>()).orElse(getTestTaskCollection())); this.finalizeActions.add(new TestSuiteSourceSetExtendsFromTestedSourceSetIfPresentRule()); @@ -217,22 +234,18 @@ public void dependencies(Action { private final PluginManager pluginManager; private final Provider defaultGroovyVersion; - private final DependencyFactory factory; + private final DependencyFactory dependencyFactory; private final Supplier> pluginUnderTestMetadataSupplier; - private final GradlePluginDevelopmentDependencyBucket implementation; - private final GradlePluginDevelopmentDependencyBucket compileOnly; - private final GradlePluginDevelopmentDependencyBucket runtimeOnly; - private final GradlePluginDevelopmentDependencyBucket annotationProcessor; - private final GradlePluginDevelopmentDependencyBucket pluginUnderTestMetadata; - - @Inject - protected abstract ConfigurationContainer getConfigurations(); - - @Inject - protected abstract DependencyHandler getDependencies(); + private final Project project; + private final Map dependencyBuckets = new LinkedHashMap<>(); + private final GradlePluginDevelopmentDependencyModifiers.DependencyModifier platformDependencyModifier; + private final GradlePluginDevelopmentDependencyModifiers.DependencyModifier enforcedPlatformDependencyModifier; + private final GradlePluginDevelopmentDependencyModifiers.DependencyModifier testFixturesDependencyModifier; + private final Transformer localOrRemoteGradleTestKit; + private final Transformer localOrRemoteGradleApi; private NamedDomainObjectProvider pluginUnderTestMetadata() { return pluginUnderTestMetadataSupplier.get(); @@ -242,52 +255,88 @@ private NamedDomainObjectProvider pluginUnderTestMetadata() { public Dependencies(Project project, Provider defaultGroovyVersion, GradlePluginDevelopmentTestSuite testSuite) { this.pluginManager = project.getPluginManager(); this.defaultGroovyVersion = defaultGroovyVersion; - this.factory = DependencyFactory.forProject(project); + this.dependencyFactory = DependencyFactory.forProject(project); + this.localOrRemoteGradleTestKit = new LocalOrRemoteVersionTransformer<>(dependencyFactory::localGradleTestKit, dependencyFactory::gradleTestKit); + this.localOrRemoteGradleApi = new LocalOrRemoteVersionTransformer<>(dependencyFactory::localGradleApi, dependencyFactory::gradleApi); this.pluginUnderTestMetadataSupplier = new PluginUnderTestMetadataConfigurationSupplier(project, testSuite); project.afterEvaluate(__ -> pluginUnderTestMetadataSupplier.get()); // for now DependencyBucketFactory bucketFactory = new DependencyBucketFactory(project, testSuite.getSourceSet()); - this.implementation = bucketFactory.create("implementation"); - this.compileOnly = bucketFactory.create("compileOnly"); - this.runtimeOnly = bucketFactory.create("runtimeOnly"); - this.annotationProcessor = bucketFactory.create("annotationProcessor"); - this.pluginUnderTestMetadata = bucketFactory.create("pluginUnderTestMetadata"); + this.platformDependencyModifier = new PlatformDependencyModifier(project); + this.enforcedPlatformDependencyModifier = new EnforcedPlatformDependencyModifier(project); + this.testFixturesDependencyModifier = new TestFixturesDependencyModifier(project); + this.project = project; + add(bucketFactory.create("implementation")); + add(bucketFactory.create("compileOnly")); + add(bucketFactory.create("runtimeOnly")); + add(bucketFactory.create("annotationProcessor")); + add(bucketFactory.create("pluginUnderTestMetadata")); + add(bucketFactory.create("pluginUnderTest")); + } + + private void add(GradlePluginDevelopmentDependencyBucket dependencyBucket) { + dependencyBuckets.put(dependencyBucket.getName(), dependencyBucket); } @Override public void implementation(Object notation) { - addDependency(implementation, notation); + addDependency(getImplementation(), notation); } @Override public void implementation(Object notation, Action action) { - implementation.add((ModuleDependency) factory.create(notation), action); + getImplementation().add((ModuleDependency) dependencyFactory.create(notation), action); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getImplementation() { + return dependencyBuckets.get("implementation"); } @Override public void compileOnly(Object notation) { - addDependency(compileOnly, notation); + addDependency(getCompileOnly(), notation); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getCompileOnly() { + return dependencyBuckets.get("compileOnly"); } @Override public void runtimeOnly(Object notation) { - addDependency(runtimeOnly, notation); + addDependency(getRuntimeOnly(), notation); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getRuntimeOnly() { + return dependencyBuckets.get("runtimeOnly"); } @Override public void annotationProcessor(Object notation) { - addDependency(annotationProcessor, notation); + addDependency(getAnnotationProcessor(), notation); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getAnnotationProcessor() { + return dependencyBuckets.get("annotationProcessor"); } @Override public void pluginUnderTestMetadata(Object notation) { - addDependency(pluginUnderTestMetadata, notation); + addDependency(dependencyBuckets.get("pluginUnderTestMetadata"), notation); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getPluginUnderTest() { + return dependencyBuckets.get("pluginUnderTest"); } private void addDependency(GradlePluginDevelopmentDependencyBucket bucket, Object notation) { if (notation instanceof Provider) { - bucket.add(((Provider) notation).map(factory::create)); + bucket.add(((Provider) notation).map(dependencyFactory::create)); } else { - bucket.add(factory.create(notation)); + bucket.add(dependencyFactory.create(notation)); } } @@ -297,13 +346,52 @@ public NamedDomainObjectProvider getPluginUnderTestMetadata() { } @Override - public Object testFixtures(Object notation) { - return getDependencies().testFixtures(notation); + public ModuleDependency testFixtures(Object notation) { + if (notation instanceof CharSequence) { + return getTestFixtures().modify((CharSequence) notation); + } else if (notation instanceof ModuleDependency) { + return getTestFixtures().modify((ModuleDependency) notation); + } else if (notation instanceof Project) { + return getTestFixtures().modify(project); + } + throw new UnsupportedOperationException(); + } + + @Override + public GradlePluginDevelopmentDependencyModifiers.DependencyModifier getTestFixtures() { + return testFixturesDependencyModifier; + } + + @Override + public ModuleDependency platform(Object notation) { + if (notation instanceof CharSequence) { + return getPlatform().modify((CharSequence) notation); + } else if (notation instanceof ModuleDependency) { + return getPlatform().modify((ModuleDependency) notation); + } else if (notation instanceof Project) { + return getPlatform().modify(project); + } + throw new UnsupportedOperationException(); + } + + @Override + public GradlePluginDevelopmentDependencyModifiers.DependencyModifier getPlatform() { + return platformDependencyModifier; + } + + @Override + public GradlePluginDevelopmentDependencyModifiers.DependencyModifier getEnforcedPlatform() { + return enforcedPlatformDependencyModifier; } @Override - public Object platform(Object notation) { - return getDependencies().platform(notation); + public ProjectDependency project(String projectPath) { + return dependencyFactory.create(project.project(projectPath)); + } + + @Override + public ProjectDependency project() { + return dependencyFactory.create(project); } @Override @@ -314,37 +402,42 @@ public Object spockFramework() { @Override public Object spockFramework(String version) { pluginManager.apply("groovy-base"); // Spock framework imply Groovy implementation language - return factory.spockFramework(version); + return dependencyFactory.spockFramework(version); } @Override public Object gradleFixtures() { - return factory.gradleFixtures(); + return dependencyFactory.gradleFixtures(); } @Override public Object gradleTestKit() { - return factory.localGradleTestKit(); + return dependencyFactory.localGradleTestKit(); } @Override public Object gradleTestKit(String version) { - return factory.gradleTestKit(version); + return localOrRemoteGradleTestKit.transform(version); } @Override public Object groovy() { - return defaultGroovyVersion.map(factory::groovy); + return defaultGroovyVersion.map(dependencyFactory::groovy); } @Override public Object groovy(String version) { - return factory.groovy(version); + return dependencyFactory.groovy(version); } @Override public Object gradleApi(String version) { - return factory.gradleApi(version); + return localOrRemoteGradleApi.transform(version); + } + + @Override + public Iterator iterator() { + return dependencyBuckets.values().iterator(); } } diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/PlatformDependencyModifier.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/PlatformDependencyModifier.java new file mode 100644 index 00000000..9a3cb9f8 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/PlatformDependencyModifier.java @@ -0,0 +1,34 @@ +package dev.gradleplugins.internal; + +import dev.gradleplugins.GradlePluginDevelopmentDependencyModifiers; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ProjectDependency; + +public final class PlatformDependencyModifier implements GradlePluginDevelopmentDependencyModifiers.DependencyModifier { + private final Project project; + private final DependencyFactory dependencyFactory; + + public PlatformDependencyModifier(Project project) { + this.project = project; + this.dependencyFactory = DependencyFactory.forProject(project); + } + + @Override + public DependencyType modify(DependencyType dependency) { + @SuppressWarnings("unchecked") + final DependencyType result = (DependencyType) project.getDependencies().platform(dependency); + return result; + } + + @Override + public ExternalModuleDependency modify(CharSequence dependencyNotation) { + return modify(dependencyFactory.create(dependencyNotation)); + } + + @Override + public ProjectDependency modify(Project project) { + return modify(dependencyFactory.create(project)); + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/PluginUnderTestMetadataConfigurationSupplier.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/PluginUnderTestMetadataConfigurationSupplier.java index 61433e6f..fbf0beef 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/PluginUnderTestMetadataConfigurationSupplier.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/PluginUnderTestMetadataConfigurationSupplier.java @@ -26,7 +26,15 @@ private SourceSet sourceSet() { @Override public NamedDomainObjectProvider get() { if (pluginUnderTestMetadata == null) { + final NamedDomainObjectProvider pluginUnderTest = project.getConfigurations().register(sourceSet().getName() + "PluginUnderTest"); + pluginUnderTest.configure(it -> { + it.setCanBeResolved(false); + it.setCanBeConsumed(false); + it.setDescription("Plugin under test for " + sourceSet() + "."); + }); + final Configuration configuration = project.getConfigurations().maybeCreate(sourceSet().getName() + "PluginUnderTestMetadata"); + configuration.extendsFrom(pluginUnderTest.get()); configuration.setCanBeResolved(true); configuration.setCanBeConsumed(false); configuration.attributes(attributes -> attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME))); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/TestFixturesDependencyModifier.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/TestFixturesDependencyModifier.java new file mode 100644 index 00000000..dbfce326 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/TestFixturesDependencyModifier.java @@ -0,0 +1,34 @@ +package dev.gradleplugins.internal; + +import dev.gradleplugins.GradlePluginDevelopmentDependencyModifiers; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ExternalModuleDependency; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ProjectDependency; + +public final class TestFixturesDependencyModifier implements GradlePluginDevelopmentDependencyModifiers.DependencyModifier { + private final Project project; + private final DependencyFactory dependencyFactory; + + public TestFixturesDependencyModifier(Project project) { + this.project = project; + this.dependencyFactory = DependencyFactory.forProject(project); + } + + @Override + public DependencyType modify(DependencyType dependency) { + @SuppressWarnings("unchecked") + final DependencyType result = (DependencyType) project.getDependencies().testFixtures(dependency); + return result; + } + + @Override + public ExternalModuleDependency modify(CharSequence dependencyNotation) { + return modify(dependencyFactory.create(dependencyNotation)); + } + + @Override + public ProjectDependency modify(Project project) { + return modify(dependencyFactory.create(project)); + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java index 405800b2..e079a92e 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java @@ -16,6 +16,7 @@ public void apply(Project project) { project.getPluginManager().apply("gradlepluginsdev.rules.project-extensions"); project.getPluginManager().apply("gradlepluginsdev.rules.gradle-plugin-compatibility-extension"); project.getPluginManager().apply("gradlepluginsdev.rules.gradle-plugin-api-extension"); + project.getPluginManager().apply("gradlepluginsdev.rules.gradle-plugin-dependencies"); whenPluginApplied("java-gradle-plugin", new RemoveGradleApiProjectDependency()).execute(project); whenPluginApplied("java-gradle-plugin", new AddGradleApiDependencyToCompileOnlyApiConfiguration()).execute(project); whenPluginApplied("java-gradle-plugin", new RemoveTestSourceSets()).execute(project); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradlePluginDevelopmentDependenciesRule.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradlePluginDevelopmentDependenciesRule.java new file mode 100644 index 00000000..7b25f8c0 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradlePluginDevelopmentDependenciesRule.java @@ -0,0 +1,277 @@ +package dev.gradleplugins.internal.rules; + +import dev.gradleplugins.GradlePluginDevelopmentDependencies; +import dev.gradleplugins.GradlePluginDevelopmentDependencyBucket; +import dev.gradleplugins.GradlePluginDevelopmentDependencyModifiers; +import dev.gradleplugins.internal.DependencyBucketFactory; +import dev.gradleplugins.internal.DependencyFactory; +import dev.gradleplugins.internal.EnforcedPlatformDependencyModifier; +import dev.gradleplugins.internal.PlatformDependencyModifier; +import dev.gradleplugins.internal.runtime.dsl.GroovyHelper; +import dev.gradleplugins.internal.util.LocalOrRemoteVersionTransformer; +import org.codehaus.groovy.runtime.MethodClosure; +import org.gradle.api.Action; +import org.gradle.api.JavaVersion; +import org.gradle.api.Plugin; +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.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 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.util.GradlePluginDevelopmentUtils.gradlePlugin; +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; + +/*private*/ abstract /*final*/ class GradlePluginDevelopmentDependenciesRule implements Plugin { + private static final String EXTENSION_NAME = "dependencies"; + + @Inject + public GradlePluginDevelopmentDependenciesRule() {} + + @Override + public void apply(Project project) { + project.getPluginManager().withPlugin("java-gradle-plugin", __ -> { + final DefaultGradlePluginDevelopmentDependencies dependencies = newDependenciesExtension(project); + + ((ExtensionAware) gradlePlugin(project)).getExtensions().add(EXTENSION_NAME, dependencies); + + // adhoc decoration of the dependencies + dependencies.forEach(dependencyBucket -> { + GroovyHelper.instance().addNewInstanceMethod(dependencies, dependencyBucket.getName(), new MethodClosure(dependencyBucket, "add")); + }); + GroovyHelper.instance().addNewInstanceMethod(dependencies, "platform", new MethodClosure(dependencies.getPlatform(), "modify")); + GroovyHelper.instance().addNewInstanceMethod(dependencies, "enforcedPlatform", new MethodClosure(dependencies.getEnforcedPlatform(), "modify")); + + // Shim missing configurations + project.afterEvaluate(new Action() { + @Override + public void execute(Project ___) { + final GradlePluginDevelopmentExtension extension = gradlePlugin(project); + 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*/ String compileOnlyApiConfigurationName(SourceSet sourceSet) { + if (sourceSet.getName().equals("main")) { + return "compileOnlyApi"; + } + return sourceSet.getName() + "CompileOnlyApi"; + } + + private /*static*/ Transformer, JavaCompile> toMajorVersion(Project project) { + return new Transformer, JavaCompile>() { + @Override + public Provider transform(JavaCompile task) { + return 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 DefaultGradlePluginDevelopmentDependencies newDependenciesExtension(Project project) { + return project.getObjects().newInstance(DefaultGradlePluginDevelopmentDependencies.class, project, new DependencyBucketFactory(project, project.provider(() -> gradlePlugin(project).getPluginSourceSet()))); + } + + /*private*/ static abstract /*final*/ class DefaultGradlePluginDevelopmentDependencies implements GradlePluginDevelopmentDependencies, Iterable { + private final Transformer localOrRemoteGradleApi; + private final Map dependencyBuckets = new LinkedHashMap<>(); + private final DependencyFactory dependencyFactory; + private final GradlePluginDevelopmentDependencyModifiers.DependencyModifier platformDependencyModifier; + private final GradlePluginDevelopmentDependencyModifiers.DependencyModifier enforcedPlatformDependencyModifier; + private final Project project; + + @Inject + public DefaultGradlePluginDevelopmentDependencies(Project project, DependencyBucketFactory dependencyBucketFactory) { + this.dependencyFactory = DependencyFactory.forProject(project); + this.localOrRemoteGradleApi = new LocalOrRemoteVersionTransformer<>(dependencyFactory::localGradleApi, dependencyFactory::gradleApi); + this.platformDependencyModifier = new PlatformDependencyModifier(project); + this.enforcedPlatformDependencyModifier = new EnforcedPlatformDependencyModifier(project); + 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")); + } + + private void add(GradlePluginDevelopmentDependencyBucket dependencyBucket) { + dependencyBuckets.put(dependencyBucket.getName(), dependencyBucket); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getApi() { + return dependencyBuckets.get("api"); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getImplementation() { + return dependencyBuckets.get("implementation"); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getCompileOnlyApi() { + return dependencyBuckets.get("compileOnlyApi"); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getCompileOnly() { + return dependencyBuckets.get("compileOnly"); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getRuntimeOnly() { + return dependencyBuckets.get("runtimeOnly"); + } + + @Override + public GradlePluginDevelopmentDependencyBucket getAnnotationProcessor() { + return dependencyBuckets.get("annotationProcessor"); + } + + @Override + public GradlePluginDevelopmentDependencyModifiers.DependencyModifier getPlatform() { + return platformDependencyModifier; + } + + @Override + public GradlePluginDevelopmentDependencyModifiers.DependencyModifier getEnforcedPlatform() { + return enforcedPlatformDependencyModifier; + } + + @Override + public Dependency gradleApi(String version) { + return localOrRemoteGradleApi.transform(version); + } + + @Override + public ProjectDependency project(String projectPath) { + return dependencyFactory.create(project.project(projectPath)); + } + + @Override + public ProjectDependency project() { + return dependencyFactory.create(project); + } + + @Override + public ExternalModuleDependency gradlePlugin(String pluginNotation) { + return dependencyFactory.gradlePlugin(pluginNotation); + } + + @Override + public Iterator iterator() { + return dependencyBuckets.values().iterator(); + } + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java index b749d9d8..ff72bc7f 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java @@ -2,22 +2,21 @@ import dev.gradleplugins.GradlePluginDevelopmentDependencyExtension; import dev.gradleplugins.internal.DependencyFactory; +import dev.gradleplugins.internal.runtime.dsl.GroovyHelper; import dev.gradleplugins.internal.util.LocalOrRemoteVersionTransformer; -import groovy.lang.Closure; +import org.codehaus.groovy.runtime.MethodClosure; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Transformer; import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; -import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.reflect.HasPublicType; import org.gradle.api.reflect.TypeOf; import javax.inject.Inject; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; /*private*/ abstract /*final*/ class ProjectDependenciesExtensionRule implements Plugin { private static final Logger LOGGER = Logging.getLogger(ProjectDependenciesExtensionRule.class); @@ -28,63 +27,21 @@ public ProjectDependenciesExtensionRule() {} @Override public void apply(Project project) { final DependencyHandler dependencies = project.getDependencies(); - dependencies.getExtensions().add("gradlePluginDevelopment", new DefaultGradlePluginDevelopmentDependencyExtension(project.getDependencies())); - try { - Method target = Class.forName("dev.gradleplugins.internal.dsl.groovy.GroovyDslRuntimeExtensions").getMethod("extendWithMethod", Object.class, String.class, Closure.class); - target.invoke(null, dependencies, "gradleApi", new GradleApiClosure(dependencies)); - target.invoke(null, dependencies, "gradleTestKit", new GradleTestKitClosure(dependencies)); - target.invoke(null, dependencies, "gradleFixtures", new GradleFixturesClosure(dependencies)); - target.invoke(null, dependencies, "gradleRunnerKit", new GradleRunnerKitClosure(dependencies)); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - LOGGER.info("Unable to extend DependencyHandler with gradleApi(String) and gradleFixtures()."); - } - } - - private static class GradleApiClosure extends Closure { - public GradleApiClosure(DependencyHandler handler) { - super(handler); - } - - public Dependency doCall(String version) { - return ((ExtensionAware) getOwner()).getExtensions().getByType(GradlePluginDevelopmentDependencyExtension.class).gradleApi(version); - } - } - - private static class GradleTestKitClosure extends Closure { - public GradleTestKitClosure(DependencyHandler handler) { - super(handler); - } + final GradlePluginDevelopmentDependencyExtension extension = dependencies.getExtensions().create("gradlePluginDevelopment", DefaultGradlePluginDevelopmentDependencyExtension.class, dependencies); - public Dependency doCall(String version) { - return ((ExtensionAware) getOwner()).getExtensions().getByType(GradlePluginDevelopmentDependencyExtension.class).gradleTestKit(version); - } + GroovyHelper.instance().addNewInstanceMethod(dependencies, "gradleApi", new MethodClosure(extension, "gradleApi")); + GroovyHelper.instance().addNewInstanceMethod(dependencies, "gradleTestKit", new MethodClosure(extension, "gradleTestKit")); + GroovyHelper.instance().addNewInstanceMethod(dependencies, "gradleFixtures", new MethodClosure(extension, "gradleFixtures")); + GroovyHelper.instance().addNewInstanceMethod(dependencies, "gradleRunnerKit", new MethodClosure(extension, "gradleRunnerKit")); + GroovyHelper.instance().addNewInstanceMethod(dependencies, "gradlePlugin", new MethodClosure(extension, "gradlePlugin")); } - private static class GradleFixturesClosure extends Closure { - public GradleFixturesClosure(DependencyHandler handler) { - super(handler); - } - - public Dependency doCall() { - return ((ExtensionAware) getOwner()).getExtensions().getByType(GradlePluginDevelopmentDependencyExtension.class).gradleFixtures(); - } - } - - private static class GradleRunnerKitClosure extends Closure { - public GradleRunnerKitClosure(DependencyHandler handler) { - super(handler); - } - - public Dependency doCall() { - return ((ExtensionAware) getOwner()).getExtensions().getByType(GradlePluginDevelopmentDependencyExtension.class).gradleRunnerKit(); - } - } - - private static final class DefaultGradlePluginDevelopmentDependencyExtension implements GradlePluginDevelopmentDependencyExtension, HasPublicType { + /*private*/ static abstract /*final*/ class DefaultGradlePluginDevelopmentDependencyExtension implements GradlePluginDevelopmentDependencyExtension, HasPublicType { private final DependencyFactory factory; private final Transformer gradleApiTransformer; private final Transformer gradleTestKitTransformer; + @Inject public DefaultGradlePluginDevelopmentDependencyExtension(DependencyHandler dependencies) { this.factory = new DependencyFactory(dependencies); this.gradleApiTransformer = new LocalOrRemoteVersionTransformer<>(factory::localGradleApi, factory::gradleApi); @@ -111,6 +68,11 @@ public Dependency gradleRunnerKit() { return factory.gradleRunnerKit(); } + @Override + public ExternalModuleDependency gradlePlugin(String pluginNotation) { + return factory.gradlePlugin(pluginNotation); + } + @Override public TypeOf getPublicType() { return TypeOf.typeOf(GradlePluginDevelopmentDependencyExtension.class); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java index 9dd020bc..524e31af 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java @@ -1,11 +1,11 @@ package dev.gradleplugins.internal.rules; import dev.gradleplugins.GradlePluginDevelopmentRepositoryExtension; -import groovy.lang.Closure; +import dev.gradleplugins.internal.runtime.dsl.GroovyHelper; +import org.codehaus.groovy.runtime.MethodClosure; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.dsl.RepositoryHandler; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.initialization.Settings; @@ -54,26 +54,12 @@ public void apply(Settings settings) { } private static void decorate(RepositoryHandler repositories) { - ((ExtensionAware) repositories).getExtensions().create("gradlePluginDevelopment", DefaultGradlePluginDevelopmentRepositoryExtension.class, repositories); - try { - Method target = Class.forName("dev.gradleplugins.internal.dsl.groovy.GroovyDslRuntimeExtensions").getMethod("extendWithMethod", Object.class, String.class, Closure.class); - target.invoke(null, repositories, "gradlePluginDevelopment", new GradlePluginDevelopmentClosure(repositories)); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - LOGGER.info("Unable to extend RepositoryHandler with gradlePluginDevelopment()."); - } - } + final GradlePluginDevelopmentRepositoryExtension extension = ((ExtensionAware) repositories).getExtensions().create("gradlePluginDevelopment", DefaultGradlePluginDevelopmentRepositoryExtension.class, repositories); - private static class GradlePluginDevelopmentClosure extends Closure { - public GradlePluginDevelopmentClosure(RepositoryHandler repositories) { - super(repositories); - } - - public MavenArtifactRepository doCall() { - return ((ExtensionAware) getOwner()).getExtensions().getByType(GradlePluginDevelopmentRepositoryExtension.class).gradlePluginDevelopment(); - } + GroovyHelper.instance().addNewInstanceMethod(repositories, "gradlePluginDevelopment", new MethodClosure(extension, "gradlePluginDevelopment")); } - /*private*/ static /*final*/ class DefaultGradlePluginDevelopmentRepositoryExtension implements GradlePluginDevelopmentRepositoryExtension, HasPublicType { + /*private*/ static abstract /*final*/ class DefaultGradlePluginDevelopmentRepositoryExtension implements GradlePluginDevelopmentRepositoryExtension, HasPublicType { private final RepositoryHandler repositories; @Inject diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/runtime/dsl/GroovyHelper.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/runtime/dsl/GroovyHelper.java new file mode 100644 index 00000000..af9e28ad --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/runtime/dsl/GroovyHelper.java @@ -0,0 +1,31 @@ +package dev.gradleplugins.internal.runtime.dsl; + +import groovy.lang.Closure; + +public abstract class GroovyHelper { + private static final Object lock = new Object(); + private static GroovyHelper INSTANCE; + + private static GroovyHelper newInstance() { + try { + return (GroovyHelper) Class.forName("dev.gradleplugins.internal.dsl.groovy.GroovyDslRuntimeExtensions").newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public static GroovyHelper instance() { + if (INSTANCE == null) { + synchronized (lock) { + if (INSTANCE == null) { + INSTANCE = newInstance(); + } + } + } + return INSTANCE; + } + + public abstract void addNewInstanceMethod(Object self, String methodName, @SuppressWarnings("rawtypes") Closure methodBody); + +// public abstract void mixin(Class type, String methodName, Closure methodBody); +} diff --git a/subprojects/gradle-plugin-development/src/main/kotlin/DependencyBucketExtensions.kt b/subprojects/gradle-plugin-development/src/main/kotlin/DependencyBucketExtensions.kt new file mode 100644 index 00000000..3f3273c0 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/kotlin/DependencyBucketExtensions.kt @@ -0,0 +1,22 @@ +import dev.gradleplugins.GradlePluginDevelopmentDependencyBucket +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ExternalModuleDependency +import org.gradle.api.file.FileCollection +import org.gradle.api.provider.Provider + +operator fun GradlePluginDevelopmentDependencyBucket.invoke(dependencyNotation: CharSequence) = this.add(dependencyNotation) + +operator fun GradlePluginDevelopmentDependencyBucket.invoke(dependencyNotation: CharSequence, configureAction: (ExternalModuleDependency) -> Unit) = this.add(dependencyNotation, configureAction) + +operator fun GradlePluginDevelopmentDependencyBucket.invoke(project: Project) = this.add(project) + +operator fun GradlePluginDevelopmentDependencyBucket.invoke(fileCollection: FileCollection) = this.add(fileCollection) + +operator fun GradlePluginDevelopmentDependencyBucket.invoke(dependency: Dependency) = this.add(dependency) + +operator fun GradlePluginDevelopmentDependencyBucket.invoke(dependency: DependencyType, configureAction: (DependencyType) -> Unit) = this.add(dependency, configureAction) + +operator fun GradlePluginDevelopmentDependencyBucket.invoke(dependencyProvider: Provider) = this.add(dependencyProvider) + +operator fun GradlePluginDevelopmentDependencyBucket.invoke(dependencyProvider: Provider, configureAction: (DependencyType) -> Unit) = this.add(dependencyProvider, configureAction) \ No newline at end of file diff --git a/subprojects/gradle-plugin-development/src/main/kotlin/DependencyModifierExtensions.kt b/subprojects/gradle-plugin-development/src/main/kotlin/DependencyModifierExtensions.kt new file mode 100644 index 00000000..2f23980b --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/kotlin/DependencyModifierExtensions.kt @@ -0,0 +1,13 @@ +import dev.gradleplugins.GradlePluginDevelopmentDependencyModifiers.DependencyModifier +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ExternalModuleDependency +import org.gradle.api.artifacts.ModuleDependency +import org.gradle.api.artifacts.ProjectDependency + +operator fun DependencyModifier.invoke(dependency: DependencyType) : ModuleDependency = this.modify(dependency) +operator fun DependencyModifier.invoke(dependencyNotation: String) : ExternalModuleDependency = this.modify(dependencyNotation) +operator fun DependencyModifier.invoke(project: Project) : ProjectDependency = this.modify(project) + +// for convenience +operator fun DependencyModifier.invoke(dependency: Dependency) : ModuleDependency = this.modify(dependency as ModuleDependency) \ No newline at end of file diff --git a/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-plugin-dependencies.properties b/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-plugin-dependencies.properties new file mode 100644 index 00000000..bd11d581 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-plugin-dependencies.properties @@ -0,0 +1 @@ +implementation-class=dev.gradleplugins.internal.rules.GradlePluginDevelopmentDependenciesRule \ No newline at end of file From 8ffbab881fea88bb19f13f5f6a2c0711d0560633 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 10 Jul 2024 12:25:41 -0400 Subject: [PATCH 2/8] Remove PublishArtifact custom implementation Signed-off-by: Daniel Lacasse --- ...dlePluginDevelopmentExtensionInternal.java | 7 +- .../internal/JarBasedPublishArtifact.java | 64 ------------------- 2 files changed, 6 insertions(+), 65 deletions(-) delete mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/JarBasedPublishArtifact.java diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradlePluginDevelopmentExtensionInternal.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradlePluginDevelopmentExtensionInternal.java index e35aebf8..ec116a8a 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradlePluginDevelopmentExtensionInternal.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradlePluginDevelopmentExtensionInternal.java @@ -4,6 +4,7 @@ import dev.gradleplugins.JavaGradlePluginDevelopmentExtension; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.artifacts.type.ArtifactTypeDefinition; import org.gradle.api.attributes.Bundling; import org.gradle.api.attributes.Category; import org.gradle.api.attributes.DocsType; @@ -92,7 +93,11 @@ public void withGroovydocJar() { } TaskProvider jar = getTasks().named(jarTaskName, Jar.class); - variant.getOutgoing().artifact(new JarBasedPublishArtifact(jar)); + variant.getOutgoing().artifact(jar, it -> { + it.setName(jar.getName()); + it.setType(ArtifactTypeDefinition.JAR_TYPE); + it.builtBy(jar); + }); AdhocComponentWithVariants component = findJavaComponent(getComponents()); if (component != null) { component.addVariantsFromConfiguration(variant, new JavaConfigurationVariantMapping("runtime", true)); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/JarBasedPublishArtifact.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/JarBasedPublishArtifact.java deleted file mode 100644 index 4c3aa903..00000000 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/JarBasedPublishArtifact.java +++ /dev/null @@ -1,64 +0,0 @@ -package dev.gradleplugins.internal; - -import org.gradle.api.Task; -import org.gradle.api.artifacts.PublishArtifact; -import org.gradle.api.artifacts.type.ArtifactTypeDefinition; -import org.gradle.api.tasks.TaskDependency; -import org.gradle.api.tasks.TaskProvider; -import org.gradle.jvm.tasks.Jar; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.Collections; -import java.util.Date; -import java.util.Set; - -public final class JarBasedPublishArtifact implements PublishArtifact { - private final TaskProvider jarTaskProvider; - - public JarBasedPublishArtifact(TaskProvider jarTaskProvider) { - this.jarTaskProvider = jarTaskProvider; - } - - @Override - public String getName() { - return jarTaskProvider.getName(); - } - - @Override - public String getExtension() { - return "jar"; - } - - @Override - public String getType() { - return ArtifactTypeDefinition.JAR_TYPE; - } - - @Nullable - @Override - public String getClassifier() { - return jarTaskProvider.get().getArchiveClassifier().getOrNull(); - } - - @Override - public File getFile() { - return jarTaskProvider.get().getArchiveFile().get().getAsFile(); - } - - @Nullable - @Override - public Date getDate() { - return null; - } - - @Override - public TaskDependency getBuildDependencies() { - return new TaskDependency() { - @Override - public Set getDependencies(@Nullable Task task) { - return Collections.singleton(jarTaskProvider.get()); - } - }; - } -} \ No newline at end of file From 3828ae8da704b018f38fe88eb7f95408b5761c31 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 10 Jul 2024 12:26:06 -0400 Subject: [PATCH 3/8] Prepare 2.0 TODOs Signed-off-by: Daniel Lacasse --- .../internal/plugins/GradlePluginDevelopmentBasePlugin.java | 2 +- .../GradlePluginDevelopmentFunctionalTestingPlugin.java | 3 ++- .../internal/plugins/GradlePluginDevelopmentPlugin.java | 3 ++- .../plugins/GradlePluginDevelopmentTestingBasePlugin.java | 3 ++- .../plugins/GradlePluginDevelopmentUnitTestingPlugin.java | 3 ++- .../internal/plugins/GroovyGradlePluginDevelopmentPlugin.java | 3 ++- .../internal/plugins/JavaGradlePluginDevelopmentPlugin.java | 3 ++- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java index e079a92e..ea97afec 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java @@ -6,7 +6,7 @@ import javax.inject.Inject; -abstract /*final*/ class GradlePluginDevelopmentBasePlugin implements Plugin { +/*private*/ abstract /*final*/ class GradlePluginDevelopmentBasePlugin implements Plugin { @Inject public GradlePluginDevelopmentBasePlugin() {} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentFunctionalTestingPlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentFunctionalTestingPlugin.java index d8c0fc55..fd842035 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentFunctionalTestingPlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentFunctionalTestingPlugin.java @@ -9,7 +9,8 @@ import static dev.gradleplugins.internal.plugins.GradlePluginDevelopmentUnitTestingPlugin.test; import static dev.gradleplugins.internal.util.GradlePluginDevelopmentUtils.gradlePlugin; -public abstract class GradlePluginDevelopmentFunctionalTestingPlugin implements Plugin { +// TODO(2.0): Make this class "private" +public abstract /*final*/ class GradlePluginDevelopmentFunctionalTestingPlugin implements Plugin { private static final String FUNCTIONAL_TEST_NAME = "functionalTest"; private static final GradlePluginDevelopmentTestSuiteRegistrationAction FUNCTIONAL_TEST_RULE = new GradlePluginDevelopmentTestSuiteRegistrationAction(FUNCTIONAL_TEST_NAME); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java index 33d2f761..6238b087 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java @@ -21,7 +21,8 @@ import static dev.gradleplugins.internal.util.GradlePluginDevelopmentUtils.sourceSets; -public abstract class GradlePluginDevelopmentPlugin implements Plugin { +// TODO(2.0): Make this class "private" +public abstract /*final*/ class GradlePluginDevelopmentPlugin implements Plugin { private static final Logger LOGGER = Logging.getLogger(GradlePluginDevelopmentPlugin.class); @Override diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentTestingBasePlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentTestingBasePlugin.java index 13cc0256..54f3ce54 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentTestingBasePlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentTestingBasePlugin.java @@ -5,7 +5,8 @@ import javax.inject.Inject; -abstract class GradlePluginDevelopmentTestingBasePlugin implements Plugin { +// TODO(2.0): Make this class "private" +abstract /*final*/ class GradlePluginDevelopmentTestingBasePlugin implements Plugin { @Inject public GradlePluginDevelopmentTestingBasePlugin() {} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentUnitTestingPlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentUnitTestingPlugin.java index cb8de856..08f5d1a7 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentUnitTestingPlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentUnitTestingPlugin.java @@ -14,7 +14,8 @@ import static dev.gradleplugins.GradlePluginDevelopmentCompatibilityExtension.compatibility; import static dev.gradleplugins.internal.util.GradlePluginDevelopmentUtils.gradlePlugin; -public abstract class GradlePluginDevelopmentUnitTestingPlugin implements Plugin { +// TODO(2.0): Make this class "private" +public abstract /*final*/ class GradlePluginDevelopmentUnitTestingPlugin implements Plugin { private static final String TEST_NAME = "test"; private static final GradlePluginDevelopmentTestSuiteRegistrationAction TEST_RULE = new GradlePluginDevelopmentTestSuiteRegistrationAction(TEST_NAME); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GroovyGradlePluginDevelopmentPlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GroovyGradlePluginDevelopmentPlugin.java index 912ed891..1d35cda7 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GroovyGradlePluginDevelopmentPlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GroovyGradlePluginDevelopmentPlugin.java @@ -35,7 +35,8 @@ import static dev.gradleplugins.internal.plugins.AbstractGradlePluginDevelopmentPlugin.registerLanguageExtension; import static dev.gradleplugins.internal.util.GradlePluginDevelopmentUtils.gradlePlugin; -public class GroovyGradlePluginDevelopmentPlugin implements Plugin { +// TODO(2.0): Make this class "private" and abstract +public /*final*/ class GroovyGradlePluginDevelopmentPlugin implements Plugin { private static final String PLUGIN_ID = "dev.gradleplugins.groovy-gradle-plugin"; @Override diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/JavaGradlePluginDevelopmentPlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/JavaGradlePluginDevelopmentPlugin.java index 020010f3..655adbed 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/JavaGradlePluginDevelopmentPlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/JavaGradlePluginDevelopmentPlugin.java @@ -22,7 +22,8 @@ import static dev.gradleplugins.internal.plugins.AbstractGradlePluginDevelopmentPlugin.*; -public class JavaGradlePluginDevelopmentPlugin implements Plugin { +// TODO(2.0): Make this class "private" and abstract +public /*final*/ class JavaGradlePluginDevelopmentPlugin implements Plugin { private static final String PLUGIN_ID = "dev.gradleplugins.java-gradle-plugin"; @Override From 2bf1828c5bba7955d5c42b3260cc1d42055405c7 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 10 Jul 2024 13:05:48 -0400 Subject: [PATCH 4/8] Fix test failures Signed-off-by: Daniel Lacasse --- ...PluginDevelopmentFunctionalTestingFunctionalTests.java | 8 ++++---- ...GradlePluginDevelopmentUnitTestingFunctionalTests.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java index 5404a5c7..8e7b9a7c 100644 --- a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java @@ -98,7 +98,7 @@ void disallowChangesToTestedSourceSetProperty() { BuildResult result = runner.withTasks("verify").buildAndFail(); assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); - assertThat(result, hasFailureCause("The value for property 'testedSourceSet' is final and cannot be changed any further.")); + assertThat(result, hasFailureCause("The value for property 'testedSourceSet' cannot be changed any further.")); } @Test @@ -137,7 +137,7 @@ void addsPluginUnderTestMetadataAsRuntimeOnlyDependency() { buildFile.append(groovyDsl( "tasks.register('verify') {", " doLast {", - " assert functionalTest.dependencies.runtimeOnly.asConfiguration.get().dependencies.any {", + " assert functionalTest.dependencies.runtimeOnly.asConfiguration.get().incoming.dependencies.any {", " it instanceof SelfResolvingDependency && it.files.singleFile.path.endsWith('/pluginUnderTestMetadataFunctionalTest')", " }", " }", @@ -180,7 +180,7 @@ void hasGradleTestKitImplementationDependencyToLocalVersion() { buildFile.append(groovyDsl( "tasks.register('verify') {", " doLast {", - " assert configurations.functionalTestImplementation.dependencies.any { it instanceof SelfResolvingDependency && it.targetComponentId?.displayName == 'Gradle TestKit' }", + " assert configurations.functionalTestImplementation.incoming.dependencies.any { it instanceof SelfResolvingDependency && it.targetComponentId?.displayName == 'Gradle TestKit' }", " }", "}" )); @@ -195,7 +195,7 @@ void hasGradleTestKitImplementationDependencyToGradleApiVersion() { "gradlePlugin.compatibility.gradleApiVersion = '5.6'", "tasks.register('verify') {", " doLast {", - " assert configurations.functionalTestImplementation.dependencies.any { 'dev.gradleplugins:gradle-test-kit:5.6' == \"${it.group}:${it.name}:${it.version}\" }", + " assert configurations.functionalTestImplementation.incoming.dependencies.any { 'dev.gradleplugins:gradle-test-kit:5.6' == \"${it.group}:${it.name}:${it.version}\" }", " }", "}" )); diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java index 96290a17..b955bb92 100644 --- a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java @@ -52,7 +52,7 @@ void canAddDependenciesBeforeCoreGradleDevelPluginApplied() { "", "tasks.register('verify') {", " doLast {", - " assert configurations.testImplementation.dependencies.any { 'org.junit.jupiter:junit-jupiter:5.8.1' == \"${it.group}:${it.name}:${it.version}\" }", + " assert configurations.testImplementation.incoming.dependencies.any { 'org.junit.jupiter:junit-jupiter:5.8.1' == \"${it.group}:${it.name}:${it.version}\" }", " }", "}" )); @@ -121,7 +121,7 @@ void disallowChangesToTestedSourceSetProperty() { BuildResult result = runner.withTasks("verify").buildAndFail(); assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); - assertThat(result, hasFailureCause("The value for property 'testedSourceSet' is final and cannot be changed any further.")); + assertThat(result, hasFailureCause("The value for property 'testedSourceSet' cannot be changed any further.")); } @Test @@ -175,7 +175,7 @@ void addsPluginUnderTestMetadataAsRuntimeOnlyDependency() { buildFile.append(groovyDsl( "tasks.register('verify') {", " doLast {", - " assert test.dependencies.runtimeOnly.asConfiguration.get().dependencies.any {", + " assert test.dependencies.runtimeOnly.asConfiguration.get().incoming.dependencies.any {", " it instanceof SelfResolvingDependency && it.files.singleFile.path.endsWith('/pluginUnderTestMetadataTest')", " }", " }", @@ -205,7 +205,7 @@ void hasGradleApiImplementationDependency() { "gradlePlugin.compatibility.minimumGradleVersion = '5.6'", "tasks.register('verify') {", " doLast {", - " assert configurations.testImplementation.dependencies.any { 'dev.gradleplugins:gradle-api:5.6' == \"${it.group}:${it.name}:${it.version}\" }", + " assert configurations.testImplementation.incoming.dependencies.any { 'dev.gradleplugins:gradle-api:5.6' == \"${it.group}:${it.name}:${it.version}\" }", " }", "}" )); From 0e39338ab89129989ebbb76eaa344b1b6cc961b6 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 10 Jul 2024 13:06:10 -0400 Subject: [PATCH 5/8] Fix decoration of DependencyHandler and RepositoryHandler Signed-off-by: Daniel Lacasse --- .../rules/ProjectDependenciesExtensionRule.java | 5 +++-- .../internal/rules/RepositoriesExtensionRules.java | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java index ff72bc7f..ca15a273 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java @@ -27,7 +27,8 @@ public ProjectDependenciesExtensionRule() {} @Override public void apply(Project project) { final DependencyHandler dependencies = project.getDependencies(); - final GradlePluginDevelopmentDependencyExtension extension = dependencies.getExtensions().create("gradlePluginDevelopment", DefaultGradlePluginDevelopmentDependencyExtension.class, dependencies); + final GradlePluginDevelopmentDependencyExtension extension = new DefaultGradlePluginDevelopmentDependencyExtension(dependencies); + dependencies.getExtensions().add("gradlePluginDevelopment", extension); GroovyHelper.instance().addNewInstanceMethod(dependencies, "gradleApi", new MethodClosure(extension, "gradleApi")); GroovyHelper.instance().addNewInstanceMethod(dependencies, "gradleTestKit", new MethodClosure(extension, "gradleTestKit")); @@ -36,7 +37,7 @@ public void apply(Project project) { GroovyHelper.instance().addNewInstanceMethod(dependencies, "gradlePlugin", new MethodClosure(extension, "gradlePlugin")); } - /*private*/ static abstract /*final*/ class DefaultGradlePluginDevelopmentDependencyExtension implements GradlePluginDevelopmentDependencyExtension, HasPublicType { + private static final class DefaultGradlePluginDevelopmentDependencyExtension implements GradlePluginDevelopmentDependencyExtension, HasPublicType { private final DependencyFactory factory; private final Transformer gradleApiTransformer; private final Transformer gradleTestKitTransformer; diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java index 524e31af..65ff4062 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java @@ -2,6 +2,9 @@ import dev.gradleplugins.GradlePluginDevelopmentRepositoryExtension; import dev.gradleplugins.internal.runtime.dsl.GroovyHelper; +import dev.gradleplugins.internal.util.ClosureWrappedConfigureAction; +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; import org.codehaus.groovy.runtime.MethodClosure; import org.gradle.api.Action; import org.gradle.api.Plugin; @@ -54,12 +57,13 @@ public void apply(Settings settings) { } private static void decorate(RepositoryHandler repositories) { - final GradlePluginDevelopmentRepositoryExtension extension = ((ExtensionAware) repositories).getExtensions().create("gradlePluginDevelopment", DefaultGradlePluginDevelopmentRepositoryExtension.class, repositories); + final GradlePluginDevelopmentRepositoryExtension extension = new DefaultGradlePluginDevelopmentRepositoryExtension(repositories); + ((ExtensionAware) repositories).getExtensions().add("gradlePluginDevelopment", extension); GroovyHelper.instance().addNewInstanceMethod(repositories, "gradlePluginDevelopment", new MethodClosure(extension, "gradlePluginDevelopment")); } - /*private*/ static abstract /*final*/ class DefaultGradlePluginDevelopmentRepositoryExtension implements GradlePluginDevelopmentRepositoryExtension, HasPublicType { + private static final class DefaultGradlePluginDevelopmentRepositoryExtension implements GradlePluginDevelopmentRepositoryExtension, HasPublicType { private final RepositoryHandler repositories; @Inject @@ -89,6 +93,10 @@ public MavenArtifactRepository gradlePluginDevelopment(Action(action)); + } + @Override public TypeOf getPublicType() { return TypeOf.typeOf(GradlePluginDevelopmentRepositoryExtension.class); From fd213dd7a107b3ae4087ff14dca22eb56695c076 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 10 Jul 2024 14:38:19 -0400 Subject: [PATCH 6/8] Fix some assertions Signed-off-by: Daniel Lacasse --- ...lePluginDevelopmentFunctionalTestingFunctionalTests.java | 6 +++--- .../GradlePluginDevelopmentUnitTestingFunctionalTests.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java index 8e7b9a7c..d9e8b361 100644 --- a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTestingFunctionalTests.java @@ -84,7 +84,7 @@ void disallowChangesToSourceSetProperty() { BuildResult result = runner.withTasks("verify").buildAndFail(); assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); - assertThat(result, hasFailureCause("The value for property 'sourceSet' is final and cannot be changed any further.")); + assertThat(result, hasFailureCause("The value for test suite 'functionalTest' property 'sourceSet' is final and cannot be changed any further.")); } @Test @@ -98,7 +98,7 @@ void disallowChangesToTestedSourceSetProperty() { BuildResult result = runner.withTasks("verify").buildAndFail(); assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); - assertThat(result, hasFailureCause("The value for property 'testedSourceSet' cannot be changed any further.")); + assertThat(result, hasFailureCause("The value for test suite 'functionalTest' property 'testedSourceSet' cannot be changed any further.")); } @Test @@ -112,7 +112,7 @@ void disallowChangesToTestingStrategiesProperty() { BuildResult result = runner.withTasks("verify").buildAndFail(); assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); - assertThat(result, hasFailureCause("The value for property 'testingStrategies' is final and cannot be changed any further.")); + assertThat(result, hasFailureCause("The value for test suite 'functionalTest' property 'testingStrategies' is final and cannot be changed any further.")); } @Test diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java index b955bb92..0005c9fd 100644 --- a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentUnitTestingFunctionalTests.java @@ -107,7 +107,7 @@ void disallowChangesToSourceSetProperty() { BuildResult result = runner.withTasks("verify").buildAndFail(); assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); - assertThat(result, hasFailureCause("The value for property 'sourceSet' is final and cannot be changed any further.")); + assertThat(result, hasFailureCause("The value for test suite 'test' property 'sourceSet' is final and cannot be changed any further.")); } @Test @@ -121,7 +121,7 @@ void disallowChangesToTestedSourceSetProperty() { BuildResult result = runner.withTasks("verify").buildAndFail(); assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); - assertThat(result, hasFailureCause("The value for property 'testedSourceSet' cannot be changed any further.")); + assertThat(result, hasFailureCause("The value for test suite 'test' property 'testedSourceSet' cannot be changed any further.")); } @Test @@ -135,7 +135,7 @@ void disallowChangesToTestingStrategiesProperty() { BuildResult result = runner.withTasks("verify").buildAndFail(); assertThat(result, hasFailureDescription("A problem occurred configuring root project 'gradle-plugin'.")); - assertThat(result, hasFailureCause("The value for property 'testingStrategies' is final and cannot be changed any further.")); + assertThat(result, hasFailureCause("The value for test suite 'test' property 'testingStrategies' is final and cannot be changed any further.")); } @Test From b296ba1c50c4a1f5638919e4296408a1eea38c0a Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 10 Jul 2024 14:38:33 -0400 Subject: [PATCH 7/8] Improve supports for minimum Gradle support and custom plugin source set Signed-off-by: Daniel Lacasse --- .../rules/GradlePluginDevelopmentDependenciesRule.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradlePluginDevelopmentDependenciesRule.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradlePluginDevelopmentDependenciesRule.java index 7b25f8c0..fffa243f 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradlePluginDevelopmentDependenciesRule.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradlePluginDevelopmentDependenciesRule.java @@ -125,13 +125,11 @@ public void execute(Project ___) { }); } - if (!extension.getPluginSourceSet().getName().equals("main")) { - apiElements.extendsFrom(api); - apiElements.extendsFrom(compileOnlyApi); + apiElements.extendsFrom(api); + apiElements.extendsFrom(compileOnlyApi); - runtimeElements.extendsFrom(project.getConfigurations().getByName(extension.getPluginSourceSet().getImplementationConfigurationName())); - runtimeElements.extendsFrom(project.getConfigurations().getByName(extension.getPluginSourceSet().getRuntimeOnlyConfigurationName())); - } + runtimeElements.extendsFrom(project.getConfigurations().getByName(extension.getPluginSourceSet().getImplementationConfigurationName())); + runtimeElements.extendsFrom(project.getConfigurations().getByName(extension.getPluginSourceSet().getRuntimeOnlyConfigurationName())); } private /*static*/ String compileOnlyApiConfigurationName(SourceSet sourceSet) { From a5fe9b65d27493aeff0e066a61fb9ec4e0858f44 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 10 Jul 2024 15:05:25 -0400 Subject: [PATCH 8/8] Remove reliance on Groovy code for Groovy DSL decoration Signed-off-by: Daniel Lacasse --- .../groovy/GroovyDslRuntimeExtensions.groovy | 21 ------------------- .../internal/runtime/dsl/GroovyHelper.java | 14 ++++++------- 2 files changed, 7 insertions(+), 28 deletions(-) delete mode 100644 subprojects/gradle-plugin-development/src/main/groovy/dev/gradleplugins/internal/dsl/groovy/GroovyDslRuntimeExtensions.groovy diff --git a/subprojects/gradle-plugin-development/src/main/groovy/dev/gradleplugins/internal/dsl/groovy/GroovyDslRuntimeExtensions.groovy b/subprojects/gradle-plugin-development/src/main/groovy/dev/gradleplugins/internal/dsl/groovy/GroovyDslRuntimeExtensions.groovy deleted file mode 100644 index cf9fde97..00000000 --- a/subprojects/gradle-plugin-development/src/main/groovy/dev/gradleplugins/internal/dsl/groovy/GroovyDslRuntimeExtensions.groovy +++ /dev/null @@ -1,21 +0,0 @@ -package dev.gradleplugins.internal.dsl.groovy - -import dev.gradleplugins.internal.runtime.dsl.GroovyHelper - -/** - * Helper class to add extension methods to Groovy DSL classes at runtime. - * The end result is comparable to the Kotlin extension methods. - */ -class GroovyDslRuntimeExtensions extends GroovyHelper { - /** - * Add an extension methods to an object. - * - * @param self the object to extend - * @param methodName the extension method name - * @param methodBody the extension method body - */ - @Override - void addNewInstanceMethod(Object self, String methodName, Closure methodBody) { - self.metaClass."${methodName}" = methodBody - } -} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/runtime/dsl/GroovyHelper.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/runtime/dsl/GroovyHelper.java index af9e28ad..cf65bc56 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/runtime/dsl/GroovyHelper.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/runtime/dsl/GroovyHelper.java @@ -1,17 +1,15 @@ package dev.gradleplugins.internal.runtime.dsl; import groovy.lang.Closure; +import groovy.lang.GroovyObject; +import org.codehaus.groovy.runtime.HandleMetaClass; -public abstract class GroovyHelper { +public final class GroovyHelper { private static final Object lock = new Object(); private static GroovyHelper INSTANCE; private static GroovyHelper newInstance() { - try { - return (GroovyHelper) Class.forName("dev.gradleplugins.internal.dsl.groovy.GroovyDslRuntimeExtensions").newInstance(); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); - } + return new GroovyHelper(); } public static GroovyHelper instance() { @@ -25,7 +23,9 @@ public static GroovyHelper instance() { return INSTANCE; } - public abstract void addNewInstanceMethod(Object self, String methodName, @SuppressWarnings("rawtypes") Closure methodBody); + public void addNewInstanceMethod(Object self, String methodName, @SuppressWarnings("rawtypes") Closure methodBody) { + new HandleMetaClass(((GroovyObject) self).getMetaClass(), self).setProperty(methodName, methodBody); + } // public abstract void mixin(Class type, String methodName, Closure methodBody); }