From 31ff1fcd16ce4d406039c5364a064db652a9cf91 Mon Sep 17 00:00:00 2001 From: 0xnm <0xnm@users.noreply.github.com> Date: Fri, 20 Sep 2024 23:17:44 +0200 Subject: [PATCH 1/5] Add Dokka support to KotlinModule --- .../src/mill/kotlinlib/KotlinModule.scala | 80 ++++++++++++++++++- main/util/src/mill/util/Jvm.scala | 13 ++- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/kotlinlib/src/mill/kotlinlib/KotlinModule.scala b/kotlinlib/src/mill/kotlinlib/KotlinModule.scala index bbd62076d1f..f901a4532e2 100644 --- a/kotlinlib/src/mill/kotlinlib/KotlinModule.scala +++ b/kotlinlib/src/mill/kotlinlib/KotlinModule.scala @@ -5,11 +5,12 @@ package mill package kotlinlib -import mill.api.{PathRef, Result} +import mill.api.{Loose, PathRef, Result} import mill.define.{Command, ModuleRef, Task} import mill.kotlinlib.worker.api.KotlinWorker import mill.scalalib.api.{CompilationResult, ZincWorkerApi} import mill.scalalib.{JavaModule, Lib, ZincWorkerModule} +import mill.util.Jvm import mill.util.Util.millProjectModule import mill.{Agg, T} @@ -134,6 +135,83 @@ trait KotlinModule extends JavaModule { outer => () } + override def docJar: T[PathRef] = T[PathRef] { + T.log.info("docJar task shouldn't be used for Kotlin modules, using dokkaJar instead") + dokkaJar() + } + + /** + * The documentation jar, containing all the Dokka files, for + * publishing to Maven Central + */ + def dokkaJar: T[PathRef] = T[PathRef] { + val outDir = T.dest + + val dokkaDir = outDir / "dokka" + os.makeDir.all(dokkaDir) + + val files = Lib.findSourceFiles(docSources(), Seq("java", "kt")) + + if (files.nonEmpty) { + val pluginClasspathOption = Seq( + "-pluginsClasspath", + dokkaPluginsClasspath().map(_.path).mkString(";") + ) + + val options = dokkaOptions() ++ + Seq("-outputDir", dokkaDir.toString()) ++ + pluginClasspathOption ++ + Seq("-sourceSet", s"-src $millSourcePath") + + T.log.info("dokka options: " + options) + + Jvm.runSubprocess( + mainClass = "", + classPath = Agg.empty, + jvmArgs = Seq("-jar", dokkaCliClasspath().head.path.toString()), + mainArgs = options + ) + } + + Jvm.createJar(Agg(dokkaDir))(outDir) + } + + /** + * Additional options to be used by the Dokka tool. + * You should not set the `-outputDir` setting for specifying the target directory, + * as that is done in the [[dokkaJar]] target. + */ + def dokkaOptions: T[Seq[String]] = Task { Seq[String]() } + + /** + * Dokka version. + */ + def dokkaVersion: T[String] = T { + "1.9.20" + } + + /** + * Classpath for running Dokka. + */ + private def dokkaCliClasspath: T[Loose.Agg[PathRef]] = T { + defaultResolver().resolveDeps( + Agg( + ivy"org.jetbrains.dokka:dokka-cli:${dokkaVersion()}" + ) + ) + } + + private def dokkaPluginsClasspath: T[Loose.Agg[PathRef]] = T { + defaultResolver().resolveDeps( + Agg( + ivy"org.jetbrains.dokka:dokka-base:${dokkaVersion()}", + ivy"org.jetbrains.dokka:analysis-kotlin-descriptors:${dokkaVersion()}", + ivy"org.jetbrains.kotlinx:kotlinx-html-jvm:0.8.0", + ivy"org.freemarker:freemarker:2.3.31" + ) + ) + } + protected def when(cond: Boolean)(args: String*): Seq[String] = if (cond) args else Seq() /** diff --git a/main/util/src/mill/util/Jvm.scala b/main/util/src/mill/util/Jvm.scala index a7daf569737..aaff3514375 100644 --- a/main/util/src/mill/util/Jvm.scala +++ b/main/util/src/mill/util/Jvm.scala @@ -87,7 +87,7 @@ object Jvm extends CoursierSupport { * it's stdout and stderr to the console. * @param mainClass The main class to run * @param classPath The classpath - * @param JvmArgs Arguments given to the forked JVM + * @param jvmArgs Arguments given to the forked JVM * @param envArgs Environment variables used when starting the forked JVM * @param workingDir The working directory to be used by the forked JVM * @param background `true` if the forked JVM should be spawned in background @@ -150,7 +150,7 @@ object Jvm extends CoursierSupport { * it's stdout and stderr to the console. * @param mainClass The main class to run * @param classPath The classpath - * @param JvmArgs Arguments given to the forked JVM + * @param jvmArgs Arguments given to the forked JVM * @param envArgs Environment variables used when starting the forked JVM * @param workingDir The working directory to be used by the forked JVM * @param backgroundOutputs If the subprocess should run in the background, a Tuple of ProcessOutputs containing out and err respectively. Specify None for nonbackground processes. @@ -186,10 +186,17 @@ object Jvm extends CoursierSupport { classPath } + val cpArgument = if (cp.nonEmpty) { + Vector("-cp", cp.iterator.mkString(java.io.File.pathSeparator)) + } else Seq.empty + val mainClassArgument = if (mainClass.nonEmpty) { + Seq(mainClass) + } else Seq.empty val args = Vector(javaExe) ++ jvmArgs ++ - Vector("-cp", cp.iterator.mkString(java.io.File.pathSeparator), mainClass) ++ + cpArgument ++ + mainClassArgument ++ mainArgs ctx.log.debug(s"Run subprocess with args: ${args.map(a => s"'${a}'").mkString(" ")}") From 09ab2ee3c35bbdbf6ef04b27bb3c1acb55480fde Mon Sep 17 00:00:00 2001 From: 0xnm <0xnm@users.noreply.github.com> Date: Fri, 20 Sep 2024 23:18:15 +0200 Subject: [PATCH 2/5] Add Kotlin example/module examples --- .../1-compilation-execution-flags/build.mill | 17 ++++ .../src/foo/Foo.kt | 7 ++ .../10-downloading-non-maven-jars/build.mill | 20 +++++ .../src/foo/Foo.kt | 28 +++++++ .../textfile.txt | 3 + .../bar/resources/application.conf | 1 + .../module/11-assembly-config/build.mill | 27 ++++++ .../foo/resources/application.conf | 1 + .../11-assembly-config/foo/src/foo/Foo.kt | 15 ++++ .../module/12-repository-config/build.mill | 40 +++++++++ .../12-repository-config/foo/src/foo/Foo.kt | 18 ++++ example/kotlinlib/module/13-jni/build.mill | 78 ++++++++++++++++++ .../module/13-jni/native-src/HelloWorld.c | 7 ++ .../module/13-jni/src/foo/HelloWorld.kt | 19 +++++ .../13-jni/test/src/foo/HelloWorldTest.kt | 10 +++ .../kotlinlib/module/2-ivy-deps/build.mill | 24 ++++++ .../module/2-ivy-deps/src/foo/Foo.kt | 9 ++ .../3-run-compile-deps/bar/src/bar/Bar.kt | 28 +++++++ .../module/3-run-compile-deps/build.mill | 31 +++++++ .../3-run-compile-deps/foo/src/foo/Foo.kt | 3 + .../kotlinlib/module/5-resources/build.mill | 25 ++++++ .../module/5-resources/foo/resources/file.txt | 1 + .../module/5-resources/foo/src/Foo.kt | 15 ++++ .../foo/test/other-files/other-file.txt | 1 + .../foo/test/resources/test-file-a.txt | 1 + .../foo/test/resources/test-file-b.txt | 1 + .../5-resources/foo/test/src/FooTests.kt | 49 +++++++++++ .../module/6-annotation-processors/build.mill | 44 ++++++++++ .../foo/src/foo/Project.kt | 6 ++ .../foo/test/src/foo/ProjectTest.kt | 18 ++++ .../kotlinlib/module/7-dokkajar/build.mill | 22 +++++ .../module/7-dokkajar/foo/src/foo/Bar.java | 10 +++ .../module/7-dokkajar/foo/src/foo/Foo.kt | 8 ++ .../module/8-unmanaged-jars/build.mill | 15 ++++ .../8-unmanaged-jars/lib/nanojson-1.8.jar | Bin 0 -> 31494 bytes .../module/8-unmanaged-jars/src/foo/Foo.kt | 13 +++ .../kotlinlib/module/9-main-class/build.mill | 10 +++ .../module/9-main-class/src/foo/Bar.kt | 6 ++ .../module/9-main-class/src/foo/Foo.kt | 6 ++ .../module/9-main-class/src/foo/Qux.kt | 3 + example/package.mill | 2 + 41 files changed, 642 insertions(+) create mode 100644 example/kotlinlib/module/1-compilation-execution-flags/build.mill create mode 100644 example/kotlinlib/module/1-compilation-execution-flags/src/foo/Foo.kt create mode 100644 example/kotlinlib/module/10-downloading-non-maven-jars/build.mill create mode 100644 example/kotlinlib/module/10-downloading-non-maven-jars/src/foo/Foo.kt create mode 100644 example/kotlinlib/module/10-downloading-non-maven-jars/textfile.txt create mode 100644 example/kotlinlib/module/11-assembly-config/bar/resources/application.conf create mode 100644 example/kotlinlib/module/11-assembly-config/build.mill create mode 100644 example/kotlinlib/module/11-assembly-config/foo/resources/application.conf create mode 100644 example/kotlinlib/module/11-assembly-config/foo/src/foo/Foo.kt create mode 100644 example/kotlinlib/module/12-repository-config/build.mill create mode 100644 example/kotlinlib/module/12-repository-config/foo/src/foo/Foo.kt create mode 100644 example/kotlinlib/module/13-jni/build.mill create mode 100644 example/kotlinlib/module/13-jni/native-src/HelloWorld.c create mode 100644 example/kotlinlib/module/13-jni/src/foo/HelloWorld.kt create mode 100644 example/kotlinlib/module/13-jni/test/src/foo/HelloWorldTest.kt create mode 100644 example/kotlinlib/module/2-ivy-deps/build.mill create mode 100644 example/kotlinlib/module/2-ivy-deps/src/foo/Foo.kt create mode 100644 example/kotlinlib/module/3-run-compile-deps/bar/src/bar/Bar.kt create mode 100644 example/kotlinlib/module/3-run-compile-deps/build.mill create mode 100644 example/kotlinlib/module/3-run-compile-deps/foo/src/foo/Foo.kt create mode 100644 example/kotlinlib/module/5-resources/build.mill create mode 100644 example/kotlinlib/module/5-resources/foo/resources/file.txt create mode 100644 example/kotlinlib/module/5-resources/foo/src/Foo.kt create mode 100644 example/kotlinlib/module/5-resources/foo/test/other-files/other-file.txt create mode 100644 example/kotlinlib/module/5-resources/foo/test/resources/test-file-a.txt create mode 100644 example/kotlinlib/module/5-resources/foo/test/resources/test-file-b.txt create mode 100644 example/kotlinlib/module/5-resources/foo/test/src/FooTests.kt create mode 100644 example/kotlinlib/module/6-annotation-processors/build.mill create mode 100644 example/kotlinlib/module/6-annotation-processors/foo/src/foo/Project.kt create mode 100644 example/kotlinlib/module/6-annotation-processors/foo/test/src/foo/ProjectTest.kt create mode 100644 example/kotlinlib/module/7-dokkajar/build.mill create mode 100644 example/kotlinlib/module/7-dokkajar/foo/src/foo/Bar.java create mode 100644 example/kotlinlib/module/7-dokkajar/foo/src/foo/Foo.kt create mode 100644 example/kotlinlib/module/8-unmanaged-jars/build.mill create mode 100644 example/kotlinlib/module/8-unmanaged-jars/lib/nanojson-1.8.jar create mode 100644 example/kotlinlib/module/8-unmanaged-jars/src/foo/Foo.kt create mode 100644 example/kotlinlib/module/9-main-class/build.mill create mode 100644 example/kotlinlib/module/9-main-class/src/foo/Bar.kt create mode 100644 example/kotlinlib/module/9-main-class/src/foo/Foo.kt create mode 100644 example/kotlinlib/module/9-main-class/src/foo/Qux.kt diff --git a/example/kotlinlib/module/1-compilation-execution-flags/build.mill b/example/kotlinlib/module/1-compilation-execution-flags/build.mill new file mode 100644 index 00000000000..3ee06d9caab --- /dev/null +++ b/example/kotlinlib/module/1-compilation-execution-flags/build.mill @@ -0,0 +1,17 @@ +//// SNIPPET:BUILD +package build +import mill._, kotlinlib._ + +object `package` extends RootModule with KotlinModule { + + def kotlinVersion = "1.9.24" + + def mainClass = Some("foo.FooKt") + + def forkArgs = Seq("-Xmx4g", "-Dmy.jvm.property=hello") + def forkEnv = Map("MY_ENV_VAR" -> "WORLD") + + def kotlincOptions = super.kotlincOptions() ++ Seq("-Werror") +} + +//// SNIPPET:END diff --git a/example/kotlinlib/module/1-compilation-execution-flags/src/foo/Foo.kt b/example/kotlinlib/module/1-compilation-execution-flags/src/foo/Foo.kt new file mode 100644 index 00000000000..ce45987698a --- /dev/null +++ b/example/kotlinlib/module/1-compilation-execution-flags/src/foo/Foo.kt @@ -0,0 +1,7 @@ +package foo + +fun main() { + val jvmProperty = System.getProperty("my.jvm.property") + val envVar = System.getenv("MY_ENV_VAR") + println("$jvmProperty $envVar") +} diff --git a/example/kotlinlib/module/10-downloading-non-maven-jars/build.mill b/example/kotlinlib/module/10-downloading-non-maven-jars/build.mill new file mode 100644 index 00000000000..cccd085d9db --- /dev/null +++ b/example/kotlinlib/module/10-downloading-non-maven-jars/build.mill @@ -0,0 +1,20 @@ +//// SNIPPET:BUILD +package build +import mill._, kotlinlib._ + +object `package` extends RootModule with KotlinModule { + + def kotlinVersion = "1.9.24" + + def mainClass = Some("foo.FooKt") + + def unmanagedClasspath = T { + os.write( + T.dest / "fastjavaio.jar", + requests.get.stream( + "https://github.com/williamfiset/FastJavaIO/releases/download/1.1/fastjavaio.jar" + ) + ) + Agg(PathRef(T.dest / "fastjavaio.jar")) + } +} diff --git a/example/kotlinlib/module/10-downloading-non-maven-jars/src/foo/Foo.kt b/example/kotlinlib/module/10-downloading-non-maven-jars/src/foo/Foo.kt new file mode 100644 index 00000000000..9a484f87828 --- /dev/null +++ b/example/kotlinlib/module/10-downloading-non-maven-jars/src/foo/Foo.kt @@ -0,0 +1,28 @@ +package foo + +import com.williamfiset.fastjavaio.InputReader + +import java.io.FileInputStream +import java.io.IOException + +fun main(args: Array) { + val filePath = args[0] + val fi = try { + InputReader(FileInputStream(filePath)) + } catch (e: IOException) { + e.printStackTrace() + null + } + + var line: String? = fi?.nextLine() + while (line != null) { + println(line) + line = fi?.nextLine() + } + + try { + fi?.close() + } catch (e: IOException) { + e.printStackTrace() + } +} diff --git a/example/kotlinlib/module/10-downloading-non-maven-jars/textfile.txt b/example/kotlinlib/module/10-downloading-non-maven-jars/textfile.txt new file mode 100644 index 00000000000..cfe70740b42 --- /dev/null +++ b/example/kotlinlib/module/10-downloading-non-maven-jars/textfile.txt @@ -0,0 +1,3 @@ +I am cow +hear me moo +I weigh twice as much as you \ No newline at end of file diff --git a/example/kotlinlib/module/11-assembly-config/bar/resources/application.conf b/example/kotlinlib/module/11-assembly-config/bar/resources/application.conf new file mode 100644 index 00000000000..9341faacf2a --- /dev/null +++ b/example/kotlinlib/module/11-assembly-config/bar/resources/application.conf @@ -0,0 +1 @@ +Bar Application Conf diff --git a/example/kotlinlib/module/11-assembly-config/build.mill b/example/kotlinlib/module/11-assembly-config/build.mill new file mode 100644 index 00000000000..63c48026a76 --- /dev/null +++ b/example/kotlinlib/module/11-assembly-config/build.mill @@ -0,0 +1,27 @@ +//// SNIPPET:BUILD +package build +import mill._, kotlinlib._ +import mill.javalib.Assembly._ + +object foo extends KotlinModule { + + def kotlinVersion = "1.9.24" + + def mainClass = Some("foo.FooKt") + + def moduleDeps = Seq(bar) + def assemblyRules = Seq( + // all application.conf files will be concatenated into single file + Rule.Append("application.conf"), + // all *.conf files will be concatenated into single file + Rule.AppendPattern(".*\\.conf"), + // all *.temp files will be excluded from a final jar + Rule.ExcludePattern(".*\\.temp"), + // the `shapeless` package will be shaded under the `shade` package + Rule.Relocate("shapeless.**", "shade.shapless.@1") + ) +} + +object bar extends KotlinModule { + def kotlinVersion = "1.9.24" +} diff --git a/example/kotlinlib/module/11-assembly-config/foo/resources/application.conf b/example/kotlinlib/module/11-assembly-config/foo/resources/application.conf new file mode 100644 index 00000000000..482e5cf3347 --- /dev/null +++ b/example/kotlinlib/module/11-assembly-config/foo/resources/application.conf @@ -0,0 +1 @@ +Foo Application Conf diff --git a/example/kotlinlib/module/11-assembly-config/foo/src/foo/Foo.kt b/example/kotlinlib/module/11-assembly-config/foo/src/foo/Foo.kt new file mode 100644 index 00000000000..62475181e7b --- /dev/null +++ b/example/kotlinlib/module/11-assembly-config/foo/src/foo/Foo.kt @@ -0,0 +1,15 @@ +package foo + +import java.io.IOException +import java.io.InputStream + +fun main(args: Array) { + ::main.javaClass + .classLoader + .getResourceAsStream("application.conf") + .use { + val conf = it.readAllBytes().toString(Charsets.UTF_8) + println("Loaded application.conf from resources: $conf") + } + +} diff --git a/example/kotlinlib/module/12-repository-config/build.mill b/example/kotlinlib/module/12-repository-config/build.mill new file mode 100644 index 00000000000..2ba868ee130 --- /dev/null +++ b/example/kotlinlib/module/12-repository-config/build.mill @@ -0,0 +1,40 @@ +//// SNIPPET:BUILD1 +package build +import mill._, kotlinlib._ +import mill.javalib.{ZincWorkerModule, CoursierModule} +import mill.define.ModuleRef +import coursier.maven.MavenRepository + +val sonatypeReleases = Seq( + MavenRepository("https://oss.sonatype.org/content/repositories/releases") +) + +object foo extends KotlinModule { + + def mainClass = Some("foo.FooKt") + + def kotlinVersion = "1.9.24" + + def ivyDeps = Agg( + ivy"com.github.ajalt.clikt:clikt-jvm:4.4.0", + ivy"org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0" + ) + + def repositoriesTask = T.task { super.repositoriesTask() ++ sonatypeReleases } +} + +//// SNIPPET:BUILD2 + +object CustomZincWorkerModule extends ZincWorkerModule with CoursierModule { + def repositoriesTask = T.task { super.repositoriesTask() ++ sonatypeReleases } +} + +object bar extends KotlinModule { + + def kotlinVersion = "1.9.24" + + def zincWorker = ModuleRef(CustomZincWorkerModule) + // ... rest of your build definitions + + def repositoriesTask = T.task { super.repositoriesTask() ++ sonatypeReleases } +} diff --git a/example/kotlinlib/module/12-repository-config/foo/src/foo/Foo.kt b/example/kotlinlib/module/12-repository-config/foo/src/foo/Foo.kt new file mode 100644 index 00000000000..36dce8285ac --- /dev/null +++ b/example/kotlinlib/module/12-repository-config/foo/src/foo/Foo.kt @@ -0,0 +1,18 @@ +package foo + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import foo.main as fooMain +import kotlinx.html.h1 +import kotlinx.html.stream.createHTML + +class Foo : CliktCommand() { + val text by option("--text").required() + + override fun run() { + println(createHTML(prettyPrint = false).h1 { text(text) }.toString()) + } +} + +fun main(args: Array) = Foo().main(args) diff --git a/example/kotlinlib/module/13-jni/build.mill b/example/kotlinlib/module/13-jni/build.mill new file mode 100644 index 00000000000..d402e8cf22d --- /dev/null +++ b/example/kotlinlib/module/13-jni/build.mill @@ -0,0 +1,78 @@ +package build +import mill._, kotlinlib._, util.Jvm + +object `package` extends RootModule with KotlinModule { + + def mainClass = Some("foo.HelloWorldKt") + + def kotlinVersion = "1.9.24" + + // Additional source folder to put C sources + def nativeSources = T.sources(millSourcePath / "native-src") + + // Compile C + def nativeCompiled = T{ + val cSourceFiles = nativeSources().map(_.path).flatMap(os.walk(_)).filter(_.ext == "c") + val output = "libhelloworld.so" + os.proc( + "clang", "-shared", "-fPIC", + "-I" + sys.props("java.home") + "/include/", // global JVM header files + "-I" + sys.props("java.home") + "/include/darwin", + "-I" + sys.props("java.home") + "/include/linux", + "-o", T.dest / output, + cSourceFiles + ) + .call(stdout = os.Inherit) + + PathRef(T.dest / output) + } + + def forkEnv = Map("HELLO_WORLD_BINARY" -> nativeCompiled().path.toString) + + object test extends KotlinModuleTests with TestModule.Junit5 { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1" + ) + def forkEnv = Map("HELLO_WORLD_BINARY" -> nativeCompiled().path.toString) + } +} + +// This is an example of how use Mill to compile C code together with your Kotlin +// code using JNI. There are three two steps: defining the C source folder, +// and then compiling the C code using `clang`. After that we have the +// `libhelloworld.so` on disk ready to use, and in this example we use an +// environment variable to pass the path of that file to the application +// code to load it using `System.load`. +// +// The above builds expect the following project layout: +// +// ---- +// build.mill +// src/ +// foo/ +// HelloWorld.kt +// +// native-src/ +// HelloWorld.c +// +// test/ +// src/ +// foo/ +// HelloWorldTest.kt +// ---- +// +// This example is pretty minimal, but it demonstrates the core principles, and +// can be extended if necessary to more elaborate use cases. The `native*` tasks +// can also be extracted out into a `trait` for re-use if you have multiple +// `KotlinModule`s that need native C components. + +/** Usage + +> ./mill run +Hello, World! + +> ./mill test +Test foo.HelloWorldTestsimple started +Test foo.HelloWorldTestsimple finished... +... +*/ diff --git a/example/kotlinlib/module/13-jni/native-src/HelloWorld.c b/example/kotlinlib/module/13-jni/native-src/HelloWorld.c new file mode 100644 index 00000000000..40e603bc60e --- /dev/null +++ b/example/kotlinlib/module/13-jni/native-src/HelloWorld.c @@ -0,0 +1,7 @@ +#include +#include + +// Implementation of the native method +JNIEXPORT jstring JNICALL Java_foo_HelloWorld_sayHello(JNIEnv *env, jobject obj) { + return (*env)->NewStringUTF(env, "Hello, World!"); +} diff --git a/example/kotlinlib/module/13-jni/src/foo/HelloWorld.kt b/example/kotlinlib/module/13-jni/src/foo/HelloWorld.kt new file mode 100644 index 00000000000..0e5d5fc752f --- /dev/null +++ b/example/kotlinlib/module/13-jni/src/foo/HelloWorld.kt @@ -0,0 +1,19 @@ +package foo + +class HelloWorld { + // Declare a native method + external fun sayHello(): String + + // Load the native library + companion object { + init { + System.load(System.getenv("HELLO_WORLD_BINARY")) + } + } +} + + +fun main(args: Array) { + val helloWorld = HelloWorld() + println(helloWorld.sayHello()) +} diff --git a/example/kotlinlib/module/13-jni/test/src/foo/HelloWorldTest.kt b/example/kotlinlib/module/13-jni/test/src/foo/HelloWorldTest.kt new file mode 100644 index 00000000000..43e46691774 --- /dev/null +++ b/example/kotlinlib/module/13-jni/test/src/foo/HelloWorldTest.kt @@ -0,0 +1,10 @@ +package foo + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe + +class HelloWorldTest : FunSpec({ + test("simple") { + HelloWorld().sayHello() shouldBe "Hello, World!" + } +}) diff --git a/example/kotlinlib/module/2-ivy-deps/build.mill b/example/kotlinlib/module/2-ivy-deps/build.mill new file mode 100644 index 00000000000..12adaa68bd3 --- /dev/null +++ b/example/kotlinlib/module/2-ivy-deps/build.mill @@ -0,0 +1,24 @@ +//// SNIPPET:BUILD +package build +import mill._, kotlinlib._ + +object `package` extends RootModule with KotlinModule { + + def kotlinVersion = "1.9.24" + + def mainClass = Some("foo.FooKt") + + def ivyDeps = Agg( + ivy"com.fasterxml.jackson.core:jackson-databind:2.13.4", + ) +} + +//// SNIPPET:SCALAIVY + +//// SNIPPET:USAGE +/** Usage + +> ./mill run i am cow +JSONified using Jackson: ["i","am","cow"] + +*/ diff --git a/example/kotlinlib/module/2-ivy-deps/src/foo/Foo.kt b/example/kotlinlib/module/2-ivy-deps/src/foo/Foo.kt new file mode 100644 index 00000000000..90718087d92 --- /dev/null +++ b/example/kotlinlib/module/2-ivy-deps/src/foo/Foo.kt @@ -0,0 +1,9 @@ +package foo + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature + +fun main(args: Array) { + val value = ObjectMapper().writeValueAsString(args) + println("JSONified using Jackson: $value") +} diff --git a/example/kotlinlib/module/3-run-compile-deps/bar/src/bar/Bar.kt b/example/kotlinlib/module/3-run-compile-deps/bar/src/bar/Bar.kt new file mode 100644 index 00000000000..2e5a47686fa --- /dev/null +++ b/example/kotlinlib/module/3-run-compile-deps/bar/src/bar/Bar.kt @@ -0,0 +1,28 @@ +package bar + +import javax.servlet.ServletException +import javax.servlet.http.HttpServlet +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import java.io.IOException +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.servlet.ServletContextHandler +import org.eclipse.jetty.servlet.ServletHolder + +class BarServlet : HttpServlet() { + protected override fun doGet(request: HttpServletRequest, response: HttpServletResponse) { + response.setContentType("text/html") + response.setStatus(HttpServletResponse.SC_OK) + response.getWriter().println("Hello World!") + } +} + +fun main(args: Array) { + val server = Server(8079) + val context = ServletContextHandler() + context.setContextPath("/") + server.setHandler(context) + context.addServlet(ServletHolder(BarServlet()), "/*") + server.start() + server.join() +} diff --git a/example/kotlinlib/module/3-run-compile-deps/build.mill b/example/kotlinlib/module/3-run-compile-deps/build.mill new file mode 100644 index 00000000000..4a9b909942f --- /dev/null +++ b/example/kotlinlib/module/3-run-compile-deps/build.mill @@ -0,0 +1,31 @@ +//// SNIPPET:BUILD1 +package build +import mill._, kotlinlib._ + +object foo extends KotlinModule { + + def kotlinVersion = "1.9.24" + + def moduleDeps = Seq(bar) + def runIvyDeps = Agg( + ivy"javax.servlet:servlet-api:2.5", + ivy"org.eclipse.jetty:jetty-server:9.4.42.v20210604", + ivy"org.eclipse.jetty:jetty-servlet:9.4.42.v20210604" + ) + def mainClass = Some("bar.Bar") +} + +//// SNIPPET:BUILD2 + +object bar extends KotlinModule { + + def kotlinVersion = "1.9.24" + + def compileIvyDeps = Agg( + ivy"javax.servlet:servlet-api:2.5", + ivy"org.eclipse.jetty:jetty-server:9.4.42.v20210604", + ivy"org.eclipse.jetty:jetty-servlet:9.4.42.v20210604" + ) +} + +//// SNIPPET:SCALASTEWARD diff --git a/example/kotlinlib/module/3-run-compile-deps/foo/src/foo/Foo.kt b/example/kotlinlib/module/3-run-compile-deps/foo/src/foo/Foo.kt new file mode 100644 index 00000000000..9e2b3bf4abe --- /dev/null +++ b/example/kotlinlib/module/3-run-compile-deps/foo/src/foo/Foo.kt @@ -0,0 +1,3 @@ +package foo + +fun main(args: Array) = println("Hello World") diff --git a/example/kotlinlib/module/5-resources/build.mill b/example/kotlinlib/module/5-resources/build.mill new file mode 100644 index 00000000000..59761027800 --- /dev/null +++ b/example/kotlinlib/module/5-resources/build.mill @@ -0,0 +1,25 @@ +//// SNIPPET:BUILD +package build +import mill._, kotlinlib._ + +object foo extends KotlinModule { + + def kotlinVersion = "1.9.24" + + object test extends KotlinModuleTests with TestModule.Junit5 { + def otherFiles = T.source(millSourcePath / "other-files") + + def forkEnv = super.forkEnv() ++ Map( + "OTHER_FILES_FOLDER" -> otherFiles().path.toString + ) + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1" + ) + } +} + +//// SNIPPET:APPLICATIONCODE + +/** See Also: foo/src/Foo.kt */ +/** See Also: foo/test/src/FooTests.kt */ diff --git a/example/kotlinlib/module/5-resources/foo/resources/file.txt b/example/kotlinlib/module/5-resources/foo/resources/file.txt new file mode 100644 index 00000000000..c3e18f6e79b --- /dev/null +++ b/example/kotlinlib/module/5-resources/foo/resources/file.txt @@ -0,0 +1 @@ +Hello World Resource File \ No newline at end of file diff --git a/example/kotlinlib/module/5-resources/foo/src/Foo.kt b/example/kotlinlib/module/5-resources/foo/src/Foo.kt new file mode 100644 index 00000000000..64c252df767 --- /dev/null +++ b/example/kotlinlib/module/5-resources/foo/src/Foo.kt @@ -0,0 +1,15 @@ +package foo + +import java.io.IOException +import java.io.InputStream + +object Foo { + + // Read `file.txt` from classpath + fun classpathResourceText(): String { + // Get the resource as an InputStream + return Foo::class.java.classLoader.getResourceAsStream("file.txt").use { + it.readAllBytes().toString(Charsets.UTF_8) + } + } +} diff --git a/example/kotlinlib/module/5-resources/foo/test/other-files/other-file.txt b/example/kotlinlib/module/5-resources/foo/test/other-files/other-file.txt new file mode 100644 index 00000000000..194f575e5c8 --- /dev/null +++ b/example/kotlinlib/module/5-resources/foo/test/other-files/other-file.txt @@ -0,0 +1 @@ +Other Hello World File \ No newline at end of file diff --git a/example/kotlinlib/module/5-resources/foo/test/resources/test-file-a.txt b/example/kotlinlib/module/5-resources/foo/test/resources/test-file-a.txt new file mode 100644 index 00000000000..9e67fcfe4be --- /dev/null +++ b/example/kotlinlib/module/5-resources/foo/test/resources/test-file-a.txt @@ -0,0 +1 @@ +Test Hello World Resource File A \ No newline at end of file diff --git a/example/kotlinlib/module/5-resources/foo/test/resources/test-file-b.txt b/example/kotlinlib/module/5-resources/foo/test/resources/test-file-b.txt new file mode 100644 index 00000000000..289e86183e8 --- /dev/null +++ b/example/kotlinlib/module/5-resources/foo/test/resources/test-file-b.txt @@ -0,0 +1 @@ +Test Hello World Resource File B \ No newline at end of file diff --git a/example/kotlinlib/module/5-resources/foo/test/src/FooTests.kt b/example/kotlinlib/module/5-resources/foo/test/src/FooTests.kt new file mode 100644 index 00000000000..e6b5edab372 --- /dev/null +++ b/example/kotlinlib/module/5-resources/foo/test/src/FooTests.kt @@ -0,0 +1,49 @@ +package foo + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe + +import java.io.InputStream +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.Path +import java.util.List +import java.util.ArrayList + +class FooTests : FunSpec({ + + test("simple") { + // Reference app module's `Foo` class which reads `file.txt` from classpath + val appClasspathResourceText = Foo.classpathResourceText() + appClasspathResourceText shouldBe "Hello World Resource File" + + // Read `test-file-a.txt` from classpath + val testClasspathResourceText = Foo::class.java.classLoader.getResourceAsStream("test-file-a.txt").use { + it.readAllBytes().toString(Charsets.UTF_8) + } + testClasspathResourceText shouldBe "Test Hello World Resource File A" + + // Use `MILL_TEST_RESOURCE_FOLDER` to read `test-file-b.txt` from filesystem + val testFileResourceDir = Paths.get(System.getenv("MILL_TEST_RESOURCE_FOLDER")) + val testFileResourceText = Files.readString( + testFileResourceDir.resolve("test-file-b.txt") + ) + testFileResourceText shouldBe "Test Hello World Resource File B" + + // Use `MILL_TEST_RESOURCE_FOLDER` to list files available in resource folder + val actualFiles = Files.list(testFileResourceDir).toList().sorted() + val expectedFiles = listOf( + testFileResourceDir.resolve("test-file-a.txt"), + testFileResourceDir.resolve("test-file-b.txt") + ) + actualFiles shouldBe expectedFiles + + // Use the `OTHER_FILES_FOLDER` configured in your build to access the + // files in `foo/test/other-files/`. + val otherFileText = Files.readString( + Paths.get(System.getenv("OTHER_FILES_FOLDER"), "other-file.txt") + ) + otherFileText shouldBe "Other Hello World File" + } +}) diff --git a/example/kotlinlib/module/6-annotation-processors/build.mill b/example/kotlinlib/module/6-annotation-processors/build.mill new file mode 100644 index 00000000000..bc633d42ddb --- /dev/null +++ b/example/kotlinlib/module/6-annotation-processors/build.mill @@ -0,0 +1,44 @@ +package build + +import mill._, kotlinlib._ +import java.io.File + +// The Kotlin compiler requires plugins to be passed explicitly. To do this, you can define +// a module to contain the exact annotation processors you want, and pass +// in `-Xplugin` to `kotlincOptions`: + +object foo extends KotlinModule { + + def kotlinVersion = "1.9.24" + + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3" + ) + + def processors = T { + defaultResolver().resolveDeps( + Agg( + ivy"org.jetbrains.kotlin:kotlin-serialization-compiler-plugin:1.9.24" + ) + ) + } + + def kotlincOptions = super.kotlincOptions() ++ Seq( + s"-Xplugin=${processors().map(_.path).head}" + ) + + object test extends KotlinModuleTests with TestModule.Junit5 { + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1" + ) + } +} + +/** + * Usage + * + * > ./mill foo.test + * Test foo.ProjectTestsimple started + * Test foo.ProjectTestsimple finished... + * ... + */ diff --git a/example/kotlinlib/module/6-annotation-processors/foo/src/foo/Project.kt b/example/kotlinlib/module/6-annotation-processors/foo/src/foo/Project.kt new file mode 100644 index 00000000000..3c026b1fcde --- /dev/null +++ b/example/kotlinlib/module/6-annotation-processors/foo/src/foo/Project.kt @@ -0,0 +1,6 @@ +package foo + +import kotlinx.serialization.Serializable + +@Serializable +data class Project(val name: String, val language: String) diff --git a/example/kotlinlib/module/6-annotation-processors/foo/test/src/foo/ProjectTest.kt b/example/kotlinlib/module/6-annotation-processors/foo/test/src/foo/ProjectTest.kt new file mode 100644 index 00000000000..9790b382c86 --- /dev/null +++ b/example/kotlinlib/module/6-annotation-processors/foo/test/src/foo/ProjectTest.kt @@ -0,0 +1,18 @@ +package foo + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import kotlinx.serialization.encodeToString +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +class ProjectTest : FunSpec({ + test("simple") { + val data = Project("kotlinx.serialization", "Kotlin") + val string = Json.encodeToString(data) + string shouldBe """{"name":"kotlinx.serialization","language":"Kotlin"}""" + // Deserializing back into objects + val obj = Json.decodeFromString(string) + obj shouldBe data + } +}) diff --git a/example/kotlinlib/module/7-dokkajar/build.mill b/example/kotlinlib/module/7-dokkajar/build.mill new file mode 100644 index 00000000000..70d28b173b2 --- /dev/null +++ b/example/kotlinlib/module/7-dokkajar/build.mill @@ -0,0 +1,22 @@ +package build +import mill._, kotlinlib._ + +object foo extends KotlinModule { + + def kotlinVersion = "1.9.24" + +} + +/** Usage + +> ./mill show foo.dokkaJar +... +...Generation completed successfully... + +> unzip -p out/foo/dokkaJar.dest/out.jar root/foo/index.html +... +...My Awesome Docs for class Foo... +... +...My Awesome Docs for class Bar... + +*/ diff --git a/example/kotlinlib/module/7-dokkajar/foo/src/foo/Bar.java b/example/kotlinlib/module/7-dokkajar/foo/src/foo/Bar.java new file mode 100644 index 00000000000..533f9855344 --- /dev/null +++ b/example/kotlinlib/module/7-dokkajar/foo/src/foo/Bar.java @@ -0,0 +1,10 @@ +package foo; + +/** + * My Awesome Docs for class Bar + */ +public class Bar { + public void run() { + System.out.println(); + } +} diff --git a/example/kotlinlib/module/7-dokkajar/foo/src/foo/Foo.kt b/example/kotlinlib/module/7-dokkajar/foo/src/foo/Foo.kt new file mode 100644 index 00000000000..c49460191cb --- /dev/null +++ b/example/kotlinlib/module/7-dokkajar/foo/src/foo/Foo.kt @@ -0,0 +1,8 @@ +package foo + +/** + * My Awesome Docs for class Foo + */ +class Foo { + fun run() = println("running Foo") +} diff --git a/example/kotlinlib/module/8-unmanaged-jars/build.mill b/example/kotlinlib/module/8-unmanaged-jars/build.mill new file mode 100644 index 00000000000..6f0e24f1860 --- /dev/null +++ b/example/kotlinlib/module/8-unmanaged-jars/build.mill @@ -0,0 +1,15 @@ +//// SNIPPET:BUILD +package build +import mill._, kotlinlib._ + +object `package` extends RootModule with KotlinModule { + + def kotlinVersion = "1.9.24" + + def mainClass = Some("foo.FooKt") + + def unmanagedClasspath = T { + if (!os.exists(millSourcePath / "lib")) Agg() + else Agg.from(os.list(millSourcePath / "lib").map(PathRef(_))) + } +} diff --git a/example/kotlinlib/module/8-unmanaged-jars/lib/nanojson-1.8.jar b/example/kotlinlib/module/8-unmanaged-jars/lib/nanojson-1.8.jar new file mode 100644 index 0000000000000000000000000000000000000000..7274f3f17a6a4f1d6a227c36e0af20127ad8716f GIT binary patch literal 31494 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°pCu%H7rxj_6 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 ZCnFB}68-=Hz) { + val jsonString = args[0] + val jsonObj = JsonParser.`object`().from(jsonString) + + jsonObj.entries.forEach { + println("Key: ${it.key}, Value: ${it.value}") + } +} diff --git a/example/kotlinlib/module/9-main-class/build.mill b/example/kotlinlib/module/9-main-class/build.mill new file mode 100644 index 00000000000..360f9aa8b76 --- /dev/null +++ b/example/kotlinlib/module/9-main-class/build.mill @@ -0,0 +1,10 @@ +//// SNIPPET:BUILD +package build +import mill._, kotlinlib._ + +object `package` extends RootModule with KotlinModule { + + def kotlinVersion = "1.9.24" + + def mainClass = Some("foo.QuxKt") +} diff --git a/example/kotlinlib/module/9-main-class/src/foo/Bar.kt b/example/kotlinlib/module/9-main-class/src/foo/Bar.kt new file mode 100644 index 00000000000..71811f4a970 --- /dev/null +++ b/example/kotlinlib/module/9-main-class/src/foo/Bar.kt @@ -0,0 +1,6 @@ +package foo + +class Bar { + + fun main() = println("Hello Bar") +} diff --git a/example/kotlinlib/module/9-main-class/src/foo/Foo.kt b/example/kotlinlib/module/9-main-class/src/foo/Foo.kt new file mode 100644 index 00000000000..0fd3dd83acf --- /dev/null +++ b/example/kotlinlib/module/9-main-class/src/foo/Foo.kt @@ -0,0 +1,6 @@ +package foo + +class Foo { + + fun main() = println("Hello Foo") +} diff --git a/example/kotlinlib/module/9-main-class/src/foo/Qux.kt b/example/kotlinlib/module/9-main-class/src/foo/Qux.kt new file mode 100644 index 00000000000..c35a9a2b9a4 --- /dev/null +++ b/example/kotlinlib/module/9-main-class/src/foo/Qux.kt @@ -0,0 +1,3 @@ +package foo + +fun main() = println("Hello Qux") diff --git a/example/package.mill b/example/package.mill index 662405296cf..a00c8b22495 100644 --- a/example/package.mill +++ b/example/package.mill @@ -41,6 +41,7 @@ object `package` extends RootModule with Module { object basic extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "basic")) object builds extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "builds")) object linting extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "linting")) + object module extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "module")) } object scalalib extends Module { object basic extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "basic")) @@ -75,6 +76,7 @@ object `package` extends RootModule with Module { case "3-multi-module" => line.replace("bar.BarTests.simple", "bar.BarTestssimple") .replace("bar.BarTests.escaping", "bar.BarTestsescaping") case "4-builtin-commands" => line.replace("compile.dest/zinc", "compile.dest/kotlin.analysis.dummy") + case "5-resources" => line.replace("FooTests.simple", "FooTestssimple") case _ => line } } From 9a244cf066087f455026c6eb1030268f6d3a79a7 Mon Sep 17 00:00:00 2001 From: 0xnm <0xnm@users.noreply.github.com> Date: Sat, 21 Sep 2024 09:48:17 +0200 Subject: [PATCH 3/5] Fix example/kotlinlib/module/6-annotations-processors --- .../module/6-annotation-processors/build.mill | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/example/kotlinlib/module/6-annotation-processors/build.mill b/example/kotlinlib/module/6-annotation-processors/build.mill index bc633d42ddb..880baa499d3 100644 --- a/example/kotlinlib/module/6-annotation-processors/build.mill +++ b/example/kotlinlib/module/6-annotation-processors/build.mill @@ -24,7 +24,7 @@ object foo extends KotlinModule { } def kotlincOptions = super.kotlincOptions() ++ Seq( - s"-Xplugin=${processors().map(_.path).head}" + s"-Xplugin=${processors().head.path}" ) object test extends KotlinModuleTests with TestModule.Junit5 { @@ -34,11 +34,11 @@ object foo extends KotlinModule { } } -/** - * Usage - * - * > ./mill foo.test - * Test foo.ProjectTestsimple started - * Test foo.ProjectTestsimple finished... - * ... - */ +/** Usage + +> ./mill foo.test +Test foo.ProjectTestsimple started +Test foo.ProjectTestsimple finished... +... + +*/ From 94d5c2fbb2fe6308305aa41ff90af1a1d838365b Mon Sep 17 00:00:00 2001 From: 0xnm <0xnm@users.noreply.github.com> Date: Sat, 21 Sep 2024 09:53:23 +0200 Subject: [PATCH 4/5] Fix example/kotlinlib/module/3-run-compile-deps --- example/kotlinlib/module/3-run-compile-deps/build.mill | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/kotlinlib/module/3-run-compile-deps/build.mill b/example/kotlinlib/module/3-run-compile-deps/build.mill index 4a9b909942f..bb9c572193d 100644 --- a/example/kotlinlib/module/3-run-compile-deps/build.mill +++ b/example/kotlinlib/module/3-run-compile-deps/build.mill @@ -12,7 +12,7 @@ object foo extends KotlinModule { ivy"org.eclipse.jetty:jetty-server:9.4.42.v20210604", ivy"org.eclipse.jetty:jetty-servlet:9.4.42.v20210604" ) - def mainClass = Some("bar.Bar") + def mainClass = Some("bar.BarKt") } //// SNIPPET:BUILD2 From 84a81ad87c6de2986b3337f27c5eb05cb7113e33 Mon Sep 17 00:00:00 2001 From: 0xnm <0xnm@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:34:26 +0200 Subject: [PATCH 5/5] Use Dokka as backend for docjar --- example/kotlinlib/module/7-dokkajar/build.mill | 10 ++++++++-- kotlinlib/src/mill/kotlinlib/KotlinModule.scala | 14 +++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/example/kotlinlib/module/7-dokkajar/build.mill b/example/kotlinlib/module/7-dokkajar/build.mill index 70d28b173b2..edccf0a9b77 100644 --- a/example/kotlinlib/module/7-dokkajar/build.mill +++ b/example/kotlinlib/module/7-dokkajar/build.mill @@ -1,3 +1,7 @@ +// To generate API documenation you can use the `docJar` task on the module you'd +// like to create the documenation for, configured via `dokkaOptions`: + +//// SNIPPET:BUILD package build import mill._, kotlinlib._ @@ -7,13 +11,15 @@ object foo extends KotlinModule { } +//// SNIPPET:END + /** Usage -> ./mill show foo.dokkaJar +> ./mill show foo.docJar ... ...Generation completed successfully... -> unzip -p out/foo/dokkaJar.dest/out.jar root/foo/index.html +> unzip -p out/foo/docJar.dest/out.jar root/foo/index.html ... ...My Awesome Docs for class Foo... ... diff --git a/kotlinlib/src/mill/kotlinlib/KotlinModule.scala b/kotlinlib/src/mill/kotlinlib/KotlinModule.scala index f901a4532e2..937ea836347 100644 --- a/kotlinlib/src/mill/kotlinlib/KotlinModule.scala +++ b/kotlinlib/src/mill/kotlinlib/KotlinModule.scala @@ -135,16 +135,12 @@ trait KotlinModule extends JavaModule { outer => () } - override def docJar: T[PathRef] = T[PathRef] { - T.log.info("docJar task shouldn't be used for Kotlin modules, using dokkaJar instead") - dokkaJar() - } - /** - * The documentation jar, containing all the Dokka files, for - * publishing to Maven Central + * The documentation jar, containing all the Dokka HTML files, for + * publishing to Maven Central. You can control Dokka version by using [[dokkaVersion]] + * and option by using [[dokkaOptions]]. */ - def dokkaJar: T[PathRef] = T[PathRef] { + override def docJar: T[PathRef] = T[PathRef] { val outDir = T.dest val dokkaDir = outDir / "dokka" @@ -179,7 +175,7 @@ trait KotlinModule extends JavaModule { outer => /** * Additional options to be used by the Dokka tool. * You should not set the `-outputDir` setting for specifying the target directory, - * as that is done in the [[dokkaJar]] target. + * as that is done in the [[docJar]] target. */ def dokkaOptions: T[Seq[String]] = Task { Seq[String]() }