The Worst Zig Version Manager

./getzig.ps1
#!/bin/sh
echo `# <#`

mkdir -p ./zig

wget https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz -O ./zig/zig-linux-x86_64-0.10.1.tar.xz
tar -xf ./zig/zig-linux-x86_64-0.10.1.tar.xz -C ./zig --strip-components=1
rm ./zig/zig-linux-x86_64-0.10.1.tar.xz

echo "Zig installed."
./zig/zig version

exit
#> > $null

Invoke-WebRequest -Uri "https://ziglang.org/download/0.10.1/zig-windows-x86_64-0.10.1.zip" -OutFile ".\zig-windows-x86_64-0.10.1.zip"
Expand-Archive -Path ".\zig-windows-x86_64-0.10.1.zip" -DestinationPath ".\" -Force
Remove-Item -Path " .\zig-windows-x86_64-0.10.1.zip"
Rename-Item -Path ".\zig-windows-x86_64-0.10.1" -NewName ".\zig"

Write-Host "Zig installed."
./zig/zig.exe version

https://github.com/matklad/hello-getzig

Longer version:

One of the values of Zig which resonates with me deeply is a mindful approach to dependencies. Zig tries hard not to ask too much from the environment, such that, if you get zig version running, you can be reasonably sure that everything else works. Thats one of the main motivations for adding an HTTP client to the Zig distribution recently. Building software today involves downloading various components from the Internet, and, if Zig wants for software built with Zig to be hermetic and self-sufficient, it needs to provide ability to download files from HTTP servers.

Theres one hurdle for self-sufficiency: how do you get Zig in the first place? One answer to this question is from your distributions package manager. This is not a very satisfying answer, at least until the language is both post 1.0 and semi-frozen in development. And even then, what if your distribution is Windows? How many distributions should be covered by Installing Zig section of your CONTRIBUTING.md?

Another answer would be a version manager, a-la rustup, nvm, or asdf. These tools work well, but they are quite complex, and rely on various subtle properties of the environment, like PATH, shell activation scripts and busybox-style multipurpose executable. And, well, this also kicks the can down the road you can use zvm to get Zig, but how do you get zvm?

I like how we do this in TigerBeetle. We dont use zig from PATH. Instead, we just put the correct version of Zig into ./zig folder in the root of the repository, and run it like this:

$ ./zig/zig build test

Suddenly, whole swaths of complexity go away. Quiz time: if you need to add a directory to PATH, which script should be edited so that both the graphical environment and the terminal are affected?

Finally, another interesting case study is Gradle. Usually Gradle is a negative example, but they do have a good approach for installing Gradle itself. The standard pattern is to store two scripts, gradlew.sh and gradlew.bat, which bootstrap the right version of Gradle by downloading a jar file (java itself is not bootstrapped this way though).

What all these approaches struggle to overcome is the problem of bootstrapping. Generally, if you need to automate anything, you can write a program to do that. But you need some pre-existing program runner! And theres just no good options out of the box bash and powershell are passable, but barely, and they are different. And bash and the set of coreutils also differs depending on the Unix in question. But theres just no good solution here if you want to bootstrap automatically, you must start with universally available tools.

But is there perhaps some scripting language which is shared between Windows and Unix? @cspotcode suggests a horrible workaround. You can write a script which is both a bash script and a powershell script. And it even isnt too too ugly!

!/bin/bash
echo `# <#`

echo "Bash!"

exit
#> > $null

Write-Host "PowerShell!"

So, heres an idea for a hermetic Zig version management workflow. Theres a canonical, short getzig.ps1 PowerShell/sh script which is vendored verbatim by various projects. Running this script downloads an appropriate version of Zig, and puts it into ./zig/zig inside the repository (.gitignore contains /zig). Building, testing, and other workflows use ./zig/zig instead of relying on global system state ($PATH).

A proof-of-concept getzig.ps1 is at the start of this article. Note that I dont know bash, powershell, and how to download files from the Internet securely, so the above PoC was mostly written by Chat GPT. But it seems to work on my machine. I clone https://github.com/matklad/hello-getzig and run

$ ./getzig.ps1
$ ./zig/zig run ./hello.zig

on both NixOS and Windows 10, and it prints hello.

If anyone wants to make an actual thing out of this idea, heres possible desiderata: