Zig support for Dart's build hooks. Automatically builds and bundles your Zig code with your Dart/Flutter application.
Note
If you want to use ffigen to auto-generate Dart bindings, you'll need to
manually write a C header file. Automatic header generation from Zig is
currently blocked by ziglang/zig#9698.
Install Zig 0.15.0+ on your development machine.
dart pub add hooks native_toolchain_zig- Create your Zig project in
my_project/zig/:
my_package/
├── hook/
│ └── build.dart
├── lib/
│ └── my_package.dart
├── zig/
│ ├── src/
│ │ └── lib.zig
│ ├── build.zig
│ └── build.zig.zon
└── pubspec.yaml
- Create
hook/build.dart:
import 'package:hooks/hooks.dart';
import 'package:native_toolchain_zig/native_toolchain_zig.dart';
Future<void> main(List<String> arguments) async {
await build(arguments, (input, output) async {
await ZigBuilder(
assetName: 'my_package.dart',
zigDir: 'zig/',
).run(input: input, output: output);
});
}- Create
zig/build.zig:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lib = b.addLibrary(.{
.name = "my_package",
.linkage = .dynamic,
.root_module = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(lib);
}Build both static and dynamic libraries
To produce both library types in a single build:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const root_module = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
// Dynamic library (.so, .dylib, .dll)
const dynamic_lib = b.addLibrary(.{
.name = "my_package",
.linkage = .dynamic,
.root_module = root_module,
});
b.installArtifact(dynamic_lib);
// Static library (.a, .lib)
const static_lib = b.addLibrary(.{
.name = "my_package",
.linkage = .static,
.root_module = root_module,
});
b.installArtifact(static_lib);
}Select linkage via command line
To control linkage type via a custom -Dlinkage option, first accept it in
your build.zig:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const linkage = b.option(
std.builtin.LinkMode,
"linkage",
"Library linkage type",
) orelse .dynamic;
const lib = b.addLibrary(.{
.name = "my_package",
.linkage = linkage,
.root_module = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(lib);
}Then pass the flag from Dart via extraArguments in hook/build.dart:
import 'package:hooks/hooks.dart';
import 'package:native_toolchain_zig/native_toolchain_zig.dart';
Future<void> main(List<String> arguments) async {
await build(arguments, (input, output) async {
await ZigBuilder(
assetName: 'my_package.dart',
zigDir: 'zig/',
// Forward the linkage option to zig build
extraArguments: ['-Dlinkage=static'],
).run(input: input, output: output);
});
}- Create
zig/build.zig.zon:
.{
.name = .my_package,
.version = "0.1.0",
.minimum_zig_version = "0.15.0",
.fingerprint = 0x..........,
.paths = .{
"src",
"build.zig",
"build.zig.zon",
},
}Important
The paths field drives incremental build tracking. ZigBuilder parses
build.zig.zon and registers every file and directory listed in paths as a
build dependency. When any of those files change, Dart's build system
automatically re-triggers the Zig build. Make sure paths includes all source
directories and files your build depends on (e.g. "src", C headers,
embedded data files).
The name field (.my_package) is a comptime enum literal and is ignored.
- Create
zig/src/lib.zig:
export fn add(a: i32, b: i32) i32 {
return a + b;
}- Create Dart bindings in
lib/my_package.dart:
import 'dart:ffi';
@Native<Int32 Function(Int32, Int32)>()
external int add(int a, int b);- Run your app:
dart runAn example can be found in /example/.
MIT License - see LICENSE for details.