How to Build Software From Source
[…] then I’ll take things in a completely different direction, by showing you how to rip apart a project’s build system and replace it with the zig build system. This will make building things from source work effortlessly for more people and more platforms, as well as annoy a lot of boomers. It’s going to be super fun and spicy!
As we all know, Zig is a C/C++ compiler and its own build system
Zig is really three things:
- Programming language
- Build system (build.zig) replacing makefiles/cmake/ninja/etc
- C/C++ compiler with an emphasis on cross-compilation
We’ve been leveraging all three in Mach engine for a while now. For example, we maintain a version of Google Chrome’s WebGPU implementation (Dawn) with its rather complex build system (code generation, python scripts, depot_tools, ninja, cmake, depot_tools, etc.) replaced with
That let’s us say that if you have a recent Zig version you can get started with Mach in ~60s on Windows, Mac, and Linux:
git clone --recursive https://github.com/hexops/mach-examples cd mach-examples/ zig build run-textured-cube
And instead of getting a bunch of dependency errors that you might need to
apt-get install or whatever, you’ll just get something that works out of the box:
Zig has a new package manager (for C/C++ too!)
Those in the Zig community know that Zig has a new package manager, it’s built into the compiler. Effectively you describe your dependencies in a
build.zig.zon file, and then the
zig compiler is able to fetch them for you as part of
zig build. You’re then able to link against/use dependencies in your
build.zig file, which declaratively says how to build your project (except, using a real language instead of a DSL like cmake/etc use.)
It’s still very experimental, has little to no documentation yet - it’s not ready for widespread use. But one strong point is that it also aims to address the issue of building C/C++ projects, not just Zig ones. You can write a
build.zig file in Zig, describing how to build your C/C++ project using Zig as the toolchain. Then for free you get quite solid cross-compilation (since Zig bundles clang, every glibc version, and more), plus now a dependency manager, as well as a declarative way to describe your build using the Zig language.
One example of this is in Andrew Kelley’s fork of ffmpeg, where he merely forked the ffmpeg repository, removed their build system & unnecessary files, and added a
build.zig file. This allows you to clone the repository and
zig build will fetch all the required dependencies and build ffmpeg for you. Fancy!
Mach engine is an upcoming game engine built in Zig, that we’re building with the aim of becoming competitive with Unity/Unreal/Godot - but with an emphasis on modularity:
- Mach core: If you choose to use core, then it’s like an alternative to GLFW+OpenGL or SDL, you just get a window+input+WebGPU with minimal dependencies. Your application runs natively on Windows/Linux/Mac using their respective graphics APIs (DirectX12, Vulkan, Metal), you get cross-compilation and zero-fuss installation, and also web/mobile support in the future with the same codebase. Write once, run everywhere.
- Mach engine: If you choose this option, you additionally get an entity component system - with a library of standard modules that you can ‘plug and play’ with for rendering/audio/etc.
Keeping our runtime C dependencies small
One way that we’re keeping runtime C dependencies (the ones your game/app would ship with!) a smaller, focused, set - is by building tooling: a
mach CLI and fully-fledged GUI editor like other engines have. But how does that help reduce runtime dependencies? Well, at runtime you may need:
- Harfbuzz: for Unicode text layout
- GLFW (and some headers): for window management)
- Basisu and PNG: for GPU supercompressed textures / lossless textures
- Opus and FLAC: for lossy and lossless audio
Mach will ‘bless’ certain formats, being opinionated in what you ship with your game. You’re free to pull in other formats, if you like, but the default/easy path will be these ones. As a result, there’s a lot we won’t need at runtime:
- JPEG, TGA, or other image formats
- MP3, ffmpeg, or other audio formats
We don’t need these because our tooling (the CLI and GUI editor) is going to make it easy to convert whatever format you want into the ‘blessed’ runtime formats. One major benefit of this is that we can nudge you to the right defaults, without you being an expert. For example, you probably want to be using texture compression formats that GPU hardware itself understands, instead of say shipping a JPEG that just gets expanded to an uncompressed texture, eating a bunch of GPU memory and harming your texture bandwidth.
Providing an ecosystem of C libraries
Similar to Andrew Kelley’s ffmpeg fork (although, with a few niceties to verify the supply chain) - Mach is now maintaining forks of various C libraries that we make use of. These aren’t Zig bindings to these libraries (which we have separately), but rather are just forks of the actual project with their build system replaced by
A massive special thanks to @LordMZTE who has been tirelessly pushing us along here over the past month-ish, helping to inch us ever-closer to fully adopting the new package manager.
Forks we maintain
We have forks of these projects which switch their build systems to
- hexops/basisu (basis_universal, supercompressed textures)
A note about supply chain verification
I personally care a lot about supply chain security - and more importantly, bugs. In general, I don’t ever want anyone to have to ‘wonder’ if our fork of a library has some strange patches applied to it or something.
As a result, in each of these forks we’ve taken the time to ensure you know the exact
git diff command you can run to verify that our fork exactly matches the upstream version - with the only difference being removing the project’s old build system, and unnecessary files.
Header packages we’re maintaining
In addition to the above, we’re maintaining the following which aren’t strict forks (a repository for each would simply be too much for us to maintain), but rather are collections of common headers that you very often need together. These can help you build GLFW, SDL, and other such applications.
Some headers are generated with platform-specific tools (e.g. in the case of Wayland this is needed.) We always provide the exact steps we used to produce the headers from upstream repositories in an
update-upstream.sh script, with the intent that you can fully reproduce what’s in these packages and have confidence it came from the upstream repository.
- hexops/linux-audio-headers includes:
- hexops/x11-headers includes:
- hexops/wayland-headers includes:
How do I use these?
Later we’ll provide more details specifically on how to use these. Effectively you just add them to a
build.zig.zon file next to your
build.zig and then call
b.dependency("name") to retrieve each one. If you need more help than that, you might need to join our Discord because as mentioned previously the Zig package manager is pretty immature and has sharp edges today. There are lots of known issues & bugs that prevent even us from using it fully today.
But, it is coming along rather quickly! We wanted to let the broader Zig community know we’re maintaining these packages to help with collaboration.
Help us become sustainable
We’re working towards Mach v0.2, this article was one of the first steps in beginning to share the progress we’ve been making towards that behind the scenes over the past several months. We have some exciting things to share next, this was the ‘boring’ article that had to go first. :)
Consider sponsoring my work to help us become a sustainable OSS project and enable us to do more in the future.
Join the Mach Discord where we’re building the future of Zig game development in realtime!