Skip to content

Zig support for Dart's build hooks. Automatically builds and bundles your Zig code with your Dart/Flutter application.

License

Notifications You must be signed in to change notification settings

ykmnkmi/native_toolchain_zig.dart

Repository files navigation

🔧 native_toolchain_zig

Pub Version Dart CI License: MIT

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.

Prerequisites

Install Zig 0.15.0+ on your development machine.

Installation

dart pub add hooks native_toolchain_zig

Project Setup

  1. 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
  1. 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);
  });
}
  1. 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);
  });
}
  1. 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.

  1. Create zig/src/lib.zig:
export fn add(a: i32, b: i32) i32 {
    return a + b;
}
  1. Create Dart bindings in lib/my_package.dart:
import 'dart:ffi';

@Native<Int32 Function(Int32, Int32)>()
external int add(int a, int b);
  1. Run your app:
dart run

Example

An example can be found in /example/.

License

MIT License - see LICENSE for details.

About

Zig support for Dart's build hooks. Automatically builds and bundles your Zig code with your Dart/Flutter application.

Topics

Resources

License

Stars

Watchers

Forks