Live data from Hacker News

Zb: An Early-Stage Build System

zombiezen.com

51–60 of 132 posts

Re: Zb: An Early-Stage Build System

#51
post #19

Whoa, nifty. Can you speak more to the interop issues with Nix? I've been working on a pretty large Nix deployment in the robotics space for the past 3ish years, and the infrastructure side is the biggest pain point: * Running a bare `nix build` in your CI isn't really enough— no hosted logs, lack of proper prioritization, may end up double-building things. * Running your own instance of Hydra is a gigantic pain; it'…

I've been wondering idly if it's possible for Nix to support the Bazel Remote Execution API that seems to be catching on[1] more generally. [1] https://github.com/bazelbuild/remote-apis?tab=readme-ov-file...

I’m very interested in better bidirectional interop between bazel and nix; it seems such a travesty that for two projects that are so ideologically aligned to work so poorly together. Nix should be able to run builds on bazel and bazel builds should decompose and cache into multiple store paths in a nix environment (think how poetry2nix works).

Re: Zb: An Early-Stage Build System

#52
post #18

Whoa, nifty. Can you speak more to the interop issues with Nix? I've been working on a pretty large Nix deployment in the robotics space for the past 3ish years, and the infrastructure side is the biggest pain point: * Running a bare `nix build` in your CI isn't really enough— no hosted logs, lack of proper prioritization, may end up double-building things. * Running your own instance of Hydra is a gigantic pain; it'…

Why are flakes such a deal-breaker? While not ideal, you can still tag your versions in the .nix file instead of the lockfile. I even had to avoid flakes in a system I developed used by ~200 developers since it involved a non-nixos OS and it involved user secrets (Tokens etc...) So with flakes I had to keep track of the secrets (and was a pain point, since they obviously didn't have to push them into the git repo) bu…

As a new user, I learned flakes first, and the tie-in with git tags/branches and the corresponding cli ergonomics aren’t something I’d be able to give up.

Re: Zb: An Early-Stage Build System

#53

Whoa, nifty. Can you speak more to the interop issues with Nix? I've been working on a pretty large Nix deployment in the robotics space for the past 3ish years, and the infrastructure side is the biggest pain point: * Running a bare `nix build` in your CI isn't really enough— no hosted logs, lack of proper prioritization, may end up double-building things. * Running your own instance of Hydra is a gigantic pain; it'…

https://github.com/edolstra/flake-compat should make flakes work with tvix

Re: Zb: An Early-Stage Build System

#54

Earlier quoted context omitted.

That's accurate (unless the config file attempts to read something from the build process, that will trigger a build). It's a good point about debugging build problems. This is an issue I've experienced in Nix and Bazel as well. I'm not convinced that I have a great solution yet, but at least for my own debugging while using the system, I've included a `zb derivation env` command which spits out a .env file that matc…

Surface-level feedback: get rid of the word "derivation". Surely there must be a better way to describe the underlying thing...

What’s wrong with it? It’s a term of art that means a specific thing in both nix and guix; it’d just be confusing if zb renamed it to something else.

Re: Zb: An Early-Stage Build System

#55

I'm excited by this! Quick question: if the build graph can be dynamic (I think they call it monadic in the paper), then does it become impossible to reason about the build statically? I think this is why Bazel has a static graph and why it scales so well.

Buck2 can express dynamic dependencies, so it can capture dynamic compilation problems like C++ modules, OCaml/Fortran modules, etc. in "user space" without built-in support like Bazel requires. The secret to why is twofold. One, your internal build graph can be fully dynamic at the implementation level; rather, it's a matter of how much expressivity you expose to the user in letting them leverage and control the dynamic graph. Just because you have a Monad, doesn't mean you have to have to expose it. You can just expose an Applicative.

And actually, if you take the view that build systems are a form of staged programming, then all build systems are monadic because the first stage is building the graph at all, and the second stage is evaluating it. Make, for example, has to parse the Makefiles, and during this phase it constructs the graph... dynamically! Based on the input source code! Rather it is during the second phase done later, when rules are evaluated, and that is now the time when the graph is static and all edges must be known. See some notes from Neil Mitchell about that.[1]

The other key is in a system like Buck or Bazel, there are actually two graphs that are clearly defined. There is the target graph where you have abstract dependencies between things (a cxx_binary depends on a cxx_library), and there is the action graph (the command gcc must run before the ld command can run).

You cannot have dynamic nodes in the target graph. Target graph construction MUST be deterministic and "complete" in the sense it captures all nodes. This is really important because it breaks features like target determination: given a list of changed files, what changed targets need to be rebuilt? You cannot know the complete list of targets when the target graph is dynamic, and evaluation can produce new nodes. That's what everyone means when they say it's "scalable." That you can detect, only given a list of input files from version control, what the difference between these two build graphs are. And then you can go build those targets exactly and skip everything else. So, if you make a small change to a monumentally sized codebase, you don't have to rebuild everything. Just a very small, locally impacted part of the whole pie.

In other words, "small changes to the code should have small changes in the resulting build." That's incremental programming in a nutshell.

OK, so there's no target graph dynamism. But you can have dynamic actions in the action graph, where the edges to those dynamic actions are well defined. For example, compiling an OCaml module first requires you to build a .m file, then read it, then run some set of commands in an order dictated by the .m file. The output is an .a file. So you always know the in/out edges for these actions, but you just don't know what order you need to run compiler commands in. That dynamic action can be captured without breaking the other stuff. There are some more notes from Neil about this.[2]

Under this interpretation, Nix also defines a static target graph in the sense that every store path/derivation is a node represented as term in the pure, lazy lambda calculus (with records). When you evaluate a Nix expression, it produces a fully closed term, and terms that are already evaluated previously (packaged and built) are shared and reused. The sharing is how "target determination" is achieved; you actually evaluate everything and anything that is shared is "free."

And under this same interpretation, the pure subset of Zb programs should, by definition, also construct a static target graph. It's not enough to just sandboxing I/O but also some other things; for example if you construct hash tables with undefined iteration order you might screw the pooch somewhere down the line. Or you could just make up things out of thin air I guess. But if you restrict yourself to the pure subset of Zb programs, you should in theory be fine (and that pure subset is arguably the actual valuable, useful subset, so it's maybe fine.)

