[Gradle] Split & migrate AbstractKotlinAndroidGradleTests to the new dsl
#KT-45748 In Progress
This commit is contained in:
committed by
Space Team
parent
8bbf98f24c
commit
fc088bb84a
-1137
File diff suppressed because it is too large
Load Diff
+202
@@ -450,6 +450,208 @@ class Kapt3AndroidIT : Kapt3BaseIT() {
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("kapt uses AGP annotation processor option providers as nested inputs")
|
||||
@GradleAndroidTest
|
||||
fun testKaptUsingApOptionProvidersAsNestedInputOutput(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion, kaptOptions = null),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
subProject("Android").buildGradle.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
class MyNested implements org.gradle.process.CommandLineArgumentProvider {
|
||||
private final File argsFile
|
||||
|
||||
MyNested(File argsFile) {
|
||||
this.argsFile = argsFile
|
||||
}
|
||||
|
||||
@InputFile
|
||||
File inputFile = null
|
||||
|
||||
@Override
|
||||
Iterable<String> asArguments() {
|
||||
// Read the arguments from a file, because changing them in a build script is treated as an
|
||||
// implementation change by Gradle:
|
||||
return [argsFile.text]
|
||||
}
|
||||
}
|
||||
|
||||
def nested = new MyNested(rootProject.file("args.txt"))
|
||||
nested.inputFile = file("${'$'}projectDir/in.txt")
|
||||
|
||||
android.applicationVariants.all {
|
||||
it.javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders.add(nested)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val inFile = subProject("Android").projectPath.resolve("in.txt")
|
||||
inFile.writeText("1234")
|
||||
val argsFile = projectPath.resolve("args.txt")
|
||||
argsFile.writeText("1234")
|
||||
|
||||
val kaptTasks = listOf(":Android:kaptFlavor1DebugKotlin")
|
||||
val javacTasks = listOf(":Android:compileFlavor1DebugJavaWithJavac")
|
||||
|
||||
val buildTasks = (kaptTasks + javacTasks).toTypedArray()
|
||||
|
||||
build(*buildTasks) {
|
||||
assertTasksExecuted(kaptTasks + javacTasks)
|
||||
}
|
||||
|
||||
inFile.appendText("5678")
|
||||
|
||||
build(*buildTasks) {
|
||||
assertTasksExecuted(kaptTasks)
|
||||
assertTasksUpToDate(javacTasks)
|
||||
}
|
||||
|
||||
// Changing only the annotation provider arguments should not trigger the tasks to run, as the arguments may be outputs,
|
||||
// internals or neither:
|
||||
argsFile.appendText("5678")
|
||||
|
||||
build(*buildTasks) {
|
||||
assertTasksUpToDate(javacTasks + kaptTasks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("agp annotation processor nested arguments are not evaluated at configuration time")
|
||||
@GradleAndroidTest
|
||||
fun testAgpNestedArgsNotEvaluatedDuringConfiguration(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
subProject("Android").buildGradle.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
class MyNested implements org.gradle.process.CommandLineArgumentProvider {
|
||||
@Override
|
||||
Iterable<String> asArguments() {
|
||||
throw new RuntimeException("This should not be invoked during configuration.")
|
||||
}
|
||||
}
|
||||
|
||||
def nested = new MyNested()
|
||||
|
||||
android.applicationVariants.all {
|
||||
it.javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders.add(nested)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
build(":Android:kaptFlavor1DebugKotlin", "--dry-run")
|
||||
|
||||
build(
|
||||
":Android:kaptFlavor1DebugKotlin", "--dry-run",
|
||||
buildOptions = buildOptions.copy(kaptOptions = BuildOptions.KaptOptions(verbose = false))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("incremental compilation works with dagger")
|
||||
@GradleAndroidTest
|
||||
fun testAndroidDaggerIC(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidDaggerProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("assembleDebug")
|
||||
|
||||
val androidModuleKt = subProject("app").javaSourcesDir().resolve("com/example/dagger/kotlin/AndroidModule.kt")
|
||||
androidModuleKt.modify {
|
||||
it.replace(
|
||||
"fun provideApplicationContext(): Context {",
|
||||
"fun provideApplicationContext(): Context? {"
|
||||
)
|
||||
}
|
||||
|
||||
build(":app:assembleDebug", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
|
||||
assertTasksExecuted(
|
||||
":app:kaptGenerateStubsDebugKotlin",
|
||||
":app:kaptDebugKotlin",
|
||||
":app:compileDebugKotlin",
|
||||
":app:compileDebugJavaWithJavac"
|
||||
)
|
||||
|
||||
// Output is combined with previous build, but we are only interested in the compilation
|
||||
// from second build to avoid false positive test failure
|
||||
val filteredOutput = output
|
||||
.lineSequence()
|
||||
.filter { it.contains("[KOTLIN] compile iteration:") }
|
||||
.drop(1)
|
||||
.joinToString(separator = "/n")
|
||||
assertCompiledKotlinSources(listOf(androidModuleKt).relativizeTo(projectPath), output = filteredOutput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("incremental compilation with android and kapt")
|
||||
@GradleAndroidTest
|
||||
fun testAndroidWithKaptIncremental(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidIncrementalMultiModule",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
val appProject = subProject("app")
|
||||
appProject.buildGradle.modify {
|
||||
//language=Gradle
|
||||
"""
|
||||
apply plugin: 'org.jetbrains.kotlin.kapt'
|
||||
$it
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
build(":app:testDebugUnitTest")
|
||||
|
||||
appProject.kotlinSourcesDir().resolve("com/example/KotlinActivity.kt").appendText(
|
||||
//language=kt
|
||||
"""
|
||||
{
|
||||
private val x = 1
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
build(":app:testDebugUnitTest") {
|
||||
assertOutputDoesNotContain(NON_INCREMENTAL_COMPILATION_WILL_BE_PERFORMED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestProject.setupDataBinding() {
|
||||
buildGradle.appendText(
|
||||
//language=Gradle
|
||||
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.android
|
||||
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.jetbrains.kotlin.gradle.util.checkedReplace
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.appendText
|
||||
import kotlin.io.path.readLines
|
||||
import kotlin.io.path.writeText
|
||||
import kotlin.test.assertContains
|
||||
|
||||
@DisplayName("base kotlin-android tests")
|
||||
@AndroidGradlePluginTests
|
||||
class KotlinAndroidIT : KGPBaseTest() {
|
||||
|
||||
@DisplayName("android project compilation smoke test")
|
||||
@GradleAndroidTest
|
||||
fun testSimpleCompile(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
fun BuildResult.assertKotlinGradleBuildServicesAreInitialized() {
|
||||
assertOutputContainsExactTimes("Initialized KotlinGradleBuildServices", expectedRepetitionTimes = 1)
|
||||
assertOutputContainsExactTimes("Disposed KotlinGradleBuildServices", expectedRepetitionTimes = 1)
|
||||
}
|
||||
|
||||
project(
|
||||
"AndroidProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion, logLevel = LogLevel.DEBUG),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
val modules = listOf("Android", "Lib")
|
||||
val flavors = listOf("Flavor1", "Flavor2")
|
||||
val buildTypes = listOf("Debug")
|
||||
|
||||
val expectedTasks = mutableListOf<String>()
|
||||
for (module in modules) {
|
||||
for (flavor in flavors) {
|
||||
for (buildType in buildTypes) {
|
||||
expectedTasks.add(":$module:compile$flavor${buildType}Kotlin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
build("assembleDebug", "test") {
|
||||
val pattern = ":Test:compile[\\w\\d]+Kotlin"
|
||||
expectedTasks.addAll(findTasksByPattern(pattern.toRegex()))
|
||||
assertTasksExecuted(expectedTasks)
|
||||
assertOutputContains("InternalDummyTest PASSED")
|
||||
assertKotlinGradleBuildServicesAreInitialized()
|
||||
}
|
||||
|
||||
// Run the a build second time, assert everything is up-to-date
|
||||
build("assembleDebug") {
|
||||
assertTasksUpToDate(expectedTasks)
|
||||
assertKotlinGradleBuildServicesAreInitialized()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KT-16897: the `assembleAndroidTest` task builds fine without the `build` task")
|
||||
@GradleAndroidTest
|
||||
fun testAssembleAndroidTest(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("assembleAndroidTest")
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KT-12303: compilation works fine with icepick")
|
||||
@GradleAndroidTest
|
||||
fun testAndroidIcepickProject(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidIcepickProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("assembleDebug")
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("compilation works fine with parcelize")
|
||||
@GradleAndroidTest
|
||||
fun testParcelize(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidParcelizeProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("assembleDebug")
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KT-46626: kotlin-android produces clear error message when no android plugin is applied")
|
||||
@GradleAndroidTest
|
||||
fun shouldAllowToApplyPluginWhenAndroidPluginIsMissing(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"simpleProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
fun replaceKotlinJvmByKotlinAndroid(specifyPluginVersion: Boolean): (String) -> CharSequence = { line ->
|
||||
if (line.contains("id \"org.jetbrains.kotlin.jvm\"")) {
|
||||
" id \"org.jetbrains.kotlin.android\"" + if (specifyPluginVersion) " version \"${'$'}kotlin_version\"" else ""
|
||||
} else {
|
||||
line
|
||||
}
|
||||
}
|
||||
|
||||
buildGradle.modify {
|
||||
it.lines().joinToString(
|
||||
separator = "\n",
|
||||
transform = replaceKotlinJvmByKotlinAndroid(false)
|
||||
)
|
||||
}
|
||||
settingsGradle.modify {
|
||||
it.lines().joinToString(
|
||||
separator = "\n",
|
||||
transform = replaceKotlinJvmByKotlinAndroid(true)
|
||||
)
|
||||
}
|
||||
|
||||
buildAndFail("tasks") {
|
||||
assertOutputContains("'kotlin-android' plugin requires one of the Android Gradle plugins.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KT-49483: lint dependencies are resolved properly")
|
||||
@GradleAndroidTest
|
||||
fun testLintDependencyResolutionKt49483(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
buildGradle.modify {
|
||||
it.replace(
|
||||
"plugins {",
|
||||
"""
|
||||
plugins {
|
||||
id("com.android.lint")
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
subProject("Lib").buildGradle.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
android {
|
||||
lintOptions.checkDependencies = true
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(":java-lib"))
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
settingsGradle.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
include("java-lib")
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
subProject("java-lib").buildGradle.apply {
|
||||
Files.createDirectories(parent)
|
||||
writeText(
|
||||
//language=Gradle
|
||||
"""
|
||||
plugins {
|
||||
id("java-library")
|
||||
id("com.android.lint")
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
// Gradle 6 + AGP 4 produce a deprecation warning on parallel executions about resolving a configuration from another project
|
||||
build(":Lib:lintFlavor1Debug", buildOptions = buildOptions.copy(parallel = gradleVersion >= GradleVersion.version("7.0"))) {
|
||||
assertOutputDoesNotContain("as an external dependency and not analyze it.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("publish with omitted stdlib version sets proper version")
|
||||
@GradleAndroidTest
|
||||
fun testOmittedStdlibVersion(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
subProject("Lib").buildGradle.modify {
|
||||
it.checkedReplace(
|
||||
"kotlin-stdlib:\$kotlin_version",
|
||||
"kotlin-stdlib"
|
||||
) +
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
defaultPublishConfig 'flavor1Debug'
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
flavorDebug(MavenPublication) {
|
||||
from components.flavor1Debug
|
||||
|
||||
group = 'com.example'
|
||||
artifactId = 'flavor1Debug'
|
||||
version = '1.0'
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url = "file://${'$'}buildDir/repo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
build(":Lib:assembleFlavor1Debug", ":Lib:publish") {
|
||||
assertTasksExecuted(":Lib:compileFlavor1DebugKotlin", ":Lib:publishFlavorDebugPublicationToMavenRepository")
|
||||
val pomLines = subProject("Lib").projectPath.resolve("build/repo/com/example/flavor1Debug/1.0/flavor1Debug-1.0.pom").readLines()
|
||||
val stdlibVersionLineNumber = pomLines.indexOfFirst { "<artifactId>kotlin-stdlib</artifactId>" in it } + 1
|
||||
val versionLine = pomLines[stdlibVersionLineNumber]
|
||||
assertContains(versionLine, "<version>${buildOptions.kotlinVersion}</version>")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.android
|
||||
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.jetbrains.kotlin.gradle.util.getFileByName
|
||||
import org.jetbrains.kotlin.gradle.util.getFilesByNames
|
||||
import org.jetbrains.kotlin.gradle.util.modify
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import kotlin.io.path.appendText
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
@DisplayName("kotlin-android incremental compilation tests")
|
||||
@AndroidGradlePluginTests
|
||||
class KotlinAndroidIncrementalIT : KGPBaseTest() {
|
||||
@DisplayName("incremental compilation works with single-module android projects")
|
||||
@GradleAndroidTest
|
||||
fun testIncrementalCompilation(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidIncrementalSingleModuleProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("assembleDebug")
|
||||
|
||||
val modifiedSrc = subProject("app").kotlinSourcesDir().resolve("com/example/getSomething.kt")
|
||||
modifiedSrc.writeText(
|
||||
//language=kt
|
||||
"""
|
||||
package com.example
|
||||
|
||||
fun getSomething() = 10
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
build("assembleDebug", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
|
||||
val affectedKotlinFiles = listOf(
|
||||
"app/src/main/kotlin/com/example/KotlinActivity1.kt",
|
||||
"app/src/main/kotlin/com/example/getSomething.kt",
|
||||
)
|
||||
val affectedJavaFiles = listOf(
|
||||
"app/src/main/java/com/example/JavaActivity.java",
|
||||
)
|
||||
assertCompiledKotlinSources(affectedKotlinFiles.toPaths(), output)
|
||||
assertCompiledJavaSources(affectedJavaFiles.toPaths(), output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("incremental compilation works with multi-module android projects")
|
||||
@GradleAndroidTest
|
||||
fun testMultiModuleIC(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidIncrementalMultiModule",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("assembleDebug")
|
||||
|
||||
val libAndroidUtilKt = subProject("libAndroid").kotlinSourcesDir().resolve("com/example/libAndroidUtil.kt")
|
||||
libAndroidUtilKt.modify { it.replace("fun libAndroidUtil(): String", "fun libAndroidUtil(): CharSequence") }
|
||||
|
||||
val optionsWithDebug = buildOptions.copy(logLevel = LogLevel.DEBUG)
|
||||
|
||||
build("assembleDebug", buildOptions = optionsWithDebug) {
|
||||
val affectedSources = listOf(
|
||||
libAndroidUtilKt,
|
||||
subProject("app").kotlinSourcesDir().resolve("com/example/useLibAndroidUtil.kt")
|
||||
).relativizeTo(projectPath)
|
||||
assertCompiledKotlinSources(affectedSources, output)
|
||||
}
|
||||
|
||||
val libAndroidClassesOnlyUtilKt = subProject("libAndroidClassesOnly").kotlinSourcesDir().resolve("com/example/LibAndroidClassesOnlyUtil.kt")
|
||||
libAndroidClassesOnlyUtilKt.modify {
|
||||
it.replace(
|
||||
"fun libAndroidClassesOnlyUtil(): String",
|
||||
"fun libAndroidClassesOnlyUtil(): CharSequence"
|
||||
)
|
||||
}
|
||||
|
||||
build("assembleDebug", buildOptions = optionsWithDebug) {
|
||||
val affectedSources = listOf(
|
||||
libAndroidClassesOnlyUtilKt,
|
||||
subProject("app").kotlinSourcesDir().resolve("com/example/useLibAndroidClassesOnlyUtil.kt")
|
||||
).relativizeTo(projectPath)
|
||||
assertCompiledKotlinSources(affectedSources, output)
|
||||
}
|
||||
|
||||
val libJvmUtilKt = subProject("libJvmClassesOnly").kotlinSourcesDir().resolve("com/example/LibJvmUtil.kt")
|
||||
libJvmUtilKt.modify { it.replace("fun libJvmUtil(): String", "fun libJvmUtil(): CharSequence") }
|
||||
|
||||
build("assembleDebug", buildOptions = optionsWithDebug) {
|
||||
val affectedSources = listOf(
|
||||
libJvmUtilKt,
|
||||
subProject("app").kotlinSourcesDir().resolve("com/example/useLibJvmUtil.kt")
|
||||
).relativizeTo(projectPath)
|
||||
assertCompiledKotlinSources(affectedSources, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("incremental compilation does nothing without changes")
|
||||
@GradleAndroidTest
|
||||
fun testIncrementalBuildWithNoChanges(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidIncrementalSingleModuleProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
val expectedTasks = listOf(
|
||||
":app:compileDebugKotlin",
|
||||
":app:compileDebugJavaWithJavac"
|
||||
)
|
||||
|
||||
build("assembleDebug") {
|
||||
assertTasksExecuted(expectedTasks)
|
||||
}
|
||||
|
||||
build("assembleDebug") {
|
||||
assertTasksUpToDate(expectedTasks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KT-49066: incremental compilation with custom module name")
|
||||
@GradleAndroidTest
|
||||
fun testCustomModuleName(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidIncrementalMultiModule",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
subProject("libAndroid").buildGradle.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
android.kotlinOptions {
|
||||
moduleName = "custom_path"
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
build("assembleDebug") {
|
||||
assertFileInProjectExists("libAndroid/build/tmp/kotlin-classes/debug/META-INF/custom_path.kotlin_module")
|
||||
}
|
||||
|
||||
val libAndroidUtilKt = subProject("libAndroid").kotlinSourcesDir().resolve("com/example/libAndroidUtil.kt")
|
||||
libAndroidUtilKt.modify { it.replace("fun libAndroidUtil(): String", "fun libAndroidUtil(): CharSequence") }
|
||||
build("assembleDebug", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
|
||||
val affectedSources = listOf(
|
||||
libAndroidUtilKt,
|
||||
subProject("app").kotlinSourcesDir().resolve("com/example/useLibAndroidUtil.kt")
|
||||
).relativizeTo(projectPath)
|
||||
assertCompiledKotlinSources(affectedSources, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+706
@@ -0,0 +1,706 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.android
|
||||
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.jetbrains.kotlin.gradle.tooling.BuildKotlinToolingMetadataTask
|
||||
import org.jetbrains.kotlin.gradle.util.AGPVersion
|
||||
import org.jetbrains.kotlin.gradle.util.testResolveAllConfigurations
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.io.path.appendText
|
||||
import kotlin.io.path.extension
|
||||
import kotlin.io.path.readText
|
||||
import kotlin.io.path.writeText
|
||||
import kotlin.streams.toList
|
||||
import kotlin.test.*
|
||||
|
||||
@DisplayName("kotlin-android with mpp")
|
||||
@AndroidGradlePluginTests
|
||||
class KotlinAndroidMppIT : KGPBaseTest() {
|
||||
@DisplayName("KT-50736: whenEvaluated waits for AGP being applied later")
|
||||
@GradleAndroidTest
|
||||
fun testAfterEvaluateOrdering(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
subProject("Lib").buildGradle.writeText(
|
||||
//language=Gradle
|
||||
"""
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
google()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:${'$'}android_tools_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.multiplatform'
|
||||
}
|
||||
|
||||
class MyAction implements kotlin.jvm.functions.Function1<Project, Void> {
|
||||
Void invoke (Project p) {
|
||||
println("compilations: " + p.kotlin.targets.getByName("android").compilations.names)
|
||||
}
|
||||
}
|
||||
|
||||
org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginKt.whenEvaluated(project, new MyAction ())
|
||||
|
||||
apply plugin : "android-library"
|
||||
|
||||
android {
|
||||
compileSdkVersion 22
|
||||
}
|
||||
|
||||
kotlin { android("android") { } }
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
build("help") {
|
||||
val reportedCompilations = output.lines()
|
||||
.single { it.contains("compilations: ") }
|
||||
.substringAfter("compilations: ")
|
||||
.removeSurrounding("[", "]")
|
||||
.split(", ")
|
||||
.toSet()
|
||||
assertEquals(
|
||||
setOf("debug", "debugAndroidTest", "debugUnitTest", "release", "releaseUnitTest"),
|
||||
reportedCompilations
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KotlinToolingMetadataArtifact is bundled into apk")
|
||||
@GradleAndroidTest
|
||||
fun testKotlinToolingMetadataBundle(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"kotlinToolingMetadataAndroid",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("assembleDebug") {
|
||||
assertTasksAreNotInTaskGraph(":${BuildKotlinToolingMetadataTask.defaultTaskName}")
|
||||
|
||||
val debugApk = projectPath.resolve("build/outputs/apk/debug/project-debug.apk")
|
||||
assertFileExists(debugApk)
|
||||
ZipFile(debugApk.toFile()).use { zip ->
|
||||
assertNull(zip.getEntry("kotlin-tooling-metadata.json"), "Expected metadata *not* being packaged into debug apk")
|
||||
}
|
||||
}
|
||||
|
||||
build("assembleRelease") {
|
||||
assertTasksExecuted(":${BuildKotlinToolingMetadataTask.defaultTaskName}")
|
||||
val releaseApk = projectPath.resolve("build/outputs/apk/release/project-release-unsigned.apk")
|
||||
|
||||
assertFileExists(releaseApk)
|
||||
ZipFile(releaseApk.toFile()).use { zip ->
|
||||
assertNotNull(zip.getEntry("kotlin-tooling-metadata.json"), "Expected metadata being packaged into release apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("mpp source sets are registered in AGP")
|
||||
@GradleAndroidTest
|
||||
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_42)
|
||||
fun testAndroidMppSourceSets(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"new-mpp-android-source-sets",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
// AbstractReportTask#generate() task action was removed in Gradle 6.8+,
|
||||
// that SourceSetTask is using: https://github.com/gradle/gradle/commit/4dac91ab87ea33ee8689d2a62b691b119198e7c7
|
||||
// leading to the issue that ":sourceSets" task is always in 'UP-TO-DATE' state.
|
||||
// Skipping this check until the test will start using AGP 7.0-alpha03+
|
||||
// AGP 4.x is not compatible with Gradle 7.0, so just skip when the Gradle version is lower than 7.0
|
||||
if (gradleVersion >= GradleVersion.version("7.0")) {
|
||||
build("sourceSets") {
|
||||
fun assertOutputContainsOsIndependent(expectedString: String) {
|
||||
assertOutputContains(expectedString.replace("/", File.separator))
|
||||
}
|
||||
assertOutputContainsOsIndependent("Android resources: [lib/src/main/res, lib/src/androidMain/res]")
|
||||
assertOutputContainsOsIndependent("Assets: [lib/src/main/assets, lib/src/androidMain/assets]")
|
||||
assertOutputContainsOsIndependent("AIDL sources: [lib/src/main/aidl, lib/src/androidMain/aidl]")
|
||||
assertOutputContainsOsIndependent("RenderScript sources: [lib/src/main/rs, lib/src/androidMain/rs]")
|
||||
assertOutputContainsOsIndependent("JNI sources: [lib/src/main/jni, lib/src/androidMain/jni]")
|
||||
assertOutputContainsOsIndependent("JNI libraries: [lib/src/main/jniLibs, lib/src/androidMain/jniLibs]")
|
||||
assertOutputContainsOsIndependent("Java-style resources: [lib/src/main/resources, lib/src/androidMain/resources]")
|
||||
|
||||
assertOutputContainsOsIndependent("Android resources: [lib/src/androidTestDebug/res, lib/src/androidAndroidTestDebug/res]")
|
||||
assertOutputContainsOsIndependent("Assets: [lib/src/androidTestDebug/assets, lib/src/androidAndroidTestDebug/assets]")
|
||||
assertOutputContainsOsIndependent("AIDL sources: [lib/src/androidTestDebug/aidl, lib/src/androidAndroidTestDebug/aidl]")
|
||||
assertOutputContainsOsIndependent("RenderScript sources: [lib/src/androidTestDebug/rs, lib/src/androidAndroidTestDebug/rs]")
|
||||
assertOutputContainsOsIndependent("JNI sources: [lib/src/androidTestDebug/jni, lib/src/androidAndroidTestDebug/jni]")
|
||||
assertOutputContainsOsIndependent("JNI libraries: [lib/src/androidTestDebug/jniLibs, lib/src/androidAndroidTestDebug/jniLibs]")
|
||||
assertOutputContainsOsIndependent("Java-style resources: [lib/src/androidTestDebug/resources, lib/src/androidAndroidTestDebug/resources]")
|
||||
|
||||
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/paidBeta/resources, lib/src/androidPaidBeta/resources]")
|
||||
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/paidBetaDebug/resources, lib/src/androidPaidBetaDebug/resources]")
|
||||
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/paidBetaRelease/resources, lib/src/androidPaidBetaRelease/resources]")
|
||||
|
||||
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/freeBeta/resources, lib/src/androidFreeBeta/resources]")
|
||||
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/freeBetaDebug/resources, lib/src/androidFreeBetaDebug/resources]")
|
||||
assertOutputContainsOsIndependent("Java-style resources: [lib/betaSrc/freeBetaRelease/resources, lib/src/androidFreeBetaRelease/resources]")
|
||||
}
|
||||
}
|
||||
|
||||
buildAndFail("testFreeBetaDebug") {
|
||||
assertOutputContains("CommonTest > fail FAILED")
|
||||
assertOutputContains("TestKotlin > fail FAILED")
|
||||
assertOutputContains("AndroidTestKotlin > fail FAILED")
|
||||
assertOutputContains("TestJava > fail FAILED")
|
||||
}
|
||||
|
||||
build("assemble")
|
||||
|
||||
buildAndFail("connectedAndroidTest") {
|
||||
assertOutputContains("No connected devices!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("android app can depend on mpp lib")
|
||||
@GradleAndroidTest
|
||||
fun testAndroidWithNewMppApp(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"new-mpp-android",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("assemble", "compileDebugUnitTestJavaWithJavac", "printCompilerPluginOptions") {
|
||||
// KT-30784
|
||||
assertOutputDoesNotContain("API 'variant.getPackageLibrary()' is obsolete and has been replaced")
|
||||
|
||||
assertOutputContains("KT-29964 OK") // Output from lib/build.gradle
|
||||
|
||||
assertTasksExecuted(
|
||||
":lib:compileDebugKotlinAndroidLib",
|
||||
":lib:compileReleaseKotlinAndroidLib",
|
||||
":lib:compileKotlinJvmLib",
|
||||
":lib:compileKotlinJsLib",
|
||||
":lib:compileCommonMainKotlinMetadata",
|
||||
":app:compileDebugKotlinAndroidApp",
|
||||
":app:compileReleaseKotlinAndroidApp",
|
||||
":app:compileKotlinJvmApp",
|
||||
":app:compileKotlinJsApp",
|
||||
":app:compileCommonMainKotlinMetadata",
|
||||
":lib:compileDebugUnitTestJavaWithJavac",
|
||||
":app:compileDebugUnitTestJavaWithJavac"
|
||||
)
|
||||
|
||||
listOf("debug", "release").forEach { variant ->
|
||||
assertFileInProjectExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/ExpectedLibClass.class")
|
||||
assertFileInProjectExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/CommonLibClass.class")
|
||||
assertFileInProjectExists("lib/build/tmp/kotlin-classes/$variant/com/example/lib/AndroidLibClass.class")
|
||||
|
||||
assertFileInProjectExists("app/build/tmp/kotlin-classes/$variant/com/example/app/AKt.class")
|
||||
assertFileInProjectExists("app/build/tmp/kotlin-classes/$variant/com/example/app/KtUsageKt.class")
|
||||
}
|
||||
}
|
||||
|
||||
val groupDir = subProject("lib").projectPath.resolve("build/repo/com/example")
|
||||
build("publish") {
|
||||
assertDirectoryExists(groupDir.resolve("lib-jvmlib"))
|
||||
assertDirectoryExists(groupDir.resolve("lib-jslib"))
|
||||
assertDirectoryExists(groupDir.resolve("lib-androidlib"))
|
||||
assertDirectoryExists(groupDir.resolve("lib-androidlib-debug"))
|
||||
}
|
||||
groupDir.deleteRecursively()
|
||||
|
||||
// Choose a single variant to publish, check that it's there:
|
||||
subProject("lib").buildGradle.appendText("\nkotlin.android('androidLib').publishLibraryVariants = ['release']")
|
||||
build("publish") {
|
||||
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar"))
|
||||
assertFileInProjectNotExists("$groupDir/lib-androidlib-debug")
|
||||
}
|
||||
groupDir.deleteRecursively()
|
||||
|
||||
// Enable publishing for all Android variants:
|
||||
subProject("lib").buildGradle.appendText("\nkotlin.android('androidLib') { publishAllLibraryVariants() }")
|
||||
build("publish") {
|
||||
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.aar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0-sources.jar"))
|
||||
}
|
||||
groupDir.deleteRecursively()
|
||||
|
||||
// Then group the variants by flavor and check that only one publication is created:
|
||||
subProject("lib").buildGradle.appendText("\nkotlin.android('androidLib').publishLibraryVariantsGroupedByFlavor = true")
|
||||
build("publish") {
|
||||
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.aar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-sources.jar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-debug.aar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0-debug-sources.jar"))
|
||||
}
|
||||
groupDir.deleteRecursively()
|
||||
|
||||
// Add one flavor dimension with two flavors, check that the flavors produce grouped publications:
|
||||
subProject("lib").buildGradle.appendText(
|
||||
"\nandroid { flavorDimensions('foo'); productFlavors { fooBar { dimension 'foo' }; fooBaz { dimension 'foo' } } }"
|
||||
)
|
||||
build("publish") {
|
||||
listOf("foobar", "foobaz").forEach { flavor ->
|
||||
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0.aar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-sources.jar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug.aar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib-$flavor/1.0/lib-androidlib-$flavor-1.0-debug-sources.jar"))
|
||||
}
|
||||
}
|
||||
groupDir.deleteRecursively()
|
||||
|
||||
// Disable the grouping and check that all the variants are published under separate artifactIds:
|
||||
subProject("lib").buildGradle.appendText(
|
||||
"\nkotlin.android('androidLib') { publishLibraryVariantsGroupedByFlavor = false }"
|
||||
)
|
||||
build("publish") {
|
||||
listOf("foobar", "foobaz").forEach { flavor ->
|
||||
listOf("-debug", "").forEach { buildType ->
|
||||
assertFileExists(groupDir.resolve("lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0.aar"))
|
||||
assertFileExists(groupDir.resolve("lib-androidlib-$flavor$buildType/1.0/lib-androidlib-$flavor$buildType-1.0-sources.jar"))
|
||||
}
|
||||
}
|
||||
}
|
||||
groupDir.deleteRecursively()
|
||||
|
||||
// Convert the 'app' project to a library, publish the two without metadata,
|
||||
// check that the dependencies in the POMs are correctly rewritten:
|
||||
val appGroupDir = subProject("app").projectPath.resolve("build/repo/com/example")
|
||||
|
||||
subProject("app").buildGradle.modify {
|
||||
it.replace("com.android.application", "com.android.library")
|
||||
.replace("applicationId", "//") +
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
apply plugin: 'maven-publish'
|
||||
publishing { repositories { maven { url = uri("${'$'}buildDir/repo") } } }
|
||||
kotlin.android('androidApp') { publishAllLibraryVariants() }
|
||||
android { flavorDimensions('foo'); productFlavors { fooBar { dimension 'foo' }; fooBaz { dimension 'foo' } } }
|
||||
""".trimIndent()
|
||||
}
|
||||
build("publish") {
|
||||
listOf("foobar", "foobaz").forEach { flavor ->
|
||||
listOf("-debug", "").forEach { buildType ->
|
||||
assertFileExists(appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.aar"))
|
||||
assertFileExists(appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0-sources.jar"))
|
||||
val pomText =
|
||||
appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.pom").readText()
|
||||
.replace("\\s+".toRegex(), "")
|
||||
assertContains(
|
||||
pomText,
|
||||
"<artifactId>lib-androidlib-$flavor$buildType</artifactId><version>1.0</version><scope>runtime</scope>"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
appGroupDir.deleteRecursively()
|
||||
|
||||
// Also check that api and runtimeOnly MPP dependencies get correctly published with the appropriate scope, KT-29476:
|
||||
subProject("app").buildGradle.modify {
|
||||
it.replace("implementation project(':lib')", "api project(':lib')") +
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
kotlin.sourceSets.commonMain.dependencies {
|
||||
runtimeOnly(kotlin('reflect'))
|
||||
}
|
||||
""".trimIndent()
|
||||
}
|
||||
build("publish") {
|
||||
listOf("foobar", "foobaz").forEach { flavor ->
|
||||
listOf("-debug", "").forEach { buildType ->
|
||||
val pomText =
|
||||
appGroupDir.resolve("app-androidapp-$flavor$buildType/1.0/app-androidapp-$flavor$buildType-1.0.pom").readText()
|
||||
.replace("\\s+".toRegex(), "")
|
||||
assertContains(
|
||||
pomText,
|
||||
"<artifactId>lib-androidlib-$flavor$buildType</artifactId><version>1.0</version><scope>compile</scope>"
|
||||
)
|
||||
assertContains(
|
||||
pomText,
|
||||
"<artifactId>kotlin-reflect</artifactId><version>${buildOptions.kotlinVersion}</version><scope>runtime</scope>"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KT-29343: mpp source set dependencies are propagated to android tests")
|
||||
@GradleAndroidTest
|
||||
fun testAndroidMppProductionDependenciesInTests(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"new-mpp-android",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
// Test the fix for KT-29343
|
||||
subProject("lib").buildGradle.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
kotlin.sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin("stdlib-common")
|
||||
}
|
||||
}
|
||||
androidLibDebug {
|
||||
dependencies {
|
||||
implementation kotlin("reflect")
|
||||
}
|
||||
}
|
||||
androidLibRelease {
|
||||
dependencies {
|
||||
implementation kotlin("test-junit")
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val kotlinVersion = buildOptions.kotlinVersion
|
||||
testResolveAllConfigurations("lib") { _, buildResult ->
|
||||
// androidLibDebug:
|
||||
buildResult.assertOutputContains(">> :lib:debugCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
|
||||
buildResult.assertOutputDoesNotContain(">> :lib:releaseCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
|
||||
buildResult.assertOutputContains(">> :lib:debugAndroidTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
|
||||
buildResult.assertOutputContains(">> :lib:debugUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
|
||||
buildResult.assertOutputDoesNotContain(">> :lib:releaseUnitTestCompileClasspath --> kotlin-reflect-$kotlinVersion.jar")
|
||||
|
||||
// androidLibRelease:
|
||||
buildResult.assertOutputDoesNotContain(">> :lib:debugCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
|
||||
buildResult.assertOutputContains(">> :lib:releaseCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
|
||||
buildResult.assertOutputDoesNotContain(">> :lib:debugAndroidTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
|
||||
buildResult.assertOutputDoesNotContain(">> :lib:debugUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
|
||||
buildResult.assertOutputContains(">> :lib:releaseUnitTestCompileClasspath --> kotlin-test-junit-$kotlinVersion.jar")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KT-27714: custom attributes are copied to android compilation configurations")
|
||||
@GradleAndroidTest
|
||||
fun testCustomAttributesInAndroidTargets(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"new-mpp-android",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
val libBuildScript = subProject("lib").buildGradle
|
||||
val appBuildScript = subProject("app").buildGradle
|
||||
|
||||
// Enable publishing for all Android variants:
|
||||
libBuildScript.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
kotlin.android('androidLib') { publishAllLibraryVariants() }
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val groupDir = subProject("lib").projectPath.resolve("build/repo/com/example")
|
||||
|
||||
build("publish") {
|
||||
// Also check that custom user-specified attributes are written in all Android modules metadata:
|
||||
assertFileContains(
|
||||
groupDir.resolve("lib-androidlib/1.0/lib-androidlib-1.0.module"),
|
||||
"\"com.example.target\": \"androidLib\"",
|
||||
"\"com.example.compilation\": \"release\""
|
||||
)
|
||||
assertFileContains(
|
||||
groupDir.resolve("lib-androidlib-debug/1.0/lib-androidlib-debug-1.0.module"),
|
||||
"\"com.example.target\": \"androidLib\"",
|
||||
"\"com.example.compilation\": \"debug\""
|
||||
)
|
||||
}
|
||||
groupDir.deleteRecursively()
|
||||
|
||||
// Check that the consumer side uses custom attributes specified in the target and compilations:
|
||||
val appBuildScriptBackup = appBuildScript.readText()
|
||||
|
||||
libBuildScript.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
kotlin.targets.all {
|
||||
attributes.attribute(
|
||||
Attribute.of("com.example.target", String),
|
||||
targetName
|
||||
)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
appBuildScript.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
kotlin.targets.androidApp.attributes.attribute(
|
||||
Attribute.of("com.example.target", String),
|
||||
"notAndroidLib"
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
buildAndFail(":app:compileDebugKotlinAndroidApp") {
|
||||
// dependency resolution should fail
|
||||
assertOutputContainsAny(
|
||||
"Required com.example.target 'notAndroidLib'",
|
||||
"attribute 'com.example.target' with value 'notAndroidLib'",
|
||||
)
|
||||
}
|
||||
|
||||
libBuildScript.writeText(
|
||||
appBuildScriptBackup +
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
kotlin.targets.all {
|
||||
compilations.all {
|
||||
attributes.attribute(
|
||||
Attribute.of("com.example.compilation", String),
|
||||
targetName + compilationName.capitalize()
|
||||
)
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
appBuildScript.writeText(
|
||||
appBuildScriptBackup +
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
kotlin.targets.androidApp.compilations.all {
|
||||
attributes.attribute(
|
||||
Attribute.of("com.example.compilation", String),
|
||||
"notDebug"
|
||||
)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
buildAndFail(":app:compileDebugKotlinAndroidApp") {
|
||||
assertOutputContainsAny(
|
||||
"Required com.example.compilation 'notDebug'",
|
||||
"attribute 'com.example.compilation' with value 'notDebug'",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KT-27170: android lint works with dependency on non-android mpp project")
|
||||
@GradleAndroidTest
|
||||
@Disabled // not fully migrated yet
|
||||
fun testLintInAndroidProjectsDependingOnMppWithoutAndroid(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"AndroidProject",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
// embedProject(Project("sample-lib", directoryPrefix = "new-mpp-lib-and-app")) // TODO
|
||||
subProject("lib").buildGradle.appendText(
|
||||
//language=Gradle
|
||||
"""
|
||||
|
||||
dependencies { implementation(project(':sample-lib')) }
|
||||
""".trimIndent()
|
||||
)
|
||||
val lintTask = ":Lib:lintFlavor1Debug"
|
||||
build(lintTask) {
|
||||
assertTasksExecuted(lintTask) // Check that the lint task ran successfully, KT-27170
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("MPP allTests task depending on Android unit tests")
|
||||
@GradleAndroidTest
|
||||
fun testMppAllTests(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"new-mpp-android",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build(":lib:allTests", "--dry-run") {
|
||||
assertOutputContains(":lib:testDebugUnitTest SKIPPED")
|
||||
assertOutputContains(":lib:testReleaseUnitTest SKIPPED")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting from AGP version 7.1.0-alpha13, a new attribute com.android.build.api.attributes.AgpVersionAttr was added.
|
||||
* This attribute is *not intended* to be published.
|
||||
*/
|
||||
@DisplayName("KT-49798: com.android.build.api.attributes.AgpVersionAttr is not published")
|
||||
@GradleAndroidTest
|
||||
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_71)
|
||||
fun testKT49798AgpVersionAttrNotPublished(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"new-mpp-android",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("publish") {
|
||||
val libProject = subProject("lib")
|
||||
val debugPublicationDirectory = libProject.projectPath.resolve("build/repo/com/example/lib-androidlib-debug")
|
||||
val releasePublicationDirectory = libProject.projectPath.resolve("build/repo/com/example/lib-androidlib")
|
||||
|
||||
listOf(debugPublicationDirectory, releasePublicationDirectory).forEach { publicationDirectory ->
|
||||
assertDirectoryExists(publicationDirectory)
|
||||
val moduleFiles = Files.walk(publicationDirectory).use { it.filter { file -> file.extension == "module" }.toList() }
|
||||
assertTrue(moduleFiles.isNotEmpty(), "Missing .module file in $publicationDirectory")
|
||||
assertTrue(moduleFiles.size == 1, "Multiple .module files in $publicationDirectory: $moduleFiles")
|
||||
|
||||
val moduleFile = moduleFiles.single()
|
||||
val moduleFileText = moduleFile.readText()
|
||||
assertTrue("AgpVersionAttr" !in moduleFileText, ".module file $moduleFile leaks AgpVersionAttr")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("produced artifacts are consumable by projects with various AGP versions")
|
||||
@GradleAndroidTest
|
||||
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_71)
|
||||
fun testAndroidMultiplatformPublicationAGPCompatibility(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"new-mpp-android-agp-compatibility",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
/* Publish a producer library with the current version of AGP */
|
||||
build(":producer:publishAllPublicationsToBuildDirRepository") {
|
||||
/* Check expected publication layout */
|
||||
assertDirectoryInProjectExists("build/repo/com/example/producer-android")
|
||||
assertDirectoryInProjectExists("build/repo/com/example/producer-android-debug")
|
||||
assertDirectoryInProjectExists("build/repo/com/example/producer-jvm")
|
||||
}
|
||||
|
||||
val checkedConsumerAGPVersions = AGPVersion.testedVersions
|
||||
// Special version added for testing KT-49798
|
||||
.plus(AGPVersion.fromString("7.1.0-beta02"))
|
||||
.filter { version -> version >= AGPVersion.fromString(TestVersions.AGP.AGP_42) }
|
||||
.filter { version -> version < AGPVersion.fromString(TestVersions.AGP.MAX_SUPPORTED) }
|
||||
.map { it.toString() }
|
||||
|
||||
checkedConsumerAGPVersions.forEach { consumerAgpVersion ->
|
||||
println("Testing compatibility for AGP consumer version $consumerAgpVersion (Producer: $agpVersion)")
|
||||
val buildOptions = buildOptions.copy(androidVersion = consumerAgpVersion)
|
||||
|
||||
/*
|
||||
Project: multiplatformAndroidConsumer is a mpp project with jvm and android targets.
|
||||
This project depends on the previous publication as 'commonMainImplementation' dependency
|
||||
*/
|
||||
build(":multiplatformAndroidConsumer:assemble", buildOptions = buildOptions)
|
||||
|
||||
/*
|
||||
Project: plainAndroidConsumer only uses the 'kotlin("android")' plugin
|
||||
This project depends on the previous publication as 'implementation' dependency
|
||||
*/
|
||||
build(":plainAndroidConsumer:assemble", buildOptions = buildOptions)
|
||||
println("Successfully tested compatibility for AGP consumer version $consumerAgpVersion (Producer: $agpVersion)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("KT-49877, KT-35916: associate compilation dependencies are passed correctly to android test compilations")
|
||||
@GradleAndroidTest
|
||||
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_71)
|
||||
fun testAssociateCompilationDependenciesArePassedToAndroidTestCompilations(
|
||||
gradleVersion: GradleVersion,
|
||||
agpVersion: String,
|
||||
jdkVersion: JdkVersions.ProvidedJdk,
|
||||
) {
|
||||
project(
|
||||
"kt-49877",
|
||||
gradleVersion,
|
||||
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
|
||||
buildJdk = jdkVersion.location
|
||||
) {
|
||||
build("allTests") {
|
||||
assertTasksExecuted(
|
||||
":compileDebugKotlinAndroid",
|
||||
":compileReleaseKotlinAndroid",
|
||||
":compileDebugUnitTestKotlinAndroid",
|
||||
":compileReleaseUnitTestKotlinAndroid",
|
||||
":testDebugUnitTest",
|
||||
":testReleaseUnitTest",
|
||||
)
|
||||
}
|
||||
|
||||
// instrumented tests don't work without a device, so we only compile them
|
||||
build("packageDebugAndroidTest") {
|
||||
assertTasksExecuted(
|
||||
":compileDebugAndroidTestKotlinAndroid",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -85,6 +85,20 @@ fun assertCompiledKotlinSources(
|
||||
assertSameFiles(expectedSources, actualSources, "Compiled Kotlin files differ${errorMessageSuffix}:\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts all the .java files from [expectedSources] and only they are compiled
|
||||
*
|
||||
* Note: log level of output should be set to [LogLevel.DEBUG]
|
||||
*/
|
||||
fun GradleProject.assertCompiledJavaSources(
|
||||
expectedSources: Iterable<Path>,
|
||||
output: String,
|
||||
errorMessageSuffix: String = ""
|
||||
) {
|
||||
val actualSources = extractCompiledJavaFiles(projectPath.toRealPath().toFile(), output)
|
||||
assertSameFiles(expectedSources, actualSources, "Compiled Java files differ${errorMessageSuffix}:\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that compilation was non-incremental.
|
||||
*
|
||||
|
||||
+13
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle.testbase
|
||||
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
|
||||
/**
|
||||
@@ -19,6 +20,18 @@ fun BuildResult.assertOutputContains(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts Gradle output contains any of [expectedSubString] strings.
|
||||
*/
|
||||
fun BuildResult.assertOutputContainsAny(
|
||||
vararg expectedSubStrings: String
|
||||
) {
|
||||
assert(expectedSubStrings.any { output.contains(it) }) {
|
||||
printBuildOutput()
|
||||
"Build output does not contain any of \"$expectedSubStrings\""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts Gradle output contains [expectedSubString] string exact times.
|
||||
*/
|
||||
|
||||
+27
@@ -8,6 +8,26 @@ package org.jetbrains.kotlin.gradle.testbase
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.testkit.runner.TaskOutcome
|
||||
|
||||
/**
|
||||
* Asserts given tasks are not present in the build task graph
|
||||
*/
|
||||
fun BuildResult.assertTasksAreNotInTaskGraph(vararg tasks: String) {
|
||||
val presentTasks = tasks.filter { task(it) != null }
|
||||
assert(presentTasks.isEmpty()) {
|
||||
printBuildOutput()
|
||||
"Tasks ${tasks.joinToString(prefix = "[", postfix = "]")} shouldn't be present in the task graph, but $presentTasks were present"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the affected during the build tasks, whose [org.gradle.api.Task.getPath] satisfies the [pattern]
|
||||
*/
|
||||
fun BuildResult.findTasksByPattern(pattern: Regex): Set<String> {
|
||||
return tasks.map { it.path }.filter { taskPath ->
|
||||
pattern.matches(taskPath)
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts given [tasks] have 'SUCCESS' execution state.
|
||||
*/
|
||||
@@ -51,6 +71,13 @@ fun BuildResult.assertTasksUpToDate(vararg tasks: String) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts given [tasks] have 'UP-TO-DATE' execution state.
|
||||
*/
|
||||
fun BuildResult.assertTasksUpToDate(tasks: Collection<String>) {
|
||||
assertTasksUpToDate(*tasks.toTypedArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts given [tasks] have 'SKIPPED' execution state.
|
||||
*/
|
||||
|
||||
+4
-3
@@ -53,12 +53,13 @@ fun TestProject.testResolveAllConfigurations(
|
||||
}
|
||||
) {
|
||||
if (!skipSetup) {
|
||||
val targetProject = subproject?.let { subProject(it) } ?: this
|
||||
when {
|
||||
Files.exists(buildGradle) -> buildGradle
|
||||
Files.exists(targetProject.buildGradle) -> targetProject.buildGradle
|
||||
.append("\n${generateResolveAllConfigurationsTask(excludeConfigurations)}")
|
||||
Files.exists(buildGradleKts) -> buildGradleKts
|
||||
Files.exists(targetProject.buildGradleKts) -> targetProject.buildGradleKts
|
||||
.append("\n${generateResolveAllConfigurationsTaskKts(excludeConfigurations)}")
|
||||
else -> error("Build script does not exists under $projectPath")
|
||||
else -> error("Build script does not exist under $projectPath")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -49,5 +49,6 @@ dependencies {
|
||||
|
||||
implementation 'com.google.dagger:dagger:2.9'
|
||||
kapt 'com.google.dagger:dagger-compiler:2.9'
|
||||
kapt 'javax.annotation:javax.annotation-api:1.3.2'
|
||||
compileOnly 'org.glassfish:javax.annotation:10.0-b28'
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ import org.gradle.api.logging.Logger
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.HashMap
|
||||
|
||||
// todo: remove when https://github.com/gradle/gradle/issues/2678 is resolved
|
||||
// todo: remove when https://github.com/gradle/gradle/issues/16991 is resolved
|
||||
internal object TaskLoggers {
|
||||
private val taskLoggers = HashMap<String, WeakReference<Logger>>()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user