Zig is not just a programming language but also a toolchain that can help you maintain and gradually modernize existing C/C++ projects, based on your needs. In this series we're using Redis, a popular in-memory key value store written in C, as an example of a real project that can be maintained with Zig. You can read more in "Maintain it with Zig".
The portability problem
Open the average Makefile and you will find a plethora of brittle capability checks that completely break any kind of attempt at cross-compiling. We've actually seen a very mild version of that when looking at Redis in the previous post, but it can get much worse.
Thankfully Zig features a build system integrated in the compiler exposed as the zig build
subcommand.
Why use Zig Build
Dependency free
Since you're already using the Zig compiler, you can build your projects on all supported platforms without depending on any system dependency, not even build-essential
, Xcode or MSVC.
Cross-compilation as first class citizen
Zig considers being able to compile from any target to any target one of its main goals. Using Zig build will make it easier to integrate with Zig's cross-compilation features.
Declarative but without special syntax
The zig build
subcommand relies on a build.zig
file. The build system uses a declarative system to describe build pipelines (very useful for instrumenting the build runner), but it uses normal Zig syntax to do so. If you know C or C++ you will be able to read Zig code very easily.
Translating Makefiles
Before we continue I have to warn you: reverse engineering some build systems is not easy if you don't have intimate knowledge of the C/C++ ecosystem.
For example if you want to see Andrew and me work on translating Redis' build system to Zig, check out the live stream archived on Andrew's Vimeo. In that video you will see that Andrew knows what to do because he's worked with C/C++ for long enough to know all the tricks.
Understanding build.zig
To learn more about the types and conventions used by the Zig build system, take a look at this series by @xq :
The full build.zig
This is the complete build.zig
file that can buld redis-cli
and redis-server
with all their respective static dependencies. What you don't see here is jemalloc and systemd support, which are left as an exercise to the reader.
Show the full build.zig file
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
const target = b.standardTargetOptions(.{});
const mode = b.standardReleaseOptions();
const hiredis = b.addStaticLibrary("hiredis", null);
hiredis.setTarget(target);
hiredis.setBuildMode(mode);
hiredis.linkLibC();
hiredis.force_pic = true;
hiredis.addCSourceFiles(&.{
"deps/hiredis/alloc.c",
"deps/hiredis/async.c",
"deps/hiredis/hiredis.c",
"deps/hiredis/net.c",
"deps/hiredis/read.c",
"deps/hiredis/sds.c",
"deps/hiredis/sockcompat.c",
}, &.{
"-Wall",
"-W",
"-Wstrict-prototypes",
"-Wwrite-strings",
"-Wno-missing-field-initializers",
});
const lua = b.addStaticLibrary("lua", null);
lua.setTarget(target);
lua.setBuildMode(mode);
lua.linkLibC();
lua.force_pic = true;
lua.addCSourceFiles(&.{
"deps/lua/src/fpconv.c",
"deps/lua/src/lapi.c",
"deps/lua/src/lauxlib.c",
"deps/lua/src/lbaselib.c",
"deps/lua/src/lcode.c",
"deps/lua/src/ldblib.c",
"deps/lua/src/ldebug.c",
"deps/lua/src/ldo.c",
"deps/lua/src/ldump.c",
"deps/lua/src/lfunc.c",
"deps/lua/src/lgc.c",
"deps/lua/src/linit.c",
"deps/lua/src/liolib.c",
"deps/lua/src/llex.c",
"deps/lua/src/lmathlib.c",
"deps/lua/src/lmem.c",
"deps/lua/src/loadlib.c",
"deps/lua/src/lobject.c",
"deps/lua/src/lopcodes.c",
"deps/lua/src/loslib.c",
"deps/lua/src/lparser.c",
"deps/lua/src/lstate.c",
"deps/lua/src/lstring.c",
"deps/lua/src/lstrlib.c",
"deps/lua/src/ltable.c",
"deps/lua/src/ltablib.c",
"deps/lua/src/ltm.c",
"deps/lua/src/lua_bit.c",
"deps/lua/src/lua_cjson.c",
"deps/lua/src/lua_cmsgpack.c",
"deps/lua/src/lua_struct.c",
"deps/lua/src/lundump.c",
"deps/lua/src/lvm.c",
"deps/lua/src/lzio.c",
"deps/lua/src/strbuf.c",
}, &.{
"-std=c99",
"-Wall",
"-DLUA_ANSI",
"-DENABLE_CJSON_GLOBAL",
"-DLUA_USE_MKSTEMP",
});
const redis_cli = b.addExecutable("redis-cli", null);
redis_cli.setTarget(target);
redis_cli.setBuildMode(mode);
redis_cli.install();
redis_cli.linkLibC();
redis_cli.linkLibrary(hiredis);
redis_cli.addIncludeDir("deps/hiredis");
redis_cli.addIncludeDir("deps/linenoise");
redis_cli.addIncludeDir("deps/lua/src");
redis_cli.addCSourceFiles(&.{
"src/adlist.c",
"src/ae.c",
"src/anet.c",
"src/cli_common.c",
"src/crc16.c",
"src/crc64.c",
"src/crcspeed.c",
"src/dict.c",
"src/monotonic.c",
"src/mt19937-64.c",
"src/redis-cli.c",
"src/release.c",
"src/redisassert.c",
"src/siphash.c",
"src/zmalloc.c",
"deps/linenoise/linenoise.c",
}, &.{
"-std=c11",
"-pedantic",
"-Wall",
"-W",
"-Wno-missing-field-initializers",
});
const redis_server = b.addExecutable("redis-server", null);
redis_server.setTarget(target);
redis_server.setBuildMode(mode);
redis_server.install();
redis_server.linkLibC();
redis_server.linkLibrary(hiredis);
redis_server.linkLibrary(lua);
redis_server.addIncludeDir("deps/hiredis");
redis_server.addIncludeDir("deps/lua/src");
redis_server.addCSourceFiles(&.{
"src/acl.c",
"src/adlist.c",
"src/ae.c",
"src/anet.c",
"src/aof.c",
"src/bio.c",
"src/bitops.c",
"src/blocked.c",
"src/childinfo.c",
"src/cluster.c",
"src/config.c",
"src/connection.c",
"src/crc16.c",
"src/crc64.c",
"src/crcspeed.c",
"src/db.c",
"src/debug.c",
"src/defrag.c",
"src/dict.c",
"src/endianconv.c",
"src/evict.c",
"src/expire.c",
"src/geo.c",
"src/geohash.c",
"src/geohash_helper.c",
"src/gopher.c",
"src/hyperloglog.c",
"src/intset.c",
"src/latency.c",
"src/lazyfree.c",
"src/listpack.c",
"src/localtime.c",
"src/lolwut.c",
"src/lolwut5.c",
"src/lolwut6.c",
"src/lzf_c.c",
"src/lzf_d.c",
"src/memtest.c",
"src/module.c",
"src/monotonic.c",
"src/mt19937-64.c",
"src/multi.c",
"src/networking.c",
"src/notify.c",
"src/object.c",
"src/pqsort.c",
"src/pubsub.c",
"src/quicklist.c",
"src/rand.c",
"src/rax.c",
"src/rdb.c",
"src/redis-check-aof.c",
"src/redis-check-rdb.c",
"src/release.c",
"src/replication.c",
"src/rio.c",
"src/scripting.c",
"src/sds.c",
"src/sentinel.c",
"src/server.c",
"src/setcpuaffinity.c",
"src/setproctitle.c",
"src/sha1.c",
"src/sha256.c",
"src/siphash.c",
"src/slowlog.c",
"src/sort.c",
"src/sparkline.c",
"src/syncio.c",
"src/t_hash.c",
"src/t_list.c",
"src/t_set.c",
"src/t_stream.c",
"src/t_string.c",
"src/t_zset.c",
"src/timeout.c",
"src/tls.c",
"src/tracking.c",
"src/util.c",
"src/ziplist.c",
"src/zipmap.c",
"src/zmalloc.c",
}, &.{
"-std=c11",
"-pedantic",
"-Wall",
"-W",
"-Wno-missing-field-initializers",
"-fno-sanitize=undefined",
});
}
You can also find this code on GitHub.
What's next?
Now that we're using zig build
, we can cross-compile very easily without needing external dependencies. This is a good place to be, but if your up for one final bit of fun, I can show you how to add Zig code to an existing C project.
Reproducibility footnote
Zig 0.8.1, Redis commit be6ce8a
.
Latest comments (2)
Unfortunately, the link to the livestream mentioned in Translating Makefiles is dead.
Yeah it seems that andrew's vimeo doesn't have the video anymore. Nothing I can do about it unfortunately.