Gradle: copy kotlin classes after java compilation
#KT-9392 fixed
#KT-12295 fixed
#KT-12736 fixed
This commit is contained in:
+25
-76
@@ -1,14 +1,5 @@
|
||||
package org.jetbrains.kotlin.gradle.tasks
|
||||
|
||||
import org.jetbrains.kotlin.com.intellij.ide.highlighter.JavaFileType
|
||||
import org.jetbrains.kotlin.com.intellij.lang.Language
|
||||
import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer
|
||||
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiClass
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiJavaFile
|
||||
import org.jetbrains.kotlin.com.intellij.psi.impl.PsiFileFactoryImpl
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.io.FilenameUtils
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.codehaus.groovy.runtime.MethodClosure
|
||||
@@ -36,6 +27,14 @@ import org.jetbrains.kotlin.cli.js.K2JSCompiler
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.com.intellij.ide.highlighter.JavaFileType
|
||||
import org.jetbrains.kotlin.com.intellij.lang.Language
|
||||
import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer
|
||||
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiClass
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
|
||||
import org.jetbrains.kotlin.com.intellij.psi.PsiJavaFile
|
||||
import org.jetbrains.kotlin.com.intellij.psi.impl.PsiFileFactoryImpl
|
||||
import org.jetbrains.kotlin.compilerRunner.ArgumentUtils
|
||||
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollector
|
||||
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
|
||||
@@ -69,11 +68,11 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCo
|
||||
// it's not possible when IncrementalTaskInputs#isIncremental returns false (i.e first build)
|
||||
var incremental: Boolean = false
|
||||
var kotlinOptions: T = createBlankArgs()
|
||||
var kotlinDestinationDir: File? = destinationDir
|
||||
var compilerCalled: Boolean = false
|
||||
// TODO: consider more reliable approach (see usage)
|
||||
var anyClassesCompiled: Boolean = false
|
||||
var friendTaskName: String? = null
|
||||
var javaOutputDir: File? = null
|
||||
|
||||
private val loggerInstance = Logging.getLogger(this.javaClass)
|
||||
override fun getLogger() = loggerInstance
|
||||
@@ -104,14 +103,7 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCo
|
||||
populateCommonArgs(args)
|
||||
populateTargetSpecificArgs(args)
|
||||
compilerCalled = true
|
||||
beforeCompileHook(args)
|
||||
|
||||
try {
|
||||
callCompiler(args, sources, inputs.isIncremental, modified, removed)
|
||||
}
|
||||
finally {
|
||||
afterCompileHook(args)
|
||||
}
|
||||
callCompiler(args, sources, inputs.isIncremental, modified, removed)
|
||||
}
|
||||
|
||||
private fun getKotlinSources(): List<File> = (getSource() as Iterable<File>).filter { it.isKotlinFile() }
|
||||
@@ -148,14 +140,10 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCo
|
||||
open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
||||
override val compiler = K2JVMCompiler()
|
||||
override fun createBlankArgs(): K2JVMCompilerArguments = K2JVMCompilerArguments()
|
||||
private val kotlinClassFiles = HashSet<File>()
|
||||
|
||||
// Should be SourceDirectorySet or File
|
||||
val srcDirsSources = HashSet<Any>()
|
||||
|
||||
// TODO: find out whether we really need to be able to override destination dir here, and how it should work with destinationDir property
|
||||
private val compilerDestinationDir: String get() = if (StringUtils.isEmpty(kotlinOptions.destination)) { kotlinDestinationDir?.path.orEmpty() } else { kotlinOptions.destination }
|
||||
|
||||
// lazy because name is probably not available when constructor is called
|
||||
private val taskBuildDirectory: File by lazy { File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name) }
|
||||
private val cacheDirectory: File by lazy { File(taskBuildDirectory, CACHES_DIR_NAME) }
|
||||
@@ -180,7 +168,12 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
||||
logger.kotlinDebug("args.classpath = ${args.classpath}")
|
||||
}
|
||||
|
||||
logger.kotlinDebug("destinationDir = $compilerDestinationDir")
|
||||
if (args.destination?.isNotBlank() ?: false) {
|
||||
// TODO: fix if needed
|
||||
logger.warn("Setting kotlinOptions.destination is not supported in gradle")
|
||||
}
|
||||
|
||||
logger.kotlinDebug("destinationDir = $destinationDir")
|
||||
|
||||
val extraProperties = extensions.extraProperties
|
||||
args.pluginClasspaths = extraProperties.getOrNull<Array<String>>("compilerPluginClasspaths") ?: arrayOf()
|
||||
@@ -204,18 +197,11 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
||||
args.languageVersion = kotlinOptions.languageVersion
|
||||
args.jvmTarget = kotlinOptions.jvmTarget
|
||||
|
||||
fun addFriendPathForTestTask(taskName: String) {
|
||||
logger.kotlinDebug("try to determine the output directory of corresponding $taskName task")
|
||||
val tasks = project.getTasksByName("$taskName", false)
|
||||
logger.kotlinDebug("tasks for $taskName: ${tasks}")
|
||||
if (tasks.size == 1) {
|
||||
val task = tasks.firstOrNull() as? KotlinCompile
|
||||
if (task != null) {
|
||||
logger.kotlinDebug("destination directory for production = ${task.destinationDir}")
|
||||
args.friendPaths = arrayOf(task.destinationDir.absolutePath)
|
||||
args.moduleName = task.kotlinOptions.moduleName ?: task.extensions.extraProperties.getOrNull<String>("defaultModuleName")
|
||||
}
|
||||
}
|
||||
fun addFriendPathForTestTask(friendKotlinTaskName: String) {
|
||||
val friendTask = project.getTasksByName(friendKotlinTaskName, /* recursive = */false).firstOrNull() as? KotlinCompile ?: return
|
||||
args.friendPaths = arrayOf(friendTask.javaOutputDir!!.absolutePath)
|
||||
args.moduleName = friendTask.kotlinOptions.moduleName ?: friendTask.extensions.extraProperties.getOrNull<String>("defaultModuleName")
|
||||
logger.kotlinDebug("java destination directory for production = ${friendTask.javaOutputDir}")
|
||||
}
|
||||
|
||||
logger.kotlinDebug { "friendTaskName = $friendTaskName" }
|
||||
@@ -237,7 +223,7 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
||||
val targetType = "java-production"
|
||||
val moduleName = args.moduleName
|
||||
val targets = listOf(TargetId(moduleName, targetType))
|
||||
val outputDir = File(compilerDestinationDir)
|
||||
val outputDir = destinationDir
|
||||
val caches = hashMapOf<TargetId, GradleIncrementalCacheImpl>()
|
||||
val lookupStorage = LookupStorage(File(cacheDirectory, "lookups"))
|
||||
val lookupTracker = LookupTrackerImpl(LookupTracker.DO_NOTHING)
|
||||
@@ -331,14 +317,9 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
||||
}
|
||||
|
||||
fun cleanupOnError() {
|
||||
val outputDirFile = File(compilerDestinationDir)
|
||||
|
||||
assert(outputDirFile.exists())
|
||||
val generatedRelPaths = allGeneratedFiles.map { it.outputFile.toRelativeString(outputDirFile) }
|
||||
logger.kotlinInfo("deleting output on error: ${generatedRelPaths.joinToString()}")
|
||||
|
||||
allGeneratedFiles.forEach { it.outputFile.delete() }
|
||||
generatedRelPaths.forEach { File(destinationDir, it).delete() }
|
||||
val filesToDelete = allGeneratedFiles.map { it.outputFile }
|
||||
logger.kotlinInfo("deleting output on error: ${filesToDelete.joinToString()}")
|
||||
filesToDelete.forEach { it.delete() }
|
||||
}
|
||||
|
||||
fun processCompilerExitCode(exitCode: ExitCode) {
|
||||
@@ -527,38 +508,6 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
||||
|
||||
private fun File.isJavaFile() = extension.equals(JavaFileType.INSTANCE.defaultExtension, ignoreCase = true)
|
||||
|
||||
override fun beforeCompileHook(args: K2JVMCompilerArguments) {
|
||||
kotlinClassFiles.addAll(listClassFiles(compilerDestinationDir))
|
||||
}
|
||||
|
||||
override fun afterCompileHook(args: K2JVMCompilerArguments) {
|
||||
kaptAnnotationsFileUpdater?.dispose()
|
||||
|
||||
if (kaptStubGeneratingMode) return
|
||||
|
||||
logger.debug("Copying resulting files to classes")
|
||||
|
||||
// Copy kotlin classes to all classes directory
|
||||
val outputDirFile = File(compilerDestinationDir)
|
||||
if (outputDirFile.exists()) {
|
||||
FileUtils.copyDirectory(outputDirFile, destinationDir)
|
||||
}
|
||||
|
||||
kotlinClassFiles.removeAll(listClassFiles(compilerDestinationDir))
|
||||
if (kotlinClassFiles.isNotEmpty()) {
|
||||
// some classes were removed during compilation
|
||||
val filesToRemove = kotlinClassFiles.map {
|
||||
val relativePath = it.relativeTo(outputDirFile).path
|
||||
File(destinationDir, relativePath)
|
||||
}
|
||||
|
||||
val notRemoved = filesToRemove.filter { !it.delete() }
|
||||
if (notRemoved.isNotEmpty()) {
|
||||
logger.kotlinDebug("Could not delete classfiles: $notRemoved")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// override setSource to track source directory sets and files (for generated android folders)
|
||||
override fun setSource(source: Any?) {
|
||||
srcDirsSources.clear()
|
||||
|
||||
-1
@@ -106,7 +106,6 @@ private fun Project.createKotlinAfterJavaTask(
|
||||
taskFactory: (suffix: String) -> AbstractCompile
|
||||
): AbstractCompile {
|
||||
val kotlinAfterJavaTask = with (taskFactory(KOTLIN_AFTER_JAVA_TASK_SUFFIX)) {
|
||||
kotlinDestinationDir = kotlinTask.kotlinDestinationDir
|
||||
destinationDir = kotlinTask.destinationDir
|
||||
classpath = kotlinTask.classpath - project.files(javaTask.destinationDir)
|
||||
this
|
||||
|
||||
+41
-7
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.gradle.internal.KotlinSourceSetImpl
|
||||
import org.jetbrains.kotlin.gradle.internal.initKapt
|
||||
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
|
||||
import org.jetbrains.kotlin.gradle.tasks.SyncOutputTask
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
@@ -145,7 +146,7 @@ class Kotlin2JvmSourceSetProcessor(
|
||||
|
||||
project.afterEvaluate { project ->
|
||||
if (project != null) {
|
||||
kotlinTask.kotlinDestinationDir = File(project.buildDir, "kotlin-classes/$sourceSetName")
|
||||
kotlinTask.destinationDir = File(project.buildDir, "kotlin-classes/$sourceSetName")
|
||||
|
||||
for (dir in sourceSet.getJava().srcDirs) {
|
||||
kotlinDirSet?.srcDir(dir)
|
||||
@@ -180,6 +181,7 @@ class Kotlin2JvmSourceSetProcessor(
|
||||
}
|
||||
|
||||
configureJavaTask(kotlinTask, javaTask, kotlinAfterJavaTask, logger)
|
||||
createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, sourceSetName)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,7 +205,7 @@ class Kotlin2JsSourceSetProcessor(
|
||||
val build = project.tasks.findByName("build")
|
||||
|
||||
val defaultKotlinDestinationDir = File(project.buildDir, "kotlin2js/${sourceSetName}")
|
||||
private fun kotlinTaskDestinationDir(): File? = kotlinTask.kotlinDestinationDir
|
||||
private fun kotlinTaskDestinationDir(): File? = kotlinTask.destinationDir
|
||||
private fun kotlinJsDestinationDir(): File? = (kotlinTask.property("outputFile") as String).let { File(it) }.let { if (it.isDirectory) it else it.parentFile }
|
||||
|
||||
private fun kotlinSourcePathsForSourceMap() = sourceSet.getAllSource()
|
||||
@@ -217,7 +219,7 @@ class Kotlin2JsSourceSetProcessor(
|
||||
tasksProvider.createKotlinJSTask(project, taskName)
|
||||
|
||||
override fun doTargetSpecificProcessing() {
|
||||
kotlinTask.kotlinDestinationDir = defaultKotlinDestinationDir
|
||||
kotlinTask.destinationDir = defaultKotlinDestinationDir
|
||||
build?.dependsOn(kotlinTaskName)
|
||||
clean?.dependsOn("clean" + kotlinTaskName.capitalize())
|
||||
|
||||
@@ -373,8 +375,7 @@ open class KotlinAndroidPlugin @Inject constructor(val scriptHandler: ScriptHand
|
||||
}
|
||||
|
||||
// store kotlin classes in separate directory. They will serve as class-path to java compiler
|
||||
kotlinTask.kotlinDestinationDir = File(project.buildDir, "tmp/kotlin-classes/$variantDataName")
|
||||
kotlinTask.destinationDir = javaTask.destinationDir
|
||||
kotlinTask.destinationDir = File(project.buildDir, "tmp/kotlin-classes/$variantDataName")
|
||||
kotlinTask.description = "Compiles the ${variantDataName} kotlin."
|
||||
kotlinTask.setDependsOn(javaTask.dependsOn)
|
||||
|
||||
@@ -420,7 +421,7 @@ open class KotlinAndroidPlugin @Inject constructor(val scriptHandler: ScriptHand
|
||||
// java classpath during project evaluation (see prepareComAndroidSupportSupportV42311Library)
|
||||
val fullClasspath = lazy {
|
||||
val androidRT = project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt))
|
||||
(javaTask.classpath + androidRT) - project.files(kotlinTask.kotlinDestinationDir)
|
||||
(javaTask.classpath + androidRT) - project.files(kotlinTask.destinationDir)
|
||||
}
|
||||
kotlinTask.classpath = javaTask.classpath
|
||||
kotlinTask.updateClasspathBeforeTask { fullClasspath.value }
|
||||
@@ -446,6 +447,7 @@ open class KotlinAndroidPlugin @Inject constructor(val scriptHandler: ScriptHand
|
||||
}
|
||||
|
||||
configureJavaTask(kotlinTask, javaTask, kotlinAfterJavaTask, logger)
|
||||
createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, variantDataName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,10 +489,42 @@ private fun configureJavaTask(kotlinTask: AbstractCompile, javaTask: AbstractCom
|
||||
* so it's only safe to modify javaTask.classpath right before its usage
|
||||
*/
|
||||
if (kotlinAfterJavaTask == null) {
|
||||
javaTask.appendClasspathDynamically(kotlinTask.kotlinDestinationDir!!)
|
||||
javaTask.appendClasspathDynamically(kotlinTask.destinationDir!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSyncOutputTask(
|
||||
project: Project,
|
||||
kotlinTask: AbstractCompile,
|
||||
javaTask: AbstractCompile,
|
||||
kotlinAfterJavaTask: AbstractCompile?,
|
||||
variantName: String
|
||||
) {
|
||||
// if kotlinAfterJavaTask is not null then kotlinTask compiles stubs, so don't sync them
|
||||
val kotlinDir = (kotlinAfterJavaTask ?: kotlinTask).destinationDir
|
||||
val javaDir = javaTask.destinationDir
|
||||
val taskName = "copy${variantName.capitalize()}KotlinClasses"
|
||||
|
||||
val syncTask = project.tasks.create(taskName, SyncOutputTask::class.java)
|
||||
syncTask.kotlinOutputDir = kotlinDir
|
||||
syncTask.javaOutputDir = javaDir
|
||||
kotlinTask.javaOutputDir = javaDir
|
||||
kotlinAfterJavaTask?.javaOutputDir = javaDir
|
||||
|
||||
// copying should be executed after a latter task
|
||||
if (kotlinAfterJavaTask != null) {
|
||||
// finalizer tasks get executed even if finalizing task has failed;
|
||||
// we want to avoid copying classes if kotlin compilation has failed
|
||||
syncTask.onlyIf { kotlinAfterJavaTask.state.failure == null }
|
||||
kotlinAfterJavaTask.finalizedBy(syncTask)
|
||||
}
|
||||
else {
|
||||
javaTask.finalizedBy(syncTask)
|
||||
}
|
||||
|
||||
project.logger.kotlinDebug { "Created task ${syncTask.path} to copy kotlin classes from $kotlinDir to $javaDir" }
|
||||
}
|
||||
|
||||
private fun loadSubplugins(project: Project): SubpluginEnvironment {
|
||||
try {
|
||||
val subplugins = ServiceLoader.load(KotlinGradleSubplugin::class.java, project.buildscript.classLoader).toList()
|
||||
|
||||
+6
@@ -70,3 +70,9 @@ open class Kotlin2JsPluginWrapper : KotlinBasePluginWrapper() {
|
||||
fun Logger.kotlinDebug(message: String) {
|
||||
this.debug("[KOTLIN] $message")
|
||||
}
|
||||
|
||||
inline fun Logger.kotlinDebug(message: () -> String) {
|
||||
if (isDebugEnabled) {
|
||||
kotlinDebug(message())
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -52,8 +52,8 @@ internal fun AbstractCompile.appendClasspathDynamically(file: File) {
|
||||
}
|
||||
|
||||
internal var AbstractTask.anyClassesCompiled: Boolean? by TaskPropertyDelegate("anyClassesCompiled")
|
||||
internal var AbstractTask.kotlinDestinationDir: File? by TaskPropertyDelegate("kotlinDestinationDir")
|
||||
internal var AbstractTask.friendTaskName: String? by TaskPropertyDelegate("friendTaskName")
|
||||
internal var AbstractTask.javaOutputDir: File? by TaskPropertyDelegate("javaOutputDir")
|
||||
|
||||
inline
|
||||
internal fun <reified T : Any> TaskPropertyDelegate(propertyName: String) =
|
||||
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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 org.jetbrains.kotlin.gradle.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.OutputFiles
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
|
||||
import org.gradle.api.tasks.incremental.InputFileDetails
|
||||
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
|
||||
import java.io.File
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.util.*
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Copies kotlin classes to java output directory.
|
||||
* No other way to get our classes into apk for android until API is provided https://code.google.com/p/android/issues/detail?id=200714
|
||||
*
|
||||
* Utilises gradle for tracking input/output changes.
|
||||
* 0. Gradle takes snapshots of annotated properties after compilation.
|
||||
* 1. kotlinOutputDir as input files. These files will be copied.
|
||||
* Copying will be done incrementally unless output classes are wiped out
|
||||
* (by IncrementalJavaCompilationSafeguard task from android gradle plugin for example).
|
||||
* 2. kotlinClassesInJavaOutputDir as output files -- copied classes in java output dir.
|
||||
* When they are changed or removed gradle tells us that incremental build is not possible (inputs.isIncremental is false).
|
||||
* 3. We do non-incremental sync in the following way:
|
||||
* a. previously copied files are deleted (to do so we save the list of copied files after copying);
|
||||
* b. all kotlin files are copied to java output dir.
|
||||
*
|
||||
* It is possible that some file was converted from kotlin to java.
|
||||
* In this case gradle will tell us to make non-incremental sync.
|
||||
* To workaround this scenario we also save timestamp of copied class.
|
||||
* On step (a) of sync process we do not delete the class in java output dir
|
||||
* if it's timestamp now is newer than when we copied it
|
||||
* (assuming it was modified by javac when class was converted to java).
|
||||
*/
|
||||
open class SyncOutputTask : DefaultTask() {
|
||||
@get:InputFiles
|
||||
var kotlinOutputDir: File by Delegates.notNull()
|
||||
var javaOutputDir: File by Delegates.notNull()
|
||||
|
||||
// OutputDirectory needed for task to be incremental
|
||||
@get:OutputDirectory
|
||||
val workingDir = File(project.buildDir, name).apply { mkdirs() }
|
||||
private val timestampsFile = File(workingDir, TIMESTAMP_FILE_NAME)
|
||||
private val timestamps: MutableMap<File, Long> = readTimestamps(timestampsFile)
|
||||
|
||||
@Suppress("unused")
|
||||
@get:OutputFiles
|
||||
val kotlinClassesInJavaOutputDir: Collection<File>
|
||||
get() = timestamps.keys
|
||||
|
||||
@Suppress("unused")
|
||||
@TaskAction
|
||||
fun execute(inputs: IncrementalTaskInputs): Unit {
|
||||
if (inputs.isIncremental) {
|
||||
logger.kotlinDebug { "Incremental copying files from $kotlinOutputDir to $javaOutputDir" }
|
||||
inputs.outOfDate { processIncrementally(it) }
|
||||
inputs.removed { processIncrementally(it) }
|
||||
}
|
||||
else {
|
||||
logger.kotlinDebug { "Non-incremental copying files from $kotlinOutputDir to $javaOutputDir" }
|
||||
processNonIncrementally()
|
||||
}
|
||||
|
||||
saveTimestamps(timestampsFile, timestamps)
|
||||
}
|
||||
|
||||
private fun processNonIncrementally() {
|
||||
for ((file, timestamp) in timestamps) {
|
||||
// wipe all files written by us
|
||||
// do not remove files converted to java (newer timestamp)
|
||||
if (file.lastModified() == timestamp) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
timestampsFile.delete()
|
||||
timestamps.clear()
|
||||
|
||||
kotlinOutputDir.walkTopDown().forEach {
|
||||
copy(it, it.siblingInJavaDir)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processIncrementally(input: InputFileDetails) {
|
||||
val fileInKotlinDir = input.file
|
||||
val fileInJavaDir = fileInKotlinDir.siblingInJavaDir
|
||||
|
||||
if (input.isRemoved) {
|
||||
// file was removed in kotlin dir, remove from java as well
|
||||
remove(fileInJavaDir)
|
||||
}
|
||||
else {
|
||||
// copy modified or added file from kotlin to java
|
||||
copy(fileInKotlinDir, fileInJavaDir)
|
||||
}
|
||||
}
|
||||
|
||||
private fun remove(fileInJavaDir: File) {
|
||||
if (!fileInJavaDir.isFile) return
|
||||
|
||||
fileInJavaDir.delete()
|
||||
timestamps.remove(fileInJavaDir)
|
||||
|
||||
logger.kotlinDebug {
|
||||
"Removed kotlin class ${fileInJavaDir.relativeTo(javaOutputDir).path} from $javaOutputDir"
|
||||
}
|
||||
}
|
||||
|
||||
private fun copy(fileInKotlinDir: File, fileInJavaDir: File) {
|
||||
if (!fileInKotlinDir.isFile) return
|
||||
|
||||
fileInJavaDir.parentFile.mkdirs()
|
||||
fileInKotlinDir.copyTo(fileInJavaDir, overwrite = true)
|
||||
timestamps[fileInJavaDir] = fileInJavaDir.lastModified()
|
||||
|
||||
logger.kotlinDebug {
|
||||
"Copied kotlin class ${fileInKotlinDir.relativeTo(kotlinOutputDir).path} from $kotlinOutputDir to $javaOutputDir"
|
||||
}
|
||||
}
|
||||
|
||||
private val File.siblingInJavaDir: File
|
||||
get() = File(javaOutputDir, this.relativeTo(kotlinOutputDir).path)
|
||||
}
|
||||
|
||||
private val TIMESTAMP_FILE_NAME = "kotlin-files-in-java-timestamps.bin"
|
||||
|
||||
private fun readTimestamps(file: File): MutableMap<File, Long> {
|
||||
val result = HashMap<File, Long>()
|
||||
if (!file.isFile) return result
|
||||
|
||||
ObjectInputStream(file.inputStream()).use { input ->
|
||||
val size = input.readInt()
|
||||
|
||||
repeat(size) {
|
||||
val path = input.readUTF()
|
||||
val timestamp = input.readLong()
|
||||
result[File(path)] = timestamp
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun saveTimestamps(snapshotFile: File, timestamps: Map<File, Long>) {
|
||||
if (!snapshotFile.exists()) {
|
||||
snapshotFile.parentFile.mkdirs()
|
||||
snapshotFile.createNewFile()
|
||||
}
|
||||
else {
|
||||
snapshotFile.delete()
|
||||
}
|
||||
|
||||
ObjectOutputStream(snapshotFile.outputStream()).use { out ->
|
||||
out.writeInt(timestamps.size)
|
||||
|
||||
for ((file, timestamp) in timestamps) {
|
||||
out.writeUTF(file.canonicalPath)
|
||||
out.writeLong(timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -29,6 +29,8 @@ class KaptIT: BaseGradleIT() {
|
||||
assertNoSuchFile("build/classes/main/example/SourceAnnotatedTestClassGenerated.class")
|
||||
assertFileExists("build/classes/main/example/BinaryAnnotatedTestClassGenerated.class")
|
||||
assertFileExists("build/classes/main/example/RuntimeAnnotatedTestClassGenerated.class")
|
||||
assertContains("example.JavaTest PASSED")
|
||||
assertContains("example.KotlinTest PASSED")
|
||||
}
|
||||
|
||||
project.build("build") {
|
||||
@@ -69,6 +71,7 @@ class KaptIT: BaseGradleIT() {
|
||||
assertFileExists("build/classes/main/example/BinaryAnnotatedTestClassGenerated.class")
|
||||
assertFileExists("build/classes/main/example/RuntimeAnnotatedTestClassGenerated.class")
|
||||
assertNotContains("w: Classpath entry points to a non-existent location")
|
||||
assertContains("example.JavaTest PASSED")
|
||||
}
|
||||
|
||||
project.build("build") {
|
||||
|
||||
+1
@@ -39,6 +39,7 @@ abstract class KaptIncrementalBaseIT(val shouldUseStubs: Boolean): BaseGradleIT(
|
||||
checkStubUsage()
|
||||
checkGenerated(*annotatedElements)
|
||||
checkNotGenerated("notAnnotatedFun")
|
||||
assertContains("foo.ATest PASSED")
|
||||
}
|
||||
|
||||
project.build("build") {
|
||||
|
||||
+47
@@ -3,8 +3,10 @@ package org.jetbrains.kotlin.gradle
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.jetbrains.kotlin.gradle.plugin.CleanUpBuildListener
|
||||
import org.jetbrains.kotlin.gradle.tasks.USING_EXPERIMENTAL_INCREMENTAL_MESSAGE
|
||||
import org.jetbrains.kotlin.gradle.util.getFileByName
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class KotlinGradleIT: BaseGradleIT() {
|
||||
@@ -199,4 +201,49 @@ class KotlinGradleIT: BaseGradleIT() {
|
||||
assertContains(USING_EXPERIMENTAL_INCREMENTAL_MESSAGE)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConvertJavaToKotlin() {
|
||||
val project = Project("convertBetweenJavaAndKotlin", GRADLE_VERSION)
|
||||
project.setupWorkingDir()
|
||||
|
||||
val barKt = project.projectDir.getFileByName("Bar.kt")
|
||||
val barKtContent = barKt.readText()
|
||||
barKt.delete()
|
||||
|
||||
project.build("build") {
|
||||
assertSuccessful()
|
||||
}
|
||||
|
||||
val barClass = project.projectDir.getFileByName("Bar.class")
|
||||
val barClassTimestamp = barClass.lastModified()
|
||||
|
||||
val barJava = project.projectDir.getFileByName("Bar.java")
|
||||
barJava.delete()
|
||||
barKt.writeText(barKtContent)
|
||||
|
||||
project.build("build") {
|
||||
assertSuccessful()
|
||||
assertNotContains(":compileKotlin UP-TO-DATE", ":compileJava UP-TO-DATE")
|
||||
assertNotEquals(barClassTimestamp, barClass.lastModified(), "Bar.class timestamp hasn't been updated")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWipeClassesDirectoryBetweenBuilds() {
|
||||
val project = Project("kotlinJavaProject", GRADLE_VERSION)
|
||||
|
||||
project.build("build") {
|
||||
assertSuccessful()
|
||||
}
|
||||
|
||||
val javaOutputDir = File(project.projectDir, "build/classes")
|
||||
assert(javaOutputDir.isDirectory) { "Classes directory does not exist $javaOutputDir" }
|
||||
javaOutputDir.deleteRecursively()
|
||||
|
||||
project.build("build") {
|
||||
assertSuccessful()
|
||||
assertContains(":compileKotlin UP-TO-DATE")
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url 'file://' + pathToKotlinPlugin }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.1-SNAPSHOT'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "java"
|
||||
apply plugin: "kotlin"
|
||||
|
||||
repositories {
|
||||
maven { url 'file://' + pathToKotlinPlugin }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.jetbrains.kotlin:kotlin-stdlib:1.1-SNAPSHOT'
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.incremental = true
|
||||
}
|
||||
|
||||
compileJava.doLast {
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=512m
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package foo;
|
||||
|
||||
public class Bar {
|
||||
private final int x;
|
||||
|
||||
Bar(int x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package foo
|
||||
|
||||
class Bar(val x: Int)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package foo;
|
||||
|
||||
public class Dummy {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package foo;
|
||||
|
||||
public class JavaBarUser {
|
||||
public void use(Bar bar) {
|
||||
System.out.println("Used from java Bar.getX() = " + bar.getX());
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package foo
|
||||
|
||||
class KotlinBarUser {
|
||||
fun use(bar: Bar) {
|
||||
println("Used from kotlin Bar.getX() = ${bar.x}")
|
||||
}
|
||||
}
|
||||
+1
@@ -24,6 +24,7 @@ dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:1.1-SNAPSHOT"
|
||||
compile "org.jetbrains.kotlin:annotation-processor-example:1.1-SNAPSHOT"
|
||||
kapt "org.jetbrains.kotlin:annotation-processor-example:1.1-SNAPSHOT"
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
kapt {
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package foo;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.Assert;
|
||||
|
||||
public class ATest {
|
||||
@Test
|
||||
public void testValA() {
|
||||
A a = new A();
|
||||
Assert.assertEquals("text", a.getValA());
|
||||
}
|
||||
}
|
||||
+1
@@ -24,6 +24,7 @@ dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:1.1-SNAPSHOT"
|
||||
compile "org.jetbrains.kotlin:annotation-processor-example:1.1-SNAPSHOT"
|
||||
kapt "org.jetbrains.kotlin:annotation-processor-example:1.1-SNAPSHOT"
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
task show << {
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package example;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class JavaTest {
|
||||
@Test
|
||||
public void test() {
|
||||
TestClass testClass = new TestClass();
|
||||
Assert.assertEquals("text", testClass.getTestVal());
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package example
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class KotlinTest {
|
||||
@Test
|
||||
fun test() {
|
||||
val testClass = TestClass()
|
||||
Assert.assertEquals("text", testClass.testVal)
|
||||
}
|
||||
}
|
||||
+1
@@ -24,6 +24,7 @@ dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:1.1-SNAPSHOT"
|
||||
compile "org.jetbrains.kotlin:annotation-processor-example:1.1-SNAPSHOT"
|
||||
kapt "org.jetbrains.kotlin:annotation-processor-example:1.1-SNAPSHOT"
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
task show << {
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package example;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class JavaTest {
|
||||
@Test
|
||||
public void test() {
|
||||
TestClass testClass = new TestClass();
|
||||
Assert.assertEquals("text", testClass.getTestVal());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user