Provide compiler classpath as task input.

This ensures that compiler classpath is what is expected
by Kotlin Plugin and removes possibility of leaking wrong jars
from Gradle wrapper classpath.

For 'kotlin.useFallbackCompilerSearch' old behaviour is still present,
but this option should be marked as deprecated and removed in one
of the Kotlin releases.
This commit is contained in:
Yahor Berdnikau
2021-02-11 14:02:32 +01:00
parent 0eaea655d0
commit 069941cdaf
5 changed files with 89 additions and 25 deletions
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.compilerRunner.*
import org.junit.Assert
import org.junit.Test
import java.io.File
import kotlin.test.assertTrue
// todo: test client file creation/deletion
// todo: test daemon start (does not start every build)
@@ -91,4 +92,43 @@ class KotlinDaemonIT : BaseGradleIT() {
}
}
}
@Test
fun testGradleBuildClasspathShouldNotBeLeakedIntoDaemonClasspath() {
val testProject = Project("kotlinProject")
testProject.setupWorkingDir()
testProject.build("assemble") {
assertGradleClasspathNotLeaked()
}
}
@Test
fun testGradleBuildClasspathShouldNotBeLeakedIntoDaemonClasspathOnUsingCustomCompilerJar() {
val testProject = Project(
projectName = "customCompilerFile",
)
testProject.setupWorkingDir()
// copy compiler embeddable into project dir using custom name
val classpath = System.getProperty("java.class.path").split(File.pathSeparator)
val kotlinEmbeddableJar = File(classpath.find { it.contains("kotlin-compiler-embeddable") }!!)
val compilerJar = File(testProject.projectDir, "compiler.jar")
kotlinEmbeddableJar.copyTo(compilerJar)
testProject.build("build") {
assertGradleClasspathNotLeaked()
}
}
private fun CompiledProject.assertGradleClasspathNotLeaked() {
assertContains("Kotlin compiler classpath:")
val daemonClasspath = output.lineSequence().find {
it.contains("Kotlin compiler classpath:")
}!!
assertTrue("Daemon classpath contains embeddable daemon jar leaked from Gradle dist classpath: $daemonClasspath") {
!daemonClasspath.contains(".gradle/wrapper/dists")
}
}
}
@@ -21,10 +21,8 @@ import org.gradle.api.NamedDomainObjectFactory
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.internal.FeaturePreviews
import org.gradle.api.internal.file.FileResolver
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.build.event.BuildEventsListenerRegistry
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
@@ -36,10 +34,10 @@ import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.addNpmDependencyExtension
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.tasks.KOTLIN_COMPILER_EMBEDDABLE
import org.jetbrains.kotlin.gradle.tasks.KOTLIN_KLIB_COMMONIZER_EMBEDDABLE
import org.jetbrains.kotlin.gradle.tasks.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.tasks.throwGradleExceptionIfError
import org.jetbrains.kotlin.gradle.testing.internal.KotlinTestsRegistry
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
import org.jetbrains.kotlin.gradle.utils.loadPropertyFromResources
@@ -68,9 +66,8 @@ abstract class KotlinBasePluginWrapper : Plugin<Project> {
whenBuildEvaluated(project)
}
project.configurations.maybeCreate(COMPILER_CLASSPATH_CONFIGURATION_NAME).defaultDependencies {
it.add(project.dependencies.create("$KOTLIN_MODULE_GROUP:$KOTLIN_COMPILER_EMBEDDABLE:$kotlinPluginVersion"))
}
addKotlinCompilerConfiguration(project)
project.configurations.maybeCreate(PLUGIN_CLASSPATH_CONFIGURATION_NAME)
project.configurations.maybeCreate(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME).apply {
isTransitive = false
@@ -106,6 +103,25 @@ abstract class KotlinBasePluginWrapper : Plugin<Project> {
project.addNpmDependencyExtension()
}
private fun addKotlinCompilerConfiguration(project: Project) {
project
.configurations
.maybeCreate(COMPILER_CLASSPATH_CONFIGURATION_NAME)
.defaultDependencies {
it.add(
project.dependencies.create("$KOTLIN_MODULE_GROUP:$KOTLIN_COMPILER_EMBEDDABLE:$kotlinPluginVersion")
)
}
project
.tasks
.withType(AbstractKotlinCompile::class.java)
.configureEach { task ->
task.defaultCompilerClasspath.setFrom(
project.configurations.named(COMPILER_CLASSPATH_CONFIGURATION_NAME)
)
}
}
open fun whenBuildEvaluated(project: Project) {
}
@@ -64,6 +64,7 @@ class KotlinJsDcePlugin : Plugin<Project> {
val dceTask = project.registerTask<KotlinJsDce>(dceTaskName) {
it.dependsOn(kotlinTask)
it.defaultCompilerClasspath.setFrom(project.configurations.named(COMPILER_CLASSPATH_CONFIGURATION_NAME))
}
project.tasks.named("build").dependsOn(dceTask)
@@ -10,6 +10,7 @@ import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.TaskProvider
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.gradle.dsl.KotlinJsDce
import org.jetbrains.kotlin.gradle.plugin.COMPILER_CLASSPATH_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
@@ -287,6 +288,7 @@ open class KotlinBrowserJs @Inject constructor(target: KotlinJsTarget) :
it.classpath = project.configurations.getByName(compilation.runtimeDependencyConfigurationName)
it.destinationDir = it.dceOptions.outputDirectory?.let { File(it) }
?: compilation.npmProject.dir.resolve(if (dev) DCE_DEV_DIR else DCE_DIR)
it.defaultCompilerClasspath.setFrom(project.configurations.named(COMPILER_CLASSPATH_CONFIGURATION_NAME))
it.source(kotlinTask.map { it.outputFile })
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.logging.Logger
import org.gradle.api.provider.Provider
@@ -98,28 +99,32 @@ abstract class AbstractKotlinCompileTool<T : CommonToolArguments>
override val metrics: BuildMetricsReporter =
BuildMetricsReporterImpl()
/**
* By default should be set by plugin from [COMPILER_CLASSPATH_CONFIGURATION_NAME] configuration.
*
* Empty classpath will fail the build.
*/
@get:Classpath
internal val defaultCompilerClasspath: ConfigurableFileCollection =
project.objects.fileCollection()
@get:Classpath
@get:InputFiles
internal val computedCompilerClasspath: List<File> by lazy {
compilerClasspath?.takeIf { it.isNotEmpty() }
?: compilerJarFile?.let {
// a hack to remove compiler jar from the cp, will be dropped when compilerJarFile will be removed
listOf(it) + findKotlinCompilerClasspath(project).filter { !it.name.startsWith("kotlin-compiler") }
}
?: if (!useFallbackCompilerSearch) {
try {
project.configurations.getByName(COMPILER_CLASSPATH_CONFIGURATION_NAME).resolve().toList()
} catch (e: Exception) {
logger.error(
"Could not resolve compiler classpath. " +
"Check if Kotlin Gradle plugin repository is configured in $project."
)
throw e
}
} else {
findKotlinCompilerClasspath(project)
}
?: throw IllegalStateException("Could not find Kotlin Compiler classpath")
require(!defaultCompilerClasspath.isEmpty) {
"Default Kotlin compiler classpath is empty! Task: ${this::class.qualifiedName}"
}
when {
!compilerClasspath.isNullOrEmpty() -> compilerClasspath!!
compilerJarFile != null -> listOf(compilerJarFile!!) +
defaultCompilerClasspath
.filterNot {
it.nameWithoutExtension.startsWith("kotlin-compiler")
}
useFallbackCompilerSearch -> findKotlinCompilerClasspath(project)
else -> defaultCompilerClasspath.toList()
}
}