It took me a while to get Zig to play nicely with VS Code. I kept running into issues where the Zig VS Code extension couldn’t find the Zig binary or it would link to the wrong version of the standard library.
I finally got a solution that works consistently. I tested it on NixOS, and it also works with VS Code Remote SSH. It defaults to Zig 0.13.0, but you can change to the bleeding edge Zig compiler or any tagged release by updating the config file.
IMO the VS Code extension should manage the Zig installation. You should be able to choose which version you want from a convenient UI, and the default should be to use minimum_zig_version from the project’s build.zig.zon.
Edit: yeah looking at your blog post, that sounds like a total pain in the ass that should be unnecessary. It always makes me sad when people blog about complicated setups to use Zig.
It is a bit tricky to manage this from within VS Code, as one reasonable requirement here is that you should be able to pop open your terminal outside of VS Code, and get the “same” Zig.
Usually, this is achieved by installing some sort of version switcher, like rustup, user-globally, but that’s a huge can of worms, and isnt’ really in scope for the extension, it shouldn’t be messing up with user’s PATH or some such.
I like the pattern we use in TigerBeetle, where we don’t put Zig into path at all, and say that $PROJECT_ROOT/zig/zig is the zig to use. So, both inside the editor and outside, in the terminal, you can consistently use ./zig/zig build. This pattern I think can be supported by an extension nicely, but than it needs to really be a common practice, as the overwhelming expectation today is that things are installed at least user-wide, and are in PATH.
I would suggest to report this to the extension’s issue tracker. The problem that has been mentioned in the linked post is a bug that should be investigated and fixed.
Is it though? I use zvm to quickly switch between versions (mainly the last stable and bleeding-edge version), and this also downloads the matching zls version (e.g. zvm install --zls master.
In the VSCode Zig extension I changed two settings fields, one for the Zig path and one for the ZLS path (which are /Users/floh/.zvm/bin/zig and /Users/floh/.zvm/bin/zls respectively).
This works fine, but then I’m also used to switching node versions all the time via nvm
PS: I sometimes wonder if the zig executable should just be a slim ‘launcher proxy’ which manages the actual Zig toolchain as an automatic download into the global Zig cache and then picks whatever Zig toolchain version is required by the current project (but then I guess those version woes won’t be much of an issue after 1.0) - e.g. the Zig toolchain itself would become a build.zig.zon package including the actual Zig compiler (and the ‘zig frontend exe’ would only have the minimal required functionality to parse build.zig.zon, download packages, and then launch the actual zig compiler…)
First, this whole dynamic proxying of executables with an extra exec in-between is fundamentally complex. It works reliably, but it isn’t simple, and is likely to induce complexity somewhere else. E.g., Cargo grew code to bypass the proxy recently: Bypass rustup wrapper when invoking rustc · Issue #10986 · rust-lang/cargo · GitHub, in the name of performance (this particular issue probably doesn’t apply to zig, as it doesn’t do separate compilation, but this class of issues about latent complexity does apply).
Second, you need to change your PATH. That can be surprisingly hard for many users. For example, on Linux, you usually have one script that affects your graphical environment, and one script that affects your shell, and it is easy to get in a situation where stuff works sometimes. Experienced users usually dodge these problems, but new users are hit hard. To solve this, Rustup actually itself adds stuff to PATH on installation, but that’s obviously fraught and somewhat distasteful.
That being said, there is an equilibrium point here where everyone uses something like zvm, and than each editor plugin can assume that zvm is used and just use zig in PATH, suggesting to install zvm if that’s not the case.
Ah, I didn’t realize it works that way! Than, the problem is that “current version of zig” is a global state, which makes it hard for tools to support working with two projects needing different versions at the same time.
Yeah the path thing is unfortunate indeed, I guess because there’s no proper standard (maybe zvm could place a link into /usr/local/bin I guess, but I don’t actually know if this is ‘idiomatic’ on macOS for instance.
This looks like one of the better implementations of version switched concept! It is like rustup, but also simpler&better in a few details:
Rust has cargo&rustc. Zig unifies compiler and build system, by making the build system simply a library (and a part of standard library), so you only have to deal with one executable, zig, which is a nice simplification.
There’s no overrides or special configuration files, fetching Zig version from build.zig.zon is as simple as it gets.
in particular, rustup has a design security bug, where it allows using paths to local toolchains, which of course is exploitable
But, when extracting version, I think you should try to validate it more, and use SemanticVersion, rather than just []const u8. I don’t think that a maliciously crafted version can turn allocPrint(allocator, "https://ziglang.org/builds/zig-" ++ url_platform ++ "-{0s}." ++ archive_ext, .{compiler_version}), into an attacked controlled URL, but I am not entirely sure, so having compiler_version be not string, but structured data would inspire more confidence.
You re-use zig-fetch, so I think everything should just work in the presence of concurrency. A major pain with rustup is that it can wedge itself if several concurrent instances try to download stuff.
Can you store hashstore in ~/.cache/zig/anyzig/hashstore? It’s also a cache, in a sense that it should be possible to blew it away? And maybe you can trust the hashes from the https://ziglang.org/? Or I guess this requires that Zig actually publishes zig-fetch compatible hashes for the compiler itself?
Still, there’s fundamentally an extra process interposed in there. I can’t say that something will definitely break here, but probably there will be some weirdness around the edges.
Then, there’s also coordination problem — if you can convince the community that most people should be use anyzig, than downstream tools can rely on that, teaching materials can assume that getting a particular version of zig is not a problem, etc.
Finally, there’s a messy problem of installing anyzig itself — you provide a pre-built binary, but there’s also the problem of adding to the PATH. For wider audience, you generally can’t assume that people are super-comfortable installing a raw binary from the internet, so some guidance there would helpful (could be just an “add to path” copy-pastable script next to download commands?)
Overall, anyzig is definitely a great version switcher, as far as version switchers go, but I am still puzzled why, fundamentally, we need version switchers. Still holding onto my dear ./zig/zig pattern
Thanks for the feedback/insights. I think your approach of downloading a specific version of zig for a project is valid and is actually the approach I’m using for Tuple. The only downsides I see is the extra code the project needs to manage it and extra disk space, but these both become negligible for sufficiently large projects. I think if anyzig ends up taking off the need for this will become less but it can still be advantageous to link to a specific version to avoid the extra complexity of the indirection like you say. Your approach avoids the main problem which is that there’s an “impedence mismatch” with having 1 version of zig installed system-wide at a time but having projects that require different versions. There’s only two solutions, you can either just not have a system-wide zig…or…you can make that system-wide zig support any version
I will say that now that I’ve been using anyzig for a couple weeks it feels very nice. I don’t have to manage zig at all any more. I can now download any project and immediately build it without having to muck around with which version of zig is currently enabled on my system. I also no longer need to remember to document which version of zig each project uses…if “anyzig” can build it, then it’s documented, otherwise, the build fails. I’m surprised how much easier this has made working with so many zig projects.