aboutsummaryrefslogtreecommitdiff
path: root/src/nix/installables.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix/installables.cc')
-rw-r--r--src/nix/installables.cc452
1 files changed, 336 insertions, 116 deletions
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 0e8bba39d..671cf513a 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -1,3 +1,4 @@
+#include "installables.hh"
#include "command.hh"
#include "attr-path.hh"
#include "common-eval-args.hh"
@@ -7,75 +8,76 @@
#include "get-drvs.hh"
#include "store-api.hh"
#include "shared.hh"
+#include "flake/flake.hh"
+#include "flake/eval-cache.hh"
#include <regex>
+#include <queue>
namespace nix {
+MixFlakeOptions::MixFlakeOptions()
+{
+ mkFlag()
+ .longName("recreate-lock-file")
+ .description("recreate lock file from scratch")
+ .set(&recreateLockFile, true);
+
+ mkFlag()
+ .longName("no-save-lock-file")
+ .description("do not save the newly generated lock file")
+ .set(&saveLockFile, false);
+
+ mkFlag()
+ .longName("no-registries")
+ .description("don't use flake registries")
+ .set(&useRegistries, false);
+}
+
+flake::HandleLockFile MixFlakeOptions::getLockFileMode()
+{
+ using namespace flake;
+ return
+ useRegistries
+ ? recreateLockFile
+ ? (saveLockFile ? RecreateLockFile : UseNewLockFile)
+ : (saveLockFile ? UpdateLockFile : UseUpdatedLockFile)
+ : AllPure;
+}
+
SourceExprCommand::SourceExprCommand()
{
mkFlag()
.shortName('f')
.longName("file")
.label("file")
- .description("evaluate FILE rather than the default")
+ .description("evaluate a set of attributes from FILE (deprecated)")
.dest(&file);
}
-Value * SourceExprCommand::getSourceExpr(EvalState & state)
+Strings SourceExprCommand::getDefaultFlakeAttrPaths()
{
- if (vSourceExpr) return vSourceExpr;
-
- auto sToplevel = state.symbols.create("_toplevel");
-
- vSourceExpr = state.allocValue();
-
- if (file != "")
- state.evalFile(lookupFileArg(state, file), *vSourceExpr);
-
- else {
-
- /* Construct the installation source from $NIX_PATH. */
-
- auto searchPath = state.getSearchPath();
-
- state.mkAttrs(*vSourceExpr, 1024);
-
- mkBool(*state.allocAttr(*vSourceExpr, sToplevel), true);
-
- std::unordered_set<std::string> seen;
-
- auto addEntry = [&](const std::string & name) {
- if (name == "") return;
- if (!seen.insert(name).second) return;
- Value * v1 = state.allocValue();
- mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath"));
- Value * v2 = state.allocValue();
- mkApp(*v2, *v1, mkString(*state.allocValue(), name));
- mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(name)),
- state.getBuiltin("import"), *v2);
- };
-
- for (auto & i : searchPath)
- /* Hack to handle channels. */
- if (i.first.empty() && pathExists(i.second + "/manifest.nix")) {
- for (auto & j : readDirectory(i.second))
- if (j.name != "manifest.nix"
- && pathExists(fmt("%s/%s/default.nix", i.second, j.name)))
- addEntry(j.name);
- } else
- addEntry(i.first);
-
- vSourceExpr->attrs->sort();
- }
+ return {"defaultPackage." + settings.thisSystem.get()};
+}
- return vSourceExpr;
+Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
+{
+ return {
+ // As a convenience, look for the attribute in
+ // 'outputs.packages'.
+ "packages." + settings.thisSystem.get() + ".",
+ // As a temporary hack until Nixpkgs is properly converted
+ // to provide a clean 'packages' set, look in 'legacyPackages'.
+ "legacyPackages." + settings.thisSystem.get() + "."
+ };
}
-ref<EvalState> SourceExprCommand::getEvalState()
+ref<EvalState> EvalCommand::getEvalState()
{
- if (!evalState)
+ if (!evalState) {
evalState = std::make_shared<EvalState>(searchPath, getStore());
+ evalState->addRegistryOverrides(registryOverrides);
+ }
return ref<EvalState>(evalState);
}
@@ -87,6 +89,27 @@ Buildable Installable::toBuildable()
return std::move(buildables[0]);
}
+App::App(EvalState & state, Value & vApp)
+{
+ state.forceAttrs(vApp);
+
+ auto aType = vApp.attrs->need(state.sType);
+ if (state.forceStringNoCtx(*aType.value, *aType.pos) != "app")
+ throw Error("value does not have type 'app', at %s", *aType.pos);
+
+ auto aProgram = vApp.attrs->need(state.symbols.create("program"));
+ program = state.forceString(*aProgram.value, context, *aProgram.pos);
+
+ // FIXME: check that 'program' is in the closure of 'context'.
+ if (!state.store->isInStore(program))
+ throw Error("app program '%s' is not in the Nix store", program);
+}
+
+App Installable::toApp(EvalState & state)
+{
+ return App(state, *toValue(state));
+}
+
struct InstallableStorePath : Installable
{
Path storePath;
@@ -99,53 +122,65 @@ struct InstallableStorePath : Installable
{
return {{isDerivation(storePath) ? storePath : "", {{"out", storePath}}}};
}
+
+ std::optional<Path> getStorePath() override
+ {
+ return storePath;
+ }
};
-struct InstallableValue : Installable
+std::vector<flake::EvalCache::Derivation> InstallableValue::toDerivations()
{
- SourceExprCommand & cmd;
-
- InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { }
+ auto state = cmd.getEvalState();
- Buildables toBuildables() override
- {
- auto state = cmd.getEvalState();
+ auto v = toValue(*state);
- auto v = toValue(*state);
+ Bindings & autoArgs = *cmd.getAutoArgs(*state);
- Bindings & autoArgs = *cmd.getAutoArgs(*state);
+ DrvInfos drvInfos;
+ getDerivations(*state, *v, "", autoArgs, drvInfos, false);
- DrvInfos drvs;
- getDerivations(*state, *v, "", autoArgs, drvs, false);
+ std::vector<flake::EvalCache::Derivation> res;
+ for (auto & drvInfo : drvInfos) {
+ res.push_back({
+ drvInfo.queryDrvPath(),
+ drvInfo.queryOutPath(),
+ drvInfo.queryOutputName()
+ });
+ }
- Buildables res;
+ return res;
+}
- PathSet drvPaths;
+Buildables InstallableValue::toBuildables()
+{
+ Buildables res;
- for (auto & drv : drvs) {
- Buildable b{drv.queryDrvPath()};
- drvPaths.insert(b.drvPath);
+ PathSet drvPaths;
- auto outputName = drv.queryOutputName();
- if (outputName == "")
- throw Error("derivation '%s' lacks an 'outputName' attribute", b.drvPath);
+ for (auto & drv : toDerivations()) {
+ Buildable b{drv.drvPath};
+ drvPaths.insert(b.drvPath);
- b.outputs.emplace(outputName, drv.queryOutPath());
+ auto outputName = drv.outputName;
+ if (outputName == "")
+ throw Error("derivation '%s' lacks an 'outputName' attribute", b.drvPath);
- res.push_back(std::move(b));
- }
+ b.outputs.emplace(outputName, drv.outPath);
- // Hack to recognize .all: if all drvs have the same drvPath,
- // merge the buildables.
- if (drvPaths.size() == 1) {
- Buildable b{*drvPaths.begin()};
- for (auto & b2 : res)
- b.outputs.insert(b2.outputs.begin(), b2.outputs.end());
- return {b};
- } else
- return res;
+ res.push_back(std::move(b));
}
-};
+
+ // Hack to recognize .all: if all drvs have the same drvPath,
+ // merge the buildables.
+ if (drvPaths.size() == 1) {
+ Buildable b{*drvPaths.begin()};
+ for (auto & b2 : res)
+ b.outputs.insert(b2.outputs.begin(), b2.outputs.end());
+ return {b};
+ } else
+ return res;
+}
struct InstallableExpr : InstallableValue
{
@@ -166,70 +201,251 @@ struct InstallableExpr : InstallableValue
struct InstallableAttrPath : InstallableValue
{
+ Value * v;
std::string attrPath;
- InstallableAttrPath(SourceExprCommand & cmd, const std::string & attrPath)
- : InstallableValue(cmd), attrPath(attrPath)
+ InstallableAttrPath(SourceExprCommand & cmd, Value * v, const std::string & attrPath)
+ : InstallableValue(cmd), v(v), attrPath(attrPath)
{ }
std::string what() override { return attrPath; }
Value * toValue(EvalState & state) override
{
- auto source = cmd.getSourceExpr(state);
+ auto vRes = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), *v);
+ state.forceValue(*vRes);
+ return vRes;
+ }
+};
+
+void makeFlakeClosureGCRoot(Store & store,
+ const FlakeRef & origFlakeRef,
+ const flake::ResolvedFlake & resFlake)
+{
+ if (std::get_if<FlakeRef::IsPath>(&origFlakeRef.data)) return;
+
+ /* Get the store paths of all non-local flakes. */
+ PathSet closure;
+
+ assert(store.isValidPath(resFlake.flake.sourceInfo.storePath));
+ closure.insert(resFlake.flake.sourceInfo.storePath);
+
+ std::queue<std::reference_wrapper<const flake::LockedInputs>> queue;
+ queue.push(resFlake.lockFile);
+
+ while (!queue.empty()) {
+ const flake::LockedInputs & flake = queue.front();
+ queue.pop();
+ /* Note: due to lazy fetching, these paths might not exist
+ yet. */
+ for (auto & dep : flake.inputs) {
+ auto path = dep.second.computeStorePath(store);
+ if (store.isValidPath(path))
+ closure.insert(path);
+ queue.push(dep.second);
+ }
+ }
- Bindings & autoArgs = *cmd.getAutoArgs(state);
+ if (closure.empty()) return;
- Value * v = findAlongAttrPath(state, attrPath, autoArgs, *source);
- state.forceValue(*v);
+ /* Write the closure to a file in the store. */
+ auto closurePath = store.addTextToStore("flake-closure", concatStringsSep(" ", closure), closure);
- return v;
+ Path cacheDir = getCacheDir() + "/nix/flake-closures";
+ createDirs(cacheDir);
+
+ auto s = origFlakeRef.to_string();
+ assert(s[0] != '.');
+ s = replaceStrings(s, "%", "%25");
+ s = replaceStrings(s, "/", "%2f");
+ s = replaceStrings(s, ":", "%3a");
+ Path symlink = cacheDir + "/" + s;
+ debug("writing GC root '%s' for flake closure of '%s'", symlink, origFlakeRef);
+ replaceSymlink(closurePath, symlink);
+ store.addIndirectRoot(symlink);
+}
+
+std::vector<std::string> InstallableFlake::getActualAttrPaths()
+{
+ std::vector<std::string> res;
+
+ for (auto & prefix : prefixes)
+ res.push_back(prefix + *attrPaths.begin());
+
+ for (auto & s : attrPaths)
+ res.push_back(s);
+
+ return res;
+}
+
+Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::ResolvedFlake & resFlake)
+{
+ auto vFlake = state.allocValue();
+
+ callFlake(state, resFlake, *vFlake);
+
+ makeFlakeClosureGCRoot(*state.store, flakeRef, resFlake);
+
+ auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
+ assert(aOutputs);
+
+ state.forceValue(*(*aOutputs)->value);
+
+ return (*aOutputs)->value;
+}
+
+std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> InstallableFlake::toDerivation()
+{
+ auto state = cmd.getEvalState();
+
+ auto resFlake = resolveFlake(*state, flakeRef, cmd.getLockFileMode());
+
+ Value * vOutputs = nullptr;
+
+ auto emptyArgs = state->allocBindings(0);
+
+ auto & evalCache = flake::EvalCache::singleton();
+
+ auto fingerprint = resFlake.getFingerprint();
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ auto drv = evalCache.getDerivation(fingerprint, attrPath);
+ if (drv) {
+ if (state->store->isValidPath(drv->drvPath))
+ return {attrPath, resFlake.flake.sourceInfo.resolvedRef, *drv};
+ }
+
+ if (!vOutputs)
+ vOutputs = getFlakeOutputs(*state, resFlake);
+
+ try {
+ auto * v = findAlongAttrPath(*state, attrPath, *emptyArgs, *vOutputs);
+ state->forceValue(*v);
+
+ auto drvInfo = getDerivation(*state, *v, false);
+ if (!drvInfo)
+ throw Error("flake output attribute '%s' is not a derivation", attrPath);
+
+ auto drv = flake::EvalCache::Derivation{
+ drvInfo->queryDrvPath(),
+ drvInfo->queryOutPath(),
+ drvInfo->queryOutputName()
+ };
+
+ evalCache.addDerivation(fingerprint, attrPath, drv);
+
+ return {attrPath, resFlake.flake.sourceInfo.resolvedRef, drv};
+ } catch (AttrPathNotFound & e) {
+ }
}
-};
+
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, concatStringsSep(", ", quoteStrings(attrPaths)));
+}
+
+std::vector<flake::EvalCache::Derivation> InstallableFlake::toDerivations()
+{
+ return {std::get<2>(toDerivation())};
+}
+
+Value * InstallableFlake::toValue(EvalState & state)
+{
+ auto resFlake = resolveFlake(state, flakeRef, cmd.getLockFileMode());
+
+ auto vOutputs = getFlakeOutputs(state, resFlake);
+
+ auto emptyArgs = state.allocBindings(0);
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ try {
+ auto * v = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
+ state.forceValue(*v);
+ return v;
+ } catch (AttrPathNotFound & e) {
+ }
+ }
+
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, concatStringsSep(", ", quoteStrings(attrPaths)));
+}
// FIXME: extend
std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)";
static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex));
-static std::vector<std::shared_ptr<Installable>> parseInstallables(
- SourceExprCommand & cmd, ref<Store> store, std::vector<std::string> ss, bool useDefaultInstallables)
+std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
+ ref<Store> store, std::vector<std::string> ss)
{
std::vector<std::shared_ptr<Installable>> result;
- if (ss.empty() && useDefaultInstallables) {
- if (cmd.file == "")
- cmd.file = ".";
- ss = {""};
- }
-
- for (auto & s : ss) {
+ if (file) {
+ // FIXME: backward compatibility hack
+ evalSettings.pureEval = false;
- if (s.compare(0, 1, "(") == 0)
- result.push_back(std::make_shared<InstallableExpr>(cmd, s));
+ auto state = getEvalState();
+ auto vFile = state->allocValue();
+ state->evalFile(lookupFileArg(*state, *file), *vFile);
- else if (s.find("/") != std::string::npos) {
+ if (ss.empty())
+ ss = {""};
- auto path = store->toStorePath(store->followLinksToStore(s));
+ for (auto & s : ss)
+ result.push_back(std::make_shared<InstallableAttrPath>(*this, vFile, s));
- if (store->isStorePath(path))
- result.push_back(std::make_shared<InstallableStorePath>(path));
- }
+ } else {
- else if (s == "" || std::regex_match(s, attrPathRegex))
- result.push_back(std::make_shared<InstallableAttrPath>(cmd, s));
+ auto follow = [&](const std::string & s) -> std::optional<Path> {
+ try {
+ return store->followLinksToStorePath(s);
+ } catch (NotInStore &) {
+ return {};
+ }
+ };
- else
- throw UsageError("don't know what to do with argument '%s'", s);
+ for (auto & s : ss) {
+
+ size_t hash;
+ std::optional<Path> storePath;
+
+ if (s.compare(0, 1, "(") == 0)
+ result.push_back(std::make_shared<InstallableExpr>(*this, s));
+
+ else if (hasPrefix(s, "nixpkgs.")) {
+ bool static warned;
+ warnOnce(warned, "the syntax 'nixpkgs.<attr>' is deprecated; use 'nixpkgs:<attr>' instead");
+ result.push_back(std::make_shared<InstallableFlake>(*this, FlakeRef("nixpkgs"),
+ Strings{"legacyPackages." + settings.thisSystem.get() + "." + std::string(s, 8)}));
+ }
+
+ else if ((hash = s.rfind('#')) != std::string::npos)
+ result.push_back(std::make_shared<InstallableFlake>(
+ *this,
+ FlakeRef(std::string(s, 0, hash), true),
+ std::string(s, hash + 1),
+ getDefaultFlakeAttrPathPrefixes()));
+
+ else {
+ try {
+ auto flakeRef = FlakeRef(s, true);
+ result.push_back(std::make_shared<InstallableFlake>(
+ *this, std::move(flakeRef), getDefaultFlakeAttrPaths()));
+ } catch (...) {
+ if (s.find('/') != std::string::npos && (storePath = follow(s)))
+ result.push_back(std::make_shared<InstallableStorePath>(*storePath));
+ else
+ throw;
+ }
+ }
+ }
}
return result;
}
-std::shared_ptr<Installable> parseInstallable(
- SourceExprCommand & cmd, ref<Store> store, const std::string & installable,
- bool useDefaultInstallables)
+std::shared_ptr<Installable> SourceExprCommand::parseInstallable(
+ ref<Store> store, const std::string & installable)
{
- auto installables = parseInstallables(cmd, store, {installable}, false);
+ auto installables = parseInstallables(store, {installable});
assert(installables.size() == 1);
return installables.front();
}
@@ -285,7 +501,7 @@ Path toStorePath(ref<Store> store, RealiseMode mode,
auto paths = toStorePaths(store, mode, {installable});
if (paths.size() != 1)
- throw Error("argument '%s' should evaluate to one store path", installable->what());
+ throw Error("argument '%s' should evaluate to one store path", installable->what());
return *paths.begin();
}
@@ -316,12 +532,16 @@ PathSet toDerivations(ref<Store> store,
void InstallablesCommand::prepare()
{
- installables = parseInstallables(*this, getStore(), _installables, useDefaultInstallables());
+ if (_installables.empty() && !file && useDefaultInstallables())
+ // FIXME: commands like "nix install" should not have a
+ // default, probably.
+ _installables.push_back(".");
+ installables = parseInstallables(getStore(), _installables);
}
void InstallableCommand::prepare()
{
- installable = parseInstallable(*this, getStore(), _installable, false);
+ installable = parseInstallable(getStore(), _installable);
}
}