Emitting an Executable
For a single file you do not need a build script at all:
zig build-exe main.zig # an executable
zig build-lib main.zig # a static library
zig build-obj main.zig # an object file
zig run main.zig # build and run in one step
zig test main.zig # build and run its tests
Useful flags
| Flag | Effect |
|---|---|
-O<Mode> | optimisation mode |
-target <triple> | cross-compile |
--name <n> | output name |
-femit-bin=<path> | where to write the binary |
-fstrip | drop debug info |
-fsingle-threaded | assume no threads |
zig run is the one you will use most
The difference between run and build-exe is only where the binary ends up:
zig run foo.zig
│
├── compile
├── binary into the cache
└── execute it
zig build-exe foo.zig
│
└── compile
│
▼
./foo
build-exe leaves an artifact in your working directory for you to run,
ship, or link. run puts the same artifact in the cache and executes it
immediately, so there is nothing to clean up and nothing to add to
.gitignore.
For anything up to a few hundred lines, zig run main.zig is the whole
workflow. It compiles to a cache directory and executes, so there is no binary
left in your working tree and no build step to remember. Arguments for the
program go after --:
zig run main.zig -- --verbose input.txt
The cache is content-addressed, so running it again after no changes does not
recompile. That makes zig run fast enough to use like a script interpreter,
which is a reasonable way to work on a single-file tool for a surprisingly
long time.
zig test works the same way, and needs no main. A file that is nothing but
test blocks is a complete, runnable program as far as Zig is concerned.
What each command actually produces
build-exe produces something the OS can run, which means it needs an entry
point and it links. build-lib produces a .a (or .lib) for someone else
to link, and adding -dynamic gives you a shared library instead. build-obj
stops before linking and hands you a single object file, which is what you
want when Zig is one part of a build driven by something else.
That last one is the quiet route into an existing codebase. Compile one module
with build-obj and link it into a C or C++ project exactly like any other
object file. Nothing else about the project has to change.
Targeting WebAssembly
This is how every snippet on this site is built:
zig build-exe main.zig -target wasm32-wasi -OReleaseSmall
For a freestanding wasm module with no WASI (the browser-canvas case), you
want -target wasm32-freestanding plus -fno-entry and explicit exports:
zig build-exe main.zig -target wasm32-freestanding \
-OReleaseSmall -fno-entry --export=add
The difference between the two is what the module expects the host to provide.
wasm32-wasi assumes a filesystem-shaped interface and a main;
wasm32-freestanding assumes nothing, so there is no entry point to call and
you name the functions the host may call instead. -OReleaseSmall matters
more here than anywhere else, because the artifact is downloaded before it
runs.
When to graduate to build.zig
Once you have more than one artifact, dependencies, generated files, or a test
step worth naming, move to a build script. zig build --help then lists the
steps and options that script defines.
The threshold in practice is usually the second target or the first
dependency. As long as one command line builds the thing, the command line is
simpler than a script. As soon as you are keeping a note of which flags to
pass, that note should be build.zig.