Update lock files from InstallableFlake::toValue()

This ensures that the lock file is updated *before* evaluating it, and
that it gets updated for any nix command, not just 'nix build'.

Also, while computing the lock file, allow arbitrary registry lookups,
not just at top-level.

Also, improve some error messages slightly.
This commit is contained in:
Eelco Dolstra 2019-04-16 15:02:02 +02:00
parent 7b312a8762
commit 60834492ae
6 changed files with 40 additions and 53 deletions

View file

@ -159,6 +159,9 @@ static FlakeRef lookupFlake(EvalState & state, const FlakeRef & flakeRef,
const std::vector<std::shared_ptr<FlakeRegistry>> & registries, const std::vector<std::shared_ptr<FlakeRegistry>> & registries,
std::vector<FlakeRef> pastSearches = {}) std::vector<FlakeRef> pastSearches = {})
{ {
if (registries.empty() && !flakeRef.isDirect())
throw Error("indirect flake reference '%s' is not allowed", flakeRef.to_string());
for (std::shared_ptr<FlakeRegistry> registry : registries) { for (std::shared_ptr<FlakeRegistry> registry : registries) {
auto i = registry->entries.find(flakeRef); auto i = registry->entries.find(flakeRef);
if (i != registry->entries.end()) { if (i != registry->entries.end()) {
@ -178,8 +181,10 @@ static FlakeRef lookupFlake(EvalState & state, const FlakeRef & flakeRef,
return lookupFlake(state, newRef, registries, pastSearches); return lookupFlake(state, newRef, registries, pastSearches);
} }
} }
if (!flakeRef.isDirect()) if (!flakeRef.isDirect())
throw Error("indirect flake URI '%s' is the result of a lookup", flakeRef.to_string()); throw Error("could not resolve flake reference '%s'", flakeRef.to_string());
return flakeRef; return flakeRef;
} }
@ -192,7 +197,8 @@ struct FlakeSourceInfo
static FlakeSourceInfo fetchFlake(EvalState & state, const FlakeRef flakeRef, bool impureIsAllowed = false) static FlakeSourceInfo fetchFlake(EvalState & state, const FlakeRef flakeRef, bool impureIsAllowed = false)
{ {
FlakeRef fRef = lookupFlake(state, flakeRef, state.getFlakeRegistries()); FlakeRef fRef = lookupFlake(state, flakeRef,
impureIsAllowed ? state.getFlakeRegistries() : std::vector<std::shared_ptr<FlakeRegistry>>());
// This only downloads only one revision of the repo, not the entire history. // This only downloads only one revision of the repo, not the entire history.
if (auto refData = std::get_if<FlakeRef::IsGitHub>(&fRef.data)) { if (auto refData = std::get_if<FlakeRef::IsGitHub>(&fRef.data)) {
@ -349,16 +355,18 @@ NonFlake getNonFlake(EvalState & state, const FlakeRef & flakeRef, FlakeAlias al
dependencies. dependencies.
FIXME: this should return a graph of flakes. FIXME: this should return a graph of flakes.
*/ */
Dependencies resolveFlake(EvalState & state, const FlakeRef & topRef, bool impureTopRef, bool isTopFlake) Dependencies resolveFlake(EvalState & state, const FlakeRef & topRef,
RegistryAccess registryAccess, bool isTopFlake)
{ {
Flake flake = getFlake(state, topRef, isTopFlake && impureTopRef); Flake flake = getFlake(state, topRef,
registryAccess == AllowRegistry || (registryAccess == AllowRegistryAtTop && isTopFlake));
Dependencies deps(flake); Dependencies deps(flake);
for (auto & nonFlakeInfo : flake.nonFlakeRequires) for (auto & nonFlakeInfo : flake.nonFlakeRequires)
deps.nonFlakeDeps.push_back(getNonFlake(state, nonFlakeInfo.second, nonFlakeInfo.first)); deps.nonFlakeDeps.push_back(getNonFlake(state, nonFlakeInfo.second, nonFlakeInfo.first));
for (auto & newFlakeRef : flake.requires) for (auto & newFlakeRef : flake.requires)
deps.flakeDeps.push_back(resolveFlake(state, newFlakeRef, false)); deps.flakeDeps.push_back(resolveFlake(state, newFlakeRef, registryAccess, false));
return deps; return deps;
} }
@ -376,9 +384,9 @@ LockFile::FlakeEntry dependenciesToFlakeEntry(const Dependencies & deps)
return entry; return entry;
} }
LockFile getLockFile(EvalState & evalState, FlakeRef & flakeRef) static LockFile makeLockFile(EvalState & evalState, FlakeRef & flakeRef)
{ {
Dependencies deps = resolveFlake(evalState, flakeRef, true); Dependencies deps = resolveFlake(evalState, flakeRef, AllowRegistry);
LockFile::FlakeEntry entry = dependenciesToFlakeEntry(deps); LockFile::FlakeEntry entry = dependenciesToFlakeEntry(deps);
LockFile lockFile; LockFile lockFile;
lockFile.flakeEntries = entry.flakeEntries; lockFile.flakeEntries = entry.flakeEntries;
@ -388,16 +396,9 @@ LockFile getLockFile(EvalState & evalState, FlakeRef & flakeRef)
void updateLockFile(EvalState & state, const Path & path) void updateLockFile(EvalState & state, const Path & path)
{ {
// 'path' is the path to the local flake repo. FlakeRef flakeRef = FlakeRef("file://" + path); // FIXME: ugly
FlakeRef flakeRef = FlakeRef("file://" + path); auto lockFile = makeLockFile(state, flakeRef);
if (std::get_if<FlakeRef::IsGit>(&flakeRef.data)) { writeLockFile(lockFile, path + "/flake.lock");
LockFile lockFile = getLockFile(state, flakeRef);
writeLockFile(lockFile, path + "/flake.lock");
} else if (std::get_if<FlakeRef::IsGitHub>(&flakeRef.data)) {
throw UsageError("you can only update local flakes, not flakes on GitHub");
} else {
throw UsageError("you can only update local flakes, not flakes through their FlakeAlias");
}
} }
void callFlake(EvalState & state, const Dependencies & flake, Value & v) void callFlake(EvalState & state, const Dependencies & flake, Value & v)
@ -436,15 +437,16 @@ void callFlake(EvalState & state, const Dependencies & flake, Value & v)
// Return the `provides` of the top flake, while assigning to `v` the provides // Return the `provides` of the top flake, while assigning to `v` the provides
// of the dependencies as well. // of the dependencies as well.
void makeFlakeValue(EvalState & state, const FlakeRef & flakeRef, bool impureTopRef, Value & v) void makeFlakeValue(EvalState & state, const FlakeRef & flakeRef, RegistryAccess registryAccess, Value & v)
{ {
callFlake(state, resolveFlake(state, flakeRef, impureTopRef), v); callFlake(state, resolveFlake(state, flakeRef, registryAccess), v);
} }
// 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)
{ {
makeFlakeValue(state, state.forceStringNoCtx(*args[0], pos), false, v); makeFlakeValue(state, state.forceStringNoCtx(*args[0], pos),
evalSettings.pureEval ? DisallowRegistry : AllowRegistryAtTop, v);
} }
static RegisterPrimOp r2("getFlake", 1, prim_getFlake); static RegisterPrimOp r2("getFlake", 1, prim_getFlake);

View file

@ -29,7 +29,9 @@ struct LockFile
Path getUserRegistryPath(); Path getUserRegistryPath();
void makeFlakeValue(EvalState & state, const FlakeRef & flakeRef, bool impureTopRef, Value & v); enum RegistryAccess { DisallowRegistry, AllowRegistry, AllowRegistryAtTop };
void makeFlakeValue(EvalState & state, const FlakeRef & flakeRef, RegistryAccess registryAccess, Value & v);
std::shared_ptr<FlakeRegistry> readRegistry(const Path &); std::shared_ptr<FlakeRegistry> readRegistry(const Path &);
@ -73,9 +75,7 @@ struct Dependencies
Dependencies(const Flake & flake) : flake(flake) {} Dependencies(const Flake & flake) : flake(flake) {}
}; };
Dependencies resolveFlake(EvalState &, const FlakeRef &, bool impureTopRef, bool isTopFlake = true); Dependencies resolveFlake(EvalState &, const FlakeRef &, RegistryAccess registryAccess, bool isTopFlake = true);
FlakeRegistry updateLockFile(EvalState &, const Flake &);
void updateLockFile(EvalState &, const Path & path); void updateLockFile(EvalState &, const Path & path);

View file

@ -11,8 +11,6 @@ struct CmdBuild : MixDryRun, InstallablesCommand
{ {
Path outLink = "result"; Path outLink = "result";
bool update = true;
CmdBuild() CmdBuild()
{ {
mkFlag() mkFlag()
@ -26,11 +24,6 @@ struct CmdBuild : MixDryRun, InstallablesCommand
.longName("no-link") .longName("no-link")
.description("do not create a symlink to the build result") .description("do not create a symlink to the build result")
.set(&outLink, Path("")); .set(&outLink, Path(""));
mkFlag()
.longName("no-update")
.description("don't update the lock file")
.set(&update, false);
} }
std::string name() override std::string name() override
@ -77,13 +70,6 @@ struct CmdBuild : MixDryRun, InstallablesCommand
store2->addPermRoot(output.second, absPath(symlink), true); store2->addPermRoot(output.second, absPath(symlink), true);
} }
} }
if (update)
for (auto installable : installables) {
auto flakeUri = installable->installableToFlakeUri();
if (flakeUri)
updateLockFile(*evalState, *flakeUri);
}
} }
}; };

