From f68bed7f67d9acc13ebe38e6f5aa8a641f6e557d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 29 Jan 2020 14:57:57 +0100 Subject: [PATCH] Add flag --override-input to override specific lock file entries E.g. $ nix flake update ~/Misc/eelco-configurations/hagbard \ --override-input 'dwarffs/nixpkgs' ../my-nixpkgs overrides the 'nixpkgs' input of the 'dwarffs' input of the top-level flake. Fixes #2837. --- src/libexpr/flake/flake.cc | 32 ++++++++++++-------------------- src/libexpr/flake/flake.hh | 11 ++++++++++- src/libexpr/flake/lockfile.cc | 17 +++++++++++++++++ src/libexpr/flake/lockfile.hh | 2 ++ src/nix/command.hh | 5 +++-- src/nix/flake.cc | 2 +- src/nix/installables.cc | 15 +++++++++++++-- tests/flakes.sh | 17 +++++++++++++++++ 8 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 15b852c7c..2982f7718 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -4,7 +4,6 @@ #include "eval-inline.hh" #include "store-api.hh" #include "fetchers/fetchers.hh" -#include "fetchers/regex.hh" #include #include @@ -62,22 +61,6 @@ static void expectType(EvalState & state, ValueType type, showType(type), showType(value.type), pos); } -static InputPath parseInputPath(std::string_view s, const Pos & pos) -{ - InputPath path; - - for (auto & elem : tokenizeString>(s, "/")) { - if (!std::regex_match(elem, fetchers::flakeIdRegex)) - throw Error("invalid flake input path element '%s' at %s", elem, pos); - path.push_back(elem); - } - - if (path.empty()) - throw Error("flake input path is empty at %s", pos); - - return path; -} - static std::map parseFlakeInputs( EvalState & state, Value * value, const Pos & pos); @@ -107,7 +90,11 @@ static FlakeInput parseFlakeInput(EvalState & state, input.overrides = parseFlakeInputs(state, attr.value, *attr.pos); } else if (attr.name == sFollows) { expectType(state, tString, *attr.value, *attr.pos); - input.follows = parseInputPath(attr.value->string.s, *attr.pos); + try { + input.follows = parseInputPath(attr.value->string.s); + } catch (Error & e) { + e.addPrefix("in flake attribute at '%s':\n"); + } } else throw Error("flake input '%s' has an unsupported attribute '%s', at %s", inputName, attr.name, *attr.pos); @@ -324,7 +311,8 @@ static std::string diffLockFiles(const LockedInputs & oldLocks, const LockedInpu LockedFlake lockFlake( EvalState & state, const FlakeRef & topRef, - LockFileMode lockFileMode) + LockFileMode lockFileMode, + const LockFlags & lockFlags) { settings.requireExperimentalFeature("flakes"); @@ -350,6 +338,9 @@ LockedFlake lockFlake( // FIXME: check whether all overrides are used. std::map overrides; + for (auto & i : lockFlags.inputOverrides) + overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second }); + /* Compute the new lock file. This is dones as a fixpoint iteration: we repeat until the new lock file no longer changes and there are no unresolved "follows" inputs. */ @@ -664,8 +655,9 @@ void callFlake(EvalState & state, // This function is exposed to be used in nix files. static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) { + LockFlags lockFlags; callFlake(state, lockFlake(state, parseFlakeRef(state.forceStringNoCtx(*args[0], pos)), - evalSettings.pureEval ? AllPure : UseUpdatedLockFile), v); + evalSettings.pureEval ? AllPure : UseUpdatedLockFile, lockFlags), v); } static RegisterPrimOp r2("getFlake", 1, prim_getFlake); diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh index 83dae1d90..eb013c4ba 100644 --- a/src/libexpr/flake/flake.hh +++ b/src/libexpr/flake/flake.hh @@ -59,7 +59,16 @@ struct LockedFlake Fingerprint getFingerprint() const; }; -LockedFlake lockFlake(EvalState &, const FlakeRef &, LockFileMode); +struct LockFlags +{ + std::map inputOverrides; +}; + +LockedFlake lockFlake( + EvalState &, + const FlakeRef &, + LockFileMode, + const LockFlags &); void callFlake(EvalState & state, const Flake & flake, diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc index a7291db5b..b92ffb70e 100644 --- a/src/libexpr/flake/lockfile.cc +++ b/src/libexpr/flake/lockfile.cc @@ -1,5 +1,6 @@ #include "lockfile.hh" #include "store-api.hh" +#include "fetchers/regex.hh" #include @@ -104,4 +105,20 @@ void LockFile::write(const Path & path) const writeFile(path, fmt("%s\n", *this)); } +InputPath parseInputPath(std::string_view s) +{ + InputPath path; + + for (auto & elem : tokenizeString>(s, "/")) { + if (!std::regex_match(elem, fetchers::flakeIdRegex)) + throw Error("invalid flake input path element '%s'", elem); + path.push_back(elem); + } + + if (path.empty()) + throw Error("flake input path is empty"); + + return path; +} + } diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh index 9210a59e2..626c7d9e0 100644 --- a/src/libexpr/flake/lockfile.hh +++ b/src/libexpr/flake/lockfile.hh @@ -81,5 +81,7 @@ struct LockFile : LockedInputs std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile); +InputPath parseInputPath(std::string_view s); + } diff --git a/src/nix/command.hh b/src/nix/command.hh index 08fa0c5fa..eb44caf05 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -4,6 +4,7 @@ #include "args.hh" #include "common-eval-args.hh" #include "path.hh" +#include "flake/lockfile.hh" #include @@ -42,11 +43,11 @@ struct EvalCommand : virtual StoreCommand, MixEvalArgs struct MixFlakeOptions : virtual Args { bool recreateLockFile = false; - bool saveLockFile = true; - bool useRegistries = true; + flake::LockFlags lockFlags; + MixFlakeOptions(); flake::LockFileMode getLockFileMode(); diff --git a/src/nix/flake.cc b/src/nix/flake.cc index f7e329b49..2852a627d 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -43,7 +43,7 @@ public: LockedFlake lockFlake() { - return flake::lockFlake(*getEvalState(), getFlakeRef(), getLockFileMode()); + return flake::lockFlake(*getEvalState(), getFlakeRef(), getLockFileMode(), lockFlags); } }; diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 8e4b53308..7d59a25ee 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -33,6 +33,17 @@ MixFlakeOptions::MixFlakeOptions() .longName("no-registries") .description("don't use flake registries") .set(&useRegistries, false); + + mkFlag() + .longName("override-input") + .description("override a specific flake input (e.g. 'dwarffs/nixpkgs')") + .arity(2) + .labels({"input-path", "flake-url"}) + .handler([&](std::vector ss) { + lockFlags.inputOverrides.insert_or_assign( + flake::parseInputPath(ss[0]), + parseFlakeRef(ss[1], absPath("."))); + }); } flake::LockFileMode MixFlakeOptions::getLockFileMode() @@ -321,7 +332,7 @@ std::tuple InstallableFlake { auto state = cmd.getEvalState(); - auto lockedFlake = lockFlake(*state, flakeRef, cmd.getLockFileMode()); + auto lockedFlake = lockFlake(*state, flakeRef, cmd.getLockFileMode(), cmd.lockFlags); Value * vOutputs = nullptr; @@ -375,7 +386,7 @@ std::vector InstallableFlake::toDerivations() Value * InstallableFlake::toValue(EvalState & state) { - auto lockedFlake = lockFlake(state, flakeRef, cmd.getLockFileMode()); + auto lockedFlake = lockFlake(state, flakeRef, cmd.getLockFileMode(), cmd.lockFlags); auto vOutputs = getFlakeOutputs(state, lockedFlake); diff --git a/tests/flakes.sh b/tests/flakes.sh index 8cd814a29..1b1912129 100644 --- a/tests/flakes.sh +++ b/tests/flakes.sh @@ -129,6 +129,12 @@ json=$(nix flake info flake1 --json | jq .) [[ $(echo "$json" | jq -r .description) = 'Bla bla' ]] [[ -d $(echo "$json" | jq -r .path) ]] [[ $(echo "$json" | jq -r .lastModified) = $(git -C $flake1Dir log -n1 --format=%ct) ]] +hash1=$(echo "$json" | jq -r .revision) + +echo -n '# foo' >> $flake1Dir/flake.nix +git -C $flake1Dir commit -a -m 'Foo' +hash2=$(nix flake info flake1 --json --refresh | jq -r .revision) +[[ $hash1 != $hash2 ]] # Test 'nix build' on a flake. nix build -o $TEST_ROOT/result flake1#foo @@ -587,3 +593,14 @@ nix build -o $TEST_ROOT/result $url # Building with an incorrect SRI hash should fail. nix build -o $TEST_ROOT/result "file://$TEST_ROOT/flake.tar.gz?narHash=sha256-qQ2Zz4DNHViCUrp6gTS7EE4+RMqFQtUfWF2UNUtJKS0=" 2>&1 | grep 'NAR hash mismatch' + +# Test --override-input. +git -C $flake3Dir reset --hard +nix flake update $flake3Dir --override-input flake2/flake1 flake5 +[[ $(jq .inputs.flake2.inputs.flake1.url $flake3Dir/flake.lock) =~ flake5 ]] + +nix flake update $flake3Dir --override-input flake2/flake1 flake1 +[[ $(jq .inputs.flake2.inputs.flake1.url $flake3Dir/flake.lock) =~ flake1.*rev=$hash2 ]] + +nix flake update $flake3Dir --override-input flake2/flake1 flake1/master/$hash1 +[[ $(jq .inputs.flake2.inputs.flake1.url $flake3Dir/flake.lock) =~ flake1.*rev=$hash1 ]]