Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Kotlin integration #3514

Merged
merged 13 commits into from
Sep 14, 2024
Merged
6 changes: 4 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,20 @@ jobs:
# For most tests, run them arbitrarily on Java 11 or Java 17 on Linux, and
# on the opposite version on Windows below, so we get decent coverage of
# each test on each Java version and each operating system
# We also try to group tests together to manuaully balance out the runtimes of each jobs
# We also try to group tests together to manually balance out the runtimes of each jobs
- java-version: 17
millargs: "'{main,scalalib,testrunner,bsp,testkit}.__.testCached'"
- java-version: '11'
millargs: "'{scalajslib,scalanativelib}.__.testCached'"
millargs: "'{scalajslib,scalanativelib,kotlinlib}.__.testCached'"
- java-version: 17
millargs: "contrib.__.testCached"

- java-version: 17
millargs: "'example.javalib.__.local.testCached'"
- java-version: 17
millargs: "'example.scalalib.__.local.testCached'"
- java-version: 17
millargs: "'example.kotlinlib.__.local.testCached'"
- java-version: '11'
millargs: "'example.thirdparty[{mockito,acyclic,commons-io}].local.testCached'"
- java-version: 17
Expand Down
2 changes: 2 additions & 0 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ object Deps {
val requests = ivy"com.lihaoyi::requests:0.9.0"
val logback = ivy"ch.qos.logback:logback-classic:1.5.7"
val sonatypeCentralClient = ivy"com.lumidion::sonatype-central-client-requests:0.3.0"
val kotlinCompiler = ivy"org.jetbrains.kotlin:kotlin-compiler:1.9.24"

object RuntimeDeps {
val errorProneCore = ivy"com.google.errorprone:error_prone_core:2.31.0"
Expand Down Expand Up @@ -757,6 +758,7 @@ object dist extends MillPublishJavaModule {
genTask(build.main.eval)() ++
genTask(build.main)() ++
genTask(build.scalalib)() ++
genTask(build.kotlinlib)() ++
genTask(build.scalajslib)() ++
genTask(build.scalanativelib)()

Expand Down
101 changes: 101 additions & 0 deletions example/kotlinlib/basic/1-simple/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//// SNIPPET:BUILD
package build
import mill._, kotlinlib._, scalalib._

object `package` extends RootModule with KotlinModule {

def kotlinVersion = "1.9.24"

def mainClass = Some("foo.FooKt")

def ivyDeps = Agg(
ivy"com.github.ajalt.clikt:clikt-jvm:4.4.0",
ivy"org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0"
)

object test extends KotlinModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1"
)
}
}

// This is a basic Mill build for a single `KotlinModule`, with two
// third-party dependencies and a test suite using the JUnit framework. As a
// single-module project, it `extends RootModule` to mark `object foo` as the
// top-level module in the build. This lets us directly perform operations
// `./mill compile` or `./mill run` without needing to prefix it as
// `foo.compile` or `foo.run`.
//
//// SNIPPET:DEPENDENCIES
//
// This example project uses two third-party dependencies - Clikt for CLI
// argument parsing, Apache Commons Text for HTML escaping - and uses them to wrap a
// given input string in HTML templates with proper escaping.
//
// You can run `assembly` to generate a standalone executable jar, which then
// can be run from the command line or deployed to be run elsewhere.

/** Usage

> ./mill resolve _ # List what tasks are available to run
assembly
...
clean
...
compile
...
run
...
show
...
inspect
...

> ./mill inspect compile # Show documentation and inputs of a task
compile(KotlinModule...)
Compiles all the sources to JVM class files.
Compiles the current module to generate compiled classfiles/bytecode.
When you override this, you probably also want/need to override [[bspCompileClassesPath]],
as that needs to point to the same compilation output path.
Keep in sync with [[bspCompileClassesPath]]
Inputs:
allJavaSourceFiles
allKotlinSourceFiles
compileClasspath
upstreamCompileOutput
javacOptions
zincReportCachedProblems
kotlincOptions
kotlinCompilerClasspath
...

> ./mill compile # compile sources into classfiles
...
Compiling 1 Kotlin sources to...

> ./mill run # run the main method, if any
error: Error: missing option --text
...

> ./mill run --text hello
<h1>hello</h1>

> ./mill test
...
Test foo.FooTest.testEscaping finished, ...
Test foo.FooTest.testSimple finished, ...
Test run foo.FooTest finished: 0 failed, 0 ignored, 2 total, ...

> ./mill assembly # bundle classfiles and libraries into a jar for deployment

> ./mill show assembly # show the output of the assembly task
".../out/assembly.dest/out.jar"

> java -jar ./out/assembly.dest/out.jar --text hello
<h1>hello</h1>

> ./out/assembly.dest/out.jar --text hello # mac/linux
<h1>hello</h1>

*/
21 changes: 21 additions & 0 deletions example/kotlinlib/basic/1-simple/src/foo/Foo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package foo

import kotlinx.html.h1
import kotlinx.html.stream.createHTML
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required

class Foo: CliktCommand() {
val text by option("-t", "--text", help="text to insert").required()

override fun run() {
echo(generateHtml(text))
}
}

fun generateHtml(text: String): String {
return createHTML(prettyPrint = false).h1 { text(text) }.toString()
}

fun main(args: Array<String>) = Foo().main(args)
15 changes: 15 additions & 0 deletions example/kotlinlib/basic/1-simple/test/src/foo/FooTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package foo

import foo.generateHtml
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe

class FooTest : FunSpec({
test("testSimple") {
generateHtml("hello") shouldBe "<h1>hello</h1>"
}

test("testEscaping") {
generateHtml("<hello>") shouldBe "<h1>&lt;hello&gt;</h1>"
}
})
27 changes: 27 additions & 0 deletions example/kotlinlib/basic/2-custom-build-logic/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//// SNIPPET:BUILD
package build
import mill._, kotlinlib._, scalalib._

object `package` extends RootModule with KotlinModule {

def kotlinVersion = "1.9.24"

def mainClass = Some("foo.FooKt")

/** Total number of lines in module's source files */
def lineCount = T{
allSourceFiles().map(f => os.read.lines(f.path).size).sum
}

/** Generate resources using lineCount of sources */
override def resources = T{
os.write(T.dest / "line-count.txt", "" + lineCount())
Seq(PathRef(T.dest))
}

object test extends KotlinModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1"
)
}
}
17 changes: 17 additions & 0 deletions example/kotlinlib/basic/2-custom-build-logic/src/foo/Foo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package foo

