Scala Native Examples

This page contains examples of using Mill as a build tool for scala-native applications. It covers setting up a basic scala-native application that calls C function within it, as well as example of two modules with a scala-native application.

Simple

build.mill (download, browse)
package build
import mill._, scalalib._, scalanativelib._

object `package` extends RootModule with ScalaNativeModule {
  def scalaVersion = "3.3.4"
  def scalaNativeVersion = "0.5.5"

  // You can have arbitrary numbers of third-party dependencies
  def ivyDeps = Agg(
    ivy"com.lihaoyi::mainargs::0.7.6"
  )

  object test extends ScalaNativeTests with TestModule.Utest {
    def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.8.4")
    def testFramework = "utest.runner.Framework"
  }
}

This example demonstrates a simple Scala program that generates HTML content from user-provided text and prints it to the standard output, utilizing Scala Native for native integration and mainargs for command-line argument parsing.

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

> ./mill show nativeLink  # Build and link native binary
".../out/nativeLink.dest/out"

> ./out/nativeLink.dest/out --text hello  # Run the executable
<h1>hello</h1>

Interop

build.mill (download, browse)
package build
import mill._, scalalib._, scalanativelib._

object `package` extends RootModule with ScalaNativeModule {
  def scalaVersion = "3.3.4"
  def scalaNativeVersion = "0.5.5"

  object test extends ScalaNativeTests {
    def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.8.4")
    def testFramework = "utest.runner.Framework"
  }

}

This is an example of how to use Mill to compile C code together with your Scala Native code.

The above build expect the following project layout:

build.mill
src/
	foo/
	    HelloWorld.scala

resources/
    scala-native/
        HelloWorld.c

test/
    src/
        foo/
            HelloWorldTests.scala

Note: C/C++ source files need to be in resources/scala-native directory so It can be linked and compiled successfully. More info from Scala Native doc here and also Scala user forum here

This example is pretty minimal, but it demonstrates the core principles, and can be extended if necessary to more elaborate use cases.

> ./mill run
Running HelloWorld function
Done...
Reversed: !dlroW ,olleH

> ./mill test
Tests: 1, Passed: 1, Failed: 0

Multi-Module

build.mill (download, browse)
package build
import mill._, scalalib._, scalanativelib._

trait MyModule extends ScalaNativeModule {
  def scalaVersion = "3.3.4"
  def scalaNativeVersion = "0.5.5"

  object test extends ScalaNativeTests {
    def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.8.4")
    def testFramework = "utest.runner.Framework"
  }
}

object foo extends MyModule {
  def moduleDeps = Seq(bar)

  def ivyDeps = Agg(ivy"com.lihaoyi::mainargs::0.7.6")
}

object bar extends MyModule

This example contains a simple Mill build with two modules, foo and bar. We don’t mark either module as top-level using extends RootModule, so running tasks needs to use the module name as the prefix e.g. foo.run or bar.run. You can define multiple modules the same way you define a single module, using def moduleDeps to define the relationship between them.

Note that we split out the test submodule configuration common to both modules into a separate trait MyModule. This lets us avoid the need to copy-paste common settings, while still letting us define any per-module configuration such as ivyDeps specific to a particular module.

The above builds expect the following project layout:

build.mill
bar/
    resources/
        scala-native/
            bar.h
            HelloWorldBar.c
    src/
        Bar.scala
    test/
        src/
            BarTests.scala
foo/
    resources/
        scala-native/
            bar.h
            HelloWorldFoo.c
    src/
        Foo.scala

Note: C/C++ source files need to be in resources/scala-native directory so It can be linked and compiled successfully. More info from Scala Native doc here and also Scala user forum here

> ./mill bar.run hello
Running HelloWorld function
Done...
Bar value: Argument length is 5

> ./mill bar.test
Tests: 1, Passed: 1, Failed: 0

> ./mill foo.run --bar-text hello --foo-text world
Foo.value: The vowel density of 'world' is 20
Bar.value: The string length of 'hello' is 5

Common Config

build.mill (download, browse)
package build
import mill._, scalalib._, scalanativelib._, scalanativelib.api._

object `package` extends RootModule with ScalaNativeModule {
  def scalaVersion = "3.3.4"
  def scalaNativeVersion = "0.5.5"

  // You can have arbitrary numbers of third-party dependencies
  // Scala Native uses double colon `::` between organization and the dependency names
  def ivyDeps = Agg(
    ivy"com.lihaoyi::fansi::0.5.0"
  )

  // Set the releaseMode to ReleaseFast.
  def releaseMode: T[ReleaseMode] = ReleaseMode.ReleaseFast

  // Set incremental compilation to true
  def nativeIncrementalCompilation: T[Boolean] = true

  // Set nativeLinkingOptions path to a directory named `target`.
  def nativeLinkingOptions = Seq("-L" + millSourcePath.toString + "/target")

  // Set nativeWorkdir directory to `newDir`
  def nativeWorkdir = T.dest / "newDir"
}

This example shows some of the common tasks you may want to override on a ScalaNativeModule: specifying the releaseMode, nativeIncrementalCompilation, `nativeLinkingOptions and nativeWorkdir.

> ./mill run
...
Value: <h1>hello</h1>

> ./mill show releaseMode
"mill.scalanativelib.api.ReleaseMode.ReleaseFast"

> ./mill show nativeIncrementalCompilation
true

> ./mill show nativeLinkingOptions
...

> ./mill show nativeWorkdir
...