1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-06 21:41:48 +02:00

Add setting 'allow-dirty-locks'

This allows writing lock files with dirty inputs, so long as they have
a NAR hash. (Currently they always have a NAR hash, but with lazy
trees that may not always be the case.)

Generally dirty locks are bad for reproducibility (we can detect if
the dirty input has changed, but we have no way to fetch it except
substitution). Hence we don't allow them by default.

Fixes #11181.
This commit is contained in:
Eelco Dolstra 2025-01-10 16:27:40 +01:00
parent 2d9b213cc2
commit e161393299
12 changed files with 67 additions and 17 deletions

View file

@ -345,6 +345,7 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
}
static LockFile readLockFile(
const Settings & settings,
const fetchers::Settings & fetchSettings,
const SourcePath & lockFilePath)
{
@ -380,6 +381,7 @@ LockedFlake lockFlake(
}
auto oldLockFile = readLockFile(
settings,
state.fetchSettings,
lockFlags.referenceLockFilePath.value_or(
flake.lockFilePath()));
@ -616,7 +618,7 @@ LockedFlake lockFlake(
inputFlake.inputs, childNode, inputPath,
oldLock
? std::dynamic_pointer_cast<const Node>(oldLock)
: readLockFile(state.fetchSettings, inputFlake.lockFilePath()).root.get_ptr(),
: readLockFile(settings, state.fetchSettings, inputFlake.lockFilePath()).root.get_ptr(),
oldLock ? lockRootPath : inputPath,
localPath,
false);
@ -678,9 +680,11 @@ LockedFlake lockFlake(
if (lockFlags.writeLockFile) {
if (sourcePath || lockFlags.outputLockFilePath) {
if (auto unlockedInput = newLockFile.isUnlocked()) {
if (auto unlockedInput = newLockFile.isUnlocked(state.fetchSettings)) {
if (lockFlags.failOnUnlocked)
throw Error("cannot write lock file of flake '%s' because it has an unlocked input ('%s').\n", topRef, *unlockedInput);
throw Error(
"Will not write lock file of flake '%s' because it has an unlocked input ('%s'). "
"Use '--allow-dirty-locks' to allow this anyway.", topRef, *unlockedInput);
if (state.fetchSettings.warnDirty)
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
} else {
@ -979,9 +983,11 @@ static RegisterPrimOp r4({
}
std::optional<Fingerprint> LockedFlake::getFingerprint(ref<Store> store) const
std::optional<Fingerprint> LockedFlake::getFingerprint(
ref<Store> store,
const fetchers::Settings & fetchSettings) const
{
if (lockFile.isUnlocked()) return std::nullopt;
if (lockFile.isUnlocked(fetchSettings)) return std::nullopt;
auto fingerprint = flake.lockedRef.input.getFingerprint(store);
if (!fingerprint) return std::nullopt;

View file

@ -129,7 +129,9 @@ struct LockedFlake
*/
std::map<ref<Node>, SourcePath> nodePaths;
std::optional<Fingerprint> getFingerprint(ref<Store> store) const;
std::optional<Fingerprint> getFingerprint(
ref<Store> store,
const fetchers::Settings & fetchSettings) const;
};
struct LockFlags

View file

@ -10,6 +10,7 @@
#include <nlohmann/json.hpp>
#include "strings.hh"
#include "flake/settings.hh"
namespace nix::flake {
@ -43,8 +44,8 @@ LockedNode::LockedNode(
, originalRef(getFlakeRef(fetchSettings, json, "original", nullptr))
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{
if (!lockedRef.input.isLocked())
throw Error("lock file contains unlocked input '%s'",
if (!lockedRef.input.isConsideredLocked(fetchSettings))
throw Error("Lock file contains unlocked input '%s'. Use '--allow-dirty-locks' to accept this lock file.",
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
// For backward compatibility, lock file entries are implicitly final.
@ -228,7 +229,7 @@ std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
return stream;
}
std::optional<FlakeRef> LockFile::isUnlocked() const
std::optional<FlakeRef> LockFile::isUnlocked(const fetchers::Settings & fetchSettings) const
{
std::set<ref<const Node>> nodes;
@ -247,7 +248,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const
for (auto & i : nodes) {
if (i == ref<const Node>(root)) continue;
auto node = i.dynamic_pointer_cast<const LockedNode>();
if (node && (!node->lockedRef.input.isLocked() || !node->lockedRef.input.isFinal()))
if (node && (!node->lockedRef.input.isConsideredLocked(fetchSettings) || !node->lockedRef.input.isFinal()))
return node->lockedRef;
}

View file

@ -71,7 +71,7 @@ struct LockFile
* Check whether this lock file has any unlocked or non-final
* inputs. If so, return one.
*/
std::optional<FlakeRef> isUnlocked() const;
std::optional<FlakeRef> isUnlocked(const fetchers::Settings & fetchSettings) const;
bool operator ==(const LockFile & other) const;

View file

@ -29,7 +29,7 @@ struct Settings : public Config
this,
false,
"accept-flake-config",
"Whether to accept nix configuration from a flake without prompting.",
"Whether to accept Nix configuration settings from a flake without prompting.",
{},
true,
Xp::Flakes};