aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2020-01-29 21:01:34 +0100
committerEelco Dolstra <edolstra@gmail.com>2020-01-29 21:04:28 +0100
commit26f895a26d37ec6049628fa835e20dfae5eb94dd (patch)
tree346863a05459c74bafcea95e0446c45c17bf581b
parentf68bed7f67d9acc13ebe38e6f5aa8a641f6e557d (diff)
Clean up the lock file handling flags
Added a flag --no-update-lock-file to barf if the lock file needs any changes. This is useful for CI systems if you're building a checkout. Fixes #2947. Renamed --no-save-lock-file to --no-write-lock-file. It is now a fatal error if the lock file needs changes but --no-write-lock-file is not given.
-rw-r--r--flake.lock4
-rw-r--r--src/libexpr/flake/flake.cc47
-rw-r--r--src/libexpr/flake/flake.hh48
-rw-r--r--src/nix/command.hh10
-rw-r--r--src/nix/flake.cc4
-rw-r--r--src/nix/installables.cc30
-rw-r--r--tests/flakes.sh10
7 files changed, 78 insertions, 75 deletions
diff --git a/flake.lock b/flake.lock
index 75c78d102..46e6c9dc5 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,9 +2,9 @@
"inputs": {
"nixpkgs": {
"inputs": {},
- "narHash": "sha256-HkMF+s/yqNOOxqZGp+rscaC8LPtOGc50nEAjLFsnJpg=",
+ "narHash": "sha256-V4jz8Hbt+mZkXhH+3KmUQcRGETOFd8mVPhgQlS4Lu5E=",
"originalUrl": "flake:nixpkgs/release-19.09",
- "url": "github:edolstra/nixpkgs/e7223c602152ee4544b05157fc9d88a3feed22c2"
+ "url": "github:edolstra/nixpkgs/dd45a16733f4469a0dded6ad0bf9a662ea39bdea"
}
},
"version": 3
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index 2982f7718..adcb88dcc 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -251,17 +251,6 @@ static std::pair<fetchers::Tree, FlakeRef> getNonFlake(
return std::make_pair(std::move(sourceInfo), resolvedRef);
}
-bool allowedToUseRegistries(LockFileMode handle, bool isTopRef)
-{
- if (handle == AllPure) return false;
- else if (handle == TopRefUsesRegistries) return isTopRef;
- else if (handle == UpdateLockFile) return true;
- else if (handle == UseUpdatedLockFile) return true;
- else if (handle == RecreateLockFile) return true;
- else if (handle == UseNewLockFile) return true;
- else assert(false);
-}
-
static void flattenLockFile(
const LockedInputs & inputs,
const InputPath & prefix,
@@ -311,20 +300,17 @@ static std::string diffLockFiles(const LockedInputs & oldLocks, const LockedInpu
LockedFlake lockFlake(
EvalState & state,
const FlakeRef & topRef,
- LockFileMode lockFileMode,
const LockFlags & lockFlags)
{
settings.requireExperimentalFeature("flakes");
RefMap refMap;
- auto flake = getFlake(state, topRef,
- allowedToUseRegistries(lockFileMode, true), refMap);
+ auto flake = getFlake(state, topRef, lockFlags.useRegistries, refMap);
LockFile oldLockFile;
- if (lockFileMode != RecreateLockFile && lockFileMode != UseNewLockFile) {
- // If recreateLockFile, start with an empty lockfile
+ if (!lockFlags.recreateLockFile) {
// FIXME: symlink attack
oldLockFile = LockFile::read(
flake.sourceInfo->actualPath + "/" + flake.resolvedRef.subdir + "/flake.lock");
@@ -436,12 +422,13 @@ LockedFlake lockFlake(
} else {
/* We need to update/create a new lock file
entry. So fetch the flake/non-flake. */
- if (lockFileMode == AllPure || lockFileMode == TopRefUsesRegistries)
+
+ if (!lockFlags.allowMutable && !input.ref.isImmutable())
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
if (input.isFlake) {
auto inputFlake = getFlake(state, input.ref,
- allowedToUseRegistries(lockFileMode, false), refMap);
+ lockFlags.useRegistries, refMap);
newLocks.inputs.insert_or_assign(id,
LockedInput(inputFlake.resolvedRef, inputFlake.originalRef, inputFlake.sourceInfo->narHash));
@@ -461,7 +448,7 @@ LockedFlake lockFlake(
else {
auto [sourceInfo, resolvedRef] = getNonFlake(state, input.ref,
- allowedToUseRegistries(lockFileMode, false), refMap);
+ lockFlags.useRegistries, refMap);
newLocks.inputs.insert_or_assign(id,
LockedInput(resolvedRef, input.ref, sourceInfo.narHash));
}
@@ -494,12 +481,15 @@ LockedFlake lockFlake(
if (!(oldLockFile == LockFile()))
printInfo("inputs of flake '%s' changed:\n%s", topRef, chomp(diffLockFiles(oldLockFile, newLockFile)));
- if (lockFileMode == UpdateLockFile || lockFileMode == RecreateLockFile) {
+ if (lockFlags.writeLockFile) {
if (auto sourcePath = topRef.input->getSourcePath()) {
if (!newLockFile.isImmutable()) {
if (settings.warnDirty)
warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
} else {
+ if (!lockFlags.updateLockFile)
+ throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
+
auto path = *sourcePath + (topRef.subdir == "" ? "" : "/" + topRef.subdir) + "/flake.lock";
if (pathExists(path))
@@ -522,9 +512,9 @@ LockedFlake lockFlake(
#endif
}
} else
- warn("cannot write lock file of remote flake '%s'", topRef);
- } else if (lockFileMode != AllPure && lockFileMode != TopRefUsesRegistries)
- warn("using updated lock file without writing it to file");
+ throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
+ } else
+ warn("not writing modified lock file of flake '%s'", topRef);
}
return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
@@ -655,9 +645,14 @@ 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, lockFlags), v);
+ callFlake(state,
+ lockFlake(state, parseFlakeRef(state.forceStringNoCtx(*args[0], pos)),
+ LockFlags {
+ .updateLockFile = false,
+ .useRegistries = !evalSettings.pureEval,
+ .allowMutable = !evalSettings.pureEval,
+ }),
+ v);
}
static RegisterPrimOp r2("getFlake", 1, prim_getFlake);
diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh
index eb013c4ba..355e22adc 100644
--- a/src/libexpr/flake/flake.hh
+++ b/src/libexpr/flake/flake.hh
@@ -13,15 +13,6 @@ namespace fetchers { struct Tree; }
namespace flake {
-enum LockFileMode : unsigned int
- { AllPure // Everything is handled 100% purely
- , TopRefUsesRegistries // The top FlakeRef uses the registries, apart from that, everything happens 100% purely
- , UpdateLockFile // Update the existing lockfile and write it to file
- , UseUpdatedLockFile // `UpdateLockFile` without writing to file
- , RecreateLockFile // Recreate the lockfile from scratch and write it to file
- , UseNewLockFile // `RecreateLockFile` without writing to file
- };
-
struct FlakeInput;
typedef std::map<FlakeId, FlakeInput> FlakeInputs;
@@ -61,21 +52,48 @@ struct LockedFlake
struct LockFlags
{
+ /* Whether to ignore the existing lock file, creating a new one
+ from scratch. */
+ bool recreateLockFile = false;
+
+ /* Whether to update the lock file at all. If set to false, if any
+ change to the lock file is needed (e.g. when an input has been
+ added to flake.nix), you get a fatal error. */
+ bool updateLockFile = true;
+
+ /* Whether to write the lock file to disk. If set to true, if the
+ any changes to the lock file are needed and the flake is not
+ writable (i.e. is not a local Git working tree or similar), you
+ get a fatal error. If set to false, Nix will use the modified
+ lock file in memory only, without writing it to disk. */
+ bool writeLockFile = true;
+
+ /* Whether to use the registries to lookup indirect flake
+ references like 'nixpkgs'. */
+ bool useRegistries = true;
+
+ /* Whether mutable flake references (i.e. those without a Git
+ revision or similar) without a corresponding lock are
+ allowed. Mutable flake references with a lock are always
+ allowed. */
+ bool allowMutable = true;
+
std::map<InputPath, FlakeRef> inputOverrides;
};
LockedFlake lockFlake(
- EvalState &,
- const FlakeRef &,
- LockFileMode,
- const LockFlags &);
+ EvalState & state,
+ const FlakeRef & flakeRef,
+ const LockFlags & lockFlags);
-void callFlake(EvalState & state,
+void callFlake(
+ EvalState & state,
const Flake & flake,
const LockedInputs & inputs,
Value & v);
-void callFlake(EvalState & state,
+void callFlake(
+ EvalState & state,
const LockedFlake & resFlake,
Value & v);
diff --git a/src/nix/command.hh b/src/nix/command.hh
index eb44caf05..305ce5588 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -16,10 +16,6 @@ class EvalState;
struct Pos;
class Store;
-namespace flake {
-enum LockFileMode : unsigned int;
-}
-
/* A command that requires a Nix store. */
struct StoreCommand : virtual Command
{
@@ -42,15 +38,9 @@ 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();
};
struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 2852a627d..49c0d30f0 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -38,12 +38,12 @@ public:
Flake getFlake()
{
auto evalState = getEvalState();
- return flake::getFlake(*evalState, getFlakeRef(), useRegistries);
+ return flake::getFlake(*evalState, getFlakeRef(), lockFlags.useRegistries);
}
LockedFlake lockFlake()
{
- return flake::lockFlake(*getEvalState(), getFlakeRef(), getLockFileMode(), lockFlags);
+ return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
}
};
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 7d59a25ee..24eb739f5 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -22,17 +22,22 @@ MixFlakeOptions::MixFlakeOptions()
mkFlag()
.longName("recreate-lock-file")
.description("recreate lock file from scratch")
- .set(&recreateLockFile, true);
+ .set(&lockFlags.recreateLockFile, true);
mkFlag()
- .longName("no-save-lock-file")
- .description("do not save the newly generated lock file")
- .set(&saveLockFile, false);
+ .longName("no-update-lock-file")
+ .description("do not allow any updates to the lock file")
+ .set(&lockFlags.updateLockFile, false);
+
+ mkFlag()
+ .longName("no-write-lock-file")
+ .description("do not write the newly generated lock file")
+ .set(&lockFlags.writeLockFile, false);
mkFlag()
.longName("no-registries")
.description("don't use flake registries")
- .set(&useRegistries, false);
+ .set(&lockFlags.useRegistries, false);
mkFlag()
.longName("override-input")
@@ -46,17 +51,6 @@ MixFlakeOptions::MixFlakeOptions()
});
}
-flake::LockFileMode MixFlakeOptions::getLockFileMode()
-{
- using namespace flake;
- return
- useRegistries
- ? recreateLockFile
- ? (saveLockFile ? RecreateLockFile : UseNewLockFile)
- : (saveLockFile ? UpdateLockFile : UseUpdatedLockFile)
- : AllPure;
-}
-
SourceExprCommand::SourceExprCommand()
{
mkFlag()
@@ -332,7 +326,7 @@ std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> InstallableFlake
{
auto state = cmd.getEvalState();
- auto lockedFlake = lockFlake(*state, flakeRef, cmd.getLockFileMode(), cmd.lockFlags);
+ auto lockedFlake = lockFlake(*state, flakeRef, cmd.lockFlags);
Value * vOutputs = nullptr;
@@ -386,7 +380,7 @@ std::vector<flake::EvalCache::Derivation> InstallableFlake::toDerivations()
Value * InstallableFlake::toValue(EvalState & state)
{
- auto lockedFlake = lockFlake(state, flakeRef, cmd.getLockFileMode(), cmd.lockFlags);
+ auto lockedFlake = lockFlake(state, flakeRef, cmd.lockFlags);
auto vOutputs = getFlakeOutputs(state, lockedFlake);
diff --git a/tests/flakes.sh b/tests/flakes.sh
index 1b1912129..3eae73cdf 100644
--- a/tests/flakes.sh
+++ b/tests/flakes.sh
@@ -156,9 +156,15 @@ nix path-info $flake1Dir/result
(! nix eval --expr "builtins.getFlake \"$flake2Dir\"")
# But should succeed in impure mode.
-nix build -o $TEST_ROOT/result flake2#bar --impure
+(! nix build -o $TEST_ROOT/result flake2#bar --impure)
+nix build -o $TEST_ROOT/result flake2#bar --impure --no-write-lock-file
-# Test automatic lock file generation.
+# Building a local flake with an unlocked dependency should fail with --no-update-lock-file.
+nix build -o $TEST_ROOT/result $flake2Dir#bar --no-update-lock-file 2>&1 | grep 'requires lock file changes'
+
+# But it should succeed without that flag.
+nix build -o $TEST_ROOT/result $flake2Dir#bar --no-write-lock-file
+nix build -o $TEST_ROOT/result $flake2Dir#bar --no-update-lock-file 2>&1 | grep 'requires lock file changes'
nix build -o $TEST_ROOT/result $flake2Dir#bar
[[ -e $flake2Dir/flake.lock ]]
git -C $flake2Dir add flake.lock