Building Zig from source for dummies?

I have a recurring problem: every once in a while, I want to contribute to Zig, but then end up spending most of my attention quant just trying to get a version of compiler that can compile master Zig.

I am looking for a magic command that makes the following work:

$ git clone && cd zig
$ <magic-command-for-dummies>
$ stage4/bin/zig build test -Denable-llvm

I don’t care if <magic-command-for-dummies> takes three hours, I am fine leaving that running in the background while I do something else.

I care very much that I don’t have to understand <magic-command-for-dummies>, and that I don’t have to supply any kind of inputs to it.

I am aware of the docs at Building Zig From Source · ziglang/zig Wiki · GitHub, but I would say they very much lack that single command. For example, this is how I failed to contribute to Zig today:

  • Reading that wiki page, option A is definitely not for me: I don’t want to learn which version of LLVM I need, and how to install that using my distro package manager.

  • Option B is sort-of closer. I still need to add a series of commands, and I need to tweak them for my use-case, but at least it should take care of dependencies.

  • git clone --depth 1 && cd zig-bootstrap. That --depth 1 feels important in this case, I wish I could copy-paste that rather than typing git clone without that, only to realize a minute later than that’s going to be long and restarting with --depth=1

  • The docs there tell you to run ./build <arch>-<os>-<abi> <mcpu> Luckily, I know from the last time that that’s not actually what you want to run, because that’ll build LLVM using one CPU core, so I go and copy-paste the actual command from Add copy-pastable build instructions by matklad · Pull Request #159 · ziglang/zig-bootstrap · GitHub.

  • After enjoyably drinking couple of pu-erhs, that command finishes, so I go back to the wiki, which tells me to do

    "$ZIG_PREFIX/bin/zig" build \
    -p stage3 \
    --search-prefix "$LLVM_PREFIX" \
    --zig-lib-dir lib \

    This again reuires some non-trivial amount of attention, as I need to set those two paths manually, and not to mix up the two

  • and that finally culminates in

    Could not start dynamically linked executable: ./zig
    NixOS cannot run dynamically linked executables intended for generic
    linux environments out of the box. For more information, see:

    That’s understandable: I use NixOS, so I brought this curse upon myself. But also, for “normal” zig, I can just download that and it works, because it is fully statically liked?

  • And at this point I decided that I’d rather write a ziggit post hoping that someone will provide a guaranteed-to-work build command, rather than continue to investigate this further :rofl:


Hi, this wiki page might help:

Download the most recent zig binary and use that to compile zig, using the normal zig build. Works in most cases.

1 Like

I think that won’t work for -Denable-llvm case? I don’t think ziglang/zig itself vendors LLVM which can be compiled from source, it expects LLVM to be pre-compiled?

But actually, for the change I have in mind I don’t need to build the compiler itself, because I only want to add a test to standard library!

oh man, you lost me here :face_vomiting:
I feel like pu-erh tastes like (clean) diapers. Sanitary, but oddly disturbing, and definitely not food.

Anyway, I strongly recommend this workflow for you:

  1. How to build LLVM, libclang, and liblld from source · ziglang/zig Wiki · GitHub
  2. Contributing · ziglang/zig Wiki · GitHub

Building LLVM/Clang/LLD from source only takes about an hour and a few GiB, and it works every time. On the other hand, I can’t control what shenanigans Linux distributions get up to with how they organize all the files.

This is the workflow I use because it works everywhere, on every OS, every distro, every time. 1 hour to build LLVM, 10 minutes to rebuild with cmake after pulling, 1 minute for compiler rebuilds.


Some copy-pastable commands for @matklad from the future:

I want to hack on the standard library

git clone && cd zig

curl -L $(curl -L |
 jq -r ".master.\"x86_64-linux\".tarball") > zig.tar.xz
mkdir stage3 && tar xf zig.tar.xz -C stage3 --strip-components 1
./stage3/zig version

./stage3/zig test lib/std/std.zig --zig-lib-dir lib

I want to hack on the compiler itself

First, check what LLVM is it. Then,

git clone
git clone --depth 1 --branch release/18.x llvm-project-18

pushd llvm-project-18
git checkout release/18.x
mkdir build-release
pushd build-release
cmake ../llvm \
  -DCMAKE_INSTALL_PREFIX=../../llvm-release \
  -DLLVM_ENABLE_PROJECTS="lld;clang" \
  -G Ninja
ninja install # pu-erh time
./llvm-release/bin/llvm-config --version

pushd zig
mkdir build
pushd build
cmake .. \
    -DCMAKE_PREFIX_PATH=$(realpath ../../llvm-release) \
    -DCMAKE_BUILD_TYPE=Release \
    -G Ninja
