blob: 58a3817bf498b9e04281ef49fe9cc9b2e064bf11 [file] [log] [blame]
/*
* Copyright 2010-2020 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
import org.gradle.api.logging.LogLevel
import org.jetbrains.kotlin.gradle.native.GeneralNativeIT.Companion.withNativeCommandLineArguments
import org.jetbrains.kotlin.gradle.native.GeneralNativeIT.Companion.containsSequentially
import org.gradle.api.logging.configuration.WarningMode
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.native.*
import org.jetbrains.kotlin.gradle.native.MPPNativeTargets
import org.jetbrains.kotlin.gradle.native.transformNativeTestProject
import org.jetbrains.kotlin.gradle.native.transformNativeTestProjectWithPluginDsl
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.*
import org.jetbrains.kotlin.gradle.plugin.ProjectLocalConfigurations
import org.jetbrains.kotlin.gradle.plugin.lowerName
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmWithJavaTargetPreset
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin
import org.jetbrains.kotlin.gradle.plugin.mpp.UnusedSourceSetsChecker
import org.jetbrains.kotlin.gradle.plugin.sources.METADATA_CONFIGURATION_NAME_SUFFIX
import org.jetbrains.kotlin.gradle.plugin.sources.UnsatisfiedSourceSetVisibilityException
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
import org.jetbrains.kotlin.gradle.testbase.TestVersions
import org.jetbrains.kotlin.gradle.util.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.library.KLIB_PROPERTY_SHORT_NAME
import org.jetbrains.kotlin.library.KLIB_PROPERTY_UNIQUE_NAME
import org.junit.Assert
import org.junit.Test
import java.util.*
import java.util.jar.JarFile
import java.util.zip.ZipFile
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class NewMultiplatformIT : BaseGradleIT() {
private val gradleVersion = GradleVersionRequired.FOR_MPP_SUPPORT
private val nativeHostTargetName = MPPNativeTargets.current
private val unsupportedNativeTargets = MPPNativeTargets.unsupported
private fun Project.targetClassesDir(targetName: String, sourceSetName: String = "main") =
classesDir(sourceSet = "$targetName/$sourceSetName")
private data class HmppFlags(
val hmppSupport: Boolean,
val enableCompatibilityMetadataArtifact: Boolean,
val name: String
) {
override fun toString() = name
}
private val noHMPP = HmppFlags(
name = "No HMPP",
hmppSupport = false,
enableCompatibilityMetadataArtifact = false
)
private val hmppWoCompatibilityMetadataArtifact = HmppFlags(
name = "HMPP without Compatibility Metadata Artifact",
hmppSupport = true,
enableCompatibilityMetadataArtifact = false
)
private val hmppWithCompatibilityMetadataArtifact = HmppFlags(
name = "HMPP with Compatibility Metadata Artifact",
hmppSupport = true,
enableCompatibilityMetadataArtifact = true
)
private val HmppFlags.buildOptions get() = defaultBuildOptions().copy(
hierarchicalMPPStructureSupport = hmppSupport,
enableCompatibilityMetadataVariant = enableCompatibilityMetadataArtifact,
)
@Test
fun testLibAndApp() = doTestLibAndApp(
"sample-lib",
"sample-app",
hmppWoCompatibilityMetadataArtifact
)
@Test
fun testLibAndAppWithoutHMPP() = doTestLibAndApp(
"sample-lib",
"sample-app",
noHMPP
)
@Test
fun testLibAndAppWithCompatibilityArtifact() = doTestLibAndApp(
"sample-lib",
"sample-app",
hmppWithCompatibilityMetadataArtifact
)
@Test
fun testLibAndAppWithGradleKotlinDsl() = doTestLibAndApp(
"sample-lib-gradle-kotlin-dsl",
"sample-app-gradle-kotlin-dsl",
hmppWoCompatibilityMetadataArtifact
)
private fun doTestLibAndApp(
libProjectName: String,
appProjectName: String,
hmppFlags: HmppFlags,
) {
val libProject = transformNativeTestProjectWithPluginDsl(libProjectName, directoryPrefix = "new-mpp-lib-and-app")
val appProject = transformNativeTestProjectWithPluginDsl(appProjectName, directoryPrefix = "new-mpp-lib-and-app")
val oldStyleAppProject = Project("sample-old-style-app", directoryPrefix = "new-mpp-lib-and-app")
val buildOptions = hmppFlags.buildOptions
val compileTasksNames =
listOf("Jvm6", "NodeJs", "Linux64").map { ":compileKotlin$it" }
with(libProject) {
build(
"publish",
options = buildOptions
) {
assertSuccessful()
assertTasksExecuted(*compileTasksNames.toTypedArray(), ":jvm6Jar", ":nodeJsJar", ":compileCommonMainKotlinMetadata")
if (hmppFlags.enableCompatibilityMetadataArtifact) {
assertTasksExecuted(":compileKotlinMetadata", ":metadataJar")
}
val groupDir = projectDir.resolve("repo/com/example")
val jvmJarName = "sample-lib-jvm6/1.0/sample-lib-jvm6-1.0.jar"
val jsExtension = "jar"
val jsJarName = "sample-lib-nodejs/1.0/sample-lib-nodejs-1.0.$jsExtension"
val metadataJarName = "sample-lib/1.0/sample-lib-1.0.jar"
val nativeKlibName = "sample-lib-linux64/1.0/sample-lib-linux64-1.0.klib"
listOf(jvmJarName, jsJarName, metadataJarName, "sample-lib/1.0/sample-lib-1.0.module").forEach {
Assert.assertTrue("$it should exist", groupDir.resolve(it).exists())
}
val gradleMetadata = groupDir.resolve("sample-lib/1.0/sample-lib-1.0.module").readText()
assertFalse(gradleMetadata.contains(ProjectLocalConfigurations.ATTRIBUTE.name))
listOf(jvmJarName, jsJarName, nativeKlibName).forEach {
val pom = groupDir.resolve(it.replaceAfterLast('.', "pom"))
Assert.assertTrue(
"$pom should contain a name section.",
pom.readText().contains("<name>Sample MPP library</name>")
)
Assert.assertFalse(
"$pom should not contain standard K/N libraries as dependencies.",
pom.readText().contains("<groupId>Kotlin/Native</groupId>")
)
}
val jvmJarEntries = ZipFile(groupDir.resolve(jvmJarName)).entries().asSequence().map { it.name }.toSet()
Assert.assertTrue("com/example/lib/CommonKt.class" in jvmJarEntries)
Assert.assertTrue("com/example/lib/MainKt.class" in jvmJarEntries)
val jsJar = ZipFile(groupDir.resolve(jsJarName))
val compiledJs = jsJar.getInputStream(jsJar.getEntry("sample-lib.js")).reader().readText()
Assert.assertTrue("function id(" in compiledJs)
Assert.assertTrue("function idUsage(" in compiledJs)
Assert.assertTrue("function expectedFun(" in compiledJs)
Assert.assertTrue("function main(" in compiledJs)
val metadataJarEntries = ZipFile(groupDir.resolve(metadataJarName)).entries().asSequence().map { it.name }.toSet()
val metadataFileFound = "com/example/lib/CommonKt.kotlin_metadata" in metadataJarEntries
Assert.assertEquals(hmppFlags.enableCompatibilityMetadataArtifact, metadataFileFound)
Assert.assertTrue(groupDir.resolve(nativeKlibName).exists())
}
}
val libLocalRepoUri = libProject.projectDir.resolve("repo").toURI()
with(appProject) {
setupWorkingDir(false)
// we use `maven { setUrl(...) }` because this syntax actually works both for Groovy and Kotlin DSLs in Gradle
gradleBuildScript().appendText("\nrepositories { maven { setUrl(\"$libLocalRepoUri\") } }")
fun CompiledProject.checkAppBuild() {
assertSuccessful()
assertTasksExecuted(*compileTasksNames.toTypedArray())
projectDir.resolve(targetClassesDir("jvm6")).run {
Assert.assertTrue(resolve("com/example/app/AKt.class").exists())
Assert.assertTrue(resolve("com/example/app/UseBothIdsKt.class").exists())
}
projectDir.resolve(targetClassesDir("jvm8")).run {
Assert.assertTrue(resolve("com/example/app/AKt.class").exists())
Assert.assertTrue(resolve("com/example/app/UseBothIdsKt.class").exists())
Assert.assertTrue(resolve("com/example/app/Jdk8ApiUsageKt.class").exists())
}
if (hmppFlags.enableCompatibilityMetadataArtifact) {
projectDir.resolve(targetClassesDir("metadata")).run {
Assert.assertTrue(resolve("com/example/app/AKt.kotlin_metadata").exists())
}
}
projectDir.resolve(targetClassesDir("nodeJs")).resolve("sample-app.js").readText().run {
Assert.assertTrue(contains("console.info"))
Assert.assertTrue(contains("function nodeJsMain("))
}
val nativeExeName = if (isWindows) "main.exe" else "main.kexe"
assertFileExists("build/bin/linux64/mainDebugExecutable/$nativeExeName")
// Check that linker options were correctly passed to the K/N compiler.
withNativeCommandLineArguments(":linkMainDebugExecutableLinux64") { arguments ->
assertTrue(arguments.containsSequentially("-linker-option", "-L."))
}
}
build(
"assemble",
"resolveRuntimeDependencies",
options = buildOptions
) {
checkAppBuild()
assertTasksExecuted(":resolveRuntimeDependencies") // KT-26301
}
// Now run again with a project dependency instead of a module one:
libProject.projectDir.copyRecursively(projectDir.resolve(libProject.projectDir.name))
gradleSettingsScript().appendText("\ninclude(\"${libProject.projectDir.name}\")")
gradleBuildScript().modify { it.replace("\"com.example:sample-lib:1.0\"", "project(\":${libProject.projectDir.name}\")") }
gradleBuildScript(libProjectName).takeIf { it.extension == "kts" }?.modify {
it.replace(Regex("""\.version\(.*\)"""), "")
}
gradleBuildScript(subproject = libProject.projectDir.name).modify {
it.lines().dropLast(5).joinToString(separator = "\n")
}
build(
"clean",
"assemble",
"--rerun-tasks",
options = buildOptions
) {
checkAppBuild()
}
}
if (hmppFlags.enableCompatibilityMetadataArtifact) {
with(oldStyleAppProject) {
setupWorkingDir()
gradleBuildScript().appendText("\nallprojects { repositories { maven { url '$libLocalRepoUri' } } }")
build("assemble", options = buildOptions) {
assertSuccessful()
assertTasksExecuted(":app-js:compileKotlin2Js", ":app-jvm:compileKotlin", ":app-common:compileKotlinCommon")
assertFileExists(kotlinClassesDir("app-common") + "com/example/app/CommonAppKt.kotlin_metadata")
val jvmClassFile = projectDir.resolve(kotlinClassesDir("app-jvm") + "com/example/app/JvmAppKt.class")
checkBytecodeContains(jvmClassFile, "CommonKt.id", "MainKt.expectedFun")
val jsCompiledFilePath = kotlinClassesDir("app-js") + "app-js.js"
assertFileContains(jsCompiledFilePath, "lib.expectedFun", "lib.id")
}
}
}
}
@Test
fun testLibAndAppJsLegacy() = doTestLibAndAppJsBothCompilers(
"sample-lib",
"sample-app",
LEGACY
)
@Test
fun testLibAndAppJsIr() = doTestLibAndAppJsBothCompilers(
"sample-lib",
"sample-app",
IR
)
@Test
fun testLibAndAppJsBoth() = doTestLibAndAppJsBothCompilers(
"sample-lib",
"sample-app",
BOTH
)
@Test
fun testLibAndAppWithGradleKotlinDslJsLegacy() = doTestLibAndAppJsBothCompilers(
"sample-lib-gradle-kotlin-dsl",
"sample-app-gradle-kotlin-dsl",
LEGACY
)
@Test
fun testLibAndAppWithGradleKotlinDslJsIr() = doTestLibAndAppJsBothCompilers(
"sample-lib-gradle-kotlin-dsl",
"sample-app-gradle-kotlin-dsl",
IR
)
@Test
fun testLibAndAppWithGradleKotlinDslJsBoth() = doTestLibAndAppJsBothCompilers(
"sample-lib-gradle-kotlin-dsl",
"sample-app-gradle-kotlin-dsl",
BOTH
)
private fun doTestLibAndAppJsBothCompilers(
libProjectName: String,
appProjectName: String,
jsCompilerType: KotlinJsCompilerType
) {
val libProject = transformProjectWithPluginsDsl(libProjectName, directoryPrefix = "both-js-lib-and-app")
val appProject = transformProjectWithPluginsDsl(appProjectName, directoryPrefix = "both-js-lib-and-app")
val compileTasksNames =
listOf(
*(if (jsCompilerType != BOTH) {
arrayOf("NodeJs")
} else {
arrayOf(
"NodeJs${LEGACY.lowerName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}",
"NodeJs${IR.lowerName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}",
)
}),
).map { ":compileKotlin$it" }
with(libProject) {
build(
"publish",
options = defaultBuildOptions().copy(jsCompilerType = jsCompilerType)
) {
assertSuccessful()
assertTasksSkipped(":compileCommonMainKotlinMetadata")
assertTasksExecuted(*compileTasksNames.toTypedArray(), ":allMetadataJar")
val groupDir = projectDir.resolve("repo/com/example")
val jsExtension = if (jsCompilerType == LEGACY) "jar" else "klib"
val jsJarName = "sample-lib-nodejs/1.0/sample-lib-nodejs-1.0.$jsExtension"
val metadataJarName = "sample-lib/1.0/sample-lib-1.0.jar"
listOf(jsJarName, metadataJarName, "sample-lib/1.0/sample-lib-1.0.module").forEach {
Assert.assertTrue("$it should exist", groupDir.resolve(it).exists())
}
val gradleMetadata = groupDir.resolve("sample-lib/1.0/sample-lib-1.0.module").readText()
assertFalse(gradleMetadata.contains(ProjectLocalConfigurations.ATTRIBUTE.name))
jsJarName.let {
val pom = groupDir.resolve(it.replaceAfterLast('.', "pom"))
Assert.assertTrue(
"$pom should contain a name section.",
pom.readText().contains("<name>Sample MPP library</name>")
)
Assert.assertFalse(
"$pom should not contain standard K/N libraries as dependencies.",
pom.readText().contains("<groupId>Kotlin/Native</groupId>")
)
}
when (jsCompilerType) {
LEGACY -> {
val jsJar = ZipFile(groupDir.resolve(jsJarName))
val compiledJs = jsJar.getInputStream(jsJar.getEntry("sample-lib.js")).reader().readText()
Assert.assertTrue("function id(" in compiledJs)
Assert.assertTrue("function idUsage(" in compiledJs)
Assert.assertTrue("function expectedFun(" in compiledJs)
Assert.assertTrue("function main(" in compiledJs)
}
IR -> {
groupDir.resolve(jsJarName).exists()
}
BOTH -> {}
}
}
}
val libLocalRepoUri = libProject.projectDir.resolve("repo").toURI()
with(appProject) {
setupWorkingDir()
// we use `maven { setUrl(...) }` because this syntax actually works both for Groovy and Kotlin DSLs in Gradle
gradleBuildScript().appendText("\nrepositories { maven { setUrl(\"$libLocalRepoUri\") } }")
fun CompiledProject.checkAppBuild(compilerType: KotlinJsCompilerType) {
assertSuccessful()
val compileTaskNames = if (jsCompilerType == compilerType) {
compileTasksNames.toTypedArray()
} else {
arrayOf(":compileKotlinNodeJs")
}
assertTasksExecuted(*compileTaskNames)
if (jsCompilerType == LEGACY) {
projectDir.resolve(targetClassesDir("nodeJs")).resolve("sample-app.js").readText().run {
Assert.assertTrue(contains("console.info"))
Assert.assertTrue(contains("function nodeJsMain("))
}
}
}
build(
"assemble",
options = defaultBuildOptions().copy(jsCompilerType = jsCompilerType)
) {
checkAppBuild(jsCompilerType)
}
if (jsCompilerType == BOTH) {
listOf(
LEGACY,
IR
).forEach {
build(
"assemble",
"--rerun-tasks",
options = defaultBuildOptions().copy(jsCompilerType = it)
) {
checkAppBuild(it)
}
}
}
// Now run again with a project dependency instead of a module one:
libProject.projectDir.copyRecursively(projectDir.resolve(libProject.projectDir.name))
gradleSettingsScript().appendText("\ninclude(\"${libProject.projectDir.name}\")")
gradleBuildScript().modify { it.replace("\"com.example:sample-lib:1.0\"", "project(\":${libProject.projectDir.name}\")") }
gradleBuildScript(libProjectName).takeIf { it.extension == "kts" }?.modify {
it.replace(Regex("""\.version\(.*\)"""), "")
}
gradleBuildScript(subproject = libProject.projectDir.name).modify {
it.lines().dropLast(5).joinToString(separator = "\n")
}
build(
"clean",
"assemble",
"--rerun-tasks",
options = defaultBuildOptions().copy(jsCompilerType = jsCompilerType)
) {
checkAppBuild(jsCompilerType)
}
}
}
@Test
fun testMavenPublishAppliedBeforeMultiplatformPlugin() =
with(transformNativeTestProject("sample-lib", directoryPrefix = "new-mpp-lib-and-app")) {
gradleBuildScript().modify { "apply plugin: 'maven-publish'\n$it" }
build {
assertSuccessful()
}
}
@Test
fun testResourceProcessing() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
val targetsWithResources = listOf("jvm6", "nodeJs", "linux64")
val processResourcesTasks =
targetsWithResources.map { ":${it}ProcessResources" }
build(*processResourcesTasks.toTypedArray()) {
assertSuccessful()
assertTasksExecuted(*processResourcesTasks.toTypedArray())
targetsWithResources.forEach {
assertFileExists("build/processedResources/$it/main/commonMainResource.txt")
assertFileExists("build/processedResources/$it/main/${it}MainResource.txt")
}
}
}
override val defaultGradleVersion: GradleVersionRequired
get() = gradleVersion
@Test
fun testSourceSetCyclicDependencyDetection() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
gradleBuildScript().appendText(
"\n" + """
kotlin.sourceSets {
a
b { dependsOn a }
c { dependsOn b }
a.dependsOn(c)
}
""".trimIndent()
)
build("assemble") {
assertFailed()
assertContains("a -> c -> b -> a")
}
}
@Test
fun testJvmWithJavaEquivalence() = doTestJvmWithJava(testJavaSupportInJvmTargets = false)
@Test
fun testJavaSupportInJvmTargets() = doTestJvmWithJava(testJavaSupportInJvmTargets = true)
private fun doTestJvmWithJava(testJavaSupportInJvmTargets: Boolean) =
with(Project("sample-lib", directoryPrefix = "new-mpp-lib-and-app")) {
embedProject(Project("sample-lib-gradle-kotlin-dsl", directoryPrefix = "new-mpp-lib-and-app"))
gradleProperties().apply {
configureJvmMemory()
}
lateinit var classesWithoutJava: Set<String>
fun getFilePathsSet(inDirectory: String): Set<String> {
val dir = projectDir.resolve(inDirectory)
return dir.walk().filter { it.isFile }.map { it.relativeTo(dir).invariantSeparatorsPath }.toSet()
}
build("assemble") {
assertSuccessful()
classesWithoutJava = getFilePathsSet("build/classes")
}
gradleBuildScript().modify {
if (testJavaSupportInJvmTargets) {
it + "\nkotlin.jvm(\"jvm6\") { " +
"${KotlinJvmTarget::withJava.name.plus("();").repeat(2)} " + // also check that the function is idempotent
"}"
} else {
it.replace("presets.jvm", "presets.jvmWithJava").replace("jvm(", "targetFromPreset(presets.jvmWithJava, ")
}.plus(
"\n" + """
buildscript {
repositories {
maven { url 'https://plugins.gradle.org/m2/' }
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:5.0.0'
}
}
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'application'
apply plugin: 'kotlin-kapt' // Check that Kapts works, generates and compiles sources
mainClassName = 'com.example.lib.CommonKt'
dependencies {
jvm6MainImplementation("com.google.dagger:dagger:2.24")
kapt("com.google.dagger:dagger-compiler:2.24")
kapt(project(":sample-lib-gradle-kotlin-dsl"))
// also check incremental Kapt class structure configurations, KT-33105
jvm6MainImplementation(project(":sample-lib-gradle-kotlin-dsl"))
}
""".trimIndent()
)
}
// also check incremental Kapt class structure configurations, KT-33105
projectDir.resolve("gradle.properties").appendText("\nkapt.incremental.apt=true")
// Check Kapt:
projectDir.resolve("src/jvm6Main/kotlin/Main.kt").appendText(
"\n" + """
interface Iface
@dagger.Module
object Module {
@JvmStatic @dagger.Provides
fun provideHeater(): Iface = object : Iface { }
}
""".trimIndent()
)
fun javaSourceRootForCompilation(compilationName: String) =
if (testJavaSupportInJvmTargets) "src/jvm6${compilationName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}/java" else "src/$compilationName/java"
val javaMainSrcDir = javaSourceRootForCompilation("main")
val javaTestSrcDir = javaSourceRootForCompilation("test")
projectDir.resolve(javaMainSrcDir).apply {
mkdirs()
// Check that Java can access the dependencies (kotlin-stdlib):
resolve("JavaClassInJava.java").writeText(
"""
package com.example.lib;
import kotlin.sequences.Sequence;
class JavaClassInJava {
Sequence<String> makeSequence() { throw new UnsupportedOperationException(); }
}
""".trimIndent()
)
// Add a Kotlin source file in the Java source root and check that it is compiled:
resolve("KotlinClassInJava.kt").writeText(
"""
package com.example.lib
class KotlinClassInJava
""".trimIndent()
)
}
projectDir.resolve(javaTestSrcDir).apply {
mkdirs()
resolve("JavaTest.java").writeText(
"""
package com.example.lib;
import org.junit.*;
public class JavaTest {
@Test
public void testAccessKotlin() {
MainKt.expectedFun();
MainKt.x();
new KotlinClassInJava();
new JavaClassInJava();
}
}
""".trimIndent()
)
}
build("clean", "build", "run", "shadowJar") {
assertSuccessful()
val expectedMainClasses =
classesWithoutJava + setOf(
// classes for Kapt test:
"java/main/com/example/lib/Module_ProvideHeaterFactory.class",
"kotlin/jvm6/main/com/example/lib/Module\$provideHeater\$1.class",
"kotlin/jvm6/main/com/example/lib/Iface.class",
"kotlin/jvm6/main/com/example/lib/Module.class",
// other added classes:
"kotlin/jvm6/main/com/example/lib/KotlinClassInJava.class",
"java/main/com/example/lib/JavaClassInJava.class",
"java/test/com/example/lib/JavaTest.class"
)
val actualClasses = getFilePathsSet("build/classes")
Assert.assertEquals(expectedMainClasses, actualClasses)
val jvmTestTaskName = if (testJavaSupportInJvmTargets) "jvm6Test" else "test"
assertTasksExecuted(":$jvmTestTaskName")
if (testJavaSupportInJvmTargets) {
assertFileExists("build/reports/tests/allTests/classes/com.example.lib.JavaTest.html")
}
if (testJavaSupportInJvmTargets) {
assertNotContains(KotlinJvmWithJavaTargetPreset.DEPRECATION_WARNING)
} else {
assertContains(KotlinJvmWithJavaTargetPreset.DEPRECATION_WARNING)
}
assertTasksExecuted(":run")
assertContains(">>> Common.kt >>> main()")
assertTasksExecuted(":shadowJar")
val entries = ZipFile(projectDir.resolve("build/libs/sample-lib-1.0-all.jar")).use { zip ->
zip.entries().asSequence().map { it.name }.toSet()
}
assertTrue { "kotlin/Pair.class" in entries }
assertTrue { "com/example/lib/CommonKt.class" in entries }
assertTrue { "com/example/lib/MainKt.class" in entries }
assertTrue { "com/example/lib/JavaClassInJava.class" in entries }
assertTrue { "com/example/lib/KotlinClassInJava.class" in entries }
}
}
@Test
fun testLibWithTests() = doTestLibWithTests(transformNativeTestProject("new-mpp-lib-with-tests", gradleVersion))
@Test
fun testLibWithTestsKotlinDsl() = with(transformNativeTestProject("new-mpp-lib-with-tests", gradleVersion)) {
gradleBuildScript().delete()
projectDir.resolve("build.gradle.kts.alternative").renameTo(projectDir.resolve("build.gradle.kts"))
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
doTestLibWithTests(this)
}
private fun doTestLibWithTests(project: Project) = with(project) {
build("check") {
assertSuccessful()
assertTasksExecuted(
// compilation tasks:
":compileKotlinJs",
":compileTestKotlinJs",
":compileKotlinJvmWithoutJava",
":compileTestKotlinJvmWithoutJava",
":compileKotlinJvmWithJava",
":compileJava",
":compileTestKotlinJvmWithJava",
":compileTestJava",
// test tasks:
":jsTest", // does not run any actual tests for now
":jvmWithoutJavaTest",
":test"
)
val expectedKotlinOutputFiles = listOf(
*kotlinClassesDir(sourceSet = "jvmWithJava/main").let {
arrayOf(
it + "com/example/lib/JavaClassUsageKt.class",
it + "com/example/lib/CommonKt.class",
it + "META-INF/new-mpp-lib-with-tests.kotlin_module"
)
},
*kotlinClassesDir(sourceSet = "jvmWithJava/test").let {
arrayOf(
it + "com/example/lib/TestCommonCode.class",
it + "com/example/lib/TestWithJava.class",
it + "META-INF/new-mpp-lib-with-tests.kotlin_module" // Note: same name as in main
)
},
*kotlinClassesDir(sourceSet = "jvmWithoutJava/main").let {
arrayOf(
it + "com/example/lib/CommonKt.class",
it + "com/example/lib/MainKt.class",
it + "Script.class",
it + "META-INF/new-mpp-lib-with-tests.kotlin_module"
)
},
*kotlinClassesDir(sourceSet = "jvmWithoutJava/test").let {
arrayOf(
it + "com/example/lib/TestCommonCode.class",
it + "com/example/lib/TestWithoutJava.class",
it + "META-INF/new-mpp-lib-with-tests.kotlin_module" // Note: same name as in main
)
}
)
expectedKotlinOutputFiles.forEach { assertFileExists(it) }
// Gradle 6.6+ slightly changed format of xml test results
// If, in the test project, preset name was updated,
// update accordingly test result output for Gradle 6.6+
val testGradleVersion = chooseWrapperVersionOrFinishTest()
val expectedTestResults = if (GradleVersion.version(testGradleVersion) < GradleVersion.version("6.6")) {
"testProject/new-mpp-lib-with-tests/TEST-all-pre6.6.xml"
} else {
"testProject/new-mpp-lib-with-tests/TEST-all.xml"
}
assertTestResults(
expectedTestResults,
"jsNodeTest",
"test", // jvmTest
"${nativeHostTargetName}Test"
)
}
}
@Test
fun testLanguageSettingsClosureForKotlinDsl() =
with(transformNativeTestProjectWithPluginDsl("sample-lib-gradle-kotlin-dsl", gradleVersion, "new-mpp-lib-and-app")) {
gradleBuildScript().appendText(
"\n" + """
kotlin.sourceSets.all {
languageSettings {
languageVersion = "1.4"
apiVersion = "1.4"
}
}
""".trimIndent()
)
listOf("compileCommonMainKotlinMetadata", "compileKotlinJvm6", "compileKotlinNodeJs").forEach {
build(it) {
assertSuccessful()
assertTasksExecuted(":$it")
assertContains("-language-version 1.4", "-api-version 1.4")
}
}
}
@Test
fun testLanguageSettingsApplied() = with(transformNativeTestProject("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
gradleBuildScript().appendText(
"\n" + """
kotlin.sourceSets.all {
it.languageSettings {
// languageVersion = '1.4'
// apiVersion = '1.4'
enableLanguageFeature('InlineClasses')
optIn('kotlin.ExperimentalUnsignedTypes')
optIn('kotlin.contracts.ExperimentalContracts')
progressiveMode = true
}
project.ext.set("kotlin.mpp.freeCompilerArgsForSourceSet.${'$'}name", ["-Xno-inline"])
}
""".trimIndent()
)
listOf(
"compileCommonMainKotlinMetadata", "compileKotlinJvm6", "compileKotlinNodeJs", "compileKotlinLinux64"
).forEach {
build(it) {
assertSuccessful()
assertTasksExecuted(":$it")
assertContains(
"-XXLanguage:+InlineClasses",
"-progressive", "-opt-in=kotlin.ExperimentalUnsignedTypes",
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-Xno-inline"
)
}
}
gradleBuildScript().appendText(
"\n" + """
kotlin.sourceSets.all {
it.languageSettings {
languageVersion = '1.4'
apiVersion = '1.4'
}
}
""".trimIndent()
)
listOf("compileCommonMainKotlinMetadata", "compileKotlinJvm6", "compileKotlinNodeJs").forEach {
build(it) {
assertSuccessful()
assertTasksExecuted(":$it")
assertContains("-language-version 1.4", "-api-version 1.4")
}
}
}
@Test
fun testLanguageSettingsConsistency() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
gradleBuildScript().appendText(
"\n" + """
kotlin.sourceSets {
foo { }
bar { dependsOn foo }
}
""".trimIndent()
)
fun testMonotonousCheck(
initialSetupForSourceSets: String?,
sourceSetConfigurationChange: String,
expectedErrorHint: String
) {
if (initialSetupForSourceSets != null) {
gradleBuildScript().appendText(
"\nkotlin.sourceSets.foo.${initialSetupForSourceSets}\n" + "" +
"kotlin.sourceSets.bar.${initialSetupForSourceSets}",
)
}
gradleBuildScript().appendText("\nkotlin.sourceSets.foo.${sourceSetConfigurationChange}")
build("tasks") {
assertFailed()
assertContains(expectedErrorHint)
}
gradleBuildScript().appendText("\nkotlin.sourceSets.bar.${sourceSetConfigurationChange}")
build("tasks") {
assertSuccessful()
}
}
fun testMonotonousCheck(sourceSetConfigurationChange: String, expectedErrorHint: String): Unit =
testMonotonousCheck(null, sourceSetConfigurationChange, expectedErrorHint)
testMonotonousCheck(
"languageSettings.languageVersion = '1.3'",
"languageSettings.languageVersion = '1.4'",
"The language version of the dependent source set must be greater than or equal to that of its dependency."
)
testMonotonousCheck(
"languageSettings.enableLanguageFeature('InlineClasses')",
"The dependent source set must enable all unstable language features that its dependency has."
)
testMonotonousCheck(
"languageSettings.optIn('kotlin.ExperimentalUnsignedTypes')",
"The dependent source set must use all opt-in annotations that its dependency uses."
)
// check that enabling a bugfix feature and progressive mode or advancing API level
// don't require doing the same for dependent source sets:
gradleBuildScript().appendText(
"\n" + """
kotlin.sourceSets.foo.languageSettings {
apiVersion = '1.4'
enableLanguageFeature('SoundSmartcastForEnumEntries')
progressiveMode = true
}
""".trimIndent()
)
build("tasks") {
assertSuccessful()
}
}
@Test
fun testResolveMppLibDependencyToMetadata() {
val libProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")
val appProject = Project("sample-app", gradleVersion, "new-mpp-lib-and-app")
libProject.build("publish") { assertSuccessful() }
val localRepo = libProject.projectDir.resolve("repo")
val localRepoUri = localRepo.toURI()
with(appProject) {
setupWorkingDir()
val pathPrefix = "metadataDependency: "
gradleBuildScript().appendText(
"\n" + """
repositories { maven { url '$localRepoUri' } }
kotlin.sourceSets {
commonMain {
dependencies {
// add these dependencies to check that they are resolved to metadata
api 'com.example:sample-lib:1.0'
compileOnly 'com.example:sample-lib:1.0'
runtimeOnly 'com.example:sample-lib:1.0'
}
}
}
task('printMetadataFiles') {
doFirst {
['Api', 'Implementation', 'CompileOnly', 'RuntimeOnly'].each { kind ->
def configuration = configurations.getByName("commonMain${'$'}kind" + '$METADATA_CONFIGURATION_NAME_SUFFIX')
configuration.files.each { println '$pathPrefix' + configuration.name + '->' + it.name }
}
}
}
""".trimIndent()
)
val metadataDependencyRegex = "$pathPrefix(.*?)->(.*)".toRegex()
build("printMetadataFiles") {
assertSuccessful()
val expectedFileName = "sample-lib-${KotlinMultiplatformPlugin.METADATA_TARGET_NAME}-1.0.jar"
val paths = metadataDependencyRegex
.findAll(output).map { it.groupValues[1] to it.groupValues[2] }
.filter { (_, f) -> "sample-lib" in f }
.toSet()
Assert.assertEquals(
listOf("Api", "Implementation", "CompileOnly", "RuntimeOnly").map {
"commonMain$it$METADATA_CONFIGURATION_NAME_SUFFIX" to expectedFileName
}.toSet(),
paths
)
}
}
}
@Test
fun testResolveJsPartOfMppLibDependencyToMetadataWithHmpp() =
testResolveJsPartOfMppLibDependencyToMetadata(hmppWoCompatibilityMetadataArtifact)
@Test
fun testResolveJsPartOfMppLibDependencyToMetadataWithHmppAndCompatibilityMetadataArtifact() =
testResolveJsPartOfMppLibDependencyToMetadata(hmppWithCompatibilityMetadataArtifact)
@Test
fun testResolveJsPartOfMppLibDependencyToMetadataWithoutHmpp() =
testResolveJsPartOfMppLibDependencyToMetadata(noHMPP)
private fun testResolveJsPartOfMppLibDependencyToMetadata(hmppFlags: HmppFlags) {
val libProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")
val appProject = Project("sample-app", gradleVersion, "new-mpp-lib-and-app")
val buildOptions = hmppFlags.buildOptions
libProject.build(
"publish",
options = buildOptions.copy(jsCompilerType = BOTH)
) {
assertSuccessful()
}
val localRepo = libProject.projectDir.resolve("repo")
val localRepoUri = localRepo.toURI()
with(appProject) {
setupWorkingDir()
val pathPrefix = "metadataDependency: "
gradleBuildScript().appendText(
"\n" + """
repositories { maven { url '$localRepoUri' } }
kotlin.sourceSets {
nodeJsMain {
dependencies {
// add these dependencies to check that they are resolved to metadata
api 'com.example:sample-lib-nodejs:1.0'
implementation 'com.example:sample-lib-nodejs:1.0'
compileOnly 'com.example:sample-lib-nodejs:1.0'
runtimeOnly 'com.example:sample-lib-nodejs:1.0'
}
}
}
task('printMetadataFiles') {
doFirst {
['Api', 'Implementation', 'CompileOnly', 'RuntimeOnly'].each { kind ->
def configuration = configurations.getByName("nodeJsMain${'$'}kind" + '$METADATA_CONFIGURATION_NAME_SUFFIX')
configuration.files.each { println '$pathPrefix' + configuration.name + '->' + it.name }
}
}
}
""".trimIndent()
)
val metadataDependencyRegex = "$pathPrefix(.*?)->(.*)".toRegex()
build(
"printMetadataFiles",
options = buildOptions.copy(jsCompilerType = IR)
) {
assertSuccessful()
val expectedFileName = "sample-lib-nodejsir-1.0.klib"
val paths = metadataDependencyRegex
.findAll(output).map { it.groupValues[1] to it.groupValues[2] }
.filter { (_, f) -> "sample-lib" in f }
.toSet()
Assert.assertEquals(
listOf("Api", "Implementation", "CompileOnly", "RuntimeOnly").map {
"nodeJsMain$it$METADATA_CONFIGURATION_NAME_SUFFIX" to expectedFileName
}.toSet(),
paths
)
}
}
}
@Test
fun testResolveMppProjectDependencyToMetadata() {
val libProject = Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")
val appProject = Project("sample-app", gradleVersion, "new-mpp-lib-and-app")
val pathPrefix = "metadataDependency: "
with(appProject) {
setupWorkingDir()
libProject.setupWorkingDir()
libProject.projectDir.copyRecursively(projectDir.resolve(libProject.projectDir.name))
gradleBuildScript(libProject.projectDir.name).modify {
it.lines().dropLast(5).joinToString(separator = "\n")
}
projectDir.resolve("settings.gradle").appendText("\ninclude '${libProject.projectDir.name}'")
gradleBuildScript().modify {
it.replace("'com.example:sample-lib:1.0'", "project(':${libProject.projectDir.name}')") +
"\n" + """
task('printMetadataFiles') {
doFirst {
configurations.getByName('commonMainImplementation$METADATA_CONFIGURATION_NAME_SUFFIX')
.files.each { println '$pathPrefix' + it.name }
}
}
""".trimIndent()
}
build("printMetadataFiles") {
assertSuccessful()
assertContains(pathPrefix + "sample-lib-metadata-1.0.jar")
}
}
}
@Test
fun testPublishingOnlySupportedNativeTargets() = with(transformNativeTestProject("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
val publishedVariant = nativeHostTargetName
val nonPublishedVariant = unsupportedNativeTargets[0]
build("publish") {
assertSuccessful()
assertFileExists("repo/com/example/sample-lib-$publishedVariant/1.0/sample-lib-$publishedVariant-1.0.klib")
assertNoSuchFile("repo/com/example/sample-lib-$nonPublishedVariant") // check that no artifacts are published for that variant
// but check that the module metadata contains all variants:
val gradleModuleMetadata = projectDir.resolve("repo/com/example/sample-lib/1.0/sample-lib-1.0.module").readText()
assertTrue(""""name": "linux64ApiElements-published"""" in gradleModuleMetadata)
assertTrue(""""name": "mingw64ApiElements-published"""" in gradleModuleMetadata)
assertTrue(""""name": "macos64ApiElements-published"""" in gradleModuleMetadata)
}
}
@Test
fun testNonMppConsumersOfLibraryPublishedWithNoMetadataOptIn() {
val repoDir = with(transformNativeTestProject("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
build(
"publish",
options = hmppWithCompatibilityMetadataArtifact.buildOptions
) { assertSuccessful() }
projectDir.resolve("repo")
}
with(Project("sample-old-style-app", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
gradleBuildScript().appendText("\nallprojects { repositories { maven { url '${repoDir.toURI()}' } } }")
gradleBuildScript("app-jvm").modify { it.replace("com.example:sample-lib:", "com.example:sample-lib-jvm6:") }
gradleBuildScript("app-js").modify { it.replace("com.example:sample-lib:", "com.example:sample-lib-nodejs:") }
build("assemble", "run") {
assertSuccessful()
assertTasksExecuted(":app-common:compileKotlinCommon", ":app-jvm:compileKotlin", ":app-jvm:run", ":app-js:compileKotlin2Js")
}
// Then run again without even reading the metadata from the repo:
projectDir.resolve("settings.gradle").modify { it.replace("enableFeaturePreview('GRADLE_METADATA')", "") }
build("assemble", "run", "--rerun-tasks") {
assertSuccessful()
assertTasksExecuted(":app-common:compileKotlinCommon", ":app-jvm:compileKotlin", ":app-jvm:run", ":app-js:compileKotlin2Js")
}
}
with(Project("sample-app-without-kotlin", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
gradleBuildScript().modify {
it.replace("com.example:sample-lib:1.0", "com.example:sample-lib-jvm6:1.0") + "\n" + """
repositories { maven { url '${repoDir.toURI()}' } }
""".trimIndent()
}
build("run") {
assertSuccessful()
assertTasksExecuted(":compileJava", ":run")
}
// Then run again without even reading the metadata from the repo:
projectDir.resolve("settings.gradle").modify { it.replace("enableFeaturePreview('GRADLE_METADATA')", "") }
build("run", "--rerun-tasks") {
assertSuccessful()
assertTasksExecuted(":compileJava", ":run")
}
}
}
@Test
fun testOptionalExpectations() = with(transformNativeTestProject("new-mpp-lib-with-tests", gradleVersion)) {
projectDir.resolve("src/commonMain/kotlin/Optional.kt").writeText(
"""
@file:Suppress("OPT_IN_USAGE_ERROR", "EXPERIMENTAL_API_USAGE_ERROR")
@OptionalExpectation
expect annotation class Optional(val value: String)
@Optional("optionalAnnotationValue")
class OptionalCommonUsage
""".trimIndent()
)
build("compileCommonMainKotlinMetadata") {
assertSuccessful()
val compilerArgsLine = output.lines().singleOrNull { ":compileCommonMainKotlinMetadata Kotlin compiler args" in it }
assertNotNull(compilerArgsLine, "The debug log should contain the compiler args for the task :compileCommonMainKotlinMetadata")
val args = compilerArgsLine.split(" ")
val xCommonSourcesArg = args.singleOrNull { it.startsWith("-Xcommon-sources=") }
assertNotNull(xCommonSourcesArg, "The compiler args for K2Metadata should contain the -Xcommon-sources argument")
val xCommonSourcesFiles = xCommonSourcesArg.substringAfter("-Xcommon-sources=").split(",")
assertTrue { xCommonSourcesFiles.any { it.endsWith("Optional.kt") } }
}
build("compileKotlinJvmWithoutJava", "compileKotlinLinux64") {
assertSuccessful()
assertFileExists(targetClassesDir("jvmWithoutJava") + "OptionalCommonUsage.class")
}
val optionalImplText = "\n" + """
@Optional("should fail, see KT-25196")
class OptionalPlatformUsage
""".trimIndent()
projectDir.resolve("src/jvmWithoutJavaMain/kotlin/OptionalImpl.kt").writeText(optionalImplText)
build("compileKotlinJvmWithoutJava") {
assertFailed()
assertContains("Declaration annotated with '@OptionalExpectation' can only be used in common module sources", ignoreCase = true)
}
projectDir.resolve("src/linux64Main/kotlin/").also {
it.mkdirs()
it.resolve("OptionalImpl.kt").writeText(optionalImplText)
}
build("compileKotlinLinux64") {
assertFailed()
assertContains("Declaration annotated with '@OptionalExpectation' can only be used in common module sources", ignoreCase = true)
}
}
@Test
fun testSourceJars() = with(transformNativeTestProject("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
build("publish") {
assertSuccessful()
val groupDir = projectDir.resolve("repo/com/example/")
val targetArtifactIdAppendices = listOf(null, "jvm6", "nodejs", "linux64")
val sourceJarSourceRoots = targetArtifactIdAppendices.associateWith { artifact ->
val sourcesJarPath = if (artifact != null) "sample-lib-$artifact/1.0/sample-lib-$artifact-1.0-sources.jar"
else "sample-lib/1.0/sample-lib-1.0-sources.jar"
val sourcesJar = JarFile(groupDir.resolve(sourcesJarPath))
val sourcesDirs = sourcesJar.entries().asSequence().map { it.name.substringBefore("/") }.toSet() - "META-INF"
sourcesDirs
}
assertEquals(
setOf("commonMain", "jvm6Main", "linux64Main", "linuxMipsel32Main", "macos64Main", "mingw64Main", "mingw86Main", "nodeJsMain", "wasmMain"),
sourceJarSourceRoots[null]
)
assertEquals(setOf("commonMain", "jvm6Main"), sourceJarSourceRoots["jvm6"])
assertEquals(setOf("commonMain", "nodeJsMain"), sourceJarSourceRoots["nodejs"])
assertEquals(setOf("commonMain", "linux64Main"), sourceJarSourceRoots["linux64"])
}
}
@Test
fun testConsumeMppLibraryFromNonKotlinProject() {
val libRepo = with(transformNativeTestProject("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
build("publish") { assertSuccessful() }
projectDir.resolve("repo")
}
with(transformNativeTestProject("sample-app-without-kotlin", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
gradleBuildScript().appendText("\nrepositories { maven { url '${libRepo.toURI()}' } }")
build("assemble") {
assertSuccessful()
assertTasksExecuted(":compileJava")
assertFileExists("build/classes/java/main/A.class")
}
}
}
@Test
fun testPublishMultimoduleProjectWithMetadata() {
val libProject = transformNativeTestProject("sample-lib", gradleVersion, "new-mpp-lib-and-app")
libProject.setupWorkingDir()
transformNativeTestProject("sample-external-lib", gradleVersion, "new-mpp-lib-and-app").apply {
setupWorkingDir()
// Publish it into local repository of adjacent lib:
gradleBuildScript().appendText(
"\n" + """
publishing {
repositories {
maven { url "file://${'$'}{rootProject.projectDir.absolutePath.replace('\\', '/')}/../sample-lib/repo" }
}
}
""".trimIndent()
)
build("publish") {
assertSuccessful()
}
}
val appProject = transformNativeTestProject("sample-app", gradleVersion, "new-mpp-lib-and-app")
with(libProject) {
setupWorkingDir()
appProject.setupWorkingDir(false)
appProject.projectDir.copyRecursively(projectDir.resolve("sample-app"))
gradleBuildScript("sample-app").modify {
it.lines().dropLast(5).joinToString(separator = "\n")
}
gradleSettingsScript().writeText("include 'sample-app'") // disables feature preview 'GRADLE_METADATA', resets rootProject name
gradleBuildScript("sample-app").modify {
it.replace("'com.example:sample-lib:1.0'", "project(':')") + "\n" + """
apply plugin: 'maven-publish'
group = "com.exampleapp"
version = "1.0"
publishing {
repositories {
maven { url "file://${'$'}{rootProject.projectDir.absolutePath.replace('\\', '/')}/repo" }
}
}
""".trimIndent()
}
gradleSettingsScript().appendText("\nenableFeaturePreview(\"GRADLE_METADATA\")")
// Add a dependency that is resolved with metadata:
gradleBuildScript("sample-app").appendText(
"\n" + """
repositories {
maven { url "file://${'$'}{rootProject.projectDir.absolutePath.replace('\\', '/')}/repo" }
}
dependencies {
commonMainApi 'com.external.dependency:external:1.2.3'
}
""".trimIndent()
)
gradleBuildScript().appendText(
"\n" + """
publishing {
publications {
jvm6 {
groupId = "foo"
artifactId = "bar"
version = "42"
}
}
}
""".trimIndent()
)
gradleBuildScript().appendText(
"\n" + """
publishing {
publications {
kotlinMultiplatform {
// KT-29485
artifactId = 'sample-lib-multiplatform'
}
}
}
""".trimIndent()
)
build("clean", "publish") {
assertSuccessful()
assertFileContains(
"repo/com/exampleapp/sample-app-nodejs/1.0/sample-app-nodejs-1.0.pom",
"<groupId>com.example</groupId>",
"<artifactId>sample-lib-nodejs</artifactId>",
"<version>1.0</version>"
)
assertFileContains(
"repo/com/exampleapp/sample-app-jvm8/1.0/sample-app-jvm8-1.0.pom",
"<groupId>foo</groupId>",
"<artifactId>bar</artifactId>",
"<version>42</version>"
)
assertFileContains(
"repo/com/exampleapp/sample-app-jvm8/1.0/sample-app-jvm8-1.0.pom",
"<groupId>com.external.dependency</groupId>",
"<artifactId>external-jvm6</artifactId>",
"<version>1.2.3</version>"
)
// Check that, despite the rewritten POM, the module metadata contains the original dependency:
val moduleMetadata = projectDir.resolve("repo/com/exampleapp/sample-app-jvm8/1.0/sample-app-jvm8-1.0.module").readText()
.replace("\\s+".toRegex(), "").replace("\n", "")
assertTrue { "\"group\":\"com.example\",\"module\":\"sample-lib-multiplatform\"" in moduleMetadata }
assertTrue { "\"group\":\"com.external.dependency\",\"module\":\"external\"" in moduleMetadata }
}
// Check that a user can disable rewriting of MPP dependencies in the POMs:
build("publish", "-Pkotlin.mpp.keepMppDependenciesIntactInPoms=true") {
assertSuccessful()
assertFileContains(
"repo/com/exampleapp/sample-app-nodejs/1.0/sample-app-nodejs-1.0.pom",
"<groupId>com.example</groupId>",
"<artifactId>sample-lib-multiplatform</artifactId>",
"<version>1.0</version>"
)
assertFileContains(
"repo/com/exampleapp/sample-app-jvm8/1.0/sample-app-jvm8-1.0.pom",
"<groupId>com.example</groupId>",
"<artifactId>sample-lib-multiplatform</artifactId>",
"<version>1.0</version>"
)
assertFileContains(
"repo/com/exampleapp/sample-app-jvm8/1.0/sample-app-jvm8-1.0.pom",
"<groupId>com.external.dependency</groupId>",
"<artifactId>external</artifactId>",
"<version>1.2.3</version>"
)
}
}
}
@Test
fun testMppBuildWithCompilerPlugins() = with(transformNativeTestProject("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
val printOptionsTaskName = "printCompilerPluginOptions"
val argsMarker = "=args=>"
val classpathMarker = "=cp=>"
val compilerPluginArgsRegex = "(\\w+)${Regex.escape(argsMarker)}(.*)".toRegex()
val compilerPluginClasspathRegex = "(\\w+)${Regex.escape(classpathMarker)}(.*)".toRegex()
gradleBuildScript().appendText(
"\n" + """
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-allopen:${'$'}kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-noarg:${'$'}kotlin_version"
}
}
apply plugin: 'kotlin-allopen'
apply plugin: 'kotlin-noarg'
allOpen { annotation 'com.example.Annotation' }
noArg { annotation 'com.example.Annotation' }
task $printOptionsTaskName {
// if the tasks are not configured during evaluation phase, configuring them during execution
// leads to new dependencies unsuccessfully added to the resolved compilerPluginsClasspath configuration
kotlin.targets.all { compilations.all { /*force to configure the*/ compileKotlinTask } }
doFirst {
kotlin.sourceSets.each { sourceSet ->
def args = sourceSet.languageSettings.compilerPluginArguments
def cp = sourceSet.languageSettings.compilerPluginClasspath.files
println sourceSet.name + '$argsMarker' + args
println sourceSet.name + '$classpathMarker' + cp
}
}
}
""".trimIndent()
)
projectDir.resolve("src/commonMain/kotlin/Annotation.kt").writeText(
"""
package com.example
annotation class Annotation
""".trimIndent()
)
projectDir.resolve("src/commonMain/kotlin/Annotated.kt").writeText(
"""
package com.example
@Annotation
open class Annotated(var y: Int) { var x = 2 }
""".trimIndent()
)
// TODO once Kotlin/Native properly supports compiler plugins, move this class to the common sources
listOf("jvm6", "nodeJs").forEach {
projectDir.resolve("src/${it}Main/kotlin/Override.kt").writeText(
"""
package com.example
@Annotation
class Override : Annotated(0) {
override var x = 3
}
""".trimIndent()
)
}
// Do not use embeddable compiler in Kotlin/Native, otherwise it would effectively enable allopen & noarg plugins for Native, and
// we'd be testing that the latest versions of allopen/noarg work with the fixed version of Kotlin/Native (defined in the root
// build.gradle.kts), which is generally not guaranteed.
build("assemble", "-Pkotlin.native.useEmbeddableCompilerJar=false", printOptionsTaskName) {
assertSuccessful()
assertTasksExecuted(*listOf("Jvm6", "NodeJs", "Linux64").map { ":compileKotlin$it" }.toTypedArray())
assertFileExists("build/classes/kotlin/jvm6/main/com/example/Annotated.class")
assertFileExists("build/classes/kotlin/jvm6/main/com/example/Override.class")
assertFileContains("build/classes/kotlin/nodeJs/main/sample-lib.js", "Override")
val (compilerPluginArgsBySourceSet, compilerPluginClasspathBySourceSet) =
listOf(compilerPluginArgsRegex, compilerPluginClasspathRegex)
.map { marker ->
marker.findAll(output).associate { it.groupValues[1] to it.groupValues[2] }
}
// TODO once Kotlin/Native properly supports compiler plugins, expand this to all source sets:
listOf("commonMain", "commonTest", "jvm6Main", "jvm6Test", "nodeJsMain", "nodeJsTest").forEach {
val expectedArgs = "[plugin:org.jetbrains.kotlin.allopen:annotation=com.example.Annotation, " +
"plugin:org.jetbrains.kotlin.noarg:annotation=com.example.Annotation]"
assertEquals(expectedArgs, compilerPluginArgsBySourceSet[it], "Expected $expectedArgs as plugin args for $it")
assertTrue { compilerPluginClasspathBySourceSet[it]!!.contains("kotlin-allopen") }
assertTrue { compilerPluginClasspathBySourceSet[it]!!.contains("kotlin-noarg") }
}
}
}
@Test
fun testJsDceInMpp() = with(Project("new-mpp-js-dce", gradleVersion)) {
build("runRhino", options = defaultBuildOptions().copy(warningMode = WarningMode.Summary)) {
assertSuccessful()
assertTasksExecuted(":mainProject:processDceBrowserKotlinJs")
val pathPrefix = "mainProject/build/kotlin-js-min/"
assertFileExists("$pathPrefix/exampleapp.js.map")
assertFileExists("$pathPrefix/examplelib.js.map")
assertFileContains("$pathPrefix/exampleapp.js.map", "\"../../src/browserMain/kotlin/exampleapp/main.kt\"")
assertFileExists("$pathPrefix/kotlin.js")
assertTrue(fileInWorkingDir("$pathPrefix/kotlin.js").length() < 500 * 1000, "Looks like kotlin.js file was not minified by DCE")
}
}
@Test
fun testDefaultSourceSetsDsl() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
val testOutputPrefix = "# default source set "
val testOutputRegex = Regex("${Regex.escape(testOutputPrefix)} (.*?) (.*?) (.*)")
gradleBuildScript().appendText(
"\n" + """
kotlin.targets.each { target ->
target.compilations.each { compilation ->
println "$testOutputPrefix ${'$'}{target.name} ${'$'}{compilation.name} ${'$'}{compilation.defaultSourceSet.name}"
}
}
""".trimIndent()
)
build {
assertSuccessful()
val actualDefaultSourceSets = testOutputRegex.findAll(output).mapTo(mutableSetOf()) {
it.groupValues.let { (_, target, compilation, sourceSet) -> Triple(target, compilation, sourceSet) }
}
val expectedDefaultSourceSets = listOf(
"jvm6", "nodeJs", "mingw64", "mingw86", "linux64", "macos64", "linuxMipsel32", "wasm"
).flatMapTo(mutableSetOf()) { target ->
listOf("main", "test").map { compilation ->
Triple(target, compilation,
"$target${compilation.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}")
}
} + Triple("metadata", "main", "commonMain")
assertEquals(expectedDefaultSourceSets, actualDefaultSourceSets)
}
}
@Test
fun testDependenciesDsl() = with(transformProjectWithPluginsDsl("newMppDependenciesDsl")) {
val originalBuildscriptContent = gradleBuildScript("app").readText()
fun testDependencies() = testResolveAllConfigurations("app") {
assertContains(">> :app:testNonTransitiveStringNotationApiDependenciesMetadata --> junit-4.13.2.jar")
assertEquals(
1,
(Regex.escape(">> :app:testNonTransitiveStringNotationApiDependenciesMetadata") + " .*").toRegex().findAll(output).count()
)
assertContains(">> :app:testNonTransitiveDependencyNotationApiDependenciesMetadata --> kotlin-reflect-${defaultBuildOptions().kotlinVersion}.jar")
assertEquals(
1,
(Regex.escape(">> :app:testNonTransitiveStringNotationApiDependenciesMetadata") + " .*").toRegex().findAll(output)
.count()
)
assertContains(">> :app:testExplicitKotlinVersionApiDependenciesMetadata --> kotlin-reflect-1.3.0.jar")
assertContains(">> :app:testExplicitKotlinVersionImplementationDependenciesMetadata --> kotlin-reflect-1.2.71.jar")
assertContains(">> :app:testExplicitKotlinVersionCompileOnlyDependenciesMetadata --> kotlin-reflect-1.2.70.jar")
assertContains(">> :app:testExplicitKotlinVersionRuntimeOnlyDependenciesMetadata --> kotlin-reflect-1.2.60.jar")
assertContains(">> :app:testProjectWithConfigurationApiDependenciesMetadata --> output.txt")
}
testDependencies()
// Then run with Gradle Kotlin DSL; the build script needs some correction to be a valid GK DSL script:
gradleBuildScript("app").run {
modify {
originalBuildscriptContent
.replace(": ", " = ")
.replace("def ", " val ")
.replace("new File(cacheRedirectorFile)", "File(cacheRedirectorFile)")
.replace("id \"org.jetbrains.kotlin.test.fixes.android\"", "id(\"org.jetbrains.kotlin.test.fixes.android\")")
}
renameTo(projectDir.resolve("app/build.gradle.kts"))
}
testDependencies()
}
@Test
fun testMultipleTargetsSamePlatform() = with(Project("newMppMultipleTargetsSamePlatform", gradleVersion)) {
testResolveAllConfigurations("app") {
assertContains(">> :app:junitCompileClasspath --> lib-junit.jar")
assertContains(">> :app:junitCompileClasspath --> junit-4.13.2.jar")
assertContains(">> :app:mixedJunitCompileClasspath --> lib-junit.jar")
assertContains(">> :app:mixedJunitCompileClasspath --> junit-4.13.2.jar")
assertContains(">> :app:testngCompileClasspath --> lib-testng.jar")
assertContains(">> :app:testngCompileClasspath --> testng-6.14.3.jar")
assertContains(">> :app:mixedTestngCompileClasspath --> lib-testng.jar")
assertContains(">> :app:mixedTestngCompileClasspath --> testng-6.14.3.jar")
}
}
@Test
fun testUnusedSourceSetsReport() = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) {
setupWorkingDir()
gradleBuildScript().appendText("\nkotlin { sourceSets { foo { } } }")
build {
assertSuccessful()
assertContains(UnusedSourceSetsChecker.WARNING_PREFIX_ONE, UnusedSourceSetsChecker.WARNING_INTRO)
}
gradleBuildScript().appendText("\nkotlin { sourceSets { bar { dependsOn foo } } }")
build {
assertSuccessful()
assertContains(UnusedSourceSetsChecker.WARNING_PREFIX_MANY, UnusedSourceSetsChecker.WARNING_INTRO)
}
gradleBuildScript().appendText("\nkotlin { sourceSets { jvm6Main { dependsOn bar } } }")
build {
assertSuccessful()
assertNotContains(
UnusedSourceSetsChecker.WARNING_PREFIX_ONE,
UnusedSourceSetsChecker.WARNING_PREFIX_MANY,
UnusedSourceSetsChecker.WARNING_INTRO
)
}
}
// https://youtrack.jetbrains.com/issue/KT-48436
@Test
fun testUnusedSourceSetsReportAndroid() = with(Project("new-mpp-android", gradleVersion)) {
setupWorkingDir()
build(
"assembleDebug",
// https://issuetracker.google.com/issues/152187160
options = defaultBuildOptions().copy(androidGradlePluginVersion = AGPVersion.v4_2_0)
) {
assertSuccessful()
assertNotContains(
UnusedSourceSetsChecker.WARNING_PREFIX_ONE,
UnusedSourceSetsChecker.WARNING_PREFIX_MANY,
UnusedSourceSetsChecker.WARNING_INTRO
)
}
}
@Test
fun testIncrementalCompilation() = with(Project("new-mpp-jvm-js-ic", gradleVersion)) {
build("build") {
assertSuccessful()
}
val libCommonClassKt = projectDir.getFileByName("LibCommonClass.kt")
val libCommonClassJsKt = projectDir.getFileByName("LibCommonClassJs.kt")
libCommonClassJsKt.modify { it.checkedReplace("platform = \"js\"", "platform = \"Kotlin/JS\"") }
val libCommonClassJvmKt = projectDir.getFileByName("LibCommonClassJvm.kt")
libCommonClassJvmKt.modify { it.checkedReplace("platform = \"jvm\"", "platform = \"Kotlin/JVM\"") }
build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(libCommonClassKt, libCommonClassJsKt, libCommonClassJvmKt))
}
val libJvmPlatformUtilKt = projectDir.getFileByName("libJvmPlatformUtil.kt")
libJvmPlatformUtilKt.modify {
it.checkedReplace("fun libJvmPlatformUtil", "inline fun libJvmPlatformUtil")
}
build("build") {
assertSuccessful()
val useLibJvmPlatformUtilKt = projectDir.getFileByName("useLibJvmPlatformUtil.kt")
assertCompiledKotlinSources(project.relativize(libJvmPlatformUtilKt, useLibJvmPlatformUtilKt))
}
val libJsPlatformUtilKt = projectDir.getFileByName("libJsPlatformUtil.kt")
libJsPlatformUtilKt.modify {
it.checkedReplace("fun libJsPlatformUtil", "inline fun libJsPlatformUtil")
}
build("build") {
assertSuccessful()
val useLibJsPlatformUtilKt = projectDir.getFileByName("useLibJsPlatformUtil.kt")
assertCompiledKotlinSources(project.relativize(libJsPlatformUtilKt, useLibJsPlatformUtilKt))
}
}
@Test
fun testPomRewritingInSinglePlatformProject() = with(Project("kt-27059-pom-rewriting")) {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
val groupDir = "build/repo/com/example/"
build(":mpp-lib:publish", options = defaultBuildOptions().copy(warningMode = WarningMode.Summary)) {
assertSuccessful()
assertFileExists(groupDir + "mpp-lib")
assertFileExists(groupDir + "mpp-lib-myjvm")
}
fun doTestPomRewriting(mppProjectDependency: Boolean, keepPomIntact: Boolean? = null) {
val params = mutableListOf("clean", ":jvm-app:publish", ":js-app:publish").apply {
if (mppProjectDependency)
add("-PmppProjectDependency=true")
if (keepPomIntact == true)
add("-Pkotlin.mpp.keepMppDependenciesIntactInPoms=true")
}.toTypedArray()
build(*params, options = defaultBuildOptions().copy(warningMode = WarningMode.Summary)) {
assertSuccessful()
assertTasksExecuted(":jvm-app:publishMainPublicationToMavenRepository")
assertTasksExecuted(":js-app:publishMainPublicationToMavenRepository")
val jvmModuleDir = groupDir + "jvm-app/1.0/"
val jsModuleDir = groupDir + "js-app/1.0/"
val jvmPom = fileInWorkingDir(jvmModuleDir + "jvm-app-1.0.pom").readText().replace("\\s+".toRegex(), "")
val jsPom = fileInWorkingDir(jsModuleDir + "js-app-1.0.pom").readText().replace("\\s+".toRegex(), "")
if (keepPomIntact != true) {
assertTrue("The JVM POM should contain the dependency on 'mpp-lib' rewritten as 'mpp-lib-myjvm'") {
jvmPom.contains(
"<groupId>com.example</groupId><artifactId>mpp-lib-myjvm</artifactId><version>1.0</version><scope>compile</scope>"
)
}
assertTrue("The JS POM should contain the dependency on 'mpp-lib' rewritten as 'mpp-lib-js'") {
jsPom.contains(
"<groupId>com.example</groupId><artifactId>mpp-lib-js</artifactId><version>1.0</version><scope>compile</scope>"
)
}
} else {
assertTrue("The JVM POM should contain the original dependency on 'mpp-lib'") {
jvmPom.contains(
"<groupId>com.example</groupId><artifactId>mpp-lib</artifactId><version>1.0</version><scope>compile</scope>"
)
}
assertTrue("The JS POM should contain the original dependency on 'mpp-lib'") {
jsPom.contains(
"<groupId>com.example</groupId><artifactId>mpp-lib</artifactId><version>1.0</version><scope>compile</scope>"
)
}
}
}
}
doTestPomRewriting(mppProjectDependency = false)
doTestPomRewriting(mppProjectDependency = true)
// This case doesn't work and never did; TODO investigate KT-29975
// doTestPomRewriting(mppProjectDependency = true, legacyPublishing = true)
// Also check that the flag for keeping POMs intact works:
doTestPomRewriting(mppProjectDependency = false, keepPomIntact = true)
}
@Test
fun testAssociateCompilations() {
testAssociateCompilationsImpl()
}
private fun testAssociateCompilationsImpl() {
with(Project("new-mpp-associate-compilations")) {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
val tasks = listOf("jvm", "js", "linux64").map {
":compileIntegrationTestKotlin${
it.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(
Locale.getDefault()
) else it.toString()
}
}" }
build(
*tasks.toTypedArray()
) {
assertSuccessful()
assertTasksExecuted(*tasks.toTypedArray())
// JVM:
checkBytecodeContains(
projectDir.resolve("build/classes/kotlin/jvm/integrationTest/com/example/HelloIntegrationTest.class"),
"Hello.internalFun\$new_mpp_associate_compilations",
"HelloTest.internalTestFun\$new_mpp_associate_compilations"
)
assertFileExists("build/classes/kotlin/jvm/integrationTest/META-INF/new-mpp-associate-compilations.kotlin_module")
// JS:
assertFileExists(
"build/classes/kotlin/js/integrationTest/new-mpp-associate-compilations_integrationTest.js"
)
// Native:
assertFileExists("build/classes/kotlin/linux64/integrationTest/klib/new-mpp-associate-compilations_integrationTest.klib")
}
gradleBuildScript().appendText(
"\nkotlin.sourceSets { getByName(\"commonTest\").requiresVisibilityOf(getByName(\"commonIntegrationTest\")) }"
)
build {
assertFailed()
assertContains(UnsatisfiedSourceSetVisibilityException::class.java.simpleName)
}
}
}
@Test
fun testTestRunsApi() = with(Project("new-mpp-associate-compilations")) {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
// TOOD: add Kotlin/JS tests once they can be tested without much performance overhead
val targetsToTest = listOf("jvm", nativeHostTargetName) + listOf("ios").takeIf { HostManager.hostIsMac }.orEmpty()
val testTasks = targetsToTest.flatMap { listOf(":${it}Test", ":${it}IntegrationTest") }.toTypedArray()
build(*testTasks) {
assertSuccessful()
assertTasksExecuted(
*testTasks,
":compileIntegrationTestKotlinJvm",
":linkIntegrationDebugTest${nativeHostTargetName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}"
)
fun checkUnitTestOutput(targetName: String) {
val classReportHtml = projectDir
.resolve("build/reports/tests/${targetName}Test/classes/com.example.HelloTest.html")
.readText()
assertTrue("secondTest" !in classReportHtml, "Test report should not contain 'secondTest':\n$classReportHtml")
}
targetsToTest.forEach {
checkUnitTestOutput(it)
}
fun checkIntegrationTestOutput(targetName: String) {
val classReportHtml = projectDir
.resolve("build/reports/tests/${targetName}IntegrationTest/classes/com.example.HelloIntegrationTest.html")
.readText()
assertTrue("test[$targetName]" in classReportHtml, "Test report should contain 'test[$targetName]':\n$classReportHtml")
assertTrue("secondTest" !in classReportHtml, "Test report should not contain 'secondTest':\n$classReportHtml")
assertTrue("thirdTest" !in classReportHtml, "Test report should not contain 'thirdTest':\n$classReportHtml")
}
targetsToTest.forEach {
checkIntegrationTestOutput(it)
}
}
}
@Test
fun testKlibsWithTheSameProjectName() = with(transformProjectWithPluginsDsl("new-mpp-klibs-with-same-name")) {
// KT-36721.
build("assemble") {
assertSuccessful()
assertTasksExecuted(
":foo:foo:compileKotlinJs",
":foo:foo:compileKotlinLinux",
":foo:compileKotlinJs",
":foo:compileKotlinLinux",
":compileKotlinJs",
":compileKotlinLinux"
)
fun getManifest(relativePath: String): Properties =
with(ZipFile(projectDir.resolve(relativePath))) {
return this.getInputStream(getEntry("default/manifest")).use { stream ->
Properties().apply { load(stream) }
}
}
val interopManifest = getManifest("foo/build/classes/kotlin/linux/main/cinterop/foo-cinterop-bar.klib")
assertEquals("org.sample.one:foo-cinterop-bar", interopManifest[KLIB_PROPERTY_UNIQUE_NAME])
val nativeManifest = getManifest("foo/build/classes/kotlin/linux/main/klib/foo.klib")
assertEquals("org.sample.one:foo", nativeManifest[KLIB_PROPERTY_UNIQUE_NAME])
// Check the short name that is used as a prefix in generated ObjC headers.
assertEquals("foo", nativeManifest[KLIB_PROPERTY_SHORT_NAME])
val jsManifest = projectDir.resolve("foo/build/classes/kotlin/js/main/default/manifest")
.inputStream().use { stream ->
Properties().apply { load(stream) }
}
assertEquals("org.sample.one:foo", jsManifest[KLIB_PROPERTY_UNIQUE_NAME])
}
}
@Test
fun testNativeCompilationShouldNotProduceAnyWarningsForAssociatedCompilations() {
with(Project("native-common-dependencies-warning", minLogLevel = LogLevel.INFO)) {
setupWorkingDir()
build("help") {
assertSuccessful()
assertNotContains("A compileOnly dependency is used in the Kotlin/Native target '${detectNativeEnabledCompilation()}':")
}
}
}
@Test
fun testNativeCompilationShouldProduceWarningOnCompileOnlyCommonDependency() {
with(Project("native-common-dependencies-warning", minLogLevel = LogLevel.INFO)) {
setupWorkingDir()
gradleBuildScript().modify {
it.replaceFirst("//compileOnly:", "")
}
build("help") {
assertSuccessful()
assertContains("A compileOnly dependency is used in the Kotlin/Native target '${detectNativeEnabledCompilation()}':")
}
}
}
@Test
fun testNativeCompilationCompileOnlyDependencyWarningCouldBeDisabled() {
with(Project("native-common-dependencies-warning", minLogLevel = LogLevel.INFO)) {
setupWorkingDir()
gradleBuildScript().modify {
it.replaceFirst("//compileOnly:", "")
}
projectDir.resolve("gradle.properties").writeText(
"""
kotlin.native.ignoreIncorrectDependencies = true
""".trimIndent()
)
build("help") {
assertSuccessful()
assertNotContains("A compileOnly dependency is used in the Kotlin/Native target '${detectNativeEnabledCompilation()}':")
}
}
}
@Test
fun testErrorInClasspathMode() {
val classpathModeOptions = defaultBuildOptions().copy(
freeCommandLineArgs = listOf("-Dorg.gradle.kotlin.dsl.provider.mode=classpath")
)
with(Project("kotlin-mpp-classpathMode")) {
build("tasks") {
assertFailed()
assertContains("ERROR DURING CONFIGURATION PHASE")
}
build("tasks", options = classpathModeOptions) {
assertSuccessful()
}
build("listCollectedErrors", options = classpathModeOptions) {
assertSuccessful()
assertContains("Collected 1 exception(s)")
assertContains("ERROR DURING CONFIGURATION PHASE")
}
}
}
@Test
fun testWasmJs() = with(Project(
"new-mpp-wasm-js",
// TODO: this test fails with deprecation error on Gradle <7.0
// Should be fixed via planned fixes in Kotlin/JS plugin: https://youtrack.jetbrains.com/issue/KFC-252
gradleVersionRequirement = GradleVersionRequired.AtLeast(TestVersions.Gradle.G_7_0)
)) {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
build("build") {
assertSuccessful()
assertTasksExecuted(":compileKotlinJs")
assertTasksExecuted(":compileKotlinWasm")
val outputPrefix = "build/js/packages/"
val jsOutput = outputPrefix + "redefined-js-module-name/kotlin/"
assertFileExists(jsOutput + "redefined-js-module-name.js")
val wasmOutput = outputPrefix + "redefined-wasm-module-name/kotlin/"
assertFileExists(wasmOutput + "redefined-wasm-module-name.mjs")
assertFileExists(wasmOutput + "redefined-wasm-module-name.wasm")
}
}
private fun testWasmTest(engine: String, name: String, useBinaryen: Boolean) = with(
Project("new-mpp-wasm-test", gradleVersionRequirement = GradleVersionRequired.AtLeast(TestVersions.Gradle.G_7_0))
) {
setupWorkingDir()
gradleBuildScript().modify {
transformBuildScriptWithPluginsDsl(it)
.replace("<JsEngine>", engine)
.replace("<ApplyBinaryen>", if (useBinaryen) "applyBinaryen()" else "")
}
build(":wasm${name}Test") {
assertTasksExecuted(":compileKotlinWasm")
if (useBinaryen) {
assertTasksExecuted(":compileTestProductionExecutableKotlinWasmOptimize")
assertTasksExecuted(":compileTestDevelopmentExecutableKotlinWasmOptimize")
} else {
assertTasksNotExecuted(":compileTestProductionExecutableKotlinWasmOptimize")
assertTasksNotExecuted(":compileTestDevelopmentExecutableKotlinWasmOptimize")
}
assertTasksFailed(":wasm${name}Test")
assertTestResults(
"testProject/new-mpp-wasm-test/TEST-${engine}.xml",
"wasm${name}Test"
)
}
}
@Test
fun testWasmNodeTest() = testWasmTest("nodejs", "Node", useBinaryen = false)
@Test
fun testWasmWithBinaryenNodeTest() = testWasmTest("nodejs", "Node", useBinaryen = true)
@Test
fun testWasmD8Test() = testWasmTest("d8", "D8", useBinaryen = false)
@Test
fun testWasmWithBinaryenD8Test() = testWasmTest("d8", "D8", useBinaryen = true)
@Test
fun testResolveMetadataCompileClasspathKt50925() {
Project("lib", directoryPrefix = "kt-50925-resolve-metadata-compile-classpath").apply {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
build("publish", options = defaultBuildOptions().copy(enableCompatibilityMetadataVariant = false)) {
assertSuccessful()
}
}
Project("app", directoryPrefix = "kt-50925-resolve-metadata-compile-classpath").apply {
setupWorkingDir()
gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
testResolveAllConfigurations { unresolvedConfigurations ->
assertTrue(unresolvedConfigurations.isEmpty(), "Unresolved configurations: $unresolvedConfigurations")
assertContains(">> :metadataCompileClasspath --> lib-metadata-1.0.jar")
assertContains(">> :metadataCompileClasspath --> subproject-metadata.jar")
assertContains(">> :metadataCommonMainCompileClasspath --> lib-metadata-1.0.jar")
assertContains(">> :metadataCommonMainCompileClasspath --> subproject-metadata.jar")
}
}
}
private fun detectNativeEnabledCompilation(): String = when {
HostManager.hostIsLinux -> "linuxX64"
HostManager.hostIsMingw -> "mingwX64"
HostManager.hostIsMac -> "macosX64"
else -> throw AssertionError("Host ${HostManager.host} is not supported for this test")
}
}