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.cc420
1 files changed, 305 insertions, 115 deletions
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index f464d0aa1..99bbe9769 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,72 +8,96 @@
#include "get-drvs.hh"
#include "store-api.hh"
#include "shared.hh"
+#include "flake/flake.hh"
+#include "flake/eval-cache.hh"
+#include "url.hh"
#include <regex>
+#include <queue>
namespace nix {
-SourceExprCommand::SourceExprCommand()
+MixFlakeOptions::MixFlakeOptions()
{
mkFlag()
- .shortName('f')
- .longName("file")
- .label("file")
- .description("evaluate FILE rather than the default")
- .dest(&file);
-}
-
-Value * SourceExprCommand::getSourceExpr(EvalState & state)
-{
- if (vSourceExpr) return vSourceExpr;
-
- auto sToplevel = state.symbols.create("_toplevel");
-
- vSourceExpr = state.allocValue();
-
- if (file != "")
- state.evalFile(lookupFileArg(state, file), *vSourceExpr);
+ .longName("recreate-lock-file")
+ .description("recreate lock file from scratch")
+ .set(&lockFlags.recreateLockFile, true);
- else {
+ mkFlag()
+ .longName("no-update-lock-file")
+ .description("do not allow any updates to the lock file")
+ .set(&lockFlags.updateLockFile, false);
- /* Construct the installation source from $NIX_PATH. */
+ mkFlag()
+ .longName("no-write-lock-file")
+ .description("do not write the newly generated lock file")
+ .set(&lockFlags.writeLockFile, false);
- auto searchPath = state.getSearchPath();
+ mkFlag()
+ .longName("no-registries")
+ .description("don't use flake registries")
+ .set(&lockFlags.useRegistries, false);
- state.mkAttrs(*vSourceExpr, 1024);
+ mkFlag()
+ .longName("commit-lock-file")
+ .description("commit changes to the lock file")
+ .set(&lockFlags.commitLockFile, true);
- mkBool(*state.allocAttr(*vSourceExpr, sToplevel), true);
+ mkFlag()
+ .longName("update-input")
+ .description("update a specific flake input")
+ .label("input-path")
+ .handler([&](std::vector<std::string> ss) {
+ lockFlags.inputUpdates.insert(flake::parseInputPath(ss[0]));
+ });
- std::unordered_set<std::string> seen;
+ mkFlag()
+ .longName("override-input")
+ .description("override a specific flake input (e.g. 'dwarffs/nixpkgs')")
+ .arity(2)
+ .labels({"input-path", "flake-url"})
+ .handler([&](std::vector<std::string> ss) {
+ lockFlags.inputOverrides.insert_or_assign(
+ flake::parseInputPath(ss[0]),
+ parseFlakeRef(ss[1], absPath(".")));
+ });
+}
- 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);
- };
+SourceExprCommand::SourceExprCommand()
+{
+ mkFlag()
+ .shortName('f')
+ .longName("file")
+ .label("file")
+ .description("evaluate attributes from FILE")
+ .dest(&file);
- 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);
+ mkFlag()
+ .longName("expr")
+ .label("expr")
+ .description("evaluate attributes from EXPR")
+ .dest(&expr);
+}
- vSourceExpr->attrs->sort();
- }
+Strings SourceExprCommand::getDefaultFlakeAttrPaths()
+{
+ 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)
evalState = std::make_shared<EvalState>(searchPath, getStore());
@@ -87,6 +112,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).first);
+}
+
struct InstallableStorePath : Installable
{
ref<Store> store;
@@ -116,54 +162,63 @@ struct InstallableStorePath : Installable
}
};
-struct InstallableValue : Installable
+std::vector<flake::EvalCache::Derivation> InstallableValue::toDerivations()
{
- SourceExprCommand & cmd;
+ auto state = cmd.getEvalState();
- InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { }
+ auto v = toValue(*state).first;
- Buildables toBuildables() override
- {
- auto state = cmd.getEvalState();
+ Bindings & autoArgs = *cmd.getAutoArgs(*state);
- auto v = toValue(*state).first;
+ DrvInfos drvInfos;
+ getDerivations(*state, *v, "", autoArgs, drvInfos, false);
- Bindings & autoArgs = *cmd.getAutoArgs(*state);
+ std::vector<flake::EvalCache::Derivation> res;
+ for (auto & drvInfo : drvInfos) {
+ res.push_back({
+ state->store->parseStorePath(drvInfo.queryDrvPath()),
+ state->store->parseStorePath(drvInfo.queryOutPath()),
+ drvInfo.queryOutputName()
+ });
+ }
- DrvInfos drvs;
- getDerivations(*state, *v, "", autoArgs, drvs, false);
+ return res;
+}
- Buildables res;
+Buildables InstallableValue::toBuildables()
+{
+ auto state = cmd.getEvalState();
- StorePathSet drvPaths;
+ Buildables res;
- for (auto & drv : drvs) {
- Buildable b{.drvPath = state->store->parseStorePath(drv.queryDrvPath())};
- drvPaths.insert(b.drvPath->clone());
+ StorePathSet drvPaths;
- auto outputName = drv.queryOutputName();
- if (outputName == "")
- throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath));
+ for (auto & drv : toDerivations()) {
+ Buildable b{.drvPath = drv.drvPath.clone()};
+ drvPaths.insert(drv.drvPath.clone());
- b.outputs.emplace(outputName, state->store->parseStorePath(drv.queryOutPath()));
+ auto outputName = drv.outputName;
+ if (outputName == "")
+ throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath));
- res.push_back(std::move(b));
- }
+ b.outputs.emplace(outputName, drv.outPath.clone());
- // Hack to recognize .all: if all drvs have the same drvPath,
- // merge the buildables.
- if (drvPaths.size() == 1) {
- Buildable b{.drvPath = drvPaths.begin()->clone()};
- for (auto & b2 : res)
- for (auto & output : b2.outputs)
- b.outputs.insert_or_assign(output.first, output.second.clone());
- Buildables bs;
- bs.push_back(std::move(b));
- return bs;
- } 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{.drvPath = drvPaths.begin()->clone()};
+ for (auto & b2 : res)
+ for (auto & output : b2.outputs)
+ b.outputs.insert_or_assign(output.first, output.second.clone());
+ Buildables bs;
+ bs.push_back(std::move(b));
+ return bs;
+ } else
+ return res;
+}
struct InstallableExpr : InstallableValue
{
@@ -184,70 +239,201 @@ 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; }
std::pair<Value *, Pos> toValue(EvalState & state) override
{
- auto source = cmd.getSourceExpr(state);
+ auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), *v);
+ state.forceValue(*vRes);
+ return {vRes, pos};
+ }
+};
- Bindings & autoArgs = *cmd.getAutoArgs(state);
+std::vector<std::string> InstallableFlake::getActualAttrPaths()
+{
+ std::vector<std::string> res;
- auto v = findAlongAttrPath(state, attrPath, autoArgs, *source).first;
- state.forceValue(*v);
+ for (auto & prefix : prefixes)
+ res.push_back(prefix + *attrPaths.begin());
- return {v, noPos};
+ for (auto & s : attrPaths)
+ res.push_back(s);
+
+ return res;
+}
+
+Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake)
+{
+ auto vFlake = state.allocValue();
+
+ callFlake(state, lockedFlake, *vFlake);
+
+ 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 lockedFlake = lockFlake(*state, flakeRef, cmd.lockFlags);
+
+ Value * vOutputs = nullptr;
+
+ auto emptyArgs = state->allocBindings(0);
+
+ auto & evalCache = flake::EvalCache::singleton();
+
+ auto fingerprint = lockedFlake.getFingerprint();
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ auto drv = evalCache.getDerivation(fingerprint, attrPath);
+ if (drv) {
+ if (state->store->isValidPath(drv->drvPath))
+ return {attrPath, lockedFlake.flake.lockedRef, std::move(*drv)};
+ }
+
+ if (!vOutputs)
+ vOutputs = getFlakeOutputs(*state, lockedFlake);
+
+ try {
+ auto * v = findAlongAttrPath(*state, attrPath, *emptyArgs, *vOutputs).first;
+ 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{
+ state->store->parseStorePath(drvInfo->queryDrvPath()),
+ state->store->parseStorePath(drvInfo->queryOutPath()),
+ drvInfo->queryOutputName()
+ };
+
+ evalCache.addDerivation(fingerprint, attrPath, drv);
+
+ return {attrPath, lockedFlake.flake.lockedRef, std::move(drv)};
+ } catch (AttrPathNotFound & e) {
+ }
}
-};
+
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, concatStringsSep(", ", quoteStrings(attrPaths)));
+}
+
+std::vector<flake::EvalCache::Derivation> InstallableFlake::toDerivations()
+{
+ std::vector<flake::EvalCache::Derivation> res;
+ res.push_back(std::get<2>(toDerivation()));
+ return res;
+}
+
+std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
+{
+ auto lockedFlake = lockFlake(state, flakeRef, cmd.lockFlags);
+
+ auto vOutputs = getFlakeOutputs(state, lockedFlake);
+
+ auto emptyArgs = state.allocBindings(0);
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ try {
+ auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
+ state.forceValue(*v);
+ return {v, pos};
+ } 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 = {""};
- }
+ if (file || expr) {
+ if (file && expr)
+ throw UsageError("'--file' and '--expr' are exclusive");
- for (auto & s : ss) {
+ // FIXME: backward compatibility hack
+ if (file) 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();
- else if (s.find("/") != std::string::npos) {
+ if (file)
+ state->evalFile(lookupFileArg(*state, *file), *vFile);
+ else {
+ auto e = state->parseExprFromString(*expr, absPath("."));
+ state->eval(e, *vFile);
+ }
- auto path = store->toStorePath(store->followLinksToStore(s));
+ for (auto & s : ss)
+ result.push_back(std::make_shared<InstallableAttrPath>(*this, vFile, s == "." ? "" : s));
- if (store->isStorePath(path))
- result.push_back(std::make_shared<InstallableStorePath>(store, 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<StorePath> {
+ 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) {
+ 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::fromAttrs({{"type", "indirect"}, {"id", "nixpkgs"}}),
+ Strings{"legacyPackages." + settings.thisSystem.get() + "." + std::string(s, 8)}, Strings{}));
+ }
+
+ else {
+ auto res = maybeParseFlakeRefWithFragment(s, absPath("."));
+ if (res) {
+ auto &[flakeRef, fragment] = *res;
+ result.push_back(std::make_shared<InstallableFlake>(
+ *this, std::move(flakeRef),
+ fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment},
+ getDefaultFlakeAttrPathPrefixes()));
+ } else {
+ std::optional<StorePath> storePath;
+ if (s.find('/') != std::string::npos && (storePath = follow(s)))
+ result.push_back(std::make_shared<InstallableStorePath>(store, store->printStorePath(*storePath)));
+ else
+ throw Error("unrecognized argument '%s'", s);
+ }
+ }
+ }
}
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();
}
@@ -302,7 +488,7 @@ StorePath 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()->clone();
}
@@ -333,12 +519,16 @@ StorePathSet toDerivations(ref<Store> store,
void InstallablesCommand::prepare()
{
- installables = parseInstallables(*this, getStore(), _installables, useDefaultInstallables());
+ if (_installables.empty() && 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);
}
}