Implement Gradle JS IC support

#KT-19956 fixed

To try the experimental JS IC add
'kotlin.incremental.js = true' to a 'local.properties'
or 'gradle.properties' file in Gradle project's root.
This commit is contained in:
Alexey Tsvetkov
2017-06-13 22:30:42 +03:00
parent eeb90656dc
commit b54414d628
13 changed files with 280 additions and 95 deletions
@@ -24,10 +24,7 @@ import org.jetbrains.kotlin.build.JvmSourceRoot
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.cli.common.arguments.validateArguments
import org.jetbrains.kotlin.cli.common.arguments.*
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
@@ -390,26 +387,73 @@ class CompileServiceImpl(
}
}
CompilerMode.INCREMENTAL_COMPILER -> {
if (targetPlatform != CompileService.TargetPlatform.JVM) {
throw IllegalStateException("Incremental compilation is not supported for target platform: $targetPlatform")
}
val k2jvmArgs = k2PlatformArgs as K2JVMCompilerArguments
val gradleIncrementalArgs = compilationOptions as IncrementalCompilationOptions
val gradleIncrementalServicesFacade = servicesFacade as IncrementalCompilerServicesFacade
withIC {
doCompile(sessionId, daemonReporter, tracer = null) { _, _ ->
execIncrementalCompiler(k2jvmArgs, gradleIncrementalArgs, gradleIncrementalServicesFacade, compilationResults!!,
messageCollector, daemonReporter)
}
}
when (targetPlatform) {
CompileService.TargetPlatform.JVM -> {
val k2jvmArgs = k2PlatformArgs as K2JVMCompilerArguments
withIC {
doCompile(sessionId, daemonReporter, tracer = null) { _, _ ->
execIncrementalCompiler(k2jvmArgs, gradleIncrementalArgs, gradleIncrementalServicesFacade, compilationResults!!,
messageCollector, daemonReporter)
}
}
}
CompileService.TargetPlatform.JS -> {
val k2jsArgs = k2PlatformArgs as K2JSCompilerArguments
withJsIC {
doCompile(sessionId, daemonReporter, tracer = null) { _, _ ->
execJsIncrementalCompiler(k2jsArgs, gradleIncrementalArgs, gradleIncrementalServicesFacade, compilationResults!!, messageCollector)
}
}
}
else -> throw IllegalStateException("Incremental compilation is not supported for target platform: $targetPlatform")
}
}
else -> throw IllegalStateException("Unknown compilation mode ${compilationOptions.compilerMode}")
}
}
private fun execJsIncrementalCompiler(
args: K2JSCompilerArguments,
incrementalCompilationOptions: IncrementalCompilationOptions,
servicesFacade: IncrementalCompilerServicesFacade,
compilationResults: CompilationResults,
compilerMessageCollector: MessageCollector
): ExitCode {
val allKotlinFiles = arrayListOf<File>()
val freeArgsWithoutKotlinFiles = arrayListOf<String>()
args.freeArgs.forEach {
if (it.endsWith(".kt") && File(it).exists()) {
allKotlinFiles.add(File(it))
}
else {
freeArgsWithoutKotlinFiles.add(it)
}
}
args.freeArgs = freeArgsWithoutKotlinFiles
val reporter = RemoteICReporter(servicesFacade, compilationResults, incrementalCompilationOptions)
val changedFiles = if (incrementalCompilationOptions.areFileChangesKnown) {
ChangedFiles.Known(incrementalCompilationOptions.modifiedFiles!!, incrementalCompilationOptions.deletedFiles!!)
}
else {
ChangedFiles.Unknown()
}
val workingDir = incrementalCompilationOptions.workingDir
val versions = commonCacheVersions(workingDir) +
customCacheVersion(incrementalCompilationOptions.customCacheVersion, incrementalCompilationOptions.customCacheVersionFileName, workingDir, forceEnable = true)
return IncrementalJsCompilerRunner(workingDir, versions, reporter)
.compile(allKotlinFiles, args, compilerMessageCollector, { changedFiles })
}
private fun execIncrementalCompiler(
k2jvmArgs: K2JVMCompilerArguments,
incrementalCompilationOptions: IncrementalCompilationOptions,
@@ -1,6 +1,8 @@
package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.jetbrains.kotlin.gradle.tasks.USING_EXPERIMENTAL_JS_INCREMENTAL_COMPILATION_MESSAGE
import org.jetbrains.kotlin.gradle.util.allKotlinFiles
import org.jetbrains.kotlin.gradle.util.getFileByName
import org.jetbrains.kotlin.gradle.util.modify
import org.junit.Test
@@ -235,4 +237,24 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() {
assertNotContains("this build assumes a single directory for all classes from a source set")
}
}
@Test
fun testIncrementalCompilation() {
val project = Project("kotlin2JsICProject", "4.0")
project.build("build") {
assertSuccessful()
assertContains(USING_EXPERIMENTAL_JS_INCREMENTAL_COMPILATION_MESSAGE)
assertCompiledKotlinSources(project.relativize(project.projectDir.allKotlinFiles()))
}
val aKt = project.projectDir.getFileByName("A.kt").apply {
modify { it.replace("val x: String", "val x: Int") }
}
val useAKt = project.projectDir.getFileByName("useA.kt")
project.build("build") {
assertSuccessful()
assertContains(USING_EXPERIMENTAL_JS_INCREMENTAL_COMPILATION_MESSAGE)
assertCompiledKotlinSources(project.relativize(aKt, useAKt))
}
}
}
@@ -0,0 +1,47 @@
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin2js'
repositories {
mavenLocal()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}
task jarSources(type: Jar) {
from sourceSets.main.allSource
classifier = 'source'
}
artifacts {
compile jarSources
}
def outDir = "${buildDir}/kotlin2js/main/"
compileKotlin2Js.kotlinOptions.outputFile = outDir + "test-library.js"
jar {
from sourceSets.main.allSource
include "**/*.kt"
from outDir
include "**/*.js"
manifest {
attributes(
"Specification-Title": "Kotlin JavaScript Lib",
"Kotlin-JS-Module-Name": "test-library"
)
}
}
jar.dependsOn(compileKotlin2Js)
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package foo
class A(val x: String)
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package foo
fun dummy() {}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package foo
fun useA(a: A) = a.x
@@ -34,8 +34,8 @@ internal class GradleIncrementalCompilerEnvironment(
val workingDir: File,
messageCollector: GradleMessageCollector,
outputItemsCollector: OutputItemsCollector,
val kaptAnnotationsFileUpdater: AnnotationFileUpdater?,
val artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider?,
val artifactFile: File?,
compilerArgs: CommonCompilerArguments
compilerArgs: CommonCompilerArguments,
val kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null,
val artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider? = null,
val artifactFile: File? = null
) : GradleCompilerEnvironment(compilerJar, messageCollector, outputItemsCollector, compilerArgs)
@@ -193,11 +193,17 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
}
val (daemon, sessionId) = connection
val targetPlatform = when (compilerClassName) {
K2JVM_COMPILER -> CompileService.TargetPlatform.JVM
K2JS_COMPILER -> CompileService.TargetPlatform.JS
K2METADATA_COMPILER -> CompileService.TargetPlatform.METADATA
else -> throw IllegalArgumentException("Unknown compiler type $compilerClassName")
}
val exitCode = try {
val res = if (environment is GradleIncrementalCompilerEnvironment) {
incrementalCompilationWithDaemon(daemon, sessionId, environment)
incrementalCompilationWithDaemon(daemon, sessionId, targetPlatform, environment)
} else {
nonIncrementalCompilationWithDaemon(daemon, sessionId, compilerClassName, environment)
nonIncrementalCompilationWithDaemon(daemon, sessionId, targetPlatform, environment)
}
exitCodeFromProcessExitCode(res.get())
}
@@ -215,16 +221,9 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
private fun nonIncrementalCompilationWithDaemon(
daemon: CompileService,
sessionId: Int,
compilerClassName: String,
targetPlatform: CompileService.TargetPlatform,
environment: GradleCompilerEnvironment
): CompileService.CallResult<Int> {
val targetPlatform = when (compilerClassName) {
K2JVM_COMPILER -> CompileService.TargetPlatform.JVM
K2JS_COMPILER -> CompileService.TargetPlatform.JS
K2METADATA_COMPILER -> CompileService.TargetPlatform.METADATA
else -> throw IllegalArgumentException("Unknown compiler type $compilerClassName")
}
val verbose = environment.compilerArgs.verbose
val compilationOptions = CompilationOptions(
compilerMode = CompilerMode.NON_INCREMENTAL_COMPILER,
@@ -240,6 +239,7 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
private fun incrementalCompilationWithDaemon(
daemon: CompileService,
sessionId: Int,
targetPlatform: CompileService.TargetPlatform,
environment: GradleIncrementalCompilerEnvironment
): CompileService.CallResult<Int> {
val knownChangedFiles = environment.changedFiles as? ChangedFiles.Known
@@ -256,7 +256,7 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
reportSeverity = reportSeverity(verbose),
requestedCompilationResults = arrayOf(CompilationResultCategory.IC_COMPILE_ITERATION.code),
compilerMode = CompilerMode.INCREMENTAL_COMPILER,
targetPlatform = CompileService.TargetPlatform.JVM
targetPlatform = targetPlatform
)
val servicesFacade = GradleIncrementalCompilerServicesFacadeImpl(project, environment)
val argsArray = ArgumentUtils.convertArgumentsToStringList(environment.compilerArgs).toTypedArray()
@@ -19,47 +19,48 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.Coroutines
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.util.*
import kotlin.reflect.KMutableProperty1
fun mapKotlinTaskProperties(project: Project, task: AbstractKotlinCompile<*>) {
propertyMappings.forEach { it.apply(project, task) }
val properties = PropertiesProvider(project)
val localPropertiesFile = project.rootProject.file("local.properties")
if (localPropertiesFile.isFile) {
val properties = Properties()
localPropertiesFile.inputStream().use {
properties.load(it)
properties["kotlin.coroutines"]?.let {
task.coroutinesFromGradleProperties = Coroutines.byCompilerArgument(it)
}
if (task is KotlinCompile) {
properties["kotlin.incremental"]?.let {
task.incremental = it.toBoolean()
}
}
if (task is Kotlin2JsCompile) {
properties["kotlin.incremental.js"]?.let {
task.incremental = it.toBoolean()
}
propertyMappings.forEach { it.apply(properties, task) }
}
}
private val propertyMappings = listOf(
KotlinPropertyMapping("kotlin.incremental", AbstractKotlinCompile<*>::incremental, String::toBoolean),
KotlinPropertyMapping("kotlin.coroutines", AbstractKotlinCompile<*>::coroutinesFromGradleProperties) { Coroutines.byCompilerArgument(it) }
)
private class PropertiesProvider(private val project: Project) {
val localProperties = Properties()
private class KotlinPropertyMapping<T>(
private val projectPropName: String,
private val taskProperty: KMutableProperty1<AbstractKotlinCompile<*>, T>,
private val transform: (String) -> T
) {
fun apply(project: Project, task: AbstractKotlinCompile<*>) {
if (!project.hasProperty(projectPropName)) return
setPropertyValue(task, project.property(projectPropName))
init {
val localPropertiesFile = project.rootProject.file("local.properties")
if (localPropertiesFile.isFile) {
localPropertiesFile.inputStream().use {
localProperties.load(it)
}
}
}
fun apply(properties: Properties, task: AbstractKotlinCompile<*>) {
setPropertyValue(task, properties.getProperty(projectPropName))
}
private fun setPropertyValue(task: AbstractKotlinCompile<*>, value: Any?) {
if (value !is String) return
val transformedValue = transform(value) ?: return
taskProperty.set(task, transformedValue)
}
operator fun get(propName: String): String? =
if (project.hasProperty(propName)) {
project.property(propName) as? String
}
else {
localProperties.getProperty(propName)
}
}
@@ -51,7 +51,8 @@ import kotlin.properties.Delegates
const val ANNOTATIONS_PLUGIN_NAME = "org.jetbrains.kotlin.kapt"
const val KOTLIN_BUILD_DIR_NAME = "kotlin"
const val USING_INCREMENTAL_COMPILATION_MESSAGE = "Using kotlin incremental compilation"
const val USING_INCREMENTAL_COMPILATION_MESSAGE = "Using Kotlin incremental compilation"
const val USING_EXPERIMENTAL_JS_INCREMENTAL_COMPILATION_MESSAGE = "Using experimental Kotlin/JS incremental compilation"
abstract class AbstractKotlinCompileTool<T : CommonToolArguments>() : AbstractCompile() {
var compilerJarFile: File? = null
@@ -63,6 +64,30 @@ abstract class AbstractKotlinCompileTool<T : CommonToolArguments>() : AbstractCo
}
abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKotlinCompileTool<T>(), CompilerArgumentAware {
internal val taskBuildDirectory: File
get() = File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name).apply { mkdirs() }
private val cacheVersions: List<CacheVersion> =
listOf(normalCacheVersion(taskBuildDirectory),
dataContainerCacheVersion(taskBuildDirectory),
gradleCacheVersion(taskBuildDirectory))
// indicates that task should compile kotlin incrementally if possible
// it's not possible when IncrementalTaskInputs#isIncremental returns false (i.e first build)
var incremental: Boolean = false
get() = field
set(value) {
field = value
logger.kotlinDebug { "Set $this.incremental=$value" }
}
internal val isCacheFormatUpToDate: Boolean
get() {
if (!incremental) return true
return cacheVersions.all { it.checkVersion() == CacheVersion.Action.DO_NOTHING }
}
abstract protected fun createCompilerArgs(): T
protected val additionalClasspath = arrayListOf<File>()
@@ -87,16 +112,6 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
private val kotlinExt: KotlinProjectExtension
get() = project.extensions.findByType(KotlinProjectExtension::class.java)!!
// indicates that task should compile kotlin incrementally if possible
// it's not possible when IncrementalTaskInputs#isIncremental returns false (i.e first build)
var incremental: Boolean = false
get() = field
set(value) {
field = value
logger.kotlinDebug { "Set $this.incremental=$value" }
System.setProperty("kotlin.incremental.compilation", value.toString())
}
private lateinit var destinationDirProvider: Lazy<File>
override fun getDestinationDir(): File {
@@ -111,10 +126,6 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
destinationDirProvider = lazyOf(destinationDir)
}
init {
incremental = true //to execute the setter as well
}
internal var coroutinesFromGradleProperties: Coroutines? = null
// Input is needed to force rebuild even if source files are not changed
@get:Input
@@ -214,20 +225,6 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
get() = kotlinOptionsImpl
internal open val sourceRootsContainer = FilteringSourceRootsContainer()
internal val taskBuildDirectory: File
get() = File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name).apply { mkdirs() }
private val cacheVersions by lazy {
listOf(normalCacheVersion(taskBuildDirectory),
dataContainerCacheVersion(taskBuildDirectory),
gradleCacheVersion(taskBuildDirectory))
}
internal val isCacheFormatUpToDate: Boolean
get() {
if (!incremental) return true
return cacheVersions.all { it.checkVersion() == CacheVersion.Action.DO_NOTHING }
}
private var kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null
val kaptOptions = KaptOptions()
@@ -245,6 +242,10 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
internal var artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider? = null
internal var artifactFile: File? = null
init {
incremental = true
}
override fun findKotlinCompilerJar(project: Project): File? =
findKotlinJvmCompilerJar(project)
@@ -298,9 +299,9 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
else -> {
logger.warn(USING_INCREMENTAL_COMPILATION_MESSAGE)
GradleIncrementalCompilerEnvironment(compilerJar, changedFiles, reporter, taskBuildDirectory,
messageCollector, outputItemCollector, kaptAnnotationsFileUpdater,
messageCollector, outputItemCollector, args, kaptAnnotationsFileUpdater,
artifactDifferenceRegistryProvider,
artifactFile, args)
artifactFile)
}
}
@@ -444,9 +445,21 @@ open class Kotlin2JsCompile() : AbstractKotlinCompile<K2JSCompilerArguments>(),
val messageCollector = GradleMessageCollector(logger)
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = GradleCompilerRunner(project)
val environment = GradleCompilerEnvironment(compilerJar, messageCollector, outputItemCollector, args)
val reporter = GradleICReporter(project.rootProject.projectDir)
val environment = when {
incremental -> {
logger.warn(USING_EXPERIMENTAL_JS_INCREMENTAL_COMPILATION_MESSAGE)
GradleIncrementalCompilerEnvironment(
compilerJar, changedFiles, reporter, taskBuildDirectory,
messageCollector, outputItemCollector, args)
}
else -> {
GradleCompilerEnvironment(compilerJar, messageCollector, outputItemCollector, args)
}
}
val exitCode = compilerRunner.runJsCompiler(sourceRoots.kotlinSourceFiles, args, environment)
throwGradleExceptionIfError(exitCode)
}
@@ -25,7 +25,6 @@ internal open class KotlinTasksProvider {
fun createKotlinJVMTask(project: Project, name: String, sourceSetName: String): KotlinCompile =
project.tasks.create(name, KotlinCompile::class.java).apply {
configure(project, sourceSetName)
outputs.upToDateWhen { isCacheFormatUpToDate }
}
fun createKotlinJSTask(project: Project, name: String, sourceSetName: String): Kotlin2JsCompile =
@@ -42,6 +41,7 @@ internal open class KotlinTasksProvider {
this.sourceSetName = sourceSetName
this.friendTaskName = taskToFriendTaskMapper[this]
mapKotlinTaskProperties(project, this)
outputs.upToDateWhen { isCacheFormatUpToDate }
}
protected open val taskToFriendTaskMapper: TaskToFriendTaskMapper =
@@ -18,7 +18,7 @@ package org.jetbrains.kotlin.incremental
import java.io.File
internal const val GRADLE_CACHE_VERSION = 3
internal const val GRADLE_CACHE_VERSION = 4
internal const val GRADLE_CACHE_VERSION_FILE_NAME = "gradle-format-version.txt"
internal fun gradleCacheVersion(dataRoot: File): CacheVersion =