Skip to content

Commit

Permalink
Add Scala Native Examples (#3657)
Browse files Browse the repository at this point in the history
This is a PR to close issue #3647
  • Loading branch information
c0d33ngr authored Oct 31, 2024
1 parent d19ed5a commit cddb8f0
Show file tree
Hide file tree
Showing 20 changed files with 495 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
** xref:scalalib/publishing.adoc[]
** xref:scalalib/build-examples.adoc[]
** xref:scalalib/web-examples.adoc[]
** xref:scalalib/native-examples.adoc[]
* xref:kotlinlib/intro.adoc[]
** xref:kotlinlib/builtin-commands.adoc[]
** xref:kotlinlib/module-config.adoc[]
Expand Down
30 changes: 30 additions & 0 deletions docs/modules/ROOT/pages/scalalib/native-examples.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
= Scala Native Examples
:page-aliases: Scala_Native_Examples.adoc

++++
<script>
gtag('config', 'AW-16649289906');
</script>
++++


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[]

1 change: 1 addition & 0 deletions example/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ object `package` extends RootModule with Module {
object linting extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "linting"))
object publishing extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "publishing"))
object web extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "web"))
object native extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "native"))
}

object fundamentals extends Module {
Expand Down
34 changes: 34 additions & 0 deletions example/scalalib/native/1-simple/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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.

/** Usage

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

*/
25 changes: 25 additions & 0 deletions example/scalalib/native/1-simple/src/Foo.scala
Original file line number Diff line number Diff line change
@@ -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 = "<h1>" + text + "</h1>\n"

implicit val z: Zone = Zone.open()
val cResult = toCString(html)
cResult

}

@main
def main(text: String) = {
stdio.printf(generateHtml(text))
}

def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
}

20 changes: 20 additions & 0 deletions example/scalalib/native/1-simple/test/src/FooTests.scala
Original file line number Diff line number Diff line change
@@ -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) == "<h1>hello</h1>\n")
fromCString(result)
}
test("simple two") {
val result = Foo.generateHtml("hello world")
assert(fromCString(result) == "<h1>hello world</h1>\n")
fromCString(result)
}
}
}

56 changes: 56 additions & 0 deletions example/scalalib/native/2-interop/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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
// https://scala-native.org/en/stable/user/native.html#using-libraries-with-native-code[here]
// and also Scala user forum https://users.scala-lang.org/t/how-to-test-scala-native-code-interop-with-c/10314/3?u=c0d33ngr[here]
//
// This example is pretty minimal, but it demonstrates the core principles, and
// can be extended if necessary to more elaborate use cases.

/** Usage

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

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

*/

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char* reverseString(const char *str) {
int length = strlen(str);
char *reversed = (char*) malloc((length + 1) * sizeof(char)); // +1 for null terminator

if (reversed == NULL) {
return NULL; // handle malloc failure
}

for (int i = 0; i < length; i++) {
reversed[i] = str[length - i - 1];
}
reversed[length] = '\0'; // Null-terminate the string

return reversed;
}

22 changes: 22 additions & 0 deletions example/scalalib/native/2-interop/src/foo/HelloWorld.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package foo

import scala.scalanative.libc._
import scala.scalanative.unsafe._
import scala.scalanative.unsigned._

object Main {
def main(args: Array[String]): Unit = {
println("Running HelloWorld function")
val reversedStr = HelloWorld.reverseString(c"Hello, World!")
println("Reversed: " + fromCString(reversedStr))
stdlib.free(reversedStr) // Free the allocated memory
println("Done...")
}
}

// Define the external module, the C library containing our function "reverseString"
@extern
object HelloWorld {
def reverseString(str: CString): CString = extern
}

20 changes: 20 additions & 0 deletions example/scalalib/native/2-interop/test/src/HelloWorldTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package foo

import utest._
import scala.scalanative.unsafe._
import scala.scalanative.libc.stdlib

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)

stdlib.free(result) // Free memory after the test
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <string.h>
#include "bar.h"

// Function to count the length of a string
int stringLength(const char* str) {
return strlen(str);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef BAR_H
#define BAR_H

// Declaration of the function to count string length
int stringLength(const char* str);

#endif
25 changes: 25 additions & 0 deletions example/scalalib/native/3-multi-module/bar/src/Bar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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)
stdio.printf(c"Bar value: Argument length is %i\n", barValue)
println("Done...")
}
}

// Define the external module, the C library containing our function "stringLength"
@extern
// Arbitrary object name
object HelloWorldBar {
// Name and signature of C function
def stringLength(str: CString): CInt = extern
}


15 changes: 15 additions & 0 deletions example/scalalib/native/3-multi-module/bar/test/src/BarTests.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
}

76 changes: 76 additions & 0 deletions example/scalalib/native/3-multi-module/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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
// https://scala-native.org/en/stable/user/native.html#using-libraries-with-native-code[here]
// and also Scala user forum https://users.scala-lang.org/t/how-to-test-scala-native-code-interop-with-c/10314/3?u=c0d33ngr[here]

/** Usage

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

*/
Loading

0 comments on commit cddb8f0

Please sign in to comment.