diff --git a/docs/modules/ROOT/pages/scalalib/native-examples.adoc b/docs/modules/ROOT/pages/scalalib/native-examples.adoc new file mode 100644 index 00000000000..90d11b01714 --- /dev/null +++ b/docs/modules/ROOT/pages/scalalib/native-examples.adoc @@ -0,0 +1,30 @@ += Scala Native Examples +:page-aliases: Scala_Native_Examples.adoc + +++++ + +++++ + + +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 + +include::partial$example/scalalib/native/1-simple.adoc[] + +== Interop + +include::partial$example/scalalib/native/2-interop.adoc[] + +== Multi-Module + +include::partial$example/scalalib/native/3-multi-module.adoc[] + +== Common Config + +include::partial$example/scalalib/native/4-common-config.adoc[] + diff --git a/example/package.mill b/example/package.mill index 9544d316c76..4b45f096411 100644 --- a/example/package.mill +++ b/example/package.mill @@ -53,6 +53,7 @@ object `package` extends RootModule with Module { object publishing extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "publishing")) object module extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "module")) object web extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "web")) + object native extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "native")) } object depth extends Module { diff --git a/example/scalalib/native/1-simple/build.mill b/example/scalalib/native/1-simple/build.mill new file mode 100644 index 00000000000..6dc53fe97ca --- /dev/null +++ b/example/scalalib/native/1-simple/build.mill @@ -0,0 +1,56 @@ +//// SNIPPET:BUILD +package build +import mill._, scalalib._, scalanativelib._ + +object `package` extends RootModule with ScalaNativeModule { + def scalaVersion = "2.13.11" + 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 { + def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.8.4") + def testFramework = "utest.runner.Framework" + } +} +//// SNIPPET:END +// +// +//// SNIPPET:DEPENDENCIES +// +// +/** Usage + + +> ./mill nativeLink # Build and link native binary +... +Linking native code (immix gc, none lto) ... + +> ./mill releaseMode # Generate some files in `out` directory about the `releaseMode`. Default releaseMode is set to Debug. + +> ./mill nativeOptimize # Generate some files in `out` directory about native optimization. The Default value is true. + +> ./mill nativeIncrementalCompilation # Generate config files in `out` directory about native incremental compilation. Default is false. + +> ./mill nativeLinkingOptions # Generate config files in `out` directory about the path in native linking options. Default path is `"-L/usr/local/lib`. + + +*/ + +//// SNIPPET:END + +// +// You can run `+mill resolve __+` to see a full list of the different tasks that +// are available, `+mill resolve _+` to see the tasks within `foo`, +// `mill inspect compile` to inspect a task's doc-comment documentation or what +// it depends on, or `mill show foo.scalaVersion` to show the output of any task. +// +// The most common *tasks* that Mill can run are cached *targets*, such as +// `compile`, and un-cached *commands* such as `foo.run`. Targets do not +// re-evaluate unless one of their inputs changes, whereas commands re-run every +// time. + + diff --git a/example/scalalib/native/1-simple/src/Foo.scala b/example/scalalib/native/1-simple/src/Foo.scala new file mode 100644 index 00000000000..a692bed07a1 --- /dev/null +++ b/example/scalalib/native/1-simple/src/Foo.scala @@ -0,0 +1,25 @@ +package foo + +import scala.scalanative.libc._ +import scala.scalanative.unsafe._ +import mainargs.{main, ParserForMethods} + +object Foo { + + def generateHtml(text: String): CString = { + val html = "

" + text + "

\n" + + implicit val z: Zone = Zone.open + val cResult = toCString(html) + z.close() + cResult + } + + @main + def main(text: String) = { + stdio.printf(generateHtml(text)) + } + + def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args) +} + diff --git a/example/scalalib/native/1-simple/test/src/FooTests.scala b/example/scalalib/native/1-simple/test/src/FooTests.scala new file mode 100644 index 00000000000..33ce63fc225 --- /dev/null +++ b/example/scalalib/native/1-simple/test/src/FooTests.scala @@ -0,0 +1,20 @@ +package foo + +import scala.scalanative.unsafe._ +import utest._ + +object FooTests extends TestSuite { + def tests = Tests { + test("simple one") { + val result = Foo.generateHtml("hello") + assert(fromCString(result) == "

hello

\n") + fromCString(result) + } + test("simple two") { + val result = Foo.generateHtml("hello world") + assert(fromCString(result) == "

hello world

\n") + fromCString(result) + } + } +} + diff --git a/example/scalalib/native/2-interop/build.mill b/example/scalalib/native/2-interop/build.mill new file mode 100644 index 00000000000..97baedc4a14 --- /dev/null +++ b/example/scalalib/native/2-interop/build.mill @@ -0,0 +1,76 @@ +package build +import mill._, scalalib._, scalanativelib._ + +object `package` extends RootModule with ScalaNativeModule { + def scalaVersion = "2.13.11" + def scalaNativeVersion = "0.5.5" + + object test extends ScalaNativeTests { + def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.8.4") + def testFramework = "utest.runner.Framework" + } + + def nativeLinkingOptions = Seq("-L" + millSourcePath.toString + "/target") + + // Compiled C + def nativeCompiled = T { + os.makeDir.all(millSourcePath / "target") + + os.proc("gcc", "-m64", "-shared", + "-c", millSourcePath.toString + "/native-src/HelloWorld.c", + "-o", millSourcePath.toString + "/target/libHelloWorld.so" + ).call(stdout = os.Inherit) + + PathRef(T.dest / "target/libHelloWorld.so") + } +} + + +// 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 +// +// native-src/ +// HelloWorld.c +// +// test/ +// src/ +// foo/ +// HelloWorldTests.scala +// ---- +// +// This example is pretty minimal, but it demonstrates the core principles, and +// can be extended if necessary to more elaborate use cases. The `native*` tasks +// can also be extracted out into a `trait1 for re-use if you have multiple +// `ScalaNativeModule`s that need native C components. + +/** Usage + +> ./mill nativeCompiled +... +#1 [info] done compiling +#1 [1/1] nativeCompiled + +> ./mill run +... +#2 [info] Compiling to native code (4749 ms) +#2 [info] Linking with [pthread, dl, HelloWorld] +#2 [info] Linking native code (immix gc, none lto) (117 ms) +#2 [info] Postprocessing (0 ms) +#2 [info] Total (11344 ms) +[90/1] run +Running HelloWorld function +Done... +Reversed: !dlroW ,olle + +> ./mill test + + +*/ diff --git a/example/scalalib/native/2-interop/native-src/HelloWorld.c b/example/scalalib/native/2-interop/native-src/HelloWorld.c new file mode 100644 index 00000000000..e5240b7d041 --- /dev/null +++ b/example/scalalib/native/2-interop/native-src/HelloWorld.c @@ -0,0 +1,15 @@ +#include +#include +#include + +char* reverseString(const char *str) { + int length = strlen(str); + char *reversed = (char*) malloc((length + 1) * sizeof(char)); + + for (int i = 0; i < length; i++) { + reversed[i] = str[length - i - 1]; + } + reversed[length] = '\0'; // Null-terminate the string + + return reversed; +} diff --git a/example/scalalib/native/2-interop/src/foo/HelloWorld.scala b/example/scalalib/native/2-interop/src/foo/HelloWorld.scala new file mode 100644 index 00000000000..55bf43573bd --- /dev/null +++ b/example/scalalib/native/2-interop/src/foo/HelloWorld.scala @@ -0,0 +1,21 @@ +package foo +import scala.scalanative.libc._ +import scala.scalanative.unsafe._ + +object Main { + def main(args: Array[String]): Unit = { + println("Running HelloWorld function") + stdio.printf(c"Reversed: %s\n", HelloWorld.reverseString(c"Hello, World!")) + println("Done...") + } +} + +// Define the external module, the C library containing our function "reverseString" +@extern +@link("HelloWorld") +// Arbitrary object name +object HelloWorld { + // Name and signature of C function + def reverseString(str: CString): CString = extern +} + diff --git a/example/scalalib/native/2-interop/test/src/HelloWorldTests.scala b/example/scalalib/native/2-interop/test/src/HelloWorldTests.scala new file mode 100644 index 00000000000..e88bda99693 --- /dev/null +++ b/example/scalalib/native/2-interop/test/src/HelloWorldTests.scala @@ -0,0 +1,19 @@ +package foo + +import utest._ +import scala.scalanative.unsafe._ + +object HelloWorldTest extends TestSuite { + val tests = Tests { + test("reverseString should reverse a C string correctly") { + val expected = "!dlrow olleH" + + val result = HelloWorld.reverseString(c"Hello World!") + + // Check if the reversed string matches the expected result + assert(fromCString(result) == expected) + fromCString(result) + } + } +} + diff --git a/example/scalalib/native/3-multi-module/bar/native-src/HelloWorldBar.c b/example/scalalib/native/3-multi-module/bar/native-src/HelloWorldBar.c new file mode 100644 index 00000000000..1a1d142c369 --- /dev/null +++ b/example/scalalib/native/3-multi-module/bar/native-src/HelloWorldBar.c @@ -0,0 +1,8 @@ +#include +#include "bar.h" + +// Function to count the length of a string +int stringLength(const char* str) { + return strlen(str); +} + diff --git a/example/scalalib/native/3-multi-module/bar/native-src/bar.h b/example/scalalib/native/3-multi-module/bar/native-src/bar.h new file mode 100644 index 00000000000..d683bb4d09a --- /dev/null +++ b/example/scalalib/native/3-multi-module/bar/native-src/bar.h @@ -0,0 +1,7 @@ +#ifndef BAR_H +#define BAR_H + +// Declaration of the function to count string length +int stringLength(const char* str); + +#endif diff --git a/example/scalalib/native/3-multi-module/bar/src/Bar.scala b/example/scalalib/native/3-multi-module/bar/src/Bar.scala new file mode 100644 index 00000000000..2d6155e0b0f --- /dev/null +++ b/example/scalalib/native/3-multi-module/bar/src/Bar.scala @@ -0,0 +1,26 @@ +package bar + +import scala.scalanative.libc._ +import scala.scalanative.unsafe._ + +object Bar { + def main(args: Array[String]): Unit = { + println("Running HelloWorld function") + implicit val z: Zone = Zone.open + val result = toCString(args(0)) + val barValue = HelloWorldBar.stringLength(result) + z.close() + stdio.printf(c"Bar value: Argument length is %i\n", barValue) + println("Done...") + } +} + +// Define the external module, the C library containing our function "generateHtml" +@extern +@link("HelloWorldBar") +// Arbitrary object name +object HelloWorldBar { + // Name and signature of C function + def stringLength(str: CString): CInt = extern +} + diff --git a/example/scalalib/native/3-multi-module/bar/test/src/BarTests.scala b/example/scalalib/native/3-multi-module/bar/test/src/BarTests.scala new file mode 100644 index 00000000000..cb5585b99e2 --- /dev/null +++ b/example/scalalib/native/3-multi-module/bar/test/src/BarTests.scala @@ -0,0 +1,15 @@ +package bar + +import utest._ +import scala.scalanative.unsafe._ + +object BarTests extends TestSuite { + def tests = Tests { + test("simple one") { + val result = HelloWorldBar.stringLength(c"hello") + assert(result == 5) + result + } + } +} + diff --git a/example/scalalib/native/3-multi-module/build.mill b/example/scalalib/native/3-multi-module/build.mill new file mode 100644 index 00000000000..7f6c48fa7df --- /dev/null +++ b/example/scalalib/native/3-multi-module/build.mill @@ -0,0 +1,91 @@ +//// SNIPPET:BUILD +package build +import mill._, scalalib._, scalanativelib._ + +trait MyModule extends ScalaNativeModule { + def scalaVersion = "2.13.11" + def scalaNativeVersion = "0.5.5" + + object test extends ScalaNativeTests { + def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.8.4") + def testFramework = "utest.runner.Framework" + } + + def nativeLinkingOptions = Seq("-L" + millSourcePath.toString + "/target") +} + +object foo extends MyModule { + def moduleDeps = Seq(bar) + + def ivyDeps = Agg(ivy"com.lihaoyi::mainargs::0.7.6") + + // Compile C + def nativeCompiled = T { + os.makeDir.all(millSourcePath / "target") + + os.proc( + "gcc", "-m64", "-shared", "-fPIC", + "-I", "/workspaces/milling-two/sunday-work/mill/example/scalalib/native/3-multi/bar/native-src", + millSourcePath.toString + "/native-src/HelloWorldFoo.c", + "-o", millSourcePath.toString + "/target/libHelloWorldFoo.so") + .call(stdout = os.Inherit) + + PathRef(T.dest / "target/libHelloWorldFoo.so") + } +} + +object bar extends MyModule { + + // Compile C + def nativeCompiled = T { + os.makeDir.all(millSourcePath / "target") + + os.proc( + "gcc", "-m64", "-shared", "-fPIC", + millSourcePath.toString + "/native-src/HelloWorldBar.c", + "-o", millSourcePath.toString + "/target/libHelloWorldBar.so") + .call(stdout = os.Inherit) + + PathRef(T.dest / "target/libHelloWorldBar.so") + } + +} +//// SNIPPET:END + +// 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: +// +//// SNIPPET:TREE +// +// ---- +// build.mill +// bar/ +// native-src/ +// bar.h +// HelloWorldBar.c +// src/ +// Bar.scala +// test/ +// src/ +// BarTests.scala +// foo/ +// native-src/ +// HelloWorldFoo.c +// src/ +// Foo.scala +// +// ---- +// +//// SNIPPET:END + + diff --git a/example/scalalib/native/3-multi-module/foo/native-src/HelloWorldFoo.c b/example/scalalib/native/3-multi-module/foo/native-src/HelloWorldFoo.c new file mode 100644 index 00000000000..e8b9da8d2a1 --- /dev/null +++ b/example/scalalib/native/3-multi-module/foo/native-src/HelloWorldFoo.c @@ -0,0 +1,24 @@ +#include +#include "bar.h" + +// Function to count vowels in a string +int vowelCount(const char* str) { + int count = 0; + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || + c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') { + count++; + } + } + return count; +} + +// Function in foo that uses bar's string length and foo's vowel count functions +int vowelDensity(const char* str) { + int length = stringLength(str); // Call bar's function for string length + int vowels = vowelCount(str); // Call foo's own function for vowel count + + return (length > 0) ? (vowels * 100) / length : 0; // Return vowel density as percentage +} + diff --git a/example/scalalib/native/3-multi-module/foo/src/Foo.scala b/example/scalalib/native/3-multi-module/foo/src/Foo.scala new file mode 100644 index 00000000000..564c3418085 --- /dev/null +++ b/example/scalalib/native/3-multi-module/foo/src/Foo.scala @@ -0,0 +1,31 @@ +package foo + +import scala.scalanative.libc._ +import scala.scalanative.unsafe._ +import mainargs.{main, ParserForMethods, arg} + +object Foo { + @main + def main(@arg(name = "foo-text") fooText: String, + @arg(name = "bar-text") barText: String): Unit = { + + implicit val z: Zone = Zone.open + val cFooText = toCString(fooText) + val cBarText = toCString(barText) + z.close() + + stdio.printf(c"Foo.value: The vowel density of '%s' is %d\n", cFooText, HelloWorldFoo.vowelDensity(cFooText)) + stdio.printf(c"Bar.value: The string length of '%s' is %d\n", cBarText, bar.HelloWorldBar.stringLength(cBarText)) + } + + def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args) +} + +@extern +@link("HelloWorldFoo") +// Arbitrary object name +object HelloWorldFoo { + // Name and signature of C function + def vowelDensity(str: CString): CInt = extern +} + diff --git a/example/scalalib/native/4-common-config/build.mill b/example/scalalib/native/4-common-config/build.mill new file mode 100644 index 00000000000..01656860367 --- /dev/null +++ b/example/scalalib/native/4-common-config/build.mill @@ -0,0 +1,59 @@ +// This example shows some of the common tasks you may want to override on a +// `ScalaNativeModule`: specifying the `releaseMode`, `nativeLinkStubs` +// `nativeIncrementalCompilation, `nativeLinkingOptions` and `nativeWorkdir`. + + +//// SNIPPET:BUILD +package build +import mill._, scalalib._, scalanativelib._, scalanativelib.api._ + +object `package` extends RootModule with ScalaNativeModule { + def scalaVersion = "2.13.11" + def scalaNativeVersion = "0.5.5" + + // You can have arbitrary numbers of third-party dependencies + def ivyDeps = Agg( + ivy"com.lihaoyi::fansi::0.5.0" + ) + + // Set the releaseMode to ReleaseFast. + def releaseMode: T[ReleaseMode] = T { ReleaseMode.ReleaseFast } + + // Set linking of `@stub` methods to true. + def nativeLinkStubs: T[Boolean] = T { true } + + // Set incremental compilation to true + def nativeIncrementalCompilation: T[Boolean] = T { true } + + // Set nativeLinkingOptions path to a directory named `target`. + def nativeLinkingOptions = Seq("-L" + millSourcePath.toString + "/target") + + // Set nativeWorkdir directory to `newDir` + def nativeWorkdir = T { T.dest / "newDir" } +} +//// SNIPPET:END + +// + +/** Usage + +> ./mill run +... +Foo.value:

hello

+ +> ./mill show releaseMode +"mill.scalanativelib.api.ReleaseMode.ReleaseFast" + +> ./mill show nativeLinkStubs +true + +> ./mill show nativeIncrementalCompilation +true + +> ./mill show nativeLinkingOptions +... + +> ./mill show nativeWorkdir +... +*/ + diff --git a/example/scalalib/native/4-common-config/src/Foo.scala b/example/scalalib/native/4-common-config/src/Foo.scala new file mode 100644 index 00000000000..05b82b0f561 --- /dev/null +++ b/example/scalalib/native/4-common-config/src/Foo.scala @@ -0,0 +1,24 @@ +package foo + +import scala.scalanative.libc._ +import scala.scalanative.unsafe._ +import fansi._ + +object Foo { + + def generateHtml(text: String): CString = { + val colored = Console.RED + "

" + text + "

" + Console.RESET + + implicit val z: Zone = Zone.open + val cResult = toCString(colored) + z.close() + cResult + } + + val value = generateHtml("hello") + + def main(args: Array[String]): Unit = { + stdio.printf(c"Foo.value: %s\n", Foo.value) + } +} +