From bcecc990071fd36bb88c8fd29cb009ed4c04d6a2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Apr 2017 11:20:37 +0200 Subject: [PATCH] Restructure installables handling in the "nix" command --- src/libexpr/value.hh | 8 ++ src/nix/build.cc | 19 +--- src/nix/command.cc | 7 ++ src/nix/command.hh | 4 + src/nix/installables.cc | 245 +++++++++++++++++++++++++++++++--------- src/nix/installables.hh | 59 ++++++---- src/nix/log.cc | 35 +++--- src/nix/path-info.cc | 1 - src/nix/run.cc | 19 +--- 9 files changed, 265 insertions(+), 132 deletions(-) diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 802e8ed2e..9df516f06 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -220,6 +220,14 @@ static inline void mkApp(Value & v, Value & left, Value & right) } +static inline void mkPrimOpApp(Value & v, Value & left, Value & right) +{ + v.type = tPrimOpApp; + v.app.left = &left; + v.app.right = &right; +} + + static inline void mkStringNoCopy(Value & v, const char * s) { v.type = tString; diff --git a/src/nix/build.cc b/src/nix/build.cc index 812464d75..0a34c68f8 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -6,7 +6,7 @@ using namespace nix; -struct CmdBuild : StoreCommand, MixDryRun, MixInstallables +struct CmdBuild : MixDryRun, MixInstallables { CmdBuild() { @@ -24,22 +24,9 @@ struct CmdBuild : StoreCommand, MixDryRun, MixInstallables void run(ref store) override { - auto elems = evalInstallables(store); + auto paths = buildInstallables(store, dryRun); - PathSet pathsToBuild; - - for (auto & elem : elems) { - if (elem.isDrv) - pathsToBuild.insert(elem.drvPath); - else - pathsToBuild.insert(elem.outPaths.begin(), elem.outPaths.end()); - } - - printMissing(store, pathsToBuild); - - if (dryRun) return; - - store->buildPaths(pathsToBuild); + printInfo("build result: %s", showPaths(paths)); } }; diff --git a/src/nix/command.cc b/src/nix/command.cc index a1b2c120a..4034de96c 100644 --- a/src/nix/command.cc +++ b/src/nix/command.cc @@ -79,6 +79,13 @@ StoreCommand::StoreCommand() mkFlag(0, "store", "store-uri", "URI of the Nix store to use", &storeUri); } +ref StoreCommand::getStore() +{ + if (!_store) + _store = createStore(); + return ref(_store); +} + ref StoreCommand::createStore() { return openStore(storeUri); diff --git a/src/nix/command.hh b/src/nix/command.hh index fa6c21abf..bb667ee32 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -33,8 +33,12 @@ struct StoreCommand : virtual Command std::string storeUri; StoreCommand(); void run() override; + ref getStore(); virtual ref createStore(); virtual void run(ref) = 0; + +private: + std::shared_ptr _store; }; /* A command that operates on zero or more store paths. */ diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 8341bbc5a..70007d62a 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -6,16 +6,21 @@ #include "get-drvs.hh" #include "installables.hh" #include "store-api.hh" +#include "shared.hh" + +#include namespace nix { -Value * MixInstallables::buildSourceExpr(EvalState & state) +Value * MixInstallables::getSourceExpr(EvalState & state) { - Value * vRoot = state.allocValue(); + if (vSourceExpr) return vSourceExpr; + + vSourceExpr = state.allocValue(); if (file != "") { Expr * e = state.parseExprFromFile(resolveExprPath(lookupFileArg(state, file))); - state.eval(e, *vRoot); + state.eval(e, *vSourceExpr); } else { @@ -24,7 +29,7 @@ Value * MixInstallables::buildSourceExpr(EvalState & state) auto searchPath = state.getSearchPath(); - state.mkAttrs(*vRoot, searchPath.size()); + state.mkAttrs(*vSourceExpr, searchPath.size()); std::unordered_set seen; @@ -32,76 +37,208 @@ Value * MixInstallables::buildSourceExpr(EvalState & state) if (i.first == "") continue; if (seen.count(i.first)) continue; seen.insert(i.first); - if (!pathExists(i.second)) continue; - mkApp(*state.allocAttr(*vRoot, state.symbols.create(i.first)), +#if 0 + auto res = state.resolveSearchPathElem(i); + if (!res.first) continue; + if (!pathExists(res.second)) continue; + mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(i.first)), state.getBuiltin("import"), - mkString(*state.allocValue(), i.second)); + mkString(*state.allocValue(), res.second)); +#endif + Value * v1 = state.allocValue(); + mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath")); + Value * v2 = state.allocValue(); + mkApp(*v2, *v1, mkString(*state.allocValue(), i.first)); + mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(i.first)), + state.getBuiltin("import"), *v2); } - vRoot->attrs->sort(); + vSourceExpr->attrs->sort(); } - return vRoot; + return vSourceExpr; } -UserEnvElems MixInstallables::evalInstallables(ref store) +struct InstallableStoreDrv : Installable { - UserEnvElems res; + Path storePath; + + InstallableStoreDrv(const Path & storePath) : storePath(storePath) { } + + std::string what() override { return storePath; } + + PathSet toBuildable() override + { + return {storePath}; + } +}; + +struct InstallableStorePath : Installable +{ + Path storePath; + + InstallableStorePath(const Path & storePath) : storePath(storePath) { } + + std::string what() override { return storePath; } + + PathSet toBuildable() override + { + return {storePath}; + } +}; + +struct InstallableExpr : Installable +{ + MixInstallables & installables; + std::string text; + + InstallableExpr(MixInstallables & installables, const std::string & text) + : installables(installables), text(text) { } + + std::string what() override { return text; } + + PathSet toBuildable() override + { + auto state = installables.getEvalState(); + + auto v = toValue(*state); + + // FIXME + std::map autoArgs_; + Bindings & autoArgs(*evalAutoArgs(*state, autoArgs_)); + + DrvInfos drvs; + getDerivations(*state, *v, "", autoArgs, drvs, false); + + PathSet res; + + for (auto & i : drvs) + res.insert(i.queryDrvPath()); + + return res; + } + + Value * toValue(EvalState & state) override + { + auto v = state.allocValue(); + state.eval(state.parseExprFromString(text, absPath(".")), *v); + return v; + } +}; + +struct InstallableAttrPath : Installable +{ + MixInstallables & installables; + std::string attrPath; + + InstallableAttrPath(MixInstallables & installables, const std::string & attrPath) + : installables(installables), attrPath(attrPath) + { } + + std::string what() override { return attrPath; } + + PathSet toBuildable() override + { + auto state = installables.getEvalState(); + + auto v = toValue(*state); + + // FIXME + std::map autoArgs_; + Bindings & autoArgs(*evalAutoArgs(*state, autoArgs_)); + + DrvInfos drvs; + getDerivations(*state, *v, "", autoArgs, drvs, false); + + PathSet res; + + for (auto & i : drvs) + res.insert(i.queryDrvPath()); + + return res; + } + + Value * toValue(EvalState & state) override + { + auto source = installables.getSourceExpr(state); + + // FIXME + std::map autoArgs_; + Bindings & autoArgs(*evalAutoArgs(state, autoArgs_)); + + Value * v = findAlongAttrPath(state, attrPath, autoArgs, *source); + state.forceValue(*v); + + return v; + } +}; + +// FIXME: extend +std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)"; +static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex)); + +std::vector> MixInstallables::parseInstallables(ref store, Strings installables) +{ + std::vector> result; for (auto & installable : installables) { if (std::string(installable, 0, 1) == "/") { if (store->isStorePath(installable)) { - - if (isDerivation(installable)) { - UserEnvElem elem; - // FIXME: handle empty case, drop version - elem.attrPath = {storePathToName(installable)}; - elem.isDrv = true; - elem.drvPath = installable; - res.push_back(elem); - } - - else { - UserEnvElem elem; - // FIXME: handle empty case, drop version - elem.attrPath = {storePathToName(installable)}; - elem.isDrv = false; - elem.outPaths = {installable}; - res.push_back(elem); - } + if (isDerivation(installable)) + result.push_back(std::make_shared(installable)); + else + result.push_back(std::make_shared(installable)); } - else - throw UsageError(format("don't know what to do with ‘%1%’") % installable); - } - - else { - - EvalState state({}, store); - - auto vRoot = buildSourceExpr(state); - - std::map autoArgs_; - Bindings & autoArgs(*evalAutoArgs(state, autoArgs_)); - - Value & v(*findAlongAttrPath(state, installable, autoArgs, *vRoot)); - state.forceValue(v); - - DrvInfos drvs; - getDerivations(state, v, "", autoArgs, drvs, false); - - for (auto & i : drvs) { - UserEnvElem elem; - elem.isDrv = true; - elem.drvPath = i.queryDrvPath(); - res.push_back(elem); + else { + result.push_back(std::make_shared( + store->toStorePath(store->followLinksToStore(installable)))); } + } + + else if (installable.compare(0, 1, "(") == 0) + result.push_back(std::make_shared(*this, installable)); + + else if (std::regex_match(installable, attrPathRegex)) + result.push_back(std::make_shared(*this, installable)); + + else + throw UsageError("don't know what to do with argument ‘%s’", installable); } - return res; + return result; +} + +PathSet MixInstallables::buildInstallables(ref store, bool dryRun) +{ + PathSet buildables; + + for (auto & i : installables) { + auto b = i->toBuildable(); + buildables.insert(b.begin(), b.end()); + } + + printMissing(store, buildables); + + if (!dryRun) + store->buildPaths(buildables); + + return buildables; +} + +ref MixInstallables::getEvalState() +{ + if (!evalState) + evalState = std::make_shared(Strings{}, getStore()); + return ref(evalState); +} + +void MixInstallables::prepare() +{ + installables = parseInstallables(getStore(), _installables); } } diff --git a/src/nix/installables.hh b/src/nix/installables.hh index a58f7dc59..5f0b0a292 100644 --- a/src/nix/installables.hh +++ b/src/nix/installables.hh @@ -1,48 +1,61 @@ #pragma once #include "args.hh" +#include "command.hh" namespace nix { -struct UserEnvElem -{ - Strings attrPath; - - // FIXME: should use boost::variant or so. - bool isDrv; - - // Derivation case: - Path drvPath; - StringSet outputNames; - - // Non-derivation case: - PathSet outPaths; -}; - -typedef std::vector UserEnvElems; - struct Value; class EvalState; +class Expr; -struct MixInstallables : virtual Args +struct Installable { - Strings installables; + virtual std::string what() = 0; + + virtual PathSet toBuildable() + { + throw Error("argument ‘%s’ cannot be built", what()); + } + + virtual Value * toValue(EvalState & state) + { + throw Error("argument ‘%s’ cannot be evaluated", what()); + } +}; + +struct MixInstallables : virtual Args, StoreCommand +{ + std::vector> installables; Path file; MixInstallables() { mkFlag('f', "file", "file", "evaluate FILE rather than the default", &file); - expectArgs("installables", &installables); + expectArgs("installables", &_installables); } - UserEnvElems evalInstallables(ref store); - /* Return a value representing the Nix expression from which we are installing. This is either the file specified by ‘--file’, or an attribute set constructed from $NIX_PATH, e.g. ‘{ nixpkgs = import ...; bla = import ...; }’. */ - Value * buildSourceExpr(EvalState & state); + Value * getSourceExpr(EvalState & state); + std::vector> parseInstallables(ref store, Strings installables); + + PathSet buildInstallables(ref store, bool dryRun); + + ref getEvalState(); + + void prepare() override; + +private: + + Strings _installables; + + std::shared_ptr evalState; + + Value * vSourceExpr = 0; }; } diff --git a/src/nix/log.cc b/src/nix/log.cc index d8a3830e9..75f3c1ab0 100644 --- a/src/nix/log.cc +++ b/src/nix/log.cc @@ -6,7 +6,7 @@ using namespace nix; -struct CmdLog : StoreCommand, MixInstallables +struct CmdLog : MixInstallables { CmdLog() { @@ -24,32 +24,23 @@ struct CmdLog : StoreCommand, MixInstallables void run(ref store) override { - auto elems = evalInstallables(store); - - PathSet paths; - - for (auto & elem : elems) { - if (elem.isDrv) - paths.insert(elem.drvPath); - else - paths.insert(elem.outPaths.begin(), elem.outPaths.end()); - } - auto subs = getDefaultSubstituters(); subs.push_front(store); - for (auto & path : paths) { - bool found = false; - for (auto & sub : subs) { - auto log = sub->getBuildLog(path); - if (!log) continue; - std::cout << *log; - found = true; - break; + for (auto & inst : installables) { + for (auto & path : inst->toBuildable()) { + bool found = false; + for (auto & sub : subs) { + auto log = sub->getBuildLog(path); + if (!log) continue; + std::cout << *log; + found = true; + break; + } + if (!found) + throw Error("build log of path ‘%s’ is not available", path); } - if (!found) - throw Error("build log of path ‘%s’ is not available", path); } } }; diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc index 30b193798..f16209238 100644 --- a/src/nix/path-info.cc +++ b/src/nix/path-info.cc @@ -99,7 +99,6 @@ struct CmdPathInfo : StorePathsCommand, MixJSON } } - } }; diff --git a/src/nix/run.cc b/src/nix/run.cc index a30031ad0..f3333b777 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -13,7 +13,7 @@ using namespace nix; -struct CmdRun : StoreCommand, MixInstallables +struct CmdRun : MixInstallables { CmdRun() { @@ -31,20 +31,7 @@ struct CmdRun : StoreCommand, MixInstallables void run(ref store) override { - auto elems = evalInstallables(store); - - PathSet pathsToBuild; - - for (auto & elem : elems) { - if (elem.isDrv) - pathsToBuild.insert(elem.drvPath); - else - pathsToBuild.insert(elem.outPaths.begin(), elem.outPaths.end()); - } - - printMissing(store, pathsToBuild); - - store->buildPaths(pathsToBuild); + auto paths = buildInstallables(store, false); auto store2 = store.dynamic_pointer_cast(); @@ -104,7 +91,7 @@ struct CmdRun : StoreCommand, MixInstallables } PathSet outPaths; - for (auto & path : pathsToBuild) + for (auto & path : paths) if (isDerivation(path)) { Derivation drv = store->derivationFromPath(path); for (auto & output : drv.outputs)