blob: 3e6662cf7903f2f36b9070895b5333b4d5cbe57b [file] [log] [blame] [edit]
@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.library.KOTLINTEST_MODULE_NAME
import plugins.configureDefaultPublishing
import plugins.configureKotlinPomAttributes
import plugins.publishing.configureMultiModuleMavenPublishing
plugins {
kotlin("multiplatform")
`maven-publish`
signing
id("nodejs-cache-redirector-configuration")
}
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
freeCompilerArgs.addAll("-Xdont-warn-on-error-suppression")
}
}
}
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=$KOTLINTEST_MODULE_NAME")
}
}
// Please remove this check after bootstrap and replacing @ExperimentalWasmDsl
val newExperimentalWasmDslAvailable = runCatching {
Class.forName("org.jetbrains.kotlin.gradle.ExperimentalWasmDsl")
}.isSuccess
if (newExperimentalWasmDslAvailable) {
logger.warn(
"""
Apparently kotlin bootstrap just happened. And @ExperimentalWasmDsl annotation was moved to a new FQN.
Please replace 'org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl'
with 'org.jetbrains.kotlin.gradle.ExperimentalWasmDsl'
and remove this check.
Please note that the same check exists in kotlin-stdlib module. Fix it there too.
""".trimIndent()
)
}
@Suppress("OPT_IN_USAGE")
// Remove line above and uncomment line below after bootstrap
// @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
wasmJs {
nodejs()
compilations["main"].compileTaskProvider.configure {
compilerOptions.freeCompilerArgs.add("-Xir-module-name=$KOTLINTEST_MODULE_NAME")
}
}
@Suppress("OPT_IN_USAGE")
// Remove line above and uncomment line below after bootstrap
// @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
wasmWasi {
nodejs()
compilations["main"].compileTaskProvider.configure {
compilerOptions.freeCompilerArgs.add("-Xir-module-name=$KOTLINTEST_MODULE_NAME")
}
}
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<Jar>("jvm${framework}Jar"))
add(runtimeElements.name, tasks.named<Jar>("jvm${framework}Jar"))
add(sourcesElements.name, tasks.named<Jar>("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<Jar> =
tasks.register<Jar>("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<MavenPublication>(module))
}
}
}
tasks.withType<GenerateModuleMetadata> {
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) }
}
}
}