[1] https://ndmitchell.com/downloads/paper-implementing_applicat...

[2] https://ndmitchell.com/downloads/slides-somewhat_dynamic_bui...

Re: Zb: An Early-Stage Build System

#56

Earlier quoted context omitted.

That's accurate (unless the config file attempts to read something from the build process, that will trigger a build). It's a good point about debugging build problems. This is an issue I've experienced in Nix and Bazel as well. I'm not convinced that I have a great solution yet, but at least for my own debugging while using the system, I've included a `zb derivation env` command which spits out a .env file that matc…

One thing I like to see is a 'dry run' like 'make -n'. Although, maybe that's not possible in all cases. Another possibility might be to output a something like a shell script that would do a rebuild the same way, so you can see what it did and hack it when debugging.

Yes. Dry runs at least, and better yet terraform-style planning that produces an artifact that can be applied. These should really be more common with all kinds of software

Re: Zb: An Early-Stage Build System

#57
Whatever choices this project makes (I have some opinions, but I think they're not too important) I don't see it mentioning one of the most absolutely critical choices Nix made that was absolutely key to its insane success (at least, IMO, as a hardcore contributor and user for like 10+ years): the monorepo, containing all of the packages and all the libraries for use by everyone downstream, and all contributions trying to go there.

Please do not give into the temptation to just write a version manager and stitch together some hodgepodge and throw the hard problem over the fence to the "community", a set of balkanized repositories to make everything work. It is really really really hard to overstate how much value Nixpkgs gets from going the monorepo route and how much the project has been able to improve, adapt, and overcome things thanks to it. It feels like Nixpkgs regularly pulls off major code-wide changes on an average Tuesday that other projects would balk at.

(It's actually a benefit early on to just keep everything in one repo too, because you can just... clean up all the code in one spot if you do something like make a major breaking change. Huge huge benefit!)

Finally: as a die hard Nix user, I also have been using Buck2 as a kind of thing-that-is-hermetic-cloud-based-and-supports-Windows tool, and it competes in the same space as Zb; a monorepo containing all BUILD files is incredibly important for things to work reliably and it's what I'm exploring right now and seeing if that can be viable. I'm even exploring the possibility of starting from stage0-posix as well. Good luck! There's still work to be done in this space and Nix isn't the final answer, even if I love it.

Re: Zb: An Early-Stage Build System

#58
post #42

How do you pronounce "Zb"? Zee-bee?

Heh, I think I need to add something to the README. I've been pronouncing it as "zeeb" in my head as in the first syllable Zebesian Space Pirate from Metroid, but TIL that that's canonically "Zay-bay-zee-uhn" so idk. Naming is hard.

I kinda dig zeeb! Naming is hard. Really awesome project by the way! Should have mentioned that first. Build systems are neat. I've always wanted to try building a build system, in a "learn how it works" sense, not so much "yet another build tool".

Re: Zb: An Early-Stage Build System

#59
post #19

Earlier quoted context omitted.

I've been wondering idly if it's possible for Nix to support the Bazel Remote Execution API that seems to be catching on[1] more generally. [1] https://github.com/bazelbuild/remote-apis?tab=readme-ov-file...

I’m very interested in better bidirectional interop between bazel and nix; it seems such a travesty that for two projects that are so ideologically aligned to work so poorly together. Nix should be able to run builds on bazel and bazel builds should decompose and cache into multiple store paths in a nix environment (think how poetry2nix works).

If you're attending BazelCon I'd love to have a chat with you about this stuff in some more detail. (If you're not I'd still love to have a chat!)

Re: Zb: An Early-Stage Build System

#60

I'd like to know more about the "Support for non-determinism" and how that differs from other build systems. Usually, build systems rerun actions when at least one of the inputs has changed. Are non-deterministic targets rerun all the time? Also, I'm curious to know if you've considered using Starlark or the build file syntax used in multiple other recent build systems (Bazel, Buck, Please, Pants).

(Hi! I recognize your name from Bazel mailing lists but I forget whether we've talked before.) I'm mostly contrasting from Nix, which has difficulty with poisoning cache when faced with non-deterministic build steps when using input-addressing (the default mode). If zb encounters a build target with multiple cached outputs for the same inputs, it rebuilds and then relies on content-addressing to obtain build outputs…

I mean, to be fair, Nix is nothing more than a big ass pile of genrule() calls, at the end of the day. Everything is really just genrule. Nix just makes it all work with the sandbox it puts all builds in. Bazel has an equivalent sandbox and I'm pretty sure you can sandbox genrule so it's in a nice, hermetic container. (Side note, but one of my biggest pet peeves is that Nix without the sandbox is actually fundamentally _broken_, yet we let people install it without the sandbox. I have no idea why "Install this thing in a broken way!" is even offered as an option. Ridiculous.)

The way Nix-like systems achieve hermetic sandboxing isn't so much a technical feat, in my mind. That's part of it -- sure, you need to get rid of /dev devices, and every build always has to look like it happens at /tmp/build within a mount namespace, and you need to set SOURCE_EPOCH_DATE and blah blah, stuff like that.

But it's also a social one, because with Nix you are expected to wrap arbitrary build systems and package mechanisms and "go where they are." That means you have to bludgeon every random hostile badly written thing into working inside the sandbox you designed, carve out exceptions, and write ptaches for things that don't -- and get them working in a deterministic way. For example, you have to change the default search paths for nearly every single tool to look inside calculated Nix store path. That's not a technical feat, it's mostly just a huge amount of hard work to write all the abstractions, like buildRustPackage or makeDerivation. You need to patch every build system like CMake or Scons in order to alleviate some of their assumptions, and so on and so forth.

Bazel and Buck like systems do not avoid this pain but they do pay for it in a different way. They don't "go where they are", they expect everyone to "come to them." Culturally, Bazel users do not accept "just run Make under a sandbox" nearly as much. The idea is to write everything as a BUILD file rule, from scratch rewriting the build system, and those BUILD files instead should perform the build "natively" in a way that is designed to work hermetically. So you don't run ./configure, you actually pick an exact set of configuration options and build with that 100% of the time. Therefore, the impurities in the build are removed "by design", which makes the strict requirements on a sandbox somewhat more lenient. You still need the sandbox, but by definition your builds are much more robust anyway. So you are trading the pain of wrapping every system for the pain of integrating every system manually. They're not the same thing but have a lot of overlap.

So the answer is, yes you can write impure genrules, but the vast majority of impurity is totally encapsulated in a way that forces it to be pure, just like Nix, so it's mostly just a small nit rather than truly fundamental. The real question is a matter of when you want to pay the pied piper.

Post reply on HN