Skip to content

Commit

Permalink
Add kotlinlib example/module examples (#3585)
Browse files Browse the repository at this point in the history
This PR adds more stuff for #3451, namely `example/kotlinlib/module`
examples.

Few notes about things being done:

* `1-compilation-execution-flags` - there is not so many flags which can
be passed to the Kotlin Compiler itself. I simply used
[-Werror](https://kotlinlang.org/docs/compiler-reference.html#werror).
* `6-annotation-processors` - I used `kotlinx.serialization` as proposed
by @lefou. It seems Kotlin Compiler doesn't automatically pick
compilation plugins from the classpath, so I had to pass path to it
explicitly using `-Xplugin` option.
* `7-dokkajar` - obviously `javadoc` cannot be used to generate docs for
Kotlin classes, so I added support for
[Dokka](https://github.com/Kotlin/dokka) to `KotlinModule` and added a
forwarding of `docJar` calls to `dokkaJar`.
* `13-jni` - there is no possibility to generate header files from
Kotlin files (unless an ugly trick is used to take compiled `.class`
files, decompile them back, clean-up and pass to `javac` to generate
headers), so I dropped header generation.

---------

Co-authored-by: 0xnm <[email protected]>
  • Loading branch information
0xnm and 0xnm authored Sep 22, 2024
1 parent 92a7211 commit b50c600
Show file tree
Hide file tree
Showing 43 changed files with 733 additions and 4 deletions.
17 changes: 17 additions & 0 deletions example/kotlinlib/module/1-compilation-execution-flags/build.mill
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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")
}
20 changes: 20 additions & 0 deletions example/kotlinlib/module/10-downloading-non-maven-jars/build.mill
Original file line number Diff line number Diff line change
@@ -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"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package foo

import com.williamfiset.fastjavaio.InputReader

import java.io.FileInputStream
import java.io.IOException

fun main(args: Array<String>) {
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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
I am cow
hear me moo
I weigh twice as much as you
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bar Application Conf
27 changes: 27 additions & 0 deletions example/kotlinlib/module/11-assembly-config/build.mill
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Foo Application Conf
15 changes: 15 additions & 0 deletions example/kotlinlib/module/11-assembly-config/foo/src/foo/Foo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package foo

import java.io.IOException
import java.io.InputStream

fun main(args: Array<String>) {
::main.javaClass
.classLoader
.getResourceAsStream("application.conf")
.use {
val conf = it.readAllBytes().toString(Charsets.UTF_8)
println("Loaded application.conf from resources: $conf")
}

}
40 changes: 40 additions & 0 deletions example/kotlinlib/module/12-repository-config/build.mill
Original file line number Diff line number Diff line change
@@ -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 }
}
18 changes: 18 additions & 0 deletions example/kotlinlib/module/12-repository-config/foo/src/foo/Foo.kt
Original file line number Diff line number Diff line change
@@ -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<String>) = Foo().main(args)
78 changes: 78 additions & 0 deletions example/kotlinlib/module/13-jni/build.mill
Original file line number Diff line number Diff line change
@@ -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...
...
*/
7 changes: 7 additions & 0 deletions example/kotlinlib/module/13-jni/native-src/HelloWorld.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <jni.h>
#include <string.h>

// Implementation of the native method
JNIEXPORT jstring JNICALL Java_foo_HelloWorld_sayHello(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "Hello, World!");
}
19 changes: 19 additions & 0 deletions example/kotlinlib/module/13-jni/src/foo/HelloWorld.kt
Original file line number Diff line number Diff line change
@@ -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<String>) {
val helloWorld = HelloWorld()
println(helloWorld.sayHello())
}
10 changes: 10 additions & 0 deletions example/kotlinlib/module/13-jni/test/src/foo/HelloWorldTest.kt
Original file line number Diff line number Diff line change
@@ -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!"
}
})
24 changes: 24 additions & 0 deletions example/kotlinlib/module/2-ivy-deps/build.mill
Original file line number Diff line number Diff line change
@@ -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"]

*/
9 changes: 9 additions & 0 deletions example/kotlinlib/module/2-ivy-deps/src/foo/Foo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package foo

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature

fun main(args: Array<String>) {
val value = ObjectMapper().writeValueAsString(args)
println("JSONified using Jackson: $value")
}
28 changes: 28 additions & 0 deletions example/kotlinlib/module/3-run-compile-deps/bar/src/bar/Bar.kt
Original file line number Diff line number Diff line change
@@ -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("<html><body>Hello World!</body></html>")
}
}

fun main(args: Array<String>) {
val server = Server(8079)
val context = ServletContextHandler()
context.setContextPath("/")
server.setHandler(context)
context.addServlet(ServletHolder(BarServlet()), "/*")
server.start()
server.join()
}
Loading

0 comments on commit b50c600

Please sign in to comment.