kymora-kyo-mill
kymora-kyo-mill provides Mill-native integrations for Kyo users. It mirrors Kyo semantics where useful, but it does not port sbt concepts into Mill: users add one Mill plugin dependency, mix traits into normal Mill modules, and run ordinary Mill tasks.
//| - io.eleven19.kymora::kymora-kyo-mill::0.1.0-SNAPSHOT
import io.eleven19.kymora.kyo.mill.compat.*
import io.eleven19.kymora.kyo.mill.doctest.*
import io.eleven19.kymora.kyo.mill.ffi.*
import io.eleven19.kymora.kyo.mill.test.*
import io.eleven19.kymora.kyo.mill.wasm.*
import mill.*
import scalalib.*The public API is organized by integration theme: test, wasm, doctest, compat, and ffi. The root package only contains shared defaults.
kyo-test
Use the platform-specific test trait for the module you are defining.
object app extends ScalaModule {
def scalaVersion = "3.8.4"
object test extends ScalaTests with KyoTestModule
}For Scala.js, Scala.js WebAssembly, and Scala Native tests, use KyoTestJSModule, KyoTestWasmModule, or KyoTestNativeModule.
Scala.js WebAssembly
KyoScalaJSWasmModule configures a Scala.js module for WebAssembly output, including the _sjs1-wasm platform suffix, ES modules, and Node.js runtime flags expected by Kyo WASM artifacts.
object app extends KyoScalaJSWasmModule {
def scalaVersion = "3.8.4"
}WASM test execution requires Node.js 24 or newer.
Doctest
KyoDoctestModule validates Markdown or Scala documentation snippets via Kyo's doctest CLI. Validation commands do not rewrite tracked files.
object docs extends ScalaModule with KyoDoctestModule {
def scalaVersion = "3.8.4"
override def doctestSources =
Task.Sources(moduleDir / "README.md")
}Run:
./mill docs.doctest
./mill docs.doctestFresh
./mill docs.doctestCleandoctestFormat is reserved for a future Kyo formatter entrypoint; Kyo 1.0.0-RC4 exposes stable validation through the CLI, but not a stable non-sbt formatting command.
Compat
KyoCompatModule helps explicit Mill modules select Kyo compat artifacts. Mill modules are statically declared, so consumers model backend/platform combinations with ordinary modules or Cross modules instead of sbt ProjectMatrix-style dynamic generation.
object zioCompat extends ScalaModule with KyoCompatModule {
def scalaVersion = "3.8.4"
def compatBackend = CompatBackend.Zio
def compatPlatform = CompatPlatform.Jvm
}Unsupported backend/platform combinations fail clearly when the compat axis is evaluated.
FFI
KyoFfiModule provides a Mill-native build integration for kyo-ffi. It keeps the Kyo runtime and codegen semantics, but exposes them as ordinary Mill tasks and module traits.
//| mvnDeps:
//| - io.eleven19.kymora::kymora-kyo-mill::0.1.0-SNAPSHOT
package build
import io.eleven19.kymora.kyo.mill.ffi.*
import io.eleven19.kymora.kyo.mill.test.*
import mill.*, scalalib.*
object app extends KyoFfiModule {
def scalaVersion = "3.8.4"
override def ffiLibraryId = Task { "math" }
object test extends ScalaTests with KyoTestModule with KyoFfiTests {
override def sources =
Task.Sources(moduleDir / os.up / os.up / "test" / "src")
override def ffiRuntimeJavaOptions =
app.ffiRuntimeJavaOptions()
}
}The default layout is Mill-native:
app/
src/
example/MathBindings.scala
src/c/
math.c
test/src/
example/MathBindingsTests.scalaApplication code stays the Kyo FFI model:
package example
import kyo.AllowUnsafe
import kyo.ffi.Ffi
trait MathBindings extends Ffi:
def mathAdd(a: Int, b: Int)(using AllowUnsafe): Int
object MathBindings extends Ffi.Config(library = "math")int math_add(int a, int b) {
return a + b;
}For Scala Native vendored C, mark the Kyo binding as bundled so generated code does not emit @link("<library>"); the Mill task copies C sources into managed resources/scala-native for Scala Native to compile into the binary:
object MathBindings extends Ffi.Config(library = "math", nativeBundled = true)sbt-compatible layout
The sbt-compatible traits are an explicit layout compatibility layer, not an sbt task/settings port. Use them when a downstream project already keeps Scala and C sources under Maven-style directories.
object app extends KyoFfiSbtModule {
def scalaVersion = "3.8.4"
override def ffiLibraryId = Task { "math" }
object test extends SbtTests with KyoTestModule with KyoFfiTests {
override def ffiRuntimeJavaOptions =
app.ffiRuntimeJavaOptions()
}
}app/
src/main/scala/
example/MathBindings.scala
src/main/c/
math.c
src/test/scala/
example/MathBindingsTests.scalaThe integration exposes these tasks:
ffiGeneratecompiles current Scala sources to scratch TASTy and generates Kyo FFI implementation sources.ffiCompilecompiles declared C sources into platform-native shared libraries on JVM and Scala.js.ffiPackagepackages JVM libraries underMETA-INF/native/<os>-<arch>/and JS libraries underkyo-ffi/native/<os>-<arch>/.ffiNativeResourcesandffiNativeLinkingOptionssupport Scala Native by making C sources/resources and link options explicit.ffiDumpCcCommandprints the compiler command shape for diagnostics.ffiNpmBundleTemplatewrites a minimal JSpackage.jsonwithkoffi: ^2.7.ffiCleanremoves generated FFI outputs.
Platform traits:
KyoFfiModuletargets JVM and discovers C sources fromsrc/c.KyoFfiJSModuletargets Scala.js and sets CommonJS output for Node/koffi compatibility while discovering C sources fromsrc/c.KyoFfiNativeModuletargets Scala Native and wiresffiNativeLinkingOptionsintonativeLinkingOptionswhile discovering C sources fromsrc/c.KyoFfiWasmModulefails clearly because Kyo FFI does not support Scala.js WebAssembly.KyoFfiSbtModule,KyoFfiSbtJSModule,KyoFfiSbtNativeModule, andKyoFfiSbtWasmModuleprovide the same platform semantics with explicitsrc/main/csource discovery for sbt/Maven-compatible layouts.
The integration fixtures cover realistic vendored-C usage across primitives, strings passed into C, buffers, structs, callbacks, JVM shared-library loading, Scala.js koffi loading, and Scala Native bundled C resources.
Realistic library examples
The integration tests include complete downstream projects that double as copyable examples.
ffi-string-library models a small textkit C library that accepts multiple String values and returns primitive results:
trait TextKitBindings extends Ffi:
def textkitCountWords(text: String)(using AllowUnsafe): Int
def textkitHasPrefix(text: String, prefix: String)(using AllowUnsafe): Boolean
def textkitSharedPrefixLength(left: String, right: String)(using AllowUnsafe): Int
def textkitScoreTitle(title: String, keyword: String)(using AllowUnsafe): Double
object TextKitBindings extends Ffi.Config(library = "textkit")bool textkit_has_prefix(const char *text, const char *prefix) {
return strncmp(text, prefix, strlen(prefix)) == 0;
}ffi-struct-library models a contacts C library with nested structs, string parameters, and a struct return containing a string field:
case class Address(zip: Int, cityCode: Int)
case class Contact(id: Int, score: Int, address: Address)
case class Badge(priority: Int, label: String)
trait ContactBindings extends Ffi:
def contactsRouteCode(contact: Contact)(using AllowUnsafe): Int
def contactsIsLocal(contact: Contact, city: String)(using AllowUnsafe): Boolean
def contactsBadge(contact: Contact)(using AllowUnsafe): Badge
object ContactBindings extends Ffi.Config(library = "contacts")typedef struct {
int32_t zip;
int32_t city_code;
} address_t;
typedef struct {
int32_t id;
int32_t score;
address_t address;
} contact_t;
int32_t contacts_badge(const contact_t *contact, const char **out_label) {
bool local = contact->address.zip == 60606;
*out_label = local ? "local-contact" : "remote-contact";
return local ? 10 : 1;
}The normative behavior spec lives in docs/ffi.ears.md, with requirement IDs mapped to unit and integration test suites.
Scope
This artifact currently covers kyo-test, Scala.js WebAssembly module defaults, doctest validation, compat artifact selection, and Kyo FFI build integration. The implementation is intentionally Mill-native: configuration is expressed as module defs, generated files flow through Mill source/resource tasks, and cleanup/template actions are explicit commands.