Register flake source trees as GC roots

This ensures that flakes don't get garbage-collected, which is
important to get nix-channel-like behaviour.

For example, running

  $ nix build hydra:

will create a GC root

  ~/.cache/nix/flake-closures/hydra -> /nix/store/xarfiqcwa4w8r4qpz1a769xxs8c3phgn-flake-closure

where the contents/references of the linked file in the store are the
flake source trees used by the 'hydra' flake:

  /nix/store/n6d5f5lkpfjbmkyby0nlg8y1wbkmbc7i-source
  /nix/store/vbkg4zy1qd29fnhflsv9k2j9jnbqd5m2-source
  /nix/store/z46xni7d47s5wk694359mq9ay353ar94-source

Note that this in itself is not enough to allow offline use; the
fetcher for the flakeref (e.g. fetchGit or downloadCached) must not
fail if it cannot fetch the latest version of the file, so long as it
knows a cached version.

Issue #2868.
This commit is contained in:
Eelco Dolstra 2019-05-23 23:42:13 +02:00
parent 6b77bfc28d
commit 90fe1dfd2f
3 changed files with 63 additions and 3 deletions

View file

@ -132,6 +132,8 @@ struct ResolvedFlake
ResolvedFlake resolveFlake(EvalState &, const FlakeRef &, HandleLockFile);
void callFlake(EvalState & state, const ResolvedFlake & resFlake, Value & v);
void updateLockFile(EvalState &, const FlakeRef & flakeRef, bool recreateLockFile);
void gitCloneFlake(FlakeRef flakeRef, EvalState &, Registries, const Path & destDir);

View file

@ -10,6 +10,7 @@
#include "primops/flake.hh"
#include <regex>
#include <queue>
namespace nix {
@ -162,6 +163,44 @@ struct InstallableAttrPath : InstallableValue
}
};
void makeFlakeClosureGCRoot(Store & store, const FlakeRef & origFlakeRef, const ResolvedFlake & resFlake)
{
if (std::get_if<FlakeRef::IsPath>(&origFlakeRef.data)) return;
/* Get the store paths of all non-local flakes. */
PathSet closure;
std::queue<std::reference_wrapper<const ResolvedFlake>> queue;
queue.push(resFlake);
while (!queue.empty()) {
const ResolvedFlake & flake = queue.front();
queue.pop();
if (!std::get_if<FlakeRef::IsPath>(&flake.flake.resolvedRef.data))
closure.insert(flake.flake.storePath);
for (const auto & dep : flake.flakeDeps)
queue.push(dep.second);
}
if (closure.empty()) return;
/* Write the closure to a file in the store. */
auto closurePath = store.addTextToStore("flake-closure", concatStringsSep(" ", closure), closure);
Path cacheDir = getCacheDir() + "/nix/flake-closures";
createDirs(cacheDir);
auto s = origFlakeRef.to_string();
assert(s[0] != '.');
s = replaceStrings(s, "%", "%25");
s = replaceStrings(s, "/", "%2f");
s = replaceStrings(s, ":", "%3a");
Path symlink = cacheDir + "/" + s;
debug("writing GC root '%s' for flake closure of '%s'", symlink, origFlakeRef);
replaceSymlink(closurePath, symlink);
store.addIndirectRoot(symlink);
}
struct InstallableFlake : InstallableValue
{
FlakeRef flakeRef;
@ -182,7 +221,11 @@ struct InstallableFlake : InstallableValue
{
auto vFlake = state.allocValue();
makeFlakeValue(state, flakeRef, cmd.getLockFileMode(), *vFlake);
auto resFlake = resolveFlake(state, flakeRef, cmd.getLockFileMode());
callFlake(state, resFlake, *vFlake);
makeFlakeClosureGCRoot(*state.store, flakeRef, resFlake);
auto vProvides = (*vFlake->attrs->get(state.symbols.create("provides")))->value;

View file

@ -5,7 +5,10 @@ if [[ -z $(type -p git) ]]; then
exit 99
fi
export _NIX_FORCE_HTTP=1
clearStore
rm -rf $TEST_HOME/.cache
registry=$TEST_ROOT/registry.json
@ -14,7 +17,7 @@ flake2Dir=$TEST_ROOT/flake2
flake3Dir=$TEST_ROOT/flake3
for repo in $flake1Dir $flake2Dir $flake3Dir; do
rm -rf $repo
rm -rf $repo $repo.tmp
mkdir $repo
git -C $repo init
git -C $repo config user.email "foobar@example.com"
@ -142,7 +145,7 @@ nix build -o $TEST_ROOT/result --flake-registry $registry flake2:bar
# Or without a registry.
# FIXME: shouldn't need '--flake-registry /no-registry'?
nix build -o $TEST_ROOT/result --flake-registry /no-registry file://$flake2Dir:bar
nix build -o $TEST_ROOT/result --flake-registry /no-registry file://$flake2Dir:bar --tarball-ttl 0
# Test whether indirect dependencies work.
nix build -o $TEST_ROOT/result --flake-registry $registry $flake3Dir:xyzzy
@ -184,3 +187,15 @@ nix build -o $TEST_ROOT/result --flake-registry $registry $flake3Dir:sth 2>&1 |
nix flake list --flake-registry file://$registry | grep -q flake3
mv $registry $registry.tmp
nix flake list --flake-registry file://$registry --tarball-ttl 0 | grep -q flake3
mv $registry.tmp $registry
# Test whether flakes are registered as GC roots for offline use.
rm -rf $TEST_HOME/.cache
nix build -o $TEST_ROOT/result --flake-registry file://$registry file://$flake2Dir:bar
mv $flake1Dir $flake1Dir.tmp
mv $flake2Dir $flake2Dir.tmp
nix-store --gc
nix build -o $TEST_ROOT/result --flake-registry file://$registry file://$flake2Dir:bar
nix build -o $TEST_ROOT/result --flake-registry file://$registry file://$flake2Dir:bar --tarball-ttl 0
mv $flake1Dir.tmp $flake1Dir
mv $flake2Dir.tmp $flake2Dir