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 {