RFD: deprecate and remove fetch-closure experimental feature #1010

Open
opened 2025-10-11 16:33:39 +00:00 by raito · 3 comments
Owner

fetchClosure is a new built-in that appeared in 2022 in CppNix 2.8.0.

Its description in the release notes are as follows:

  • New experimental builtin function builtins.fetchClosure that
    copies a closure from a binary cache at evaluation time and rewrites
    it to content-addressed form (if it isn't already). Like
    builtins.storePath, this allows importing pre-built store paths;
    the difference is that it doesn't require the user to configure
    binary caches and trusted public keys.

In practice, fetchClosure knows how to do more than that:

  • rewrite non-CA path to CA
  • reject non-CA paths at fetching time
  • reject CA paths at fetching time

Users of fetchClosure as far as I could tell are:

Both use cases are somewhat of an antipattern in Nix semantics. If the user cannot fetch a program directly via the substituter mechanism and fall back to local build, this is a feature AND a misconfiguration. If the user cannot build certain derivations because they are too expensive, the build directives should pass -j0 or similar.

As for the second usecase, there's a different way to do it that also allows to have a way to reproduce the paths that are hardcoded in that file, perform import (fetchurl "https://my-cache/${hashparts storepath}.drv") rather, i.e. an IFD to a possibly well known name. The backend can generate them on the fly or once, and possess stable names.

Finally, as for the non-CA → CA features, Lix removed ca-derivations. fetchClosure offers ca-derivations-like features which suffers from similar shortcomings albeit lessened. It only follows that we should rather deprecate and remove these capabilities.

Proposed timeline:

  • Deprecate and remove non-CA → CA features immediately
  • Deprecate fetchClosure (deprecated-features in 2.94.0)
  • Remove fetchClosure on the next release (2.95.0)
`fetchClosure` is a new built-in that appeared in 2022 in CppNix 2.8.0. Its description in the release notes are as follows: > * New experimental builtin function `builtins.fetchClosure` that > copies a closure from a binary cache at evaluation time and rewrites > it to content-addressed form (if it isn't already). Like > `builtins.storePath`, this allows importing pre-built store paths; > the difference is that it doesn't require the user to configure > binary caches and trusted public keys. In practice, `fetchClosure` knows how to do more than that: - rewrite non-CA path to CA - reject non-CA paths at fetching time - reject CA paths at fetching time Users of `fetchClosure` as far as I could tell are: - devbox — https://github.com/jetify-com/devbox/blob/main/internal/shellgen/tmpl/flake.nix.tmpl — this is a way to prevent users from having to build any package and force going via the declared cache. - FlyingCircus package overlay — https://github.com/flyingcircusio/fc-nixos/blob/fc-25.11-dev/pkgs/overlay.nix — this is a way to use ancient/old software without paying the evaluation cost of a second nixpkgs. Both use cases are somewhat of an antipattern in Nix semantics. If the user cannot fetch a program directly via the substituter mechanism and fall back to local build, this is a feature AND a misconfiguration. If the user *cannot* build certain derivations because they are too expensive, the build directives should pass `-j0` or similar. As for the second usecase, there's a different way to do it that also allows to have a way to reproduce the paths that are hardcoded in that file, perform `import (fetchurl "https://my-cache/${hashparts storepath}.drv")` rather, i.e. an IFD to a possibly well known name. The backend can generate them on the fly or once, and possess stable names. Finally, as for the non-CA → CA features, Lix removed `ca-derivations`. `fetchClosure` offers `ca-derivations`-like features which suffers from similar shortcomings albeit lessened. It only follows that we should rather deprecate and remove these capabilities. Proposed timeline: - Deprecate and remove non-CA → CA features immediately - Deprecate `fetchClosure` (`deprecated-features` in 2.94.0) - Remove `fetchClosure` on the next release (2.95.0)
Owner

fetchurl is not actually IFD! the fetch runs during eval, creating creating an immediate store path that is not a derivation output. and the ca rewriting things absolutely should go away, they're broken. we'd also like to get rid of make-content-addressed, but, yeah.

unfortunately it also turns out that it isn't actually this simple. importing drv files and getting runtime derivation back only works on files that are already in the store. we would still need a variant of fetchClosure to make these appear if we wanted to do it with straight import. there's also the option of having a smarter cache that returns json-formatted derivation objects that are reconstituted after fetch, but that's even more horrid than import. we could do actual ifd to materialize the derivations and then import them though, like seq (readFile (toDerivation drvpath)) (import drvpath) with toDerivation from nixpkgs lib. this seems to work perfectly fine and causes a ton of substitution, but no build or eval activity as such. this trick works more directly with store paths (where toDerivation directly creates a substitutable entity from a non-present store path as long as some substituters has it), no ifd required. the latter also has the nice side effect of substitutions not happening during eval, which can speed things up significantly.

tl;dr we don't even need to go through importing drvs, just slamming a store path into a derivation-ish set emulates fetchClosure (excluding ca rewrites) pretty well. it also doesn't block eval, which makes it superior to fetchClosure in every meaningful way

fetchurl is not actually IFD! the fetch runs during eval, creating creating an immediate store path that is not a derivation output. and the ca rewriting things absolutely should go away, they're broken. we'd also like to get rid of `make-content-addressed`, but, yeah. unfortunately it also turns out that it isn't actually this simple. importing drv files and getting runtime derivation back only works on files that are *already in the store*. we would still need a variant of fetchClosure to make these appear if we wanted to do it with straight `import`. there's also the option of having a smarter cache that returns json-formatted derivation objects that are reconstituted after fetch, but that's even more horrid than import. we could do *actual* ifd to materialize the derivations and then import them though, like `seq (readFile (toDerivation drvpath)) (import drvpath)` with toDerivation from nixpkgs lib. this seems to work perfectly fine and causes a ton of substitution, but no build or eval activity as such. this trick works more directly with store paths (where toDerivation directly creates a substitutable entity from a non-present store path as long as some substituters has it), no ifd required. the latter also has the nice side effect of substitutions not happening during eval, which can speed things up significantly. tl;dr we don't even *need* to go through importing drvs, just slamming a store path into a derivation-ish set emulates fetchClosure (excluding ca rewrites) pretty well. it also doesn't block eval, which makes it superior to fetchClosure in every meaningful way
Owner

I have an unusual use case for this: at work we're building store paths with references and ca metadata from buck2 targets, which are totally opaque to nix because they are imported directly into the store and there's absolutely no way to express them as a derivation. Currently to consume these from nix language, we use appendContext crimes, but I've not at all checked whether substitution of these opaque paths works. It's possible that we could use this builtin but we're only likely to do that if it is found to be mandatory as experimental features are preferable to avoid.

We do not support self references and we have no intent to use the rewrite mechanism in any way; these are morally just FODs with references.

I have an unusual use case for this: at work we're building store paths with references and ca metadata from buck2 targets, which are totally opaque to nix because they are imported directly into the store and there's absolutely no way to express them as a derivation. Currently to consume these from nix language, we use appendContext crimes, but I've not at all checked whether substitution of these opaque paths works. It's possible that we could use this builtin but we're only likely to do that if it is found to be mandatory as experimental features are preferable to avoid. We *do not* support self references and we have no intent to use the rewrite mechanism in any way; these are morally just FODs with references.
Owner

you can toDerivation aStorePathThatExistsOnSomeCache and receive an object that is substitutable with that store path and collapses to that store path in most places. all non-technical store paths can be substituted, opaque or not, which is also why you can do and receive error messages like this (because cno doesn't cache drvs):

❯ nix build /nix/store/rl5m4zxd24mkysmpbp4j9ak6q7ia6vj8-hello-2.12.2.drv # or any drv for a cached path, really
error: path '/nix/store/rl5m4zxd24mkysmpbp4j9ak6q7ia6vj8-hello-2.12.2.drv' is required, but there is no substituter that can build it
you can `toDerivation aStorePathThatExistsOnSomeCache` and receive an object that is substitutable with that store path and collapses to that store path in most places. all non-technical store paths can be substituted, opaque or not, which is also why you can do and receive error messages like this (because cno doesn't cache drvs): ``` ❯ nix build /nix/store/rl5m4zxd24mkysmpbp4j9ak6q7ia6vj8-hello-2.12.2.drv # or any drv for a cached path, really error: path '/nix/store/rl5m4zxd24mkysmpbp4j9ak6q7ia6vj8-hello-2.12.2.drv' is required, but there is no substituter that can build it ```
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lix-project/lix#1010
No description provided.