1
0
Fork
You've already forked Zanama
0
A Zig to Java FFI bindings generator, inspired by JExtract.
  • Kotlin 45.2%
  • Zig 28.2%
  • Java 26.6%
Find a file
2026年02月10日 20:21:28 +01:00
.idea Initial Commit 2025年07月31日 20:07:49 +02:00
gradle/wrapper build: update gradle from 9.1 to 9.3 2026年02月10日 20:11:53 +01:00
LICENSES fix: Add apache 2.0 license text to repo (gradlew scripts) 2026年02月10日 18:55:44 +01:00
plugin build: update gradle publish plugin 2026年02月10日 20:21:28 +01:00
src Initial Commit 2025年07月31日 20:07:49 +02:00
.gitignore Initial Commit 2025年07月31日 20:07:49 +02:00
build.gradle.kts ci: 0.3.0 2026年02月10日 20:20:56 +01:00
build.zig Initial Commit 2025年07月31日 20:07:49 +02:00
build.zig.zon Initial Commit 2025年07月31日 20:07:49 +02:00
CHANGELOG.md ci: 0.3.0 2026年02月10日 20:20:56 +01:00
gradle.properties Initial Commit 2025年07月31日 20:07:49 +02:00
gradlew build: Update gradle from 8.14 to 9.1 2025年10月17日 02:11:57 +02:00
gradlew.bat build: Update gradle from 8.14 to 9.1 2025年10月17日 02:11:57 +02:00
LICENSE Initial Commit 2025年07月31日 20:07:49 +02:00
README.MD docs: fix typo in readme 2025年08月05日 16:38:10 +02:00
REUSE.toml Initial Commit 2025年07月31日 20:07:49 +02:00
settings.gradle.kts build: update zigbuild from 0.1.1 to 0.3.2 2026年02月10日 20:12:31 +01:00

Zanama

A Zig to Java FFI bindings generator, inspired by JExtract.

Usage

Step 1: Add the gradle plugin to your repo

plugins {
 id("com.falsepattern.zanama") version "ZANAMA_VERSION"
 id("com.falsepattern.zigbuild") version "ZIGBUILD_VERSION" //Optional, but it's highly recommended you use this plugin for compiling zig via gradle.
}

Step 2: Create a zig build gradle task

// The directory where zig will output the generated json and binaries
val zigPrefix = layout.buildDirectory.dir("zig-out")
val zigInstall = tasks.register<ZigBuildTask>("zigInstall") {
 options {
 steps.add("install")
 }
 prefixDirectory = zigPrefix
 clearPrefixDirectory = true
 sourceFiles.from(
 // Any files/directories that are referenced by your zig buildscript, as well as:
 layout.buildDirectory.dir("zanama"),
 )
 //This task provides the Zig side of the bindings generator
 dependsOn("extractZanama")
}

Step 3: Set up the zig build for generating the binaries and the json output

The following is a small example of how the zig build side of zanama can be used. This will require changes based on how your project is actually structured.

build.zig.zon:

