[Gradle] Add integration test project 'commonizeMultipleCInteropsWithTests'

Implemented `test multiple cinterops with test source sets and compilations`

This test will assert source set dependencies
and shared native compilations.

^KT-47775
^KT-47053
This commit is contained in:
sebastian.sellmair
2021-07-23 11:30:08 +02:00
committed by Space
parent 4ae51e1d02
commit db10732d6c
28 changed files with 551 additions and 0 deletions
@@ -24,6 +24,7 @@ dependencies {
testImplementation(project(":kotlin-sam-with-receiver"))
testImplementation(project(":kotlin-test:kotlin-test-jvm"))
testImplementation(project(":native:kotlin-native-utils"))
testImplementation(project(":native:kotlin-klib-commonizer-api"))
testImplementation(projectRuntimeJar(":kotlin-compiler-embeddable"))
testImplementation(intellijCoreDep()) { includeJars("jdom") }
@@ -5,9 +5,14 @@
package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel.INFO
import org.gradle.internal.os.OperatingSystem
import org.jetbrains.kotlin.commonizer.CommonizerTarget
import org.jetbrains.kotlin.gradle.internals.DISABLED_NATIVE_TARGETS_REPORTER_WARNING_PREFIX
import org.jetbrains.kotlin.gradle.util.reportSourceSetCommonizerDependencies
import org.jetbrains.kotlin.incremental.testingUtils.assertEqualDirectories
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget.*
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@@ -349,6 +354,143 @@ class CommonizerIT : BaseGradleIT() {
}
}
@Test
fun `test multiple cinterops with test source sets and compilations`() {
with(Project("commonizeMultipleCInteropsWithTests", minLogLevel = INFO)) {
val isUnix = HostManager.hostIsMac || HostManager.hostIsLinux
val isMac = HostManager.hostIsMac
val isWindows = HostManager.hostIsMingw
reportSourceSetCommonizerDependencies(this) {
getCommonizerDependencies("nativeMain").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper".takeIf { isUnix })
assertTargetOnAllDependencies(
CommonizerTarget(IOS_X64, IOS_ARM64, LINUX_X64, LINUX_ARM64, MACOS_X64, MINGW_X64, MINGW_X86)
)
}
getCommonizerDependencies("nativeTest").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper".takeIf { isUnix }, ".*nativeTestHelper")
assertTargetOnAllDependencies(
CommonizerTarget(IOS_X64, IOS_ARM64, LINUX_X64, LINUX_ARM64, MACOS_X64, MINGW_X64, MINGW_X86)
)
}
if (isUnix) {
getCommonizerDependencies("unixMain").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64, LINUX_X64, LINUX_ARM64, MACOS_X64))
}
getCommonizerDependencies("unixTest").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*nativeTestHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64, LINUX_X64, LINUX_ARM64, MACOS_X64))
}
getCommonizerDependencies("linuxMain").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper")
assertTargetOnAllDependencies(CommonizerTarget(LINUX_X64, LINUX_ARM64))
}
getCommonizerDependencies("linuxTest").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*nativeTestHelper")
assertTargetOnAllDependencies(CommonizerTarget(LINUX_X64, LINUX_ARM64))
}
getCommonizerDependencies("linuxX64Main").assertEmpty()
getCommonizerDependencies("linuxArm64Main").assertEmpty()
getCommonizerDependencies("linuxX64Test").assertEmpty()
getCommonizerDependencies("linuxArm64Test").assertEmpty()
}
if (isMac) {
getCommonizerDependencies("appleMain").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*appleHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64, MACOS_X64))
}
getCommonizerDependencies("appleTest").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*appleHelper", ".*nativeTestHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64, MACOS_X64))
}
getCommonizerDependencies("iosMain").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*unixHelper", ".*appleHelper")
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64))
}
getCommonizerDependencies("iosTest").onlyCInterops().apply {
assertDependencyFilesMatches(
".*nativeHelper", ".*unixHelper", ".*appleHelper", ".*nativeTestHelper", ".*iosTestHelper"
)
assertTargetOnAllDependencies(CommonizerTarget(IOS_X64, IOS_ARM64))
}
getCommonizerDependencies("macosMain").assertEmpty()
getCommonizerDependencies("macosTest").assertEmpty()
getCommonizerDependencies("iosX64Main").assertEmpty()
getCommonizerDependencies("iosX64Test").assertEmpty()
getCommonizerDependencies("iosArm64Main").assertEmpty()
getCommonizerDependencies("iosArm64Test").assertEmpty()
}
if (isWindows) {
getCommonizerDependencies("windowsMain").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*windowsHelper")
assertTargetOnAllDependencies(CommonizerTarget(MINGW_X86, MINGW_X64))
}
getCommonizerDependencies("windowsTest").onlyCInterops().apply {
assertDependencyFilesMatches(".*nativeHelper", ".*windowsHelper", ".*nativeTestHelper")
assertTargetOnAllDependencies(CommonizerTarget(MINGW_X86, MINGW_X64))
}
getCommonizerDependencies("windowsX64Main").assertEmpty()
getCommonizerDependencies("windowsX64Test").assertEmpty()
getCommonizerDependencies("windowsX86Main").assertEmpty()
getCommonizerDependencies("windowsX86Test").assertEmpty()
}
}
build(":assemble") {
assertSuccessful()
assertTasksUpToDate(":commonizeNativeDistribution")
assertTasksUpToDate(":commonizeCInterop")
}
build(":compileNativeMainKotlinMetadata") {
assertSuccessful()
}
if (isUnix) {
build(":compileUnixMainKotlinMetadata") {
assertSuccessful()
}
build(":compileLinuxMainKotlinMetadata") {
assertSuccessful()
}
}
if (isMac) {
build(":compileAppleMainKotlinMetadata") {
assertSuccessful()
}
build(":compileIosMainKotlinMetadata") {
assertSuccessful()
}
}
if (isWindows) {
build(":compileWindowsMainKotlinMetadata") {
assertSuccessful()
}
}
}
}
@Test
fun `test KT-47641 commonizing c-interops does not depend on any source compilation`() {
with(Project("commonize-kt-47641-cinterops-compilation-dependency")) {
@@ -0,0 +1,154 @@
/*
* Copyright 2010-2021 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.util
import org.intellij.lang.annotations.Language
import org.intellij.lang.annotations.RegExp
import org.jetbrains.kotlin.commonizer.CommonizerTarget
import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.commonizer.parseCommonizerTargetOrNull
import org.jetbrains.kotlin.commonizer.util.transitiveClosure
import org.jetbrains.kotlin.gradle.BaseGradleIT
import java.io.File
import javax.annotation.RegEx
import kotlin.test.fail
data class SourceSetCommonizerDependency(
val sourceSetName: String,
val target: CommonizerTarget,
val file: File
)
data class SourceSetCommonizerDependencies(
val sourceSetName: String,
val dependencies: Set<SourceSetCommonizerDependency>
) {
fun onlyCInterops(): SourceSetCommonizerDependencies {
return SourceSetCommonizerDependencies(
sourceSetName,
dependencies.filter { dependency ->
/* Cinterop dependencies are located in the project's '.gradle' directory */
dependency.file.allParents.any { parentFile -> parentFile.name == ".gradle" }
}.toSet()
)
}
fun assertTargetOnAllDependencies(target: CommonizerTarget) {
dependencies.forEach { dependency ->
if (dependency.target != target) {
fail("$sourceSetName: Expected target $target but found dependency with target ${dependency.target}\n$dependency")
}
}
}
fun assertEmpty() {
if (dependencies.isNotEmpty()) {
fail("$sourceSetName: Expected no dependencies in set. Found $dependencies")
}
}
fun assertDependencyFilesMatches(@Language("RegExp") @RegEx @RegExp vararg fileMatchers: String?) {
assertDependencyFilesMatches(fileMatchers.filterNotNull().map(::Regex).toSet())
}
fun assertDependencyFilesMatches(vararg fileMatchers: Regex?) {
assertDependencyFilesMatches(fileMatchers.filterNotNull().toSet())
}
fun assertDependencyFilesMatches(fileMatchers: Set<Regex>) {
val unmatchedDependencies = dependencies.filter { dependency ->
fileMatchers.none { matcher -> dependency.file.absolutePath.matches(matcher) }
}
val unmatchedMatchers = fileMatchers.filter { matcher ->
dependencies.none { dependency -> dependency.file.absolutePath.matches(matcher) }
}
if (unmatchedDependencies.isNotEmpty() || unmatchedMatchers.isNotEmpty()) {
fail(buildString {
appendLine("$sourceSetName: Set of commonizer dependencies does not match given 'fileMatchers'")
if (unmatchedDependencies.isNotEmpty()) {
appendLine("Unmatched dependencies: $unmatchedDependencies")
}
if (unmatchedMatchers.isNotEmpty()) {
appendLine("Unmatched fileMatchers: $unmatchedMatchers")
}
})
}
}
}
fun interface WithSourceSetCommonizerDependencies {
fun getCommonizerDependencies(sourceSetName: String): SourceSetCommonizerDependencies
}
fun BaseGradleIT.reportSourceSetCommonizerDependencies(
project: BaseGradleIT.Project,
test: WithSourceSetCommonizerDependencies.() -> Unit
) = with(project) {
if (!projectDir.exists()) {
setupWorkingDir()
}
gradleBuildScript().apply {
appendText("\n\n")
appendText(taskSourceCode)
appendText("\n\n")
}
build(
":reportCommonizerSourceSetDependencies",
options = defaultBuildOptions().copy(forceOutputToStdout = true)
) {
assertSuccessful()
val dependencyReports = output.lineSequence().filter { line -> line.contains("SourceSetCommonizerDependencyReport") }.toList()
val withSourceSetCommonizerDependencies = WithSourceSetCommonizerDependencies { sourceSetName ->
val reportMarker = "Report[$sourceSetName]"
val reportForSourceSet = dependencyReports.firstOrNull { line -> line.contains(reportMarker) }
?: fail("Missing dependency report for $sourceSetName")
val files = reportForSourceSet.split(reportMarker, limit = 2).last().split("|#+#|").map(::File)
val dependencies = files.mapNotNull { file ->
val allParents = file.allParents
if (allParents.any { it.name == "commonized" } || allParents.any { it.name == "commonizer" }) {
val target = parseCommonizerTargetOrNull(file.parentFile.name) as? SharedCommonizerTarget ?: return@mapNotNull null
SourceSetCommonizerDependency(sourceSetName, target, file)
} else null
}
SourceSetCommonizerDependencies(sourceSetName, dependencies.toSet())
}
withSourceSetCommonizerDependencies.test()
}
}
private val File.allParents: Set<File> get() = transitiveClosure(this) { listOfNotNull(parentFile) }
private const val dollar = "\$"
private val taskSourceCode = """
tasks.register("reportCommonizerSourceSetDependencies") {
kotlin.sourceSets.withType(DefaultKotlinSourceSet::class).all {
inputs.files(configurations.getByName(intransitiveMetadataConfigurationName))
}
doLast {
kotlin.sourceSets.filterIsInstance<DefaultKotlinSourceSet>().forEach { sourceSet ->
val configuration = configurations.getByName(sourceSet.intransitiveMetadataConfigurationName)
val dependencies = configuration.files
logger.quiet(
"SourceSetCommonizerDependencyReport[$dollar{sourceSet.name}]$dollar{dependencies.joinToString("|#+#|")}"
)
}
}
}
""".trimIndent()
@@ -0,0 +1,143 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.konan.target.Family.*
operator fun KotlinSourceSet.invoke(builder: SourceSetHierarchyBuilder.() -> Unit): KotlinSourceSet {
SourceSetHierarchyBuilder(this).builder()
return this
}
class SourceSetHierarchyBuilder(private val node: KotlinSourceSet) {
operator fun KotlinSourceSet.unaryMinus() = this.dependsOn(node)
}
plugins {
kotlin("multiplatform")
}
repositories {
mavenLocal()
mavenCentral()
}
kotlin {
jvm()
linuxX64()
linuxArm64()
macosX64("macos")
ios()
mingwX64("windowsX64")
mingwX86("windowsX86")
val commonMain by sourceSets.getting
val commonTest by sourceSets.getting
val jvmMain by sourceSets.getting
val nativeMain by sourceSets.creating
val nativeTest by sourceSets.creating
val unixMain by sourceSets.creating
val unixTest by sourceSets.creating
val linuxMain by sourceSets.creating
val linuxTest by sourceSets.creating
val linuxX64Main by sourceSets.getting
val linuxX64Test by sourceSets.getting
val linuxArm64Main by sourceSets.getting
val linuxArm64Test by sourceSets.getting
val appleMain by sourceSets.creating
val appleTest by sourceSets.creating
val macosMain by sourceSets.getting
val macosTest by sourceSets.getting
val iosMain by sourceSets.getting
val iosTest by sourceSets.getting
val windowsMain by sourceSets.creating
val windowsTest by sourceSets.creating
val windowsX64Main by sourceSets.getting
val windowsX64Test by sourceSets.getting
val windowsX86Main by sourceSets.getting
val windowsX86Test by sourceSets.getting
commonMain {
-jvmMain
-nativeMain {
-unixMain {
-appleMain {
-iosMain
-macosMain
}
-linuxMain {
-linuxArm64Main
-linuxX64Main
}
}
-windowsMain {
-windowsX64Main
-windowsX86Main
}
}
}
commonTest {
-nativeTest {
-unixTest {
-appleTest {
-iosTest
-macosTest
}
-linuxTest {
-linuxArm64Test
-linuxX64Test
}
}
-windowsTest {
-windowsX64Test
-windowsX86Test
}
}
}
if (properties["testSourceSetsDependingOnMain"] == "true") {
logger.quiet("testSourceSetsDependingOnMain is set")
nativeTest.dependsOn(nativeMain)
unixTest.dependsOn(unixMain)
appleTest.dependsOn(appleMain)
linuxTest.dependsOn(linuxMain)
windowsTest.dependsOn(windowsMain)
}
targets.withType<KotlinNativeTarget>().forEach { target ->
target.compilations.getByName("main").cinterops.create("nativeHelper") {
headers(file("libs/nativeHelper.h"))
}
target.compilations.getByName("test").cinterops.create("nativeTestHelper") {
headers(file("libs/nativeTestHelper.h"))
}
if (target.konanTarget.family.isAppleFamily || target.konanTarget.family == LINUX) {
target.compilations.getByName("main").cinterops.create("unixHelper") {
headers(file("libs/unixHelper.h"))
}
}
if (target.konanTarget.family.isAppleFamily) {
target.compilations.getByName("main").cinterops.create("appleHelper") {
headers(file("libs/appleHelper.h"))
}
}
if (target.konanTarget.family == IOS) {
target.compilations.getByName("test").cinterops.create("iosTestHelper") {
headers(file("libs/iosTestHelper.h"))
}
}
if (target.konanTarget.family == MINGW) {
target.compilations.getByName("main").cinterops.create("windowsHelper") {
headers(file("libs/windowsHelper.h"))
}
}
}
}
@@ -0,0 +1,4 @@
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
kotlin.mpp.enableCInteropCommonization=true
kotlin.mpp.enableIntransitiveMetadataConfiguration=true
@@ -0,0 +1,12 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
val kotlin_version: String by settings
plugins {
kotlin("multiplatform").version(kotlin_version)
}
}
@@ -0,0 +1,11 @@
@file:Suppress("unused")
import appleHelper.appleHelper
import nativeHelper.nativeHelper
import unixHelper.unixHelper
object AppleMain {
val native = nativeHelper()
val unix = unixHelper()
val apple = appleHelper()
}
@@ -0,0 +1,11 @@
@file:Suppress("unused")
import appleHelper.appleHelper
import nativeHelper.nativeHelper
import unixHelper.unixHelper
object IosMain {
val native = nativeHelper()
val unix = unixHelper()
val apple = appleHelper()
}
@@ -0,0 +1,9 @@
@file:Suppress("unused")
import nativeHelper.nativeHelper
import unixHelper.unixHelper
object LinuxArm64Main {
val native = nativeHelper()
val unix = unixHelper()
}
@@ -0,0 +1,9 @@
@file:Suppress("unused")
import nativeHelper.nativeHelper
import unixHelper.unixHelper
object LinuxMain {
val native = nativeHelper()
val unix = unixHelper()
}
@@ -0,0 +1,9 @@
@file:Suppress("unused")
import nativeHelper.nativeHelper
import unixHelper.unixHelper
object LinuxX64Main {
val native = nativeHelper()
val unix = unixHelper()
}
@@ -0,0 +1,12 @@
@file:Suppress("unused")
import appleHelper.appleHelper
import nativeHelper.nativeHelper
import unixHelper.unixHelper
object MacosMain {
val native = nativeHelper()
val unix = unixHelper()
val apple = appleHelper()
}
@@ -0,0 +1,7 @@
@file:Suppress("unused")
import nativeHelper.nativeHelper
object NativeMain {
val native = nativeHelper()
}
@@ -0,0 +1,10 @@
@file:Suppress("unused")
import nativeHelper.nativeHelper
import unixHelper.unixHelper
object UnixMain {
val native = nativeHelper()
val unix = unixHelper()
}
@@ -0,0 +1,9 @@
@file:Suppress("unused")
import nativeHelper.nativeHelper
import windowsHelper.windowsHelper
object WindowsMain {
val native = nativeHelper()
val windows = windowsHelper()
}