import java.io.IOException

fun getLineCount(): String? {
return try {
String(
::main.javaClass.classLoader.getResourceAsStream("line-count.txt").readAllBytes()
)
} catch (e: IOException) {
null
}
}

fun main() {
println("Line Count: " + getLineCount())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package foo

import foo.getLineCount
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe

class FooTests : FunSpec({

test("testSimple") {
val expectedLineCount = 12
val actualLineCount = getLineCount()?.trim().let { Integer.parseInt(it) }
actualLineCount shouldBe expectedLineCount
}
})
12 changes: 12 additions & 0 deletions example/kotlinlib/basic/3-multi-module/bar/src/bar/Bar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package bar

import kotlinx.html.h1
import kotlinx.html.stream.createHTML

fun generateHtml(text: String): String {
return createHTML(prettyPrint = false).h1 { text(text) }.toString()
}

fun main(args: Array<String>) {
println("Bar.value: " + generateHtml(args[0]))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package bar

import bar.generateHtml
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe

class BarTests : FunSpec({

test("simple") {
val result = generateHtml("hello")
result shouldBe "<h1>hello</h1>"
}

test("escaping") {
val result = generateHtml("<hello>")
result shouldBe "<h1>&lt;hello&gt;</h1>"
}
})
57 changes: 57 additions & 0 deletions example/kotlinlib/basic/3-multi-module/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//// SNIPPET:BUILD
package build
import mill._, kotlinlib._, scalalib._

trait MyModule extends KotlinModule {

def kotlinVersion = "1.9.24"

}

object foo extends MyModule {
def mainClass = Some("foo.FooKt")
def moduleDeps = Seq(bar)
def ivyDeps = Agg(
ivy"com.github.ajalt.clikt:clikt-jvm:4.4.0"
)
}

object bar extends MyModule {
def mainClass = Some("bar.BarKt")
def ivyDeps = Agg(
ivy"org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0"
)

object test extends KotlinModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1"
)
}
}

//// SNIPPET:TREE
// ----
// build.mill
// foo/
// src/
// foo/
// Foo.java
// resources/
// ...
// bar/
// src/
// bar/
// Bar.java
// resources/
// ...
// out/
// foo/
// compile.json
// compile.dest/
// ...
// bar/
// compile.json
// compile.dest/
// ...
// ----
//
23 changes: 23 additions & 0 deletions example/kotlinlib/basic/3-multi-module/foo/src/foo/Foo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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

const val VALUE: String = "hello"

class Foo: CliktCommand() {
val fooText by option("--foo-text").required()
val barText by option("--bar-text").required()

override fun run() {
mainFunction(fooText, barText)
}
}

fun mainFunction(fooText: String, barText: String) {
println("Foo.value: " + VALUE)
println("Bar.value: " + bar.generateHtml(barText))
}

fun main(args: Array<String>) = Foo().main(args)
12 changes: 12 additions & 0 deletions example/kotlinlib/basic/4-builtin-commands/bar/src/bar/Bar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package bar

import kotlinx.html.h1
import kotlinx.html.stream.createHTML

fun generateHtml(text: String): String {
return createHTML(prettyPrint = false).h1 { text("world") }.toString()
}

fun main(args: Array<String>) {
println("Bar.value: " + generateHtml(args[0]))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package bar

import bar.generateHtml
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe

class BarTests : FunSpec({

test("simple") {
val result = generateHtml("hello")
result shouldBe "<h1>hello</h1>"
}

test("escaping") {
val result = generateHtml("<hello>")
result shouldBe "<h1>&lt;hello&gt;</h1>"
}
})
Loading
Loading