Skip to content

Programming-Rivers/rules_scala_native

rules_scala_native

Bazel rulesets for building Scala Native applications.

This project provides two main rules:

  • scala_native_library — compiles Scala sources to class files, tasty files, and NIR (Native Intermediate Representation).
  • scala_native_binary — links NIR files into a native executable using a hermetic C++ toolchain (clang/lld).
  • scala_native_test — a test rule for running Scala Native JUnit tests.

Requirements

  • Bazel 9+ (bzlmod only, no WORKSPACE support)
  • Scala 3.8.1
  • Scala Native 0.5.10
  • Linux, macOS, Windows (x86_64, aarch64)

Quick Start

1. Configure MODULE.bazel

module(name = "my_scala_native_app")

bazel_dep(name = "protobuf", version = "33.4")
bazel_dep(name = "rules_scala", version = "7.2.2")
bazel_dep(name = "rules_scala_native", version = "0.1.0")
bazel_dep(name = "llvm", version = "0.5.4")

# Register hermetic C++ toolchain (clang/lld) for cross-compilation
register_toolchains(
    "@llvm//toolchain:all",
)

# Configure Scala
scala_config = use_extension(
    "@rules_scala//scala/extensions:config.bzl",
    "scala_config",
)
scala_config.settings(scala_version = "3.8.1")
use_repo(scala_config, "rules_scala_config")

scala_deps = use_extension(
    "@rules_scala//scala/extensions:deps.bzl",
    "scala_deps",
)
scala_deps.scala()
use_repo(scala_deps, "rules_scala_toolchains")

register_toolchains(
    "@rules_scala_toolchains//...:all",
)

2. Configure .bazelrc

common --tool_java_runtime_version=remotejdk_17
common --java_runtime_version=remotejdk_17
common --@protobuf//bazel/toolchains:prefer_prebuilt_protoc

3. Write Scala Native code

// HelloNative.scala
package examples.native

@main
def sayHello(name: String): Unit =
    println(s"Hello from Scala Native to $name")
// NativeTest.scala
package examples.native

import org.junit.Test
import org.junit.Assert.*

class NativeTest:
  @Test
  def testMath(): Unit =
    assertEquals("Basic arithmetic should work", 4, 2 + 2)

4. Define build targets

load("@rules_scala_native//scala_native:scala_native_library.bzl", "scala_native_library")
load("@rules_scala_native//scala_native:scala_native_binary.bzl", "scala_native_binary")
load("@rules_scala_native//scala_native:scala_native_test.bzl", "scala_native_test")

scala_native_library(
    name = "hello_native",
    srcs = ["HelloNative.scala"],
)

scala_native_binary(
    name = "hello_native_bin",
    main_class = "examples.native.sayHello",
    deps = [":hello_native"],
)

scala_native_test(
    name = "hello_native_test",
    srcs = ["NativeTest.scala"],
    deps = [":hello_native"],
    suites = ["examples.native.NativeTest"],   # test suites must be explicitly listed
)

Warning

The test rule requires the suites to be listed explicitly. This is a known issue and is being worked on.

5. Build and run

$ bazel run //:hello_native_bin -- "the World!"

It should print this output:

Hello from Scala Native to the World
Hello from a C function!

To run tests:

$ bazel test //:hello_native_test

Cross-Compilation Support

Cross-compilation is as easy as passing the --platforms flag to Bazel:

 bazel build //... --platforms=@llvm//platforms:linux_aarch64

rules_scala_native uses a hermetic C++ toolchain based on LLVM/Clang to cross-compile Scala Native code.

Support Matrix

Platform Architectures C Library / Toolchain Build Execution
Linux aarch64 glibc (2.28—2.42), musl ✅ Succeeded ❓ Not tested
Linux x86_64 glibc (2.28—2.42), musl ✅ Succeeded ✅ Succeeded
macOS aarch64 Native Apple SDK ✅ Succeeded ✅ Succeeded
macOS x86_64 Native Apple SDK ✅ Succeeded ❓ Not tested
Windows aarch64 - ✅ Succeeded ❓ Not tested
Windows x86_64 - ✅ Succeeded ❓ Not tested

