From 1a5605a6e6e1ddf921852a6d487e4a1f208dd5e2 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov <4046447+0xnm@users.noreply.github.com> Date: Thu, 7 Nov 2024 01:27:07 +0100 Subject: [PATCH] Add `arrow-kt` example (#3886) This PR partially addresses #3670: it contains working example, but doesn't contain any docs. This example contains compilation and test execution for all the modules of https://github.com/arrow-kt/arrow for the JVM and JS (Node) targets, except [arrow-optics-compose](https://github.com/arrow-kt/arrow/tree/main/arrow-libs/optics/arrow-optics-compose) module (it requires better Android support by Mill). Full example run takes 6 minutes on my machine, so it is extracted into a dedicated CI job. The following Gradle plugins are not added to the example build script, because their respective tooling is Gradle-dependent (no raw-jar or CLI): * https://github.com/xvik/gradle-animalsniffer-plugin (although maybe it is possible to use options from https://www.mojohaus.org/animal-sniffer/) * https://github.com/Kotlin/kotlinx-knit * https://github.com/Kotlin/binary-compatibility-validator * https://github.com/diffplug/spotless There is also no publication support in this example (requires Kotlin Multiplatform publishing support, see https://github.com/com-lihaoyi/mill/issues/3867) I faced the following issues while writing this example: * There is quite a lot of duplication for JVM/JS configuration, because there is no support of [Kotlin Multiplatform hierarchies](https://kotlinlang.org/docs/multiplatform-hierarchy.html) * Each Maven or Module dependency should be with JS/JVM/etc. target qualifier, which also quite explodes amount of code to be written. This is also a question of Kotlin Multiplatform hierarchies support and resolution. * Until KMP hierarchies and target resolution support is not implemented, adding a new compilation target will bring quite a lot of new code in the build script. And there is a lot of targets in Arrow: https://github.com/arrow-kt/arrow-gradle-config/blob/97ba7b5eab810a336cf4070eb717f05533d208a8/arrow-gradle-config-kotlin/src/main/kotlin/io.arrow-kt.arrow-gradle-config-kotlin.gradle.kts#L34-L69 * There is no possibility to disable tests for the particular module (so I had to use a hack with overriding `compile` / `test` tasks) - this is because tests is dedicated module, but if comes as a part of the trait, we cannot add / remove it dynamically. Having such control is handy when certain multiplatform modules have a common trait for the main compilation unit, but some of them may have no tests. Regarding docs: I think it is too early to write any comparison with Gradle, because clearly new functionality will be added to the Kotlin support in Mill which will affect overall execution time and also the comparison should be done not only for JVM targets, but for Kotlin/JS and Kotlin/Native targets as well. And ideally it should be a defined methodology for such testing (for example, certain Gradle plugins should be removed from the Arrow Gradle build script, to have the same set as Mill build script; compiler execution time should be excluded (it may be different because of the different flags passed to it under the hood)). So I propose to open a dedicated bounty for writing such doc. --------- Co-authored-by: 0xnm <0xnm@users.noreply.github.com> Co-authored-by: Li Haoyi --- .github/workflows/run-tests.yml | 2 + example/package.mill | 3 +- example/thirdparty/arrow/build.mill | 652 ++++++++++++++++++ .../src/mill/kotlinlib/KotlinModule.scala | 67 +- .../mill/kotlinlib/js/KotlinJsModule.scala | 113 ++- .../mill/kotlinlib/js/KotlinJsLinkTests.scala | 2 + 6 files changed, 796 insertions(+), 43 deletions(-) create mode 100644 example/thirdparty/arrow/build.mill diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5262ed6cf40..b38301903d1 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -74,6 +74,8 @@ jobs: millargs: "'example.thirdparty[{mockito,acyclic,commons-io}].local.testCached'" - java-version: 17 millargs: "'example.thirdparty[{fansi,jimfs,netty,gatling}].local.testCached'" + - java-version: '17' + millargs: "'example.thirdparty[arrow].local.testCached'" - java-version: '11' millargs: "'example.{depth,extending}.__.local.testCached'" diff --git a/example/package.mill b/example/package.mill index b5dfaaa99cf..4ed7222057b 100644 --- a/example/package.mill +++ b/example/package.mill @@ -240,7 +240,8 @@ object `package` extends RootModule with Module { "commons-io" -> ("apache/commons-io", "b91a48074231ef813bc9b91a815d77f6343ff8f0"), "netty" -> ("netty/netty", "20a790ed362a3c11e0e990b58598e4ac6aa88bef"), "mockito" -> ("mockito/mockito", "97f3574cc07fdf36f1f76ba7332ac57675e140b1"), - "gatling" -> ("gatling/gatling", "3870fda86e6bca005fbd53108c60a65db36279b6") + "gatling" -> ("gatling/gatling", "3870fda86e6bca005fbd53108c60a65db36279b6"), + "arrow" -> ("arrow-kt/arrow", "bc9bf92cc98e01c21bdd2bf8640cf7db0f97204a") ) object thirdparty extends Cross[ThirdPartyModule](build.listIn(millSourcePath / "thirdparty")) trait ThirdPartyModule extends ExampleCrossModule { diff --git a/example/thirdparty/arrow/build.mill b/example/thirdparty/arrow/build.mill new file mode 100644 index 00000000000..cf6800e619b --- /dev/null +++ b/example/thirdparty/arrow/build.mill @@ -0,0 +1,652 @@ +package build + +import mill._, kotlinlib._, kotlinlib.js._ +import mill.scalalib.CoursierModule +import mill.api.Result +import mill.testrunner.TestResult +import mill.scalalib.api.CompilationResult +import mill.kotlinlib.kover.KoverModule + +object libraries { + + object versions { + val coroutines = "1.9.0" + val classgraph = "4.8.177" + val dokka = "1.9.20" + val kotest = "5.9.1" + val kotlin = "2.0.21" + val kotlinCompileTesting = "0.5.1" + val kspVersion = "2.0.21-1.0.25" + val kotlinxSerialization = "1.7.3" + val mockWebServer = "4.12.0" + val retrofit = "2.11.0" + val moshi = "1.15.1" + val cache4k = "0.13.0" + // not used yet + val compose = "1.7.4" + val composePlugin = "1.7.0" + val agp = "8.7.1" + val androidCompileSdk = "34" + } + + // libraries + val coroutinesCoreJvm = + ivy"org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:${versions.coroutines}" + val coroutinesCoreJs = + ivy"org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${versions.coroutines}" + val coroutinesTestJvm = + ivy"org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm:${versions.coroutines}" + val coroutinesTestJs = + ivy"org.jetbrains.kotlinx:kotlinx-coroutines-test-js:${versions.coroutines}" + val kotestAssertionsCoreJvm = ivy"io.kotest:kotest-assertions-core-jvm:${versions.kotest}" + val kotestAssertionsCoreJs = ivy"io.kotest:kotest-assertions-core-js:${versions.kotest}" + val kotestPropertyJvm = ivy"io.kotest:kotest-property-jvm:${versions.kotest}" + val kotestPropertyJs = ivy"io.kotest:kotest-property-js:${versions.kotest}" + val kotlinReflect = ivy"org.jetbrains.kotlin:kotlin-reflect:${versions.kotlin}" + val kotlinStdlib = ivy"org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}" + val kotlinTestJunit5 = ivy"org.jetbrains.kotlin:kotlin-test-junit5:${versions.kotlin}" + val kotlinTestJs = ivy"org.jetbrains.kotlin:kotlin-test-js:${versions.kotlin}" + val kotlinxSerializationCoreJvm = + ivy"org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:${versions.kotlinxSerialization}" + val kotlinxSerializationCoreJs = + ivy"org.jetbrains.kotlinx:kotlinx-serialization-core-js:${versions.kotlinxSerialization}" + val kotlinxSerializationJson = + ivy"org.jetbrains.kotlinx:kotlinx-serialization-json:${versions.kotlinxSerialization}" + val kotlinxSerializationJsonJs = + ivy"org.jetbrains.kotlinx:kotlinx-serialization-json-js:${versions.kotlinxSerialization}" + val squareupOkhttpMockWebServer = + ivy"com.squareup.okhttp3:mockwebserver:${versions.mockWebServer}" + val squareupRetrofitLib = ivy"com.squareup.retrofit2:retrofit:${versions.retrofit}" + val squareupRetrofitConverterGson = + ivy"com.squareup.retrofit2:converter-gson:${versions.retrofit}" + val squareupRetrofitConverterMoshi = + ivy"com.squareup.retrofit2:converter-moshi:${versions.retrofit}" + val squareupRetrofitConverterKotlinxSerialization = + ivy"com.squareup.retrofit2:converter-kotlinx-serialization:${versions.retrofit}" + val squareupMoshiKotlin = ivy"com.squareup.moshi:moshi-kotlin:${versions.moshi}" + val ksp = ivy"com.google.devtools.ksp:symbol-processing-api:${versions.kspVersion}" + val kspGradlePlugin = + ivy"com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:${versions.kspVersion}" + val classgraph = ivy"io.github.classgraph:classgraph:${versions.classgraph}" + val composeRuntime = ivy"androidx.compose.runtime:runtime:${versions.compose}" + val kotlinCompileTesting = ivy"dev.zacsweers.kctfork:core:${versions.kotlinCompileTesting}" + val kotlinCompileTestingKsp = ivy"dev.zacsweers.kctfork:ksp:${versions.kotlinCompileTesting}" + val cache4kJs = ivy"io.github.reactivecircus.cache4k:cache4k-js:${versions.cache4k}" + val cache4kJvm = ivy"io.github.reactivecircus.cache4k:cache4k-jvm:${versions.cache4k}" + + // plugins + val kotlinxSerializationPlugin = + ivy"org.jetbrains.kotlin:kotlin-serialization-compiler-plugin:${versions.kotlin}" +} + +object `package` extends RootModule { + + // comment to disable test for the module + val modulesWithTestingEnabled = Set[CoursierModule]( +// these modules don't have hand-written tests +// `arrow-libs`.core.`arrow-annotations`, +// `arrow-libs`.core.`arrow-cache4k`, +// `arrow-libs`.core.`arrow-core-high-arity`, + + `arrow-libs`.core.`arrow-atomic`, + `arrow-libs`.core.`arrow-autoclose`, + `arrow-libs`.core.`arrow-core-retrofit`, + `arrow-libs`.core.`arrow-core-serialization`, + `arrow-libs`.core.`arrow-core`, + `arrow-libs`.core.`arrow-eval`, + `arrow-libs`.core.`arrow-functions`, + `arrow-libs`.fx.`arrow-collectors`, + `arrow-libs`.fx.`arrow-fx-coroutines`, + `arrow-libs`.fx.`arrow-fx-stm`, +// Cannot test this one - it relies on the module dependency in the .jar format, while Mill provides .class files +// `arrow-libs`.optics.`arrow-optics-ksp-plugin`, + `arrow-libs`.optics.`arrow-optics-reflect`, + `arrow-libs`.optics.`arrow-optics`, + + `arrow-libs`.resilience.`arrow-resilience` + ) + + def majorVersion(version: String): String = version.split("\\.", 3).take(2).mkString(".") + + // TODO support Kotlin Multiplatform hierarchies + // TODO add more targets once available + trait ArrowMultiplatformModule extends CoursierModule { multiplatformRoot => + + def kotlinExplicitApi: T[Boolean] = true + + protected def commonSourcesArg(sources: Seq[PathRef], commonSourcesDirName: String): String = { + val files = + sources + .flatMap(pr => os.walk(pr.path)) + .filter(p => p.segments.contains(commonSourcesDirName) && os.isFile(p)) + // TODO this is to prevent compilation failure in cases when there is actual declaration in Foo.kt and expect + // declaration in Foo.kt instead of Foo.[jvm,js,...].kt, because in this case symbols belong to the same module. + // This is allowed with `-Xcommon-sources` flag, but probably will be prohibited in the future. + // Ideally, it should be in the [[KotlinModule]] directly, but this implies [[KotlinModule]] should know where + // common sources are, making some assumptions about layout. + s"-Xcommon-sources=${files.mkString(",")}" + } + + trait ArrowPlatformModule extends KotlinMavenModule with PlatformKotlinModule { outer => + def kotlinVersion = libraries.versions.kotlin + def kotlinLanguageVersion = majorVersion(kotlinVersion()) + def kotlinApiVersion = majorVersion(kotlinVersion()) + def kotlinExplicitApi = ArrowMultiplatformModule.this.kotlinExplicitApi + + override def sources: T[Seq[PathRef]] = Task.Sources { + val sourcesRootPath = millSourcePath / "src" + var sources = Seq("common", platformCrossSuffix) + .map(platform => PathRef(sourcesRootPath / s"${platform}Main" / "kotlin")) + .filter(p => os.exists(p.path)) + if (platformCrossSuffix != "jvm") { + val nonJvmSourcesPath = sourcesRootPath / "nonJvmMain" / "kotlin" + if (os.exists(nonJvmSourcesPath)) { + sources ++= Seq(PathRef(nonJvmSourcesPath)) + } + } + sources + } + + override def kotlincOptions = + super.kotlincOptions() ++ + Seq("-Xexpect-actual-classes", commonSourcesArg(sources(), "commonMain")) + + trait ArrowPlatformTests extends KotlinMavenTests{ + + override def compile: T[CompilationResult] = + if (modulesWithTestingEnabled(multiplatformRoot)) super.compile + else Task { CompilationResult(T.dest, PathRef(T.dest)) } + + override def test(args: String*): Command[(String, Seq[TestResult])] = + if (modulesWithTestingEnabled(multiplatformRoot)) super.test(args: _*) + else Task.Command { ("", Seq.empty[TestResult]) } + + override def sources: T[Seq[PathRef]] = Task.Sources { + val sourcesRootPath = outer.millSourcePath / "src" + Seq("common", outer.platformCrossSuffix) + .map(platform => PathRef(sourcesRootPath / s"${platform}Test" / "kotlin")) + .filter(p => os.exists(p.path)) + } + } + } + + def jvm: ArrowPlatformJvmModule + + trait ArrowPlatformJvmModule extends ArrowPlatformModule with KoverModule { + def test: ArrowPlatformJvmTests + trait ArrowPlatformJvmTests extends KotlinMavenTests with KoverTests with TestModule.Junit5 with ArrowPlatformTests{ + } + } + + def js: ArrowPlatformJsModule + trait ArrowPlatformJsModule extends ArrowPlatformModule with KotlinJsModule { + + def kotlinJsRunTarget = Some(RunTarget.Node) + + def test: ArrowPlatformJsTests + trait ArrowPlatformJsTests extends KotlinMavenTests with KotlinTestPackageTests with ArrowPlatformTests{ + def testTimeout = 30_000L + } + } + } + + trait ArrowJvmModule extends KotlinMavenModule with KoverModule { jvmRoot => + + def kotlinVersion = libraries.versions.kotlin + def kotlinExplicitApi = true + def kotlinLanguageVersion = majorVersion(kotlinVersion()) + def kotlinApiVersion = majorVersion(kotlinVersion()) + + def test: ArrowJvmTests + trait ArrowJvmTests extends KotlinMavenTests with KoverTests with TestModule.Junit5 { + override def compile: T[CompilationResult] = + if (modulesWithTestingEnabled(jvmRoot)) super.compile + else Task { CompilationResult(T.dest, PathRef(T.dest)) } + + override def test(args: String*): Command[(String, Seq[TestResult])] = + if (modulesWithTestingEnabled(jvmRoot)) super.test(args: _*) + else Task.Command { ("", Seq.empty[TestResult]) } + } + } + + object `arrow-libs` extends Module { + object core extends Module { + object `arrow-annotations` extends ArrowMultiplatformModule{ + object jvm extends ArrowPlatformJvmModule{ + object test extends ArrowPlatformJvmTests + } + object js extends ArrowPlatformJsModule{ + object test extends ArrowPlatformJsTests + } + } + object `arrow-atomic` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + object test extends ArrowPlatformJvmTests{ + def moduleDeps = super.moduleDeps ++ Seq(fx.`arrow-fx-coroutines`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + } + } + object js extends ArrowPlatformJsModule{ + object test extends ArrowPlatformJsTests{ + def moduleDeps = super.moduleDeps ++ Seq(fx.`arrow-fx-coroutines`.js) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.coroutinesTestJs, + libraries.kotestAssertionsCoreJs, + libraries.kotestPropertyJs + ) + } + } + + + + } + object `arrow-autoclose` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-atomic`.jvm) + object test extends ArrowPlatformJvmTests{ + def moduleDeps = super.moduleDeps ++ Seq(fx.`arrow-fx-coroutines`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm + ) + def kotlincOptions = super.kotlincOptions() ++ Seq("-Xcontext-receivers") + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-atomic`.js) + object test extends ArrowPlatformJsTests{ + def moduleDeps = super.moduleDeps ++ Seq(fx.`arrow-fx-coroutines`.js) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.coroutinesTestJs, + libraries.kotestAssertionsCoreJs + ) + } + } + } + + object `arrow-cache4k` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-core`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.cache4kJvm) + object test extends ArrowPlatformJvmTests + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-core`.js) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.cache4kJs) + object test extends ArrowPlatformJsTests + } + } + + object `arrow-core-high-arity` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-core`.jvm) + object test extends ArrowPlatformJvmTests + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-core`.js) + object test extends ArrowPlatformJsTests + } + } + + object `arrow-core-retrofit` extends ArrowJvmModule { + def moduleDeps = Seq(`arrow-core`.jvm) + def compileIvyDeps = Agg(libraries.squareupRetrofitLib) + def processors = Task { + defaultResolver().resolveDeps(Agg(libraries.kotlinxSerializationPlugin)) + } + + object test extends ArrowJvmTests{ + def kotlincOptions = super.kotlincOptions() ++ Seq(s"-Xplugin=${processors().head.path}") + def moduleDeps = super.moduleDeps ++ Seq(`arrow-core`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm, + libraries.squareupOkhttpMockWebServer, + libraries.squareupRetrofitConverterGson, + libraries.squareupRetrofitConverterMoshi, + libraries.kotlinxSerializationJson, + libraries.squareupRetrofitConverterKotlinxSerialization, + libraries.squareupMoshiKotlin + ) + } + } + object `arrow-core-serialization` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-core`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.kotlinxSerializationCoreJvm) + object test extends ArrowPlatformJvmTests{ + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.kotlinxSerializationJson, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + def kotlincOptions = super.kotlincOptions() ++ Seq(s"-Xplugin=${processors().head.path}") + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-core`.js) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.kotlinxSerializationCoreJs) + object test extends ArrowPlatformJsTests{ + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinxSerializationJsonJs, + libraries.coroutinesTestJs, + libraries.kotestAssertionsCoreJs, + libraries.kotestPropertyJs + ) + def kotlincOptions = super.kotlincOptions() ++ Seq(s"-Xplugin=${processors().head.path}") + def testTimeout = 60_000L + } + } + + def processors = Task { + defaultResolver().resolveDeps(Agg(libraries.kotlinxSerializationPlugin)) + } + } + + object `arrow-core` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-atomic`.jvm, `arrow-annotations`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.kotlinxSerializationCoreJvm) + def kotlincOptions = super.kotlincOptions() ++ Seq("-Xcontext-receivers") + object test extends ArrowPlatformJvmTests{ + def moduleDeps = super.moduleDeps ++ Seq(fx.`arrow-fx-coroutines`.jvm) + + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-atomic`.js, `arrow-annotations`.js) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.kotlinxSerializationCoreJs) + object test extends ArrowPlatformJsTests { + def moduleDeps = super.moduleDeps ++ Seq(fx.`arrow-fx-coroutines`.js) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.coroutinesTestJs, + libraries.kotestAssertionsCoreJs, + libraries.kotestPropertyJs + ) + def testTimeout = 300_000L + } + } + } + + object `arrow-eval` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-core`.jvm) + object test extends ArrowPlatformJvmTests{ + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-core`.js) + object test extends ArrowPlatformJsTests{ + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotestAssertionsCoreJs, + libraries.kotestPropertyJs + ) + } + } + } + + object `arrow-functions` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-atomic`.jvm, `arrow-annotations`.jvm) + object test extends ArrowPlatformJvmTests{ + def moduleDeps = super.moduleDeps ++ Seq(fx.`arrow-fx-coroutines`.jvm) + + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-atomic`.js, `arrow-annotations`.js) + object test extends ArrowPlatformJsTests{ + def moduleDeps = super.moduleDeps ++ Seq(fx.`arrow-fx-coroutines`.js) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.coroutinesTestJs, + libraries.kotestAssertionsCoreJs, + libraries.kotestPropertyJs + ) + } + } + } + } + + object fx extends Module { + object `arrow-collectors` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-atomic`.jvm, `arrow-fx-coroutines`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.coroutinesCoreJvm) + object test extends ArrowPlatformJvmTests{ + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-atomic`.js, `arrow-fx-coroutines`.js) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.coroutinesCoreJs) + object test extends ArrowPlatformJsTests{ + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.coroutinesTestJs, + libraries.kotestAssertionsCoreJs, + libraries.kotestPropertyJs + ) + def testTimeout = 120_000L + } + } + + + } + object `arrow-fx-coroutines` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.jvm, core.`arrow-autoclose`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.coroutinesCoreJvm) + object test extends ArrowPlatformJvmTests{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.jvm, core.`arrow-atomic`.jvm) + + + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.js, core.`arrow-autoclose`.js) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.coroutinesCoreJs) + object test extends ArrowPlatformJsTests{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.js, core.`arrow-atomic`.js) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.coroutinesTestJs, + libraries.kotestAssertionsCoreJs, + libraries.kotestPropertyJs + ) + def testTimeout = 60_000L + } + } + + + + + } + object `arrow-fx-stm` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.coroutinesCoreJvm) + def kotlincOptions = super.kotlincOptions() ++ additionalKotlincOptions + object test extends ArrowPlatformJvmTests{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-fx-coroutines`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.js) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.coroutinesCoreJs) + def kotlincOptions = super.kotlincOptions() ++ additionalKotlincOptions + object test extends ArrowPlatformJsTests{ + def moduleDeps = super.moduleDeps ++ Seq(`arrow-fx-coroutines`.js) + + + + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.coroutinesTestJs, + libraries.kotestAssertionsCoreJs, + libraries.kotestPropertyJs + ) + def testTimeout = 60_000L + } + } + val additionalKotlincOptions = Seq("-Xconsistent-data-class-copy-visibility") + + + } + } + + object optics extends Module { + // TODO requires better Android support + // object `arrow-optics-compose` extends Module + object `arrow-optics-ksp-plugin` extends ArrowJvmModule { + def kotlinExplicitApi = false + def ivyDeps = Agg(libraries.ksp) + + object test extends ArrowJvmTests{ + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm, + libraries.classgraph, + libraries.kotlinCompileTesting, + libraries.kotlinCompileTestingKsp + ) + + def runModuleDeps = super.runModuleDeps ++ Seq( + core.`arrow-annotations`.jvm, + core.`arrow-core`.jvm, + `arrow-optics`.jvm + ) + } + } + object `arrow-optics-reflect` extends ArrowJvmModule { + def moduleDeps = Seq(core.`arrow-core`.jvm, `arrow-optics`.jvm) + def ivyDeps = Agg(libraries.kotlinReflect) + + object test extends ArrowJvmTests{ + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + } + } + object `arrow-optics` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.jvm) + object test extends ArrowPlatformJvmTests{ + def kotlincOptions = super.kotlincOptions() ++ Seq(commonSourcesArg(jvm.test.sources(), "commonTest")) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm, + libraries.kotestAssertionsCoreJvm, + libraries.kotestPropertyJvm + ) + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.js) + object test extends ArrowPlatformJsTests{ + def kotlincOptions = super.kotlincOptions() ++ Seq(commonSourcesArg(js.test.sources(), "commonTest")) + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.coroutinesTestJs, + libraries.kotestAssertionsCoreJs, + libraries.kotestPropertyJs + ) + } + } + } + } + + object resilience extends Module { + object `arrow-resilience` extends ArrowMultiplatformModule { + object jvm extends ArrowPlatformJvmModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.jvm) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.coroutinesCoreJvm) + object test extends ArrowPlatformJvmTests{ + def moduleDeps = super.moduleDeps ++ Seq(fx.`arrow-fx-coroutines`.jvm) + + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.kotlinTestJunit5, + libraries.coroutinesTestJvm + ) + } + } + object js extends ArrowPlatformJsModule{ + def moduleDeps = super.moduleDeps ++ Seq(core.`arrow-core`.js) + def ivyDeps = super.ivyDeps() ++ Agg(libraries.coroutinesCoreJs) + object test extends ArrowPlatformJsTests{ + def moduleDeps = super.moduleDeps ++Seq(fx.`arrow-fx-coroutines`.js) + + def ivyDeps = super.ivyDeps() ++ Agg( + libraries.coroutinesTestJs + ) + } + } + } + } + } +} + +/** Usage + +> sed -i.bak 's/.cause.shouldBeInstanceOf()/.cause.shouldBeInstanceOf()/g' arrow-libs/core/arrow-core-retrofit/src/test/kotlin/arrow/retrofit/adapter/either/networkhandling/NetworkEitherCallAdapterTest.kt # fix wrong assertion + +> ./mill -j5 __.compile +Compiling 6 Kotlin sources to ...arrow-libs/resilience/arrow-resilience/jvm/compile.dest/classes ... +Compiling 6 Kotlin sources to ...resilience/arrow-resilience/js/compile.dest/classes ... +Compiling 11 Kotlin sources to ...fx/arrow-fx-stm/jvm/compile.dest/classes ... +Compiling 11 Kotlin sources to ...fx/arrow-fx-stm/js/compile.dest/classes ... + +> ./mill __.test +Test arrow.resilience... +Test arrow.collectors... +Test arrow.core... + +> ./mill mill.kotlinlib.kover.Kover/htmlReportAll + +> ./mill __:^TestModule.docJar + +*/ diff --git a/kotlinlib/src/mill/kotlinlib/KotlinModule.scala b/kotlinlib/src/mill/kotlinlib/KotlinModule.scala index 2727dc1b2a0..5ce0f2c6483 100644 --- a/kotlinlib/src/mill/kotlinlib/KotlinModule.scala +++ b/kotlinlib/src/mill/kotlinlib/KotlinModule.scala @@ -40,7 +40,7 @@ trait KotlinModule extends JavaModule { outer => * Subset of [[allSourceFiles]]. */ def allKotlinSourceFiles: T[Seq[PathRef]] = Task { - allSourceFiles().filter(path => Seq("kt", "kts").exists(path.path.ext.toLowerCase() == _)) + allSourceFiles().filter(path => Seq("kt", "kts").contains(path.path.ext.toLowerCase())) } /** @@ -64,6 +64,21 @@ trait KotlinModule extends JavaModule { outer => */ def kotlinCompilerVersion: T[String] = Task { kotlinVersion() } + /** + * The compiler language version. Default is not set. + */ + def kotlinLanguageVersion: T[String] = Task { "" } + + /** + * The compiler API version. Default is not set. + */ + def kotlinApiVersion: T[String] = Task { "" } + + /** + * Flag to use explicit API check in the compiler. Default is `false`. + */ + def kotlinExplicitApi: T[Boolean] = Task { false } + type CompileProblemReporter = mill.api.CompileProblemReporter protected def zincWorkerRef: ModuleRef[ZincWorkerModule] = zincWorker @@ -144,11 +159,24 @@ trait KotlinModule extends JavaModule { outer => // `;` separator is used on all platforms! dokkaPluginsClasspath().map(_.path).mkString(";") ) + val depClasspath = (compileClasspath() ++ runClasspath()) + .filter(p => os.exists(p.path)) + .map(_.path.toString()).mkString(";") + // TODO need to provide a dedicated source set for common sources in case of Multiplatform + // platforms supported: jvm, js, wasm, native, common val options = dokkaOptions() ++ Seq("-outputDir", dokkaDir.toString()) ++ pluginClasspathOption ++ - Seq("-sourceSet", s"-src $millSourcePath") + Seq( + s"-sourceSet", + Seq( + s"-src ${docSources().map(_.path).filter(os.exists).mkString(";")}", + s"-displayName $dokkaSourceSetDisplayName", + s"-classpath $depClasspath", + s"-analysisPlatform $dokkaAnalysisPlatform" + ).mkString(" ") + ) T.log.info("dokka options: " + options) @@ -199,6 +227,9 @@ trait KotlinModule extends JavaModule { outer => ) } + protected def dokkaAnalysisPlatform: String = "jvm" + protected def dokkaSourceSetDisplayName: String = "jvm" + protected def when(cond: Boolean)(args: String*): Seq[String] = if (cond) args else Seq() /** @@ -244,11 +275,17 @@ trait KotlinModule extends JavaModule { outer => val compilerArgs: Seq[String] = Seq( // destdir Seq("-d", classes.toString()), + // apply multi-platform support (expect/actual) + // TODO if there is penalty for activating it in the compiler, put it behind configuration flag + Seq("-Xmulti-platform"), // classpath when(compileCp.iterator.nonEmpty)( "-classpath", compileCp.iterator.mkString(File.pathSeparator) ), + when(kotlinExplicitApi())( + "-Xexplicit-api=strict" + ), kotlincOptions(), extraKotlinArgs, // parameters @@ -287,13 +324,19 @@ trait KotlinModule extends JavaModule { outer => * Additional Kotlin compiler options to be used by [[compile]]. */ def kotlincOptions: T[Seq[String]] = Task { - Seq("-no-stdlib") ++ - when(!kotlinVersion().startsWith("1.0"))( - "-language-version", - kotlinVersion().split("[.]", 3).take(2).mkString("."), - "-api-version", - kotlinVersion().split("[.]", 3).take(2).mkString(".") - ) + val options = Seq.newBuilder[String] + options += "-no-stdlib" + val languageVersion = kotlinLanguageVersion() + if (!languageVersion.isBlank) { + options += "-language-version" + options += languageVersion + } + val kotlinkotlinApiVersion = kotlinApiVersion() + if (!kotlinkotlinApiVersion.isBlank) { + options += "-api-version" + options += kotlinkotlinApiVersion + } + options.result() } private[kotlinlib] def internalCompileJavaFiles( @@ -328,9 +371,13 @@ trait KotlinModule extends JavaModule { outer => * A test sub-module linked to its parent module best suited for unit-tests. */ trait KotlinTests extends JavaTests with KotlinModule { + override def kotlinExplicitApi: T[Boolean] = false override def kotlinVersion: T[String] = Task { outer.kotlinVersion() } override def kotlinCompilerVersion: T[String] = Task { outer.kotlinCompilerVersion() } - override def kotlincOptions: T[Seq[String]] = Task { outer.kotlincOptions() } + override def kotlincOptions: T[Seq[String]] = Task { + outer.kotlincOptions().filterNot(_.startsWith("-Xcommon-sources")) ++ + Seq(s"-Xfriend-paths=${outer.compile().classes.path.toString()}") + } } } diff --git a/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala b/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala index 5bf273f2994..0d94959ca76 100644 --- a/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala +++ b/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala @@ -2,7 +2,7 @@ package mill.kotlinlib.js import mainargs.arg import mill.api.{PathRef, Result} -import mill.define.{Command, Segment, Task} +import mill.define.{Command, Task} import mill.kotlinlib.worker.api.{KotlinWorker, KotlinWorkerTarget} import mill.kotlinlib.{Dep, DepSyntax, KotlinModule} import mill.scalalib.Lib @@ -72,9 +72,9 @@ trait KotlinJsModule extends KotlinModule { outer => T.traverse(transitiveModuleCompileModuleDeps)(m => Task.Anon { val transitiveModuleArtifactPath = - (if (m.isInstanceOf[KotlinJsModule]) { - m.asInstanceOf[KotlinJsModule].createKlib(T.dest, m.compile().classes) - } else m.compile().classes) + if (m.isInstanceOf[KotlinJsModule] && m != friendModule.orNull) { + m.asInstanceOf[KotlinJsModule].klib() + } else m.compile().classes m.localCompileClasspath() ++ Agg(transitiveModuleArtifactPath) } )().flatten @@ -98,6 +98,8 @@ trait KotlinJsModule extends KotlinModule { outer => esTarget = kotlinJsESTarget(), kotlinVersion = kotlinVersion(), destinationRoot = T.dest, + artifactId = artifactId(), + explicitApi = kotlinExplicitApi(), extraKotlinArgs = kotlincOptions(), worker = kotlinWorkerTask() ) @@ -113,6 +115,7 @@ trait KotlinJsModule extends KotlinModule { outer => moduleKind = moduleKind(), binaryDir = linkBinary().classes.path, runTarget = kotlinJsRunTarget(), + artifactId = artifactId(), envArgs = T.env, workingDir = T.dest ).map(_ => ()).getOrThrow @@ -130,12 +133,15 @@ trait KotlinJsModule extends KotlinModule { outer => mill.api.Result.Failure("runMain is not supported in Kotlin/JS.") } + protected[js] def friendModule: Option[KotlinJsModule] = None + protected[js] def runJsBinary( args: Args = Args(), binaryKind: Option[BinaryKind], moduleKind: ModuleKind, binaryDir: os.Path, runTarget: Option[RunTarget], + artifactId: String, envArgs: Map[String, String] = Map.empty[String, String], workingDir: os.Path )(implicit ctx: mill.api.Ctx): Result[Int] = { @@ -153,7 +159,7 @@ trait KotlinJsModule extends KotlinModule { outer => runTarget match { case Some(RunTarget.Node) => - val binaryPath = (binaryDir / s"${moduleName()}.${moduleKind.extension}") + val binaryPath = (binaryDir / s"$artifactId.${moduleKind.extension}") .toIO.getAbsolutePath Jvm.runSubprocessWithResult( commandArgs = Seq( @@ -189,6 +195,8 @@ trait KotlinJsModule extends KotlinModule { outer => esTarget = kotlinJsESTarget(), kotlinVersion = kotlinVersion(), destinationRoot = T.dest, + artifactId = artifactId(), + explicitApi = kotlinExplicitApi(), extraKotlinArgs = kotlincOptions() ++ extraKotlinArgs, worker = kotlinWorkerTask() ) @@ -212,26 +220,35 @@ trait KotlinJsModule extends KotlinModule { outer => esTarget = kotlinJsESTarget(), kotlinVersion = kotlinVersion(), destinationRoot = T.dest, + artifactId = artifactId(), + explicitApi = kotlinExplicitApi(), extraKotlinArgs = kotlincOptions(), worker = kotlinWorkerTask() ) } - // endregion - - // region private - - private def createKlib(destFolder: os.Path, irPathRef: PathRef): PathRef = { - val outputPath = destFolder / s"${moduleName()}.klib" + /** + * A klib containing only this module's resources and compiled IR files, + * without those from upstream modules and dependencies + */ + def klib: T[PathRef] = Task { + val outputPath = T.dest / s"${artifactId()}.klib" Jvm.createJar( outputPath, - Agg(irPathRef.path), + Agg(compile().classes.path), mill.api.JarManifest.MillDefault, fileFilter = (_, _) => true ) PathRef(outputPath) } + // endregion + + // region private + + protected override def dokkaAnalysisPlatform = "js" + protected override def dokkaSourceSetDisplayName = "js" + private[kotlinlib] def kotlinJsCompile( outputMode: OutputMode, allKotlinSourceFiles: Seq[PathRef], @@ -246,6 +263,8 @@ trait KotlinJsModule extends KotlinModule { outer => esTarget: Option[String], kotlinVersion: String, destinationRoot: os.Path, + artifactId: String, + explicitApi: Boolean, extraKotlinArgs: Seq[String], worker: KotlinWorker )(implicit ctx: mill.api.Ctx): Result[CompilationResult] = { @@ -291,8 +310,7 @@ trait KotlinJsModule extends KotlinModule { outer => } ) } - // what is the better way to find a module simple name, without root path? - innerCompilerArgs ++= Seq("-ir-output-name", moduleName()) + innerCompilerArgs ++= Seq("-ir-output-name", s"$artifactId") if (produceSourceMaps) { innerCompilerArgs += "-source-map" innerCompilerArgs ++= Seq( @@ -315,8 +333,12 @@ trait KotlinJsModule extends KotlinModule { outer => innerCompilerArgs += "-Xir-only" if (splitPerModule) { innerCompilerArgs += s"-Xir-per-module" - innerCompilerArgs += s"-Xir-per-module-output-name=${fullModuleName()}" + // should be unique among all the modules loaded in the consumer classpath + innerCompilerArgs += s"-Xir-per-module-output-name=$artifactId" } + // apply multi-platform support (expect/actual) + // TODO if there is penalty for activating it in the compiler, put it behind configuration flag + innerCompilerArgs += "-Xmulti-platform" val outputArgs = outputMode match { case OutputMode.KlibFile => Seq( @@ -339,11 +361,15 @@ trait KotlinJsModule extends KotlinModule { outer => } innerCompilerArgs ++= outputArgs - innerCompilerArgs += s"-Xir-module-name=${moduleName()}" + // should be unique among all the modules loaded in the consumer classpath + innerCompilerArgs += s"-Xir-module-name=$artifactId" innerCompilerArgs ++= (esTarget match { case Some(x) => Seq("-target", x) case None => Seq.empty }) + if (explicitApi) { + innerCompilerArgs ++= Seq("-Xexplicit-api=strict") + } val compilerArgs: Seq[String] = Seq( innerCompilerArgs.result(), @@ -368,7 +394,7 @@ trait KotlinJsModule extends KotlinModule { outer => } val artifactLocation = outputMode match { - case OutputMode.KlibFile => compileDestination / s"${moduleName()}.klib" + case OutputMode.KlibFile => compileDestination / s"$artifactId.klib" case OutputMode.KlibDir => compileDestination case OutputMode.Js => compileDestination } @@ -392,19 +418,6 @@ trait KotlinJsModule extends KotlinModule { outer => case Some(BinaryKind.Executable) => OutputMode.Js } - // these 2 exist to ignore values added to the display name in case of the cross-modules - // we already have cross-modules in the paths, so we don't need them here - private def fullModuleNameSegments() = { - millModuleSegments.value - .collect { case label: Segment.Label => label.value } match { - case Nil => Seq("root") - case segments => segments - } - } - - protected[js] def moduleName(): String = fullModuleNameSegments().last - protected[js] def fullModuleName(): String = fullModuleNameSegments().mkString("-") - // **NOTE**: This logic may (and probably is) be incomplete private def isKotlinJsLibrary(path: os.Path)(implicit ctx: mill.api.Ctx): Boolean = { if (os.isDir(path)) { @@ -428,6 +441,15 @@ trait KotlinJsModule extends KotlinModule { outer => } } + override def artifactId: T[String] = Task { + val name = super.artifactId() + if (name.isEmpty) { + "root" + } else { + name + } + } + // endregion // region Tests module @@ -436,10 +458,15 @@ trait KotlinJsModule extends KotlinModule { outer => * Generic trait to run tests for Kotlin/JS which doesn't specify test * framework. For the particular implementation see [[KotlinTestPackageTests]] or [[KotestTests]]. */ - trait KotlinJsTests extends KotlinTests with KotlinJsModule { + trait KotlinJsTests extends KotlinJsModule with KotlinTests { private val defaultXmlReportName = "test-report.xml" + /** + * Test timeout in milliseconds. Default is 2000. + */ + def testTimeout: T[Long] = Task { 2000L } + // region private // TODO may be optimized if there is a single folder for all modules @@ -473,6 +500,17 @@ trait KotlinJsModule extends KotlinModule { outer => // endregion + override def kotlincOptions: T[Seq[String]] = Task { + super.kotlincOptions().map { item => + if (item.startsWith("-Xfriend-paths=")) { + // JVM -> JS option name + item.replace("-Xfriend-paths=", "-Xfriend-modules=") + } else { + item + } + } + } + override def testFramework = "" override def kotlinJsRunTarget: T[Option[RunTarget]] = outer.kotlinJsRunTarget() @@ -486,10 +524,18 @@ trait KotlinJsModule extends KotlinModule { outer => this.test(args: _*)() } + override protected[js] def friendModule: Option[KotlinJsModule] = Some(outer) + override protected def testTask( args: Task[Seq[String]], globSelectors: Task[Seq[String]] ): Task[(String, Seq[TestResult])] = Task.Anon { + val runTarget = kotlinJsRunTarget() + if (runTarget.isEmpty) { + throw new IllegalStateException( + "Cannot run Kotlin/JS tests, because run target is not specified." + ) + } runJsBinary( // TODO add runner to be able to use test selector args = Args(args() ++ Seq( @@ -498,6 +544,8 @@ trait KotlinJsModule extends KotlinModule { outer => "--require", sourceMapSupportModule().path.toString(), mochaModule().path.toString(), + "--timeout", + testTimeout().toString, "--reporter", "xunit", "--reporter-option", @@ -506,7 +554,8 @@ trait KotlinJsModule extends KotlinModule { outer => binaryKind = Some(BinaryKind.Executable), moduleKind = moduleKind(), binaryDir = linkBinary().classes.path, - runTarget = kotlinJsRunTarget(), + runTarget = runTarget, + artifactId = artifactId(), envArgs = T.env, workingDir = T.dest ) diff --git a/kotlinlib/test/src/mill/kotlinlib/js/KotlinJsLinkTests.scala b/kotlinlib/test/src/mill/kotlinlib/js/KotlinJsLinkTests.scala index 1eaf4b4356d..1464b502cc0 100644 --- a/kotlinlib/test/src/mill/kotlinlib/js/KotlinJsLinkTests.scala +++ b/kotlinlib/test/src/mill/kotlinlib/js/KotlinJsLinkTests.scala @@ -15,6 +15,8 @@ object KotlinJsLinkTests extends TestSuite { override def splitPerModule: T[Boolean] = crossValue override def kotlinJsBinaryKind: T[Option[BinaryKind]] = Some(BinaryKind.Executable) override def moduleDeps = Seq(module.bar) + // drop cross-value + override def artifactNameParts = super.artifactNameParts().dropRight(1) } object module extends TestBaseModule {