Building the Universal VST3 Bundle

macOS ships on two architectures. A plugin bundle holds one universal binary so that hosts of either architecture load the same file.

zig build vst3

That step, in build.zig, does four things.

1. Compiles the plugin twice. Once for aarch64-macos and once for x86_64-macos, via b.resolveTargetQuery. Zig cross-compiles both from whichever machine you are on, so no second toolchain is needed.

const arches = [_]std.Target.Cpu.Arch{ .aarch64, .x86_64 };
const suffixes = [_][]const u8{ "arm64", "x86" };

inline for (arches, suffixes) |arch, suffix| {
    const arch_target = b.resolveTargetQuery(.{ .cpu_arch = arch, .os_tag = .macos });
    // ... build danzig_<suffix> and DanzigGain_<suffix>
    lipo.addArtifactArg(plugin);
}

2. Merges them with lipo. b.addSystemCommand(&.{ "lipo", "-create" }) collects both artifacts and writes one fat Mach-O into the build cache. The output path is a build-graph node, so the merge reruns only when an input changes.

3. Lays out the bundle. b.addWriteFiles() builds the directory:

DanzigGain.vst3/
  Contents/
    Info.plist          generated from a template in build.zig
    PkgInfo             the 8 bytes "BNDL????"
    MacOS/
      DanzigGain        the universal binary, no file extension

The executable carries no extension. That is a bundle requirement, and it is why the lipo output is copied rather than installed under its library name.

4. Installs into zig-out/. The result is zig-out/DanzigGain.vst3.

Then:

zig build install-vst3

removes any existing copy and copies the bundle to $HOME/Library/Audio/Plug-Ins/VST3/DanzigGain.vst3, which is where macOS hosts scan.

The bundle is kept behind its own step rather than the default install because it doubles the compile work and only applies to macOS.

Sizes, from a ReleaseFast build:

ArtifactSize
zig-out/DanzigGain.vst3/Contents/MacOS/DanzigGain84 KB (universal)
zig-out/lib/libDanzigGain.dylib52 KB (arm64)
zig-out/lib/libDanzigMinimal.dylib52 KB (arm64)
zig-out/bin/danzig-minimal168 KB

The same artifacts in the default Debug build run about 1 to 2 MB each.