There is no plan to support WebAssembly targets.

Note

Cross-compilation has been verified from a Linux x86_64 host to a total of 72 targets, including multiple architectures, musl, and glibc versions 2.28 through 2.42.

Interoperability with Other Languages

rules_scala_native makes it easy to interoperate with other native languages by leveraging Bazel's dependency system. Since scala_native_binary links against the hermetic C++ toolchain, it can seamlessly link with static libraries produced by cc_library, rust_static_library, zig_static_library, and more.

C Interop

You can link directly with C code using cc_library.

C Code:

// math_utils.c
int add(int a, int b) {
    return a + b;
}

Scala Definition:

@extern
object MathUtils:
    def add(a: CInt, b: CInt): CInt = extern

BUILD.bazel:

cc_library(
    name = "math_utils",
    srcs = ["math_utils.c"],
)

scala_native_binary(
    name = "app",
    deps = [":math_utils", ...],
    ...
)

Rust Interop

Interoperate with Rust by using rules_rust to create a rust_static_library with extern "C" functions.

Rust Code:

#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
    a + b
}

Scala Definition:

@extern
object RustLib:
    @name("rust_add")
    def add(a: CInt, b: CInt): CInt = extern

BUILD.bazel:

rust_static_library(
    name = "rust_lib",
    srcs = ["lib.rs"],
)

scala_native_binary(
    name = "app",
    deps = [":rust_lib", ...],
    ...
)

Zig Interop

Similarly, you can use rules_zig to link with Zig code.

Zig Code:

export fn zig_add(a: i32, b: i32) i32 {
    return a + b;
}

Scala Definition:

@extern
object ZigLib:
    @name("zig_add")
    def add(a: CInt, b: CInt): CInt = extern

BUILD.bazel:

zig_static_library(
    name = "zig_lib",
    main = "lib.zig",
)

scala_native_binary(
    name = "app",
    deps = [":zig_lib", ...],
    ...
)

C++ Interop

C++ interop is supported by wrapping C++ functionality in extern "C" blocks to ensure stable ABI linkage.

C++ Code:

// greeter.cpp
#include <iostream>
#include <string>

class Greeter {
public:
    Greeter(const std::string& name) : name_(name) {}
    void greet() { std::cout << "Hello, " << name_ << "!" << std::endl; }
private:
    std::string name_;
};

extern "C" {
    void* greeter_new(const char* name) { return new Greeter(name); }
    void greeter_greet(void* g) { static_cast<Greeter*>(g)->greet(); }
    void greeter_delete(void* g) { delete static_cast<Greeter*>(g); }
}

Scala Definition:

@extern
object CppGreeter:
    def greeter_new(name: CString): Ptr[Byte] = extern
    def greeter_greet(greeter: Ptr[Byte]): Unit = extern
    def greeter_delete(greeter: Ptr[Byte]): Unit = extern

BUILD.bazel:

cc_library(
    name = "cpp_greeter",
    srcs = ["greeter.cpp"],
)

scala_native_binary(
    name = "app",
    deps = [":cpp_greeter", ...],
    ...
)

Architecture

Build Pipeline

```mermaid
graph LR
    Scala_Sources[scala sources] --> scalac
    nscplugin --> scalac
    scalac --> class_files[".class files"]
    scalac --> nir_files[".nir files"]
    class_files --> scala_native_linker[Scala native linker]
    nir_files --> scala_native_linker
    scala_native_linker --> LLVM
    LLVM --> native_binary[native binary]

Key Components

Component Description
scala_native_toolchain Manages Scala Native dependencies (nscplugin, runtime libs, linker)
scala_native_library Injects the nscplugin compiler plugin for NIR generation
scala_native_test Runs Scala Native JUnit tests
NativeLinker Bridges the Scala Native build API with Bazel's action graph

Dependencies

This project depends on:

  • rules_scala — for Scala compilation infrastructure
  • rules_cc — for C++ toolchain access
  • llvm — for hermetic clang/lld

Current Limitations

  • Only Scala 3.8.1 is supported
  • Only Scala Native 0.5.10 is supported
  • Windows and WebAssembly support is currently work-in-progress.

Related

License

See LICENSE.txt.