View file

@ -66,11 +66,6 @@ struct Installable
Buildable toBuildable(); Buildable toBuildable();
virtual std::optional<std::string> installableToFlakeUri()
{
return std::nullopt;
}
virtual Value * toValue(EvalState & state) virtual Value * toValue(EvalState & state)
{ {
throw Error("argument '%s' cannot be evaluated", what()); throw Error("argument '%s' cannot be evaluated", what());
@ -81,6 +76,8 @@ struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
{ {
std::optional<Path> file; std::optional<Path> file;
bool updateLockFile = true;
SourceExprCommand(); SourceExprCommand();
ref<EvalState> getEvalState(); ref<EvalState> getEvalState();

View file

@ -80,7 +80,7 @@ struct CmdFlakeDeps : FlakeCommand, MixJSON, StoreCommand, MixEvalArgs
FlakeRef flakeRef(flakeUri); FlakeRef flakeRef(flakeUri);
Dependencies deps = resolveFlake(*evalState, flakeRef, true); Dependencies deps = resolveFlake(*evalState, flakeRef, AllowRegistryAtTop);
std::queue<Dependencies> todo; std::queue<Dependencies> todo;
todo.push(deps); todo.push(deps);

View file

@ -21,6 +21,11 @@ SourceExprCommand::SourceExprCommand()
.label("file") .label("file")
.description("evaluate a set of attributes from FILE (deprecated)") .description("evaluate a set of attributes from FILE (deprecated)")
.dest(&file); .dest(&file);
mkFlag()
.longName("no-update")
.description("don't create/update flake lock files")
.set(&updateLockFile, false);
} }
ref<EvalState> SourceExprCommand::getEvalState() ref<EvalState> SourceExprCommand::getEvalState()
@ -147,8 +152,13 @@ struct InstallableFlake : InstallableValue
Value * toValue(EvalState & state) override Value * toValue(EvalState & state) override
{ {
auto path = std::get_if<FlakeRef::IsPath>(&flakeRef.data);
if (cmd.updateLockFile && path) {
updateLockFile(state, path->path);
}
auto vFlake = state.allocValue(); auto vFlake = state.allocValue();
makeFlakeValue(state, flakeRef, true, *vFlake); makeFlakeValue(state, flakeRef, AllowRegistryAtTop, *vFlake);
auto vProvides = (*vFlake->attrs->get(state.symbols.create("provides")))->value; auto vProvides = (*vFlake->attrs->get(state.symbols.create("provides")))->value;
@ -169,14 +179,6 @@ struct InstallableFlake : InstallableValue
state.forceValue(*v); state.forceValue(*v);
return v; return v;
} }
std::optional<std::string> installableToFlakeUri() override
{
if (std::get_if<FlakeRef::IsPath>(&flakeRef.data))
return flakeRef.to_string();
else
return std::nullopt;
}
}; };
// FIXME: extend // FIXME: extend