-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recursive Nix #13
Comments
I agree about the end-goal, but I'm not sure I like (or understand) the means to get there. What exactly would it mean for a builder to realize a derivation while realizing a derivation? Will realization now require knowledge of the Nix language instead of just the lower-level derivation language? Doesn't this remove a lot of the security of all inputs being taken into account by the hash? I think dynamic import statements are a better approach to this issue. They already exist, they require no modifications to how realisation works, and IMO they better preserve the currently straightforward relationship between the contents of the .drv file and the actions nix takes in realising it. The issues with querying are fixable, I think, by only doing instantiation-time realisation when absolutely necessary for the information requested and when the user allows it (by command line flag or nix.conf setting), and otherwise either failing gracefully or filling in dummy information. I also think a case can be made that a query that doesn't take into account all the relevant derivations (as would happen if realizing a derivation could lead to the realization of arbitrary other derivations) is a broken query anyway. Do you think it would be possible for you to give a list of blockers that would need to be addressed before you'd be OK with dynamic imports in nixpkgs? |
The idea is that a builder could unpack a source tree containing (among other things) some Nix expressions and call "nix-build -A foo" to build them, just as it can call "make" to build a Make-based package. This is entirely pure, but there is one problem: Nix won't know that the store path produced by the outer build has a potential runtime dependency on the output of the inner build. This is because the hash scanner only looks for paths that appeared in the closure of the inputs. It doesn't know that there is a potential dependency on "foo". The only thing that we need is for the inner Nix to signal the outer Nix that it's building some paths on behalf of the outer build. The outer Nix would then add these paths to the set of hashes to be scanned for. One simple way to do this:
One minor issue is chroot builds, since there the inner Nix doesn't have access to the complete store. This could be fixed by making the Unix domain socket of the Nix worker available in the chroot. What do you think? Would there be some way to violate purity with this mechanism? |
Will inner nix-instantiates have access to any out-of-store nix expressions (e.g. nixpkgs)? If so, a single .drv could result in wildly different outputs depending on which version of nixpkgs is present. If not, each inner nix expression will have to bootstrap its dependencies from the ground up (leading to huge amounts of duplication of work and outputs), or nixpkgs itself will need to be an input of the top-level derivation, in which case every nixpkgs checkout will require rebuilding every package that uses nix-build in the builder. Also, this greatly complicates things and makes queries much less useful. The nice subset relationship between build-time dependencies and run-time dependencies is lost. It's possible the above problem can be fixed and purity thereby maintained, but it makes derivations much less declarative: a change in inputs is only reflected in a change in a tarball's hash instead of in an easily machine-traceable chain of dependencies. It will require new checks for cyclic dependencies to avoid a build doing infinite nix-build recursion. Why is this preferable to fixing how queries handle dynamic imports? Do you disagree that queries will lose a lot of their value anyway if this system were put into place? |
It's preferable because
Not having a single graph is too bad, but pretty much unavoidable for these reasons. I don't see why new checks for cyclic dependencies are necessary. A build can always go into an infinite loop. I haven't really thought about the Nix expressions used by the inner build, but I think the Nixpkgs source tree (or whatever it wants to use) should be passed in as an ordinary dependency. It could be a copy of the outer Nixpkgs tree though (i.e. you pass "nixpkgsSrc = pkgs.path;" as an attribute). That would require a rebuild of the package if the Nixpkgs tree changes, but if it primary does a nix-build to build itself, a rebuild wouldn't take a lot of time. |
Ok. I think the first point can be fixable (and really leave us no worse off than queries in the recursive build scenario) by just filling in dummy information when a nix-env -qa would have required a build, but your second point is harder to overcome. Maybe having some sort of max depth for queries, or making it so they never recurse into dynamic imports even if the derivation is already built? I'm not sure. I guess I'll just build it instead of talking about it so we can actually see what's possible. WRT cyclic dependencies: Can we be sure nothing insane (beyond an infinite recursion or a single build failure) happens if an inner build tries to use an outer build as one of its dependencies? Suppose glibc one day uses nix in place of make, and uses nixpkgs' bootstrap to build itself. Won't the entire system have to be rebuilt when nixpkgs changes? Sure, the glibc build itself will be fast, but what about everything else? |
I think the inner build should only be able to use its own Nix expressions and anything the outside wants to pass through (known statically). So it should not be able to import < nixpkgs > or whatever. For projects using make, it's basically the same. Everything they need is provided thought buildInputs and the like. I can't think of a good usecase for letting the inside sniff around. The barrier @edolstra talks about should hold 2 ways. If a package wants to use nixpkgs, it should just provide an expression for nixpkgs(outside) which sets this up. What would be the usecase for having the inside nix expression import/depend on stuff in nixpkgs without statically clarifying this on the outside? |
FWIW, after a nice long civil discussion I think I may have convinced @edolstra that the import-from-derivation route might be better in the long run... So recursive nix may not happen. |
Can you explain a bit about that route? |
If you import from a path that is based on a derivation (e.g. The problem with this currently is that the derivation will be built even if you're just querying the package. Most 'sane' imports-from-derivation will probably be just download and unpack a tarball, but even then you don't want to download 100 tarballs just to do |
Cool, tnx for the info 👍 I'll have a look at #52 then, it sounds like a nice solution indeed |
@shlevy Are there any examples of build scripts that do this currently that I could take a look at? |
What about writing the builder in nix itself instead? I propose a "do" syntactic sugar for a possible ">>" sequential operator. Then a builder:
and so on. Those $x have to be expanded in the builder environment, and must follow the >>. That is: |
Monads! |
Yes monads. In a dynamic language like nix shouldn't be hard to achieve. I propose myself to implement/design (or help implementing/designing) such stuff. You are against monads? Don't understand much the nature of your comment :-P |
Phase separation, such as quoting the |
This was originally removed in d4d0e44. The intent was not to maintain hydra expression at two places. Nowadays we have enough devs to maintain this despite copy/pasta. This should encourage more people to use Hydra, which is a really great piece of software together with Nix. Tested a deploy using https://github.com/peti/hydra-tutorial
IntroductionI spent most of my internship this past summer at Awake Security on making Haskell builds incremental via IFD, and came to the conclusion that any solution to the incremental build problem that involves the build system is going to be brittle and is going to involve essentially reimplementing the logic of your build system in Nix. The right way to go is recursive Nix. My experience with incrementalization via IFDI went into this project leaning heavily towards IFD as a solution to the incremental builds via Nix problem. I thought that it was a much more elegant solution when compared to recursive Nix. The approach I had in mind would have been fairly reusable across different build systems as I was using Ninja as an intermediate representation (i.e.: splitting the task into two tools, In any case, I still think that, if we are dead-set on not using recursive Nix, the
Method 1 is generally a lot of work, even for relatively well-written build systems like Cabal, and in some cases (GNU Make, CMake, autotools) is basically impossible. Method 2 is the easiest option to get working (in the worst case, it would end up linearizing the build graph), but seems tricky to get right, as it depends on you being able to figure out all the information that needs to be printed to recover the dependency graph. If the build system changes, it also seems like it could be very easy for bugs to creep in, as the log might stop being consistent with what you had in mind when you wrote the log "parser". Method 3 is about as hard as Method 1, and has a huge maintenance burden, as you need to keep the semantics of your reimplementation up to date with the actual build system. Recursive Nix is the right abstractionAll of the brittleness and difficulty above comes from the fact that we have to care about the build system, and build systems are, in general, very complicated. In contrast, compilers tend to be relatively simple, and in most cases caching each compiler invocation is sufficient to get an incremental build (granted, this is not the case for GHC, but that problem seems relatively tractable, and even if we can't solve it we still get caching at the level of Cabal components). IFD is also slow, unsuitable for Hydra (without changes to the Hydra evaluator), and creates an enormous build graph. In a world where every build system is written in Nix, it might make sense, but if you want any kind of compatibility with less-well-engineered build systems it becomes a really hard problem. ConclusionI think a massive amount of developer time and compute power is being thrown down the drain every day because we don't have this feature, and it should probably be priority number 2 after the UI improvement work. AFAIK there is considerable interest in incremental builds among industrial users of Nix (Awake Security, Takt, IOHK, Obsidian Systems). Everyday users of Nix(OS) would probably also benefit greatly; in fact, it feels like pretty much every time someone mentions a limitation of Nix it boils down to "you can't safely share work between invocations of nix-build". I won't be at NixCon this year, but I hope there is a healthy discussion about this feature. |
The way I link to think about this is that a lot of existing build tools have their own approach to caching build products. Recursive Nix lets you transparently modify them to use Nix to cache their intermediate build products instead Here's a very common example from our own work environment for developing Haskell packages:
The elegant solution would be for There are some other benefits of this approach: For example, this would improve
... you get a wasteful build for the final step since This would also allow users within an organization to share their intermediate build products with each other or to download intermediate build products from a shared cache (i.e. Hydra). I wouldn't need to build anything to seed my local cache of built intermediate modules since I can just download it from Hydra |
@taktoa @Gabriel439 Sorry, I could use some clarification. How does recursive nix enable incremental building? The only way I can think of to do it would be to reduce e.g. a Haskell derivation to one derivation per module, and somehow coax Cabal into doing |
what if the then it would become a pure nixified version of ccache |
@ElvishJerricco Having Recursive Nix means that we only need to worry about building a drop-in replacement for |
This deadlocks ProgressBar, e.g. # nix run --impure --no-substitute --store '/tmp/nix2?store=/foo' --expr 'derivation { builder = /nix/store/zi90rxslsm4mlr46l2xws1rm94g7pk8p-busybox-1.31.1-x86_64-unknown-linux-musl/bin/busybox; }' leads to Thread 1 (Thread 0x7ffff6126e80 (LWP 12250)): #0 0x00007ffff7215d62 in __lll_lock_wait () from /nix/store/9df65igwjmf2wbw0gbrrgair6piqjgmi-glibc-2.31/lib/libpthread.so.0 #1 0x00007ffff720e721 in pthread_mutex_lock () from /nix/store/9df65igwjmf2wbw0gbrrgair6piqjgmi-glibc-2.31/lib/libpthread.so.0 #2 0x00007ffff7ad17fa in __gthread_mutex_lock (__mutex=0x6c5448) at /nix/store/h31cy7jm6g7cfqbhc5pm4rf9c53i3qfb-gcc-9.3.0/include/c++/9.3.0/x86_64-unknown-linux-gnu/bits/gthr-default.h:749 #3 std::mutex::lock (this=0x6c5448) at /nix/store/h31cy7jm6g7cfqbhc5pm4rf9c53i3qfb-gcc-9.3.0/include/c++/9.3.0/bits/std_mutex.h:100 #4 std::unique_lock<std::mutex>::lock (this=0x7fffffff09a8, this=0x7fffffff09a8) at /nix/store/h31cy7jm6g7cfqbhc5pm4rf9c53i3qfb-gcc-9.3.0/include/c++/9.3.0/bits/unique_lock.h:141 #5 std::unique_lock<std::mutex>::unique_lock (__m=..., this=0x7fffffff09a8) at /nix/store/h31cy7jm6g7cfqbhc5pm4rf9c53i3qfb-gcc-9.3.0/include/c++/9.3.0/bits/unique_lock.h:71 #6 nix::Sync<nix::ProgressBar::State, std::mutex>::Lock::Lock (s=0x6c5448, this=0x7fffffff09a0) at src/libutil/sync.hh:45 #7 nix::Sync<nix::ProgressBar::State, std::mutex>::lock (this=0x6c5448) at src/libutil/sync.hh:85 #8 nix::ProgressBar::logEI (this=0x6c5440, ei=...) at src/libmain/progress-bar.cc:131 #9 0x00007ffff7608cfd in nix::Logger::logEI (ei=..., lvl=nix::lvlError, this=0x6c5440) at src/libutil/logging.hh:88 #10 nix::getCodeLines (errPos=...) at src/libutil/error.cc:66 #11 0x00007ffff76073f2 in nix::showErrorInfo (out=..., einfo=..., showTrace=<optimized out>) at /nix/store/h31cy7jm6g7cfqbhc5pm4rf9c53i3qfb-gcc-9.3.0/include/c++/9.3.0/optional:897 #12 0x00007ffff7ad19e7 in nix::ProgressBar::logEI (this=0x6c5440, ei=...) at src/libmain/progress-bar.cc:134 #13 0x00007ffff7ab9d10 in nix::Logger::logEI (ei=..., lvl=nix::lvlError, this=0x6c5440) at src/libutil/logging.hh:88 #14 nix::handleExceptions(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::function<void ()>) (programName="/home/eelco/Dev/nix/outputs/out/bin/nix", fun=...) at src/libmain/shared.cc:328 #15 0x000000000046226b in main (argc=<optimized out>, argv=<optimized out>) at /nix/store/h31cy7jm6g7cfqbhc5pm4rf9c53i3qfb-gcc-9.3.0/include/c++/9.3.0/ext/new_allocator.h:80
I marked this as stale due to inactivity. → More info |
I marked this as stale due to inactivity. → More info |
@edolstra what is the status? What are the next steps to fully support this? |
@davidak See RFC 92 for a related alternative, though it's going to be reworked a bit and reworded quite a lot. |
I marked this as stale due to inactivity. → More info |
We have an unstable experimental feature to track, so this should stay open. |
We now also have a label for tracking the feature.
recursive-nix
|
Nix builders should be able to call Nix to build things. This is essential if we want to use Nix as a "low-level" build tool (i.e. as a Make replacement), since then we need to support derivations that unpack a source distribution containing a Nix expression to do the rest of the build.
The text was updated successfully, but these errors were encountered: