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 matches the environment the builder runs under. I'd like to extend that to pop open a shell.
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.
Thing is the plan/apply split isn’t even primarily for users necessarily, it’s just good design. It makes testing easier, and leaves open the possibility for plugging in totally different resolution strategies without rewriting the whole core. The benefits are so big there that I’d strongly prefer that more software is using it more often, even if I’m going to shut my eyes and auto apply every time without glancing at the plan.
I could never have pulled off what this guy did https://roscidus.com/blog/blog/2021/03/07/qubes-lite-with-kv..., though ironically his journal is probably one of the best “how nix actually works” tutorials I’ve ever seen, even though it isn’t intended for that or complete for such a purpose. He’s the only reason I know that a derivation is basically an intermediate build object.
You'll spend a LOT of time fighting the system, which gets old fast. Docker may have a sucky plan format (and they STILL won't let you set your goddamn MAC address), but it's good enough for most things and not too terrible to learn.
Any time you put in a key that it doesn't recognize, it just gets ignored. So you get to spend hours/days trying to figure out why the hell your system won't do what you told it to, and won't even validate that what you put in makes sense.
A “package” is a high-level description (written in scheme) of how to build something, like: “using the GNU build system with inputs a, b, c, and configure flags x, y, z, build the source available at https://github.com/foo/bar”
The actual builder daemon doesn’t know about the GNU build system, or how to fetch things from GitHub, or how to compute nested dependencies, etc.; it is very simple. All it knows is how to build derivations, which are low-level descriptions of how to build something: “create a container that can see paths a, b, and c (which are themselves other derivations or files stored in the store and addressed by their hash), then invoke the builder script x.”
So when you ask guix to build something, it reads the package definition, finds the source and stores it in the store, generates the builder script (which is by convention usually also written in scheme, though theoretically nothing stops you from defining a package whose builder was written in some other language), computes the input derivation paths, etc., and ultimately generates a derivation which it then asks the daemon to build.
I believe in Nix, rather than scheme, packages are written in nix lang and builder scripts can be written in any language but by convention are usually bash.
So basically long story short, the package is the higher-level representation on the guix side, and the derivation is the lower-level representation on the guix-daemon side.
The same is true for "flakes". It's a uniquely Nix term with no previous technical usage AFAIK.
Ideally you want to avoid using specialized terms if possible. But if you do decide to do that, then your documentation needs to be very clear and precise, which is another thing that Nix(OS) spectacularly fumbles. Take this page[2] that's supposed to explain derivations, for example. The first sentence has a circular reference to the term, only mentioning in parenthesis that it's a "build task". So why not call it that? And why not start with the definition first, before bringing up technical terms like functions and attributes? There are examples like this in many places, even without general problems of it being outdated or incomplete.
Though I don't think going the other way and overloading general terms is a good idea either. For example, Homebrew likes to use terms like "tap" and "bottle" to describe technical concepts, which has the similar effect of having to explain what the thing actually is.
Docker is a good example of getting this right: containers, images, layers, build stages, intermediate images, etc. It uses already familiar technical terms and adopts them where they make most sense. When you additionally have excellent documentation, all these things come together for a good user experience, and become essential to a widespread adoption of the tool.
[1]: https://discourse.nixos.org/t/what-is-a-derivation/28311/6
and then i go to nix.dev and derivations are presented after fetchers? no surprise it’s so confusing, even though the concept is quite simple.
a derivation is a dict that is composed of (1) a shell script and (2) environment parameters it will have access to. a nix command will read the derivation, create the environment with only these parameters and execute the script. that’s it.
everything else about nix language is about building derivations. like copying files into its store. for example, evaluating “${pkgs.hello}” will be interpolated into a path. so in your derivation, you can define an env variable “hello = ${pkgs.hello}/bin” and it will be available in your script as “$hello” and will have the value of “/nix/store/<hash>-hello/bin”. nix will do the fetching and storing for you. so you can have “command $hello” in your script. neat!
play around with evaluating the ‘derivation’ built-in function.
I tried to dabble in Nix several times and the term never stuck.
I suppose for you it's impossible to accept that the term is just bad and unintuitive. And other comments here say the same.
I for one can't trace the train of thought that is going from "intermediate build artifact" and somehow arrives at "derivation".
I found out just enough about Nix to reject it. My take is still informed, I simply didn't buy its pitch.
It's really not, though. String is a common technical term used in programming languages for many decades. If a new language decided to call them "textrons", _that_ would be weird. And this is the exact thing Nix did with "derivations", "flakes", etc. There is no precedent for these terms in other software, so they're unfamiliar even to its core audience.
It would be different if Nix invented an entirely new branch of technology that didn't have any known precedent. But for a reproducible build system that uses a declarative language? C'mon.
No idea why you keep digging at this, the takeaway was clear at least three comments ago.
Say hi to the others in your club of "I'm gonna pretend I didn't get it for no reason whatsoever" for me.
If I understand the architecture correctly, the imperative calls in the config file don't actually run the build process. They run a Builder Pattern that sets up the state machine necessary for the builds to happen. So it's a bit like LINQ in C# (but older).
I have no idea how that plays out single-step debugging build problems though. That depends on how it's implemented and a lot of abstractions (especially frameworks) seem to forget that breakpoints are things other people want to use as well.