lix/src/libcmd/repl.cc

1069 lines
32 KiB
C++
Raw Normal View History

#include <iostream>
#include <cstdlib>
2018-10-29 13:44:58 +00:00
#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" {
2018-10-29 13:44:58 +00:00
#include <editline.h>
}
#endif
2018-10-29 13:44:58 +00:00
#include "repl.hh"
#include "ansicolor.hh"
#include "shared.hh"
#include "eval.hh"
#include "eval-cache.hh"
#include "eval-inline.hh"
#include "eval-settings.hh"
#include "attr-path.hh"
#include "store-api.hh"
#include "log-store.hh"
#include "common-eval-args.hh"
2013-09-06 12:58:53 +00:00
#include "get-drvs.hh"
#include "derivations.hh"
#include "globals.hh"
#include "flake/flake.hh"
#include "flake/lockfile.hh"
#include "editor-for.hh"
#include "finally.hh"
2020-08-24 16:10:33 +00:00
#include "markdown.hh"
#include "local-fs-store.hh"
#include "print.hh"
#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
#include <gc/gc_cpp.h>
#endif
2017-04-25 16:48:40 +00:00
namespace nix {
struct NixRepl
: AbstractNixRepl
#if HAVE_BOEHMGC
, gc
#endif
{
size_t debugTraceIndex;
2021-12-20 19:32:21 +00:00
2013-09-09 14:02:46 +00:00
Strings loadedFiles;
std::function<AnnotatedValues()> getValues;
2013-09-09 14:02:46 +00:00
2013-09-09 15:06:14 +00:00
const static int envSize = 32768;
2021-09-14 16:49:22 +00:00
std::shared_ptr<StaticEnv> staticEnv;
Env * env;
int displ;
2013-09-06 17:51:59 +00:00
StringSet varNames;
const Path historyFile;
NixRepl(const SearchPath & searchPath, nix::ref<Store> store,ref<EvalState> state,
std::function<AnnotatedValues()> getValues);
virtual ~NixRepl();
void mainLoop() override;
void initEnv() override;
StringSet completePrefix(const std::string & prefix);
2022-05-25 10:32:22 +00:00
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);
2021-12-20 19:32:21 +00:00
void loadFiles();
2013-09-09 14:02:46 +00:00
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);
2022-02-21 15:28:23 +00:00
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 SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<NixRepl::AnnotatedValues()> getValues)
: AbstractNixRepl(state)
2022-03-28 18:09:21 +00:00
, debugTraceIndex(0)
, getValues(getValues)
2022-05-25 16:21:20 +00:00
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
, historyFile(getDataDir() + "/nix/repl-history")
{
}
2017-04-25 16:48:40 +00:00
NixRepl::~NixRepl()
{
2018-10-29 13:44:58 +00:00
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
2018-10-29 13:44:58 +00:00
static char * completionCallback(char * s, int *match) {
2020-06-24 19:10:41 +00:00
auto possible = curRepl->completePrefix(s);
if (possible.size() == 1) {
2020-06-24 19:14:49 +00:00
*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) {
2020-06-24 19:10:41 +00:00
*match = 1;
2020-06-24 19:14:49 +00:00
auto *res = strdup(std::string(*possible.begin(), start, len).c_str());
2020-06-24 19:10:41 +00:00
if (!res) throw Error("allocation failure");
return res;
2020-06-24 19:14:49 +00:00
}
2020-06-24 19:10:41 +00:00
}
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
*match = 0;
return nullptr;
2018-10-29 13:44:58 +00:00
}
static int listPossibleCallback(char *s, char ***avp) {
2020-06-24 19:10:41 +00:00
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;
};
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
vp = check((char **)malloc(possible.size() * sizeof(char*)));
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
for (auto & p : possible)
2020-06-24 19:14:49 +00:00
vp[ac++] = check(strdup(p.c_str()));
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
*avp = vp;
2018-10-29 13:44:58 +00:00
2020-06-24 19:10:41 +00:00
return ac;
2017-04-25 16:48:40 +00:00
}
namespace {
2020-06-24 19:10:41 +00:00
// Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
volatile sig_atomic_t g_signal_received = 0;
2020-06-24 19:10:41 +00:00
void sigintHandler(int signo) {
g_signal_received = signo;
}
}
2017-04-25 16:48:40 +00:00
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");
2021-12-20 19:32:21 +00:00
loadFiles();
2018-10-29 13:44:58 +00:00
// Allow nix-repl specific settings in .inputrc
rl_readline_name = "nix-repl";
try {
createDirs(dirOf(historyFile));
} catch (SysError & e) {
logWarning(e.info());
}
#ifndef READLINE
2018-10-29 13:44:58 +00:00
el_hist_size = 1000;
#endif
2018-10-29 13:44:58 +00:00
read_history(historyFile.c_str());
auto oldRepl = curRepl;
curRepl = this;
Finally restoreRepl([&] { curRepl = oldRepl; });
#ifndef READLINE
2018-10-29 13:44:58 +00:00
rl_set_complete_func(completionCallback);
rl_set_list_possib_func(listPossibleCallback);
#endif
std::string input;
while (true) {
// Hide the progress bar while waiting for user input, so that it won't interfere.
logger->pause();
2016-02-18 13:04:55 +00:00
// 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> " : " ")) {
2022-02-15 16:49:25 +00:00
// ctrl-D should exit the debugger.
2022-05-25 16:21:20 +00:00
state->debugStop = false;
state->debugQuit = true;
logger->cout("");
2013-09-09 13:02:56 +00:00
break;
2022-02-15 16:49:25 +00:00
}
logger->resume();
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 {
2020-06-24 19:14:49 +00:00
printMsg(lvlError, e.msg());
}
} catch (EvalError & e) {
2022-04-29 17:24:54 +00:00
// 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.
2022-05-25 16:21:20 +00:00
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)
2013-09-06 17:51:59 +00:00
{
struct sigaction act, old;
sigset_t savedSignalMask, set;
auto setupSignals = [&]() {
2020-06-24 19:10:41 +00:00
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 = [&]() {
2020-06-24 19:10:41 +00:00
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
throw SysError("restoring signals");
2020-06-24 19:10:41 +00:00
if (sigaction(SIGINT, &old, 0))
throw SysError("restoring handler for SIGINT");
};
setupSignals();
Finally resetTerminal([&]() { rl_deprep_terminal(); });
2018-10-29 13:44:58 +00:00
char * s = readline(prompt.c_str());
2017-11-28 00:30:05 +00:00
Finally doFree([&]() { free(s); });
restoreSignals();
if (g_signal_received) {
g_signal_received = 0;
input.clear();
return true;
}
2018-10-29 13:44:58 +00:00
if (!s)
2020-06-24 19:10:41 +00:00
return false;
input += s;
2018-04-11 09:42:17 +00:00
input += '\n';
return true;
2013-09-06 17:51:59 +00:00
}
StringSet NixRepl::completePrefix(const std::string & prefix)
2013-09-06 17:51:59 +00:00
{
StringSet completions;
2017-04-25 17:19:15 +00:00
size_t start = prefix.find_last_of(" \n\r\t(){}[]");
std::string prev, cur;
if (start == std::string::npos) {
prev = "";
cur = prefix;
2016-02-18 12:50:52 +00:00
} else {
prev = std::string(prefix, 0, start + 1);
cur = std::string(prefix, start + 1);
2013-09-06 17:51:59 +00:00
}
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, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
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.
}
2013-09-06 17:51:59 +00:00
}
return completions;
2013-09-06 17:51:59 +00:00
}
// FIXME: DRY and match or use the parser
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)
2022-03-28 21:28:59 +00:00
{
initEnv();
2022-03-28 21:28:59 +00:00
2022-05-25 16:21:20 +00:00
auto se = state->getStaticEnv(dt.expr);
if (se) {
2022-05-25 16:21:20 +00:00
auto vm = mapStaticEnvBindings(state->symbols, *se.get(), dt.env);
2022-03-28 21:28:59 +00:00
// add staticenv vars.
for (auto & [name, value] : *(vm.get()))
2022-05-25 16:21:20 +00:00
addVarToScope(state->symbols.create(name), *value);
2022-03-28 21:28:59 +00:00
}
}
bool NixRepl::processLine(std::string line)
{
line = trim(line);
2013-09-09 13:02:56 +00:00
if (line == "") return true;
_isInterrupted = false;
std::string command, arg;
2013-09-09 13:02:56 +00:00
if (line[0] == ':') {
2016-02-18 12:59:51 +00:00
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));
2013-09-09 13:02:56 +00:00
} else {
arg = line;
}
2013-09-09 14:02:46 +00:00
if (command == ":?" || command == ":help") {
2020-12-09 12:07:01 +00:00
// FIXME: convert to Markdown, include in the 'nix repl' manpage.
std::cout
2020-06-24 19:14:49 +00:00
<< "The following commands are available:\n"
<< "\n"
2023-01-23 10:50:44 +00:00
<< " <expr> Evaluate and print expression\n"
<< " <x> = <expr> Bind expression to variable\n"
<< " :a, :add <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\n"
<< " working directory\n"
<< " :e, :edit <expr> Open package or function in $EDITOR\n"
<< " :i <expr> Build derivation, then install result into\n"
<< " current profile\n"
<< " :l, :load <path> Load Nix expression and add it to scope\n"
<< " :lf, :load-flake <ref> Load Nix flake and add it to scope\n"
<< " :p, :print <expr> Evaluate and print expression recursively\n"
<< " :q, :quit Exit nix-repl\n"
<< " :r, :reload Reload all files\n"
<< " :sh <expr> Build dependencies of derivation, then start\n"
<< " 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, :trace-enable [bool] Enable, disable or toggle showing traces for\n"
<< " errors\n"
<< " :?, :help Brings up this help menu\n"
;
2022-05-25 16:21:20 +00:00
if (state->debugRepl) {
std::cout
<< "\n"
<< " Debug mode commands\n"
2023-01-23 10:50:44 +00:00
<< " :env Show env stack\n"
<< " :bt, :backtrace Show trace stack\n"
<< " :st Show current trace\n"
<< " :st <idx> Change to another trace in the stack\n"
<< " :c, :continue Go until end of program, exception, or builtins.break\n"
<< " :s, :step Go one step\n"
;
}
2021-12-20 19:32:21 +00:00
}
2022-05-25 16:21:20 +00:00
else if (state->debugRepl && (command == ":bt" || command == ":backtrace")) {
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
2022-05-06 03:23:03 +00:00
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
2022-05-25 16:21:20 +00:00
showDebugTrace(std::cout, state->positions, i);
}
2022-05-06 03:23:03 +00:00
}
2022-05-25 16:21:20 +00:00
else if (state->debugRepl && (command == ":env")) {
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
2022-05-06 03:23:03 +00:00
if (idx == debugTraceIndex) {
2022-05-25 16:21:20 +00:00
printEnvBindings(*state, i.expr, i.env);
2022-05-06 03:23:03 +00:00
break;
2022-03-28 18:09:21 +00:00
}
}
2022-05-06 03:23:03 +00:00
}
2022-05-25 16:21:20 +00:00
else if (state->debugRepl && (command == ":st")) {
2022-05-06 03:23:03 +00:00
try {
// change the DebugTrace index.
debugTraceIndex = stoi(arg);
} catch (...) { }
2022-05-25 16:21:20 +00:00
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
2022-05-06 03:23:03 +00:00
if (idx == debugTraceIndex) {
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
2022-05-25 16:21:20 +00:00
showDebugTrace(std::cout, state->positions, i);
2022-05-06 03:23:03 +00:00
std::cout << std::endl;
2022-05-25 16:21:20 +00:00
printEnvBindings(*state, i.expr, i.env);
2022-05-06 03:23:03 +00:00
loadDebugTraceEnv(i);
break;
}
2021-12-20 19:32:21 +00:00
}
2022-05-06 03:23:03 +00:00
}
2022-05-25 16:21:20 +00:00
else if (state->debugRepl && (command == ":s" || command == ":step")) {
2022-05-06 03:23:03 +00:00
// set flag to stop at next DebugTrace; exit repl.
2022-05-25 16:21:20 +00:00
state->debugStop = true;
2022-05-06 03:23:03 +00:00
return false;
}
2022-05-25 16:21:20 +00:00
else if (state->debugRepl && (command == ":c" || command == ":continue")) {
2022-05-06 03:23:03 +00:00
// set flag to run to next breakpoint or end of program; exit repl.
2022-05-25 16:21:20 +00:00
state->debugStop = false;
2022-05-06 03:23:03 +00:00
return false;
2013-09-09 11:22:33 +00:00
}
2013-09-09 14:02:46 +00:00
else if (command == ":a" || command == ":add") {
Value v;
2013-09-09 13:02:56 +00:00
evalString(arg, v);
addAttrsToScope(v);
}
2013-09-09 14:02:46 +00:00
else if (command == ":l" || command == ":load") {
state->resetFileCache();
2013-09-09 13:02:56 +00:00
loadFile(arg);
}
else if (command == ":lf" || command == ":load-flake") {
loadFlake(arg);
}
2013-09-09 14:02:46 +00:00
else if (command == ":r" || command == ":reload") {
state->resetFileCache();
2013-09-09 14:02:46 +00:00
reloadFiles();
}
else if (command == ":e" || command == ":edit") {
Value v;
evalString(arg, v);
const auto [path, line] = [&] () -> std::pair<SourcePath, uint32_t> {
if (v.type() == nPath || v.type() == nString) {
Use `std::set<StringContextElem>` not `PathSet` for string contexts Motivation `PathSet` is not correct because string contexts have other forms (`Built` and `DrvDeep`) that are not rendered as plain store paths. Instead of wrongly using `PathSet`, or "stringly typed" using `StringSet`, use `std::std<StringContextElem>`. ----- In support of this change, `NixStringContext` is now defined as `std::std<StringContextElem>` not `std:vector<StringContextElem>`. The old definition was just used by a `getContext` method which was only used by the eval cache. It can be deleted altogether since the types are now unified and the preexisting `copyContext` function already suffices. Summarizing the previous paragraph: Old: - `value/context.hh`: `NixStringContext = std::vector<StringContextElem>` - `value.hh`: `NixStringContext Value::getContext(...)` - `value.hh`: `copyContext(...)` New: - `value/context.hh`: `NixStringContext = std::set<StringContextElem>` - `value.hh`: `copyContext(...)` ---- The string representation of string context elements no longer contains the store dir. The diff of `src/libexpr/tests/value/context.cc` should make clear what the new representation is, so we recommend reviewing that file first. This was done for two reasons: Less API churn: `Value::mkString` and friends did not take a `Store` before. But if `NixStringContextElem::{parse, to_string}` *do* take a store (as they did before), then we cannot have the `Value` functions use them (in order to work with the fully-structured `NixStringContext`) without adding that argument. That would have been a lot of churn of threading the store, and this diff is already large enough, so the easier and less invasive thing to do was simply make the element `parse` and `to_string` functions not take the `Store` reference, and the easiest way to do that was to simply drop the store dir. Space usage: Dropping the `/nix/store/` (or similar) from the internal representation will safe space in the heap of the Nix programming being interpreted. If the heap contains many strings with non-trivial contexts, the saving could add up to something significant. ---- The eval cache version is bumped. The eval cache serialization uses `NixStringContextElem::{parse, to_string}`, and since those functions are changed per the above, that means the on-disk representation is also changed. This is simply done by changing the name of the used for the eval cache from `eval-cache-v4` to eval-cache-v5`. ---- To avoid some duplication `EvalCache::mkPathString` is added to abstract over the simple case of turning a store path to a string with just that string in the context. Context This PR picks up where #7543 left off. That one introduced the fully structured `NixStringContextElem` data type, but kept `PathSet context` as an awkward middle ground between internal `char[][]` interpreter heap string contexts and `NixStringContext` fully parsed string contexts. The infelicity of `PathSet context` was specifically called out during Nix team group review, but it was agreeing that fixing it could be left as future work. This is that future work. A possible follow-up step would be to get rid of the `char[][]` evaluator heap representation, too, but it is not yet clear how to do that. To use `NixStringContextElem` there we would need to get the STL containers to GC pointers in the GC build, and I am not sure how to do that. ---- PR #7543 effectively is writing the inverse of a `mkPathString`, `mkOutputString`, and one more such function for the `DrvDeep` case. I would like that PR to have property tests ensuring it is actually the inverse as expected. This PR sets things up nicely so that reworking that PR to be in that more elegant and better tested way is possible. Co-authored-by: Théophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com>
2023-01-29 01:31:10 +00:00
NixStringContext 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];
2023-04-06 13:25:06 +00:00
if (auto path = std::get_if<SourcePath>(&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);
2019-10-23 14:48:28 +00:00
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;
2013-09-09 13:02:56 +00:00
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") {
2013-09-06 12:58:53 +00:00
Value v;
2013-09-09 13:02:56 +00:00
evalString(arg, v);
StorePath drvPath = getDerivationPath(v);
Path drvPathRaw = state->store->printStorePath(drvPath);
if (command == ":b" || command == ":bl") {
state->store->buildPaths({
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath),
.outputs = OutputsSpec::All { },
},
});
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});
}
}
2013-09-09 14:02:46 +00:00
else if (command == ":p" || command == ":print") {
Value v;
2013-09-09 13:02:56 +00:00
evalString(arg, v);
printValue(std::cout, v, 1000000000) << std::endl;
}
2022-02-15 16:49:25 +00:00
else if (command == ":q" || command == ":quit") {
2022-05-25 16:21:20 +00:00
state->debugStop = false;
state->debugQuit = true;
2013-09-09 13:02:56 +00:00
return false;
2022-02-15 16:49:25 +00:00
}
2013-09-09 13:02:56 +00:00
else if (command == ":doc") {
Value v;
evalString(arg, v);
2020-08-25 11:31:11 +00:00
if (auto doc = state->getDoc(v)) {
std::string markdown;
if (!doc->args.empty() && doc->name) {
auto args = doc->args;
2020-08-24 16:10:33 +00:00
for (auto & arg : args)
arg = "*" + arg + "*";
2020-08-25 11:31:11 +00:00
markdown +=
"**Synopsis:** `builtins." + (std::string) (*doc->name) + "` "
+ concatStringsSep(" ", args) + "\n\n";
}
markdown += stripIndentation(doc->doc);
2020-08-24 16:10:33 +00:00
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);
};
}
2013-09-09 13:02:56 +00:00
else if (command != "")
throw Error("unknown command '%1%'", command);
2013-09-06 12:58:53 +00:00
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;
}
}
2013-09-09 13:02:56 +00:00
return true;
}
void NixRepl::loadFile(const Path & path)
{
2013-09-09 14:02:46 +00:00
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);
}
2013-09-09 15:06:14 +00:00
void NixRepl::initEnv()
{
env = &state->allocEnv(envSize);
env->up = &state->baseEnv;
2013-09-09 15:06:14 +00:00
displ = 0;
2021-09-14 16:49:22 +00:00
staticEnv->vars.clear();
2013-09-09 15:22:42 +00:00
varNames.clear();
2022-05-25 16:21:20 +00:00
for (auto & i : state->staticBaseEnv->vars)
varNames.emplace(state->symbols[i.first]);
2013-09-09 15:06:14 +00:00
}
2013-09-09 14:02:46 +00:00
void NixRepl::reloadFiles()
{
2013-09-09 15:06:14 +00:00
initEnv();
2021-12-20 19:32:21 +00:00
loadFiles();
}
void NixRepl::loadFiles()
{
2013-09-09 14:02:46 +00:00
Strings old = loadedFiles;
loadedFiles.clear();
for (auto & i : old) {
notice("Loading '%1%'...", i);
loadFile(i);
2013-09-09 14:02:46 +00:00
}
for (auto & [i, what] : getValues()) {
notice("Loading installable '%1%'...", what);
addAttrsToScope(*i);
2013-09-09 14:02:46 +00:00
}
}
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) {
2022-01-04 01:13:16 +00:00
staticEnv->vars.emplace_back(i.name, displ);
env->values[displ++] = i.value;
varNames.emplace(state->symbols[i.name]);
}
2022-01-04 01:13:16 +00:00
staticEnv->sort();
staticEnv->deduplicate();
notice("Added %1% variables.", attrs.attrs->size());
}
void NixRepl::addVarToScope(const Symbol name, Value & v)
{
2013-09-09 15:06:14 +00:00
if (displ >= envSize)
throw Error("environment full; cannot add more variables");
2021-12-20 19:32:21 +00:00
if (auto oldVar = staticEnv->find(name); oldVar != staticEnv->vars.end())
staticEnv->vars.erase(oldVar);
2021-11-25 15:53:59 +00:00
staticEnv->vars.emplace_back(name, displ);
staticEnv->sort();
env->values[displ++] = &v;
varNames.emplace(state->symbols[name]);
}
Expr * NixRepl::parseString(std::string s)
{
return state->parseExprFromString(std::move(s), state->rootPath(CanonPath::fromCwd()), staticEnv);
}
void NixRepl::evalString(std::string s, Value & v)
{
Expr * e = parseString(s);
e->eval(*state, *env, v);
state->forceValue(v, v.determinePos(noPos));
}
std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth)
{
ValuesSeen seen;
return printValue(str, v, maxDepth, seen);
}
// 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, v.determinePos(noPos));
switch (v.type()) {
case nInt:
2020-06-24 19:10:41 +00:00
str << ANSI_CYAN << v.integer << ANSI_NORMAL;
break;
case nBool:
str << ANSI_CYAN;
2023-04-16 10:56:31 +00:00
printLiteralBool(str, v.boolean);
str << ANSI_NORMAL;
2020-06-24 19:10:41 +00:00
break;
case nString:
2021-09-14 08:38:10 +00:00
str << ANSI_WARNING;
2023-04-16 10:56:31 +00:00
printLiteralString(str, v.string.s);
2020-06-24 19:10:41 +00:00
str << ANSI_NORMAL;
break;
case nPath:
str << ANSI_GREEN << v.path().to_string() << ANSI_NORMAL; // !!! escaping?
2020-06-24 19:10:41 +00:00
break;
case nNull:
2020-06-24 19:10:41 +00:00
str << ANSI_CYAN "null" ANSI_NORMAL;
break;
case nAttrs: {
2020-06-24 19:10:41 +00:00
seen.insert(&v);
2020-06-24 19:10:41 +00:00
bool isDrv = state->isDerivation(v);
2020-06-24 19:10:41 +00:00
if (isDrv) {
str << "«derivation ";
Bindings::iterator i = v.attrs->find(state->sDrvPath);
Use `std::set<StringContextElem>` not `PathSet` for string contexts Motivation `PathSet` is not correct because string contexts have other forms (`Built` and `DrvDeep`) that are not rendered as plain store paths. Instead of wrongly using `PathSet`, or "stringly typed" using `StringSet`, use `std::std<StringContextElem>`. ----- In support of this change, `NixStringContext` is now defined as `std::std<StringContextElem>` not `std:vector<StringContextElem>`. The old definition was just used by a `getContext` method which was only used by the eval cache. It can be deleted altogether since the types are now unified and the preexisting `copyContext` function already suffices. Summarizing the previous paragraph: Old: - `value/context.hh`: `NixStringContext = std::vector<StringContextElem>` - `value.hh`: `NixStringContext Value::getContext(...)` - `value.hh`: `copyContext(...)` New: - `value/context.hh`: `NixStringContext = std::set<StringContextElem>` - `value.hh`: `copyContext(...)` ---- The string representation of string context elements no longer contains the store dir. The diff of `src/libexpr/tests/value/context.cc` should make clear what the new representation is, so we recommend reviewing that file first. This was done for two reasons: Less API churn: `Value::mkString` and friends did not take a `Store` before. But if `NixStringContextElem::{parse, to_string}` *do* take a store (as they did before), then we cannot have the `Value` functions use them (in order to work with the fully-structured `NixStringContext`) without adding that argument. That would have been a lot of churn of threading the store, and this diff is already large enough, so the easier and less invasive thing to do was simply make the element `parse` and `to_string` functions not take the `Store` reference, and the easiest way to do that was to simply drop the store dir. Space usage: Dropping the `/nix/store/` (or similar) from the internal representation will safe space in the heap of the Nix programming being interpreted. If the heap contains many strings with non-trivial contexts, the saving could add up to something significant. ---- The eval cache version is bumped. The eval cache serialization uses `NixStringContextElem::{parse, to_string}`, and since those functions are changed per the above, that means the on-disk representation is also changed. This is simply done by changing the name of the used for the eval cache from `eval-cache-v4` to eval-cache-v5`. ---- To avoid some duplication `EvalCache::mkPathString` is added to abstract over the simple case of turning a store path to a string with just that string in the context. Context This PR picks up where #7543 left off. That one introduced the fully structured `NixStringContextElem` data type, but kept `PathSet context` as an awkward middle ground between internal `char[][]` interpreter heap string contexts and `NixStringContext` fully parsed string contexts. The infelicity of `PathSet context` was specifically called out during Nix team group review, but it was agreeing that fixing it could be left as future work. This is that future work. A possible follow-up step would be to get rid of the `char[][]` evaluator heap representation, too, but it is not yet clear how to do that. To use `NixStringContextElem` there we would need to get the STL containers to GC pointers in the GC build, and I am not sure how to do that. ---- PR #7543 effectively is writing the inverse of a `mkPathString`, `mkOutputString`, and one more such function for the `DrvDeep` case. I would like that PR to have property tests ensuring it is actually the inverse as expected. This PR sets things up nicely so that reworking that PR to be in that more elegant and better tested way is possible. Co-authored-by: Théophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com>
2023-01-29 01:31:10 +00:00
NixStringContext 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 << "»";
2020-06-24 19:10:41 +00:00
}
2020-06-24 19:10:41 +00:00
else if (maxDepth > 0) {
str << "{ ";
typedef std::map<std::string, Value *> Sorted;
2020-06-24 19:10:41 +00:00
Sorted sorted;
for (auto & i : *v.attrs)
sorted.emplace(state->symbols[i.name], i.value);
2020-06-24 19:10:41 +00:00
for (auto & i : sorted) {
printAttributeName(str, i.first);
2020-06-24 19:10:41 +00:00
str << " = ";
if (seen.count(i.second))
2020-06-24 19:10:41 +00:00
str << "«repeated»";
else
try {
printValue(str, *i.second, maxDepth - 1, seen);
} catch (AssertionError & e) {
str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
}
str << "; ";
}
2020-06-24 19:10:41 +00:00
str << "}";
} else
str << "{ ... }";
2020-06-24 19:10:41 +00:00
break;
}
case nList:
2020-06-24 19:10:41 +00:00
seen.insert(&v);
str << "[ ";
if (maxDepth > 0)
for (auto elem : v.listItems()) {
if (seen.count(elem))
2020-06-24 19:10:41 +00:00
str << "«repeated»";
else
try {
printValue(str, *elem, maxDepth - 1, seen);
2020-06-24 19:10:41 +00:00
} 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();
}
2020-06-24 19:10:41 +00:00
break;
case nFloat:
2020-06-24 19:10:41 +00:00
str << v.fpoint;
break;
2020-06-19 19:44:08 +00:00
case nThunk:
case nExternal:
2020-06-24 19:10:41 +00:00
default:
str << ANSI_RED "«unknown»" ANSI_NORMAL;
break;
}
return str;
}
std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<AnnotatedValues()> getValues)
{
return std::make_unique<NixRepl>(
searchPath,
openStore(),
state,
getValues
);
}
void AbstractNixRepl::runSimple(
ref<EvalState> evalState,
2022-05-06 02:26:10 +00:00
const ValMap & extraEnv)
{
auto getValues = [&]()->NixRepl::AnnotatedValues{
NixRepl::AnnotatedValues values;
return values;
};
SearchPath searchPath = {};
auto repl = std::make_unique<NixRepl>(
searchPath,
openStore(),
evalState,
getValues
);
repl->initEnv();
// add 'extra' vars.
for (auto & [name, value] : extraEnv)
2022-05-25 16:21:20 +00:00
repl->addVarToScope(repl->state->symbols.create(name), *value);
repl->mainLoop();
}
}