@file:Suppress("UNUSED_VARIABLE", "NAME_SHADOWING") import org.gradle.api.internal.component.SoftwareComponentInternal import org.gradle.api.internal.component.UsageContext import org.gradle.jvm.tasks.Jar import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl import org.jetbrains.kotlin.gradle.targets.js.d8.D8RootPlugin import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinTargetWithNodeJsDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWasmTargetDsl import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile import org.jetbrains.kotlin.gradle.tasks.UsesKotlinJavaToolchain import plugins.configureDefaultPublishing import plugins.configureKotlinPomAttributes import kotlin.io.path.copyTo plugins { id("kotlin-multiplatform") `maven-publish` signing } description = "Kotlin Standard Library" configureJvmToolchain(JdkMajorVersion.JDK_1_8) fun resolvingConfiguration(name: String, configure: Action = Action {}) = configurations.create(name) { isCanBeResolved = true isCanBeConsumed = false configure(this) } fun outgoingConfiguration(name: String, configure: Action = Action {}) = configurations.create(name) { isCanBeResolved = false isCanBeConsumed = true configure(this) } val configurationBuiltins = resolvingConfiguration("builtins") { attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements.JAR)) } dependencies { configurationBuiltins(project(":core:builtins")) } val builtinsDir = "${rootDir}/core/builtins" val builtinsSrcDir = "${buildDir}/src/builtin-sources" val jsDir = "${projectDir}/js" // for js-ir val jsIrMainSources = "${buildDir}/src/jsMainSources" lateinit var jsIrTarget: KotlinJsTargetDsl val commonOptIns = listOf( "kotlin.ExperimentalMultiplatform", "kotlin.contracts.ExperimentalContracts", ) val commonTestOptIns = listOf( "kotlin.ExperimentalUnsignedTypes", "kotlin.ExperimentalStdlibApi", "kotlin.io.encoding.ExperimentalEncodingApi", ) kotlin { val renderDiagnosticNames by extra(project.kotlinBuildProperties.renderDiagnosticNames) val diagnosticNamesArg = if (renderDiagnosticNames) "-Xrender-internal-diagnostic-names" else null metadata { compilations { all { compileTaskProvider.configure { kotlinOptions { freeCompilerArgs = listOfNotNull( "-Xallow-kotlin-package", "-module-name", "kotlin-stdlib-common", "-Xexpect-actual-classes", diagnosticNamesArg, ) } // workaround for compiling legacy MPP metadata, remove when this compilation is not needed anymore // restate the list of opt-ins compilerOptions.optIn.addAll(commonOptIns) } } } } jvm { withJava() compilations { val compileOnlyDeclarations by creating { compileTaskProvider.configure { kotlinOptions { freeCompilerArgs = listOfNotNull("-Xallow-kotlin-package", diagnosticNamesArg) } } } val main by getting { compileTaskProvider.configure { this as UsesKotlinJavaToolchain kotlinJavaToolchain.toolchain.use(getToolchainLauncherFor(JdkMajorVersion.JDK_1_6)) kotlinOptions { moduleName = "kotlin-stdlib" jvmTarget = "1.8" // providing exhaustive list of args here freeCompilerArgs = listOfNotNull( "-Xallow-kotlin-package", "-Xexpect-actual-classes", "-Xmultifile-parts-inherit", "-Xuse-14-inline-classes-mangling-scheme", "-Xbuiltins-from-sources", "-Xno-new-java-annotation-targets", diagnosticNamesArg, ) } } defaultSourceSet { dependencies { compileOnly(compileOnlyDeclarations.output.allOutputs) } } } val mainJdk7 by creating { associateWith(main) compileTaskProvider.configure { this as UsesKotlinJavaToolchain kotlinJavaToolchain.toolchain.use(getToolchainLauncherFor(JdkMajorVersion.JDK_1_7)) kotlinOptions { moduleName = "kotlin-stdlib-jdk7" jvmTarget = "1.8" freeCompilerArgs = listOfNotNull( "-Xallow-kotlin-package", "-Xmultifile-parts-inherit", "-Xno-new-java-annotation-targets", diagnosticNamesArg, ) } } } val mainJdk8 by creating { associateWith(main) associateWith(mainJdk7) compileTaskProvider.configure { kotlinOptions { moduleName = "kotlin-stdlib-jdk8" freeCompilerArgs = listOfNotNull( "-Xallow-kotlin-package", "-Xmultifile-parts-inherit", "-Xno-new-java-annotation-targets", diagnosticNamesArg, ) } } } project.sourceSets.create("java9") { java.srcDir("jvm/java9") } configureJava9Compilation("kotlin.stdlib", listOf( main.output.allOutputs, mainJdk7.output.allOutputs, mainJdk8.output.allOutputs, ), main.configurations.compileDependencyConfiguration) val test by getting { associateWith(mainJdk7) associateWith(mainJdk8) compileTaskProvider.configure { kotlinOptions { freeCompilerArgs += listOf( "-Xallow-kotlin-package", // TODO: maybe rename test packages "-Xexpect-actual-classes", ) if (kotlinBuildProperties.useFir) { freeCompilerArgs += "-Xuse-k2" } // This is needed for JavaTypeTest; typeOf for non-reified type parameters doesn't work otherwise, for implementation reasons. freeCompilerArgs -= "-Xno-optimized-callable-references" } } } val longRunningTest by creating { associateWith(main) associateWith(mainJdk7) associateWith(mainJdk8) } } } jsIrTarget = js(IR) { if (!kotlinBuildProperties.isTeamcityBuild) { browser {} } nodejs { testTask { useMocha { timeout = "10s" } } } compilations { all { kotlinOptions { freeCompilerArgs += "-Xallow-kotlin-package" } } val main by getting main.apply { kotlinOptions { freeCompilerArgs += listOf( "-Xir-module-name=kotlin", "-Xexpect-actual-classes", ) if (!kotlinBuildProperties.disableWerror) { allWarningsAsErrors = true } } } } } D8RootPlugin.apply(rootProject).version = v8Version fun KotlinWasmTargetDsl.commonWasmTargetConfiguration() { (this as KotlinTargetWithNodeJsDsl).nodejs() compilations { all { kotlinOptions.freeCompilerArgs += listOf("-Xallow-kotlin-package", "-Xexpect-actual-classes") } val main by getting main.apply { kotlinOptions.freeCompilerArgs += "-Xir-module-name=kotlin" kotlinOptions.allWarningsAsErrors = true } } } @OptIn(ExperimentalWasmDsl::class) wasmJs { commonWasmTargetConfiguration() } @OptIn(ExperimentalWasmDsl::class) wasmWasi { commonWasmTargetConfiguration() } sourceSets { all { kotlin.setSrcDirs(emptyList()) } commonMain { val prepareCommonSources by tasks.registering { dependsOn(":prepare:build.version:writeStdlibVersion") } kotlin { srcDir("common/src") srcDir(files("src").builtBy(prepareCommonSources)) srcDir("unsigned/src") if (!kotlinBuildProperties.isInIdeaSync) { srcDir("$builtinsDir/src/kotlin/internal") } if (kotlinBuildProperties.isInIdeaSync) { // required for correct resolution of builtin classes in common code in K2 IDE srcDir("$builtinsDir/src") } } } commonTest { dependencies { // TODO: use project dependency when kotlin-test is migrated compileOnly("org.jetbrains.kotlin:kotlin-test-common:$bootstrapKotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-test-annotations-common:$bootstrapKotlinVersion") // compileOnly(project(":kotlin-test:kotlin-test-common")) // compileOnly(project(":kotlin-test:kotlin-test-annotations-common")) } kotlin { srcDir("common/test") srcDir("test") } } val jvmCompileOnlyDeclarations by getting { kotlin.srcDir("jvm/compileOnly") } val jvmMain by getting { project.configurations.getByName("jvmMainCompileOnly").extendsFrom(configurationBuiltins) dependencies { api("org.jetbrains:annotations:13.0") } val jvmSrcDirs = listOfNotNull( "jvm/src", "jvm/runtime", "$builtinsDir/src".takeUnless { kotlinBuildProperties.isInIdeaSync } ) project.sourceSets["main"].java.srcDirs(*jvmSrcDirs.toTypedArray()) kotlin.setSrcDirs(jvmSrcDirs) } val jvmMainJdk7 by getting { kotlin.srcDir("jdk7/src") } val jvmMainJdk8 by getting { kotlin.srcDir("jdk8/src") } val jvmTest by getting { languageSettings { optIn("kotlin.io.path.ExperimentalPathApi") } dependencies { api(project(":kotlin-test:kotlin-test-junit")) } kotlin.srcDir("jvm/test") kotlin.srcDir("jdk7/test") kotlin.srcDir("jdk8/test") } val jvmLongRunningTest by getting { dependencies { api(project(":kotlin-test:kotlin-test-junit")) } kotlin.srcDir("jvm/testLongRunning") } val jsMain by getting { val prepareJsIrMainSources by tasks.registering(Sync::class) kotlin { srcDir(prepareJsIrMainSources) srcDir("$jsDir/builtins") srcDir("$jsDir/runtime") srcDir("$jsDir/src").apply { exclude("kotlin/browser") exclude("kotlin/dom") exclude("kotlinx") exclude("org.w3c") } } prepareJsIrMainSources.configure { val unimplementedNativeBuiltIns = (file("$builtinsDir/native/kotlin/").list()!!.toSortedSet() - file("$jsDir/builtins/").list()!!) .map { "core/builtins/native/kotlin/$it" } // TODO: try to reuse absolute paths defined in the beginning val sources = listOf( "core/builtins/src/kotlin/", ) + unimplementedNativeBuiltIns val excluded = listOf( // JS-specific optimized version of emptyArray() already defined "core/builtins/src/kotlin/ArrayIntrinsics.kt", // included in common "core/builtins/src/kotlin/internal/**", ) sources.forEach { path -> from("$rootDir/$path") { into(path.dropLastWhile { it != '/' }) excluded.filter { it.startsWith(path) }.forEach { exclude(it.substring(path.length)) } } } into(jsIrMainSources) // Required to compile native builtins with the rest of runtime val builtInsHeader = """@file:Suppress( "NON_ABSTRACT_FUNCTION_WITH_NO_BODY", "MUST_BE_INITIALIZED_OR_BE_ABSTRACT", "EXTERNAL_TYPE_EXTENDS_NON_EXTERNAL_TYPE", "PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED", "WRONG_MODIFIER_TARGET", "UNUSED_PARAMETER" ) """ doLast { unimplementedNativeBuiltIns.forEach { path -> val file = File("$destinationDir/$path") val sourceCode = builtInsHeader + file.readText() file.writeText(sourceCode) } } } } val jsTest by getting { dependencies { api(project(":kotlin-test:kotlin-test-js-ir")) } kotlin.srcDir("${jsDir}/test") } val nativeWasmMain by creating { dependsOn(commonMain.get()) kotlin.srcDir("native-wasm/src") } val nativeWasmTest by creating { dependsOn(commonTest.get()) kotlin.srcDir("native-wasm/test") } val wasmCommonMain by creating { dependsOn(nativeWasmMain) val prepareWasmBuiltinSources by tasks.registering(Sync::class) kotlin { srcDir(prepareWasmBuiltinSources) srcDir("wasm/builtins") srcDir("wasm/internal") srcDir("wasm/runtime") srcDir("wasm/src") srcDir("wasm/stubs") } prepareWasmBuiltinSources.configure { val unimplementedNativeBuiltIns = (file("$rootDir/core/builtins/native/kotlin/").list().toSortedSet() - file("wasm/builtins/kotlin/").list()) .map { "core/builtins/native/kotlin/$it" } val sources = listOf( "core/builtins/src/kotlin/" ) + unimplementedNativeBuiltIns val excluded = listOf( // included in commonMain "internal/InternalAnnotations.kt", // JS-specific optimized version of emptyArray() already defined "ArrayIntrinsics.kt", // Included with K/N collections "Collections.kt", "Iterator.kt", "Iterators.kt" ) sources.forEach { path -> from("$rootDir/$path") { into(path.dropLastWhile { it != '/' }) excluded.forEach { exclude(it) } } } into("$buildDir/src/wasm-builtin-sources") } } val wasmCommonTest by creating { dependsOn(nativeWasmTest) kotlin { srcDir("wasm/test") } } val wasmJsMain by getting { dependsOn(wasmCommonMain) kotlin { srcDir("wasm/js/builtins") srcDir("wasm/js/internal") srcDir("wasm/js/src") } } val wasmJsTest by getting { dependsOn(wasmCommonTest) dependencies { api(project(":kotlin-test:kotlin-test-wasm-js")) } kotlin { srcDir("wasm/js/test") } } val wasmWasiMain by getting { dependsOn(wasmCommonMain) kotlin { srcDir("wasm/wasi/builtins") srcDir("wasm/wasi/src") } languageSettings { optIn("kotlin.wasm.unsafe.UnsafeWasmMemoryApi") } } val wasmWasiTest by getting { dependsOn(wasmCommonTest) dependencies { api(project(":kotlin-test:kotlin-test-wasm-wasi")) } kotlin { srcDir("wasm/wasi/test") } } all sourceSet@ { languageSettings { // TODO: progressiveMode = use build property 'test.progressive.mode' if (this@sourceSet == jvmCompileOnlyDeclarations) { return@languageSettings } commonOptIns.forEach { optIn(it) } if (this@sourceSet.name.endsWith("Test")) { commonTestOptIns.forEach { optIn(it) } } } } } } dependencies { val jvmMainApi by configurations.getting val commonMainMetadataElementsWithClassifier by configurations.creating val metadataApiElements by configurations.getting val nativeApiElements by configurations.creating constraints { // there is no dependency anymore from kotlin-stdlib to kotlin-stdlib-common, // but use this constraint to align it if another library brings it transitively jvmMainApi(project(":kotlin-stdlib-common")) commonMainMetadataElementsWithClassifier(project(":kotlin-stdlib-common")) metadataApiElements(project(":kotlin-stdlib-common")) nativeApiElements(project(":kotlin-stdlib-common")) // to avoid split package and duplicate classes on classpath after moving them from these artifacts in 1.8.0 jvmMainApi("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") jvmMainApi("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") } } tasks { val metadataJar by existing(Jar::class) { archiveAppendix.set("metadata") } val sourcesJar by existing(Jar::class) { archiveAppendix.set("metadata") } val jvmJar by existing(Jar::class) { dependsOn(configurationBuiltins) duplicatesStrategy = DuplicatesStrategy.FAIL archiveAppendix.set(null as String?) manifestAttributes(manifest, "Main", multiRelease = true) manifest.attributes(mapOf("Implementation-Title" to "kotlin-stdlib")) from { zipTree(configurationBuiltins.singleFile) } from(kotlin.jvm().compilations["mainJdk7"].output.allOutputs) from(kotlin.jvm().compilations["mainJdk8"].output.allOutputs) from(project.sourceSets["java9"].output) } val jvmSourcesJar by existing(Jar::class) { duplicatesStrategy = DuplicatesStrategy.FAIL archiveAppendix.set(null as String?) into("jvmMain") { from("${rootDir}/core/builtins/native") from(kotlin.sourceSets["jvmMainJdk7"].kotlin) { into("jdk7") } from(kotlin.sourceSets["jvmMainJdk8"].kotlin) { into("jdk8") } } } dexMethodCount { from(jvmJar) ownPackages.set(listOf("kotlin")) } val jsJar by existing(Jar::class) { manifestAttributes(manifest, "Main") manifest.attributes(mapOf("Implementation-Title" to "kotlin-stdlib-js")) } val jsJarForTests by registering(Copy::class) { from(jsJar) rename { _ -> "full-runtime.klib" } // some tests expect stdlib-js klib in this location into(rootProject.buildDir.resolve("js-ir-runtime")) } val jsRearrangedSourcesJar by registering(Jar::class) { archiveClassifier.set("js-sources") archiveVersion.set("") destinationDirectory.set(file("$buildDir/lib")) includeEmptyDirs = false duplicatesStrategy = DuplicatesStrategy.FAIL into("commonMain") { from(kotlin.sourceSets.commonMain.get().kotlin) } into("jsMain") { from(kotlin.sourceSets["jsMain"].kotlin) { // just to depend on source-generating tasks exclude("**") } from("${rootDir}/core/builtins/native/kotlin") { into("kotlin") include("Comparable.kt") include("Enum.kt") } from("$jsIrMainSources/core/builtins/native") { exclude("kotlin/Comparable.kt") } from("$jsIrMainSources/core/builtins/src") from("$jsIrMainSources/libraries/stdlib/js/src") from("$jsDir/builtins") { into("kotlin") exclude("Enum.kt") } from("$jsDir/runtime") { into("runtime") } from("$jsDir/src") { include("**/*.kt") } } } val jsSourcesJar by existing(Jar::class) { val jsSourcesJarFile = jsRearrangedSourcesJar.get().archiveFile inputs.file(jsSourcesJarFile) doLast { jsSourcesJarFile.get().asFile.toPath().copyTo(archiveFile.get().asFile.toPath(), overwrite = true) } } val wasmJsJar by existing(Jar::class) { manifestAttributes(manifest, "Main") } val wasmWasiJar by existing(Jar::class) { manifestAttributes(manifest, "Main") } artifacts { val distJsJar = configurations.create("distJsJar") val distJsSourcesJar = configurations.create("distJsSourcesJar") val distJsKlib = configurations.create("distJsKlib") add(distJsSourcesJar.name, jsSourcesJar) add(distJsKlib.name, jsJar) } val jvmTest by existing(Test::class) listOf(JdkMajorVersion.JDK_9_0, JdkMajorVersion.JDK_10_0, JdkMajorVersion.JDK_11_0).forEach { jvmVersion -> val jvmVersionTest = register("jvm${jvmVersion.majorVersion}Test", Test::class) { group = "verification" javaLauncher.set(getToolchainLauncherFor(jvmVersion)) // additional test tasks are not configured automatically same as the main test task // after KMP plugin stopped applying java plugin classpath = jvmTest.get().classpath testClassesDirs = jvmTest.get().testClassesDirs } check.configure { dependsOn(jvmVersionTest) } } val jvmLongRunningTest by registering(Test::class) { val compilation = kotlin.jvm().compilations["longRunningTest"] classpath = compilation.compileDependencyFiles + compilation.runtimeDependencyFiles + compilation.output.allOutputs testClassesDirs = compilation.output.classesDirs } if (project.hasProperty("kotlin.stdlib.test.long.running")) { check.configure { dependsOn(jvmLongRunningTest) } } listOf("Js", "Wasi").forEach { wasmTarget -> named("compileTestKotlinWasm$wasmTarget", AbstractKotlinCompile::class) { // TODO: fix all warnings, enable -Werror compilerOptions.suppressWarnings = true // exclusions due to KT-51647 exclude("generated/minmax/*") exclude("collections/MapTest.kt") } named("compileTestDevelopmentExecutableKotlinWasm$wasmTarget", KotlinJsIrLink::class) { kotlinOptions.freeCompilerArgs += listOf("-Xwasm-enable-array-range-checks") } } /* We are using a custom 'kotlin-project-structure-metadata' to ensure 'nativeApiElements' lists 'commonMain' as source set */ val generateProjectStructureMetadata by existing { val outputFile = file("build/kotlinProjectStructureMetadata/kotlin-project-structure-metadata.json") 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 = outputFile.readText().trim() val expectedFileContent = outputTestFile.readText().trim() if (outputFileText != expectedFileContent) error( "${outputFile.path} file content does not match expected content\n\n" + "expected:\n\n$expectedFileContent\n\nactual:\n\n$outputFileText" ) } patchedFile.copyTo(outputFile, overwrite = true) } } } // region ==== Publishing ==== configureDefaultPublishing() open class ComponentsFactoryAccess @javax.inject.Inject constructor(val factory: SoftwareComponentFactory) val componentFactory = objects.newInstance().factory val emptyJavadocJar by tasks.creating(org.gradle.api.tasks.bundling.Jar::class) { archiveClassifier.set("javadoc") } publishing { val artifactBaseName = base.archivesName.get() configureMultiModuleMavenPublishing { val rootModule = module("rootModule") { mavenPublication { artifactId = artifactBaseName configureKotlinPomAttributes(project, "Kotlin Standard Library") artifact(emptyJavadocJar) } // creates a variant from existing configuration or creates new one variant("jvmApiElements") variant("jvmRuntimeElements") variant("jvmSourcesElements") variant("metadataApiElements") variant("commonMainMetadataElementsWithClassifier") { name = "commonMainMetadataElements" configuration { isCanBeConsumed = false } attributes { copyAttributes(from = project.configurations["commonMainMetadataElements"].attributes, to = this) } artifact(tasks["metadataJar"]) { classifier = "common" } } variant("metadataSourcesElementsFromJvm") { name = "metadataSourcesElements" configuration { // to avoid clash in Gradle 8+ with metadataSourcesElements configuration with the same attributes isCanBeConsumed = false } attributes { copyAttributes(from = project.configurations["metadataSourcesElements"].attributes, to = this) } artifact(tasks["sourcesJar"]) { classifier = "common-sources" } } 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) } } } // we cannot publish legacy common artifact with metadata in kotlin-stdlib-common // because it will cause problems in explicitly configured stdlib dependencies in project // val common = module("commonModule") { // mavenPublication { // artifactId = "$artifactBaseName-common" // configureKotlinPomAttributes(project, "Kotlin Common Standard Library (for compatibility with legacy multiplatform)") // artifact(tasks["sourcesJar"]) // publish sources.jar just for maven, without including it in Gradle metadata // } // variant("commonMainMetadataElements") // } val js = module("jsModule") { mavenPublication { artifactId = "$artifactBaseName-js" configureKotlinPomAttributes(project, "Kotlin Standard Library for JS", packaging = "klib") } variant("jsApiElements") variant("jsRuntimeElements") variant("jsSourcesElements") } val wasmJs = module("wasmJsModule") { mavenPublication { artifactId = "$artifactBaseName-wasm-js" configureKotlinPomAttributes(project, "Kotlin Standard 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 Standard Library for experimental WebAssembly WASI platform", packaging = "klib") } variant("wasmWasiApiElements") variant("wasmWasiRuntimeElements") variant("wasmWasiSourcesElements") } // Makes all variants from accompanying artifacts visible through `available-at` rootModule.include(js, wasmJs, wasmWasi) } publications { val rootModule by existing(MavenPublication::class) val jsModule by existing(MavenPublication::class) configureSbom("Main", "kotlin-stdlib", setOf("jvmRuntimeClasspath"), rootModule) configureSbom("Js", "kotlin-stdlib-js", setOf("jsRuntimeClasspath"), jsModule) val wasmJsModule by existing(MavenPublication::class) val wasmWasiModule by existing(MavenPublication::class) configureSbom("Wasm-Js", "kotlin-stdlib-wasm-js", setOf("wasmJsRuntimeClasspath"), wasmJsModule) configureSbom("Wasm-Wasi", "kotlin-stdlib-wasm-wasi", setOf("wasmWasiRuntimeClasspath"), wasmWasiModule) } } fun copyAttributes(from: AttributeContainer, to: AttributeContainer,) { // capture type argument T fun copyOneAttribute(from: AttributeContainer, to: AttributeContainer, key: Attribute) { val value = checkNotNull(from.getAttribute(key)) to.attribute(key, value) } for (key in from.keySet()) { copyOneAttribute(from, to, key) } } class MultiModuleMavenPublishingConfiguration() { val modules = mutableMapOf() class Module(val name: String) { val variants = mutableMapOf() val includes = mutableSetOf() class Variant( val configurationName: String ) { var name: String = configurationName val attributesConfigurations = mutableListOf Unit>() fun attributes(code: AttributeContainer.() -> Unit) { attributesConfigurations += code } val artifactsWithConfigurations = mutableListOf Unit>>() fun artifact(file: Any, code: ConfigurablePublishArtifact.() -> Unit = {}) { artifactsWithConfigurations += file to code } val configurationConfigurations = mutableListOf Unit>() fun configuration(code: Configuration.() -> Unit) { configurationConfigurations += code } val variantDetailsConfigurations = mutableListOf Unit>() fun configureVariantDetails(code: ConfigurationVariantDetails.() -> Unit) { variantDetailsConfigurations += code } } val mavenPublicationConfigurations = mutableListOf Unit>() fun mavenPublication(code: MavenPublication.() -> Unit) { mavenPublicationConfigurations += code } fun variant(fromConfigurationName: String, code: Variant.() -> Unit = {}): Variant { val variant = variants.getOrPut(fromConfigurationName) { Variant(fromConfigurationName) } variant.code() return variant } fun include(vararg modules: Module) { includes.addAll(modules) } } fun module(name: String, code: Module.() -> Unit): Module { val module = modules.getOrPut(name) { Module(name) } module.code() return module } } fun configureMultiModuleMavenPublishing(code: MultiModuleMavenPublishingConfiguration.() -> Unit) { val publishingConfiguration = MultiModuleMavenPublishingConfiguration() publishingConfiguration.code() val components = publishingConfiguration .modules .mapValues { (_, module) -> project.createModulePublication(module) } val componentsWithExternals = publishingConfiguration .modules .filter { (_, module) -> module.includes.isNotEmpty() } .mapValues { (moduleName, module) -> val mainComponent = components[moduleName] ?: error("Component with name $moduleName wasn't created") val externalComponents = module.includes .map { components[it.name] ?: error("Component with name ${it.name} wasn't created") } .toSet() ComponentWithExternalVariants(mainComponent, externalComponents) } // override some components wih items from componentsWithExternals val mergedComponents = components + componentsWithExternals val publicationsContainer = publishing.publications for ((componentName, component) in mergedComponents) { publicationsContainer.create(componentName) { from(component) val module = publishingConfiguration.modules[componentName]!! module.mavenPublicationConfigurations.forEach { configure -> configure() } } } } fun Project.createModulePublication(module: MultiModuleMavenPublishingConfiguration.Module): SoftwareComponent { val component = componentFactory.adhoc(module.name) module.variants.values.forEach { addVariant(component, it) } val newNames = module.variants.map { it.key to it.value.name }.filter { it.first != it.second }.toMap() return if (newNames.isNotEmpty()) { ComponentWithRenamedVariants(newNames, component as SoftwareComponentInternal) } else { component } } fun Project.addVariant(component: AdhocComponentWithVariants, variant: MultiModuleMavenPublishingConfiguration.Module.Variant) { val configuration = configurations.getOrCreate(variant.configurationName) configuration.apply { isCanBeResolved = false isCanBeConsumed = true variant.attributesConfigurations.forEach { configure -> attributes.configure() } } for ((artifactNotation, configure) in variant.artifactsWithConfigurations) { artifacts.add(configuration.name, artifactNotation) { configure() } } for (configure in variant.configurationConfigurations) { configuration.apply(configure) } component.addVariantsFromConfiguration(configuration) { variant.variantDetailsConfigurations.forEach { configure -> configure() } } } private class RenamedVariant(val newName: String, context: UsageContext) : UsageContext by context { override fun getName(): String = newName } private class ComponentWithRenamedVariants( val newNames: Map, private val base: SoftwareComponentInternal ): SoftwareComponentInternal by base { override fun getName(): String = base.name override fun getUsages(): Set { return base.usages.map { val newName = newNames[it.name] if (newName != null) { RenamedVariant(newName, it) } else { it } }.toSet() } } private class ComponentWithExternalVariants( private val mainComponent: SoftwareComponent, private val externalComponents: Set ) : ComponentWithVariants, SoftwareComponentInternal { override fun getName(): String = mainComponent.name override fun getUsages(): Set = (mainComponent as SoftwareComponentInternal).usages override fun getVariants(): Set = externalComponents } // endregion // for legacy intra-project dependencies for (name in listOf("sources", "distSources")) { val sourcesConfiguration = configurations.getOrCreate(name).apply { isCanBeResolved = false isCanBeConsumed = true } artifacts.add(sourcesConfiguration.name, tasks["jvmSourcesJar"]) }