diff --git a/build.gradle.kts b/build.gradle.kts index 4e2d368..7baf9a6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -100,9 +100,11 @@ testing { val testJavaToolchain = project.findProperty("test.java-toolchain") testJavaToolchain?.also { - val metadata = project.javaToolchains.launcherFor { - languageVersion.set(JavaLanguageVersion.of(testJavaToolchain.toString())) - }.get().metadata + val launcher = + project.javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(testJavaToolchain.toString())) + } + val metadata = launcher.get().metadata systemProperty("test.java-version", metadata.languageVersion.asInt()) systemProperty("test.java-home", metadata.installationPath.asFile.canonicalPath) } @@ -165,15 +167,14 @@ publishing { spotless { kotlinGradle { - ktlint("0.49.1") + ktlint("1.3.1") } kotlin { - ktlint("0.49.1") + ktlint("1.3.1") } } -fun cmd(vararg cmdarray: String) = - Runtime.getRuntime().exec(cmdarray, null, rootDir) +fun cmd(vararg cmdarray: String) = Runtime.getRuntime().exec(cmdarray, null, rootDir) val Process.text: String get() = inputStream.bufferedReader().readText() diff --git a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/AbstractPluginIntegrationTest.kt b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/AbstractPluginIntegrationTest.kt index 610dc0e..3a29a32 100644 --- a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/AbstractPluginIntegrationTest.kt +++ b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/AbstractPluginIntegrationTest.kt @@ -6,7 +6,6 @@ import java.io.File import java.util.Properties abstract class AbstractPluginIntegrationTest { - @TempDir lateinit var testProjectDir: File lateinit var settingsFile: File @@ -21,16 +20,18 @@ abstract class AbstractPluginIntegrationTest { store(it, null) } } - settingsFile = testProjectDir.resolve("settings.gradle.kts").apply { - createNewFile() - } - buildFile = testProjectDir.resolve("build.gradle.kts").apply { - writeText( - """ - import net.ltgt.gradle.errorprone.* + settingsFile = + testProjectDir.resolve("settings.gradle.kts").apply { + createNewFile() + } + buildFile = + testProjectDir.resolve("build.gradle.kts").apply { + writeText( + """ + import net.ltgt.gradle.errorprone.* - """.trimIndent(), - ) - } + """.trimIndent(), + ) + } } } diff --git a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ErrorPronePluginIntegrationTest.kt b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ErrorPronePluginIntegrationTest.kt index a69ab1b..e9057f7 100644 --- a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ErrorPronePluginIntegrationTest.kt +++ b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ErrorPronePluginIntegrationTest.kt @@ -7,7 +7,6 @@ import org.junit.jupiter.api.Test import java.io.File class ErrorPronePluginIntegrationTest : AbstractPluginIntegrationTest() { - @BeforeEach fun setup() { buildFile.appendText( @@ -236,8 +235,8 @@ class ErrorPronePluginIntegrationTest : AbstractPluginIntegrationTest() { "com.google.errorprone.bugpatterns.BugChecker", ).writeText( """ - com.google.errorprone.sample.CPSChecker - com.google.errorprone.sample.EffectivelyFinalChecker + com.google.errorprone.sample.CPSChecker + com.google.errorprone.sample.EffectivelyFinalChecker """.trimIndent(), ) diff --git a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/Fixtures.kt b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/Fixtures.kt index 49abb97..d079286 100644 --- a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/Fixtures.kt +++ b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/Fixtures.kt @@ -22,15 +22,15 @@ fun File.writeSuccessSource() { createNewFile() writeText( """ - package test; + package test; - public class Success { - // See http://errorprone.info/bugpattern/ArrayEquals - @SuppressWarnings("ArrayEquals") - public boolean arrayEquals(int[] a, int[] b) { - return a.equals(b); - } + public class Success { + // See http://errorprone.info/bugpattern/ArrayEquals + @SuppressWarnings("ArrayEquals") + public boolean arrayEquals(int[] a, int[] b) { + return a.equals(b); } + } """.trimIndent(), ) } @@ -41,50 +41,49 @@ fun File.writeFailureSource() { createNewFile() writeText( """ - package test; + package test; - public class Failure { - // See http://errorprone.info/bugpattern/ArrayEquals - public boolean arrayEquals(int[] a, int[] b) { - return a.equals(b); - } + public class Failure { + // See http://errorprone.info/bugpattern/ArrayEquals + public boolean arrayEquals(int[] a, int[] b) { + return a.equals(b); } + } """.trimIndent(), ) } } -fun File.buildWithArgs(vararg tasks: String): BuildResult { - return prepareBuild(*tasks) +fun File.buildWithArgs(vararg tasks: String): BuildResult = + prepareBuild(*tasks) .build() -} -fun File.buildWithArgsAndFail(vararg tasks: String): BuildResult { - return prepareBuild(*tasks) +fun File.buildWithArgsAndFail(vararg tasks: String): BuildResult = + prepareBuild(*tasks) .buildAndFail() -} -fun File.prepareBuild(vararg tasks: String): GradleRunner { - return GradleRunner.create() +fun File.prepareBuild(vararg tasks: String): GradleRunner = + GradleRunner + .create() .withGradleVersion(testGradleVersion.version) .withProjectDir(this) .withPluginClasspath() .withArguments(*tasks) .forwardOutput() -} // Based on https://docs.gradle.org/current/userguide/compatibility.html#java_runtime -val COMPATIBLE_GRADLE_VERSIONS = mapOf( +val COMPATIBLE_GRADLE_VERSIONS = + mapOf( + JavaVersion.VERSION_16 to GradleVersion.version("7.0"), + JavaVersion.VERSION_17 to GradleVersion.version("7.3"), + JavaVersion.VERSION_18 to GradleVersion.version("7.5"), + JavaVersion.VERSION_19 to GradleVersion.version("7.6"), + JavaVersion.VERSION_20 to GradleVersion.version("8.3"), + JavaVersion.VERSION_21 to GradleVersion.version("8.5"), + JavaVersion.VERSION_22 to GradleVersion.version("8.8"), + JavaVersion.VERSION_23 to GradleVersion.version("8.10"), + ) - JavaVersion.VERSION_16 to GradleVersion.version("7.0"), - JavaVersion.VERSION_17 to GradleVersion.version("7.3"), - JavaVersion.VERSION_18 to GradleVersion.version("7.5"), - JavaVersion.VERSION_19 to GradleVersion.version("7.6"), - JavaVersion.VERSION_20 to GradleVersion.version("8.3"), - JavaVersion.VERSION_21 to GradleVersion.version("8.5"), - JavaVersion.VERSION_22 to GradleVersion.version("8.8"), - JavaVersion.VERSION_23 to GradleVersion.version("8.10"), -) fun assumeCompatibleGradleAndJavaVersions() { assume().that(testGradleVersion >= COMPATIBLE_GRADLE_VERSIONS[testJavaVersion] ?: GradleVersion.version("6.8")).isTrue() } diff --git a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/GroovyDslIntegrationTest.kt b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/GroovyDslIntegrationTest.kt index f1289a6..f90cad6 100644 --- a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/GroovyDslIntegrationTest.kt +++ b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/GroovyDslIntegrationTest.kt @@ -9,7 +9,6 @@ import java.io.File import java.util.Properties class GroovyDslIntegrationTest { - @TempDir lateinit var testProjectDir: File lateinit var settingsFile: File @@ -25,25 +24,27 @@ class GroovyDslIntegrationTest { store(it, null) } } - settingsFile = testProjectDir.resolve("settings.gradle").apply { - createNewFile() - } - buildFile = testProjectDir.resolve("build.gradle").apply { - appendText( - """ - plugins { - id("java-library") - id("${ErrorPronePlugin.PLUGIN_ID}") - } - repositories { - mavenCentral() - } - dependencies { - errorprone "com.google.errorprone:error_prone_core:$errorproneVersion" - } - """.trimIndent(), - ) - } + settingsFile = + testProjectDir.resolve("settings.gradle").apply { + createNewFile() + } + buildFile = + testProjectDir.resolve("build.gradle").apply { + appendText( + """ + plugins { + id("java-library") + id("${ErrorPronePlugin.PLUGIN_ID}") + } + repositories { + mavenCentral() + } + dependencies { + errorprone "com.google.errorprone:error_prone_core:$errorproneVersion" + } + """.trimIndent(), + ) + } } @Test diff --git a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/Java8IntegrationTest.kt b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/Java8IntegrationTest.kt index a12fdad..c4abd7c 100644 --- a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/Java8IntegrationTest.kt +++ b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/Java8IntegrationTest.kt @@ -10,16 +10,21 @@ import org.junit.jupiter.api.Test import java.io.File class Java8IntegrationTest : AbstractPluginIntegrationTest() { - companion object { private val FORKED = "${System.lineSeparator()}Fork: true${System.lineSeparator()}" private val NOT_FORKED = "${System.lineSeparator()}Fork: false${System.lineSeparator()}" private val JVM_ARG = "${System.lineSeparator()}JVM Arg: " private val JVM_ARG_BOOTCLASSPATH = jvmArg("-Xbootclasspath/p:") private val JVM_ARG_BOOTCLASSPATH_ERRORPRONE_JAVAC = - """\Q$JVM_ARG_BOOTCLASSPATH\E.*\Q${File.separator}com.google.errorprone${File.separator}javac${File.separator}9+181-r4173-1${File.separator}\E.*\Q${File.separator}javac-9+181-r4173-1.jar\E(?:\Q${File.pathSeparator}\E|${Regex.escape(System.lineSeparator())})""" + """\Q$JVM_ARG_BOOTCLASSPATH\E.*\Q${File.separator}com.google.errorprone${File.separator}javac${File.separator}9+181-r4173-1${File.separator}\E.*\Q${File.separator}javac-9+181-r4173-1.jar\E(?:\Q${File.pathSeparator}\E|${Regex.escape( + System.lineSeparator(), + )})""" .toPattern() - private val JVM_ARGS_STRONG_ENCAPSULATION = ErrorPronePlugin.JVM_ARGS_STRONG_ENCAPSULATION.joinToString(prefix = JVM_ARG, separator = JVM_ARG) + private val JVM_ARGS_STRONG_ENCAPSULATION = + ErrorPronePlugin.JVM_ARGS_STRONG_ENCAPSULATION.joinToString( + prefix = JVM_ARG, + separator = JVM_ARG, + ) private fun jvmArg(argPrefix: String) = "$JVM_ARG$argPrefix" @@ -225,16 +230,20 @@ class Java8IntegrationTest : AbstractPluginIntegrationTest() { // given val javaHome = System.getProperty("java.home") - val ext = when { - System.getProperty("os.name").startsWith("Windows") -> ".exe" - else -> "" - } + val ext = + when { + System.getProperty("os.name").startsWith("Windows") -> ".exe" + else -> "" + } buildFile.appendText( """ compileJava.apply { options.isFork = true - options.forkOptions.executable = ""${'"'}${javaHome.replace("\$", "\${'\$'}")}${File.separator}bin${File.separator}javac$ext${'"'}"" + options.forkOptions.executable = ""${'"'}${javaHome.replace( + "\$", + "\${'\$'}", + )}${File.separator}bin${File.separator}javac$ext${'"'}"" } """.trimIndent(), ) diff --git a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ManualConfigurationIntegrationTest.kt b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ManualConfigurationIntegrationTest.kt index 1087e40..9f790e7 100644 --- a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ManualConfigurationIntegrationTest.kt +++ b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ManualConfigurationIntegrationTest.kt @@ -6,7 +6,6 @@ import org.gradle.util.GradleVersion import org.junit.jupiter.api.Test class ManualConfigurationIntegrationTest : AbstractPluginIntegrationTest() { - @Test fun `in non-java project with applied plugin`() { // given diff --git a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ToolchainsIntegrationTest.kt b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ToolchainsIntegrationTest.kt index ac2b724..e2086c5 100644 --- a/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ToolchainsIntegrationTest.kt +++ b/src/integrationTest/kotlin/net/ltgt/gradle/errorprone/ToolchainsIntegrationTest.kt @@ -12,16 +12,21 @@ import org.junit.jupiter.api.TestInfo import java.io.File class ToolchainsIntegrationTest : AbstractPluginIntegrationTest() { - companion object { private val FORKED = "${System.lineSeparator()}Fork: true${System.lineSeparator()}" private val NOT_FORKED = "${System.lineSeparator()}Fork: false${System.lineSeparator()}" private val JVM_ARG = "${System.lineSeparator()}JVM Arg: " private val JVM_ARG_BOOTCLASSPATH = jvmArg("-Xbootclasspath/p:") private val JVM_ARG_BOOTCLASSPATH_ERRORPRONE_JAVAC = - """\Q$JVM_ARG_BOOTCLASSPATH\E.*\Q${File.separator}com.google.errorprone${File.separator}javac${File.separator}9+181-r4173-1${File.separator}\E.*\Q${File.separator}javac-9+181-r4173-1.jar\E(?:\Q${File.pathSeparator}\E|${Regex.escape(System.lineSeparator())})""" + """\Q$JVM_ARG_BOOTCLASSPATH\E.*\Q${File.separator}com.google.errorprone${File.separator}javac${File.separator}9+181-r4173-1${File.separator}\E.*\Q${File.separator}javac-9+181-r4173-1.jar\E(?:\Q${File.pathSeparator}\E|${Regex.escape( + System.lineSeparator(), + )})""" .toPattern() - private val JVM_ARGS_STRONG_ENCAPSULATION = ErrorPronePlugin.JVM_ARGS_STRONG_ENCAPSULATION.joinToString(prefix = JVM_ARG, separator = JVM_ARG) + private val JVM_ARGS_STRONG_ENCAPSULATION = + ErrorPronePlugin.JVM_ARGS_STRONG_ENCAPSULATION.joinToString( + prefix = JVM_ARG, + separator = JVM_ARG, + ) private fun jvmArg(argPrefix: String) = "$JVM_ARG$argPrefix" @@ -47,7 +52,14 @@ class ToolchainsIntegrationTest : AbstractPluginIntegrationTest() { mavenCentral() } dependencies { - errorprone("com.google.errorprone:error_prone_core:${if (testInfo.displayName.contains("JDK 8 VM")) MAX_JDK8_COMPATIBLE_ERRORPRONE_VERSION else errorproneVersion}") + errorprone("com.google.errorprone:error_prone_core:${if (testInfo.displayName.contains( + "JDK 8 VM", + ) + ) { + MAX_JDK8_COMPATIBLE_ERRORPRONE_VERSION + } else { + errorproneVersion + }}") } tasks { diff --git a/src/main/kotlin/net/ltgt/gradle/errorprone/ErrorProneOptions.kt b/src/main/kotlin/net/ltgt/gradle/errorprone/ErrorProneOptions.kt index 1cbe675..6271b79 100644 --- a/src/main/kotlin/net/ltgt/gradle/errorprone/ErrorProneOptions.kt +++ b/src/main/kotlin/net/ltgt/gradle/errorprone/ErrorProneOptions.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:no-wildcard-imports") + package net.ltgt.gradle.errorprone import org.gradle.api.Action @@ -9,7 +11,7 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Nested import org.gradle.api.tasks.Optional import org.gradle.api.tasks.compile.CompileOptions -import org.gradle.kotlin.dsl.* // ktlint-disable no-wildcard-imports +import org.gradle.kotlin.dsl.* import org.gradle.process.CommandLineArgumentProvider open class ErrorProneOptions constructor( @@ -131,7 +133,10 @@ open class ErrorProneOptions constructor( * * @see checks */ - fun check(checkName: String, severity: CheckSeverity) { + fun check( + checkName: String, + severity: CheckSeverity, + ) { validateName(checkName) checks.put(checkName, severity) } @@ -143,7 +148,10 @@ open class ErrorProneOptions constructor( * * @see checks */ - fun check(checkName: String, severity: Provider) { + fun check( + checkName: String, + severity: Provider, + ) { validateName(checkName) checks.put(checkName, severity) } @@ -190,8 +198,10 @@ open class ErrorProneOptions constructor( */ fun error(vararg checkNames: String) = set(*checkNames, atSeverity = CheckSeverity.ERROR) - private fun set(vararg checkNames: String, atSeverity: CheckSeverity) = - checkNames.forEach { check(it, atSeverity) } + private fun set( + vararg checkNames: String, + atSeverity: CheckSeverity, + ) = checkNames.forEach { check(it, atSeverity) } /** * Adds a check option with a given boolean value. @@ -202,7 +212,10 @@ open class ErrorProneOptions constructor( * * @see checkOptions */ - @JvmOverloads fun option(name: String, value: Boolean = true) = option(name, value.toString()) + @JvmOverloads fun option( + name: String, + value: Boolean = true, + ) = option(name, value.toString()) /** * Adds a check option with a given value. @@ -211,7 +224,10 @@ open class ErrorProneOptions constructor( * * @see checkOptions */ - fun option(name: String, value: String) { + fun option( + name: String, + value: String, + ) { checkOptions.put(name, value) } @@ -222,12 +238,15 @@ open class ErrorProneOptions constructor( * * @see checkOptions */ - fun option(name: String, value: Provider) { + fun option( + name: String, + value: Provider, + ) { checkOptions.put(name, value) } - override fun toString(): String { - return ( + override fun toString(): String = + ( sequenceOf( booleanOption("-XepDisableAllChecks", disableAllChecks), booleanOption("-XepDisableAllWarnings", disableAllWarnings), @@ -239,23 +258,32 @@ open class ErrorProneOptions constructor( booleanOption("-XepCompilingTestOnlyCode", isCompilingTestOnlyCode), stringOption("-XepExcludedPaths", excludedPaths), ).filterNotNull() + - checks.getOrElse(emptyMap()).asSequence().map { (name, severity) -> validateName(name); "-Xep:$name${severity.asArg}" } + + checks.getOrElse(emptyMap()).asSequence().map { (name, severity) -> + validateName(name) + "-Xep:$name${severity.asArg}" + } + checkOptions.getOrElse(emptyMap()).asSequence().map { (name, value) -> "-XepOpt:$name=$value" } + errorproneArgs.getOrElse(emptyList()) + errorproneArgumentProviders.asSequence().flatMap { it.asArguments().asSequence() } - ).onEach(::validate) + ).onEach(::validate) .joinToString(separator = " ") - } - private fun booleanOption(name: String, value: Provider): String? = - name.takeIf { value.getOrElse(false) } + private fun booleanOption( + name: String, + value: Provider, + ): String? = name.takeIf { value.getOrElse(false) } - private fun stringOption(name: String, value: Provider): String? = - value.orNull?.let { "$name:$it" } + private fun stringOption( + name: String, + value: Provider, + ): String? = value.orNull?.let { "$name:$it" } } enum class CheckSeverity { - DEFAULT, OFF, WARN, ERROR + DEFAULT, + OFF, + WARN, + ERROR, } private val CheckSeverity.asArg: String diff --git a/src/main/kotlin/net/ltgt/gradle/errorprone/ErrorPronePlugin.kt b/src/main/kotlin/net/ltgt/gradle/errorprone/ErrorPronePlugin.kt index 4ec712c..3d3bc23 100644 --- a/src/main/kotlin/net/ltgt/gradle/errorprone/ErrorPronePlugin.kt +++ b/src/main/kotlin/net/ltgt/gradle/errorprone/ErrorPronePlugin.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:no-wildcard-imports") + package net.ltgt.gradle.errorprone import org.gradle.api.JavaVersion @@ -17,7 +19,7 @@ import org.gradle.api.tasks.Optional import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.compile.CompileOptions import org.gradle.api.tasks.compile.JavaCompile -import org.gradle.kotlin.dsl.* // ktlint-disable no-wildcard-imports +import org.gradle.kotlin.dsl.* import org.gradle.process.CommandLineArgumentProvider import org.gradle.util.GradleVersion import javax.inject.Inject @@ -25,117 +27,128 @@ import javax.inject.Inject /** * A [Plugin] that configures [JavaCompile] tasks to use the [Error Prone compiler](https://errorprone.info/). */ -class ErrorPronePlugin @Inject constructor( - private val providers: ProviderFactory, -) : Plugin { - - companion object { - const val PLUGIN_ID = "net.ltgt.errorprone" +class ErrorPronePlugin + @Inject + constructor( + private val providers: ProviderFactory, + ) : Plugin { + companion object { + const val PLUGIN_ID = "net.ltgt.errorprone" - const val CONFIGURATION_NAME = "errorprone" + const val CONFIGURATION_NAME = "errorprone" - const val JAVAC_CONFIGURATION_NAME = "errorproneJavac" + const val JAVAC_CONFIGURATION_NAME = "errorproneJavac" - internal const val TOO_OLD_TOOLCHAIN_ERROR_MESSAGE = "Must not enable ErrorProne when compiling with JDK < 8" + internal const val TOO_OLD_TOOLCHAIN_ERROR_MESSAGE = "Must not enable ErrorProne when compiling with JDK < 8" - private val HAS_JVM_ARGUMENT_PROVIDERS = GradleVersion.current() >= GradleVersion.version("7.1") + private val HAS_JVM_ARGUMENT_PROVIDERS = GradleVersion.current() >= GradleVersion.version("7.1") - internal val JVM_ARGS_STRONG_ENCAPSULATION = listOf( - "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", - ) - - private val CURRENT_JVM_NEEDS_FORKING by lazy { - // Needs bootclasspath - JavaVersion.current() == JavaVersion.VERSION_1_8 || ( - // Needs --add-exports and --add-opens - JavaVersion.current() >= JavaVersion.VERSION_16 && - StrongEncapsulationHelper().needsForking() + internal val JVM_ARGS_STRONG_ENCAPSULATION = + listOf( + "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", ) - } - } - override fun apply(project: Project) { - if (GradleVersion.current() < GradleVersion.version("6.8")) { - throw UnsupportedOperationException("$PLUGIN_ID requires at least Gradle 6.8") + private val CURRENT_JVM_NEEDS_FORKING by lazy { + // Needs bootclasspath + JavaVersion.current() == JavaVersion.VERSION_1_8 || + ( + // Needs --add-exports and --add-opens + JavaVersion.current() >= JavaVersion.VERSION_16 && + StrongEncapsulationHelper().needsForking() + ) + } } - val errorproneConfiguration = project.configurations.create(CONFIGURATION_NAME) { - description = "Error Prone dependencies, will be extended by all source sets' annotationProcessor configurations" - isVisible = false - isCanBeConsumed = false - isCanBeResolved = false - - exclude(group = "com.google.errorprone", module = "javac") - } - val javacConfiguration: FileCollection = project.configurations.create(JAVAC_CONFIGURATION_NAME) { - description = "Error Prone Javac dependencies, will only be used when using JDK 8 (i.e. not JDK 9 or superior)" - isVisible = false - isCanBeConsumed = false - isCanBeResolved = true - defaultDependencies { - add(project.dependencies.create("com.google.errorprone:javac:9+181-r4173-1")) + override fun apply(project: Project) { + if (GradleVersion.current() < GradleVersion.version("6.8")) { + throw UnsupportedOperationException("$PLUGIN_ID requires at least Gradle 6.8") } - } - project.tasks.withType().configureEach { - val errorproneOptions = - (options as ExtensionAware).extensions.create(ErrorProneOptions.NAME, ErrorProneOptions::class.java) - options - .compilerArgumentProviders - .add(ErrorProneCompilerArgumentProvider(errorproneOptions)) - - val jvmArgumentProvider = ErrorProneJvmArgumentProvider(this, errorproneOptions, javacConfiguration) - if (HAS_JVM_ARGUMENT_PROVIDERS) { - options.forkOptions.jvmArgumentProviders.add(jvmArgumentProvider) - } else { - inputs.property("errorprone.compilerVersion", providers.provider { jvmArgumentProvider.compilerVersion }) - .optional(true) - inputs.files(providers.provider { jvmArgumentProvider.bootstrapClasspath }) - .withPropertyName("errorprone.bootstrapClasspath") - .withNormalizer(ClasspathNormalizer::class) - .optional() - doFirst("Configure JVM arguments for errorprone") { - options.forkOptions.jvmArgs!!.addAll(jvmArgumentProvider.asArguments()) + val errorproneConfiguration = + project.configurations.create(CONFIGURATION_NAME) { + description = "Error Prone dependencies, will be extended by all source sets' annotationProcessor configurations" + isVisible = false + isCanBeConsumed = false + isCanBeResolved = false + + exclude(group = "com.google.errorprone", module = "javac") } - } - doFirst("Configure forking for errorprone") { - if (!errorproneOptions.isEnabled.getOrElse(false)) return@doFirst - jvmArgumentProvider.compilerVersion?.let { - if (it < JavaVersion.VERSION_1_8) throw UnsupportedOperationException(TOO_OLD_TOOLCHAIN_ERROR_MESSAGE) - if ((it == JavaVersion.VERSION_1_8 || (it == JavaVersion.current() && CURRENT_JVM_NEEDS_FORKING)) && !options.isFork) options.isFork = true + val javacConfiguration: FileCollection = + project.configurations.create(JAVAC_CONFIGURATION_NAME) { + description = "Error Prone Javac dependencies, will only be used when using JDK 8 (i.e. not JDK 9 or superior)" + isVisible = false + isCanBeConsumed = false + isCanBeResolved = true + defaultDependencies { + add(project.dependencies.create("com.google.errorprone:javac:9+181-r4173-1")) + } + } + + project.tasks.withType().configureEach { + val errorproneOptions = + (options as ExtensionAware).extensions.create(ErrorProneOptions.NAME, ErrorProneOptions::class.java) + options + .compilerArgumentProviders + .add(ErrorProneCompilerArgumentProvider(errorproneOptions)) + + val jvmArgumentProvider = ErrorProneJvmArgumentProvider(this, errorproneOptions, javacConfiguration) + if (HAS_JVM_ARGUMENT_PROVIDERS) { + options.forkOptions.jvmArgumentProviders.add(jvmArgumentProvider) + } else { + inputs + .property("errorprone.compilerVersion", providers.provider { jvmArgumentProvider.compilerVersion }) + .optional(true) + inputs + .files(providers.provider { jvmArgumentProvider.bootstrapClasspath }) + .withPropertyName("errorprone.bootstrapClasspath") + .withNormalizer(ClasspathNormalizer::class) + .optional() + doFirst("Configure JVM arguments for errorprone") { + options.forkOptions.jvmArgs!!.addAll(jvmArgumentProvider.asArguments()) + } + } + doFirst("Configure forking for errorprone") { + if (!errorproneOptions.isEnabled.getOrElse(false)) return@doFirst + jvmArgumentProvider.compilerVersion?.let { + if (it < JavaVersion.VERSION_1_8) throw UnsupportedOperationException(TOO_OLD_TOOLCHAIN_ERROR_MESSAGE) + if ((it == JavaVersion.VERSION_1_8 || (it == JavaVersion.current() && CURRENT_JVM_NEEDS_FORKING)) && + !options.isFork + ) { + options.isFork = true + } + } } } - } - project.plugins.withType { - project.extensions.getByName("sourceSets").configureEach { - project.configurations[annotationProcessorConfigurationName].extendsFrom(errorproneConfiguration) - project.tasks.named(compileJavaTaskName) { - options.errorprone { - isEnabled.convention(javaCompiler.map { it.metadata.languageVersion.asInt() >= 8 }.orElse(true)) - isCompilingTestOnlyCode.convention(this@configureEach.name.matches(TEST_SOURCE_SET_NAME_REGEX)) + project.plugins.withType { + project.extensions.getByName("sourceSets").configureEach { + project.configurations[annotationProcessorConfigurationName].extendsFrom(errorproneConfiguration) + project.tasks.named(compileJavaTaskName) { + options.errorprone { + isEnabled.convention(javaCompiler.map { it.metadata.languageVersion.asInt() >= 8 }.orElse(true)) + isCompilingTestOnlyCode.convention(this@configureEach.name.matches(TEST_SOURCE_SET_NAME_REGEX)) + } } } } } } -} internal class ErrorProneJvmArgumentProvider( private val task: JavaCompile, private val errorproneOptions: ErrorProneOptions, private val javacConfiguration: FileCollection, -) : CommandLineArgumentProvider, Named { - +) : CommandLineArgumentProvider, + Named { @Internal override fun getName(): String = "errorprone" @get:Input @@ -148,40 +161,47 @@ internal class ErrorProneJvmArgumentProvider( @get:Classpath @get:Optional - val bootstrapClasspath get() = javacConfiguration.takeIf { - errorproneOptions.isEnabled.getOrElse(false) && - compilerVersion == JavaVersion.VERSION_1_8 - } + val bootstrapClasspath get() = + javacConfiguration.takeIf { + errorproneOptions.isEnabled.getOrElse(false) && + compilerVersion == JavaVersion.VERSION_1_8 + } - override fun asArguments(): Iterable = when { - !errorproneOptions.isEnabled.getOrElse(false) -> emptyList() - compilerVersion == null -> emptyList() - compilerVersion == JavaVersion.VERSION_1_8 -> listOf("-Xbootclasspath/p:${javacConfiguration.asPath}") - compilerVersion!! > JavaVersion.VERSION_1_8 -> ErrorPronePlugin.JVM_ARGS_STRONG_ENCAPSULATION - else -> emptyList() - } + override fun asArguments(): Iterable = + when { + !errorproneOptions.isEnabled.getOrElse(false) -> emptyList() + compilerVersion == null -> emptyList() + compilerVersion == JavaVersion.VERSION_1_8 -> listOf("-Xbootclasspath/p:${javacConfiguration.asPath}") + compilerVersion!! > JavaVersion.VERSION_1_8 -> ErrorPronePlugin.JVM_ARGS_STRONG_ENCAPSULATION + else -> emptyList() + } } internal class ErrorProneCompilerArgumentProvider( private val errorproneOptions: ErrorProneOptions, -) : CommandLineArgumentProvider, Named { - +) : CommandLineArgumentProvider, + Named { @Internal override fun getName(): String = "errorprone" @Suppress("unused") @Nested @Optional - fun getErrorproneOptions(): ErrorProneOptions? { - return errorproneOptions.takeIf { it.isEnabled.getOrElse(false) } - } + fun getErrorproneOptions(): ErrorProneOptions? = errorproneOptions.takeIf { it.isEnabled.getOrElse(false) } - override fun asArguments(): Iterable { - return when { + override fun asArguments(): Iterable = + when { // should-stop.ifError is for JDK 9+, shouldStopPolicyIfError for JDK 8; it's safe to indiscriminately pass both - errorproneOptions.isEnabled.getOrElse(false) -> listOf("-Xplugin:ErrorProne $errorproneOptions", "-XDcompilePolicy=simple", "-XDshould-stop.ifError=FLOW", "-XDshouldStopPolicyIfError=FLOW") + errorproneOptions.isEnabled.getOrElse( + false, + ) -> + listOf( + "-Xplugin:ErrorProne $errorproneOptions", + "-XDcompilePolicy=simple", + "-XDshould-stop.ifError=FLOW", + "-XDshouldStopPolicyIfError=FLOW", + ) else -> emptyList() } - } } internal val TEST_SOURCE_SET_NAME_REGEX = @@ -191,45 +211,60 @@ private val CompileOptions.isCommandLine get() = isFork && (forkOptions.javaHome != null || forkOptions.executable != null) private class StrongEncapsulationHelper { - fun needsForking() = try { - val unnamedModule: Any = this::class.java.classLoader.unnamedModule - sequenceOf( - "com.sun.tools.javac.api.BasicJavacTask", - "com.sun.tools.javac.api.JavacTrees", - "com.sun.tools.javac.file.JavacFileManager", - "com.sun.tools.javac.main.JavaCompiler", - "com.sun.tools.javac.model.JavacElements", - "com.sun.tools.javac.parser.JavacParser", - "com.sun.tools.javac.processing.JavacProcessingEnvironment", - "com.sun.tools.javac.tree.JCTree", - "com.sun.tools.javac.util.JCDiagnostic", - ).any { - val klass = Class.forName(it) - return@any !klass.module.isExported(klass.packageName, unnamedModule) - } && + fun needsForking() = + try { + val unnamedModule: Any = this::class.java.classLoader.unnamedModule sequenceOf( - "com.sun.tools.javac.code.Symbol", - "com.sun.tools.javac.comp.Enter", + "com.sun.tools.javac.api.BasicJavacTask", + "com.sun.tools.javac.api.JavacTrees", + "com.sun.tools.javac.file.JavacFileManager", + "com.sun.tools.javac.main.JavaCompiler", + "com.sun.tools.javac.model.JavacElements", + "com.sun.tools.javac.parser.JavacParser", + "com.sun.tools.javac.processing.JavacProcessingEnvironment", + "com.sun.tools.javac.tree.JCTree", + "com.sun.tools.javac.util.JCDiagnostic", ).any { val klass = Class.forName(it) - return@any !klass.module.isOpen(klass.packageName, unnamedModule) - } - } catch (e: ClassNotFoundException) { - true - } + return@any !klass.module.isExported(klass.packageName, unnamedModule) + } && + sequenceOf( + "com.sun.tools.javac.code.Symbol", + "com.sun.tools.javac.comp.Enter", + ).any { + val klass = Class.forName(it) + return@any !klass.module.isOpen(klass.packageName, unnamedModule) + } + } catch (e: ClassNotFoundException) { + true + } // Defined for backward/forward compatibility: // - compiles with jdk-release=8 just fine // - just remove those when upgrading to a newer minimum JDK without having to touch the code above private val getPackageName: java.lang.reflect.Method = Class::class.java.getMethod("getPackageName") private val getModule: java.lang.reflect.Method = Class::class.java.getMethod("getModule") - private val isExported: java.lang.reflect.Method = getModule.returnType.getMethod("isExported", String::class.java, getModule.returnType) + private val isExported: java.lang.reflect.Method = + getModule.returnType.getMethod( + "isExported", + String::class.java, + getModule.returnType, + ) private val isOpen: java.lang.reflect.Method = getModule.returnType.getMethod("isOpen", String::class.java, getModule.returnType) private val getUnnamedModule: java.lang.reflect.Method = ClassLoader::class.java.getMethod("getUnnamedModule") private val Class<*>.packageName get(): String = getPackageName(this) as String private val Class<*>.module get(): Any = getModule(this) - private fun Any.isExported(pn: String, other: Any): Boolean = isExported(this, pn, other) as Boolean - private fun Any.isOpen(pn: String, other: Any): Boolean = isOpen(this, pn, other) as Boolean + + private fun Any.isExported( + pn: String, + other: Any, + ): Boolean = isExported(this, pn, other) as Boolean + + private fun Any.isOpen( + pn: String, + other: Any, + ): Boolean = isOpen(this, pn, other) as Boolean + private val ClassLoader.unnamedModule: Any get() = getUnnamedModule(this) } diff --git a/src/test/kotlin/net/ltgt/gradle/errorprone/ErrorProneOptionsTest.kt b/src/test/kotlin/net/ltgt/gradle/errorprone/ErrorProneOptionsTest.kt index e964791..b8bc3d3 100644 --- a/src/test/kotlin/net/ltgt/gradle/errorprone/ErrorProneOptionsTest.kt +++ b/src/test/kotlin/net/ltgt/gradle/errorprone/ErrorProneOptionsTest.kt @@ -21,12 +21,13 @@ import java.io.File @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ErrorProneOptionsTest { - lateinit var objects: ObjectFactory lateinit var providers: ProviderFactory @BeforeAll - fun setup(@TempDir projectDir: File) { + fun setup( + @TempDir projectDir: File, + ) { ProjectBuilder.builder().withProjectDir(projectDir).build().let { project -> objects = project.objects providers = project.providers @@ -47,6 +48,7 @@ class ErrorProneOptionsTest { } private fun StringSubject.isTestSourceSetName() = this.matches(TEST_SOURCE_SET_NAME_REGEX.toPattern()) + private fun StringSubject.isNotTestSourceSetName() = this.doesNotMatch(TEST_SOURCE_SET_NAME_REGEX.toPattern()) @Test @@ -105,7 +107,10 @@ class ErrorProneOptionsTest { doTestOptions( { errorproneArgs.set(mutableListOf("-XepDisableAllChecks", "-Xep:BetaApi")) }, - { disableAllChecks.set(true); enable("BetaApi") }, + { + disableAllChecks.set(true) + enable("BetaApi") + }, ) doTestOptions( @@ -160,7 +165,10 @@ class ErrorProneOptionsTest { ) } - private fun doTestOptions(configure: ErrorProneOptions.() -> Unit, reference: ErrorProneOptions.() -> Unit) { + private fun doTestOptions( + configure: ErrorProneOptions.() -> Unit, + reference: ErrorProneOptions.() -> Unit, + ) { val options = ErrorProneOptions(objects).apply(reference) val parsedOptions = parseOptions(ErrorProneOptions(objects).apply(configure)) assertOptionsEqual(options, parsedOptions) @@ -180,7 +188,10 @@ class ErrorProneOptionsTest { } } - private fun doTestSpaces(argPrefix: String, configure: ErrorProneOptions.() -> Unit) { + private fun doTestSpaces( + argPrefix: String, + configure: ErrorProneOptions.() -> Unit, + ) { try { ErrorProneOptions(objects).apply(configure).toString() fail("Should have thrown") @@ -195,7 +206,8 @@ class ErrorProneOptionsTest { ErrorProneOptions(objects).apply({ enable("ArrayEquals:OFF") }).toString() fail("Should have thrown") } catch (e: InvalidUserDataException) { - assertThat(e).hasMessageThat() + assertThat(e) + .hasMessageThat() .isEqualTo("""Error Prone check name cannot contain a colon (":"): "ArrayEquals:OFF".""") } @@ -209,11 +221,13 @@ class ErrorProneOptionsTest { }, ) fail("Should have thrown") - } catch (ignore: InvalidCommandLineOptionException) {} + } catch (ignore: InvalidCommandLineOptionException) { + } } private fun parseOptions(options: ErrorProneOptions) = - com.google.errorprone.ErrorProneOptions.processArgs(splitArgs(options.toString())) + com.google.errorprone.ErrorProneOptions + .processArgs(splitArgs(options.toString())) // This is how JavaC "parses" the -Xplugin: values: https://git.io/vx8yI @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")