SN bindgen

Scala 3 Native binding generator to C libraries

Quickest way to start would be to use the template.

For more involved examples, see a dedicated repository

Pre-requisites

The bindgen's binary is dynamically linked against LLVM 17.

Therefore you must have it installed on the system that generates the code, you can follow Scala Native's instructions.

The location of installed LLVM is baked into the binary and is:

  • /usr/lib/llvm-17/... on Linux
  • /opt/homebrew/opt/llvm@17/... on ARM MacOS
  • /usr/local/opt/llvm@17/... on Intel MacOS

Code generated by bindgen has no runtime dependencies on bindgen, LLVM, or Clang.

Supported platforms

OS Architecture Tested on CI Binary on Maven Binary on Github releases
Linux x64
Mac OS X x64
Mac OS X ARM64 (Apple silicon)
Windows x64
Linux arm64
Windows arm64

Installation

CLI

  • You can download binaries for all supported platforms on Github releases
  • You can install the binary via Coursier: cs install sn-bindgen --contrib
  • You can install the binary (and required LLVM version) using Homebrew: brew install indoorvivants/tap/sn-bindgen

SBT plugin (stable)

project/plugins.sbt

addSbtPlugin("com.indoorvivants" % "bindgen-sbt-plugin" % "0.1.2")

SBT plugin (latest)

project/plugins.sbt

resolvers += Resolver.sonatypeRepo("snapshots")
addSbtPlugin("com.indoorvivants" % "bindgen-sbt-plugin" % "0.1.2+3-2d791cf0-SNAPSHOT")

Usage

This covers usage as part of an SBT project

build.sbt

// only add this line if you're living on the edge and using 
// a version that has "SNAPSHOT" in it
resolvers += Resolver.sonatypeRepo("snapshots")
scalaVersion := "3.2.2"

enablePlugins(ScalaNativePlugin, BindgenPlugin)

import bindgen.interface.Binding

bindgenBindings := Seq(
  Binding
    .builder(
      /* 1 */  (Compile / resourceDirectory).value / "scala-native" / "header.h",
      /* 2 */  "libtest"
    )
    .addCImport("header.h") /* 3 */
    .build
  )
)
  1. Path to the header file - in this example we're putting it into a location recognised by Scala Native's SBT plugin, any sources there will be linked alongside the Scala sources
  2. Package name where generated definitions will be put
  3. List of imports that will be added to generated C files (if there are any, see Semantics page for details)

There are more settings available, see Configuration page for details.

Now all that is left to do is put some definitions into the header file:

Source C code


    typedef enum { X = 1, Y = 2} bla;
    void sum(bla one, bla two);
    

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.scalanative.*

object predef:
  private[libtest] trait CEnumU[T](using eq: T =:= UInt):
    given Tag[T] = Tag.UInt.asInstanceOf[Tag[T]]
    extension (inline t: T)
     inline def int: CInt = eq.apply(t).toInt
     inline def uint: CUnsignedInt = eq.apply(t)
     inline def value: CUnsignedInt = eq.apply(t)


object enumerations:
  import predef.*
  /**
   * [bindgen] header: /tmp/12905517493933496563header.h
  */
  opaque type bla = CUnsignedInt
  object bla extends CEnumU[bla]:
    given _tag: Tag[bla] = Tag.UInt
    inline def define(inline a: Long): bla = a.toUInt
    val X = define(1)
    val Y = define(2)
    inline def getName(inline value: bla): Option[String] =
      inline value match
        case X => Some("X")
        case Y => Some("Y")
        case _ => None
    extension (a: bla)
      inline def &(b: bla): bla = a & b
      inline def |(b: bla): bla = a | b
      inline def is(b: bla): Boolean = (a & b) == b


@extern
private[libtest] object extern_functions:
  import _root_.libtest.enumerations.*
  import _root_.libtest.predef.*
  /**
   * [bindgen] header: /tmp/12905517493933496563header.h
  */
  def sum(one : bla, two : bla): Unit = extern


object functions:
  import _root_.libtest.enumerations.*
  import _root_.libtest.predef.*
  import extern_functions.*
  export extern_functions.*

object types:
  export _root_.libtest.enumerations.*

object all:
  export _root_.libtest.enumerations.bla
  export _root_.libtest.functions.sum


After that in your Scala sources you can use libtest package to access the generated bindings.