Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Scala Native Examples #3657

Merged
merged 54 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3346097
Create Foo.scala
c0d33ngr Oct 3, 2024
52a38c4
Add files via upload
c0d33ngr Oct 3, 2024
367bcb0
import scala native lib properly
c0d33ngr Oct 3, 2024
a48faac
Update Foo.scala
c0d33ngr Oct 3, 2024
7df4367
Create FooTests.scala
c0d33ngr Oct 3, 2024
cee766e
Update build.mill
c0d33ngr Oct 3, 2024
ef802d3
Create Hello world.c
c0d33ngr Oct 3, 2024
11a6e75
Create HelloWorld.scala
c0d33ngr Oct 3, 2024
2783005
Rename Hello world.c to HelloWorld.c
c0d33ngr Oct 3, 2024
24b9543
update interop example
c0d33ngr Oct 3, 2024
b1015e7
Create build.mill
c0d33ngr Oct 3, 2024
8740b01
Create Makefile
c0d33ngr Oct 3, 2024
19ed282
Update build.mill of interop example
c0d33ngr Oct 3, 2024
2c9d4ec
add some example files
c0d33ngr Oct 4, 2024
61f53b7
add changes to 2-interop example
c0d33ngr Oct 5, 2024
9dc9053
update scala-native examples code
c0d33ngr Oct 5, 2024
4f3a61b
update scala=native example code
c0d33ngr Oct 5, 2024
dfb0e5e
Create draft build.mill for multi-module example
c0d33ngr Oct 5, 2024
bd1f756
Create MyResource.txt
c0d33ngr Oct 5, 2024
e41d609
Create MyOtherResource.txt
c0d33ngr Oct 5, 2024
69ffd7d
add common-config code
c0d33ngr Oct 6, 2024
7cada73
add doc file for scala-native examples
c0d33ngr Oct 6, 2024
bf2edb2
add draft build.mill for common-config example
c0d33ngr Oct 6, 2024
8dc0853
update 1-simple example code
c0d33ngr Oct 6, 2024
a367ead
fix the errors in 1-simple and 2-interop examples
c0d33ngr Oct 7, 2024
55c143f
add third party dependency to 1-simple
c0d33ngr Oct 8, 2024
c37e9a0
update code to fix some errors
c0d33ngr Oct 9, 2024
24729ea
updated code examples
c0d33ngr Oct 12, 2024
f047193
fix syntax in 1-simple example build.mill file
c0d33ngr Oct 12, 2024
3919933
update code
c0d33ngr Oct 12, 2024
950440a
use task in build.mill
c0d33ngr Oct 17, 2024
dda37bf
add pathref in build.mill
c0d33ngr Oct 17, 2024
ca18f5f
update 4-common-config to scala native specific configs
c0d33ngr Oct 18, 2024
7cbf92e
update the build.mill files
c0d33ngr Oct 20, 2024
82ed7c0
rework the scala native examples
c0d33ngr Oct 21, 2024
ba260e5
add 1-simple to testsuite
c0d33ngr Oct 23, 2024
aed36a5
add 4-common-config to testsuite
c0d33ngr Oct 23, 2024
d98e8c2
add 2-interop to testsuit
c0d33ngr Oct 23, 2024
45fb020
update 1-simple/build.mill to pass CI tests
c0d33ngr Oct 23, 2024
1c5e946
update code examples
c0d33ngr Oct 27, 2024
8659821
fix CI error for 1-simple test
c0d33ngr Oct 27, 2024
6b5b1bf
fix CI error for 3-multi-module test
c0d33ngr Oct 27, 2024
956f223
fix CI test error
c0d33ngr Oct 27, 2024
f2c0e9e
complete multi-module example
c0d33ngr Oct 28, 2024
5768cbd
header file for foo in multi-module
c0d33ngr Oct 28, 2024
7e312b2
remove cause of failing CI tests
c0d33ngr Oct 28, 2024
8d3e300
update build.mill for native examples
c0d33ngr Oct 29, 2024
495c44f
update build.mill for 2-interop and 3-multi-module example
c0d33ngr Oct 29, 2024
4501c2e
Update 1-simple build.mill
c0d33ngr Oct 30, 2024
9242768
update code
c0d33ngr Oct 30, 2024
76bb361
update 1-simple build file
c0d33ngr Oct 31, 2024
a599da6
fix merge conflict
c0d33ngr Oct 31, 2024
9ac2cc1
Merge branch 'main' into add-scalanative-doc
c0d33ngr Oct 31, 2024
084dfb4
add some changes
c0d33ngr Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>

c0d33ngr marked this conversation as resolved.
Show resolved Hide resolved
*/
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't cResult escape here the Zone where it was allocated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright... Noted

I'm into it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1-simple/src/Foo.scala

object Foo {
  
  def generateHtml(text: String) (using Zone) = {
    val html = "<h1>" + text + "</h1>\n"

    val cResult = toCString(html)
    cResult
  
  }

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

3-multi-module/foo/src/Foo.scala

...
object Foo {
  @main
  def main(@arg(name = "foo-text") fooText: String,
           @arg(name = "bar-text") barText: String): Unit = Zone {

    val cFooText = toCString(fooText)
    val cBarText = toCString(barText)

...

3-multi-module/bar/src/Bar.scala

...
object Bar {
  def main(args: Array[String]): Unit = Zone {
    println("Running HelloWorld function")
    val result = toCString(args(0))
    val barValue = HelloWorldBar.stringLength(result)
...

4-common-config/src/Foo.scala

...
object Foo {

  def generateHtml(text: String) (using Zone) = {
    val colored = Console.RED + "<h1>" + text + "</h1>" + Console.RESET

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

Hello @sideeffffect Here's is the updated code. Are they good to go?
Also please do a review on the scala-native codes I wrote just in case there are some changes needed to be made. It came to my notice that doc should be as good it can be cause future readers would depend on it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@c0d33ngr please open a new PR with these changes, it's easier to discuss individual code changes on the PR, and when we're done we can just click the merge button :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright..


}

@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
Loading