ninja install
./zig/build/stage3/bin/zig version

cd zig
./build/stage3/bin/zig build --prefix ./stage4 -Denable-llvm -Dno-lib
./stage4/bin/zig build test -Denable-llvm

Though, there’s one mystery in the above for me: how does stage4 discover LLVM?

That is, when we compile stage3 using cmake/ninja, we explicitly pass -DCMAKE_PREFIX_PATH=$(realpath ../../llvm-release).

But we don’t pass anything like that when we use stage3/zig build to build stage4. This seems unexpected!

Someone please correct me if I’m wrong, but I believe this is because at this point you are rebuilding Zig entirely using a Zig binary you built for “stage3”. Rebuilding Zig with Zig if you will. To build “stage3”, however, you’re using CMake to drive LLVM/Clang to compile Zig sources into a binary. This is why, if the versions of Zig are close enough, you can skip the LLVM compilation/stage 3 portion and just use a pre-built Zig binary as “stage3” to build “stage4”.

Very recently went through this myself, and the two links Andrew posted are definitely the magic combo.

Ok, what happens here is that stage4 “inherits” LLVM from stage3. Specifically, there’s a code in build.zig which tries to find LLVM next to the Zig compiler used to drive build.zig:

That config.h file contains the path to LLVM we used to build stage3:

#define ZIG_CMAKE_PREFIX_PATH "/home/matklad/tmp/zl/llvm-release;"

Nifty, though quiet magical!


Ahhhh okay, that’s a good clarification!

Here’s my version where I make a couple changes, first is I would separate out building llvm from building zig, since you should only need to build llvm once for every version and you can re-use that for multiple zig repositories. Second I’d avoid changing directories as I find it nice to have commands I can copy/paste and run at any point without having to change directories. At least for LLVM, after that I’d change directories to the zig repo and the rest of the commands would be from that directory since that’s where I’d be living for the rest of development.

# Clone/Configure/Build LLVM
git clone --depth 1 --branch release/18.x llvm-project-18
mkdir llvm-project-18/build-release

cmake -S llvm-project-18/llvm -B llvm-project-18/build-release \
  -DCMAKE_INSTALL_PREFIX=llvm-release-18 \
  -DLLVM_ENABLE_PROJECTS="lld;clang" \
  -G Ninja

ninja -C llvm-project-18/build-release install # pu-erh time

./llvm-release-18/bin/llvm-config --version

Has anyone created a Docker image for this purpose? That would be a real “for dummies” solution.

Nah, I can’t docker, too hard for me :slight_smile:


Not a bad idea! A separate image for each LLVM version Zig uses/has used would save you the hour(s) of compiling LLVM from source.

1 Like

If someone creates the images, I can write a little JS tool that further automate the process (install Docker, pull the right image, etc.). I got some code lying around already. Then going from zero to building would simply require typing in npx zig-compiler-dev.

1 Like

I think I can swing this, I just built both from source so it’s fresh in my memory. May be a couple days since it will take a LONG time to build these images and there will likely be some debug cycles :slight_smile:

1 Like

Alright check it out!

The actual built image isn’t hosted anywhere yet ATM, but confirmed was able to build “stage3” and “stage4” using this image!


Great! On my poor dual-core notebook it took 3 hours to build, LOL. The image is a whopping 14G. I think if you combine the git clone, cmake, and install commands into one command and add rm -rf /llvm-project-18, that would reduce the image size by 8.4G. We shouldn’t need the LLVM project directory after we install its libraries, right?

I’m sorry for being a downer, but I really wish that people would learn to build from source on their actual host machine rather than introducing a dependency on Fedora and Docker. It’s a high cost to pay just to avoid learning some practically useful stuff.

I put a lot of work into the build-from-source process to make it as painless and standard as possible, so it pains me when people turn to Docker.

At least, after #16270 is done, I can make it truly painless since there will be no dependencies other than a C compiler. I mean, it will be exactly the process detailed in the README except all those listed downsides will be eliminated.


I am wondering if, even without fully eliminating LLVM, it would be a terrible idea to teach the build.zig in the Zig repo to do git clone LLVM && cmake && ninja dance…

The main stumbling block for me I think is that “downloading & installing LLVM dependency” is out of scope for the Zig build process, so you need to teach a human to do that, and humans are lazy learners (well, at least this particular human). I generally see “download&build dependencies” in scope for each project, because it’s the downstream who should control the versions of dependencies (with an optional override for further downstream packager or integrator).

I guess the long-term plan here is to wrapping llvm/clang into a Zig package, so that the relevant cmake logic is encoded in some build.zig, which lives outside of the main repository?

1 Like