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 = super.ivyDeps() ++ Agg(
ivy"net.sourceforge.argparse4j:argparse4j:0.9.0",
ivy"org.apache.commons:commons-text:1.12.0"
)

object test extends KotlinModuleTests with TestModule.Junit4{
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"com.google.guava:guava:33.3.0-jre"
)
}
0xnm marked this conversation as resolved.
Show resolved Hide resolved
}

// 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 - ArgParse4J 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: argument -t/--text is required
...

> ./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>

*/
30 changes: 30 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,30 @@
package foo

import org.apache.commons.text.StringEscapeUtils
import net.sourceforge.argparse4j.ArgumentParsers
import net.sourceforge.argparse4j.inf.ArgumentParser
import net.sourceforge.argparse4j.inf.ArgumentParserException
import net.sourceforge.argparse4j.inf.Namespace

fun generateHtml(text: String): String {
return "<h1>" + StringEscapeUtils.escapeHtml4(text) + "</h1>"
}

fun main(args: Array<String>) {
val parser = ArgumentParsers.newFor("template").build()
.defaultHelp(true)
.description("Inserts text into a HTML template")

parser.addArgument("-t", "--text")
.required(true)
.help("text to insert")

try {
parser.parseArgs(args).let {
println(generateHtml(it.getString("text")))
}
} catch (e: Exception) {
println(e.message)
System.exit(1)
}
}
18 changes: 18 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,18 @@
package foo

import com.google.common.html.HtmlEscapers.htmlEscaper
import foo.generateHtml
import org.junit.Assert.assertEquals
import org.junit.Test

class FooTest {
@Test
fun testSimple() {
assertEquals(generateHtml("hello"), "<h1>hello</h1>")
}

@Test
fun testEscaping() {
assertEquals(generateHtml("<hello>"), "<h1>" + htmlEscaper().escape("<hello>") + "</h1>")
}
}
23 changes: 23 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,23 @@
//// 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.Junit4
}
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,15 @@
package foo

import org.junit.Assert.assertEquals
import org.junit.Test
import foo.getLineCount

class FooTests {

@Test
fun testSimple() {
val expectedLineCount = 12
val actualLineCount = getLineCount()?.trim().let { Integer.parseInt(it) }
assertEquals(expectedLineCount, actualLineCount)
}
}
14 changes: 14 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,14 @@
package bar

import org.apache.commons.text.StringEscapeUtils
import net.sourceforge.argparse4j.ArgumentParsers
import net.sourceforge.argparse4j.inf.ArgumentParser
import net.sourceforge.argparse4j.inf.Namespace

fun generateHtml(text: String): String {
return "<h1>" + StringEscapeUtils.escapeHtml4(text) + "</h1>"
}

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

import bar.generateHtml
import org.junit.Assert.assertEquals
import org.junit.Test

class BarTests {

@Test
fun simple() {
val result = generateHtml("hello")
assertEquals("<h1>hello</h1>", result)
}

@Test
fun escaping() {
val result = generateHtml("<hello>")
assertEquals("<h1>&lt;hello&gt;</h1>", result)
}
}
54 changes: 54 additions & 0 deletions example/kotlinlib/basic/3-multi-module/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//// 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 = super.ivyDeps() ++ Agg(
ivy"net.sourceforge.argparse4j:argparse4j:0.9.0"
)
}

object bar extends MyModule {
def mainClass = Some("bar.BarKt")
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"net.sourceforge.argparse4j:argparse4j:0.9.0",
ivy"org.apache.commons:commons-text:1.12.0"
)

object test extends KotlinModuleTests with TestModule.Junit4
}

//// 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/
// ...
// ----
//
25 changes: 25 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,25 @@
package foo

import net.sourceforge.argparse4j.ArgumentParsers
import net.sourceforge.argparse4j.inf.ArgumentParser
import net.sourceforge.argparse4j.inf.Namespace

const val VALUE: String = "hello"

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

fun main(args: Array<String>) {
val parser = ArgumentParsers.newFor("Foo").build()
parser.addArgument("--foo-text").required(true)
parser.addArgument("--bar-text").required(true)

val res = parser.parseArgs(args)

val fooText = res.getString("foo_text")
val barText = res.getString("bar_text")

mainFunction(fooText, barText)
}
14 changes: 14 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,14 @@
package bar

import org.apache.commons.text.StringEscapeUtils
import net.sourceforge.argparse4j.ArgumentParsers
import net.sourceforge.argparse4j.inf.ArgumentParser
import net.sourceforge.argparse4j.inf.Namespace

fun generateHtml(text: String): String {
return "<h1>" + StringEscapeUtils.escapeHtml4("world") + "</h1>"
}

fun main(args: Array<String>) {
println("Bar.value: " + generateHtml(args[0]))
}
Loading
Loading