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..bb9c572193d --- /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.BarKt") +} + +//// 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..880baa499d3 --- /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().head.path}" + ) + + 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..edccf0a9b77 --- /dev/null +++ b/example/kotlinlib/module/7-dokkajar/build.mill @@ -0,0 +1,28 @@ +// 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._ + +object foo extends KotlinModule { + + def kotlinVersion = "1.9.24" + +} + +//// SNIPPET:END + +/** Usage + +> ./mill show foo.docJar +... +...Generation completed successfully... + +> unzip -p out/foo/docJar.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 00000000000..7274f3f17a6 Binary files /dev/null and b/example/kotlinlib/module/8-unmanaged-jars/lib/nanojson-1.8.jar differ diff --git a/example/kotlinlib/module/8-unmanaged-jars/src/foo/Foo.kt b/example/kotlinlib/module/8-unmanaged-jars/src/foo/Foo.kt new file mode 100644 index 00000000000..2ea77277d37 --- /dev/null +++ b/example/kotlinlib/module/8-unmanaged-jars/src/foo/Foo.kt @@ -0,0 +1,13 @@ +package foo + +import com.grack.nanojson.JsonParser +import com.grack.nanojson.JsonObject + +fun main(args: Array) { + 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 } } diff --git a/kotlinlib/src/mill/kotlinlib/KotlinModule.scala b/kotlinlib/src/mill/kotlinlib/KotlinModule.scala index bbd62076d1f..937ea836347 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,79 @@ trait KotlinModule extends JavaModule { outer => () } + /** + * 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]]. + */ + override def docJar: 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 [[docJar]] 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(" ")}")