Build-On-Save
ZLS can be configured to run your build script on save. If that results in build errors, they will be displayed in your editor like any other diagnostic.
Warning (Linux)
On Linux, Build-On-Save requires a kernel version of at least 6.5
. Use uname -r
to check your version. If an incompatible version is used, a warning will be logged and Build-On-Save will be disabled. A workaround implementation is available if enable_build_on_save
has been set to true
.
This is especially important to WSL2 (Windows Subsystem for Linux version 2) users where the default kernel version is 5.15
as of January 2025.
Configuration
By default, ZLS will automatically enable Build-On-Save if your build script defines a “check” step. Otherwise you will need to set the enable_build_on_save
config option. You can use build_on_save_args
to configure which command-line arguments should be passed to your build script.
Here is an example:
{
"enable_build_on_save": true,
"build_on_save_args": ["install", "-Dtarget=wasm32-wasi", "-fwasmtime"],
}
Add a “check” step to your build.zig
This part is more deeply tied to your specific project but the gist is the following: whatever you do to define your main executable or library, you do it again in a new step named check.
I’ll use for this example the executable definition step you get generated automatically from zig init
.
// Create a "module" just like in the `zig init` template.
const module = b.addModule("foo", .{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Any other code to define dependencies would probably be here.
// Create an example from the given module.
// Still the same as the `zig init` template.
const exe = b.addExecutable(.{
.name = "foo",
.root_module = module,
});
b.installArtifact(exe);
// This is where the interesting part begins.
// As you can see we are re-defining the same executable but
// we're binding it to a dedicated build step.
const exe_check = b.addExecutable(.{
.name = "foo",
.root_module = module,
});
// There is no `b.installArtifact(exe_check);` here.
// Finally we add the "check" step which will be detected
// by ZLS and automatically enable Build-On-Save.
// If you copy this into your `build.zig`, make sure to rename 'foo'
const check = b.step("check", "Check if foo compiles");
check.dependOn(&exe_check.step);
The most important part about this second executable definition is that we ask to build it, but we never install it. If you look at the final line of the first section, you will see that we call b.installArtifact
on the original executable, but for the executable bound to the “check” step, we don’t.
This one-line difference will have a big impact on the resulting behavior of the build as it will add the -fno-emit-bin
flag to the compiler invocation which, in other words, means that Zig will analyze your code (and report any error) but it won’t bother calling into LLVM since you don’t plan to install the executable anyway.
The result is that you will get diagnostics pretty fast since you won’t have to go through the “LLVM Emit Code…” phase.
Once you’re done with this, restart your editor (or at least ZLS), save your file with an error in it, and enjoy your new spiffy diagnostics.
Special Thanks
This Guide is based on the “Improving Your Zig Language Server Experience” Blog Post by Loris Cro.