Subsystem · kotlin

Kotlin support

Drop a .kt file into your sources and the rest happens automatically. No [kotlin] section to fill in, no plugin to add, no kotlinc to install.

Detection

On every build Curie scans the source roots. If at least one .kt file is present, it switches into mixed mode: the Kotlin compiler and the Kotlin stdlib are resolved from Maven Central (cached in ~/.m2 after the first run), and compilation runs in two phases.

Two-phase compile

  1. Phase 1 — kotlinc. Every .kt and every .java source is handed to kotlinc together. Kotlin resolves Java types directly from source — no pre-compiled stubs needed — and writes .class files to target/classes/.
  2. Phase 2 — javac. Only the .java sources are re-compiled, this time with target/classes/ on the classpath so javac sees the Kotlin bytecode from phase 1. This step is skipped when there are no Java sources.

The two-phase order gives both directions of interop. Java can call Kotlin types (resolved against phase-1 bytecode); Kotlin can call Java types (resolved against source in phase 1).

Source layouts

Three layouts coexist in the same project:

Main class auto-detection

For Kotlin fun main() declarations, Curie infers the synthetic FileNameKt class name and writes it into the JAR's Main-Class manifest entry. You can still set mainClass in Curie.toml to override.

src/main/kotlin/com/example/Hello.kt
package com.example

fun main() {
    println("Hello from Curie (Kotlin)!")
}

Build the project and Curie picks com.example.HelloKt automatically — no mainClass required in Curie.toml.

Stale-class detection

Removing a top-level type from a .kt file — or deleting a .kt file outright — leaves orphan .class files in target/classes that the next javac run could silently re-pick-up. Curie cleans them in three steps:

  1. Before kotlinc runs, every .class file whose JVM SourceFile attribute ends in .kt is deleted.
  2. kotlinc compiles the current source set and re-emits exactly the classes still produced. Anything not re-emitted stays gone.
  3. A canonical Kotlin source list is stamped to target/.kt-sources so a pure deletion (which leaves no mtime to compare against) still triggers a recompile next time.

The Java equivalent — driven by a source→class manifest the javac wrapper emits — covers the same cases for .java files. See Incremental builds › stale detection.

Worked example

A Java entry point calling into a Kotlin data class — both files in the same flat-package directory:

src/com.example.mixed/Greeting.kt
package com.example.mixed

data class Greeting(val name: String) {
    fun message(): String = "Hello, $name, from Kotlin!"
}
src/com.example.mixed/Main.java
package com.example.mixed;

public class Main {
    public static void main(String[] args) {
        Greeting g = new Greeting("Curie");
        System.out.println(g.message());
    }
}
$ curie build
Building hello-mixed v0.1.0
  Resolve Kotlin  8 JAR(s)
  Compile         2 source file(s)  [no class files]
  Tests           ✔ 2 tests successful
  Done            target/hello-mixed-0.1.0.jar