From b9fb3720750a24d8b2fbe6b1491696d6a51dcff3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 29 Jan 2020 23:12:58 +0100 Subject: [PATCH] Add --update-input flag to update a specific flake input Typical usage: $ nix flake update ~/Misc/eelco-configurations/hagbard --update-input nixpkgs to update the 'nixpkgs' input of a flake while leaving every other input unchanged. The argument is an input path, so you can do e.g. '--update-input dwarffs/nixpkgs' to update an input of an input. Fixes #2928. --- src/libexpr/flake/flake.cc | 53 +++++++++++++++++++++++++---------- src/libexpr/flake/flake.hh | 5 ++++ src/libexpr/flake/lockfile.cc | 16 +++++++++++ src/libexpr/flake/lockfile.hh | 2 ++ src/nix/installables.cc | 8 ++++++ tests/flakes.sh | 7 +++++ 6 files changed, 76 insertions(+), 15 deletions(-) diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index c752eeedc..a8f05efb6 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -394,7 +394,13 @@ LockedFlake lockFlake( continue; } - auto oldLock = oldLocks.inputs.find(id); + /* Do we have an entry in the existing lock file? And + we don't have a --update-input flag for this + input? */ + auto oldLock = + lockFlags.inputUpdates.count(inputPath) + ? oldLocks.inputs.end() + : oldLocks.inputs.find(id); if (oldLock != oldLocks.inputs.end() && oldLock->second.originalRef == input.ref && !hasOverride) { /* Copy the input from the old lock file if its @@ -402,23 +408,40 @@ LockedFlake lockFlake( from a higher level flake. */ newLocks.inputs.insert_or_assign(id, oldLock->second); - /* However there may be new overrides on the - inputs of this flake, so we need to check those - (without fetching this flake - we need to be - lazy). */ - FlakeInputs fakeInputs; + /* If we have an --update-input flag for an input + of this input, then we must fetch the flake to + to update it. */ + auto lb = lockFlags.inputUpdates.lower_bound(inputPath); - for (auto & i : oldLock->second.inputs) { - fakeInputs.emplace(i.first, FlakeInput { - .ref = i.second.originalRef - }); + auto hasChildUpdate = + lb != lockFlags.inputUpdates.end() + && lb->size() > inputPath.size() + && std::equal(inputPath.begin(), inputPath.end(), lb->begin()); + + if (hasChildUpdate) { + auto inputFlake = getFlake(state, oldLock->second.ref, false, flakeCache); + + updateLocks(inputFlake.inputs, + (const LockedInputs &) oldLock->second, + newLocks.inputs.find(id)->second, + inputPath); + + } else { + /* No need to fetch this flake, we can be + lazy. However there may be new overrides on + the inputs of this flake, so we need to + check those. */ + FlakeInputs fakeInputs; + + for (auto & i : oldLock->second.inputs) + fakeInputs.emplace(i.first, FlakeInput { .ref = i.second.originalRef }); + + updateLocks(fakeInputs, + oldLock->second, + newLocks.inputs.find(id)->second, + inputPath); } - updateLocks(fakeInputs, - oldLock->second, - newLocks.inputs.find(id)->second, - inputPath); - } else { /* We need to update/create a new lock file entry. So fetch the flake/non-flake. */ diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh index 355e22adc..fd03faf13 100644 --- a/src/libexpr/flake/flake.hh +++ b/src/libexpr/flake/flake.hh @@ -78,7 +78,12 @@ struct LockFlags allowed. */ bool allowMutable = true; + /* Flake inputs to be overriden. */ std::map inputOverrides; + + /* Flake inputs to be updated. This means that any existing lock + for those inputs will be ignored. */ + std::set inputUpdates; }; LockedFlake lockFlake( diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc index b92ffb70e..edb7628fd 100644 --- a/src/libexpr/flake/lockfile.cc +++ b/src/libexpr/flake/lockfile.cc @@ -72,6 +72,22 @@ std::optional LockedInputs::findInput(const InputPath & path) return (LockedInput *) pos; } +void LockedInputs::removeInput(const InputPath & path) +{ + assert(!path.empty()); + + LockedInputs * pos = this; + + for (size_t n = 0; n < path.size(); n++) { + auto i = pos->inputs.find(path[n]); + if (i == pos->inputs.end()) return; + if (n + 1 == path.size()) + pos->inputs.erase(i); + else + pos = &i->second; + } +} + nlohmann::json LockFile::toJson() const { auto json = LockedInputs::toJson(); diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh index 626c7d9e0..ff1181d13 100644 --- a/src/libexpr/flake/lockfile.hh +++ b/src/libexpr/flake/lockfile.hh @@ -27,6 +27,8 @@ struct LockedInputs bool isImmutable() const; std::optional findInput(const InputPath & path); + + void removeInput(const InputPath & path); }; /* Lock file information about a flake input. */ diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 24eb739f5..77a43ef2a 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -39,6 +39,14 @@ MixFlakeOptions::MixFlakeOptions() .description("don't use flake registries") .set(&lockFlags.useRegistries, false); + mkFlag() + .longName("update-input") + .description("update a specific flake input") + .label("input-path") + .handler([&](std::vector ss) { + lockFlags.inputUpdates.insert(flake::parseInputPath(ss[0])); + }); + mkFlag() .longName("override-input") .description("override a specific flake input (e.g. 'dwarffs/nixpkgs')") diff --git a/tests/flakes.sh b/tests/flakes.sh index 3eae73cdf..6d2ee80f1 100644 --- a/tests/flakes.sh +++ b/tests/flakes.sh @@ -610,3 +610,10 @@ nix flake update $flake3Dir --override-input flake2/flake1 flake1 nix flake update $flake3Dir --override-input flake2/flake1 flake1/master/$hash1 [[ $(jq .inputs.flake2.inputs.flake1.url $flake3Dir/flake.lock) =~ flake1.*rev=$hash1 ]] + +# Test --update-input. +nix flake update $flake3Dir +[[ $(jq .inputs.flake2.inputs.flake1.url $flake3Dir/flake.lock) =~ flake1.*rev=$hash1 ]] + +nix flake update $flake3Dir --update-input flake2/flake1 +[[ $(jq .inputs.flake2.inputs.flake1.url $flake3Dir/flake.lock) =~ flake1.*rev=$hash2 ]]