.{.dependencies=.{.zanama=.{.path="build/zanama"},//This path is generated by the extractZanama gradle task.},}

build.zig:

conststd=@import("std");constzanama=@import("zanama");// This creates a single linux x86_64 baseline binarypubfnbuild(b:*std.Build)void{constoptimize=b.standardOptimizeOption(.{});// Zanama needs to be aware of itself as the dependency, as well as the main build instanceconstzanama_dep=b.dependency("zanama",.{});constzb=zanama.Build.init(b,optimize,zanama_dep);// Your codeconstroot_module=b.createModule(.{.root_source_file=b.path("src/main/zig/root.zig"),.imports=&.{.{.name="zanama",.module=zanama_dep.module("api")},},});// Generating zanama bindings, as well as the libraries.// There are other createZanamaLibs* functions, check the source code for more infoconstlibs=zb.createZanamaLibsQuery("root",root_module,&.{zanama.target.common.x86_64.linux.baseline});// Putting the libraries and generated json into the prefix directory (specified in the gradle task)constinstall_step=b.getInstallStep();for(libs.artifacts)|artifact|{install_step.dependOn(&b.addInstallArtifact(artifact,.{//Configured like this to make the gradle side simpler.dest_dir=.{.override=.lib},.h_dir=.disabled,.implib_dir=.disabled,.pdb_dir=.disabled,}).step);}constinstall_json=b.addInstallFile(libs.json,"root.json");install_json.step.dependOn(libs.json_step);install_step.dependOn(&install_json.step);}

src/main/zig/root.zig:

constzanama=@import("zanama");pubconstBar=externstruct{x:i32,y:i32,z:i32,// Any function you want to call from Java must have the C calling convention.pubfnfoo(_:NonPacked)callconv(.c)void{}};comptime{zanama.genBindings(&.{.{.name="myproject.Bar",.Struct=Bar},})catchunreachable;}

Step 4: Processing the zig build outputs in gradle

val translateJavaSources = layout.buildDirectory.dir("generated/zanama_root")
val zigTranslate = tasks.register<ZanamaTranslate>("zigTranslate") {
 // zigPrefix variable specified in step 2
 from = zigPrefix.map { it.file("root.json") }
 into = translateJavaSources
 //The package you want to put the generated code into. May create sub-packages for nested structs, but never above this package.
 rootPkg = "com.example.myproject.natives"
 //The prefix of the .name part in the zanam genBindings call for all the bindings you added
 bindRoot = "myproject"
 //The name of the "main" class of this translation unit. It contains shared type info used by all bindings in the json
 className = "root_z"
 dependsOn(zigInstall) // from step 2
}
sourceSets["main"].java.srcDir(translateJavaSources)
tasks.compileJava {
 dependsOn(zigTranslate)
}
//You can either package the output DLLs into your program, include it in the generated jar file, or whatever else
tasks.processResources {
 dependsOn(zigInstall)
 into("com/example/myproject/natives") {
 from(zigPrefix.map { it.dir("lib") } )
 include("*.dll", "*.so", "*.dylib") // windows, linux, macos
 }
}
//Add the generated libraries to your jar

//Pull in the zanama runtime.
dependencies {
 implementation("com.falsepattern:zanama-rt:ZANAMA_VERSION")
}

Step 5: Create the initializer class

The generated main class of the translation requires you to load the native library manually.

root_z_init.java:

class root_z_init{//You can use the unpacker for jar-bundled resources (CAREFUL: gradle's application `run` task DOES NOT run the jar, so createWithUnpacker won't work there! Make a custom gradle task that runs your jar if you want to use it)//Alternatively, you can ship the natives already unpacked, and use NativeContext.create(Path)//You can share a single NativeContext between all *_init classes as long as you package all the natives into the same directoryprivatestaticfinalNativeContextCTX=NativeContext.createWithUnpacker(Path.of("natives"),"com/example/myproject/natives/");publicstaticNativeContext.LibcreateLib(){// You can do this checked and handle the errors yourself, etc.// For now, Zanama is not flexible enough for a better architecture, so this is fine.// Determining the library name is up to the user.// You can use the Platform class as extra help for determining the OS/CPU.varname="root-linux-x86_64-baseline";returnCTX.loadUnchecked(root_z_init.class,name);}}

At this point, you should be able to use the generated zanama bindings to work with structs and call methods.

Notes/todos

  • Zanama is not meant for generating bindings to arbitrary libraries, and instead is designed as the "inverse" of JNI headers. This means that it should not be used for mass-binding random libraries, but instead done with "pinhole" bindings for code that you directly control. If you want to do mass bindings, use JExtract with C headers, it's way more robust for that use case.
  • Zanama currently uses global state. Once a library is loaded, it can never be unloaded until the process exits, or you unload the zanama classes and discard the arena (accessible with the alternative NativeContext methods)
  • Translation for struct default values are not yet implemented
  • Non-type/function constants (pub const x: u32 = 123;) crash the translator. This is a known issue.