diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentExtensionFunctionalTest.groovy b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentExtensionFunctionalTest.groovy index 1e9cfbfd..0d61020b 100644 --- a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentExtensionFunctionalTest.groovy +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentExtensionFunctionalTest.groovy @@ -4,10 +4,12 @@ import dev.gradleplugins.fixtures.sample.GradlePluginElement import dev.gradleplugins.fixtures.sample.GroovyBasicGradlePlugin import dev.gradleplugins.fixtures.sample.JavaBasicGradlePlugin import org.apache.commons.lang3.JavaVersion +import org.gradle.util.GradleVersion import spock.lang.Unroll import spock.util.environment.Jvm import static org.junit.Assume.assumeFalse +import static org.junit.Assume.assumeTrue abstract class AbstractGradlePluginDevelopmentExtensionFunctionalTest extends AbstractGradlePluginDevelopmentFunctionalSpec { def "register an compatibility extension on gradlePlugin extension"() { @@ -97,6 +99,37 @@ abstract class AbstractGradlePluginDevelopmentExtensionFunctionalTest extends Ab succeeds('verify') } + def "override JVM compatibility via toolchain"() { + assumeFalse(Jvm.current.javaVersion == '12') + assumeTrue(GradleVersion.version(gradleDistributionUnderTest) >= GradleVersion.version("6.7")) + + given: + makeSingleProject() + buildFile << """ + gradlePlugin { + compatibility.minimumGradleVersion = '6.2.1' + } + + java { + toolchain { + languageVersion = JavaLanguageVersion.of(12) + } + } + + tasks.register('verify') { + doLast { + assert java.sourceCompatibility.toString() == '${JavaVersion.JAVA_12}' + assert java.targetCompatibility.toString() == '${JavaVersion.JAVA_12}' + assert tasks.compileJava.sourceCompatibility == '${JavaVersion.JAVA_12}' + assert tasks.compileJava.targetCompatibility == '${JavaVersion.JAVA_12}' + } + } + """ + + expect: + succeeds('verify') + } + def "does not change source/target compatibility if already configured when a no minimum Gradle version is configured"() { assumeFalse(Jvm.current.javaVersion == '12') diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTest.groovy b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTest.groovy index e0a89c5d..e322cc82 100644 --- a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTest.groovy +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentFunctionalTest.groovy @@ -5,6 +5,7 @@ import dev.gradleplugins.fixtures.sample.JavaBasicGradlePlugin import dev.gradleplugins.integtests.fixtures.AbstractGradleSpecification import dev.gradleplugins.test.fixtures.gradle.GradleScriptDsl import org.gradle.api.artifacts.repositories.MavenArtifactRepository +import org.gradle.util.GradleVersion import spock.lang.Unroll class GradlePluginDevelopmentFunctionalTest extends AbstractGradleSpecification { @@ -395,6 +396,7 @@ public class Bar {} private static String mainClassName(String mainClass) { return """ + import ${GradleVersion.canonicalName} if (GradleVersion.version('8.0') > GradleVersion.current()) { mainClassName = '${mainClass}' } else { diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/JvmCompatibilities.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/JvmCompatibilities.java index 8e28dce6..edc7db89 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/JvmCompatibilities.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/JvmCompatibilities.java @@ -9,11 +9,18 @@ import org.gradle.api.Transformer; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.compile.JavaCompile; import javax.inject.Inject; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Iterator; +import java.util.concurrent.Callable; import java.util.function.Consumer; import java.util.function.Supplier; @@ -23,29 +30,88 @@ public abstract class JvmCompatibilities { public static abstract class ForProjectExtension extends JvmCompatibilities {} + private static Provider javaToolchainLanguageVersion(Project project) { + final JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class); + + try { + Method JavaPluginExtension__getToolchain = java.getClass().getDeclaredMethod("getToolchain"); + Object toolchain = JavaPluginExtension__getToolchain.invoke(java); + Method JavaToolchainSpec__getLanguageVersion = toolchain.getClass().getDeclaredMethod("getLanguageVersion"); + Provider languageVersion = (Provider) JavaToolchainSpec__getLanguageVersion.invoke(toolchain); + return languageVersion.map(Object::toString).map(JavaVersion::toVersion); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + // ignore failure, most likely lower Gradle + } + + return project.provider(() -> null); + } + + private static Provider> asList(Provider provider) { + return provider.map(Collections::singletonList).orElse(Collections.emptyList()); + } + + private static Provider firstDefined(Project project, Action> action) { + @SuppressWarnings("unchecked") + ListProperty versions = (ListProperty) project.getObjects().listProperty(Object.class); + versions.finalizeValueOnRead(); + action.execute(versions); + return versions.map(it -> { + final Iterator iter = it.iterator(); + if (!iter.hasNext()) { + return null; + } else { + return iter.next(); + } + }); + } + + private static Callable disconnect(Provider provider) { + return new Callable() { + private T value; + private boolean memoized = false; + + @Override + public T call() throws Exception { + if (!memoized) { + value = provider.getOrNull(); + memoized = true; + } + return value; + } + }; + } + /*private*/ static abstract /*final*/ class ProjectJvmCompatibilitiesExtension extends ForProjectExtension { @Inject public ProjectJvmCompatibilitiesExtension(Project project) { JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class); - getTargetCompatibility().convention(project.provider(java::getTargetCompatibility).map(toFinalValue(java::getTargetCompatibility, java::setTargetCompatibility, it -> { - // Important to restore the normla behaviour - // Normally, targetCompatibility derived from sourceCompatibility when not set - final JavaVersion sourceCompatibility = java.getSourceCompatibility(); - if (!sourceCompatibility.equals(JavaVersion.VERSION_1_1)) { - java.setTargetCompatibility(sourceCompatibility); - } else { - java.setTargetCompatibility(it); - } - })).orElse(getSourceCompatibility())); + getTargetCompatibility().convention(firstDefined(project, (ListProperty versions) -> { + versions.addAll(asList(javaToolchainLanguageVersion(project))); + versions.addAll(asList(project.provider(disconnect(project.provider(java::getTargetCompatibility).map(toFinalValue(java::getTargetCompatibility, java::setTargetCompatibility, it -> { + // Important to restore the normal behaviour + // Normally, targetCompatibility derived from sourceCompatibility when not set + final JavaVersion sourceCompatibility = java.getSourceCompatibility(); + if (!sourceCompatibility.equals(JavaVersion.VERSION_1_1)) { + java.setTargetCompatibility(sourceCompatibility); + } else if (javaToolchainLanguageVersion(project).isPresent()) { + java.setTargetCompatibility(null); + } else { + java.setTargetCompatibility(it); + } + })))))); + }).orElse(getSourceCompatibility())); getTargetCompatibility().finalizeValueOnRead(); getTargetCompatibility().disallowChanges(); project.afterEvaluate(__ -> getTargetCompatibility().finalizeValue()); - getSourceCompatibility().convention(project.provider(java::getSourceCompatibility).map(toFinalValue(java::getSourceCompatibility, java::setSourceCompatibility, it -> { - // Important to restore the normal behaviour - java.setSourceCompatibility(it); - }))); + getSourceCompatibility().convention(firstDefined(project, (ListProperty versions) -> { + versions.addAll(asList(javaToolchainLanguageVersion(project))); + versions.addAll(asList(project.provider(disconnect(project.provider(java::getSourceCompatibility).map(toFinalValue(java::getSourceCompatibility, java::setSourceCompatibility, it -> { + // Important to restore the normal behaviour + java.setSourceCompatibility(it); + })))))); + })); getSourceCompatibility().finalizeValueOnRead(); getSourceCompatibility().disallowChanges(); project.afterEvaluate(__ -> getSourceCompatibility().finalizeValue()); @@ -76,8 +142,10 @@ public void apply(Project project) { final SourceSetJvmCompatibilitiesExtension extension = project.getExtensions().create("$jvmCompatibilities", SourceSetJvmCompatibilitiesExtension.class); extension.configureEach(it -> { - it.getSourceCompatibility().convention(jvmCompatibilities.getSourceCompatibility()); - it.getTargetCompatibility().convention(jvmCompatibilities.getTargetCompatibility() + it.getSourceCompatibility().convention(javaToolchainLanguageVersion(project) + .orElse(jvmCompatibilities.getSourceCompatibility())); + it.getTargetCompatibility().convention(javaToolchainLanguageVersion(project) + .orElse(jvmCompatibilities.getTargetCompatibility()) .orElse(it.getSourceCompatibility())); });