Gradle: copy kotlin classes after java compilation

#KT-9392 fixed
    #KT-12295 fixed
    #KT-12736 fixed
This commit is contained in:
Alexey Tsvetkov
2016-06-24 18:00:14 +03:00
parent 3a59e1d222
commit 16b1616ebc
23 changed files with 418 additions and 85 deletions
@@ -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()
@@ -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
@@ -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()
@@ -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())
}
}
@@ -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) =
@@ -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)
}
}
}
@@ -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") {
@@ -39,6 +39,7 @@ abstract class KaptIncrementalBaseIT(val shouldUseStubs: Boolean): BaseGradleIT(
checkStubUsage()
checkGenerated(*annotatedElements)
checkNotGenerated("notAnnotatedFun")
assertContains("foo.ATest PASSED")
}
project.build("build") {
@@ -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")
}
}
}
@@ -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 {
}
@@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=512m
@@ -0,0 +1,13 @@
package foo;
public class Bar {
private final int x;
Bar(int x) {
this.x = x;
}
public int getX() {
return x;
}
}
@@ -0,0 +1,7 @@
package foo;
public class JavaBarUser {
public void use(Bar bar) {
System.out.println("Used from java Bar.getX() = " + bar.getX());
}
}
@@ -0,0 +1,7 @@
package foo
class KotlinBarUser {
fun use(bar: Bar) {
println("Used from kotlin Bar.getX() = ${bar.x}")
}
}
@@ -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 {
@@ -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());
}
}
@@ -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 << {
@@ -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());
}
}
@@ -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)
}
}
@@ -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 << {
@@ -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());
}
}