Skip to content

Commit b9154d6

Browse files
authored
First-class support for Kotlin compiler plugins (com-lihaoyi#4779)
Fix com-lihaoyi#4776 This introduces multiple new tasks, analog to the ones in `ScalaModule`. * `kotlincPluginIvyDeps` - Dependencies resembling compiler plugins * `kotlincPluginJars` - The coursier-resolved jars of the compiler plugins; resolved intransitively; also usable to add plugins not available via coursier/Maven * `mandatoryKotlincOptions` - compiler options derived from other module settings, like Kotlin versions and compiler plugin enablement * `allKotlincOptions` - all compiler options Revised the existing modules and used the new tasks to provide Kotlin compiler plugins and their configuration. Removed `composeProcessor` and `kotestProcessor` tasks. Also added some comments for stuff that need further work like hard-coded versions. Pull request: com-lihaoyi#4779
1 parent d2e1e98 commit b9154d6

File tree

11 files changed

+103
-93
lines changed

11 files changed

+103
-93
lines changed

example/kotlinlib/basic/1-simple/build.mill

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,14 @@ foo.run
7070
foo.compile(KotlinModule...)
7171
Compiles all the sources to JVM class files.
7272
Compiles the current module to generate compiled classfiles/bytecode.
73-
When you override this, you probably also want/need to override [[bspCompileClassesPath]],
74-
as that needs to point to the same compilation output path.
75-
Keep in sync with [[bspCompileClassesPath]]
7673
Inputs:
7774
foo.allJavaSourceFiles
7875
foo.allKotlinSourceFiles
7976
foo.compileClasspath
8077
foo.upstreamCompileOutput
8178
foo.javacOptions
8279
foo.zincReportCachedProblems
83-
foo.kotlincOptions
80+
foo.allKotlincOptions
8481
foo.kotlinCompilerClasspath
8582
...
8683

example/kotlinlib/basic/6-realistic/build.mill

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ object qux extends MyModule {
5555

5656
// A semi-realistic build setup, combining all the individual Mill concepts:
5757
//
58-
// - Three `KotlinModule`s that depend on each other
58+
// - Three ``KotlinModule``s that depend on each other
5959
//
6060
// - With unit testing and publishing set up
6161
//
@@ -65,9 +65,10 @@ object qux extends MyModule {
6565
// Note that for multi-module builds like this, using using xref:cli/query-syntax.adoc[queries] to run tasks on
6666
// multiple modules at once can be very convenient:
6767
//
68+
// [source,sh]
6869
// ----
69-
// __.test
70-
// __.publishLocal
70+
// > mill __.test
71+
// > mill __.publishLocal
7172
// ----
7273
//
7374
// Also note how you can use ``trait``s to bundle together common combinations of

kotlinlib/src/mill/kotlinlib/KotlinMavenModule.scala

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,13 @@ import mill.javalib.MavenModule
77
* A [[KotlinModule]] with a Maven compatible directory layout.
88
*/
99
trait KotlinMavenModule extends KotlinModule with MavenModule {
10-
override def sources = Task.Sources(
11-
"src/main/java",
12-
"src/main/kotlin"
13-
)
14-
override def resources = Task.Sources {
15-
"src/main/resources"
16-
}
10+
private def sources0 = Task.Sources("src/main/kotlin")
11+
override def sources = super.sources() ++ sources0()
1712

1813
trait KotlinMavenTests extends KotlinTests with MavenTests {
1914
override def intellijModulePath: os.Path = moduleDir / "src/test"
2015

21-
override def sources = Task.Sources(
22-
"src/test/java",
23-
"src/test/kotlin"
24-
)
25-
override def resources = Task.Sources {
26-
"src/test/resources"
27-
}
16+
private def sources0 = Task.Sources("src/test/kotlin")
17+
override def sources = super.sources() ++ sources0()
2818
}
2919
}

kotlinlib/src/mill/kotlinlib/KotlinModule.scala

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ trait KotlinModule extends JavaModule { outer =>
6161
/**
6262
* The version of the Kotlin compiler to be used.
6363
* Default is derived from [[kotlinVersion]].
64+
* This is deprecated, as it's identical to [[kotlinVersion]]
6465
*/
66+
@deprecated("Use kotlinVersion instead", "Mill 0.13.0-M1")
6567
def kotlinCompilerVersion: T[String] = Task { kotlinVersion() }
6668

6769
/**
@@ -153,6 +155,23 @@ trait KotlinModule extends JavaModule { outer =>
153155
)
154156
}
155157

158+
/**
159+
* Compiler Plugin dependencies.
160+
*/
161+
def kotlincPluginIvyDeps: T[Seq[Dep]] = Task { Seq.empty[Dep] }
162+
163+
/**
164+
* The resolved plugin jars
165+
*/
166+
def kotlincPluginJars: T[Seq[PathRef]] = Task {
167+
val jars = defaultResolver().resolveDeps(
168+
kotlincPluginIvyDeps()
169+
// Don't resolve transitive jars
170+
.map(d => d.exclude("*" -> "*"))
171+
)
172+
jars.toSeq
173+
}
174+
156175
def kotlinWorkerTask: Task[KotlinWorker] = Task.Anon {
157176
kotlinWorkerRef().kotlinWorkerManager().get(kotlinCompilerClasspath())
158177
}
@@ -321,7 +340,7 @@ trait KotlinModule extends JavaModule { outer =>
321340
when(kotlinExplicitApi())(
322341
"-Xexplicit-api=strict"
323342
),
324-
kotlincOptions(),
343+
allKotlincOptions(),
325344
extraKotlinArgs,
326345
// parameters
327346
(kotlinSourceFiles ++ javaSourceFiles).map(_.toString())
@@ -353,20 +372,29 @@ trait KotlinModule extends JavaModule { outer =>
353372
/**
354373
* Additional Kotlin compiler options to be used by [[compile]].
355374
*/
356-
def kotlincOptions: T[Seq[String]] = Task {
357-
val options = Seq.newBuilder[String]
358-
options += "-no-stdlib"
375+
def kotlincOptions: T[Seq[String]] = Task { Seq.empty[String] }
376+
377+
/**
378+
* Mandatory command-line options to pass to the Kotlin compiler
379+
* that shouldn't be removed by overriding `scalacOptions`
380+
*/
381+
protected def mandatoryKotlincOptions: T[Seq[String]] = Task {
359382
val languageVersion = kotlinLanguageVersion()
360-
if (!languageVersion.isBlank) {
361-
options += "-language-version"
362-
options += languageVersion
363-
}
364383
val kotlinkotlinApiVersion = kotlinApiVersion()
365-
if (!kotlinkotlinApiVersion.isBlank) {
366-
options += "-api-version"
367-
options += kotlinkotlinApiVersion
368-
}
369-
options.result()
384+
val plugins = kotlincPluginJars().map(_.path)
385+
386+
Seq("-no-stdlib") ++
387+
when(!languageVersion.isBlank)("-language-version", languageVersion) ++
388+
when(!kotlinkotlinApiVersion.isBlank)("-api-version", kotlinkotlinApiVersion) ++
389+
plugins.map(p => s"-Xplugin=$p")
390+
}
391+
392+
/**
393+
* Aggregation of all the options passed to the Kotlin compiler.
394+
* In most cases, instead of overriding this Target you want to override `kotlincOptions` instead.
395+
*/
396+
def allKotlincOptions: T[Seq[String]] = Task {
397+
mandatoryKotlincOptions() ++ kotlincOptions()
370398
}
371399

372400
private[kotlinlib] def internalCompileJavaFiles(
@@ -408,12 +436,15 @@ trait KotlinModule extends JavaModule { outer =>
408436
override def kotlinExplicitApi: T[Boolean] = false
409437
override def kotlinVersion: T[String] = Task { outer.kotlinVersion() }
410438
override def kotlinCompilerVersion: T[String] = Task { outer.kotlinCompilerVersion() }
439+
override def kotlincPluginIvyDeps: T[Seq[Dep]] =
440+
Task { outer.kotlincPluginIvyDeps() }
441+
// TODO: make Xfriend-path an explicit setting
411442
override def kotlincOptions: T[Seq[String]] = Task {
412443
outer.kotlincOptions().filterNot(_.startsWith("-Xcommon-sources")) ++
413444
Seq(s"-Xfriend-paths=${outer.compile().classes.path.toString()}")
414445
}
415-
416-
override def kotlinCompilerEmbeddable: Task[Boolean] = outer.kotlinCompilerEmbeddable
446+
override def kotlinCompilerEmbeddable: Task[Boolean] =
447+
Task.Anon { outer.kotlinCompilerEmbeddable() }
417448
}
418449

419450
}

kotlinlib/src/mill/kotlinlib/android/AndroidAppKotlinModule.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,26 +54,25 @@ trait AndroidAppKotlinModule extends AndroidAppModule with AndroidKotlinModule {
5454
/* There are no testclasses for screenshot tests, just the engine running a diff over the images */
5555
override def discoveredTestClasses: T[Seq[String]] = Task { Seq.empty[String] }
5656

57-
override def mapDependencies: Task[Dependency => Dependency] = outer.mapDependencies
57+
override def mapDependencies: Task[Dependency => Dependency] =
58+
Task.Anon(outer.mapDependencies())
5859

59-
override def androidCompileSdk: T[Int] = outer.androidCompileSdk
60+
override def androidCompileSdk: T[Int] = outer.androidCompileSdk()
6061

61-
override def androidMergedManifest: T[PathRef] = outer.androidMergedManifest
62+
override def androidMergedManifest: T[PathRef] = outer.androidMergedManifest()
6263

6364
override def androidSdkModule: ModuleRef[AndroidSdkModule] = outer.androidSdkModule
6465

66+
// FIXME: avoid hardcoded version
6567
def layoutLibVersion: String = "14.0.9"
68+
// FIXME: avoid hardcoded version
6669
def composePreviewRendererVersion: String = "0.0.1-alpha08"
6770

6871
def namespace: String
6972

7073
override def moduleDeps: Seq[JavaModule] = Seq(outer)
7174

72-
override final def kotlinVersion = outer.kotlinVersion
73-
74-
override def kotlincOptions = super.kotlincOptions() ++ Seq(
75-
s"-Xplugin=${composeProcessor().path}"
76-
)
75+
override final def kotlinVersion = outer.kotlinVersion()
7776

7877
override def sources: T[Seq[PathRef]] = Task.Sources(
7978
outer.moduleDir / "src/screenshotTest/kotlin",
@@ -125,6 +124,7 @@ trait AndroidAppKotlinModule extends AndroidAppModule with AndroidKotlinModule {
125124
PathRef(extractDestination)
126125
}
127126

127+
// FIXME: avoid hardcoded version
128128
def uiToolingVersion: String = "1.7.6"
129129

130130
/*
Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
package mill.kotlinlib.android
22

33
import mill.{T, Task}
4-
import mill.api.PathRef
5-
import mill.kotlinlib.{DepSyntax, KotlinModule}
4+
import mill.api.Result
5+
import mill.kotlinlib.{Dep, DepSyntax, KotlinModule}
66

7+
// TODO expose Compose configuration options
8+
// https://kotlinlang.org/docs/compose-compiler-options.html possible options
79
trait AndroidKotlinModule extends KotlinModule {
810

9-
override def kotlincOptions = super.kotlincOptions() ++ {
10-
if (androidEnableCompose()) {
11-
Seq(
12-
// TODO expose Compose configuration options
13-
// https://kotlinlang.org/docs/compose-compiler-options.html possible options
14-
s"-Xplugin=${composeProcessor().path}"
15-
)
16-
} else Seq.empty
17-
}
18-
1911
/**
2012
* Enable Jetpack Compose support in the module. Default is `false`.
2113
*/
2214
def androidEnableCompose: T[Boolean] = false
2315

24-
def composeProcessor = Task {
25-
// cut-off usages for Kotlin 1.x, because of the need to maintain the table of
26-
// Compose compiler version -> Kotlin version
27-
if (kotlinVersion().startsWith("1"))
28-
throw new IllegalStateException("Compose can be used only with Kotlin version 2 or newer.")
29-
defaultResolver().resolveDeps(
30-
Seq(
31-
ivy"org.jetbrains.kotlin:kotlin-compose-compiler-plugin:${kotlinVersion()}"
32-
)
33-
).head
16+
override def kotlincPluginIvyDeps: T[Seq[Dep]] = Task {
17+
val kv = kotlinVersion()
18+
19+
val deps = super.kotlincPluginIvyDeps()
20+
21+
if (androidEnableCompose()) {
22+
if (kv.startsWith("1")) {
23+
// cut-off usages for Kotlin 1.x, because of the need to maintain the table of
24+
// Compose compiler version -> Kotlin version
25+
Result.Failure("Compose can be used only with Kotlin version 2 or newer.")
26+
} else {
27+
Result.Success(deps ++ Seq(
28+
ivy"org.jetbrains.kotlin:kotlin-compose-compiler-plugin:${kotlinVersion()}"
29+
))
30+
}
31+
} else Result.Success(deps)
3432
}
3533
}

kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ trait KotlinJsModule extends KotlinModule { outer =>
2525
// region Kotlin/JS configuration
2626

2727
/** The kind of JS module generated by the compiler */
28+
// FIXME: Rename to kotlinKsModuleKind
2829
def moduleKind: T[ModuleKind] = ModuleKind.PlainModule
2930

3031
/** Call main function upon execution. */
32+
// FIXME: Rename to kotlinJsCallMain
3133
def callMain: T[Boolean] = true
3234

3335
/** Binary type (if any) to produce. If [[BinaryKind.Executable]] is selected, then .js file(s) will be produced. */
@@ -49,6 +51,7 @@ trait KotlinJsModule extends KotlinModule { outer =>
4951
def kotlinJsSourceMapNamesPolicy: T[SourceMapNamesPolicy] = SourceMapNamesPolicy.No
5052

5153
/** Split generated .js per-module. Effective only if [[BinaryKind.Executable]] is selected. */
54+
// FIXME: rename to kotlinJsSplitPerModule
5255
def splitPerModule: T[Boolean] = true
5356

5457
/** Run target for the executable (if [[BinaryKind.Executable]] is set). */
@@ -100,7 +103,7 @@ trait KotlinJsModule extends KotlinModule { outer =>
100103
destinationRoot = Task.dest,
101104
artifactId = artifactId(),
102105
explicitApi = kotlinExplicitApi(),
103-
extraKotlinArgs = kotlincOptions(),
106+
extraKotlinArgs = allKotlincOptions(),
104107
worker = kotlinWorkerTask()
105108
)
106109
}
@@ -200,7 +203,7 @@ trait KotlinJsModule extends KotlinModule { outer =>
200203
destinationRoot = Task.dest,
201204
artifactId = artifactId(),
202205
explicitApi = kotlinExplicitApi(),
203-
extraKotlinArgs = kotlincOptions() ++ extraKotlinArgs,
206+
extraKotlinArgs = allKotlincOptions() ++ extraKotlinArgs,
204207
worker = kotlinWorkerTask()
205208
)
206209
}
@@ -227,7 +230,7 @@ trait KotlinJsModule extends KotlinModule { outer =>
227230
destinationRoot = Task.dest,
228231
artifactId = artifactId(),
229232
explicitApi = kotlinExplicitApi(),
230-
extraKotlinArgs = kotlincOptions(),
233+
extraKotlinArgs = allKotlincOptions(),
231234
worker = kotlinWorkerTask()
232235
)
233236
}
@@ -670,7 +673,7 @@ trait KotlinJsModule extends KotlinModule { outer =>
670673
* Run tests for Kotlin/JS target using `kotlin.test` package.
671674
*/
672675
trait KotlinTestPackageTests extends KotlinJsTests {
673-
override def ivyDeps = Seq(
676+
override def ivyDeps = super.ivyDeps() ++ Seq(
674677
ivy"org.jetbrains.kotlin:kotlin-test-js:${kotlinVersion()}"
675678
)
676679
}
@@ -680,21 +683,15 @@ trait KotlinJsModule extends KotlinModule { outer =>
680683
*/
681684
trait KotestTests extends KotlinJsTests {
682685

686+
// FIXME: get this version from Mill build info
683687
def kotestVersion: T[String] = "5.9.1"
684688

685-
private def kotestProcessor = Task {
686-
defaultResolver().resolveDeps(
687-
Seq(
688-
ivy"io.kotest:kotest-framework-multiplatform-plugin-embeddable-compiler-jvm:${kotestVersion()}"
689-
)
690-
).head
691-
}
692-
693-
override def kotlincOptions = super.kotlincOptions() ++ Seq(
694-
s"-Xplugin=${kotestProcessor().path}"
695-
)
689+
override def kotlincPluginIvyDeps: T[Seq[Dep]] =
690+
super.kotlincPluginIvyDeps() ++ Seq(
691+
ivy"io.kotest:kotest-framework-multiplatform-plugin-embeddable-compiler-jvm:${kotestVersion()}"
692+
)
696693

697-
override def ivyDeps = Seq(
694+
override def ivyDeps = super.ivyDeps() ++ Seq(
698695
ivy"io.kotest:kotest-framework-engine-js:${kotestVersion()}",
699696
ivy"io.kotest:kotest-assertions-core-js:${kotestVersion()}"
700697
)

kotlinlib/src/mill/kotlinlib/ksp/KspModule.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ trait KspModule extends KotlinModule { outer =>
130130
"-classpath",
131131
compileCp.iterator.mkString(File.pathSeparator)
132132
),
133-
kotlincOptions(),
133+
allKotlincOptions(),
134134
kspCompilerArgs,
135135
// parameters
136136
sourceFiles.map(_.toString())

kotlinlib/test/src/mill/kotlinlib/js/KotlinJsKotestModuleTests.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,17 @@ import utest.{TestSuite, Tests, assert, test}
99

1010
object KotlinJsKotestModuleTests extends TestSuite {
1111

12-
private val resourcePath = os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "kotlin-js"
13-
14-
private val kotlinVersion = "1.9.25"
12+
private val testResourcePath = os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "kotlin-js"
13+
private val testKotlinVersion = "1.9.25"
1514

1615
object module extends TestBaseModule {
1716

1817
object bar extends KotlinJsModule {
19-
def kotlinVersion = KotlinJsKotestModuleTests.kotlinVersion
18+
def kotlinVersion = testKotlinVersion
2019
}
2120

2221
object foo extends KotlinJsModule {
23-
def kotlinVersion = KotlinJsKotestModuleTests.kotlinVersion
22+
def kotlinVersion = testKotlinVersion
2423
override def kotlinJsRunTarget = Some(RunTarget.Node)
2524
override def moduleDeps = Seq(module.bar)
2625

@@ -33,7 +32,7 @@ object KotlinJsKotestModuleTests extends TestSuite {
3332
lazy val millDiscover = Discover[this.type]
3433
}
3534

36-
private def testEval() = UnitTester(module, resourcePath)
35+
private def testEval() = UnitTester(module, testResourcePath)
3736

3837
def tests: Tests = Tests {
3938

0 commit comments

Comments
 (0)