aboutsummaryrefslogtreecommitdiff
path: root/src/libcmd
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcmd')
-rw-r--r--src/libcmd/command.cc23
-rw-r--r--src/libcmd/command.hh26
-rw-r--r--src/libcmd/common-eval-args.cc97
-rw-r--r--src/libcmd/common-eval-args.hh2
-rw-r--r--src/libcmd/installables.cc374
-rw-r--r--src/libcmd/installables.hh22
-rw-r--r--src/libcmd/local.mk4
-rw-r--r--src/libcmd/markdown.cc6
-rw-r--r--src/libcmd/repl.cc1139
9 files changed, 1531 insertions, 162 deletions
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
index a53b029b7..0740ea960 100644
--- a/src/libcmd/command.cc
+++ b/src/libcmd/command.cc
@@ -86,6 +86,12 @@ ref<Store> CopyCommand::getDstStore()
EvalCommand::EvalCommand()
{
+ addFlag({
+ .longName = "debugger",
+ .description = "Start an interactive environment if evaluation fails.",
+ .category = MixEvalArgs::category,
+ .handler = {&startReplOnEvalErrors, true},
+ });
}
EvalCommand::~EvalCommand()
@@ -103,7 +109,7 @@ ref<Store> EvalCommand::getEvalStore()
ref<EvalState> EvalCommand::getEvalState()
{
- if (!evalState)
+ if (!evalState) {
evalState =
#if HAVE_BOEHMGC
std::allocate_shared<EvalState>(traceable_allocator<EvalState>(),
@@ -113,6 +119,11 @@ ref<EvalState> EvalCommand::getEvalState()
searchPath, getEvalStore(), getStore())
#endif
;
+
+ if (startReplOnEvalErrors) {
+ evalState->debugRepl = &runRepl;
+ };
+ }
return ref<EvalState>(evalState);
}
@@ -197,17 +208,17 @@ void StorePathCommand::run(ref<Store> store, std::vector<StorePath> && storePath
run(store, *storePaths.begin());
}
-Strings editorFor(const Pos & pos)
+Strings editorFor(const Path & file, uint32_t line)
{
auto editor = getEnv("EDITOR").value_or("cat");
auto args = tokenizeString<Strings>(editor);
- if (pos.line > 0 && (
+ if (line > 0 && (
editor.find("emacs") != std::string::npos ||
editor.find("nano") != std::string::npos ||
editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos))
- args.push_back(fmt("+%d", pos.line));
- args.push_back(pos.file);
+ args.push_back(fmt("+%d", line));
+ args.push_back(file);
return args;
}
@@ -215,7 +226,7 @@ MixProfile::MixProfile()
{
addFlag({
.longName = "profile",
- .description = "The profile to update.",
+ .description = "The profile to operate on.",
.labels = {"path"},
.handler = {&profile},
.completer = completePath
diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh
index 84bbb5292..3b4b40981 100644
--- a/src/libcmd/command.hh
+++ b/src/libcmd/command.hh
@@ -57,6 +57,9 @@ struct CopyCommand : virtual StoreCommand
struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
+ bool startReplOnEvalErrors = false;
+ bool ignoreExceptionsDuringTry = false;
+
EvalCommand();
~EvalCommand();
@@ -75,10 +78,16 @@ struct MixFlakeOptions : virtual Args, EvalCommand
{
flake::LockFlags lockFlags;
+ std::optional<std::string> needsFlakeInputCompletion = {};
+
MixFlakeOptions();
- virtual std::optional<FlakeRef> getFlakeRefForCompletion()
+ virtual std::vector<std::string> getFlakesForCompletion()
{ return {}; }
+
+ void completeFlakeInput(std::string_view prefix);
+
+ void completionHook() override;
};
struct SourceExprCommand : virtual Args, MixFlakeOptions
@@ -114,12 +123,13 @@ struct InstallablesCommand : virtual Args, SourceExprCommand
InstallablesCommand();
void prepare() override;
+ Installables load();
virtual bool useDefaultInstallables() { return true; }
- std::optional<FlakeRef> getFlakeRefForCompletion() override;
+ std::vector<std::string> getFlakesForCompletion() override;
-private:
+protected:
std::vector<std::string> _installables;
};
@@ -133,9 +143,9 @@ struct InstallableCommand : virtual Args, SourceExprCommand
void prepare() override;
- std::optional<FlakeRef> getFlakeRefForCompletion() override
+ std::vector<std::string> getFlakesForCompletion() override
{
- return parseFlakeRefWithFragment(_installable, absPath(".")).first;
+ return {_installable};
}
private:
@@ -219,7 +229,7 @@ static RegisterCommand registerCommand2(std::vector<std::string> && name)
/* Helper function to generate args that invoke $EDITOR on
filename:lineno. */
-Strings editorFor(const Pos & pos);
+Strings editorFor(const Path & file, uint32_t line);
struct MixProfile : virtual StoreCommand
{
@@ -270,4 +280,8 @@ void printClosureDiff(
const StorePath & afterPath,
std::string_view indent);
+
+void runRepl(
+ ref<EvalState> evalState,
+ const ValMap & extraEnv);
}
diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc
index 5b6e82388..0e321e5e4 100644
--- a/src/libcmd/common-eval-args.cc
+++ b/src/libcmd/common-eval-args.cc
@@ -13,8 +13,6 @@ namespace nix {
MixEvalArgs::MixEvalArgs()
{
- auto category = "Common evaluation options";
-
addFlag({
.longName = "arg",
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
@@ -34,7 +32,77 @@ MixEvalArgs::MixEvalArgs()
addFlag({
.longName = "include",
.shortName = 'I',
- .description = "Add *path* to the list of locations used to look up `<...>` file names.",
+ .description = R"(
+ Add *path* to the Nix search path. The Nix search path is
+ initialized from the colon-separated [`NIX_PATH`](./env-common.md#env-NIX_PATH) environment
+ variable, and is used to look up the location of Nix expressions using [paths](../language/values.md#type-path) enclosed in angle
+ brackets (i.e., `<nixpkgs>`).
+
+ For instance, passing
+
+ ```
+ -I /home/eelco/Dev
+ -I /etc/nixos
+ ```
+
+ will cause Nix to look for paths relative to `/home/eelco/Dev` and
+ `/etc/nixos`, in that order. This is equivalent to setting the
+ `NIX_PATH` environment variable to
+
+ ```
+ /home/eelco/Dev:/etc/nixos
+ ```
+
+ It is also possible to match paths against a prefix. For example,
+ passing
+
+ ```
+ -I nixpkgs=/home/eelco/Dev/nixpkgs-branch
+ -I /etc/nixos
+ ```
+
+ will cause Nix to search for `<nixpkgs/path>` in
+ `/home/eelco/Dev/nixpkgs-branch/path` and `/etc/nixos/nixpkgs/path`.
+
+ If a path in the Nix search path starts with `http://` or `https://`,
+ it is interpreted as the URL of a tarball that will be downloaded and
+ unpacked to a temporary location. The tarball must consist of a single
+ top-level directory. For example, passing
+
+ ```
+ -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/master.tar.gz
+ ```
+
+ tells Nix to download and use the current contents of the `master`
+ branch in the `nixpkgs` repository.
+
+ The URLs of the tarballs from the official `nixos.org` channels
+ (see [the manual page for `nix-channel`](../nix-channel.md)) can be
+ abbreviated as `channel:<channel-name>`. For instance, the
+ following two flags are equivalent:
+
+ ```
+ -I nixpkgs=channel:nixos-21.05
+ -I nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz
+ ```
+
+ You can also fetch source trees using [flake URLs](./nix3-flake.md#url-like-syntax) and add them to the
+ search path. For instance,
+
+ ```
+ -I nixpkgs=flake:nixpkgs
+ ```
+
+ specifies that the prefix `nixpkgs` shall refer to the source tree
+ downloaded from the `nixpkgs` entry in the flake registry. Similarly,
+
+ ```
+ -I nixpkgs=flake:github:NixOS/nixpkgs/nixos-22.05
+ ```
+
+ makes `<nixpkgs>` refer to a particular branch of the
+ `NixOS/nixpkgs` repository on GitHub.
+ )",
.category = category,
.labels = {"path"},
.handler = {[&](std::string s) { searchPath.push_back(s); }}
@@ -91,14 +159,25 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
Path lookupFileArg(EvalState & state, std::string_view s)
{
- if (isUri(s)) {
- return state.store->toRealPath(
- fetchers::downloadTarball(
- state.store, resolveUri(s), "source", false).first.storePath);
- } else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
+ if (EvalSettings::isPseudoUrl(s)) {
+ auto storePath = fetchers::downloadTarball(
+ state.store, EvalSettings::resolvePseudoUrl(s), "source", false).first.storePath;
+ return state.store->toRealPath(storePath);
+ }
+
+ else if (hasPrefix(s, "flake:")) {
+ settings.requireExperimentalFeature(Xp::Flakes);
+ auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false);
+ auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath;
+ return state.store->toRealPath(storePath);
+ }
+
+ else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
Path p(s.substr(1, s.size() - 2));
return state.findFile(p);
- } else
+ }
+
+ else
return absPath(std::string(s));
}
diff --git a/src/libcmd/common-eval-args.hh b/src/libcmd/common-eval-args.hh
index 03fa226aa..1ec800613 100644
--- a/src/libcmd/common-eval-args.hh
+++ b/src/libcmd/common-eval-args.hh
@@ -10,6 +10,8 @@ class Bindings;
struct MixEvalArgs : virtual Args
{
+ static constexpr auto category = "Common evaluation options";
+
MixEvalArgs();
Bindings * getAutoArgs(EvalState & state);
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index 74c8a6df5..79361e94e 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -23,17 +23,6 @@
namespace nix {
-void completeFlakeInputPath(
- ref<EvalState> evalState,
- const FlakeRef & flakeRef,
- std::string_view prefix)
-{
- auto flake = flake::getFlake(*evalState, flakeRef, true);
- for (auto & input : flake.inputs)
- if (hasPrefix(input.first, prefix))
- completions->add(input.first);
-}
-
MixFlakeOptions::MixFlakeOptions()
{
auto category = "Common flake-related options";
@@ -86,8 +75,7 @@ MixFlakeOptions::MixFlakeOptions()
lockFlags.inputUpdates.insert(flake::parseInputPath(s));
}},
.completer = {[&](size_t, std::string_view prefix) {
- if (auto flakeRef = getFlakeRefForCompletion())
- completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
+ needsFlakeInputCompletion = {std::string(prefix)};
}}
});
@@ -103,12 +91,10 @@ MixFlakeOptions::MixFlakeOptions()
parseFlakeRef(flakeRef, absPath("."), true));
}},
.completer = {[&](size_t n, std::string_view prefix) {
- if (n == 0) {
- if (auto flakeRef = getFlakeRefForCompletion())
- completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
- } else if (n == 1) {
+ if (n == 0)
+ needsFlakeInputCompletion = {std::string(prefix)};
+ else if (n == 1)
completeFlakeRef(getEvalState()->store, prefix);
- }
}}
});
@@ -139,6 +125,24 @@ MixFlakeOptions::MixFlakeOptions()
});
}
+void MixFlakeOptions::completeFlakeInput(std::string_view prefix)
+{
+ auto evalState = getEvalState();
+ for (auto & flakeRefS : getFlakesForCompletion()) {
+ auto flakeRef = parseFlakeRefWithFragment(expandTilde(flakeRefS), absPath(".")).first;
+ auto flake = flake::getFlake(*evalState, flakeRef, true);
+ for (auto & input : flake.inputs)
+ if (hasPrefix(input.first, prefix))
+ completions->add(input.first);
+ }
+}
+
+void MixFlakeOptions::completionHook()
+{
+ if (auto & prefix = needsFlakeInputCompletion)
+ completeFlakeInput(*prefix);
+}
+
SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
{
addFlag({
@@ -146,7 +150,8 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
.shortName = 'f',
.description =
"Interpret installables as attribute paths relative to the Nix expression stored in *file*. "
- "If *file* is the character -, then a Nix expression will be read from standard input.",
+ "If *file* is the character -, then a Nix expression will be read from standard input. "
+ "Implies `--impure`.",
.category = installablesCategory,
.labels = {"file"},
.handler = {&file},
@@ -163,7 +168,7 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
addFlag({
.longName = "derivation",
- .description = "Operate on the store derivation rather than its outputs.",
+ .description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.",
.category = installablesCategory,
.handler = {&operateOn, OperateOn::Derivation},
});
@@ -202,55 +207,59 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
void SourceExprCommand::completeInstallable(std::string_view prefix)
{
- if (file) {
- completionType = ctAttrs;
+ try {
+ if (file) {
+ completionType = ctAttrs;
- evalSettings.pureEval = false;
- auto state = getEvalState();
- Expr *e = state->parseExprFromFile(
- resolveExprPath(state->checkSourcePath(lookupFileArg(*state, *file)))
- );
+ evalSettings.pureEval = false;
+ auto state = getEvalState();
+ Expr *e = state->parseExprFromFile(
+ resolveExprPath(state->checkSourcePath(lookupFileArg(*state, *file)))
+ );
- Value root;
- state->eval(e, root);
+ Value root;
+ state->eval(e, root);
- auto autoArgs = getAutoArgs(*state);
+ auto autoArgs = getAutoArgs(*state);
- std::string prefix_ = std::string(prefix);
- auto sep = prefix_.rfind('.');
- std::string searchWord;
- if (sep != std::string::npos) {
- searchWord = prefix_.substr(sep + 1, std::string::npos);
- prefix_ = prefix_.substr(0, sep);
- } else {
- searchWord = prefix_;
- prefix_ = "";
- }
+ std::string prefix_ = std::string(prefix);
+ auto sep = prefix_.rfind('.');
+ std::string searchWord;
+ if (sep != std::string::npos) {
+ searchWord = prefix_.substr(sep + 1, std::string::npos);
+ prefix_ = prefix_.substr(0, sep);
+ } else {
+ searchWord = prefix_;
+ prefix_ = "";
+ }
- auto [v, pos] = findAlongAttrPath(*state, prefix_, *autoArgs, root);
- Value &v1(*v);
- state->forceValue(v1, pos);
- Value v2;
- state->autoCallFunction(*autoArgs, v1, v2);
-
- if (v2.type() == nAttrs) {
- for (auto & i : *v2.attrs) {
- std::string name = i.name;
- if (name.find(searchWord) == 0) {
- if (prefix_ == "")
- completions->add(name);
- else
- completions->add(prefix_ + "." + name);
+ auto [v, pos] = findAlongAttrPath(*state, prefix_, *autoArgs, root);
+ Value &v1(*v);
+ state->forceValue(v1, pos);
+ Value v2;
+ state->autoCallFunction(*autoArgs, v1, v2);
+
+ if (v2.type() == nAttrs) {
+ for (auto & i : *v2.attrs) {
+ std::string name = state->symbols[i.name];
+ if (name.find(searchWord) == 0) {
+ if (prefix_ == "")
+ completions->add(name);
+ else
+ completions->add(prefix_ + "." + name);
+ }
}
}
+ } else {
+ completeFlakeRefWithFragment(
+ getEvalState(),
+ lockFlags,
+ getDefaultFlakeAttrPathPrefixes(),
+ getDefaultFlakeAttrPaths(),
+ prefix);
}
- } else {
- completeFlakeRefWithFragment(
- getEvalState(),
- lockFlags,
- getDefaultFlakeAttrPathPrefixes(),
- getDefaultFlakeAttrPaths(),
- prefix);
+ } catch (EvalError&) {
+ // Don't want eval errors to mess-up with the completion engine, so let's just swallow them
}
}
@@ -291,7 +300,7 @@ void completeFlakeRefWithFragment(
std::string lastAttr;
if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) {
- lastAttr = attrPath.back();
+ lastAttr = evalState->symbols[attrPath.back()];
attrPath.pop_back();
}
@@ -299,11 +308,11 @@ void completeFlakeRefWithFragment(
if (!attr) continue;
for (auto & attr2 : (*attr)->getAttrs()) {
- if (hasPrefix(attr2, lastAttr)) {
+ if (hasPrefix(evalState->symbols[attr2], lastAttr)) {
auto attrPath2 = (*attr)->getAttrPath(attr2);
/* Strip the attrpath prefix. */
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
- completions->add(flakeRefS + "#" + concatStringsSep(".", attrPath2));
+ completions->add(flakeRefS + "#" + concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
}
}
}
@@ -390,44 +399,56 @@ static StorePath getDeriver(
struct InstallableStorePath : Installable
{
ref<Store> store;
- StorePath storePath;
+ DerivedPath req;
InstallableStorePath(ref<Store> store, StorePath && storePath)
- : store(store), storePath(std::move(storePath)) { }
+ : store(store),
+ req(storePath.isDerivation()
+ ? (DerivedPath) DerivedPath::Built {
+ .drvPath = std::move(storePath),
+ .outputs = {},
+ }
+ : (DerivedPath) DerivedPath::Opaque {
+ .path = std::move(storePath),
+ })
+ { }
- std::string what() const override { return store->printStorePath(storePath); }
+ InstallableStorePath(ref<Store> store, DerivedPath && req)
+ : store(store), req(std::move(req))
+ { }
+
+ std::string what() const override
+ {
+ return req.to_string(*store);
+ }
DerivedPaths toDerivedPaths() override
{
- if (storePath.isDerivation()) {
- auto drv = store->readDerivation(storePath);
- return {
- DerivedPath::Built {
- .drvPath = storePath,
- .outputs = drv.outputNames(),
- }
- };
- } else {
- return {
- DerivedPath::Opaque {
- .path = storePath,
- }
- };
- }
+ return { req };
}
StorePathSet toDrvPaths(ref<Store> store) override
{
- if (storePath.isDerivation()) {
- return {storePath};
- } else {
- return {getDeriver(store, *this, storePath)};
- }
+ return std::visit(overloaded {
+ [&](const DerivedPath::Built & bfd) -> StorePathSet {
+ return { bfd.drvPath };
+ },
+ [&](const DerivedPath::Opaque & bo) -> StorePathSet {
+ return { getDeriver(store, *this, bo.path) };
+ },
+ }, req.raw());
}
std::optional<StorePath> getStorePath() override
{
- return storePath;
+ return std::visit(overloaded {
+ [&](const DerivedPath::Built & bfd) {
+ return bfd.drvPath;
+ },
+ [&](const DerivedPath::Opaque & bo) {
+ return bo.path;
+ },
+ }, req.raw());
}
};
@@ -440,10 +461,8 @@ DerivedPaths InstallableValue::toDerivedPaths()
// Group by derivation, helps with .all in particular
for (auto & drv : toDerivations()) {
- auto outputName = drv.outputName;
- if (outputName == "")
- throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath));
- drvsToOutputs[drv.drvPath].insert(outputName);
+ for (auto & outputName : drv.outputsToInstall)
+ drvsToOutputs[drv.drvPath].insert(outputName);
drvsToCopy.insert(drv.drvPath);
}
@@ -466,14 +485,24 @@ struct InstallableAttrPath : InstallableValue
SourceExprCommand & cmd;
RootValue v;
std::string attrPath;
-
- InstallableAttrPath(ref<EvalState> state, SourceExprCommand & cmd, Value * v, const std::string & attrPath)
- : InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath)
+ OutputsSpec outputsSpec;
+
+ InstallableAttrPath(
+ ref<EvalState> state,
+ SourceExprCommand & cmd,
+ Value * v,
+ const std::string & attrPath,
+ OutputsSpec outputsSpec)
+ : InstallableValue(state)
+ , cmd(cmd)
+ , v(allocRootValue(v))
+ , attrPath(attrPath)
+ , outputsSpec(std::move(outputsSpec))
{ }
std::string what() const override { return attrPath; }
- std::pair<Value *, Pos> toValue(EvalState & state) override
+ std::pair<Value *, PosIdx> toValue(EvalState & state) override
{
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
state.forceValue(*vRes, pos);
@@ -497,7 +526,19 @@ std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations
auto drvPath = drvInfo.queryDrvPath();
if (!drvPath)
throw Error("'%s' is not a derivation", what());
- res.push_back({ *drvPath, drvInfo.queryOutputName() });
+
+ std::set<std::string> outputsToInstall;
+
+ if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
+ outputsToInstall = *outputNames;
+ else
+ for (auto & output : drvInfo.queryOutputs(false, std::get_if<DefaultOutputs>(&outputsSpec)))
+ outputsToInstall.insert(output.first);
+
+ res.push_back(DerivationInfo {
+ .drvPath = *drvPath,
+ .outputsToInstall = std::move(outputsToInstall)
+ });
}
return res;
@@ -550,7 +591,7 @@ ref<eval_cache::EvalCache> openEvalCache(
auto vFlake = state.allocValue();
flake::callFlake(state, *lockedFlake, *vFlake);
- state.forceAttrs(*vFlake, noPos);
+ state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
assert(aOutputs);
@@ -574,6 +615,7 @@ InstallableFlake::InstallableFlake(
ref<EvalState> state,
FlakeRef && flakeRef,
std::string_view fragment,
+ OutputsSpec outputsSpec,
Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags)
@@ -581,6 +623,7 @@ InstallableFlake::InstallableFlake(
flakeRef(flakeRef),
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
prefixes(fragment == "" ? Strings{} : prefixes),
+ outputsSpec(std::move(outputsSpec)),
lockFlags(lockFlags)
{
if (cmd && cmd->getAutoArgs(*state)->size())
@@ -589,6 +632,8 @@ InstallableFlake::InstallableFlake(
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
{
+ Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
+
auto attr = getCursor(*state);
auto attrPath = attr->getAttrPathStr();
@@ -598,9 +643,41 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
auto drvPath = attr->forceDerivation();
+ std::set<std::string> outputsToInstall;
+ std::optional<NixInt> priority;
+
+ if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
+ if (aOutputSpecified->getBool()) {
+ if (auto aOutputName = attr->maybeGetAttr("outputName"))
+ outputsToInstall = { aOutputName->getString() };
+ }
+ }
+
+ else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
+ if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
+ for (auto & s : aOutputsToInstall->getListOfStrings())
+ outputsToInstall.insert(s);
+ if (auto aPriority = aMeta->maybeGetAttr("priority"))
+ priority = aPriority->getInt();
+ }
+
+ if (outputsToInstall.empty() || std::get_if<AllOutputs>(&outputsSpec)) {
+ outputsToInstall.clear();
+ if (auto aOutputs = attr->maybeGetAttr(state->sOutputs))
+ for (auto & s : aOutputs->getListOfStrings())
+ outputsToInstall.insert(s);
+ }
+
+ if (outputsToInstall.empty())
+ outputsToInstall.insert("out");
+
+ if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
+ outputsToInstall = *outputNames;
+
auto drvInfo = DerivationInfo {
- std::move(drvPath),
- attr->getAttr(state->sOutputName)->getString()
+ .drvPath = std::move(drvPath),
+ .outputsToInstall = std::move(outputsToInstall),
+ .priority = priority,
};
return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};
@@ -613,7 +690,7 @@ std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
return res;
}
-std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
+std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
{
return {&getCursor(state)->forceValue(), noPos};
}
@@ -716,22 +793,44 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
if (file == "-") {
auto e = state->parseStdin();
state->eval(e, *vFile);
- } else if (file)
+ }
+ else if (file)
state->evalFile(lookupFileArg(*state, *file), *vFile);
else {
auto e = state->parseExprFromString(*expr, absPath("."));
state->eval(e, *vFile);
}
- for (auto & s : ss)
- result.push_back(std::make_shared<InstallableAttrPath>(state, *this, vFile, s == "." ? "" : s));
+ for (auto & s : ss) {
+ auto [prefix, outputsSpec] = parseOutputsSpec(s);
+ result.push_back(
+ std::make_shared<InstallableAttrPath>(
+ state, *this, vFile,
+ prefix == "." ? "" : prefix,
+ outputsSpec));
+ }
} else {
for (auto & s : ss) {
std::exception_ptr ex;
- if (s.find('/') != std::string::npos) {
+ auto found = s.rfind('^');
+ if (found != std::string::npos) {
+ try {
+ result.push_back(std::make_shared<InstallableStorePath>(
+ store,
+ DerivedPath::Built::parse(*store, s.substr(0, found), s.substr(found + 1))));
+ continue;
+ } catch (BadStorePath &) {
+ } catch (...) {
+ if (!ex)
+ ex = std::current_exception();
+ }
+ }
+
+ found = s.find('/');
+ if (found != std::string::npos) {
try {
result.push_back(std::make_shared<InstallableStorePath>(store, store->followLinksToStorePath(s)));
continue;
@@ -743,12 +842,13 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
}
try {
- auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath("."));
+ auto [flakeRef, fragment, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(s, absPath("."));
result.push_back(std::make_shared<InstallableFlake>(
this,
getEvalState(),
std::move(flakeRef),
fragment,
+ outputsSpec,
getDefaultFlakeAttrPaths(),
getDefaultFlakeAttrPathPrefixes(),
lockFlags));
@@ -772,20 +872,20 @@ std::shared_ptr<Installable> SourceExprCommand::parseInstallable(
return installables.front();
}
-BuiltPaths Installable::build(
+std::vector<BuiltPathWithResult> Installable::build(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
BuildMode bMode)
{
- BuiltPaths res;
- for (auto & [_, builtPath] : build2(evalStore, store, mode, installables, bMode))
- res.push_back(builtPath);
+ std::vector<BuiltPathWithResult> res;
+ for (auto & [_, builtPathWithResult] : build2(evalStore, store, mode, installables, bMode))
+ res.push_back(builtPathWithResult);
return res;
}
-std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::build2(
+std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> Installable::build2(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -805,7 +905,7 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
}
}
- std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> res;
+ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> res;
switch (mode) {
@@ -822,32 +922,31 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
auto drvOutputs = drv.outputsAndOptPaths(*store);
for (auto & output : bfd.outputs) {
- if (!outputHashes.count(output))
+ auto outputHash = get(outputHashes, output);
+ if (!outputHash)
throw Error(
"the derivation '%s' doesn't have an output named '%s'",
store->printStorePath(bfd.drvPath), output);
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
- DrvOutput outputId { outputHashes.at(output), output };
+ DrvOutput outputId { *outputHash, output };
auto realisation = store->queryRealisation(outputId);
if (!realisation)
- throw Error(
- "cannot operate on an output of unbuilt "
- "content-addressed derivation '%s'",
- outputId.to_string());
+ throw MissingRealisation(outputId);
outputs.insert_or_assign(output, realisation->outPath);
} else {
// If ca-derivations isn't enabled, assume that
// the output path is statically known.
- assert(drvOutputs.count(output));
- assert(drvOutputs.at(output).second);
+ auto drvOutput = get(drvOutputs, output);
+ assert(drvOutput);
+ assert(drvOutput->second);
outputs.insert_or_assign(
- output, *drvOutputs.at(output).second);
+ output, *drvOutput->second);
}
}
- res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
+ res.push_back({installable, {.path = BuiltPath::Built { bfd.drvPath, outputs }}});
},
[&](const DerivedPath::Opaque & bo) {
- res.push_back({installable, BuiltPath::Opaque { bo.path }});
+ res.push_back({installable, {.path = BuiltPath::Opaque { bo.path }}});
},
}, path.raw());
}
@@ -856,6 +955,9 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
break;
case Realise::Outputs: {
+ if (settings.printMissing)
+ printMissing(store, pathsToBuild, lvlInfo);
+
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
if (!buildResult.success())
buildResult.rethrow();
@@ -866,10 +968,10 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
std::map<std::string, StorePath> outputs;
for (auto & path : buildResult.builtOutputs)
outputs.emplace(path.first.outputName, path.second.outPath);
- res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
+ res.push_back({installable, {.path = BuiltPath::Built { bfd.drvPath, outputs }, .result = buildResult}});
},
[&](const DerivedPath::Opaque & bo) {
- res.push_back({installable, BuiltPath::Opaque { bo.path }});
+ res.push_back({installable, {.path = BuiltPath::Opaque { bo.path }, .result = buildResult}});
},
}, buildResult.path.raw());
}
@@ -892,9 +994,12 @@ BuiltPaths Installable::toBuiltPaths(
OperateOn operateOn,
const std::vector<std::shared_ptr<Installable>> & installables)
{
- if (operateOn == OperateOn::Output)
- return Installable::build(evalStore, store, mode, installables);
- else {
+ if (operateOn == OperateOn::Output) {
+ BuiltPaths res;
+ for (auto & p : Installable::build(evalStore, store, mode, installables))
+ res.push_back(p.path);
+ return res;
+ } else {
if (mode == Realise::Nothing)
settings.readOnlyMode = true;
@@ -969,21 +1074,26 @@ InstallablesCommand::InstallablesCommand()
void InstallablesCommand::prepare()
{
+ installables = load();
+}
+
+Installables InstallablesCommand::load() {
+ Installables installables;
if (_installables.empty() && useDefaultInstallables())
// FIXME: commands like "nix profile install" should not have a
// default, probably.
_installables.push_back(".");
- installables = parseInstallables(getStore(), _installables);
+ return parseInstallables(getStore(), _installables);
}
-std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
+std::vector<std::string> InstallablesCommand::getFlakesForCompletion()
{
if (_installables.empty()) {
if (useDefaultInstallables())
- return parseFlakeRefWithFragment(".", absPath(".")).first;
+ return {"."};
return {};
}
- return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first;
+ return _installables;
}
InstallableCommand::InstallableCommand(bool supportReadOnlyMode)
diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
index b847f8939..02ea351d3 100644
--- a/src/libcmd/installables.hh
+++ b/src/libcmd/installables.hh
@@ -7,6 +7,7 @@
#include "eval.hh"
#include "store-api.hh"
#include "flake/flake.hh"
+#include "build-result.hh"
#include <optional>
@@ -51,6 +52,12 @@ enum class OperateOn {
Derivation
};
+struct BuiltPathWithResult
+{
+ BuiltPath path;
+ std::optional<BuildResult> result;
+};
+
struct Installable
{
virtual ~Installable() { }
@@ -68,7 +75,7 @@ struct Installable
UnresolvedApp toApp(EvalState & state);
- virtual std::pair<Value *, Pos> toValue(EvalState & state)
+ virtual std::pair<Value *, PosIdx> toValue(EvalState & state)
{
throw Error("argument '%s' cannot be evaluated", what());
}
@@ -91,14 +98,14 @@ struct Installable
return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
}
- static BuiltPaths build(
+ static std::vector<BuiltPathWithResult> build(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
BuildMode bMode = bmNormal);
- static std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> build2(
+ static std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> build2(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
@@ -132,6 +139,8 @@ struct Installable
const std::vector<std::shared_ptr<Installable>> & installables);
};
+typedef std::vector<std::shared_ptr<Installable>> Installables;
+
struct InstallableValue : Installable
{
ref<EvalState> state;
@@ -141,7 +150,8 @@ struct InstallableValue : Installable
struct DerivationInfo
{
StorePath drvPath;
- std::string outputName;
+ std::set<std::string> outputsToInstall;
+ std::optional<NixInt> priority;
};
virtual std::vector<DerivationInfo> toDerivations() = 0;
@@ -156,6 +166,7 @@ struct InstallableFlake : InstallableValue
FlakeRef flakeRef;
Strings attrPaths;
Strings prefixes;
+ OutputsSpec outputsSpec;
const flake::LockFlags & lockFlags;
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
@@ -164,6 +175,7 @@ struct InstallableFlake : InstallableValue
ref<EvalState> state,
FlakeRef && flakeRef,
std::string_view fragment,
+ OutputsSpec outputsSpec,
Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags);
@@ -178,7 +190,7 @@ struct InstallableFlake : InstallableValue
std::vector<DerivationInfo> toDerivations() override;
- std::pair<Value *, Pos> toValue(EvalState & state) override;
+ std::pair<Value *, PosIdx> toValue(EvalState & state) override;
/* Get a cursor to every attrpath in getActualAttrPaths() that
exists. */
diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk
index 7a2f83cc7..152bc388d 100644
--- a/src/libcmd/local.mk
+++ b/src/libcmd/local.mk
@@ -6,9 +6,9 @@ libcmd_DIR := $(d)
libcmd_SOURCES := $(wildcard $(d)/*.cc)
-libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers
+libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -I src/nix
-libcmd_LDFLAGS += $(LOWDOWN_LIBS) -pthread
+libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) -pthread
libcmd_LIBS = libstore libutil libexpr libmain libfetchers
diff --git a/src/libcmd/markdown.cc b/src/libcmd/markdown.cc
index 29bb4d31e..668a07763 100644
--- a/src/libcmd/markdown.cc
+++ b/src/libcmd/markdown.cc
@@ -9,14 +9,16 @@ namespace nix {
std::string renderMarkdownToTerminal(std::string_view markdown)
{
+ int windowWidth = getWindowSize().second;
+
struct lowdown_opts opts {
.type = LOWDOWN_TERM,
.maxdepth = 20,
- .cols = std::max(getWindowSize().second, (unsigned short) 80),
+ .cols = (size_t) std::max(windowWidth - 5, 60),
.hmargin = 0,
.vmargin = 0,
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
- .oflags = 0,
+ .oflags = LOWDOWN_TERM_NOLINK,
};
auto doc = lowdown_doc_new(&opts);
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
new file mode 100644
index 000000000..71a7e079a
--- /dev/null
+++ b/src/libcmd/repl.cc
@@ -0,0 +1,1139 @@
+#include <iostream>
+#include <cstdlib>
+#include <cstring>
+#include <climits>
+
+#include <setjmp.h>
+
+#ifdef READLINE
+#include <readline/history.h>
+#include <readline/readline.h>
+#else
+// editline < 1.15.2 don't wrap their API for C++ usage
+// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
+// This results in linker errors due to to name-mangling of editline C symbols.
+// For compatibility with these versions, we wrap the API here
+// (wrapping multiple times on newer versions is no problem).
+extern "C" {
+#include <editline.h>
+}
+#endif
+
+#include "ansicolor.hh"
+#include "shared.hh"
+#include "eval.hh"
+#include "eval-cache.hh"
+#include "eval-inline.hh"
+#include "attr-path.hh"
+#include "store-api.hh"
+#include "log-store.hh"
+#include "common-eval-args.hh"
+#include "get-drvs.hh"
+#include "derivations.hh"
+#include "globals.hh"
+#include "command.hh"
+#include "finally.hh"
+#include "markdown.hh"
+#include "local-fs-store.hh"
+#include "progress-bar.hh"
+
+#if HAVE_BOEHMGC
+#define GC_INCLUDE_NEW
+#include <gc/gc_cpp.h>
+#endif
+
+namespace nix {
+
+struct NixRepl
+ #if HAVE_BOEHMGC
+ : gc
+ #endif
+{
+ std::string curDir;
+ ref<EvalState> state;
+ Bindings * autoArgs;
+
+ size_t debugTraceIndex;
+
+ Strings loadedFiles;
+ typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
+ std::function<AnnotatedValues()> getValues;
+
+ const static int envSize = 32768;
+ std::shared_ptr<StaticEnv> staticEnv;
+ Env * env;
+ int displ;
+ StringSet varNames;
+
+ const Path historyFile;
+
+ NixRepl(const Strings & searchPath, nix::ref<Store> store,ref<EvalState> state,
+ std::function<AnnotatedValues()> getValues);
+ ~NixRepl();
+ void mainLoop();
+ StringSet completePrefix(const std::string & prefix);
+ bool getLine(std::string & input, const std::string & prompt);
+ StorePath getDerivationPath(Value & v);
+ bool processLine(std::string line);
+
+ void loadFile(const Path & path);
+ void loadFlake(const std::string & flakeRef);
+ void initEnv();
+ void loadFiles();
+ void reloadFiles();
+ void addAttrsToScope(Value & attrs);
+ void addVarToScope(const Symbol name, Value & v);
+ Expr * parseString(std::string s);
+ void evalString(std::string s, Value & v);
+ void loadDebugTraceEnv(DebugTrace & dt);
+
+ typedef std::set<Value *> ValuesSeen;
+ std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
+ std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
+};
+
+
+std::string removeWhitespace(std::string s)
+{
+ s = chomp(s);
+ size_t n = s.find_first_not_of(" \n\r\t");
+ if (n != std::string::npos) s = std::string(s, n);
+ return s;
+}
+
+
+NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
+ std::function<NixRepl::AnnotatedValues()> getValues)
+ : state(state)
+ , debugTraceIndex(0)
+ , getValues(getValues)
+ , staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
+ , historyFile(getDataDir() + "/nix/repl-history")
+{
+ curDir = absPath(".");
+}
+
+
+NixRepl::~NixRepl()
+{
+ write_history(historyFile.c_str());
+}
+
+void runNix(Path program, const Strings & args,
+ const std::optional<std::string> & input = {})
+{
+ auto subprocessEnv = getEnv();
+ subprocessEnv["NIX_CONFIG"] = globalConfig.toKeyValue();
+
+ runProgram2(RunOptions {
+ .program = settings.nixBinDir+ "/" + program,
+ .args = args,
+ .environment = subprocessEnv,
+ .input = input,
+ });
+
+ return;
+}
+
+static NixRepl * curRepl; // ugly
+
+static char * completionCallback(char * s, int *match) {
+ auto possible = curRepl->completePrefix(s);
+ if (possible.size() == 1) {
+ *match = 1;
+ auto *res = strdup(possible.begin()->c_str() + strlen(s));
+ if (!res) throw Error("allocation failure");
+ return res;
+ } else if (possible.size() > 1) {
+ auto checkAllHaveSameAt = [&](size_t pos) {
+ auto &first = *possible.begin();
+ for (auto &p : possible) {
+ if (p.size() <= pos || p[pos] != first[pos])
+ return false;
+ }
+ return true;
+ };
+ size_t start = strlen(s);
+ size_t len = 0;
+ while (checkAllHaveSameAt(start + len)) ++len;
+ if (len > 0) {
+ *match = 1;
+ auto *res = strdup(std::string(*possible.begin(), start, len).c_str());
+ if (!res) throw Error("allocation failure");
+ return res;
+ }
+ }
+
+ *match = 0;
+ return nullptr;
+}
+
+static int listPossibleCallback(char *s, char ***avp) {
+ auto possible = curRepl->completePrefix(s);
+
+ if (possible.size() > (INT_MAX / sizeof(char*)))
+ throw Error("too many completions");
+
+ int ac = 0;
+ char **vp = nullptr;
+
+ auto check = [&](auto *p) {
+ if (!p) {
+ if (vp) {
+ while (--ac >= 0)
+ free(vp[ac]);
+ free(vp);
+ }
+ throw Error("allocation failure");
+ }
+ return p;
+ };
+
+ vp = check((char **)malloc(possible.size() * sizeof(char*)));
+
+ for (auto & p : possible)
+ vp[ac++] = check(strdup(p.c_str()));
+
+ *avp = vp;
+
+ return ac;
+}
+
+namespace {
+ // Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
+ volatile sig_atomic_t g_signal_received = 0;
+
+ void sigintHandler(int signo) {
+ g_signal_received = signo;
+ }
+}
+
+static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positions, const DebugTrace & dt)
+{
+ if (dt.isError)
+ out << ANSI_RED "error: " << ANSI_NORMAL;
+ out << dt.hint.str() << "\n";
+
+ // prefer direct pos, but if noPos then try the expr.
+ auto pos = dt.pos
+ ? dt.pos
+ : static_cast<std::shared_ptr<AbstractPos>>(positions[dt.expr.getPos() ? dt.expr.getPos() : noPos]);
+
+ if (pos) {
+ out << pos;
+ if (auto loc = pos->getCodeLines()) {
+ out << "\n";
+ printCodeLines(out, "", *pos, *loc);
+ out << "\n";
+ }
+ }
+
+ return out;
+}
+
+void NixRepl::mainLoop()
+{
+ std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
+ notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
+
+ loadFiles();
+
+ // Allow nix-repl specific settings in .inputrc
+ rl_readline_name = "nix-repl";
+ try {
+ createDirs(dirOf(historyFile));
+ } catch (SysError & e) {
+ logWarning(e.info());
+ }
+#ifndef READLINE
+ el_hist_size = 1000;
+#endif
+ read_history(historyFile.c_str());
+ curRepl = this;
+#ifndef READLINE
+ rl_set_complete_func(completionCallback);
+ rl_set_list_possib_func(listPossibleCallback);
+#endif
+
+ /* Stop the progress bar because it interferes with the display of
+ the repl. */
+ stopProgressBar();
+
+ std::string input;
+
+ while (true) {
+ // When continuing input from previous lines, don't print a prompt, just align to the same
+ // number of chars as the prompt.
+ if (!getLine(input, input.empty() ? "nix-repl> " : " ")) {
+ // ctrl-D should exit the debugger.
+ state->debugStop = false;
+ state->debugQuit = true;
+ logger->cout("");
+ break;
+ }
+ try {
+ if (!removeWhitespace(input).empty() && !processLine(input)) return;
+ } catch (ParseError & e) {
+ if (e.msg().find("unexpected end of file") != std::string::npos) {
+ // For parse errors on incomplete input, we continue waiting for the next line of
+ // input without clearing the input so far.
+ continue;
+ } else {
+ printMsg(lvlError, e.msg());
+ }
+ } catch (EvalError & e) {
+ // in debugger mode, an EvalError should trigger another repl session.
+ // when that session returns the exception will land here. No need to show it again;
+ // show the error for this repl session instead.
+ if (state->debugRepl && !state->debugTraces.empty())
+ showDebugTrace(std::cout, state->positions, state->debugTraces.front());
+ else
+ printMsg(lvlError, e.msg());
+ } catch (Error & e) {
+ printMsg(lvlError, e.msg());
+ } catch (Interrupted & e) {
+ printMsg(lvlError, e.msg());
+ }
+
+ // We handled the current input fully, so we should clear it
+ // and read brand new input.
+ input.clear();
+ std::cout << std::endl;
+ }
+}
+
+
+bool NixRepl::getLine(std::string & input, const std::string & prompt)
+{
+ struct sigaction act, old;
+ sigset_t savedSignalMask, set;
+
+ auto setupSignals = [&]() {
+ act.sa_handler = sigintHandler;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGINT, &act, &old))
+ throw SysError("installing handler for SIGINT");
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGINT);
+ if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
+ throw SysError("unblocking SIGINT");
+ };
+ auto restoreSignals = [&]() {
+ if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
+ throw SysError("restoring signals");
+
+ if (sigaction(SIGINT, &old, 0))
+ throw SysError("restoring handler for SIGINT");
+ };
+
+ setupSignals();
+ Finally resetTerminal([&]() { rl_deprep_terminal(); });
+ char * s = readline(prompt.c_str());
+ Finally doFree([&]() { free(s); });
+ restoreSignals();
+
+ if (g_signal_received) {
+ g_signal_received = 0;
+ input.clear();
+ return true;
+ }
+
+ if (!s)
+ return false;
+ input += s;
+ input += '\n';
+ return true;
+}
+
+
+StringSet NixRepl::completePrefix(const std::string & prefix)
+{
+ StringSet completions;
+
+ size_t start = prefix.find_last_of(" \n\r\t(){}[]");
+ std::string prev, cur;
+ if (start == std::string::npos) {
+ prev = "";
+ cur = prefix;
+ } else {
+ prev = std::string(prefix, 0, start + 1);
+ cur = std::string(prefix, start + 1);
+ }
+
+ size_t slash, dot;
+
+ if ((slash = cur.rfind('/')) != std::string::npos) {
+ try {
+ auto dir = std::string(cur, 0, slash);
+ auto prefix2 = std::string(cur, slash + 1);
+ for (auto & entry : readDirectory(dir == "" ? "/" : dir)) {
+ if (entry.name[0] != '.' && hasPrefix(entry.name, prefix2))
+ completions.insert(prev + dir + "/" + entry.name);
+ }
+ } catch (Error &) {
+ }
+ } else if ((dot = cur.rfind('.')) == std::string::npos) {
+ /* This is a variable name; look it up in the current scope. */
+ StringSet::iterator i = varNames.lower_bound(cur);
+ while (i != varNames.end()) {
+ if (i->substr(0, cur.size()) != cur) break;
+ completions.insert(prev + *i);
+ i++;
+ }
+ } else {
+ /* Temporarily disable the debugger, to avoid re-entering readline. */
+ auto debug_repl = state->debugRepl;
+ state->debugRepl = nullptr;
+ Finally restoreDebug([&]() { state->debugRepl = debug_repl; });
+ try {
+ /* This is an expression that should evaluate to an
+ attribute set. Evaluate it to get the names of the
+ attributes. */
+ auto expr = cur.substr(0, dot);
+ auto cur2 = cur.substr(dot + 1);
+
+ Expr * e = parseString(expr);
+ Value v;
+ e->eval(*state, *env, v);
+ state->forceAttrs(v, noPos, "nevermind, it is ignored anyway");
+
+ for (auto & i : *v.attrs) {
+ std::string_view name = state->symbols[i.name];
+ if (name.substr(0, cur2.size()) != cur2) continue;
+ completions.insert(concatStrings(prev, expr, ".", name));
+ }
+
+ } catch (ParseError & e) {
+ // Quietly ignore parse errors.
+ } catch (EvalError & e) {
+ // Quietly ignore evaluation errors.
+ } catch (UndefinedVarError & e) {
+ // Quietly ignore undefined variable errors.
+ } catch (BadURL & e) {
+ // Quietly ignore BadURL flake-related errors.
+ }
+ }
+
+ return completions;
+}
+
+
+static bool isVarName(std::string_view s)
+{
+ if (s.size() == 0) return false;
+ char c = s[0];
+ if ((c >= '0' && c <= '9') || c == '-' || c == '\'') return false;
+ for (auto & i : s)
+ if (!((i >= 'a' && i <= 'z') ||
+ (i >= 'A' && i <= 'Z') ||
+ (i >= '0' && i <= '9') ||
+ i == '_' || i == '-' || i == '\''))
+ return false;
+ return true;
+}
+
+
+StorePath NixRepl::getDerivationPath(Value & v) {
+ auto drvInfo = getDerivation(*state, v, false);
+ if (!drvInfo)
+ throw Error("expression does not evaluate to a derivation, so I can't build it");
+ auto drvPath = drvInfo->queryDrvPath();
+ if (!drvPath)
+ throw Error("expression did not evaluate to a valid derivation (no 'drvPath' attribute)");
+ if (!state->store->isValidPath(*drvPath))
+ throw Error("expression evaluated to invalid derivation '%s'", state->store->printStorePath(*drvPath));
+ return *drvPath;
+}
+
+void NixRepl::loadDebugTraceEnv(DebugTrace & dt)
+{
+ initEnv();
+
+ auto se = state->getStaticEnv(dt.expr);
+ if (se) {
+ auto vm = mapStaticEnvBindings(state->symbols, *se.get(), dt.env);
+
+ // add staticenv vars.
+ for (auto & [name, value] : *(vm.get()))
+ addVarToScope(state->symbols.create(name), *value);
+ }
+}
+
+bool NixRepl::processLine(std::string line)
+{
+ line = trim(line);
+ if (line == "") return true;
+
+ _isInterrupted = false;
+
+ std::string command, arg;
+
+ if (line[0] == ':') {
+ size_t p = line.find_first_of(" \n\r\t");
+ command = line.substr(0, p);
+ if (p != std::string::npos) arg = removeWhitespace(line.substr(p));
+ } else {
+ arg = line;
+ }
+
+ if (command == ":?" || command == ":help") {
+ // FIXME: convert to Markdown, include in the 'nix repl' manpage.
+ std::cout
+ << "The following commands are available:\n"
+ << "\n"
+ << " <expr> Evaluate and print expression\n"
+ << " <x> = <expr> Bind expression to variable\n"
+ << " :a <expr> Add attributes from resulting set to scope\n"
+ << " :b <expr> Build a derivation\n"
+ << " :bl <expr> Build a derivation, creating GC roots in the working directory\n"
+ << " :e <expr> Open package or function in $EDITOR\n"
+ << " :i <expr> Build derivation, then install result into current profile\n"
+ << " :l <path> Load Nix expression and add it to scope\n"
+ << " :lf <ref> Load Nix flake and add it to scope\n"
+ << " :p <expr> Evaluate and print expression recursively\n"
+ << " :q Exit nix-repl\n"
+ << " :r Reload all files\n"
+ << " :sh <expr> Build dependencies of derivation, then start nix-shell\n"
+ << " :t <expr> Describe result of evaluation\n"
+ << " :u <expr> Build derivation, then start nix-shell\n"
+ << " :doc <expr> Show documentation of a builtin function\n"
+ << " :log <expr> Show logs for a derivation\n"
+ << " :te [bool] Enable, disable or toggle showing traces for errors\n"
+ ;
+ if (state->debugRepl) {
+ std::cout
+ << "\n"
+ << " Debug mode commands\n"
+ << " :env Show env stack\n"
+ << " :bt Show trace stack\n"
+ << " :st Show current trace\n"
+ << " :st <idx> Change to another trace in the stack\n"
+ << " :c Go until end of program, exception, or builtins.break\n"
+ << " :s Go one step\n"
+ ;
+ }
+
+ }
+
+ else if (state->debugRepl && (command == ":bt" || command == ":backtrace")) {
+ for (const auto & [idx, i] : enumerate(state->debugTraces)) {
+ std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
+ showDebugTrace(std::cout, state->positions, i);
+ }
+ }
+
+ else if (state->debugRepl && (command == ":env")) {
+ for (const auto & [idx, i] : enumerate(state->debugTraces)) {
+ if (idx == debugTraceIndex) {
+ printEnvBindings(*state, i.expr, i.env);
+ break;
+ }
+ }
+ }
+
+ else if (state->debugRepl && (command == ":st")) {
+ try {
+ // change the DebugTrace index.
+ debugTraceIndex = stoi(arg);
+ } catch (...) { }
+
+ for (const auto & [idx, i] : enumerate(state->debugTraces)) {
+ if (idx == debugTraceIndex) {
+ std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
+ showDebugTrace(std::cout, state->positions, i);
+ std::cout << std::endl;
+ printEnvBindings(*state, i.expr, i.env);
+ loadDebugTraceEnv(i);
+ break;
+ }
+ }
+ }
+
+ else if (state->debugRepl && (command == ":s" || command == ":step")) {
+ // set flag to stop at next DebugTrace; exit repl.
+ state->debugStop = true;
+ return false;
+ }
+
+ else if (state->debugRepl && (command == ":c" || command == ":continue")) {
+ // set flag to run to next breakpoint or end of program; exit repl.
+ state->debugStop = false;
+ return false;
+ }
+
+ else if (command == ":a" || command == ":add") {
+ Value v;
+ evalString(arg, v);
+ addAttrsToScope(v);
+ }
+
+ else if (command == ":l" || command == ":load") {
+ state->resetFileCache();
+ loadFile(arg);
+ }
+
+ else if (command == ":lf" || command == ":load-flake") {
+ loadFlake(arg);
+ }
+
+ else if (command == ":r" || command == ":reload") {
+ state->resetFileCache();
+ reloadFiles();
+ }
+
+ else if (command == ":e" || command == ":edit") {
+ Value v;
+ evalString(arg, v);
+
+ const auto [path, line] = [&] () -> std::pair<Path, uint32_t> {
+ if (v.type() == nPath || v.type() == nString) {
+ PathSet context;
+ auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
+ return {path, 0};
+ } else if (v.isLambda()) {
+ auto pos = state->positions[v.lambda.fun->pos];
+ if (auto path = std::get_if<Path>(&pos.origin))
+ return {*path, pos.line};
+ else
+ throw Error("'%s' cannot be shown in an editor", pos);
+ } else {
+ // assume it's a derivation
+ return findPackageFilename(*state, v, arg);
+ }
+ }();
+
+ // Open in EDITOR
+ auto args = editorFor(path, line);
+ auto editor = args.front();
+ args.pop_front();
+
+ // runProgram redirects stdout to a StringSink,
+ // using runProgram2 to allow editors to display their UI
+ runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args });
+
+ // Reload right after exiting the editor
+ state->resetFileCache();
+ reloadFiles();
+ }
+
+ else if (command == ":t") {
+ Value v;
+ evalString(arg, v);
+ logger->cout(showType(v));
+ }
+
+ else if (command == ":u") {
+ Value v, f, result;
+ evalString(arg, v);
+ evalString("drv: (import <nixpkgs> {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f);
+ state->callFunction(f, v, result, PosIdx());
+
+ StorePath drvPath = getDerivationPath(result);
+ runNix("nix-shell", {state->store->printStorePath(drvPath)});
+ }
+
+ else if (command == ":b" || command == ":bl" || command == ":i" || command == ":sh" || command == ":log") {
+ Value v;
+ evalString(arg, v);
+ StorePath drvPath = getDerivationPath(v);
+ Path drvPathRaw = state->store->printStorePath(drvPath);
+
+ if (command == ":b" || command == ":bl") {
+ state->store->buildPaths({DerivedPath::Built{drvPath}});
+ auto drv = state->store->readDerivation(drvPath);
+ logger->cout("\nThis derivation produced the following outputs:");
+ for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath)) {
+ auto localStore = state->store.dynamic_pointer_cast<LocalFSStore>();
+ if (localStore && command == ":bl") {
+ std::string symlink = "repl-result-" + outputName;
+ localStore->addPermRoot(outputPath, absPath(symlink));
+ logger->cout(" ./%s -> %s", symlink, state->store->printStorePath(outputPath));
+ } else {
+ logger->cout(" %s -> %s", outputName, state->store->printStorePath(outputPath));
+ }
+ }
+ } else if (command == ":i") {
+ runNix("nix-env", {"-i", drvPathRaw});
+ } else if (command == ":log") {
+ settings.readOnlyMode = true;
+ Finally roModeReset([&]() {
+ settings.readOnlyMode = false;
+ });
+ auto subs = getDefaultSubstituters();
+
+ subs.push_front(state->store);
+
+ bool foundLog = false;
+ RunPager pager;
+ for (auto & sub : subs) {
+ auto * logSubP = dynamic_cast<LogStore *>(&*sub);
+ if (!logSubP) {
+ printInfo("Skipped '%s' which does not support retrieving build logs", sub->getUri());
+ continue;
+ }
+ auto & logSub = *logSubP;
+
+ auto log = logSub.getBuildLog(drvPath);
+ if (log) {
+ printInfo("got build log for '%s' from '%s'", drvPathRaw, logSub.getUri());
+ logger->writeToStdout(*log);
+ foundLog = true;
+ break;
+ }
+ }
+ if (!foundLog) throw Error("build log of '%s' is not available", drvPathRaw);
+ } else {
+ runNix("nix-shell", {drvPathRaw});
+ }
+ }
+
+ else if (command == ":p" || command == ":print") {
+ Value v;
+ evalString(arg, v);
+ printValue(std::cout, v, 1000000000) << std::endl;
+ }
+
+ else if (command == ":q" || command == ":quit") {
+ state->debugStop = false;
+ state->debugQuit = true;
+ return false;
+ }
+
+ else if (command == ":doc") {
+ Value v;
+ evalString(arg, v);
+ if (auto doc = state->getDoc(v)) {
+ std::string markdown;
+
+ if (!doc->args.empty() && doc->name) {
+ auto args = doc->args;
+ for (auto & arg : args)
+ arg = "*" + arg + "*";
+
+ markdown +=
+ "**Synopsis:** `builtins." + (std::string) (*doc->name) + "` "
+ + concatStringsSep(" ", args) + "\n\n";
+ }
+
+ markdown += stripIndentation(doc->doc);
+
+ logger->cout(trim(renderMarkdownToTerminal(markdown)));
+ } else
+ throw Error("value does not have documentation");
+ }
+
+ else if (command == ":te" || command == ":trace-enable") {
+ if (arg == "false" || (arg == "" && loggerSettings.showTrace)) {
+ std::cout << "not showing error traces\n";
+ loggerSettings.showTrace = false;
+ } else if (arg == "true" || (arg == "" && !loggerSettings.showTrace)) {
+ std::cout << "showing error traces\n";
+ loggerSettings.showTrace = true;
+ } else {
+ throw Error("unexpected argument '%s' to %s", arg, command);
+ };
+ }
+
+ else if (command != "")
+ throw Error("unknown command '%1%'", command);
+
+ else {
+ size_t p = line.find('=');
+ std::string name;
+ if (p != std::string::npos &&
+ p < line.size() &&
+ line[p + 1] != '=' &&
+ isVarName(name = removeWhitespace(line.substr(0, p))))
+ {
+ Expr * e = parseString(line.substr(p + 1));
+ Value & v(*state->allocValue());
+ v.mkThunk(env, e);
+ addVarToScope(state->symbols.create(name), v);
+ } else {
+ Value v;
+ evalString(line, v);
+ printValue(std::cout, v, 1) << std::endl;
+ }
+ }
+
+ return true;
+}
+
+void NixRepl::loadFile(const Path & path)
+{
+ loadedFiles.remove(path);
+ loadedFiles.push_back(path);
+ Value v, v2;
+ state->evalFile(lookupFileArg(*state, path), v);
+ state->autoCallFunction(*autoArgs, v, v2);
+ addAttrsToScope(v2);
+}
+
+void NixRepl::loadFlake(const std::string & flakeRefS)
+{
+ if (flakeRefS.empty())
+ throw Error("cannot use ':load-flake' without a path specified. (Use '.' for the current working directory.)");
+
+ auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true);
+ if (evalSettings.pureEval && !flakeRef.input.isLocked())
+ throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
+
+ Value v;
+
+ flake::callFlake(*state,
+ flake::lockFlake(*state, flakeRef,
+ flake::LockFlags {
+ .updateLockFile = false,
+ .useRegistries = !evalSettings.pureEval,
+ .allowUnlocked = !evalSettings.pureEval,
+ }),
+ v);
+ addAttrsToScope(v);
+}
+
+
+void NixRepl::initEnv()
+{
+ env = &state->allocEnv(envSize);
+ env->up = &state->baseEnv;
+ displ = 0;
+ staticEnv->vars.clear();
+
+ varNames.clear();
+ for (auto & i : state->staticBaseEnv->vars)
+ varNames.emplace(state->symbols[i.first]);
+}
+
+
+void NixRepl::reloadFiles()
+{
+ initEnv();
+
+ loadFiles();
+}
+
+
+void NixRepl::loadFiles()
+{
+ Strings old = loadedFiles;
+ loadedFiles.clear();
+
+ for (auto & i : old) {
+ notice("Loading '%1%'...", i);
+ loadFile(i);
+ }
+
+ for (auto & [i, what] : getValues()) {
+ notice("Loading installable '%1%'...", what);
+ addAttrsToScope(*i);
+ }
+}
+
+
+void NixRepl::addAttrsToScope(Value & attrs)
+{
+ state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope");
+ if (displ + attrs.attrs->size() >= envSize)
+ throw Error("environment full; cannot add more variables");
+
+ for (auto & i : *attrs.attrs) {
+ staticEnv->vars.emplace_back(i.name, displ);
+ env->values[displ++] = i.value;
+ varNames.emplace(state->symbols[i.name]);
+ }
+ staticEnv->sort();
+ staticEnv->deduplicate();
+ notice("Added %1% variables.", attrs.attrs->size());
+}
+
+
+void NixRepl::addVarToScope(const Symbol name, Value & v)
+{
+ if (displ >= envSize)
+ throw Error("environment full; cannot add more variables");
+ if (auto oldVar = staticEnv->find(name); oldVar != staticEnv->vars.end())
+ staticEnv->vars.erase(oldVar);
+ staticEnv->vars.emplace_back(name, displ);
+ staticEnv->sort();
+ env->values[displ++] = &v;
+ varNames.emplace(state->symbols[name]);
+}
+
+
+Expr * NixRepl::parseString(std::string s)
+{
+ Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv);
+ return e;
+}
+
+
+void NixRepl::evalString(std::string s, Value & v)
+{
+ Expr * e = parseString(s);
+ e->eval(*state, *env, v);
+ state->forceValue(v, [&]() { return v.determinePos(noPos); });
+}
+
+
+std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth)
+{
+ ValuesSeen seen;
+ return printValue(str, v, maxDepth, seen);
+}
+
+
+std::ostream & printStringValue(std::ostream & str, const char * string) {
+ str << "\"";
+ for (const char * i = string; *i; i++)
+ if (*i == '\"' || *i == '\\') str << "\\" << *i;
+ else if (*i == '\n') str << "\\n";
+ else if (*i == '\r') str << "\\r";
+ else if (*i == '\t') str << "\\t";
+ else str << *i;
+ str << "\"";
+ return str;
+}
+
+
+// FIXME: lot of cut&paste from Nix's eval.cc.
+std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen)
+{
+ str.flush();
+ checkInterrupt();
+
+ state->forceValue(v, [&]() { return v.determinePos(noPos); });
+
+ switch (v.type()) {
+
+ case nInt:
+ str << ANSI_CYAN << v.integer << ANSI_NORMAL;
+ break;
+
+ case nBool:
+ str << ANSI_CYAN << (v.boolean ? "true" : "false") << ANSI_NORMAL;
+ break;
+
+ case nString:
+ str << ANSI_WARNING;
+ printStringValue(str, v.string.s);
+ str << ANSI_NORMAL;
+ break;
+
+ case nPath:
+ str << ANSI_GREEN << v.path << ANSI_NORMAL; // !!! escaping?
+ break;
+
+ case nNull:
+ str << ANSI_CYAN "null" ANSI_NORMAL;
+ break;
+
+ case nAttrs: {
+ seen.insert(&v);
+
+ bool isDrv = state->isDerivation(v);
+
+ if (isDrv) {
+ str << "«derivation ";
+ Bindings::iterator i = v.attrs->find(state->sDrvPath);
+ PathSet context;
+ if (i != v.attrs->end())
+ str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
+ else
+ str << "???";
+ str << "»";
+ }
+
+ else if (maxDepth > 0) {
+ str << "{ ";
+
+ typedef std::map<std::string, Value *> Sorted;
+ Sorted sorted;
+ for (auto & i : *v.attrs)
+ sorted.emplace(state->symbols[i.name], i.value);
+
+ for (auto & i : sorted) {
+ if (isVarName(i.first))
+ str << i.first;
+ else
+ printStringValue(str, i.first.c_str());
+ str << " = ";
+ if (seen.count(i.second))
+ str << "«repeated»";
+ else
+ try {
+ printValue(str, *i.second, maxDepth - 1, seen);
+ } catch (AssertionError & e) {
+ str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
+ }
+ str << "; ";
+ }
+
+ str << "}";
+ } else
+ str << "{ ... }";
+
+ break;
+ }
+
+ case nList:
+ seen.insert(&v);
+
+ str << "[ ";
+ if (maxDepth > 0)
+ for (auto elem : v.listItems()) {
+ if (seen.count(elem))
+ str << "«repeated»";
+ else
+ try {
+ printValue(str, *elem, maxDepth - 1, seen);
+ } catch (AssertionError & e) {
+ str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
+ }
+ str << " ";
+ }
+ else
+ str << "... ";
+ str << "]";
+ break;
+
+ case nFunction:
+ if (v.isLambda()) {
+ std::ostringstream s;
+ s << state->positions[v.lambda.fun->pos];
+ str << ANSI_BLUE "«lambda @ " << filterANSIEscapes(s.str()) << "»" ANSI_NORMAL;
+ } else if (v.isPrimOp()) {
+ str << ANSI_MAGENTA "«primop»" ANSI_NORMAL;
+ } else if (v.isPrimOpApp()) {
+ str << ANSI_BLUE "«primop-app»" ANSI_NORMAL;
+ } else {
+ abort();
+ }
+ break;
+
+ case nFloat:
+ str << v.fpoint;
+ break;
+
+ default:
+ str << ANSI_RED "«unknown»" ANSI_NORMAL;
+ break;
+ }
+
+ return str;
+}
+
+void runRepl(
+ ref<EvalState>evalState,
+ const ValMap & extraEnv)
+{
+ auto getValues = [&]()->NixRepl::AnnotatedValues{
+ NixRepl::AnnotatedValues values;
+ return values;
+ };
+ const Strings & searchPath = {};
+ auto repl = std::make_unique<NixRepl>(
+ searchPath,
+ openStore(),
+ evalState,
+ getValues
+ );
+
+ repl->initEnv();
+
+ // add 'extra' vars.
+ for (auto & [name, value] : extraEnv)
+ repl->addVarToScope(repl->state->symbols.create(name), *value);
+
+ repl->mainLoop();
+}
+
+struct CmdRepl : InstallablesCommand
+{
+ CmdRepl() {
+ evalSettings.pureEval = false;
+ }
+
+ void prepare() override
+ {
+ if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
+ warn("future versions of Nix will require using `--file` to load a file");
+ if (this->_installables.size() > 1)
+ warn("more than one input file is not currently supported");
+ auto filePath = this->_installables[0].data();
+ file = std::optional(filePath);
+ _installables.front() = _installables.back();
+ _installables.pop_back();
+ }
+ installables = InstallablesCommand::load();
+ }
+
+ std::vector<std::string> files;
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {""};
+ }
+
+ bool useDefaultInstallables() override
+ {
+ return file.has_value() or expr.has_value();
+ }
+
+ bool forceImpureByDefault() override
+ {
+ return true;
+ }
+
+ std::string description() override
+ {
+ return "start an interactive environment for evaluating Nix expressions";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "repl.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto state = getEvalState();
+ auto getValues = [&]()->NixRepl::AnnotatedValues{
+ auto installables = load();
+ NixRepl::AnnotatedValues values;
+ for (auto & installable: installables){
+ auto what = installable->what();
+ if (file){
+ auto [val, pos] = installable->toValue(*state);
+ auto what = installable->what();
+ state->forceValue(*val, pos);
+ auto autoArgs = getAutoArgs(*state);
+ auto valPost = state->allocValue();
+ state->autoCallFunction(*autoArgs, *val, *valPost);
+ state->forceValue(*valPost, pos);
+ values.push_back( {valPost, what });
+ } else {
+ auto [val, pos] = installable->toValue(*state);
+ values.push_back( {val, what} );
+ }
+ }
+ return values;
+ };
+ auto repl = std::make_unique<NixRepl>(
+ searchPath,
+ openStore(),
+ state,
+ getValues
+ );
+ repl->autoArgs = getAutoArgs(*repl->state);
+ repl->initEnv();
+ repl->mainLoop();
+ }
+};
+
+static auto rCmdRepl = registerCommand<CmdRepl>("repl");
+
+}