You can do this by compiling your C code to a dynamic library and using the lib in a custom build step that depends on the libs install step like this:
build.zig:
const std = @import("std");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const c_dep_mod = blk: {
const dep = b.dependency("c_dep", .{});
const tc = b.addTranslateC(.{
.root_source_file = dep.path("hello.h"),
.target = b.graph.host,
.optimize = optimize,
});
const mod = tc.createModule();
mod.addCSourceFile(.{
.file = dep.path("hello.c"),
.flags = &.{"-std=c99"},
.language = .c,
});
break :blk mod;
};
const lib = b.addLibrary(.{
.name = "hello",
.root_module = c_dep_mod,
.linkage = .dynamic,
});
const c_dep_install_artifact = b.addInstallArtifact(lib, .{});
const use_c_deps_step = UseCDeps.create(b);
use_c_deps_step.dependOn(&c_dep_install_artifact.step);
b.getInstallStep().dependOn(use_c_deps_step);
[...]
}
const UseCDeps = struct {
pub fn create(owner: *std.Build) *std.Build.Step {
const step = owner.allocator.create(std.Build.Step) catch @panic("OOM");
step.* = .init(.{
.id = .custom,
.name = "use c deps",
.owner = owner,
.makeFn = make,
});
return step;
}
pub fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) !void {
_ = options;
const b = step.owner;
// Adjust lib name to your target
var c_deps = try std.DynLib.open(b.getInstallPath(.lib, "libhello.dylib"));
defer c_deps.close();
const helloFromC = c_deps.lookup(*const fn () callconv(.c) void, "hello_from_c").?;
helloFromC();
}
};
build.zig.zon:
.{
[...]
.dependencies = .{
.c_dep = .{
.path = "c_dep",
},
},
[...]
}
c_dep/hello.h:
#pragma once
void hello_from_c();
c_dep/hello.c:
#include <stdio.h>
void hello_from_c() {
printf("Hello from C!\n");
}
zig build
output:
$ zig build
Hello from C!