forked from lix-project/lix
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:
parent
f68bed7f67
commit
26f895a26d
|
@ -2,9 +2,9 @@
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"inputs": {},
|
"inputs": {},
|
||||||
"narHash": "sha256-HkMF+s/yqNOOxqZGp+rscaC8LPtOGc50nEAjLFsnJpg=",
|
"narHash": "sha256-V4jz8Hbt+mZkXhH+3KmUQcRGETOFd8mVPhgQlS4Lu5E=",
|
||||||
"originalUrl": "flake:nixpkgs/release-19.09",
|
"originalUrl": "flake:nixpkgs/release-19.09",
|
||||||
"url": "github:edolstra/nixpkgs/e7223c602152ee4544b05157fc9d88a3feed22c2"
|
"url": "github:edolstra/nixpkgs/dd45a16733f4469a0dded6ad0bf9a662ea39bdea"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version": 3
|
"version": 3
|
||||||
|
|
|
@ -251,17 +251,6 @@ static std::pair<fetchers::Tree, FlakeRef> getNonFlake(
|
||||||
return std::make_pair(std::move(sourceInfo), resolvedRef);
|
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(
|
static void flattenLockFile(
|
||||||
const LockedInputs & inputs,
|
const LockedInputs & inputs,
|
||||||
const InputPath & prefix,
|
const InputPath & prefix,
|
||||||
|
@ -311,20 +300,17 @@ static std::string diffLockFiles(const LockedInputs & oldLocks, const LockedInpu
|
||||||
LockedFlake lockFlake(
|
LockedFlake lockFlake(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const FlakeRef & topRef,
|
const FlakeRef & topRef,
|
||||||
LockFileMode lockFileMode,
|
|
||||||
const LockFlags & lockFlags)
|
const LockFlags & lockFlags)
|
||||||
{
|
{
|
||||||
settings.requireExperimentalFeature("flakes");
|
settings.requireExperimentalFeature("flakes");
|
||||||
|
|
||||||
RefMap refMap;
|
RefMap refMap;
|
||||||
|
|
||||||
auto flake = getFlake(state, topRef,
|
auto flake = getFlake(state, topRef, lockFlags.useRegistries, refMap);
|
||||||
allowedToUseRegistries(lockFileMode, true), refMap);
|
|
||||||
|
|
||||||
LockFile oldLockFile;
|
LockFile oldLockFile;
|
||||||
|
|
||||||
if (lockFileMode != RecreateLockFile && lockFileMode != UseNewLockFile) {
|
if (!lockFlags.recreateLockFile) {
|
||||||
// If recreateLockFile, start with an empty lockfile
|
|
||||||
// FIXME: symlink attack
|
// FIXME: symlink attack
|
||||||
oldLockFile = LockFile::read(
|
oldLockFile = LockFile::read(
|
||||||
flake.sourceInfo->actualPath + "/" + flake.resolvedRef.subdir + "/flake.lock");
|
flake.sourceInfo->actualPath + "/" + flake.resolvedRef.subdir + "/flake.lock");
|
||||||
|
@ -436,12 +422,13 @@ LockedFlake lockFlake(
|
||||||
} else {
|
} else {
|
||||||
/* We need to update/create a new lock file
|
/* We need to update/create a new lock file
|
||||||
entry. So fetch the flake/non-flake. */
|
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);
|
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
|
||||||
|
|
||||||
if (input.isFlake) {
|
if (input.isFlake) {
|
||||||
auto inputFlake = getFlake(state, input.ref,
|
auto inputFlake = getFlake(state, input.ref,
|
||||||
allowedToUseRegistries(lockFileMode, false), refMap);
|
lockFlags.useRegistries, refMap);
|
||||||
|
|
||||||
newLocks.inputs.insert_or_assign(id,
|
newLocks.inputs.insert_or_assign(id,
|
||||||
LockedInput(inputFlake.resolvedRef, inputFlake.originalRef, inputFlake.sourceInfo->narHash));
|
LockedInput(inputFlake.resolvedRef, inputFlake.originalRef, inputFlake.sourceInfo->narHash));
|
||||||
|
@ -461,7 +448,7 @@ LockedFlake lockFlake(
|
||||||
|
|
||||||
else {
|
else {
|
||||||
auto [sourceInfo, resolvedRef] = getNonFlake(state, input.ref,
|
auto [sourceInfo, resolvedRef] = getNonFlake(state, input.ref,
|
||||||
allowedToUseRegistries(lockFileMode, false), refMap);
|
lockFlags.useRegistries, refMap);
|
||||||
newLocks.inputs.insert_or_assign(id,
|
newLocks.inputs.insert_or_assign(id,
|
||||||
LockedInput(resolvedRef, input.ref, sourceInfo.narHash));
|
LockedInput(resolvedRef, input.ref, sourceInfo.narHash));
|
||||||
}
|
}
|
||||||
|
@ -494,12 +481,15 @@ LockedFlake lockFlake(
|
||||||
if (!(oldLockFile == LockFile()))
|
if (!(oldLockFile == LockFile()))
|
||||||
printInfo("inputs of flake '%s' changed:\n%s", topRef, chomp(diffLockFiles(oldLockFile, newLockFile)));
|
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 (auto sourcePath = topRef.input->getSourcePath()) {
|
||||||
if (!newLockFile.isImmutable()) {
|
if (!newLockFile.isImmutable()) {
|
||||||
if (settings.warnDirty)
|
if (settings.warnDirty)
|
||||||
warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
|
warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
|
||||||
} else {
|
} 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";
|
auto path = *sourcePath + (topRef.subdir == "" ? "" : "/" + topRef.subdir) + "/flake.lock";
|
||||||
|
|
||||||
if (pathExists(path))
|
if (pathExists(path))
|
||||||
|
@ -522,9 +512,9 @@ LockedFlake lockFlake(
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
warn("cannot write lock file of remote flake '%s'", topRef);
|
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
|
||||||
} else if (lockFileMode != AllPure && lockFileMode != TopRefUsesRegistries)
|
} else
|
||||||
warn("using updated lock file without writing it to file");
|
warn("not writing modified lock file of flake '%s'", topRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
|
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.
|
// This function is exposed to be used in nix files.
|
||||||
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
LockFlags lockFlags;
|
callFlake(state,
|
||||||
callFlake(state, lockFlake(state, parseFlakeRef(state.forceStringNoCtx(*args[0], pos)),
|
lockFlake(state, parseFlakeRef(state.forceStringNoCtx(*args[0], pos)),
|
||||||
evalSettings.pureEval ? AllPure : UseUpdatedLockFile, lockFlags), v);
|
LockFlags {
|
||||||
|
.updateLockFile = false,
|
||||||
|
.useRegistries = !evalSettings.pureEval,
|
||||||
|
.allowMutable = !evalSettings.pureEval,
|
||||||
|
}),
|
||||||
|
v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp r2("getFlake", 1, prim_getFlake);
|
static RegisterPrimOp r2("getFlake", 1, prim_getFlake);
|
||||||
|
|
|
@ -13,15 +13,6 @@ namespace fetchers { struct Tree; }
|
||||||
|
|
||||||
namespace flake {
|
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;
|
struct FlakeInput;
|
||||||
|
|
||||||
typedef std::map<FlakeId, FlakeInput> FlakeInputs;
|
typedef std::map<FlakeId, FlakeInput> FlakeInputs;
|
||||||
|
@ -61,21 +52,48 @@ struct LockedFlake
|
||||||
|
|
||||||
struct LockFlags
|
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;
|
std::map<InputPath, FlakeRef> inputOverrides;
|
||||||
};
|
};
|
||||||
|
|
||||||
LockedFlake lockFlake(
|
LockedFlake lockFlake(
|
||||||
EvalState &,
|
EvalState & state,
|
||||||
const FlakeRef &,
|
const FlakeRef & flakeRef,
|
||||||
LockFileMode,
|
const LockFlags & lockFlags);
|
||||||
const LockFlags &);
|
|
||||||
|
|
||||||
void callFlake(EvalState & state,
|
void callFlake(
|
||||||
|
EvalState & state,
|
||||||
const Flake & flake,
|
const Flake & flake,
|
||||||
const LockedInputs & inputs,
|
const LockedInputs & inputs,
|
||||||
Value & v);
|
Value & v);
|
||||||
|
|
||||||
void callFlake(EvalState & state,
|
void callFlake(
|
||||||
|
EvalState & state,
|
||||||
const LockedFlake & resFlake,
|
const LockedFlake & resFlake,
|
||||||
Value & v);
|
Value & v);
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,6 @@ class EvalState;
|
||||||
struct Pos;
|
struct Pos;
|
||||||
class Store;
|
class Store;
|
||||||
|
|
||||||
namespace flake {
|
|
||||||
enum LockFileMode : unsigned int;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A command that requires a Nix store. */
|
/* A command that requires a Nix store. */
|
||||||
struct StoreCommand : virtual Command
|
struct StoreCommand : virtual Command
|
||||||
{
|
{
|
||||||
|
@ -42,15 +38,9 @@ struct EvalCommand : virtual StoreCommand, MixEvalArgs
|
||||||
|
|
||||||
struct MixFlakeOptions : virtual Args
|
struct MixFlakeOptions : virtual Args
|
||||||
{
|
{
|
||||||
bool recreateLockFile = false;
|
|
||||||
bool saveLockFile = true;
|
|
||||||
bool useRegistries = true;
|
|
||||||
|
|
||||||
flake::LockFlags lockFlags;
|
flake::LockFlags lockFlags;
|
||||||
|
|
||||||
MixFlakeOptions();
|
MixFlakeOptions();
|
||||||
|
|
||||||
flake::LockFileMode getLockFileMode();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions
|
struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions
|
||||||
|
|
|
@ -38,12 +38,12 @@ public:
|
||||||
Flake getFlake()
|
Flake getFlake()
|
||||||
{
|
{
|
||||||
auto evalState = getEvalState();
|
auto evalState = getEvalState();
|
||||||
return flake::getFlake(*evalState, getFlakeRef(), useRegistries);
|
return flake::getFlake(*evalState, getFlakeRef(), lockFlags.useRegistries);
|
||||||
}
|
}
|
||||||
|
|
||||||
LockedFlake lockFlake()
|
LockedFlake lockFlake()
|
||||||
{
|
{
|
||||||
return flake::lockFlake(*getEvalState(), getFlakeRef(), getLockFileMode(), lockFlags);
|
return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,17 +22,22 @@ MixFlakeOptions::MixFlakeOptions()
|
||||||
mkFlag()
|
mkFlag()
|
||||||
.longName("recreate-lock-file")
|
.longName("recreate-lock-file")
|
||||||
.description("recreate lock file from scratch")
|
.description("recreate lock file from scratch")
|
||||||
.set(&recreateLockFile, true);
|
.set(&lockFlags.recreateLockFile, true);
|
||||||
|
|
||||||
mkFlag()
|
mkFlag()
|
||||||
.longName("no-save-lock-file")
|
.longName("no-update-lock-file")
|
||||||
.description("do not save the newly generated lock file")
|
.description("do not allow any updates to the lock file")
|
||||||
.set(&saveLockFile, false);
|
.set(&lockFlags.updateLockFile, false);
|
||||||
|
|
||||||
|
mkFlag()
|
||||||
|
.longName("no-write-lock-file")
|
||||||
|
.description("do not write the newly generated lock file")
|
||||||
|
.set(&lockFlags.writeLockFile, false);
|
||||||
|
|
||||||
mkFlag()
|
mkFlag()
|
||||||
.longName("no-registries")
|
.longName("no-registries")
|
||||||
.description("don't use flake registries")
|
.description("don't use flake registries")
|
||||||
.set(&useRegistries, false);
|
.set(&lockFlags.useRegistries, false);
|
||||||
|
|
||||||
mkFlag()
|
mkFlag()
|
||||||
.longName("override-input")
|
.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()
|
SourceExprCommand::SourceExprCommand()
|
||||||
{
|
{
|
||||||
mkFlag()
|
mkFlag()
|
||||||
|
@ -332,7 +326,7 @@ std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> InstallableFlake
|
||||||
{
|
{
|
||||||
auto state = cmd.getEvalState();
|
auto state = cmd.getEvalState();
|
||||||
|
|
||||||
auto lockedFlake = lockFlake(*state, flakeRef, cmd.getLockFileMode(), cmd.lockFlags);
|
auto lockedFlake = lockFlake(*state, flakeRef, cmd.lockFlags);
|
||||||
|
|
||||||
Value * vOutputs = nullptr;
|
Value * vOutputs = nullptr;
|
||||||
|
|
||||||
|
@ -386,7 +380,7 @@ std::vector<flake::EvalCache::Derivation> InstallableFlake::toDerivations()
|
||||||
|
|
||||||
Value * InstallableFlake::toValue(EvalState & state)
|
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);
|
auto vOutputs = getFlakeOutputs(state, lockedFlake);
|
||||||
|
|
||||||
|
|
|
@ -156,9 +156,15 @@ nix path-info $flake1Dir/result
|
||||||
(! nix eval --expr "builtins.getFlake \"$flake2Dir\"")
|
(! nix eval --expr "builtins.getFlake \"$flake2Dir\"")
|
||||||
|
|
||||||
# But should succeed in impure mode.
|
# 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
|
nix build -o $TEST_ROOT/result $flake2Dir#bar
|
||||||
[[ -e $flake2Dir/flake.lock ]]
|
[[ -e $flake2Dir/flake.lock ]]
|
||||||
git -C $flake2Dir add flake.lock
|
git -C $flake2Dir add flake.lock
|
||||||
|
|
Loading…
Reference in a new issue