Drop support for Gradle 3.x

#KT-27885 Fixed
This commit is contained in:
Alexey Tsvetkov
2018-10-30 06:29:22 +03:00
parent c92a6f9ca2
commit b51461b5c5
27 changed files with 71 additions and 630 deletions
@@ -47,14 +47,9 @@ enum class KotlinPlatformType: Named, Serializable {
fun setupAttributesMatchingStrategy(attributesSchema: AttributesSchema) {
attributesSchema.attribute(KotlinPlatformType.attribute).run {
if (isGradleAtLeast(4, 0)) {
compatibilityRules.add(CompatibilityRule::class.java)
disambiguationRules.add(DisambiguationRule::class.java)
}
compatibilityRules.add(CompatibilityRule::class.java)
disambiguationRules.add(DisambiguationRule::class.java)
}
}
}
}
private fun isGradleAtLeast(major: Int, minor: Int) =
GradleVersion.current() >= GradleVersion.version("$major.$minor")
@@ -14,7 +14,7 @@ import kotlin.test.assertTrue
class KotlinAndroidGradleIT : AbstractKotlinAndroidGradleTests(androidGradlePluginVersion = "2.3.0") {
override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.InRange("3.4", "4.10.2")
get() = GradleVersionRequired.InRange("4.0", "4.10.2")
}
// TODO If we there is a way to fetch the latest Android plugin version, test against the latest version
@@ -329,17 +329,6 @@ abstract class AbstractKotlinAndroidGradleTests(val androidGradlePluginVersion:
// Execute 'assembleAndroidTest' first, without 'build' side effects
project.build("assembleAndroidTest") {
assertSuccessful()
if (project.testGradleVersionBelow("4.0")) {
val tasks = ArrayList<String>().apply {
for (subProject in listOf("Android", "Lib")) {
for (flavor in listOf("Flavor1", "Flavor2")) {
add(":$subProject:copy${flavor}DebugKotlinClasses")
}
}
}
// with the new AGP we don't need copy classes tasks
assertTasksExecuted(tasks)
}
}
}
@@ -590,12 +579,10 @@ fun getSomething() = 10
":libAndroid:compileReleaseUnitTestKotlin"
)
val kotlinFolder = if (project.testGradleVersionAtLeast("4.0")) "kotlin" else ""
assertFileExists("lib/build/classes/$kotlinFolder/main/foo/PlatformClass.kotlin_metadata")
assertFileExists("lib/build/classes/$kotlinFolder/test/foo/PlatformTest.kotlin_metadata")
assertFileExists("libJvm/build/classes/$kotlinFolder/main/foo/PlatformClass.class")
assertFileExists("libJvm/build/classes/$kotlinFolder/test/foo/PlatformTest.class")
assertFileExists("lib/build/classes/kotlin/main/foo/PlatformClass.kotlin_metadata")
assertFileExists("lib/build/classes/kotlin/test/foo/PlatformTest.kotlin_metadata")
assertFileExists("libJvm/build/classes/kotlin/main/foo/PlatformClass.class")
assertFileExists("libJvm/build/classes/kotlin/test/foo/PlatformTest.class")
assertFileExists("libAndroid/build/tmp/kotlin-classes/debug/foo/PlatformClass.class")
assertFileExists("libAndroid/build/tmp/kotlin-classes/release/foo/PlatformClass.class")
@@ -511,14 +511,10 @@ abstract class BaseGradleIT {
assertSameFiles(sources, compiledJavaSources.projectRelativePaths(this.project), "Compiled Java files differ:\n ")
fun Project.resourcesDir(subproject: String? = null, sourceSet: String = "main"): String =
(subproject?.plus("/") ?: "") + "build/" +
(if (testGradleVersionBelow("4.0")) "classes/" else "resources/") +
sourceSet + "/"
(subproject?.plus("/") ?: "") + "build/resources/$sourceSet/"
fun Project.classesDir(subproject: String? = null, sourceSet: String = "main", language: String = "kotlin"): String =
(subproject?.plus("/") ?: "") + "build/classes/" +
(if (testGradleVersionAtLeast("4.0")) "$language/" else "") +
sourceSet + "/"
(subproject?.plus("/") ?: "") + "build/classes/$language/$sourceSet/"
fun Project.testGradleVersionAtLeast(version: String): Boolean =
GradleVersion.version(chooseWrapperVersionOrFinishTest()) >= GradleVersion.version(version)
@@ -21,10 +21,7 @@ import org.junit.Assume
sealed class GradleVersionRequired(val minVersion: String, val maxVersion: String?) {
companion object {
//TODO once the plugin is compiled with API level 1.0, replace with the really supported one (3.2)
// Currently, it will lead to failing tests due to the conflict with the pre-release kotlin-reflect
// bundled into Gradle 3.2...3.4
const val OLDEST_SUPPORTED = "3.5"
const val OLDEST_SUPPORTED = "4.0"
}
class Exact(version: String) : GradleVersionRequired(version, version)
@@ -308,28 +308,6 @@ open class Kapt3IT : Kapt3BaseIT() {
}
}
@Test
fun testKaptClassesDirSync() {
val project = Project("autoService", GradleVersionRequired.Exact("3.5"), directoryPrefix = "kapt2")
project.build("build") {
assertSuccessful()
assertKaptSuccessful()
assertFileExists("processor/build/classes/main/META-INF/services/javax.annotation.processing.Processor")
assertFileExists("processor/build/classes/main/processor/MyProcessor.class")
}
project.projectDir.getFileByName("MyProcessor.kt").modify {
it.replace("@AutoService(Processor::class)", "")
}
project.build(":processor:build") {
assertSuccessful()
assertNoSuchFile("processor/build/classes/main/META-INF/services/javax.annotation.processing.Processor")
assertFileExists("processor/build/classes/main/processor/MyProcessor.class")
}
}
/**
* Tests that compile arguments are properly copied from compileKotlin to kaptTask
*/
@@ -133,7 +133,7 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() {
@Test
fun testCompilerTestAccessInternalProduction() {
val project = Project("kotlin2JsInternalTest", GradleVersionRequired.Exact("3.5"))
val project = Project("kotlin2JsInternalTest")
project.build("runRhino") {
assertSuccessful()
@@ -167,7 +167,7 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() {
@Test
fun testKotlinJsBuiltins() {
val project = Project("kotlinBuiltins", GradleVersionRequired.AtLeast("4.0"))
val project = Project("kotlinBuiltins")
project.setupWorkingDir()
val buildGradle = File(project.projectDir, "app").getFileByName("build.gradle")
@@ -309,7 +309,7 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() {
/** Issue: KT-18495 */
@Test
fun testNoSeparateClassesDirWarning() {
val project = Project("kotlin2JsProject", GradleVersionRequired.AtLeast("4.0"))
val project = Project("kotlin2JsProject")
project.build("build") {
assertSuccessful()
assertNotContains("this build assumes a single directory for all classes from a source set")
@@ -317,7 +317,7 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() {
}
@Test
fun testIncrementalCompilation() = Project("kotlin2JsICProject", GradleVersionRequired.AtLeast("4.0")).run {
fun testIncrementalCompilation() = Project("kotlin2JsICProject").run {
build("build") {
assertSuccessful()
assertContains(USING_JS_INCREMENTAL_COMPILATION_MESSAGE)
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.jetbrains.kotlin.gradle.plugin.CopyClassesToJavaOutputStatus
import org.jetbrains.kotlin.gradle.tasks.USING_JVM_INCREMENTAL_COMPILATION_MESSAGE
import org.jetbrains.kotlin.gradle.util.*
import org.junit.Test
@@ -259,24 +258,6 @@ class KotlinGradleIT : BaseGradleIT() {
}
}
@Test
fun testWipeClassesDirectoryBetweenBuilds() {
val project = Project("kotlinJavaProject", GradleVersionRequired.Exact("3.5"))
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()
assertTasksUpToDate(":compileKotlin")
}
}
@Test
fun testMoveClassToOtherModule() {
val project = Project("moveClassToOtherModule")
@@ -317,7 +298,7 @@ class KotlinGradleIT : BaseGradleIT() {
@Test
fun testKotlinBuiltins() {
val project = Project("kotlinBuiltins", GradleVersionRequired.AtLeast("4.0"))
val project = Project("kotlinBuiltins")
project.build("build") {
assertSuccessful()
@@ -361,42 +342,6 @@ class KotlinGradleIT : BaseGradleIT() {
}
}
@Test
fun testChangeDestinationDir() {
val project = Project("kotlinProject", GradleVersionRequired.Exact("3.5"))
project.setupWorkingDir()
val fileToRemove = File(project.projectDir, "src/main/kotlin/removeMe.kt")
fileToRemove.writeText("val x = 1")
val classFilePath = "build/classes/main/RemoveMeKt.class"
project.build("build") {
assertSuccessful()
assertFileExists(classFilePath)
}
// Check that after the change the build succeeds and no stale classes remain in the java classes dir
File(project.projectDir, "build.gradle").modify {
"$it\n\ncompileKotlin.destinationDir = file(\"\${project.buildDir}/compileKotlin\")"
}
fileToRemove.delete()
project.build("build") {
assertSuccessful()
assertNoSuchFile(classFilePath)
// Check that the fallback to non-incremental copying was chosen
assertContains("Non-incremental copying files")
}
// Check that the classes are copied incrementally under normal conditions
fileToRemove.writeText("val x = 1")
project.build("build") {
assertSuccessful()
assertFileExists(classFilePath)
assertNotContains("Non-incremental copying files")
}
}
@Test
fun testDowngradeTo106() {
val project = Project("kotlinProject")
@@ -515,7 +460,7 @@ class KotlinGradleIT : BaseGradleIT() {
@Test
fun testSeparateOutputGradle40() {
val project = Project("kotlinJavaProject", GradleVersionRequired.AtLeast("4.0"))
val project = Project("kotlinJavaProject")
project.build("compileDeployKotlin", "assemble") {
assertSuccessful()
@@ -529,10 +474,6 @@ class KotlinGradleIT : BaseGradleIT() {
// Check that the Java output is intact:
assertFileExists("build/classes/java/main/demo/Greeter.class")
// Check that the sync output task is not used with Gradle 4.0+ and there's no old Kotlin output layout
assertNotContains(":copyMainKotlinClasses")
assertNoSuchFile("build/kotlin-classes")
}
}
@@ -574,80 +515,6 @@ class KotlinGradleIT : BaseGradleIT() {
}
}
@Test
fun testDisableSeparateClassesDirs() {
fun CompiledProject.check(
copyClassesToJavaOutput: Boolean?,
expectBuildCacheWarning: Boolean,
expectGradleLowVersionWarning: Boolean
) {
val separateDirPath = kotlinClassesDir() + "demo/KotlinGreetingJoiner.class"
val singleDirPath = javaClassesDir() + "demo/KotlinGreetingJoiner.class"
assertSuccessful()
when (copyClassesToJavaOutput) {
true -> {
assertNoSuchFile(separateDirPath)
assertFileExists(singleDirPath)
}
false -> {
assertFileExists(separateDirPath)
assertNoSuchFile(singleDirPath)
}
}
if (expectBuildCacheWarning)
assertContains(CopyClassesToJavaOutputStatus.buildCacheWarningMessage)
else
assertNotContains(CopyClassesToJavaOutputStatus.buildCacheWarningMessage)
if (expectGradleLowVersionWarning)
assertContains(CopyClassesToJavaOutputStatus.gradleVersionTooLowWarningMessage)
else
assertNotContains(CopyClassesToJavaOutputStatus.gradleVersionTooLowWarningMessage)
}
Project("simpleProject", GradleVersionRequired.Exact("4.0")).apply {
build("build") {
check(
copyClassesToJavaOutput = false,
expectBuildCacheWarning = false,
expectGradleLowVersionWarning = false
)
}
File(projectDir, "build.gradle").appendText("\nkotlin.copyClassesToJavaOutput = true")
build("clean", "build") {
check(
copyClassesToJavaOutput = true,
expectBuildCacheWarning = false,
expectGradleLowVersionWarning = false
)
}
build("clean", "build", options = defaultBuildOptions().copy(withBuildCache = true)) {
check(
copyClassesToJavaOutput = true,
expectBuildCacheWarning = true,
expectGradleLowVersionWarning = false
)
}
projectDir.deleteRecursively()
}
Project("simpleProject", GradleVersionRequired.Exact("3.4")).apply {
setupWorkingDir()
File(projectDir, "build.gradle").appendText("\nkotlin.copyClassesToJavaOutput = true")
build("build") {
check(
copyClassesToJavaOutput = null,
expectBuildCacheWarning = false,
expectGradleLowVersionWarning = true
)
}
}
}
@Test
fun testSrcDirTaskDependency() {
Project("simpleProject", GradleVersionRequired.AtLeast("4.1")).apply {
@@ -29,7 +29,7 @@ class MultiplatformGradleIT : BaseGradleIT() {
@Test
fun testMultiplatformCompile() {
val project = Project("multiplatformProject", GradleVersionRequired.AtLeast("4.0"))
val project = Project("multiplatformProject")
project.build("build") {
assertSuccessful()
@@ -89,7 +89,7 @@ class MultiplatformGradleIT : BaseGradleIT() {
@Test
fun testSubprojectWithAnotherClassLoader() {
with(Project("multiplatformProject", GradleVersionRequired.AtLeast("4.0"))) {
with(Project("multiplatformProject")) {
setupWorkingDir()
// Make sure there is a plugin applied with the plugins DSL, so that Gradle loads the
@@ -137,12 +137,7 @@ class UpToDateIT : BaseGradleIT() {
lateinit var helloWorldKtClass: File
override fun mutateProject(project: Project) = with(project) {
val kotlinOutputPath =
if (testGradleVersionAtLeast("4.0"))
project.classesDir()
else
// Before 4.0, we should delete the classes from the temporary dir to make compileKotlin rerun:
"build/kotlin-classes/main/"
val kotlinOutputPath = project.classesDir()
helloWorldKtClass = File(projectDir, kotlinOutputPath + "demo/KotlinGreetingJoiner.class")
Assume.assumeTrue(helloWorldKtClass.exists())
@@ -6,16 +6,12 @@
package org.jetbrains.kotlin.gradle.model
import org.jetbrains.kotlin.gradle.BaseGradleIT
import org.jetbrains.kotlin.gradle.GradleVersionRequired
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
class AllOpenModelIT : BaseGradleIT() {
override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.AtLeast("4.0")
@Test
fun testAllOpenSimple() {
val project = Project("allOpenSimple")
@@ -6,16 +6,11 @@
package org.jetbrains.kotlin.gradle.model
import org.jetbrains.kotlin.gradle.BaseGradleIT
import org.jetbrains.kotlin.gradle.GradleVersionRequired
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
class KaptModelIT : BaseGradleIT() {
override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.AtLeast("4.0")
@Test
fun testKaptSimple() {
val project = Project("simple", directoryPrefix = "kapt2")
@@ -14,9 +14,6 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue
class NoArgModelIT : BaseGradleIT() {
override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.AtLeast("4.0")
@Test
fun testNoArgKt18668() {
val project = Project("noArgKt18668")
@@ -6,16 +6,12 @@
package org.jetbrains.kotlin.gradle.model
import org.jetbrains.kotlin.gradle.BaseGradleIT
import org.jetbrains.kotlin.gradle.GradleVersionRequired
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
class SamWithReceiverModelIT : BaseGradleIT() {
override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.AtLeast("4.0")
@Test
fun testSamWithReceiverSimple() {
val project = Project("samWithReceiverSimple")
@@ -51,13 +51,7 @@ open class KotlinSingleJavaTargetExtension : KotlinProjectExtension() {
internal lateinit var target: KotlinWithJavaTarget<*>
}
open class KotlinJvmProjectExtension : KotlinSingleJavaTargetExtension() {
/**
* With Gradle 4.0+, disables the separate output directory for Kotlin, falling back to sharing the deprecated
* single classes directory per source set. With Gradle < 4.0, has no effect.
* */
var copyClassesToJavaOutput = false
}
open class KotlinJvmProjectExtension : KotlinSingleJavaTargetExtension()
open class ExperimentalExtension {
var coroutines: Coroutines? = null
@@ -379,9 +379,8 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
kotlinCompilation?.run {
output.apply {
if (tryAddClassesDir { project.files(classesOutputDir).builtBy(kaptTask) }) {
kotlinCompile.attachClassesDir { classesOutputDir }
}
addClassesDir { project.files(classesOutputDir).builtBy(kaptTask) }
kotlinCompile.attachClassesDir { classesOutputDir }
}
}
@@ -1,54 +0,0 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.logging.kotlinWarn
import org.jetbrains.kotlin.gradle.utils.ParsedGradleVersion
import java.util.*
object CopyClassesToJavaOutputStatus {
fun isEnabled(project: Project): Boolean {
val kotlinJvmExt = project.kotlinJvmExt ?: return false
if (kotlinJvmExt.copyClassesToJavaOutput) {
if (isGradleVersionTooLow(project)) {
if (warningReportedForProject.add(project)) {
project.logger.kotlinWarn(gradleVersionTooLowWarningMessage)
}
} else {
addCacheWarningToJavaTasks(project)
}
return true
}
return false
}
private val warningReportedForProject = Collections.newSetFromMap<Project>(WeakHashMap())
private fun isGradleVersionTooLow(project: Project): Boolean {
return (ParsedGradleVersion.parse(project.gradle.gradleVersion) ?: return false) < ParsedGradleVersion(4, 0)
}
private fun addCacheWarningToJavaTasks(project: Project) {
project.tasks.withType(JavaCompile::class.java).all {
val warningCacheIfSpec = Spec<Task> {
if (warningReportedForProject.add(project)) {
project.logger.kotlinWarn(buildCacheWarningMessage)
}
return@Spec true
}
it.outputs.javaClass.getMethod("cacheIf", Spec::class.java).invoke(it.outputs, warningCacheIfSpec)
}
}
const val buildCacheWarningMessage = "The 'kotlin.copyClassesToJavaOutput' option should not be used with Gradle " +
"build cache"
const val gradleVersionTooLowWarningMessage = "The 'kotlin.copyClassesToJavaOutput' option has no effect when " +
"used with Gradle < 4.0"
private val Project.kotlinJvmExt: KotlinJvmProjectExtension?
get() = extensions.findByType(KotlinJvmProjectExtension::class.java)
}
@@ -59,29 +59,21 @@ internal abstract class KotlinSourceSetProcessor<T : AbstractKotlinCompile<*>>(
protected abstract fun doTargetSpecificProcessing()
protected val logger = Logging.getLogger(this.javaClass)!!
protected val isSeparateClassesDirSupported: Boolean by lazy {
!CopyClassesToJavaOutputStatus.isEnabled(project) && isGradleVersionAtLeast(4, 0)
}
protected val sourceSetName: String = kotlinCompilation.compilationName
protected val kotlinTask: TaskHolder<out T> = registerKotlinCompileTask()
protected val javaSourceSet: SourceSet? = (kotlinCompilation as? KotlinWithJavaCompilation<*>)?.javaSourceSet
protected open val defaultKotlinDestinationDir: File
private val defaultKotlinDestinationDir: File
get() {
return if (isSeparateClassesDirSupported) {
val kotlinExt = project.kotlinExtension
val targetSubDirectory =
if (kotlinExt is KotlinSingleJavaTargetExtension)
"" // In single-target projects, don't add the target name part to this path
else
kotlinCompilation.target.disambiguationClassifier?.let { "$it/" }.orEmpty()
File(project.buildDir, "classes/kotlin/$targetSubDirectory${kotlinCompilation.compilationName}")
} else {
kotlinCompilation.output.classesDirs.singleFile
}
val kotlinExt = project.kotlinExtension
val targetSubDirectory =
if (kotlinExt is KotlinSingleJavaTargetExtension)
"" // In single-target projects, don't add the target name part to this path
else
kotlinCompilation.target.disambiguationClassifier?.let { "$it/" }.orEmpty()
return File(project.buildDir, "classes/kotlin/$targetSubDirectory${kotlinCompilation.compilationName}")
}
private fun registerKotlinCompileTask(): TaskHolder<out T> {
@@ -91,7 +83,7 @@ internal abstract class KotlinSourceSetProcessor<T : AbstractKotlinCompile<*>>(
it.description = taskDescription
it.mapClasspath { kotlinCompilation.compileDependencyFiles }
it.setDestinationDir { defaultKotlinDestinationDir }
kotlinCompilation.output.tryAddClassesDir { project.files(kotlinTask.doGetTask().destinationDir).builtBy(kotlinTask.doGetTask()) }
kotlinCompilation.output.addClassesDir { project.files(kotlinTask.doGetTask().destinationDir).builtBy(kotlinTask.doGetTask()) }
}
return kotlinCompile
}
@@ -171,11 +163,6 @@ internal class Kotlin2JvmSourceSetProcessor(
) : KotlinSourceSetProcessor<KotlinCompile>(
project, tasksProvider, "Compiles the $kotlinCompilation.", kotlinCompilation
) {
override val defaultKotlinDestinationDir: File
get() = if (!isSeparateClassesDirSupported)
File(project.buildDir, "kotlin-classes/$sourceSetName") else
super.defaultKotlinDestinationDir
override fun doRegisterTask(project: Project, taskName: String, configureAction: (KotlinCompile)->(Unit)): TaskHolder<out KotlinCompile> =
tasksProvider.registerKotlinJVMTask(project, taskName, kotlinCompilation, configureAction)
@@ -201,19 +188,8 @@ internal class Kotlin2JvmSourceSetProcessor(
javaTask?.let { configureJavaTask(kotlinTaskInstance, it, logger) }
var syncOutputTask: SyncOutputTask? = null
if (!isSeparateClassesDirSupported && javaTask != null) {
syncOutputTask = registerSyncOutputTask(project, kotlinTaskInstance, javaTask, sourceSetName)
}
if (project.pluginManager.hasPlugin("java-library") && sourceSetName == SourceSet.MAIN_SOURCE_SET_NAME) {
val (classesProviderTask, classesDirectory) = when {
isSeparateClassesDirSupported -> kotlinTaskInstance.let { it to it.destinationDir }
else -> syncOutputTask!!.let { it to it.javaOutputDir }
}
registerKotlinOutputForJavaLibrary(classesDirectory, classesProviderTask)
registerKotlinOutputForJavaLibrary(kotlinTaskInstance.destinationDir, kotlinTaskInstance)
}
}
}
@@ -249,15 +225,9 @@ internal class Kotlin2JvmSourceSetProcessor(
}
}
internal fun KotlinCompilationOutput.tryAddClassesDir(
classesDirProvider: () -> FileCollection
): Boolean =
if (isGradleVersionAtLeast(4, 0)) {
classesDirs.from(Callable { classesDirProvider() })
true
} else {
false
}
internal fun KotlinCompilationOutput.addClassesDir(classesDirProvider: () -> FileCollection) {
classesDirs.from(Callable { classesDirProvider() })
}
internal class Kotlin2JsSourceSetProcessor(
project: Project,
@@ -305,10 +275,6 @@ internal class Kotlin2JsSourceSetProcessor(
kotlinTaskInstance.destinationDir = outputDir
if (!isSeparateClassesDirSupported && kotlinCompilation is KotlinWithJavaCompilation<*>) {
kotlinCompilation.javaSourceSet.output.setClassesDirCompatible(kotlinTaskInstance.destinationDir)
}
appliedPlugins
.flatMap { it.getSubpluginKotlinTasks(project, kotlinTaskInstance) }
.forEach { task -> kotlinCompilation.allKotlinSourceSets.forEach { sourceSet -> task.source(sourceSet.kotlin) } }
@@ -621,6 +587,8 @@ internal open class KotlinAndroidPlugin(
) : Plugin<Project> {
override fun apply(project: Project) {
checkGradleCompatibility()
val androidTarget = KotlinAndroidTarget("", project)
applyToTarget(kotlinPluginVersion, androidTarget)
registry.register(KotlinModelBuilder(kotlinPluginVersion, androidTarget))
@@ -873,38 +841,10 @@ internal fun configureJavaTask(kotlinTask: KotlinCompile, javaTask: AbstractComp
* ex. it adds some support libraries jars after execution of prepareComAndroidSupportSupportV42311Library task,
* so it's only safe to modify javaTask.classpath right before its usage
*/
// todo: remove?
javaTask.appendClasspathDynamically(kotlinTask.destinationDir!!)
}
internal fun syncOutputTaskName(variantName: String) = "copy${variantName.capitalize()}KotlinClasses"
internal fun registerSyncOutputTask(
project: Project,
kotlinCompile: KotlinCompile,
javaTask: AbstractCompile,
variantName: String
): SyncOutputTask {
val kotlinDir = kotlinCompile.destinationDir
val javaDir = javaTask.destinationDir
val taskName = syncOutputTaskName(variantName)
// registerTask(project: Project, name: String, type: Class<T>, disableLazy: Boolean, body: (T)->(Unit)) : TaskHolder<T> {
val syncTask = registerTask(project, taskName, SyncOutputTask::class.java) {
it.kotlinOutputDir = kotlinDir
it.javaOutputDir = javaDir
it.kotlinTask = kotlinCompile
it.kaptClassesDir = getKaptGeneratedClassesDir(project, variantName)
// copying should be executed after a latter task
javaTask.finalizedByIfNotFailed(it)
}
kotlinCompile.javaOutputDir = javaDir
project.logger.kotlinDebug { "Created task ${syncTask.doGetTask().path} to copy kotlin classes from $kotlinDir to $javaDir" }
return syncTask.doGetTask()
}
private fun ifKaptEnabled(project: Project, block: () -> Unit) {
var triggered = false
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSetFactory
import org.jetbrains.kotlin.gradle.tasks.KOTLIN_COMPILER_EMBEDDABLE
import org.jetbrains.kotlin.gradle.tasks.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
import java.io.FileNotFoundException
import java.util.*
import javax.inject.Inject
@@ -49,6 +50,8 @@ abstract class KotlinBasePluginWrapper(
DefaultKotlinSourceSetFactory(project, fileResolver)
override fun apply(project: Project) {
checkGradleCompatibility()
project.configurations.maybeCreate(COMPILER_CLASSPATH_CONFIGURATION_NAME).defaultDependencies {
it.add(project.dependencies.create("$KOTLIN_MODULE_GROUP:$KOTLIN_COMPILER_EMBEDDABLE:$kotlinPluginVersion"))
}
@@ -35,7 +35,6 @@ import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile
import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.io.File
import java.util.*
@@ -517,7 +516,7 @@ open class KotlinNativeTargetConfigurator(
destinationDir = klibOutputDirectory(compilation)
addCompilerPlugins()
compilation.output.tryAddClassesDir {
compilation.output.addClassesDir {
project.files(this.outputFile).builtBy(this)
}
}
@@ -777,14 +776,7 @@ open class KotlinNativeTargetConfigurator(
}
internal fun Project.usageByName(usageName: String): Usage =
if (isGradleVersionAtLeast(4, 0)) {
// `project.objects` is an API introduced in Gradle 4.0
project.objects.named(Usage::class.java, usageName)
} else {
val usagesClass = Class.forName("org.gradle.api.internal.attributes.Usages")
val usagesMethod = usagesClass.getMethod("usage", String::class.java)
usagesMethod(null, usageName) as Usage
}
project.objects.named(Usage::class.java, usageName)
fun Configuration.usesPlatformOf(target: KotlinTarget): Configuration {
attributes.attribute(KotlinPlatformType.attribute, target.platformType)
@@ -63,14 +63,7 @@ internal class LegacyAndroidAndroidProjectHandler(kotlinConfigurationTools: Kotl
javaTask.classpath + project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt))
}
getTestedVariantData(variantData)?.let { testedVariantData ->
// Android Gradle plugin bypasses the Gradle finalizedBy for its tasks in some cases, and
// the Kotlin classes may not be copied for the tested variant. Make sure they are.
kotlinTask.dependsOn(syncOutputTaskName(getVariantName(testedVariantData)))
}
configureJavaTask(kotlinTask, javaTask, logger)
registerSyncOutputTask(project, kotlinTask, javaTask, getVariantName(variantData))
// In lib modules, the androidTest variants get the classes jar in their classpath instead of the Java
// destination dir. Attach the JAR to be consumed as friend path:
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
object ProjectLocalConfigurations {
val ATTRIBUTE: Attribute<String> = Attribute.of("org.jetbrains.kotlin.localToProject", String::class.java)
@@ -18,10 +17,8 @@ object ProjectLocalConfigurations {
fun setupAttributesMatchingStrategy(attributesSchema: AttributesSchema) = with(attributesSchema) {
attribute(ATTRIBUTE) {
if (gradleVersionSupportsAttributeRules) {
it.compatibilityRules.add(ProjectLocalCompatibility::class.java)
it.disambiguationRules.add(ProjectLocalDisambiguation::class.java)
}
it.compatibilityRules.add(ProjectLocalCompatibility::class.java)
it.disambiguationRules.add(ProjectLocalDisambiguation::class.java)
}
}
@@ -41,23 +38,17 @@ object ProjectLocalConfigurations {
}
internal fun Configuration.setupAsLocalTargetSpecificConfigurationIfSupported(target: KotlinTarget) {
if (gradleVersionSupportsAttributeRules &&
// don't setup in old MPP common modules, as their output configurations with KotlinPlatformType attribute would
// fail to resolve as transitive dependencies of the platform modules, just as we don't mark their
// `api/RuntimeElements` with the KotlinPlatformType
(target !is KotlinWithJavaTarget<*> || target.platformType != KotlinPlatformType.common)
) {
// don't setup in old MPP common modules, as their output configurations with KotlinPlatformType attribute would
// fail to resolve as transitive dependencies of the platform modules, just as we don't mark their
// `api/RuntimeElements` with the KotlinPlatformType
if ((target !is KotlinWithJavaTarget<*> || target.platformType != KotlinPlatformType.common)) {
usesPlatformOf(target)
attributes.attribute(ProjectLocalConfigurations.ATTRIBUTE, ProjectLocalConfigurations.LOCAL_TO_PROJECT_PREFIX + target.project.path)
}
}
internal fun Configuration.setupAsPublicConfigurationIfSupported(target: KotlinTarget) {
if (gradleVersionSupportsAttributeRules &&
(target !is KotlinWithJavaTarget<*> || target.platformType != KotlinPlatformType.common)
) {
if ((target !is KotlinWithJavaTarget<*> || target.platformType != KotlinPlatformType.common)) {
attributes.attribute(ProjectLocalConfigurations.ATTRIBUTE, ProjectLocalConfigurations.PUBLIC_VALUE)
}
}
internal val gradleVersionSupportsAttributeRules = isGradleVersionAtLeast(4, 0)
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.presetName
@@ -59,6 +60,8 @@ class KotlinMultiplatformPlugin(
}
override fun apply(project: Project) {
checkGradleCompatibility()
project.plugins.apply(JavaBasePlugin::class.java)
SingleWarningPerBuild.show(project, "Kotlin Multiplatform Projects are an experimental feature.")
@@ -97,11 +97,9 @@ object KotlinUsages {
}
internal fun setupAttributesMatchingStrategy(attributesSchema: AttributesSchema) {
if (isGradleVersionAtLeast(4, 0)) {
attributesSchema.attribute(Usage.USAGE_ATTRIBUTE) { strategy ->
strategy.compatibilityRules.add(KotlinJavaRuntimeJarsCompatibility::class.java)
strategy.disambiguationRules.add(KotlinUsagesDisambiguation::class.java)
}
attributesSchema.attribute(Usage.USAGE_ATTRIBUTE) { strategy ->
strategy.compatibilityRules.add(KotlinJavaRuntimeJarsCompatibility::class.java)
strategy.disambiguationRules.add(KotlinUsagesDisambiguation::class.java)
}
}
}
@@ -9,7 +9,6 @@ import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationOutput
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import java.io.File
import java.util.concurrent.Callable
@@ -48,12 +47,7 @@ class KotlinWithJavaCompilationOutput(
}
override val classesDirs: ConfigurableFileCollection =
if (isGradleVersionAtLeast(4, 0))
javaSourceSetOutput.classesDirs as ConfigurableFileCollection
else
// In Gradle < 4.0 `SourceSetOutput` does not have `classesDirs`
@Suppress("DEPRECATION")
compilation.target.project.files(Callable { javaSourceSetOutput.classesDir })
javaSourceSetOutput.classesDirs as ConfigurableFileCollection
override val allOutputs: FileCollection
get() = javaSourceSetOutput
@@ -10,8 +10,6 @@ import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.*
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.utils.isClassFile
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import java.io.File
internal open class InspectClassesForMultiModuleIC : DefaultTask() {
@@ -33,13 +31,8 @@ internal open class InspectClassesForMultiModuleIC : DefaultTask() {
val convention = project.convention.findPlugin(JavaPluginConvention::class.java)
val sourceSet = convention?.sourceSets?.findByName(sourceSetName) ?: return project.files()
return if (isGradleVersionAtLeast(4, 0)) {
val fileTrees = sourceSet.output.classesDirs.map { project.fileTree(it).include("**/*.class") }
project.files(fileTrees)
} else {
sourceSet.output.asFileTree.filter { it.isClassFile() }
}
val fileTrees = sourceSet.output.classesDirs.map { project.fileTree(it).include("**/*.class") }
return project.files(fileTrees)
}
@get:Input
@@ -1,216 +0,0 @@
/*
* 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.*
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.gradle.api.tasks.incremental.InputFileDetails
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.utils.isParentOf
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).
*/
internal open class SyncOutputTask : DefaultTask() {
@get:InputFiles
var kotlinOutputDir: File by Delegates.notNull()
@get:InputFiles
var kaptClassesDir: File by Delegates.notNull()
// Marked as input to make Gradle fall back to non-incremental build when it changes.
@get:Input
protected val classesDirs: List<File>
get() = listOf(kotlinOutputDir, kaptClassesDir).filter(File::exists)
@get:OutputDirectory
var javaOutputDir: File by Delegates.notNull()
@get:Internal
var kotlinTask: KotlinCompile by Delegates.notNull()
// OutputDirectory needed for task to be incremental
@get:OutputDirectory
val workingDir: File by lazy {
File(kotlinTask.taskBuildDirectory, "sync")
}
private val timestampsFile: File by lazy {
File(workingDir, TIMESTAMP_FILE_NAME)
}
private val timestamps: MutableMap<File, Long> by lazy {
readTimestamps(timestampsFile, javaOutputDir)
}
init {
outputs.upToDateWhen {
for ((file, ts) in timestamps) {
if (!file.exists()) {
logger.kotlinDebug { "$file does not exist" }
return@upToDateWhen false
}
if (file.lastModified() != ts) {
logger.kotlinDebug { "$file ts is different" }
return@upToDateWhen false
}
}
return@upToDateWhen true
}
}
@Suppress("unused")
@TaskAction
fun execute(inputs: IncrementalTaskInputs): Unit {
val sourceDirs = classesDirs.joinToString()
if (inputs.isIncremental) {
logger.kotlinDebug { "Incremental copying files from $sourceDirs to $javaOutputDir" }
inputs.outOfDate { processIncrementally(it) }
inputs.removed { processIncrementally(it) }
} else {
logger.kotlinDebug { "Non-incremental copying files from $sourceDirs to $javaOutputDir" }
processNonIncrementally()
}
saveTimestamps(timestampsFile, timestamps, javaOutputDir)
}
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()
for (dir in classesDirs) {
dir.walkTopDown().forEach {
copy(it, it.siblingInJavaDir(baseDir = dir))
}
}
}
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 fun File.siblingInJavaDir(baseDir: File? = null): File {
val base = baseDir ?: classesDirs.find { it.isParentOf(this, strict = true) }!!
return File(javaOutputDir, this.relativeTo(base).path)
}
}
private val TIMESTAMP_FILE_NAME = "kotlin-files-in-java-timestamps.bin"
private fun readTimestamps(tsFile: File, filesBaseDir: File): MutableMap<File, Long> {
val result = HashMap<File, Long>()
if (!tsFile.isFile) return result
ObjectInputStream(tsFile.inputStream()).use { input ->
val size = input.readInt()
repeat(size) {
val path = input.readUTF()
val timestamp = input.readLong()
result[File(filesBaseDir, path)] = timestamp
}
}
return result
}
private fun saveTimestamps(snapshotFile: File, timestamps: Map<File, Long>, filesBaseDir: File) {
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.relativeTo(filesBaseDir).path)
out.writeLong(timestamp)
}
}
}
@@ -16,10 +16,12 @@
package org.jetbrains.kotlin.gradle.utils
import org.gradle.api.GradleException
import org.gradle.api.Task
import org.gradle.api.tasks.SourceSetOutput
import org.gradle.api.tasks.TaskInputs
import org.gradle.api.tasks.TaskOutputs
import org.gradle.util.GradleVersion
internal val Task.inputsCompatible: TaskInputs get() = inputs
@@ -93,4 +95,14 @@ private val setClassesDirMethod by lazy {
internal fun SourceSetOutput.setClassesDirCompatible(dirPath: Any) {
setClassesDirMethod(this, dirPath)
}
internal fun checkGradleCompatibility(minSupportedVersion: GradleVersion = GradleVersion.version("4.0")) {
val currentVersion = GradleVersion.current()
if (currentVersion < minSupportedVersion) {
throw GradleException(
"Current version of Gradle $currentVersion is not compatible with Kotlin plugin. " +
"Please use Gradle $minSupportedVersion or newer or previous version of Kotlin plugin."
)
}
}