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.
This commit is contained in:
Eelco Dolstra 2020-01-29 21:01:34 +01:00
parent f68bed7f67
commit 26f895a26d
7 changed files with 78 additions and 75 deletions

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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);
}
};

View file

@ -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);

View file

@ -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