@file:Suppress("UNUSED_VARIABLE") import com.google.gson.GsonBuilder import com.google.gson.JsonObject import org.gradle.api.publish.internal.PublicationInternal import org.gradle.jvm.tasks.Jar import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.mpp.GenerateProjectStructureMetadata import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl import plugins.configureDefaultPublishing import plugins.configureKotlinPomAttributes import plugins.publishing.* plugins { kotlin("multiplatform") `maven-publish` signing } description = "Kotlin Test Library" base.archivesName = "kotlin-test" configureJvmToolchain(JdkMajorVersion.JDK_1_8) val kotlinTestCapability = "$group:${base.archivesName.get()}:$version" // add to variants with explicit capabilities when the default one is needed, too val baseCapability = "$group:kotlin-test-framework:$version" val implCapability = "$group:kotlin-test-framework-impl:$version" enum class JvmTestFramework { JUnit, JUnit5, TestNG; fun lowercase() = name.lowercase() } val jvmTestFrameworks = JvmTestFramework.values().toList() kotlin { explicitApi() jvm { compilations { all { compileTaskProvider.configure { compilerOptions { freeCompilerArgs.empty() // avoid common options set from the root project } } } val main by getting val test by getting configureJava9Compilation( "kotlin.test", listOf(main.output.allOutputs), main.configurations.compileDependencyConfiguration, project.sourceSets.create("jvmJava9") { java.srcDir("jvm/src/java9/java") }.name, ) jvmTestFrameworks.forEach { framework -> val frameworkMain = create("$framework") { associateWith(main) compileTaskProvider.configure { compilerOptions { freeCompilerArgs.add("-Xexplicit-api=strict") } } } create("${framework}Test") { associateWith(frameworkMain) } val frameworkJava9SourceSet = project.sourceSets.create("jvm${framework}Java9") { java.srcDir("${framework.lowercase()}/src/java9/java") } configureJava9Compilation( "kotlin.test.${framework.lowercase()}", listOf(frameworkMain.output.allOutputs), frameworkMain.configurations.compileDependencyConfiguration, frameworkJava9SourceSet.name, ) val java9CompileOnly = configurations[frameworkJava9SourceSet.compileOnlyConfigurationName] project.dependencies { java9CompileOnly(project) } } test.associateWith(getByName("JUnit")) } } js { if (!kotlinBuildProperties.isTeamcityBuild) { browser {} } nodejs {} compilations["main"].compileTaskProvider.configure { compilerOptions.freeCompilerArgs.add("-Xir-module-name=kotlin-test") } } @OptIn(ExperimentalWasmDsl::class) wasmJs { nodejs() compilations["main"].compileTaskProvider.configure { compilerOptions.freeCompilerArgs.add("-Xir-module-name=kotlin-test") } } @OptIn(ExperimentalWasmDsl::class) wasmWasi { nodejs() compilations["main"].compileTaskProvider.configure { compilerOptions.freeCompilerArgs.add("-Xir-module-name=kotlin-test") } } targets.all { compilations.all { compileTaskProvider.configure { compilerOptions { optIn.add("kotlin.contracts.ExperimentalContracts") freeCompilerArgs.addAll( "-Xallow-kotlin-package", "-Xexpect-actual-classes", ) } } } } sourceSets { val commonMain by getting { dependencies { api(kotlinStdlib()) } } val annotationsCommonMain by creating { dependsOn(commonMain) kotlin.srcDir("annotations-common/src/main/kotlin") } val assertionsCommonMain by creating { dependsOn(commonMain) kotlin.srcDir("common/src/main/kotlin") } val commonTest by getting { kotlin.srcDir("annotations-common/src/test/kotlin") kotlin.srcDir("common/src/test/kotlin") } val jvmMain by getting { dependsOn(assertionsCommonMain) kotlin.srcDir("jvm/src/main/kotlin") } val jvmTest by getting { kotlin.srcDir("jvm/src/test/kotlin") } val jvmJUnit by getting { dependsOn(annotationsCommonMain) kotlin.srcDir("junit/src/main/kotlin") resources.srcDir("junit/src/main/resources") dependencies { api("junit:junit:4.13.2") } } val jvmJUnitTest by getting { dependsOn(commonTest) kotlin.srcDir("junit/src/test/kotlin") } val jvmJUnit5 by getting { dependsOn(annotationsCommonMain) kotlin.srcDir("junit5/src/main/kotlin") resources.srcDir("junit5/src/main/resources") dependencies { compileOnly("org.junit.jupiter:junit-jupiter-api:5.0.0") } } val jvmJUnit5Test by getting { dependsOn(commonTest) kotlin.srcDir("junit5/src/test/kotlin") dependencies { runtimeOnly(libs.junit.jupiter.engine) runtimeOnly(libs.junit.platform.launcher) } } val jvmTestNG by getting { dependsOn(annotationsCommonMain) kotlin.srcDir("testng/src/main/kotlin") resources.srcDir("testng/src/main/resources") dependencies { api("org.testng:testng:7.0.0") } } val jvmTestNGTest by getting { dependsOn(commonTest) kotlin.srcDir("testng/src/test/kotlin") dependencies { implementation("org.testng:testng:7.5.1") } } val jsMain by getting { dependsOn(assertionsCommonMain) dependsOn(annotationsCommonMain) kotlin.srcDir("js/src/main/kotlin") } val jsTest by getting { kotlin.srcDir("js/src/test/kotlin") } val wasmCommonMain by creating { dependsOn(assertionsCommonMain) dependsOn(annotationsCommonMain) kotlin.srcDir("wasm/src/main/kotlin") } val wasmJsMain by getting { dependsOn(wasmCommonMain) kotlin.srcDir("wasm/js/src/main/kotlin") } val wasmJsTest by getting { kotlin.srcDir("wasm/js/src/test/kotlin") } val wasmWasiMain by getting { dependsOn(wasmCommonMain) kotlin.srcDir("wasm/wasi/src/main/kotlin") } } } tasks { val allMetadataJar by existing(Jar::class) { archiveClassifier = "all" } val jvmJar by existing(Jar::class) { archiveAppendix = null from(project.sourceSets["jvmJava9"].output) manifestAttributes(manifest, "Test", multiRelease = true) } val jvmSourcesJar by existing(Jar::class) { archiveAppendix = null kotlin.sourceSets["annotationsCommonMain"].let { sourceSet -> into(sourceSet.name) { from(sourceSet.kotlin) } } } val jvmJarTasks = jvmTestFrameworks.map { framework -> named("jvm${framework}Jar", Jar::class) { archiveBaseName = base.archivesName archiveAppendix = framework.lowercase() from(project.sourceSets["jvm${framework}Java9"].output) manifestAttributes(manifest, "Test", multiRelease = true) manifest.attributes("Implementation-Title" to "${archiveBaseName.get()}-${archiveAppendix.get()}") } } val jvmSourcesJarTasks = jvmTestFrameworks.map { framework -> register("jvm${framework}SourcesJar", Jar::class) { archiveAppendix = framework.lowercase() archiveClassifier = "sources" kotlin.jvm().compilations[framework.name].allKotlinSourceSets.forEach { from(it.kotlin.sourceDirectories) { into(it.name) } from(it.resources.sourceDirectories) { into(it.name) } } } } val jsJar by existing(Jar::class) { manifestAttributes(manifest, "Test") manifest.attributes("Implementation-Title" to "${archiveBaseName.get()}-${archiveAppendix.get()}") } val wasmJsJar by existing(Jar::class) { manifestAttributes(manifest, "Test") manifest.attributes("Implementation-Title" to "${archiveBaseName.get()}-${archiveAppendix.get()}") } val wasmWasiJar by existing(Jar::class) { manifestAttributes(manifest, "Test") manifest.attributes("Implementation-Title" to "${archiveBaseName.get()}-${archiveAppendix.get()}") } val assemble by existing { dependsOn(jvmJarTasks) } val jvmTestTasks = jvmTestFrameworks.flatMap { framework -> listOf(false, true).map { excludeAsserterContributor -> register("jvm${framework}${if (excludeAsserterContributor) "NoAsserter" else ""}Test", Test::class) { group = "verification" val testCompilation = kotlin.jvm().compilations["${framework}Test"] classpath = testCompilation.runtimeDependencyFiles + testCompilation.output.allOutputs if (excludeAsserterContributor) { val mainCompilation = kotlin.jvm().compilations["$framework"] classpath -= mainCompilation.output.allOutputs filter.excludePatterns += "*ContributorTest" } testClassesDirs = testCompilation.output.classesDirs when (framework) { JvmTestFramework.JUnit -> useJUnit() JvmTestFramework.JUnit5 -> useJUnitPlatform() JvmTestFramework.TestNG -> useTestNG() } } } } val allTests by existing { dependsOn(jvmTestTasks) } val generateProjectStructureMetadata by existing(GenerateProjectStructureMetadata::class) { val outputTestFile = file("kotlin-project-structure-metadata.beforePatch.json") val patchedFile = file("kotlin-project-structure-metadata.json") inputs.file(patchedFile) inputs.file(outputTestFile) doLast { /* Check that the generated 'outputFile' by default matches our expectations stored in the .beforePatch file This will fail if the kotlin-project-structure-metadata.json file would change unnoticed (w/o updating our patched file) */ run { val outputFileText = resultFile.readText().trim() val expectedFileContent = outputTestFile.readText().trim() if (outputFileText != expectedFileContent) error( "${resultFile.path} file content does not match expected content\n\n" + "expected:\n\n$expectedFileContent\n\nactual:\n\n$outputFileText" ) } patchedFile.copyTo(resultFile, overwrite = true) } } } configurations { val metadataApiElements by getting metadataApiElements.outgoing.capability(kotlinTestCapability) for (framework in jvmTestFrameworks) { val frameworkCapability = "$group:kotlin-test-framework-${framework.lowercase()}:$version" metadataApiElements.outgoing.capability(frameworkCapability) val runtimeDeps = create("jvm${framework}RuntimeDependencies") { isCanBeResolved = true isCanBeConsumed = false } val (apiElements, runtimeElements, sourcesElements) = listOf(KotlinUsages.KOTLIN_API, KotlinUsages.KOTLIN_RUNTIME, KotlinUsages.KOTLIN_SOURCES).map { usage -> val name = "jvm$framework${usage.substringAfter("kotlin-").replaceFirstChar { it.uppercase() }}Elements" create(name) { isCanBeResolved = false isCanBeConsumed = true outgoing.capability(baseCapability) outgoing.capability(frameworkCapability) attributes { attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objects.named(TargetJvmEnvironment.STANDARD_JVM)) attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR)) attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm) when (usage) { KotlinUsages.KOTLIN_SOURCES -> { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION)) attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES)) attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL)) } KotlinUsages.KOTLIN_API -> { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_API)) attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) extendsFrom(getByName("jvm${framework}Api")) } KotlinUsages.KOTLIN_RUNTIME -> { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) runtimeDeps.extendsFrom(getByName("jvm${framework}Api")) runtimeDeps.extendsFrom(getByName("jvm${framework}Implementation")) runtimeDeps.extendsFrom(getByName("jvm${framework}RuntimeOnly")) extendsFrom(runtimeDeps) } else -> error(usage) } } } } dependencies { apiElements(project) runtimeDeps(project) when (framework) { JvmTestFramework.JUnit -> {} JvmTestFramework.JUnit5 -> { apiElements("org.junit.jupiter:junit-jupiter-api:5.10.1") runtimeDeps("org.junit.jupiter:junit-jupiter-engine:5.10.1") runtimeDeps("org.junit.platform:junit-platform-launcher:1.10.1") } JvmTestFramework.TestNG -> { apiElements("org.testng:testng:7.5.1") } } } artifacts { add(apiElements.name, tasks.named("jvm${framework}Jar")) add(runtimeElements.name, tasks.named("jvm${framework}Jar")) add(sourcesElements.name, tasks.named("jvm${framework}SourcesJar")) } } for (configurationName in listOf("kotlinTestCommon", "kotlinTestAnnotationsCommon")) { val legacyConfigurationDeps = create("${configurationName}Dependencies") { isCanBeResolved = true isCanBeConsumed = false } val legacyConfiguration = create("${configurationName}Elements") { isCanBeResolved = false isCanBeConsumed = false attributes { attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) } extendsFrom(legacyConfigurationDeps) } dependencies { legacyConfigurationDeps(project) } } val jvmMainApi by getting val nativeApiElements by creating for (artifactName in listOf("kotlin-test-common", "kotlin-test-annotations-common")) { dependencies { constraints { val artifactCoordinates = "$group:$artifactName:$version" // there is no dependency anymore from kotlin-test to kotlin-test-common and -annotations-common, // but use this constraint to align it if another library brings it transitively jvmMainApi(artifactCoordinates) metadataApiElements(artifactCoordinates) nativeApiElements(artifactCoordinates) } } } } configureDefaultPublishing() fun emptyJavadocJar(appendix: String? = null): TaskProvider = tasks.register("emptyJavadocJar${appendix.orEmpty().capitalize()}") { archiveAppendix = appendix archiveClassifier = "javadoc" } publishing { val artifactBaseName = base.archivesName.get() configureMultiModuleMavenPublishing { val rootModule = module("rootModule") { mavenPublication { artifactId = artifactBaseName configureKotlinPomAttributes(project, "Kotlin Test Multiplatform library") artifact(emptyJavadocJar()) } variant("metadataApiElements") { suppressPomMetadataWarnings() } variant("jvmApiElements") variant("jvmRuntimeElements") { configureVariantDetails { mapToMavenScope("runtime") } } variant("jvmSourcesElements") variant("nativeApiElements") { attributes { attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objects.named("non-jvm")) attribute(Usage.USAGE_ATTRIBUTE, objects.named(KotlinUsages.KOTLIN_API)) attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) } } } val frameworkModules = jvmTestFrameworks.map { framework -> module("${framework.lowercase()}Module") { mavenPublication { artifactId = "$artifactBaseName-${framework.lowercase()}" configureKotlinPomAttributes(project, "Kotlin Test library support for ${framework}") artifact(emptyJavadocJar(framework.lowercase())) } variant("jvm${framework}ApiElements") { suppressPomMetadataWarnings() } variant("jvm${framework}RuntimeElements") { suppressPomMetadataWarnings() configureVariantDetails { mapToMavenScope("runtime") } } variant("jvm${framework}SourcesElements") { suppressPomMetadataWarnings() } } } val js = module("jsModule") { mavenPublication { artifactId = "$artifactBaseName-js" configureKotlinPomAttributes(project, "Kotlin Test library for JS", packaging = "klib") } variant("jsApiElements") variant("jsRuntimeElements") variant("jsSourcesElements") } val wasmJs = module("wasmJsModule") { mavenPublication { artifactId = "$artifactBaseName-wasm-js" configureKotlinPomAttributes(project, "Kotlin Test library for experimental WebAssembly JS platform", packaging = "klib") } variant("wasmJsApiElements") variant("wasmJsRuntimeElements") variant("wasmJsSourcesElements") } val wasmWasi = module("wasmWasiModule") { mavenPublication { artifactId = "$artifactBaseName-wasm-wasi" configureKotlinPomAttributes(project, "Kotlin Test library for experimental WebAssembly WASI platform", packaging = "klib") } variant("wasmWasiApiElements") variant("wasmWasiRuntimeElements") variant("wasmWasiSourcesElements") } module("testCommonModule") { mavenPublication { artifactId = "$artifactBaseName-common" configureKotlinPomAttributes(project, "Legacy artifact of Kotlin Test library. Use kotlin-test instead", packaging = "pom") (this as PublicationInternal<*>).isAlias = true } variant("kotlinTestCommonElements") } module("testAnnotationsCommonModule") { mavenPublication { artifactId = "$artifactBaseName-annotations-common" configureKotlinPomAttributes(project, "Legacy artifact of Kotlin Test library. Use kotlin-test instead", packaging = "pom") (this as PublicationInternal<*>).isAlias = true } variant("kotlinTestAnnotationsCommonElements") } // Makes all variants from accompanying artifacts visible through `available-at` rootModule.include(js, *frameworkModules.toTypedArray(), wasmJs, wasmWasi) } publications { (listOf( listOf("rootModule", "Main", "kotlin-test", "jvmRuntimeClasspath"), listOf("jsModule", "Js", "kotlin-test-js", "jsRuntimeClasspath"), listOf("wasmJsModule", "Wasm-Js", "kotlin-test-wasm-js", "wasmJsRuntimeClasspath"), listOf("wasmWasiModule", "Wasm-Wasi", "kotlin-test-wasm-wasi", "wasmWasiRuntimeClasspath"), listOf("testCommonModule", "Common", "kotlin-test-common", "kotlinTestCommonDependencies"), listOf("testAnnotationsCommonModule", "AnnotationsCommon", "kotlin-test-annotations-common", "kotlinTestAnnotationsCommonDependencies"), ) + jvmTestFrameworks.map { framework -> listOf("${framework.lowercase()}Module", "$framework", "kotlin-test-${framework.lowercase()}", "jvm${framework}RuntimeDependencies") }).forEach { (module, sbomTarget, sbomDocument, classpath) -> configureSbom(sbomTarget, sbomDocument, setOf(classpath), named(module)) } } } tasks.withType { val publication = publication.get() as MavenPublication // alter capabilities of leaf JVM framework artifacts published by "available-at" coordinates if (jvmTestFrameworks.map { it.lowercase() }.any { publication.artifactId.endsWith(it) }) { fun capability(group: String, name: String, version: String) = mapOf("group" to group, "name" to name, "version" to version) val defaultCapability = publication.let { capability(it.groupId, it.artifactId, it.version) } val implCapability = implCapability.split(":").let { (g, n, v) -> capability(g, n, v) } val capabilities = listOf(defaultCapability, implCapability) doLast { val output = outputFile.get().asFile val gson = GsonBuilder().setPrettyPrinting().create() val moduleJson = output.bufferedReader().use { gson.fromJson(it, JsonObject::class.java) } val variants = moduleJson.getAsJsonArray("variants") variants.forEach { variant -> variant as JsonObject variant.remove("capabilities") variant.add("capabilities", gson.toJsonTree(capabilities)) } output.bufferedWriter().use { writer -> gson.toJson(moduleJson, writer) } } } }