From aa3927f0f19a654a80166aa007b7f830fc3536b8 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Thu, 14 Apr 2022 13:49:47 +0100 Subject: [PATCH 01/79] feat: include openssh in docker image When leveraging remote builders or cache in CI workloads, sometimes you need to configure nix to connect via SSH to a remote server. It is the case for example when using nixbuild.net. By including `openssh` package, CI should be able to reach remote builders when configured i.e. with environment variables. --- docker.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/docker.nix b/docker.nix index 251bd2f46..0cd64856f 100644 --- a/docker.nix +++ b/docker.nix @@ -22,6 +22,7 @@ let findutils iana-etc git + openssh ]; users = { From 75b62e52600a44b42693944b50638bf580a2c86e Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 18 Jun 2020 17:54:16 +0000 Subject: [PATCH 02/79] Avoid `fmt` when constructor already does it There is a correctnes issue here, but #3724 will fix that. This is just a cleanup for brevity's sake. --- src/libfetchers/mercurial.cc | 4 +-- src/libmain/shared.cc | 22 +++++++------- src/libstore/filetransfer.cc | 13 ++++---- src/libstore/local-store.cc | 6 ++-- src/libstore/sqlite.cc | 57 ++++++++++++++++++++++-------------- src/libstore/sqlite.hh | 16 ++++++++-- src/libutil/util.cc | 4 +-- src/nix/repl.cc | 2 +- 8 files changed, 73 insertions(+), 51 deletions(-) diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 1beb8b944..5c5671681 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -36,7 +36,7 @@ static std::string runHg(const Strings & args, const std::optional auto res = runProgram(std::move(opts)); if (!statusOk(res.first)) - throw ExecError(res.first, fmt("hg %1%", statusToString(res.first))); + throw ExecError(res.first, "hg %1%", statusToString(res.first)); return res.second; } @@ -273,7 +273,7 @@ struct MercurialInputScheme : InputScheme runHg({ "recover", "-R", cacheDir }); runHg({ "pull", "-R", cacheDir, "--", actualUrl }); } else { - throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status))); + throw ExecError(e.status, "'hg pull' %s", statusToString(e.status)); } } } else { diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 562d1b414..31454e49d 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -60,37 +60,37 @@ void printMissing(ref store, const StorePathSet & willBuild, { if (!willBuild.empty()) { if (willBuild.size() == 1) - printMsg(lvl, fmt("this derivation will be built:")); + printMsg(lvl, "this derivation will be built:"); else - printMsg(lvl, fmt("these %d derivations will be built:", willBuild.size())); + printMsg(lvl, "these %d derivations will be built:", willBuild.size()); auto sorted = store->topoSortPaths(willBuild); reverse(sorted.begin(), sorted.end()); for (auto & i : sorted) - printMsg(lvl, fmt(" %s", store->printStorePath(i))); + printMsg(lvl, " %s", store->printStorePath(i)); } if (!willSubstitute.empty()) { const float downloadSizeMiB = downloadSize / (1024.f * 1024.f); const float narSizeMiB = narSize / (1024.f * 1024.f); if (willSubstitute.size() == 1) { - printMsg(lvl, fmt("this path will be fetched (%.2f MiB download, %.2f MiB unpacked):", + printMsg(lvl, "this path will be fetched (%.2f MiB download, %.2f MiB unpacked):", downloadSizeMiB, - narSizeMiB)); + narSizeMiB); } else { - printMsg(lvl, fmt("these %d paths will be fetched (%.2f MiB download, %.2f MiB unpacked):", + printMsg(lvl, "these %d paths will be fetched (%.2f MiB download, %.2f MiB unpacked):", willSubstitute.size(), downloadSizeMiB, - narSizeMiB)); + narSizeMiB); } for (auto & i : willSubstitute) - printMsg(lvl, fmt(" %s", store->printStorePath(i))); + printMsg(lvl, " %s", store->printStorePath(i)); } if (!unknown.empty()) { - printMsg(lvl, fmt("don't know how to build these paths%s:", - (settings.readOnlyMode ? " (may be caused by read-only store access)" : ""))); + printMsg(lvl, "don't know how to build these paths%s:", + (settings.readOnlyMode ? " (may be caused by read-only store access)" : "")); for (auto & i : unknown) - printMsg(lvl, fmt(" %s", store->printStorePath(i))); + printMsg(lvl, " %s", store->printStorePath(i)); } } diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index c46262299..529a41891 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -443,14 +443,13 @@ struct curlFileTransfer : public FileTransfer : httpStatus != 0 ? FileTransferError(err, std::move(response), - fmt("unable to %s '%s': HTTP error %d ('%s')", - request.verb(), request.uri, httpStatus, statusMsg) - + (code == CURLE_OK ? "" : fmt(" (curl error: %s)", curl_easy_strerror(code))) - ) + "unable to %s '%s': HTTP error %d%s", + request.verb(), request.uri, httpStatus, + code == CURLE_OK ? "" : fmt(" (curl error: %s)", curl_easy_strerror(code))) : FileTransferError(err, std::move(response), - fmt("unable to %s '%s': %s (%d)", - request.verb(), request.uri, curl_easy_strerror(code), code)); + "unable to %s '%s': %s (%d)", + request.verb(), request.uri, curl_easy_strerror(code), code); /* If this is a transient error, then maybe retry the download after a while. If we're writing to a @@ -704,7 +703,7 @@ struct curlFileTransfer : public FileTransfer auto s3Res = s3Helper.getObject(bucketName, key); FileTransferResult res; if (!s3Res.data) - throw FileTransferError(NotFound, {}, "S3 object '%s' does not exist", request.uri); + throw FileTransferError(NotFound, "S3 object '%s' does not exist", request.uri); res.data = std::move(*s3Res.data); callback(std::move(res)); #else diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index d77fff963..ece5bb5ef 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -482,18 +482,18 @@ void LocalStore::openDB(State & state, bool create) SQLiteStmt stmt; stmt.create(db, "pragma main.journal_mode;"); if (sqlite3_step(stmt) != SQLITE_ROW) - throwSQLiteError(db, "querying journal mode"); + SQLiteError::throw_(db, "querying journal mode"); prevMode = std::string((const char *) sqlite3_column_text(stmt, 0)); } if (prevMode != mode && sqlite3_exec(db, ("pragma main.journal_mode = " + mode + ";").c_str(), 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "setting journal mode"); + SQLiteError::throw_(db, "setting journal mode"); /* Increase the auto-checkpoint interval to 40000 pages. This seems enough to ensure that instantiating the NixOS system derivation is done in a single fsync(). */ if (mode == "wal" && sqlite3_exec(db, "pragma wal_autocheckpoint = 40000;", 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "setting autocheckpoint interval"); + SQLiteError::throw_(db, "setting autocheckpoint interval"); /* Initialise the database schema, if necessary. */ if (create) { diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index 1d82b4ab1..32d2fc021 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -8,22 +8,35 @@ namespace nix { -[[noreturn]] void throwSQLiteError(sqlite3 * db, const FormatOrString & fs) +template +SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, const Args & ... args) + : Error(""), path(path), errNo(errNo), extendedErrNo(extendedErrNo) +{ + auto hf = hintfmt(args...); + err.msg = hintfmt("%s: %s (in '%s')", + normaltxt(hf.str()), + sqlite3_errstr(extendedErrNo), + path ? path : "(in-memory)"); +} + +template +[[noreturn]] void SQLiteError::throw_(sqlite3 * db, const std::string & fs, const Args & ... args) { int err = sqlite3_errcode(db); int exterr = sqlite3_extended_errcode(db); auto path = sqlite3_db_filename(db, nullptr); - if (!path) path = "(in-memory)"; if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { - throw SQLiteBusy( + auto exp = SQLiteBusy(path, err, exterr, fs, args...); + exp.err.msg = hintfmt( err == SQLITE_PROTOCOL - ? fmt("SQLite database '%s' is busy (SQLITE_PROTOCOL)", path) - : fmt("SQLite database '%s' is busy", path)); - } - else - throw SQLiteError("%s: %s (in '%s')", fs.s, sqlite3_errstr(exterr), path); + ? "SQLite database '%s' is busy (SQLITE_PROTOCOL)" + : "SQLite database '%s' is busy", + path ? path : "(in-memory)"); + throw exp; + } else + throw SQLiteError(path, err, exterr, fs, args...); } SQLite::SQLite(const Path & path, bool create) @@ -37,7 +50,7 @@ SQLite::SQLite(const Path & path, bool create) throw Error("cannot open SQLite database '%s'", path); if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) - throwSQLiteError(db, "setting timeout"); + SQLiteError::throw_(db, "setting timeout"); exec("pragma foreign_keys = 1"); } @@ -46,7 +59,7 @@ SQLite::~SQLite() { try { if (db && sqlite3_close(db) != SQLITE_OK) - throwSQLiteError(db, "closing database"); + SQLiteError::throw_(db, "closing database"); } catch (...) { ignoreException(); } @@ -62,7 +75,7 @@ void SQLite::exec(const std::string & stmt) { retrySQLite([&]() { if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, format("executing SQLite statement '%s'") % stmt); + SQLiteError::throw_(db, "executing SQLite statement '%s'", stmt); }); } @@ -76,7 +89,7 @@ void SQLiteStmt::create(sqlite3 * db, const std::string & sql) checkInterrupt(); assert(!stmt); if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK) - throwSQLiteError(db, fmt("creating statement '%s'", sql)); + SQLiteError::throw_(db, "creating statement '%s'", sql); this->db = db; this->sql = sql; } @@ -85,7 +98,7 @@ SQLiteStmt::~SQLiteStmt() { try { if (stmt && sqlite3_finalize(stmt) != SQLITE_OK) - throwSQLiteError(db, fmt("finalizing statement '%s'", sql)); + SQLiteError::throw_(db, "finalizing statement '%s'", sql); } catch (...) { ignoreException(); } @@ -109,7 +122,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (std::string_view value, bool not { if (notNull) { if (sqlite3_bind_text(stmt, curArg++, value.data(), -1, SQLITE_TRANSIENT) != SQLITE_OK) - throwSQLiteError(stmt.db, "binding argument"); + SQLiteError::throw_(stmt.db, "binding argument"); } else bind(); return *this; @@ -119,7 +132,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (const unsigned char * data, size { if (notNull) { if (sqlite3_bind_blob(stmt, curArg++, data, len, SQLITE_TRANSIENT) != SQLITE_OK) - throwSQLiteError(stmt.db, "binding argument"); + SQLiteError::throw_(stmt.db, "binding argument"); } else bind(); return *this; @@ -129,7 +142,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull) { if (notNull) { if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK) - throwSQLiteError(stmt.db, "binding argument"); + SQLiteError::throw_(stmt.db, "binding argument"); } else bind(); return *this; @@ -138,7 +151,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull) SQLiteStmt::Use & SQLiteStmt::Use::bind() { if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK) - throwSQLiteError(stmt.db, "binding argument"); + SQLiteError::throw_(stmt.db, "binding argument"); return *this; } @@ -152,14 +165,14 @@ void SQLiteStmt::Use::exec() int r = step(); assert(r != SQLITE_ROW); if (r != SQLITE_DONE) - throwSQLiteError(stmt.db, fmt("executing SQLite statement '%s'", sqlite3_expanded_sql(stmt.stmt))); + SQLiteError::throw_(stmt.db, fmt("executing SQLite statement '%s'", sqlite3_expanded_sql(stmt.stmt))); } bool SQLiteStmt::Use::next() { int r = step(); if (r != SQLITE_DONE && r != SQLITE_ROW) - throwSQLiteError(stmt.db, fmt("executing SQLite query '%s'", sqlite3_expanded_sql(stmt.stmt))); + SQLiteError::throw_(stmt.db, fmt("executing SQLite query '%s'", sqlite3_expanded_sql(stmt.stmt))); return r == SQLITE_ROW; } @@ -185,14 +198,14 @@ SQLiteTxn::SQLiteTxn(sqlite3 * db) { this->db = db; if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "starting transaction"); + SQLiteError::throw_(db, "starting transaction"); active = true; } void SQLiteTxn::commit() { if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "committing transaction"); + SQLiteError::throw_(db, "committing transaction"); active = false; } @@ -200,7 +213,7 @@ SQLiteTxn::~SQLiteTxn() { try { if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK) - throwSQLiteError(db, "aborting transaction"); + SQLiteError::throw_(db, "aborting transaction"); } catch (...) { ignoreException(); } diff --git a/src/libstore/sqlite.hh b/src/libstore/sqlite.hh index 99f0d56ce..72ec302e1 100644 --- a/src/libstore/sqlite.hh +++ b/src/libstore/sqlite.hh @@ -96,10 +96,20 @@ struct SQLiteTxn }; -MakeError(SQLiteError, Error); -MakeError(SQLiteBusy, SQLiteError); +struct SQLiteError : Error +{ + const char *path; + int errNo, extendedErrNo; -[[noreturn]] void throwSQLiteError(sqlite3 * db, const FormatOrString & fs); + template + [[noreturn]] static void throw_(sqlite3 * db, const std::string & fs, const Args & ... args); + +protected: + template + SQLiteError(const char *path, int errNo, int extendedErrNo, const Args & ... args); +}; + +MakeError(SQLiteBusy, SQLiteError); void handleSQLiteBusy(const SQLiteBusy & e); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index c075a14b4..d4379a0cf 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1062,7 +1062,7 @@ std::string runProgram(Path program, bool searchPath, const Strings & args, auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input}); if (!statusOk(res.first)) - throw ExecError(res.first, fmt("program '%1%' %2%", program, statusToString(res.first))); + throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first)); return res.second; } @@ -1190,7 +1190,7 @@ void runProgram2(const RunOptions & options) if (source) promise.get_future().get(); if (status) - throw ExecError(status, fmt("program '%1%' %2%", options.program, statusToString(status))); + throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status)); } diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 1f9d4fb4e..ec8a57a8e 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -119,7 +119,7 @@ std::string runNix(Path program, const Strings & args, }); if (!statusOk(res.first)) - throw ExecError(res.first, fmt("program '%1%' %2%", program, statusToString(res.first))); + throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first)); return res.second; } From 0e2b01b14e77dfb9a6f1748dec353273f91d1609 Mon Sep 17 00:00:00 2001 From: ckie Date: Mon, 18 Apr 2022 20:21:47 +0300 Subject: [PATCH 03/79] nix repl: make symlinks with the :bl command Requested by ppepino on the Matrix: https://matrix.to/#/!KqkRjyTEzAGRiZFBYT:nixos.org/$Tb32BS3rVE2BSULAX4sPm0h6CDewX2hClOTGzTC7gwM?via=nixos.org&via=matrix.org&via=nixos.dev This adds a new command, :bl, which works like :b but also creates a GC root symlink to the various derivation outputs. ckie@cookiemonster ~/git/nix -> ./outputs/out/bin/nix repl Welcome to Nix 2.6.0. Type :? for help. nix-repl> :l Added 16118 variables. nix-repl> :b runCommand "hello" {} "echo hi > $out" This derivation produced the following outputs: ./repl-result-out -> /nix/store/kidqq2acdpi05c4a9mlbg2baikmzik44-hello [1 built, 0.0 MiB DL] ckie@cookiemonster ~/git/nix -> cat ./repl-result-out hi --- .gitignore | 1 + doc/manual/src/release-notes/rl-next.md | 3 +++ src/nix/repl.cc | 20 +++++++++++++++----- tests/repl.sh | 16 ++++++++++++---- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 58e7377fb..db363aefd 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ perl/Makefile.config /tests/shell.drv /tests/config.nix /tests/ca/config.nix +/tests/repl-result-out # /tests/lang/ /tests/lang/*.out diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index c869b5e2f..3e2998c6c 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1 +1,4 @@ # Release X.Y (202?-??-??) + +* `nix repl` has a new build-'n-link (`:bl`) command that builds a derivation + while creating GC root symlinks. diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 1f9d4fb4e..b055698b3 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -33,6 +33,7 @@ extern "C" { #include "command.hh" #include "finally.hh" #include "markdown.hh" +#include "local-fs-store.hh" #if HAVE_BOEHMGC #define GC_INCLUDE_NEW @@ -419,7 +420,8 @@ bool NixRepl::processLine(std::string line) << " Evaluate and print expression\n" << " = Bind expression to variable\n" << " :a Add attributes from resulting set to scope\n" - << " :b Build derivation\n" + << " :b Build a derivation\n" + << " :bl Build a derivation, creating GC roots in the working directory\n" << " :e Open package or function in $EDITOR\n" << " :i Build derivation, then install result into current profile\n" << " :l Load Nix expression and add it to scope\n" @@ -502,18 +504,26 @@ bool NixRepl::processLine(std::string line) runNix("nix-shell", {state->store->printStorePath(drvPath)}); } - else if (command == ":b" || command == ":i" || command == ":s" || command == ":log") { + else if (command == ":b" || command == ":bl" || command == ":i" || command == ":s" || command == ":log") { Value v; evalString(arg, v); StorePath drvPath = getDerivationPath(v); Path drvPathRaw = state->store->printStorePath(drvPath); - if (command == ":b") { + 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)) - logger->cout(" %s -> %s", outputName, state->store->printStorePath(outputPath)); + for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath)) { + auto localStore = state->store.dynamic_pointer_cast(); + 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") { diff --git a/tests/repl.sh b/tests/repl.sh index 6505f1741..b6937b9e9 100644 --- a/tests/repl.sh +++ b/tests/repl.sh @@ -1,29 +1,37 @@ source common.sh +testDir="$PWD" +cd "$TEST_ROOT" + replCmds=" simple = 1 -simple = import ./simple.nix -:b simple +simple = import $testDir/simple.nix +:bl simple :log simple " replFailingCmds=" -failing = import ./simple-failing.nix +failing = import $testDir/simple-failing.nix :b failing :log failing " replUndefinedVariable=" -import ./undefined-variable.nix +import $testDir/undefined-variable.nix " testRepl () { local nixArgs=("$@") + rm -rf repl-result-out || true # cleanup from other runs backed by a foreign nix store local replOutput="$(nix repl "${nixArgs[@]}" <<< "$replCmds")" echo "$replOutput" local outPath=$(echo "$replOutput" |& grep -o -E "$NIX_STORE_DIR/\w*-simple") nix path-info "${nixArgs[@]}" "$outPath" + [ "$(realpath ./repl-result-out)" == "$outPath" ] || fail "nix repl :bl doesn't make a symlink" + # run it again without checking the output to ensure the previously created symlink gets overwritten + nix repl "${nixArgs[@]}" <<< "$replCmds" || fail "nix repl does not work twice with the same inputs" + # simple.nix prints a PATH during build echo "$replOutput" | grep -qs 'PATH=' || fail "nix repl :log doesn't output logs" local replOutput="$(nix repl "${nixArgs[@]}" <<< "$replFailingCmds" 2>&1)" From ebf2fd76b106d5eb8f45ccce0615653108bb99bc Mon Sep 17 00:00:00 2001 From: Yorick van Pelt Date: Wed, 20 Apr 2022 15:41:01 +0200 Subject: [PATCH 04/79] Add custom to_json and from_json functions for ExperimentalFeature nix show-config --json was serializing experimental features as ints. nlohmann::json will automatically use these definitions to serialize and deserialize ExperimentalFeatures. Strictly, we don't use the from_json instance yet, it's provided for completeness and hopefully future use. --- src/libutil/experimental-features.cc | 14 ++++++++++++++ src/libutil/experimental-features.hh | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index e033a4116..df37edf57 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -58,4 +58,18 @@ std::ostream & operator <<(std::ostream & str, const ExperimentalFeature & featu return str << showExperimentalFeature(feature); } +void to_json(nlohmann::json& j, const ExperimentalFeature& feature) { + j = showExperimentalFeature(feature); +} + +void from_json(const nlohmann::json& j, ExperimentalFeature& feature) { + const std::string input = j; + const auto parsed = parseExperimentalFeature(input); + + if (parsed.has_value()) + feature = *parsed; + else + throw Error("Unknown experimental feature '%s' in JSON input", input); +} + } diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index 266e41a22..a6d080094 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -51,4 +51,11 @@ public: MissingExperimentalFeature(ExperimentalFeature); }; +/** + * Semi-magic conversion to and from json. + * See the nlohmann/json readme for more details. + */ +void to_json(nlohmann::json&, const ExperimentalFeature&); +void from_json(const nlohmann::json&, ExperimentalFeature&); + } From 51cfea8bb05d3f284534b2486cc29bb0ebf17ade Mon Sep 17 00:00:00 2001 From: Artturin Date: Sat, 19 Mar 2022 20:16:05 +0200 Subject: [PATCH 05/79] nix build: add --print-out-paths flag has the same functionality as default nix-build $ nix-build . -A "bash" -A "bash.dev" -A "tinycc" /nix/store/4nmqxajzaf60yjribkgvj5j54x9yvr1r-bash-5.1-p12 /nix/store/c49i1ggnr5cc8gxmk9xm0cn961z104dn-bash-5.1-p12-dev /nix/store/dbapb08862ajgaax3621fz8hly9fdah3-tcc-0.9.27+date=2022-01-11 $ nix-build . -A "bash" /nix/store/4nmqxajzaf60yjribkgvj5j54x9yvr1r-bash-5.1-p12 $ $HOME/nixgits/nix/result/bin/nix build "nixpkgs#bash" "nixpkgs#bash.dev" "nixpkgs#tinycc" --print-out-paths /nix/store/4nmqxajzaf60yjribkgvj5j54x9yvr1r-bash-5.1-p12 /nix/store/c49i1ggnr5cc8gxmk9xm0cn961z104dn-bash-5.1-p12-dev /nix/store/dbapb08862ajgaax3621fz8hly9fdah3-tcc-0.9.27+date=2022-01-11 $ $HOME/nixgits/nix/result/bin/nix build "nixpkgs#bash" --print-out-paths /nix/store/4nmqxajzaf60yjribkgvj5j54x9yvr1r-bash-5.1-p12 --- doc/manual/src/release-notes/rl-next.md | 3 +++ src/nix/build.cc | 24 ++++++++++++++++++++++++ src/nix/build.md | 7 +++++++ tests/build-remote.sh | 8 ++++++++ 4 files changed, 42 insertions(+) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 3e2998c6c..452002dca 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -2,3 +2,6 @@ * `nix repl` has a new build-'n-link (`:bl`) command that builds a derivation while creating GC root symlinks. + +* `nix build` has a new `--print-out-paths` flag to print the resulting output paths. + This matches the default behaviour of `nix-build`. diff --git a/src/nix/build.cc b/src/nix/build.cc index 840c7ca38..9c648d28e 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -4,6 +4,7 @@ #include "shared.hh" #include "store-api.hh" #include "local-fs-store.hh" +#include "progress-bar.hh" #include @@ -12,6 +13,7 @@ using namespace nix; struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile { Path outLink = "result"; + bool printOutputPaths = false; BuildMode buildMode = bmNormal; CmdBuild() @@ -31,6 +33,12 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile .handler = {&outLink, Path("")}, }); + addFlag({ + .longName = "print-out-paths", + .description = "Print the resulting output paths", + .handler = {&printOutputPaths, true}, + }); + addFlag({ .longName = "rebuild", .description = "Rebuild an already built package and compare the result to the existing store paths.", @@ -93,6 +101,22 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile }, buildable.raw()); } + if (printOutputPaths) { + stopProgressBar(); + for (auto & buildable : buildables) { + std::visit(overloaded { + [&](const BuiltPath::Opaque & bo) { + std::cout << store->printStorePath(bo.path) << std::endl; + }, + [&](const BuiltPath::Built & bfd) { + for (auto & output : bfd.outputs) { + std::cout << store->printStorePath(output.second) << std::endl; + } + }, + }, buildable.raw()); + } + } + updateProfile(buildables); } }; diff --git a/src/nix/build.md b/src/nix/build.md index 20138b7e0..6a79f308c 100644 --- a/src/nix/build.md +++ b/src/nix/build.md @@ -25,6 +25,13 @@ R""( lrwxrwxrwx 1 … result-1 -> /nix/store/rkfrm0z6x6jmi7d3gsmma4j53h15mg33-cowsay-3.03+dfsg2 ``` +* Build GNU Hello and print the resulting store path. + + ```console + # nix build nixpkgs#hello --print-out-paths + /nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10 + ``` + * Build a specific output: ```console diff --git a/tests/build-remote.sh b/tests/build-remote.sh index 094366872..d1da134dc 100644 --- a/tests/build-remote.sh +++ b/tests/build-remote.sh @@ -34,6 +34,14 @@ outPath=$(readlink -f $TEST_ROOT/result) grep 'FOO BAR BAZ' $TEST_ROOT/machine0/$outPath +testPrintOutPath=$(nix build -L -v -f $file --print-out-paths --max-jobs 0 \ + --arg busybox $busybox \ + --store $TEST_ROOT/machine0 \ + --builders "$(join_by '; ' "${builders[@]}")" +) + +[[ $testPrintOutPath =~ store.*build-remote ]] + set -o pipefail # Ensure that input1 was built on store1 due to the required feature. From 05ec0beb40f5e4e162903570b68837b34811a02d Mon Sep 17 00:00:00 2001 From: John Ericson Date: Wed, 20 Apr 2022 16:57:06 +0000 Subject: [PATCH 06/79] Move templated functions to `sqlite-impl.hh` This ensures that use-sites properly trigger new monomorphisations on one hand, and on the other hand keeps the main `sqlite.hh` clean and interface-only. I think that is good practice in general, but in this situation in particular we do indeed have `sqlite.hh` users that don't need the `throw_` function. --- src/libstore/local-store.cc | 1 + src/libstore/sqlite-impl.hh | 42 +++++++++++++++++++++++++++++++++++++ src/libstore/sqlite.cc | 32 +--------------------------- 3 files changed, 44 insertions(+), 31 deletions(-) create mode 100644 src/libstore/sqlite-impl.hh diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index ece5bb5ef..42cc30cbf 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -10,6 +10,7 @@ #include "topo-sort.hh" #include "finally.hh" #include "compression.hh" +#include "sqlite-impl.hh" #include #include diff --git a/src/libstore/sqlite-impl.hh b/src/libstore/sqlite-impl.hh new file mode 100644 index 000000000..c0a99403b --- /dev/null +++ b/src/libstore/sqlite-impl.hh @@ -0,0 +1,42 @@ +#include "sqlite.hh" +#include "globals.hh" +#include "util.hh" + +#include + +#include + +namespace nix { + +template +SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, const Args & ... args) + : Error(""), path(path), errNo(errNo), extendedErrNo(extendedErrNo) +{ + auto hf = hintfmt(args...); + err.msg = hintfmt("%s: %s (in '%s')", + normaltxt(hf.str()), + sqlite3_errstr(extendedErrNo), + path ? path : "(in-memory)"); +} + +template +[[noreturn]] void SQLiteError::throw_(sqlite3 * db, const std::string & fs, const Args & ... args) +{ + int err = sqlite3_errcode(db); + int exterr = sqlite3_extended_errcode(db); + + auto path = sqlite3_db_filename(db, nullptr); + + if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { + auto exp = SQLiteBusy(path, err, exterr, fs, args...); + exp.err.msg = hintfmt( + err == SQLITE_PROTOCOL + ? "SQLite database '%s' is busy (SQLITE_PROTOCOL)" + : "SQLite database '%s' is busy", + path ? path : "(in-memory)"); + throw exp; + } else + throw SQLiteError(path, err, exterr, fs, args...); +} + +} diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index 32d2fc021..80290fa87 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -1,4 +1,5 @@ #include "sqlite.hh" +#include "sqlite-impl.hh" #include "globals.hh" #include "util.hh" @@ -8,37 +9,6 @@ namespace nix { -template -SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, const Args & ... args) - : Error(""), path(path), errNo(errNo), extendedErrNo(extendedErrNo) -{ - auto hf = hintfmt(args...); - err.msg = hintfmt("%s: %s (in '%s')", - normaltxt(hf.str()), - sqlite3_errstr(extendedErrNo), - path ? path : "(in-memory)"); -} - -template -[[noreturn]] void SQLiteError::throw_(sqlite3 * db, const std::string & fs, const Args & ... args) -{ - int err = sqlite3_errcode(db); - int exterr = sqlite3_extended_errcode(db); - - auto path = sqlite3_db_filename(db, nullptr); - - if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { - auto exp = SQLiteBusy(path, err, exterr, fs, args...); - exp.err.msg = hintfmt( - err == SQLITE_PROTOCOL - ? "SQLite database '%s' is busy (SQLITE_PROTOCOL)" - : "SQLite database '%s' is busy", - path ? path : "(in-memory)"); - throw exp; - } else - throw SQLiteError(path, err, exterr, fs, args...); -} - SQLite::SQLite(const Path & path, bool create) { // useSQLiteWAL also indicates what virtual file system we need. Using From f63b0f4540b61d8cdac7a3c52ca6e190f7c1b8cf Mon Sep 17 00:00:00 2001 From: John Ericson Date: Wed, 20 Apr 2022 17:37:59 +0000 Subject: [PATCH 07/79] Actually, solve this in a lighter-weight way The templating is very superficial --- src/libstore/local-store.cc | 1 - src/libstore/sqlite-impl.hh | 42 ------------------------------------- src/libstore/sqlite.cc | 29 ++++++++++++++++++++++++- src/libstore/sqlite.hh | 14 +++++++++++-- 4 files changed, 40 insertions(+), 46 deletions(-) delete mode 100644 src/libstore/sqlite-impl.hh diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 42cc30cbf..ece5bb5ef 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -10,7 +10,6 @@ #include "topo-sort.hh" #include "finally.hh" #include "compression.hh" -#include "sqlite-impl.hh" #include #include diff --git a/src/libstore/sqlite-impl.hh b/src/libstore/sqlite-impl.hh deleted file mode 100644 index c0a99403b..000000000 --- a/src/libstore/sqlite-impl.hh +++ /dev/null @@ -1,42 +0,0 @@ -#include "sqlite.hh" -#include "globals.hh" -#include "util.hh" - -#include - -#include - -namespace nix { - -template -SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, const Args & ... args) - : Error(""), path(path), errNo(errNo), extendedErrNo(extendedErrNo) -{ - auto hf = hintfmt(args...); - err.msg = hintfmt("%s: %s (in '%s')", - normaltxt(hf.str()), - sqlite3_errstr(extendedErrNo), - path ? path : "(in-memory)"); -} - -template -[[noreturn]] void SQLiteError::throw_(sqlite3 * db, const std::string & fs, const Args & ... args) -{ - int err = sqlite3_errcode(db); - int exterr = sqlite3_extended_errcode(db); - - auto path = sqlite3_db_filename(db, nullptr); - - if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { - auto exp = SQLiteBusy(path, err, exterr, fs, args...); - exp.err.msg = hintfmt( - err == SQLITE_PROTOCOL - ? "SQLite database '%s' is busy (SQLITE_PROTOCOL)" - : "SQLite database '%s' is busy", - path ? path : "(in-memory)"); - throw exp; - } else - throw SQLiteError(path, err, exterr, fs, args...); -} - -} diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index 80290fa87..2090beabd 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -1,5 +1,4 @@ #include "sqlite.hh" -#include "sqlite-impl.hh" #include "globals.hh" #include "util.hh" @@ -9,6 +8,34 @@ namespace nix { +SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf) + : Error(""), path(path), errNo(errNo), extendedErrNo(extendedErrNo) +{ + err.msg = hintfmt("%s: %s (in '%s')", + normaltxt(hf.str()), + sqlite3_errstr(extendedErrNo), + path ? path : "(in-memory)"); +} + +[[noreturn]] void SQLiteError::throw_(sqlite3 * db, hintformat && hf) +{ + int err = sqlite3_errcode(db); + int exterr = sqlite3_extended_errcode(db); + + auto path = sqlite3_db_filename(db, nullptr); + + if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { + auto exp = SQLiteBusy(path, err, exterr, std::move(hf)); + exp.err.msg = hintfmt( + err == SQLITE_PROTOCOL + ? "SQLite database '%s' is busy (SQLITE_PROTOCOL)" + : "SQLite database '%s' is busy", + path ? path : "(in-memory)"); + throw exp; + } else + throw SQLiteError(path, err, exterr, std::move(hf)); +} + SQLite::SQLite(const Path & path, bool create) { // useSQLiteWAL also indicates what virtual file system we need. Using diff --git a/src/libstore/sqlite.hh b/src/libstore/sqlite.hh index 72ec302e1..3a4ad8633 100644 --- a/src/libstore/sqlite.hh +++ b/src/libstore/sqlite.hh @@ -102,11 +102,21 @@ struct SQLiteError : Error int errNo, extendedErrNo; template - [[noreturn]] static void throw_(sqlite3 * db, const std::string & fs, const Args & ... args); + [[noreturn]] static void throw_(sqlite3 * db, const std::string & fs, const Args & ... args) { + throw_(db, hintfmt(fs, args...)); + } protected: + + SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf); + template - SQLiteError(const char *path, int errNo, int extendedErrNo, const Args & ... args); + SQLiteError(const char *path, int errNo, int extendedErrNo, const std::string & fs, const Args & ... args) + : SQLiteError(path, errNo, extendedErrNo, hintfmt(fs, args...)) + { } + + [[noreturn]] static void throw_(sqlite3 * db, hintformat && hf); + }; MakeError(SQLiteBusy, SQLiteError); From 445ddebde533cc1605ca817d97d573e0161db66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Thu, 21 Apr 2022 09:26:45 +0200 Subject: [PATCH 08/79] Fix the error message in case of a missing realisation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don’t say that the derivation is CA as it might happen on a non-ca derivation too. Technically we could always recover _something_ for a purely input-addressed derivation (like we already do when the `ca-derivations` xp feature isn’t enabled), but it seems better to consistently fail − the end-result wouldn’t really make sense anyways in most cases. --- src/libcmd/installables.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 74c8a6df5..4250c321e 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -831,8 +831,8 @@ std::vector, BuiltPath>> Installable::bui auto realisation = store->queryRealisation(outputId); if (!realisation) throw Error( - "cannot operate on an output of unbuilt " - "content-addressed derivation '%s'", + "cannot operate on an output of the " + "unbuilt derivation '%s'", outputId.to_string()); outputs.insert_or_assign(output, realisation->outPath); } else { From e7d79c78616425cbbea6619ea28ea9a5ec75cabe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Thu, 21 Apr 2022 09:40:55 +0200 Subject: [PATCH 09/79] Make the default SQLiteError constructor public Otherwise the clang builds fail because the constructor of `SQLiteBusy` inherits it, `SQLiteError::_throw` tries to call it, which fails. Strangely, gcc works fine with it. Not sure what the correct behavior is and who is buggy here, but either way, making it public is at the worst a reasonable workaround --- src/libstore/sqlite.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libstore/sqlite.hh b/src/libstore/sqlite.hh index 3a4ad8633..1d1c553ea 100644 --- a/src/libstore/sqlite.hh +++ b/src/libstore/sqlite.hh @@ -106,10 +106,10 @@ struct SQLiteError : Error throw_(db, hintfmt(fs, args...)); } -protected: - SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf); +protected: + template SQLiteError(const char *path, int errNo, int extendedErrNo, const std::string & fs, const Args & ... args) : SQLiteError(path, errNo, extendedErrNo, hintfmt(fs, args...)) From 6ada4963111908211a4bbcc2ae65f4205cffaa18 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Mon, 4 Oct 2021 09:00:01 +0100 Subject: [PATCH 10/79] nix: add (failing) selfreference test for multiple realizations The test illustrates failure in issue #5320. Here derivation and it's built input have identical CA sotre path. As a result we generate extraneout reference to build input: $ make installcheck ... ran test tests/selfref-gc.sh... [PASS] ran test tests/ca/selfref-gc.sh... [FAIL] ... deleting '/tmp/.../tests/ca/selfref-gc/store/iqciq1mpg5hc7p6a52fp2bjxbyc9av0v-selfref-gc' deleting '/tmp/...tests/ca/selfref-gc/store/zh0kwpnirw3qbv6dl1ckr1y0kd5aw6ax-selfref-gc.drv' error: executing SQLite statement 'delete from ValidPaths where path = '/tmp/.../tests/ca/selfref-gc/store/fsjq0k146r85lsh01l0icl30rnhv7z72-selfref-gc';': constraint failed (in '/tmp/.../tests/ca/selfref-gc/var/nix/db/db.sqlite') --- tests/ca/selfref-gc.sh | 11 +++++++++++ tests/local.mk | 1 + tests/selfref-gc.sh | 28 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100755 tests/ca/selfref-gc.sh create mode 100644 tests/selfref-gc.sh diff --git a/tests/ca/selfref-gc.sh b/tests/ca/selfref-gc.sh new file mode 100755 index 000000000..89657f6de --- /dev/null +++ b/tests/ca/selfref-gc.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +source common.sh + +requireDaemonNewerThan "2.4pre20210626" + +sed -i 's/experimental-features .*/& ca-derivations ca-references nix-command flakes/' "$NIX_CONF_DIR"/nix.conf + +export NIX_TESTS_CA_BY_DEFAULT=1 +cd .. +source ./selfref-gc.sh diff --git a/tests/local.mk b/tests/local.mk index cb869f32e..e3c4ff4eb 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -92,6 +92,7 @@ nix_tests = \ plugins.sh \ build.sh \ ca/nix-run.sh \ + selfref-gc.sh ca/selfref-gc.sh \ db-migration.sh \ bash-profile.sh \ pass-as-file.sh \ diff --git a/tests/selfref-gc.sh b/tests/selfref-gc.sh new file mode 100644 index 000000000..cf1c4d18e --- /dev/null +++ b/tests/selfref-gc.sh @@ -0,0 +1,28 @@ +source common.sh + +clearStore + +nix-build --no-out-link -E ' + with import ./config.nix; + + let d1 = mkDerivation { + name = "selfref-gc"; + outputs = [ "out" ]; + buildCommand = " + echo SELF_REF: $out > $out + "; + }; in + + # the only change from d1 is d1 as an (unused) build input + # to get identical store path in CA. + mkDerivation { + name = "selfref-gc"; + outputs = [ "out" ]; + buildCommand = " + echo UNUSED: ${d1} + echo SELF_REF: $out > $out + "; + } +' + +nix-collect-garbage From 92656da0b953b92228ef4a252d504c07710e4b47 Mon Sep 17 00:00:00 2001 From: regnat Date: Wed, 3 Nov 2021 16:30:22 +0100 Subject: [PATCH 11/79] Fix the gc with indirect self-references via the realisations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the derivation `foo` depends on `bar`, and they both have the same output path (because they are CA derivations), then this output path will depend both on the realisation of `foo` and of `bar`, which themselves depend on each other. This confuses SQLite which isn’t able to automatically solve this diamond dependency scheme. Help it by adding a trigger to delete all the references between the relevant realisations. Fix #5320 --- src/libstore/ca-specific-schema.sql | 13 +++++++++++++ src/libstore/local-store.cc | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/libstore/ca-specific-schema.sql b/src/libstore/ca-specific-schema.sql index 64cc97fde..d2ea347fb 100644 --- a/src/libstore/ca-specific-schema.sql +++ b/src/libstore/ca-specific-schema.sql @@ -13,6 +13,19 @@ create table if not exists Realisations ( create index if not exists IndexRealisations on Realisations(drvPath, outputName); +-- We can end-up in a weird edge-case where a path depends on itself because +-- it’s an output of a CA derivation, that happens to be the same as one of its +-- dependencies. +-- In that case we have a dependency loop (path -> realisation1 -> realisation2 +-- -> path) that we need to break by removing the dependencies between the +-- realisations +create trigger if not exists DeleteSelfRefsViaRealisations before delete on ValidPaths + begin + delete from RealisationsRefs where realisationReference = ( + select id from Realisations where outputPath = old.id + ); + end; + create table if not exists RealisationsRefs ( referrer integer not null, realisationReference integer, diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index ece5bb5ef..e64917956 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -81,7 +81,7 @@ int getSchema(Path schemaPath) void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) { - const int nixCASchemaVersion = 3; + const int nixCASchemaVersion = 4; int curCASchema = getSchema(schemaPath); if (curCASchema != nixCASchemaVersion) { if (curCASchema > nixCASchemaVersion) { @@ -143,6 +143,19 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) )"); txn.commit(); } + if (curCASchema < 4) { + SQLiteTxn txn(db); + db.exec(R"( + create trigger if not exists DeleteSelfRefsViaRealisations before delete on ValidPaths + begin + delete from RealisationsRefs where realisationReference = ( + select id from Realisations where outputPath = old.id + ); + end; + )"); + txn.commit(); + } + writeFile(schemaPath, fmt("%d", nixCASchemaVersion)); lockFile(lockFd.get(), ltRead, true); } From b6e59d71371c85277b3ad3d5fe6352f2462d39e5 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Sat, 26 Mar 2022 16:35:06 +0000 Subject: [PATCH 12/79] tests: remove 'ca-references' feature The feature was ctabilized in d589a6aa8a5d0c9f391400d7e0e209106e89c857. --- tests/build-remote-content-addressed-floating.sh | 2 +- tests/ca/common.sh | 2 +- tests/ca/selfref-gc.sh | 2 +- tests/nix-profile.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/build-remote-content-addressed-floating.sh b/tests/build-remote-content-addressed-floating.sh index 1f474dde0..e83b42b41 100644 --- a/tests/build-remote-content-addressed-floating.sh +++ b/tests/build-remote-content-addressed-floating.sh @@ -2,7 +2,7 @@ source common.sh file=build-hook-ca-floating.nix -enableFeatures "ca-derivations ca-references" +enableFeatures "ca-derivations" CONTENT_ADDRESSED=true diff --git a/tests/ca/common.sh b/tests/ca/common.sh index b9d415863..b104b5a78 100644 --- a/tests/ca/common.sh +++ b/tests/ca/common.sh @@ -1,5 +1,5 @@ source ../common.sh -enableFeatures "ca-derivations ca-references" +enableFeatures "ca-derivations" restartDaemon diff --git a/tests/ca/selfref-gc.sh b/tests/ca/selfref-gc.sh index 89657f6de..248778894 100755 --- a/tests/ca/selfref-gc.sh +++ b/tests/ca/selfref-gc.sh @@ -4,7 +4,7 @@ source common.sh requireDaemonNewerThan "2.4pre20210626" -sed -i 's/experimental-features .*/& ca-derivations ca-references nix-command flakes/' "$NIX_CONF_DIR"/nix.conf +enableFeatures "ca-derivations nix-command flakes" export NIX_TESTS_CA_BY_DEFAULT=1 cd .. diff --git a/tests/nix-profile.sh b/tests/nix-profile.sh index a7a4d4fa2..fad62b993 100644 --- a/tests/nix-profile.sh +++ b/tests/nix-profile.sh @@ -3,7 +3,7 @@ source common.sh clearStore clearProfiles -enableFeatures "ca-derivations ca-references" +enableFeatures "ca-derivations" restartDaemon # Make a flake. From 74d6782a6a864eed4c49ab606d130aec1201e584 Mon Sep 17 00:00:00 2001 From: regnat Date: Wed, 3 Nov 2021 17:52:49 +0100 Subject: [PATCH 13/79] Disable the selfref-gc test when the daemon is too old --- tests/selfref-gc.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/selfref-gc.sh b/tests/selfref-gc.sh index cf1c4d18e..3f1f50eea 100644 --- a/tests/selfref-gc.sh +++ b/tests/selfref-gc.sh @@ -1,5 +1,7 @@ source common.sh +requireDaemonNewerThan "2.6.0pre20211215" + clearStore nix-build --no-out-link -E ' From 975b0b52e74168b034ccff23827eff4d626f1c46 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Sat, 26 Mar 2022 16:18:51 +0000 Subject: [PATCH 14/79] ca: add sqlite index on `RealisationsRefs(realisationReference)` Without the change any CA deletion triggers linear scan on large RealisationsRefs table: sqlite>.eqp full sqlite> delete from RealisationsRefs where realisationReference IN ( select id from Realisations where outputPath = 1234567890 ); QUERY PLAN |--SCAN RealisationsRefs `--LIST SUBQUERY 1 `--SEARCH Realisations USING COVERING INDEX IndexRealisationsRefsOnOutputPath (outputPath=?) With the change it gets turned into a lookup: sqlite> CREATE INDEX IndexRealisationsRefsRealisationReference on RealisationsRefs(realisationReference); sqlite> delete from RealisationsRefs where realisationReference IN ( select id from Realisations where outputPath = 1234567890 ); QUERY PLAN |--SEARCH RealisationsRefs USING INDEX IndexRealisationsRefsRealisationReference (realisationReference=?) `--LIST SUBQUERY 1 `--SEARCH Realisations USING COVERING INDEX IndexRealisationsRefsOnOutputPath (outputPath=?) --- src/libstore/ca-specific-schema.sql | 2 ++ src/libstore/local-store.cc | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libstore/ca-specific-schema.sql b/src/libstore/ca-specific-schema.sql index c01dc5a76..4ca91f585 100644 --- a/src/libstore/ca-specific-schema.sql +++ b/src/libstore/ca-specific-schema.sql @@ -32,6 +32,8 @@ create table if not exists RealisationsRefs ( foreign key (referrer) references Realisations(id) on delete cascade, foreign key (realisationReference) references Realisations(id) on delete restrict ); +-- used by deletion trigger +create index if not exists IndexRealisationsRefsRealisationReference on RealisationsRefs(realisationReference); -- used by QueryRealisationReferences create index if not exists IndexRealisationsRefs on RealisationsRefs(referrer); diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 76524b01c..5cc5c91cc 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -152,6 +152,8 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) select id from Realisations where outputPath = old.id ); end; + -- used by deletion trigger + create index if not exists IndexRealisationsRefsRealisationReference on RealisationsRefs(realisationReference); )"); txn.commit(); } From 86d7a11c6b338a73c30476be13a6cac562e309c3 Mon Sep 17 00:00:00 2001 From: regnat Date: Fri, 5 Nov 2021 11:17:22 +0100 Subject: [PATCH 15/79] Make sure to delete all the realisation refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deleting just one will only work in the test cases where I didn’t bother creating too many of them :p --- src/libstore/ca-specific-schema.sql | 2 +- src/libstore/local-store.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libstore/ca-specific-schema.sql b/src/libstore/ca-specific-schema.sql index d2ea347fb..c01dc5a76 100644 --- a/src/libstore/ca-specific-schema.sql +++ b/src/libstore/ca-specific-schema.sql @@ -21,7 +21,7 @@ create index if not exists IndexRealisations on Realisations(drvPath, outputName -- realisations create trigger if not exists DeleteSelfRefsViaRealisations before delete on ValidPaths begin - delete from RealisationsRefs where realisationReference = ( + delete from RealisationsRefs where realisationReference in ( select id from Realisations where outputPath = old.id ); end; diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index e64917956..76524b01c 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -148,7 +148,7 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) db.exec(R"( create trigger if not exists DeleteSelfRefsViaRealisations before delete on ValidPaths begin - delete from RealisationsRefs where realisationReference = ( + delete from RealisationsRefs where realisationReference in ( select id from Realisations where outputPath = old.id ); end; From f05e1f6fbb8a760f23a7af16b065078df6588acf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 21 Apr 2022 11:58:40 +0200 Subject: [PATCH 16/79] Move hiliteMatches into a separate header This is mostly so that we don't #include everywhere (which adds quite a bit of compilation time). --- src/libutil/fmt.hh | 12 ------------ src/libutil/{fmt.cc => hilite.cc} | 4 +--- src/libutil/hilite.hh | 20 ++++++++++++++++++++ src/nix/search.cc | 2 +- 4 files changed, 22 insertions(+), 16 deletions(-) rename src/libutil/{fmt.cc => hilite.cc} (96%) create mode 100644 src/libutil/hilite.hh diff --git a/src/libutil/fmt.hh b/src/libutil/fmt.hh index 0821b3b74..7664e5c04 100644 --- a/src/libutil/fmt.hh +++ b/src/libutil/fmt.hh @@ -2,7 +2,6 @@ #include #include -#include #include "ansicolor.hh" @@ -155,15 +154,4 @@ inline hintformat hintfmt(std::string plain_string) return hintfmt("%s", normaltxt(plain_string)); } -/* Highlight all the given matches in the given string `s` by wrapping - them between `prefix` and `postfix`. - - If some matches overlap, then their union will be wrapped rather - than the individual matches. */ -std::string hiliteMatches( - std::string_view s, - std::vector matches, - std::string_view prefix, - std::string_view postfix); - } diff --git a/src/libutil/fmt.cc b/src/libutil/hilite.cc similarity index 96% rename from src/libutil/fmt.cc rename to src/libutil/hilite.cc index 3dd93d73e..a5991ca39 100644 --- a/src/libutil/fmt.cc +++ b/src/libutil/hilite.cc @@ -1,6 +1,4 @@ -#include "fmt.hh" - -#include +#include "hilite.hh" namespace nix { diff --git a/src/libutil/hilite.hh b/src/libutil/hilite.hh new file mode 100644 index 000000000..f8bdbfc55 --- /dev/null +++ b/src/libutil/hilite.hh @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +namespace nix { + +/* Highlight all the given matches in the given string `s` by wrapping + them between `prefix` and `postfix`. + + If some matches overlap, then their union will be wrapped rather + than the individual matches. */ +std::string hiliteMatches( + std::string_view s, + std::vector matches, + std::string_view prefix, + std::string_view postfix); + +} diff --git a/src/nix/search.cc b/src/nix/search.cc index e96a85ea2..e284de95c 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -9,7 +9,7 @@ #include "shared.hh" #include "eval-cache.hh" #include "attr-path.hh" -#include "fmt.hh" +#include "hilite.hh" #include #include From f1eee873ea064e8f94369bdfe2557c18ba35ccc9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 21 Apr 2022 13:00:24 +0200 Subject: [PATCH 17/79] Fix fmt test --- src/libutil/tests/fmt.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libutil/tests/fmt.cc b/src/libutil/tests/fmt.cc index 33772162c..1ff5980d5 100644 --- a/src/libutil/tests/fmt.cc +++ b/src/libutil/tests/fmt.cc @@ -1,9 +1,7 @@ -#include "fmt.hh" +#include "hilite.hh" #include -#include - namespace nix { /* ----------- tests for fmt.hh -------------------------------------------------*/ From 3b9d31b88c95e591c28f3a7423f83c40b9788781 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 21 Apr 2022 13:00:50 +0200 Subject: [PATCH 18/79] Rename fmt test -> hilte --- src/libutil/tests/{fmt.cc => hilite.cc} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/libutil/tests/{fmt.cc => hilite.cc} (100%) diff --git a/src/libutil/tests/fmt.cc b/src/libutil/tests/hilite.cc similarity index 100% rename from src/libutil/tests/fmt.cc rename to src/libutil/tests/hilite.cc From 90b5c0a1a67c31a3f2553fef110417e4ba1b635d Mon Sep 17 00:00:00 2001 From: pennae Date: Sat, 5 Mar 2022 19:26:36 +0100 Subject: [PATCH 19/79] turn primop names into strings we don't *need* symbols here. the only advantage they have over strings is making call-counting slightly faster, but that's a diagnostic feature and thus needn't be optimized. this also fixes a move bug that previously didn't show up: PrimOp structs were accessed after being moved from, which technically invalidates them. previously the names remained valid because Symbol copies on move, but strings are invalidated. we now copy the entire primop struct instead of moving since primop registration happen once and are not performance-sensitive. --- src/libexpr/eval.cc | 14 +++++++------- src/libexpr/eval.hh | 6 +++--- src/libexpr/primops.cc | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index b87e06ef5..a5eb2e473 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -645,14 +645,14 @@ Value * EvalState::addPrimOp(const std::string & name, the primop to a dummy value. */ if (arity == 0) { auto vPrimOp = allocValue(); - vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = sym }); + vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = name2 }); Value v; v.mkApp(vPrimOp, vPrimOp); return addConstant(name, v); } Value * v = allocValue(); - v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym }); + v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 }); staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl); baseEnv.values[baseEnvDispl++] = v; baseEnv.values[0]->attrs->push_back(Attr(sym, v)); @@ -667,21 +667,21 @@ Value * EvalState::addPrimOp(PrimOp && primOp) if (primOp.arity == 0) { primOp.arity = 1; auto vPrimOp = allocValue(); - vPrimOp->mkPrimOp(new PrimOp(std::move(primOp))); + vPrimOp->mkPrimOp(new PrimOp(primOp)); Value v; v.mkApp(vPrimOp, vPrimOp); return addConstant(primOp.name, v); } - Symbol envName = primOp.name; + Symbol envName = symbols.create(primOp.name); if (hasPrefix(primOp.name, "__")) - primOp.name = symbols.create(std::string(primOp.name, 2)); + primOp.name = primOp.name.substr(2); Value * v = allocValue(); - v->mkPrimOp(new PrimOp(std::move(primOp))); + v->mkPrimOp(new PrimOp(primOp)); staticBaseEnv.vars.emplace_back(envName, baseEnvDispl); baseEnv.values[baseEnvDispl++] = v; - baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v)); + baseEnv.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v)); return v; } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 7ed376e8d..7c39e3704 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -30,7 +30,7 @@ struct PrimOp { PrimOpFun fun; size_t arity; - Symbol name; + std::string name; std::vector args; const char * doc = nullptr; }; @@ -305,7 +305,7 @@ public: struct Doc { Pos pos; - std::optional name; + std::optional name; size_t arity; std::vector args; const char * doc; @@ -391,7 +391,7 @@ private: bool countCalls; - typedef std::map PrimOpCalls; + typedef std::map PrimOpCalls; PrimOpCalls primOpCalls; typedef std::map FunctionCalls; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 73817dbdd..3ebc2f1df 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -3906,7 +3906,7 @@ void EvalState::createBaseEnv() addPrimOp({ .fun = primOp.fun, .arity = std::max(primOp.args.size(), primOp.arity), - .name = symbols.create(primOp.name), + .name = primOp.name, .args = primOp.args, .doc = primOp.doc, }); From ff0fd91ed23ae9d851bf27c4df3ec77f7028699b Mon Sep 17 00:00:00 2001 From: pennae Date: Sat, 5 Mar 2022 14:20:25 +0100 Subject: [PATCH 20/79] remove Symbol::empty the only use of this function is to determine whether a lambda has a non-set formal, but this use is arguably better served by Symbol::set and using a non-Symbol instead of an empty symbol in the parser when no such formal is present. --- src/libexpr/eval.cc | 4 ++-- src/libexpr/nixexpr.cc | 8 ++++---- src/libexpr/parser.y | 2 +- src/libexpr/symbol-table.hh | 5 ----- src/libexpr/value-to-xml.cc | 2 +- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index a5eb2e473..b39b24227 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1342,7 +1342,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & ExprLambda & lambda(*vCur.lambda.fun); auto size = - (lambda.arg.empty() ? 0 : 1) + + (!lambda.arg.set() ? 0 : 1) + (lambda.hasFormals() ? lambda.formals->formals.size() : 0); Env & env2(allocEnv(size)); env2.up = vCur.lambda.env; @@ -1355,7 +1355,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & else { forceAttrs(*args[0], pos); - if (!lambda.arg.empty()) + if (lambda.arg.set()) env2.values[displ++] = args[0]; /* For each formal argument, get the actual argument. If diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index a2def65a6..bf01935a9 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -144,9 +144,9 @@ void ExprLambda::show(std::ostream & str) const str << "..."; } str << " }"; - if (!arg.empty()) str << " @ "; + if (arg.set()) str << " @ "; } - if (!arg.empty()) str << arg; + if (arg.set()) str << arg; str << ": " << *body << ")"; } @@ -364,11 +364,11 @@ void ExprLambda::bindVars(const StaticEnv & env) StaticEnv newEnv( false, &env, (hasFormals() ? formals->formals.size() : 0) + - (arg.empty() ? 0 : 1)); + (!arg.set() ? 0 : 1)); Displacement displ = 0; - if (!arg.empty()) newEnv.vars.emplace_back(arg, displ++); + if (arg.set()) newEnv.vars.emplace_back(arg, displ++); if (hasFormals()) { for (auto & i : formals->formals) diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 919b9cfae..49c401603 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -369,7 +369,7 @@ expr_function : ID ':' expr_function { $$ = new ExprLambda(CUR_POS, data->symbols.create($1), 0, $3); } | '{' formals '}' ':' expr_function - { $$ = new ExprLambda(CUR_POS, data->symbols.create(""), toFormals(*data, $2), $5); } + { $$ = new ExprLambda(CUR_POS, {}, toFormals(*data, $2), $5); } | '{' formals '}' '@' ID ':' expr_function { Symbol arg = data->symbols.create($5); diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh index 48d20c29d..297605295 100644 --- a/src/libexpr/symbol-table.hh +++ b/src/libexpr/symbol-table.hh @@ -60,11 +60,6 @@ public: return s; } - bool empty() const - { - return s->empty(); - } - friend std::ostream & operator << (std::ostream & str, const Symbol & sym); }; diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc index afeaf5694..7f8edcba6 100644 --- a/src/libexpr/value-to-xml.cc +++ b/src/libexpr/value-to-xml.cc @@ -139,7 +139,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, if (v.lambda.fun->hasFormals()) { XMLAttrs attrs; - if (!v.lambda.fun->arg.empty()) attrs["name"] = v.lambda.fun->arg; + if (v.lambda.fun->arg.set()) attrs["name"] = v.lambda.fun->arg; if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1"; XMLOpenElement _(doc, "attrspat", attrs); for (auto & i : v.lambda.fun->formals->lexicographicOrder()) From 38de79fcf7e00187107e638036c010911d1b675b Mon Sep 17 00:00:00 2001 From: pennae Date: Fri, 4 Mar 2022 19:47:32 +0100 Subject: [PATCH 21/79] remove Bindings::need a future commit will remove the ability to convert the symbol type used in bindings to strings. since we only have two users we can inline the error check. --- src/libexpr/attr-set.hh | 12 ------------ src/nix/bundle.cc | 6 ++++-- src/nix/prefetch.cc | 10 ++++++---- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh index cad9743ea..1e6c548c6 100644 --- a/src/libexpr/attr-set.hh +++ b/src/libexpr/attr-set.hh @@ -73,18 +73,6 @@ public: return nullptr; } - Attr & need(const Symbol & name, const Pos & pos = noPos) - { - auto a = get(name); - if (!a) - throw Error({ - .msg = hintfmt("attribute '%s' missing", name), - .errPos = pos - }); - - return *a; - } - iterator begin() { return &attrs[0]; } iterator end() { return &attrs[size_]; } diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc index 81fb8464a..ee91e8ed0 100644 --- a/src/nix/bundle.cc +++ b/src/nix/bundle.cc @@ -110,8 +110,10 @@ struct CmdBundle : InstallableCommand auto outPathS = store->printStorePath(outPath); if (!outLink) { - auto &attr = vRes->attrs->need(evalState->sName); - outLink = evalState->forceStringNoCtx(*attr.value,*attr.pos); + auto * attr = vRes->attrs->get(evalState->sName); + if (!attr) + throw Error("attribute 'name' missing"); + outLink = evalState->forceStringNoCtx(*attr->value, *attr->pos); } // TODO: will crash if not a localFSStore? diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index f2dd44ba4..ce3288dc1 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -199,11 +199,13 @@ static int main_nix_prefetch_url(int argc, char * * argv) state->forceAttrs(v, noPos); /* Extract the URL. */ - auto & attr = v.attrs->need(state->symbols.create("urls")); - state->forceList(*attr.value, noPos); - if (attr.value->listSize() < 1) + auto * attr = v.attrs->get(state->symbols.create("urls")); + if (!attr) + throw Error("attribute 'urls' missing"); + state->forceList(*attr->value, noPos); + if (attr->value->listSize() < 1) throw Error("'urls' list is empty"); - url = state->forceString(*attr.value->listElems()[0]); + url = state->forceString(*attr->value->listElems()[0]); /* Extract the hash mode. */ auto attr2 = v.attrs->get(state->symbols.create("outputHashMode")); From 39df15fb8e766c0a4fa2fda83784fb8a478a766c Mon Sep 17 00:00:00 2001 From: pennae Date: Fri, 4 Mar 2022 20:54:50 +0100 Subject: [PATCH 22/79] don't use full Pos for findPackageFilename/editorFor only file and line of the returned position were ever used, it wasn't actually used a position. as such we may as well use a path+int pair for only those two values and remove a use of Pos that would not work well with a position table. --- src/libcmd/command.cc | 8 ++++---- src/libcmd/command.hh | 2 +- src/libexpr/attr-path.cc | 6 ++---- src/libexpr/attr-path.hh | 2 +- src/nix/edit.cc | 16 ++++++++-------- src/nix/repl.cc | 26 +++++++++++++------------- 6 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index a53b029b7..f28cfe5de 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -197,17 +197,17 @@ void StorePathCommand::run(ref store, std::vector && 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(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; } diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 84bbb5292..078e2a2ce 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -219,7 +219,7 @@ static RegisterCommand registerCommand2(std::vector && 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 { diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 32deecfae..c6e3a9c92 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -106,7 +106,7 @@ std::pair findAlongAttrPath(EvalState & state, const std::string & } -Pos findPackageFilename(EvalState & state, Value & v, std::string what) +std::pair findPackageFilename(EvalState & state, Value & v, std::string what) { Value * v2; try { @@ -132,9 +132,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what) throw ParseError("cannot parse line number '%s'", pos); } - Symbol file = state.symbols.create(filename); - - return { foFile, file, lineno, 0 }; + return { std::move(filename), lineno }; } diff --git a/src/libexpr/attr-path.hh b/src/libexpr/attr-path.hh index ff1135a06..f06d28f7f 100644 --- a/src/libexpr/attr-path.hh +++ b/src/libexpr/attr-path.hh @@ -17,7 +17,7 @@ std::pair findAlongAttrPath( Value & vIn); /* Heuristic to find the filename and lineno or a nix value. */ -Pos findPackageFilename(EvalState & state, Value & v, std::string what); +std::pair findPackageFilename(EvalState & state, Value & v, std::string what); std::vector parseAttrPath(EvalState & state, std::string_view s); diff --git a/src/nix/edit.cc b/src/nix/edit.cc index fc48db0d7..ffe79af89 100644 --- a/src/nix/edit.cc +++ b/src/nix/edit.cc @@ -30,17 +30,17 @@ struct CmdEdit : InstallableCommand auto [v, pos] = installable->toValue(*state); - try { - pos = findPackageFilename(*state, *v, installable->what()); - } catch (NoPositionInfo &) { - } - - if (pos == noPos) - throw Error("cannot find position information for '%s", installable->what()); + const auto [file, line] = [&] { + try { + return findPackageFilename(*state, *v, installable->what()); + } catch (NoPositionInfo &) { + throw Error("cannot find position information for '%s", installable->what()); + } + }(); stopProgressBar(); - auto args = editorFor(pos); + auto args = editorFor(file, line); restoreProcessContext(); diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 0eb037858..391255ce9 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -461,21 +461,21 @@ bool NixRepl::processLine(std::string line) Value v; evalString(arg, v); - Pos pos; - - if (v.type() == nPath || v.type() == nString) { - PathSet context; - auto filename = state->coerceToString(noPos, v, context); - pos.file = state->symbols.create(*filename); - } else if (v.isLambda()) { - pos = v.lambda.fun->pos; - } else { - // assume it's a derivation - pos = findPackageFilename(*state, v, arg); - } + const auto [file, line] = [&] () -> std::pair { + if (v.type() == nPath || v.type() == nString) { + PathSet context; + auto filename = state->coerceToString(noPos, v, context); + return {state->symbols.create(*filename), 0}; + } else if (v.isLambda()) { + return {v.lambda.fun->pos.file, v.lambda.fun->pos.line}; + } else { + // assume it's a derivation + return findPackageFilename(*state, v, arg); + } + }(); // Open in EDITOR - auto args = editorFor(pos); + auto args = editorFor(file, line); auto editor = args.front(); args.pop_front(); From 34b72775cfe755db1bc61cb950c25759c0694be4 Mon Sep 17 00:00:00 2001 From: pennae Date: Mon, 7 Mar 2022 21:02:17 +0100 Subject: [PATCH 23/79] make throw*Error member functions of EvalState when we introduce position and symbol tables we'll need to do lookups to turn indices into those tables into actual positions/symbols. having the error functions as members of EvalState will avoid a lot of churn for adding lookups into the tables for each caller. --- src/libexpr/eval-inline.hh | 19 ----------- src/libexpr/eval.cc | 65 +++++++++++++++++++++++++------------- src/libexpr/eval.hh | 39 +++++++++++++++++++++++ 3 files changed, 82 insertions(+), 41 deletions(-) diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index 08a419923..dec122462 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -2,27 +2,8 @@ #include "eval.hh" -#define LocalNoInline(f) static f __attribute__((noinline)); f -#define LocalNoInlineNoReturn(f) static f __attribute__((noinline, noreturn)); f - namespace nix { -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s)) -{ - throw EvalError({ - .msg = hintfmt(s), - .errPos = pos - }); -} - -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v)) -{ - throw TypeError({ - .msg = hintfmt(s, showType(v)), - .errPos = pos - }); -} - /* Note: Various places expect the allocated memory to be zeroed. */ [[gnu::always_inline]] diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index b39b24227..418017357 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -714,12 +714,30 @@ std::optional EvalState::getDoc(Value & v) evaluator. So here are some helper functions for throwing exceptions. */ -LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2)) +void EvalState::throwEvalError(const Pos & pos, const char * s) const +{ + throw EvalError({ + .msg = hintfmt(s), + .errPos = pos + }); +} + +void EvalState::throwTypeError(const Pos & pos, const char * s, const Value & v) const +{ + throw TypeError({ + .msg = hintfmt(s, showType(v)), + .errPos = pos + }); + +} + +void EvalState::throwEvalError(const char * s, const std::string & s2) const { throw EvalError(s, s2); } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2)) +void EvalState::throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, + const std::string & s2) const { throw EvalError(ErrorInfo { .msg = hintfmt(s, s2), @@ -728,7 +746,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & s }); } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2)) +void EvalState::throwEvalError(const Pos & pos, const char * s, const std::string & s2) const { throw EvalError(ErrorInfo { .msg = hintfmt(s, s2), @@ -736,12 +754,13 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const }); } -LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2, const std::string & s3)) +void EvalState::throwEvalError(const char * s, const std::string & s2, const std::string & s3) const { throw EvalError(s, s2, s3); } -LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3)) +void EvalState::throwEvalError(const Pos & pos, const char * s, const std::string & s2, + const std::string & s3) const { throw EvalError({ .msg = hintfmt(s, s2, s3), @@ -749,7 +768,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const }); } -LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2)) +void EvalState::throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2) const { // p1 is where the error occurred; p2 is a position mentioned in the message. throw EvalError({ @@ -758,7 +777,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const }); } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s)) +void EvalState::throwTypeError(const Pos & pos, const char * s) const { throw TypeError({ .msg = hintfmt(s), @@ -766,7 +785,8 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s)) }); } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2)) +void EvalState::throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, + const Symbol & s2) const { throw TypeError({ .msg = hintfmt(s, fun.showNamePos(), s2), @@ -774,7 +794,8 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const }); } -LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2)) +void EvalState::throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, + const ExprLambda & fun, const Symbol & s2) const { throw TypeError(ErrorInfo { .msg = hintfmt(s, fun.showNamePos(), s2), @@ -784,12 +805,12 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & s } -LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) +void EvalState::throwTypeError(const char * s, const Value & v) const { throw TypeError(s, showType(v)); } -LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const std::string & s1)) +void EvalState::throwAssertionError(const Pos & pos, const char * s, const std::string & s1) const { throw AssertionError({ .msg = hintfmt(s, s1), @@ -797,7 +818,7 @@ LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, }); } -LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1)) +void EvalState::throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1) const { throw UndefinedVarError({ .msg = hintfmt(s, s1), @@ -805,7 +826,7 @@ LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * }); } -LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1)) +void EvalState::throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1) const { throw MissingArgumentError({ .msg = hintfmt(s, s1), @@ -813,12 +834,12 @@ LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char }); } -LocalNoInline(void addErrorTrace(Error & e, const char * s, const std::string & s2)) +void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const { e.addTrace(std::nullopt, s, s2); } -LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, const std::string & s2)) +void EvalState::addErrorTrace(Error & e, const Pos & pos, const char * s, const std::string & s2) const { e.addTrace(pos, s, s2); } @@ -1169,7 +1190,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) Symbol nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos); + state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos); i.valueExpr->setName(nameSym); /* Keep sorted order so find can catch duplicates */ @@ -1260,7 +1281,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) std::set allAttrNames; for (auto & attr : *vAttrs->attrs) allAttrNames.insert(attr.name); - throwEvalError( + state.throwEvalError( pos, Suggestions::bestMatches(allAttrNames, name), "attribute '%1%' missing", name); @@ -1275,7 +1296,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) } catch (Error & e) { if (*pos2 != noPos && pos2->file != state.sDerivationNix) - addErrorTrace(e, *pos2, "while evaluating the attribute '%1%'", + state.addErrorTrace(e, *pos2, "while evaluating the attribute '%1%'", showAttrPath(state, env, attrPath)); throw; } @@ -1587,7 +1608,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) if (!state.evalBool(env, cond, pos)) { std::ostringstream out; cond->show(out); - throwAssertionError(pos, "assertion '%1%' failed", out.str()); + state.throwAssertionError(pos, "assertion '%1%' failed", out.str()); } body->eval(state, env, v); } @@ -1764,14 +1785,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) nf = n; nf += vTmp.fpoint; } else - throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp)); + state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp)); } else if (firstType == nFloat) { if (vTmp.type() == nInt) { nf += vTmp.integer; } else if (vTmp.type() == nFloat) { nf += vTmp.fpoint; } else - throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp)); + state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp)); } else { if (s.empty()) s.reserve(es->size()); /* skip canonization of first path, which would only be not @@ -1791,7 +1812,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) v.mkFloat(nf); else if (firstType == nPath) { if (!context.empty()) - throwEvalError(pos, "a string that refers to a store path cannot be appended to a path"); + state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path"); v.mkPath(canonPath(str())); } else v.mkStringMove(c_str(), context); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 7c39e3704..787294b89 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -249,6 +249,45 @@ public: std::string_view forceString(Value & v, PathSet & context, const Pos & pos = noPos); std::string_view forceStringNoCtx(Value & v, const Pos & pos = noPos); + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const Pos & pos, const char * s) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const Pos & pos, const char * s, const Value & v) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const char * s, const std::string & s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, + const std::string & s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const Pos & pos, const char * s, const std::string & s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const char * s, const std::string & s2, const std::string & s3) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3) const; + [[gnu::noinline, gnu::noreturn]] + void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const Pos & pos, const char * s) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, + const ExprLambda & fun, const Symbol & s2) const; + [[gnu::noinline, gnu::noreturn]] + void throwTypeError(const char * s, const Value & v) const; + [[gnu::noinline, gnu::noreturn]] + void throwAssertionError(const Pos & pos, const char * s, const std::string & s1) const; + [[gnu::noinline, gnu::noreturn]] + void throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1) const; + [[gnu::noinline, gnu::noreturn]] + void throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1) const; + + [[gnu::noinline]] + void addErrorTrace(Error & e, const char * s, const std::string & s2) const; + [[gnu::noinline]] + void addErrorTrace(Error & e, const Pos & pos, const char * s, const std::string & s2) const; + +public: /* Return true iff the value `v' denotes a derivation (i.e. a set with attribute `type = "derivation"'). */ bool isDerivation(Value & v); From 6526d1676ba5a645f65d751e7529ccd273579017 Mon Sep 17 00:00:00 2001 From: pennae Date: Fri, 4 Mar 2022 19:31:59 +0100 Subject: [PATCH 24/79] replace most Pos objects/ptrs with indexes into a position table Pos objects are somewhat wasteful as they duplicate the origin file name and input type for each object. on files that produce more than one Pos when parsed this a sizeable waste of memory (one pointer per Pos). the same goes for ptr on 64 bit machines: parsing enough source to require 8 bytes to locate a position would need at least 8GB of input and 64GB of expression memory. it's not likely that we'll hit that any time soon, so we can use a uint32_t index to locate positions instead. --- src/libcmd/installables.cc | 4 +- src/libcmd/installables.hh | 4 +- src/libexpr/attr-path.cc | 6 +- src/libexpr/attr-path.hh | 2 +- src/libexpr/attr-set.cc | 4 +- src/libexpr/attr-set.hh | 16 +- src/libexpr/eval-inline.hh | 6 +- src/libexpr/eval.cc | 157 ++++++------- src/libexpr/eval.hh | 77 +++---- src/libexpr/flake/flake.cc | 56 ++--- src/libexpr/function-trace.hh | 2 +- src/libexpr/get-drvs.cc | 18 +- src/libexpr/lexer.l | 10 +- src/libexpr/nixexpr.cc | 96 ++++---- src/libexpr/nixexpr.hh | 169 +++++++++----- src/libexpr/parser.y | 78 +++---- src/libexpr/primops.cc | 309 +++++++++++++------------- src/libexpr/primops.hh | 4 +- src/libexpr/primops/context.cc | 26 +-- src/libexpr/primops/fetchClosure.cc | 26 +-- src/libexpr/primops/fetchMercurial.cc | 12 +- src/libexpr/primops/fetchTree.cc | 36 +-- src/libexpr/primops/fromTOML.cc | 4 +- src/libexpr/value-to-json.cc | 12 +- src/libexpr/value-to-json.hh | 4 +- src/libexpr/value-to-xml.cc | 18 +- src/libexpr/value-to-xml.hh | 2 +- src/libexpr/value.hh | 5 +- src/libutil/types.hh | 52 +++++ src/nix-env/user-env.cc | 4 +- src/nix/bundle.cc | 6 +- src/nix/edit.cc | 4 +- src/nix/eval.cc | 12 +- src/nix/flake.cc | 122 +++++----- src/nix/repl.cc | 9 +- tests/plugins/plugintest.cc | 2 +- 36 files changed, 752 insertions(+), 622 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 4250c321e..6197f4be4 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -473,7 +473,7 @@ struct InstallableAttrPath : InstallableValue std::string what() const override { return attrPath; } - std::pair toValue(EvalState & state) override + std::pair toValue(EvalState & state) override { auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v); state.forceValue(*vRes, pos); @@ -613,7 +613,7 @@ std::vector InstallableFlake::toDerivations() return res; } -std::pair InstallableFlake::toValue(EvalState & state) +std::pair InstallableFlake::toValue(EvalState & state) { return {&getCursor(state)->forceValue(), noPos}; } diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index b847f8939..de8b08525 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -68,7 +68,7 @@ struct Installable UnresolvedApp toApp(EvalState & state); - virtual std::pair toValue(EvalState & state) + virtual std::pair toValue(EvalState & state) { throw Error("argument '%s' cannot be evaluated", what()); } @@ -178,7 +178,7 @@ struct InstallableFlake : InstallableValue std::vector toDerivations() override; - std::pair toValue(EvalState & state) override; + std::pair toValue(EvalState & state) override; /* Get a cursor to every attrpath in getActualAttrPaths() that exists. */ diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index c6e3a9c92..1c12dfbe2 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -41,13 +41,13 @@ std::vector parseAttrPath(EvalState & state, std::string_view s) } -std::pair findAlongAttrPath(EvalState & state, const std::string & attrPath, +std::pair findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn) { Strings tokens = parseAttrPath(attrPath); Value * v = &vIn; - Pos pos = noPos; + PosIdx pos = noPos; for (auto & attr : tokens) { @@ -83,7 +83,7 @@ std::pair findAlongAttrPath(EvalState & state, const std::string & throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath); } v = &*a->value; - pos = *a->pos; + pos = a->pos; } else { diff --git a/src/libexpr/attr-path.hh b/src/libexpr/attr-path.hh index f06d28f7f..117e0051b 100644 --- a/src/libexpr/attr-path.hh +++ b/src/libexpr/attr-path.hh @@ -10,7 +10,7 @@ namespace nix { MakeError(AttrPathNotFound, Error); MakeError(NoPositionInfo, Error); -std::pair findAlongAttrPath( +std::pair findAlongAttrPath( EvalState & state, const std::string & attrPath, Bindings & autoArgs, diff --git a/src/libexpr/attr-set.cc b/src/libexpr/attr-set.cc index 52ac47e9b..61996eae4 100644 --- a/src/libexpr/attr-set.cc +++ b/src/libexpr/attr-set.cc @@ -40,7 +40,7 @@ Value * EvalState::allocAttr(Value & vAttrs, std::string_view name) } -Value & BindingsBuilder::alloc(const Symbol & name, ptr pos) +Value & BindingsBuilder::alloc(const Symbol & name, PosIdx pos) { auto value = state.allocValue(); bindings->push_back(Attr(name, value, pos)); @@ -48,7 +48,7 @@ Value & BindingsBuilder::alloc(const Symbol & name, ptr pos) } -Value & BindingsBuilder::alloc(std::string_view name, ptr pos) +Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos) { return alloc(state.symbols.create(name), pos); } diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh index 1e6c548c6..23d68dda2 100644 --- a/src/libexpr/attr-set.hh +++ b/src/libexpr/attr-set.hh @@ -17,10 +17,10 @@ struct Attr { Symbol name; Value * value; - ptr pos; - Attr(Symbol name, Value * value, ptr pos = ptr(&noPos)) + PosIdx pos; + Attr(Symbol name, Value * value, PosIdx pos = noPos) : name(name), value(value), pos(pos) { }; - Attr() : pos(&noPos) { }; + Attr() { }; bool operator < (const Attr & a) const { return name < a.name; @@ -35,13 +35,13 @@ class Bindings { public: typedef uint32_t size_t; - ptr pos; + PosIdx pos; private: size_t size_, capacity_; Attr attrs[0]; - Bindings(size_t capacity) : pos(&noPos), size_(0), capacity_(capacity) { } + Bindings(size_t capacity) : size_(0), capacity_(capacity) { } Bindings(const Bindings & bindings) = delete; public: @@ -118,7 +118,7 @@ public: : bindings(bindings), state(state) { } - void insert(Symbol name, Value * value, ptr pos = ptr(&noPos)) + void insert(Symbol name, Value * value, PosIdx pos = noPos) { insert(Attr(name, value, pos)); } @@ -133,9 +133,9 @@ public: bindings->push_back(attr); } - Value & alloc(const Symbol & name, ptr pos = ptr(&noPos)); + Value & alloc(const Symbol & name, PosIdx pos = noPos); - Value & alloc(std::string_view name, ptr pos = ptr(&noPos)); + Value & alloc(std::string_view name, PosIdx pos = noPos); Bindings * finish() { diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh index dec122462..7f01d08e3 100644 --- a/src/libexpr/eval-inline.hh +++ b/src/libexpr/eval-inline.hh @@ -80,7 +80,7 @@ Env & EvalState::allocEnv(size_t size) [[gnu::always_inline]] -void EvalState::forceValue(Value & v, const Pos & pos) +void EvalState::forceValue(Value & v, const PosIdx pos) { forceValue(v, [&]() { return pos; }); } @@ -109,7 +109,7 @@ void EvalState::forceValue(Value & v, Callable getPos) [[gnu::always_inline]] -inline void EvalState::forceAttrs(Value & v, const Pos & pos) +inline void EvalState::forceAttrs(Value & v, const PosIdx pos) { forceAttrs(v, [&]() { return pos; }); } @@ -126,7 +126,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos) [[gnu::always_inline]] -inline void EvalState::forceList(Value & v, const Pos & pos) +inline void EvalState::forceList(Value & v, const PosIdx pos) { forceValue(v, pos); if (!v.isList()) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 418017357..e6314f63e 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -236,10 +236,10 @@ std::string showType(const Value & v) } } -Pos Value::determinePos(const Pos & pos) const +PosIdx Value::determinePos(const PosIdx pos) const { switch (internalType) { - case tAttrs: return *attrs->pos; + case tAttrs: return attrs->pos; case tLambda: return lambda.fun->pos; case tApp: return app.left->determinePos(pos); default: return pos; @@ -698,7 +698,7 @@ std::optional EvalState::getDoc(Value & v) auto v2 = &v; if (v2->primOp->doc) return Doc { - .pos = noPos, + .pos = {}, .name = v2->primOp->name, .arity = v2->primOp->arity, .args = v2->primOp->args, @@ -714,21 +714,20 @@ std::optional EvalState::getDoc(Value & v) evaluator. So here are some helper functions for throwing exceptions. */ -void EvalState::throwEvalError(const Pos & pos, const char * s) const +void EvalState::throwEvalError(const PosIdx pos, const char * s) const { throw EvalError({ .msg = hintfmt(s), - .errPos = pos + .errPos = positions[pos] }); } -void EvalState::throwTypeError(const Pos & pos, const char * s, const Value & v) const +void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v) const { throw TypeError({ .msg = hintfmt(s, showType(v)), - .errPos = pos + .errPos = positions[pos] }); - } void EvalState::throwEvalError(const char * s, const std::string & s2) const @@ -736,21 +735,21 @@ void EvalState::throwEvalError(const char * s, const std::string & s2) const throw EvalError(s, s2); } -void EvalState::throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, +void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2) const { throw EvalError(ErrorInfo { .msg = hintfmt(s, s2), - .errPos = pos, + .errPos = positions[pos], .suggestions = suggestions, }); } -void EvalState::throwEvalError(const Pos & pos, const char * s, const std::string & s2) const +void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const { throw EvalError(ErrorInfo { .msg = hintfmt(s, s2), - .errPos = pos + .errPos = positions[pos] }); } @@ -759,47 +758,47 @@ void EvalState::throwEvalError(const char * s, const std::string & s2, const std throw EvalError(s, s2, s3); } -void EvalState::throwEvalError(const Pos & pos, const char * s, const std::string & s2, +void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const { throw EvalError({ .msg = hintfmt(s, s2, s3), - .errPos = pos + .errPos = positions[pos] }); } -void EvalState::throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2) const +void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol & sym, const PosIdx p2) const { // p1 is where the error occurred; p2 is a position mentioned in the message. throw EvalError({ - .msg = hintfmt(s, sym, p2), - .errPos = p1 + .msg = hintfmt(s, sym, positions[p2]), + .errPos = positions[p1] }); } -void EvalState::throwTypeError(const Pos & pos, const char * s) const +void EvalState::throwTypeError(const PosIdx pos, const char * s) const { throw TypeError({ .msg = hintfmt(s), - .errPos = pos + .errPos = positions[pos] }); } -void EvalState::throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, +void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol & s2) const { throw TypeError({ - .msg = hintfmt(s, fun.showNamePos(), s2), - .errPos = pos + .msg = hintfmt(s, fun.showNamePos(positions), s2), + .errPos = positions[pos] }); } -void EvalState::throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, +void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2) const { throw TypeError(ErrorInfo { - .msg = hintfmt(s, fun.showNamePos(), s2), - .errPos = pos, + .msg = hintfmt(s, fun.showNamePos(positions), s2), + .errPos = positions[pos], .suggestions = suggestions, }); } @@ -810,27 +809,27 @@ void EvalState::throwTypeError(const char * s, const Value & v) const throw TypeError(s, showType(v)); } -void EvalState::throwAssertionError(const Pos & pos, const char * s, const std::string & s1) const +void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const { throw AssertionError({ .msg = hintfmt(s, s1), - .errPos = pos + .errPos = positions[pos] }); } -void EvalState::throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1) const +void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const { throw UndefinedVarError({ .msg = hintfmt(s, s1), - .errPos = pos + .errPos = positions[pos] }); } -void EvalState::throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1) const +void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const { throw MissingArgumentError({ .msg = hintfmt(s, s1), - .errPos = pos + .errPos = positions[pos] }); } @@ -839,9 +838,9 @@ void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) e.addTrace(std::nullopt, s, s2); } -void EvalState::addErrorTrace(Error & e, const Pos & pos, const char * s, const std::string & s2) const +void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const { - e.addTrace(pos, s, s2); + e.addTrace(positions[pos], s, s2); } @@ -898,7 +897,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) } Bindings::iterator j = env->values[0]->attrs->find(var.name); if (j != env->values[0]->attrs->end()) { - if (countCalls) attrSelects[*j->pos]++; + if (countCalls) attrSelects[j->pos]++; return j->value; } if (!env->prevWith) @@ -932,13 +931,14 @@ void EvalState::mkThunk_(Value & v, Expr * expr) } -void EvalState::mkPos(Value & v, ptr pos) +void EvalState::mkPos(Value & v, PosIdx p) { - if (pos->file.set()) { + auto pos = positions[p]; + if (pos.file.set()) { auto attrs = buildBindings(3); - attrs.alloc(sFile).mkString(pos->file); - attrs.alloc(sLine).mkInt(pos->line); - attrs.alloc(sColumn).mkInt(pos->column); + attrs.alloc(sFile).mkString(pos.file); + attrs.alloc(sLine).mkInt(pos.line); + attrs.alloc(sColumn).mkInt(pos.column); v.mkAttrs(attrs); } else v.mkNull(); @@ -1071,7 +1071,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e) } -inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos) +inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos) { Value v; e->eval(*this, env, v); @@ -1145,7 +1145,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) } else vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2); env2.values[displ++] = vAttr; - v.attrs->push_back(Attr(i.first, vAttr, ptr(&i.second.pos))); + v.attrs->push_back(Attr(i.first, vAttr, i.second.pos)); } /* If the rec contains an attribute called `__overrides', then @@ -1177,7 +1177,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) else for (auto & i : attrs) - v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), ptr(&i.second.pos))); + v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), i.second.pos)); /* Dynamic attrs apply *after* rec and __overrides. */ for (auto & i : dynamicAttrs) { @@ -1190,15 +1190,15 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) Symbol nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) - state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos); + state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos); i.valueExpr->setName(nameSym); /* Keep sorted order so find can catch duplicates */ - v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), ptr(&i.pos))); + v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos)); v.attrs->sort(); // FIXME: inefficient } - v.attrs->pos = ptr(&pos); + v.attrs->pos = pos; } @@ -1256,7 +1256,7 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a void ExprSelect::eval(EvalState & state, Env & env, Value & v) { Value vTmp; - ptr pos2(&noPos); + PosIdx pos2; Value * vAttrs = &vTmp; e->eval(state, env, vTmp); @@ -1289,14 +1289,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) } vAttrs = j->value; pos2 = j->pos; - if (state.countCalls) state.attrSelects[*pos2]++; + if (state.countCalls) state.attrSelects[pos2]++; } - state.forceValue(*vAttrs, (*pos2 != noPos ? *pos2 : this->pos ) ); + state.forceValue(*vAttrs, (pos2 ? pos2 : this->pos ) ); } catch (Error & e) { - if (*pos2 != noPos && pos2->file != state.sDerivationNix) - state.addErrorTrace(e, *pos2, "while evaluating the attribute '%1%'", + auto pos2r = state.positions[pos2]; + if (pos2 && pos2r.file != state.sDerivationNix) + state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'", showAttrPath(state, env, attrPath)); throw; } @@ -1336,9 +1337,11 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v) } -void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos) +void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos) { - auto trace = evalSettings.traceFunctionCalls ? std::make_unique(pos) : nullptr; + auto trace = evalSettings.traceFunctionCalls + ? std::make_unique(positions[pos]) + : nullptr; forceValue(fun, pos); @@ -1701,7 +1704,7 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) } -void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos) +void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos) { nrListConcats++; @@ -1821,7 +1824,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) void ExprPos::eval(EvalState & state, Env & env, Value & v) { - state.mkPos(v, ptr(&pos)); + state.mkPos(v, pos); } @@ -1841,7 +1844,7 @@ void EvalState::forceValueDeep(Value & v) try { recurse(*i.value); } catch (Error & e) { - addErrorTrace(e, *i.pos, "while evaluating the attribute '%1%'", i.name); + addErrorTrace(e, i.pos, "while evaluating the attribute '%1%'", i.name); throw; } } @@ -1856,7 +1859,7 @@ void EvalState::forceValueDeep(Value & v) } -NixInt EvalState::forceInt(Value & v, const Pos & pos) +NixInt EvalState::forceInt(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() != nInt) @@ -1865,7 +1868,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos) } -NixFloat EvalState::forceFloat(Value & v, const Pos & pos) +NixFloat EvalState::forceFloat(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() == nInt) @@ -1876,7 +1879,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos) } -bool EvalState::forceBool(Value & v, const Pos & pos) +bool EvalState::forceBool(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() != nBool) @@ -1891,7 +1894,7 @@ bool EvalState::isFunctor(Value & fun) } -void EvalState::forceFunction(Value & v, const Pos & pos) +void EvalState::forceFunction(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() != nFunction && !isFunctor(v)) @@ -1899,7 +1902,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos) } -std::string_view EvalState::forceString(Value & v, const Pos & pos) +std::string_view EvalState::forceString(Value & v, const PosIdx pos) { forceValue(v, pos); if (v.type() != nString) { @@ -1952,7 +1955,7 @@ NixStringContext Value::getContext(const Store & store) } -std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos) +std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos) { auto s = forceString(v, pos); copyContext(v, context); @@ -1960,7 +1963,7 @@ std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos } -std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos) +std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos) { auto s = forceString(v, pos); if (v.string.context) { @@ -1980,13 +1983,13 @@ bool EvalState::isDerivation(Value & v) if (v.type() != nAttrs) return false; Bindings::iterator i = v.attrs->find(sType); if (i == v.attrs->end()) return false; - forceValue(*i->value, *i->pos); + forceValue(*i->value, i->pos); if (i->value->type() != nString) return false; return strcmp(i->value->string.s, "derivation") == 0; } -std::optional EvalState::tryAttrsToString(const Pos & pos, Value & v, +std::optional EvalState::tryAttrsToString(const PosIdx pos, Value & v, PathSet & context, bool coerceMore, bool copyToStore) { auto i = v.attrs->find(sToString); @@ -1999,7 +2002,7 @@ std::optional EvalState::tryAttrsToString(const Pos & pos, Value & return {}; } -BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, +BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context, bool coerceMore, bool copyToStore, bool canonicalizePath) { forceValue(v, pos); @@ -2028,7 +2031,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & } if (v.type() == nExternal) - return v.external->coerceToString(pos, context, coerceMore, copyToStore); + return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore); if (coerceMore) { @@ -2081,7 +2084,7 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path) } -Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) +Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context) { auto path = coerceToString(pos, v, context, false, false).toOwned(); if (path == "" || path[0] != '/') @@ -2090,14 +2093,14 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) } -StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context) +StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context) { auto path = coerceToString(pos, v, context, false, false).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; throw EvalError({ .msg = hintfmt("path '%1%' is not in the Nix store", path), - .errPos = pos + .errPos = positions[pos] }); } @@ -2268,10 +2271,10 @@ void EvalState::printStats() obj.attr("name", (const std::string &) i.first->name); else obj.attr("name", nullptr); - if (i.first->pos) { - obj.attr("file", (const std::string &) i.first->pos.file); - obj.attr("line", i.first->pos.line); - obj.attr("column", i.first->pos.column); + if (auto pos = positions[i.first->pos]) { + obj.attr("file", (const std::string &) pos.file); + obj.attr("line", pos.line); + obj.attr("column", pos.column); } obj.attr("count", i.second); } @@ -2280,10 +2283,10 @@ void EvalState::printStats() auto list = topObj.list("attributes"); for (auto & i : attrSelects) { auto obj = list.object(); - if (i.first) { - obj.attr("file", (const std::string &) i.first.file); - obj.attr("line", i.first.line); - obj.attr("column", i.first.column); + if (auto pos = positions[i.first]) { + obj.attr("file", (const std::string &) pos.file); + obj.attr("line", pos.line); + obj.attr("column", pos.column); } obj.attr("count", i.second); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 787294b89..b05e8d5d0 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -23,7 +23,7 @@ class StorePath; enum RepairFlag : bool; -typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v); +typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v); struct PrimOp @@ -73,6 +73,7 @@ class EvalState { public: SymbolTable symbols; + PosTable positions; const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, @@ -205,7 +206,7 @@ public: /* Look up a file in the search path. */ Path findFile(const std::string_view path); - Path findFile(SearchPath & searchPath, const std::string_view path, const Pos & pos = noPos); + Path findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos); /* If the specified search path element is a URI, download it. */ std::pair resolveSearchPathElem(const SearchPathElem & elem); @@ -217,14 +218,14 @@ public: /* Evaluation the expression, then verify that it has the expected type. */ inline bool evalBool(Env & env, Expr * e); - inline bool evalBool(Env & env, Expr * e, const Pos & pos); + inline bool evalBool(Env & env, Expr * e, const PosIdx pos); inline void evalAttrs(Env & env, Expr * e, Value & v); /* If `v' is a thunk, enter it and overwrite `v' with the result of the evaluation of the thunk. If `v' is a delayed function application, call the function and overwrite `v' with the result. Otherwise, this is a no-op. */ - inline void forceValue(Value & v, const Pos & pos); + inline void forceValue(Value & v, const PosIdx pos); template inline void forceValue(Value & v, Callable getPos); @@ -234,72 +235,72 @@ public: void forceValueDeep(Value & v); /* Force `v', and then verify that it has the expected type. */ - NixInt forceInt(Value & v, const Pos & pos); - NixFloat forceFloat(Value & v, const Pos & pos); - bool forceBool(Value & v, const Pos & pos); + NixInt forceInt(Value & v, const PosIdx pos); + NixFloat forceFloat(Value & v, const PosIdx pos); + bool forceBool(Value & v, const PosIdx pos); - void forceAttrs(Value & v, const Pos & pos); + void forceAttrs(Value & v, const PosIdx pos); template inline void forceAttrs(Value & v, Callable getPos); - inline void forceList(Value & v, const Pos & pos); - void forceFunction(Value & v, const Pos & pos); // either lambda or primop - std::string_view forceString(Value & v, const Pos & pos = noPos); - std::string_view forceString(Value & v, PathSet & context, const Pos & pos = noPos); - std::string_view forceStringNoCtx(Value & v, const Pos & pos = noPos); + inline void forceList(Value & v, const PosIdx pos); + void forceFunction(Value & v, const PosIdx pos); // either lambda or primop + std::string_view forceString(Value & v, const PosIdx pos = noPos); + std::string_view forceString(Value & v, PathSet & context, const PosIdx pos = noPos); + std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos); [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const Pos & pos, const char * s) const; + void throwEvalError(const PosIdx pos, const char * s) const; [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const Pos & pos, const char * s, const Value & v) const; + void throwTypeError(const PosIdx pos, const char * s, const Value & v) const; [[gnu::noinline, gnu::noreturn]] void throwEvalError(const char * s, const std::string & s2) const; [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, + void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2) const; [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const Pos & pos, const char * s, const std::string & s2) const; + void throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const; [[gnu::noinline, gnu::noreturn]] void throwEvalError(const char * s, const std::string & s2, const std::string & s3) const; [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3) const; + void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const; [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2) const; + void throwEvalError(const PosIdx p1, const char * s, const Symbol & sym, const PosIdx p2) const; [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const Pos & pos, const char * s) const; + void throwTypeError(const PosIdx pos, const char * s) const; [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2) const; + void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol & s2) const; [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, + void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2) const; [[gnu::noinline, gnu::noreturn]] void throwTypeError(const char * s, const Value & v) const; [[gnu::noinline, gnu::noreturn]] - void throwAssertionError(const Pos & pos, const char * s, const std::string & s1) const; + void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const; [[gnu::noinline, gnu::noreturn]] - void throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1) const; + void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const; [[gnu::noinline, gnu::noreturn]] - void throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1) const; + void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const; [[gnu::noinline]] void addErrorTrace(Error & e, const char * s, const std::string & s2) const; [[gnu::noinline]] - void addErrorTrace(Error & e, const Pos & pos, const char * s, const std::string & s2) const; + void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const; public: /* Return true iff the value `v' denotes a derivation (i.e. a set with attribute `type = "derivation"'). */ bool isDerivation(Value & v); - std::optional tryAttrsToString(const Pos & pos, Value & v, + std::optional tryAttrsToString(const PosIdx pos, Value & v, PathSet & context, bool coerceMore = false, bool copyToStore = true); /* String coercion. Converts strings, paths and derivations to a string. If `coerceMore' is set, also converts nulls, integers, booleans and lists to a string. If `copyToStore' is set, referenced paths are copied to the Nix store as a side effect. */ - BackedStringView coerceToString(const Pos & pos, Value & v, PathSet & context, + BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context, bool coerceMore = false, bool copyToStore = true, bool canonicalizePath = true); @@ -308,10 +309,10 @@ public: /* Path coercion. Converts strings, paths and derivations to a path. The result is guaranteed to be a canonicalised, absolute path. Nothing is copied to the store. */ - Path coerceToPath(const Pos & pos, Value & v, PathSet & context); + Path coerceToPath(const PosIdx pos, Value & v, PathSet & context); /* Like coerceToPath, but the result must be a store path. */ - StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context); + StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context); public: @@ -372,9 +373,9 @@ public: bool isFunctor(Value & fun); // FIXME: use std::span - void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos); + void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos); - void callFunction(Value & fun, Value & arg, Value & vRes, const Pos & pos) + void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos) { Value * args[] = {&arg}; callFunction(fun, 1, args, vRes, pos); @@ -400,9 +401,9 @@ public: void mkList(Value & v, size_t length); void mkThunk_(Value & v, Expr * expr); - void mkPos(Value & v, ptr pos); + void mkPos(Value & v, PosIdx pos); - void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos); + void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos); /* Print statistics. */ void printStats(); @@ -438,7 +439,7 @@ private: void incrFunctionCall(ExprLambda * fun); - typedef std::map AttrSelects; + typedef std::map AttrSelects; AttrSelects attrSelects; friend struct ExprOpUpdate; @@ -449,9 +450,9 @@ private: friend struct ExprFloat; friend struct ExprPath; friend struct ExprSelect; - friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v); - friend void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v); - friend void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v); + friend void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v); + friend void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v); + friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v); friend struct Value; }; diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 22257c6b3..44fb8317a 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -72,7 +72,7 @@ static std::tuple fetchOrSubstituteTree( return {std::move(tree), resolvedRef, lockedRef}; } -static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos) +static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos) { if (value.isThunk() && value.isTrivial()) state.forceValue(value, pos); @@ -80,20 +80,20 @@ static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos) static void expectType(EvalState & state, ValueType type, - Value & value, const Pos & pos) + Value & value, const PosIdx pos) { forceTrivialValue(state, value, pos); if (value.type() != type) throw Error("expected %s but got %s at %s", - showType(type), showType(value.type()), pos); + showType(type), showType(value.type()), state.positions[pos]); } static std::map parseFlakeInputs( - EvalState & state, Value * value, const Pos & pos, + EvalState & state, Value * value, const PosIdx pos, const std::optional & baseDir, InputPath lockRootPath); static FlakeInput parseFlakeInput(EvalState & state, - const std::string & inputName, Value * value, const Pos & pos, + const std::string & inputName, Value * value, const PosIdx pos, const std::optional & baseDir, InputPath lockRootPath) { expectType(state, nAttrs, *value, pos); @@ -111,16 +111,16 @@ static FlakeInput parseFlakeInput(EvalState & state, for (nix::Attr attr : *(value->attrs)) { try { if (attr.name == sUrl) { - expectType(state, nString, *attr.value, *attr.pos); + expectType(state, nString, *attr.value, attr.pos); url = attr.value->string.s; attrs.emplace("url", *url); } else if (attr.name == sFlake) { - expectType(state, nBool, *attr.value, *attr.pos); + expectType(state, nBool, *attr.value, attr.pos); input.isFlake = attr.value->boolean; } else if (attr.name == sInputs) { - input.overrides = parseFlakeInputs(state, attr.value, *attr.pos, baseDir, lockRootPath); + input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath); } else if (attr.name == sFollows) { - expectType(state, nString, *attr.value, *attr.pos); + expectType(state, nString, *attr.value, attr.pos); auto follows(parseInputPath(attr.value->string.s)); follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end()); input.follows = follows; @@ -141,7 +141,7 @@ static FlakeInput parseFlakeInput(EvalState & state, } } } catch (Error & e) { - e.addTrace(*attr.pos, hintfmt("in flake attribute '%s'", attr.name)); + e.addTrace(state.positions[attr.pos], hintfmt("in flake attribute '%s'", attr.name)); throw; } } @@ -150,13 +150,13 @@ static FlakeInput parseFlakeInput(EvalState & state, try { input.ref = FlakeRef::fromAttrs(attrs); } catch (Error & e) { - e.addTrace(pos, hintfmt("in flake input")); + e.addTrace(state.positions[pos], hintfmt("in flake input")); throw; } else { attrs.erase("url"); if (!attrs.empty()) - throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, pos); + throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, state.positions[pos]); if (url) input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake); } @@ -168,7 +168,7 @@ static FlakeInput parseFlakeInput(EvalState & state, } static std::map parseFlakeInputs( - EvalState & state, Value * value, const Pos & pos, + EvalState & state, Value * value, const PosIdx pos, const std::optional & baseDir, InputPath lockRootPath) { std::map inputs; @@ -180,7 +180,7 @@ static std::map parseFlakeInputs( parseFlakeInput(state, inputAttr.name, inputAttr.value, - *inputAttr.pos, + inputAttr.pos, baseDir, lockRootPath)); } @@ -218,22 +218,22 @@ static Flake getFlake( Value vInfo; state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack - expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0)); + expectType(state, nAttrs, vInfo, state.positions.add({state.symbols.create(flakeFile), foFile}, 0, 0)); if (auto description = vInfo.attrs->get(state.sDescription)) { - expectType(state, nString, *description->value, *description->pos); + expectType(state, nString, *description->value, description->pos); flake.description = description->value->string.s; } auto sInputs = state.symbols.create("inputs"); if (auto inputs = vInfo.attrs->get(sInputs)) - flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos, flakeDir, lockRootPath); + flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath); auto sOutputs = state.symbols.create("outputs"); if (auto outputs = vInfo.attrs->get(sOutputs)) { - expectType(state, nFunction, *outputs->value, *outputs->pos); + expectType(state, nFunction, *outputs->value, outputs->pos); if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) { for (auto & formal : outputs->value->lambda.fun->formals->formals) { @@ -250,29 +250,29 @@ static Flake getFlake( auto sNixConfig = state.symbols.create("nixConfig"); if (auto nixConfig = vInfo.attrs->get(sNixConfig)) { - expectType(state, nAttrs, *nixConfig->value, *nixConfig->pos); + expectType(state, nAttrs, *nixConfig->value, nixConfig->pos); for (auto & setting : *nixConfig->value->attrs) { - forceTrivialValue(state, *setting.value, *setting.pos); + forceTrivialValue(state, *setting.value, setting.pos); if (setting.value->type() == nString) - flake.config.settings.insert({setting.name, std::string(state.forceStringNoCtx(*setting.value, *setting.pos))}); + flake.config.settings.insert({setting.name, std::string(state.forceStringNoCtx(*setting.value, setting.pos))}); else if (setting.value->type() == nPath) { PathSet emptyContext = {}; flake.config.settings.emplace( setting.name, - state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true) .toOwned()); + state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true) .toOwned()); } else if (setting.value->type() == nInt) - flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos)}); + flake.config.settings.insert({setting.name, state.forceInt(*setting.value, setting.pos)}); else if (setting.value->type() == nBool) - flake.config.settings.insert({setting.name, Explicit { state.forceBool(*setting.value, *setting.pos) }}); + flake.config.settings.insert({setting.name, Explicit { state.forceBool(*setting.value, setting.pos) }}); else if (setting.value->type() == nList) { std::vector ss; for (auto elem : setting.value->listItems()) { if (elem->type() != nString) throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected", setting.name, showType(*setting.value)); - ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos)); + ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos)); } flake.config.settings.insert({setting.name, ss}); } @@ -288,7 +288,7 @@ static Flake getFlake( attr.name != sOutputs && attr.name != sNixConfig) throw Error("flake '%s' has an unsupported attribute '%s', at %s", - lockedRef, attr.name, *attr.pos); + lockedRef, attr.name, state.positions[attr.pos]); } return flake; @@ -704,12 +704,12 @@ void callFlake(EvalState & state, state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos); } -static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::string flakeRefS(state.forceStringNoCtx(*args[0], pos)); auto flakeRef = parseFlakeRef(flakeRefS, {}, true); if (evalSettings.pureEval && !flakeRef.input.isLocked()) - throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, pos); + throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]); callFlake(state, lockFlake(state, flakeRef, diff --git a/src/libexpr/function-trace.hh b/src/libexpr/function-trace.hh index 472f2045e..e9a2526bd 100644 --- a/src/libexpr/function-trace.hh +++ b/src/libexpr/function-trace.hh @@ -8,7 +8,7 @@ namespace nix { struct FunctionCallTrace { - const Pos & pos; + const Pos pos; FunctionCallTrace(const Pos & pos); ~FunctionCallTrace(); }; diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index bb7e77b61..afb35586d 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -61,7 +61,7 @@ std::string DrvInfo::querySystem() const { if (system == "" && attrs) { auto i = attrs->find(state->sSystem); - system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos); + system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos); } return system; } @@ -75,7 +75,7 @@ std::optional DrvInfo::queryDrvPath() const if (i == attrs->end()) drvPath = {std::nullopt}; else - drvPath = {state->coerceToStorePath(*i->pos, *i->value, context)}; + drvPath = {state->coerceToStorePath(i->pos, *i->value, context)}; } return drvPath.value_or(std::nullopt); } @@ -95,7 +95,7 @@ StorePath DrvInfo::queryOutPath() const Bindings::iterator i = attrs->find(state->sOutPath); PathSet context; if (i != attrs->end()) - outPath = state->coerceToStorePath(*i->pos, *i->value, context); + outPath = state->coerceToStorePath(i->pos, *i->value, context); } if (!outPath) throw UnimplementedError("CA derivations are not yet supported"); @@ -109,23 +109,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall /* Get the ‘outputs’ list. */ Bindings::iterator i; if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { - state->forceList(*i->value, *i->pos); + state->forceList(*i->value, i->pos); /* For each output... */ for (auto elem : i->value->listItems()) { - std::string output(state->forceStringNoCtx(*elem, *i->pos)); + std::string output(state->forceStringNoCtx(*elem, i->pos)); if (withPaths) { /* Evaluate the corresponding set. */ Bindings::iterator out = attrs->find(state->symbols.create(output)); if (out == attrs->end()) continue; // FIXME: throw error? - state->forceAttrs(*out->value, *i->pos); + state->forceAttrs(*out->value, i->pos); /* And evaluate its ‘outPath’ attribute. */ Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? PathSet context; - outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context)); + outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context)); } else outputs.emplace(output, std::nullopt); } @@ -168,7 +168,7 @@ Bindings * DrvInfo::getMeta() if (!attrs) return 0; Bindings::iterator a = attrs->find(state->sMeta); if (a == attrs->end()) return 0; - state->forceAttrs(*a->value, *a->pos); + state->forceAttrs(*a->value, a->pos); meta = a->value->attrs; return meta; } @@ -369,7 +369,7 @@ static void getDerivations(EvalState & state, Value & vIn, `recurseForDerivations = true' attribute. */ if (i->value->type() == nAttrs) { Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); - if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos)) + if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos)) getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); } } diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index d574121b0..4c28b976e 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -28,9 +28,9 @@ using namespace nix; namespace nix { -static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data) +static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data) { - return Pos(data->origin, data->file, loc.first_line, loc.first_column); + return data->state.positions.add(data->origin, loc.first_line, loc.first_column); } #define CUR_POS makeCurPos(*yylloc, data) @@ -155,7 +155,7 @@ or { return OR_KW; } } catch (const boost::bad_lexical_cast &) { throw ParseError({ .msg = hintfmt("invalid integer '%1%'", yytext), - .errPos = CUR_POS, + .errPos = data->state.positions[CUR_POS], }); } return INT; @@ -165,7 +165,7 @@ or { return OR_KW; } if (errno != 0) throw ParseError({ .msg = hintfmt("invalid float '%1%'", yytext), - .errPos = CUR_POS, + .errPos = data->state.positions[CUR_POS], }); return FLOAT; } @@ -294,7 +294,7 @@ or { return OR_KW; } <> { throw ParseError({ .msg = hintfmt("path has a trailing slash"), - .errPos = CUR_POS, + .errPos = data->state.positions[CUR_POS], }); } diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index bf01935a9..4138977ea 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -249,33 +249,31 @@ std::string showAttrPath(const AttrPath & attrPath) } -Pos noPos; - /* Computing levels/displacements for variables. */ -void Expr::bindVars(const StaticEnv & env) +void Expr::bindVars(const PosTable & pt, const StaticEnv & env) { abort(); } -void ExprInt::bindVars(const StaticEnv & env) +void ExprInt::bindVars(const PosTable & pt, const StaticEnv & env) { } -void ExprFloat::bindVars(const StaticEnv & env) +void ExprFloat::bindVars(const PosTable & pt, const StaticEnv & env) { } -void ExprString::bindVars(const StaticEnv & env) +void ExprString::bindVars(const PosTable & pt, const StaticEnv & env) { } -void ExprPath::bindVars(const StaticEnv & env) +void ExprPath::bindVars(const PosTable & pt, const StaticEnv & env) { } -void ExprVar::bindVars(const StaticEnv & env) +void ExprVar::bindVars(const PosTable & pt, const StaticEnv & env) { /* Check whether the variable appears in the environment. If so, set its level and displacement. */ @@ -302,30 +300,30 @@ void ExprVar::bindVars(const StaticEnv & env) if (withLevel == -1) throw UndefinedVarError({ .msg = hintfmt("undefined variable '%1%'", name), - .errPos = pos + .errPos = pt[pos] }); fromWith = true; this->level = withLevel; } -void ExprSelect::bindVars(const StaticEnv & env) +void ExprSelect::bindVars(const PosTable & pt, const StaticEnv & env) { - e->bindVars(env); - if (def) def->bindVars(env); + e->bindVars(pt, env); + if (def) def->bindVars(pt, env); for (auto & i : attrPath) if (!i.symbol.set()) - i.expr->bindVars(env); + i.expr->bindVars(pt, env); } -void ExprOpHasAttr::bindVars(const StaticEnv & env) +void ExprOpHasAttr::bindVars(const PosTable & pt, const StaticEnv & env) { - e->bindVars(env); + e->bindVars(pt, env); for (auto & i : attrPath) if (!i.symbol.set()) - i.expr->bindVars(env); + i.expr->bindVars(pt, env); } -void ExprAttrs::bindVars(const StaticEnv & env) +void ExprAttrs::bindVars(const PosTable & pt, const StaticEnv & env) { const StaticEnv * dynamicEnv = &env; StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0); @@ -340,26 +338,26 @@ void ExprAttrs::bindVars(const StaticEnv & env) // No need to sort newEnv since attrs is in sorted order. for (auto & i : attrs) - i.second.e->bindVars(i.second.inherited ? env : newEnv); + i.second.e->bindVars(pt, i.second.inherited ? env : newEnv); } else for (auto & i : attrs) - i.second.e->bindVars(env); + i.second.e->bindVars(pt, env); for (auto & i : dynamicAttrs) { - i.nameExpr->bindVars(*dynamicEnv); - i.valueExpr->bindVars(*dynamicEnv); + i.nameExpr->bindVars(pt, *dynamicEnv); + i.valueExpr->bindVars(pt, *dynamicEnv); } } -void ExprList::bindVars(const StaticEnv & env) +void ExprList::bindVars(const PosTable & pt, const StaticEnv & env) { for (auto & i : elems) - i->bindVars(env); + i->bindVars(pt, env); } -void ExprLambda::bindVars(const StaticEnv & env) +void ExprLambda::bindVars(const PosTable & pt, const StaticEnv & env) { StaticEnv newEnv( false, &env, @@ -377,20 +375,20 @@ void ExprLambda::bindVars(const StaticEnv & env) newEnv.sort(); for (auto & i : formals->formals) - if (i.def) i.def->bindVars(newEnv); + if (i.def) i.def->bindVars(pt, newEnv); } - body->bindVars(newEnv); + body->bindVars(pt, newEnv); } -void ExprCall::bindVars(const StaticEnv & env) +void ExprCall::bindVars(const PosTable & pt, const StaticEnv & env) { - fun->bindVars(env); + fun->bindVars(pt, env); for (auto e : args) - e->bindVars(env); + e->bindVars(pt, env); } -void ExprLet::bindVars(const StaticEnv & env) +void ExprLet::bindVars(const PosTable & pt, const StaticEnv & env) { StaticEnv newEnv(false, &env, attrs->attrs.size()); @@ -401,12 +399,12 @@ void ExprLet::bindVars(const StaticEnv & env) // No need to sort newEnv since attrs->attrs is in sorted order. for (auto & i : attrs->attrs) - i.second.e->bindVars(i.second.inherited ? env : newEnv); + i.second.e->bindVars(pt, i.second.inherited ? env : newEnv); - body->bindVars(newEnv); + body->bindVars(pt, newEnv); } -void ExprWith::bindVars(const StaticEnv & env) +void ExprWith::bindVars(const PosTable & pt, const StaticEnv & env) { /* Does this `with' have an enclosing `with'? If so, record its level so that `lookupVar' can look up variables in the previous @@ -420,36 +418,36 @@ void ExprWith::bindVars(const StaticEnv & env) break; } - attrs->bindVars(env); + attrs->bindVars(pt, env); StaticEnv newEnv(true, &env); - body->bindVars(newEnv); + body->bindVars(pt, newEnv); } -void ExprIf::bindVars(const StaticEnv & env) +void ExprIf::bindVars(const PosTable & pt, const StaticEnv & env) { - cond->bindVars(env); - then->bindVars(env); - else_->bindVars(env); + cond->bindVars(pt, env); + then->bindVars(pt, env); + else_->bindVars(pt, env); } -void ExprAssert::bindVars(const StaticEnv & env) +void ExprAssert::bindVars(const PosTable & pt, const StaticEnv & env) { - cond->bindVars(env); - body->bindVars(env); + cond->bindVars(pt, env); + body->bindVars(pt, env); } -void ExprOpNot::bindVars(const StaticEnv & env) +void ExprOpNot::bindVars(const PosTable & pt, const StaticEnv & env) { - e->bindVars(env); + e->bindVars(pt, env); } -void ExprConcatStrings::bindVars(const StaticEnv & env) +void ExprConcatStrings::bindVars(const PosTable & pt, const StaticEnv & env) { for (auto & i : *es) - i.second->bindVars(env); + i.second->bindVars(pt, env); } -void ExprPos::bindVars(const StaticEnv & env) +void ExprPos::bindVars(const PosTable & pt, const StaticEnv & env) { } @@ -468,9 +466,9 @@ void ExprLambda::setName(Symbol & name) } -std::string ExprLambda::showNamePos() const +std::string ExprLambda::showNamePos(const PosTable & pt) const { - return fmt("%1% at %2%", name.set() ? "'" + (std::string) name + "'" : "anonymous function", pos); + return fmt("%1% at %2%", name.set() ? "'" + (std::string) name + "'" : "anonymous function", pt[pos]); } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 4dbe31510..d9392cff5 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "value.hh" #include "symbol-table.hh" #include "error.hh" @@ -24,31 +27,91 @@ MakeError(RestrictedPathError, Error); struct Pos { Symbol file; + FileOrigin origin; uint32_t line; - FileOrigin origin:2; - uint32_t column:30; - Pos() : line(0), origin(foString), column(0) { }; - Pos(FileOrigin origin, const Symbol & file, uint32_t line, uint32_t column) - : file(file), line(line), origin(origin), column(column) { }; - operator bool() const + uint32_t column; + + explicit operator bool() const { return line > 0; } +}; + +class PosIdx { + friend class PosTable; + +private: + uint32_t id; + + explicit PosIdx(uint32_t id): id(id) {} + +public: + PosIdx() : id(0) {} + + explicit operator bool() const { return id > 0; } + + bool operator<(const PosIdx other) const { return id < other.id; } +}; + +class PosTable +{ +public: + class Origin { + friend PosTable; + private: + // must always be invalid by default, add() replaces this with the actual value. + // subsequent add() calls use this index as a token to quickly check whether the + // current origins.back() can be reused or not. + mutable uint32_t idx = std::numeric_limits::max(); + + explicit Origin(uint32_t idx): idx(idx), file{}, origin{} {} + + public: + const Symbol file; + const FileOrigin origin; + + Origin(Symbol file, FileOrigin origin): file(file), origin(origin) {} + }; + + struct Offset { + uint32_t line, column; + }; + +private: + std::vector origins; + ChunkedVector offsets; + +public: + PosTable(): offsets(1024) { - return line != 0; + origins.reserve(1024); } - bool operator < (const Pos & p2) const + PosIdx add(const Origin & origin, uint32_t line, uint32_t column) { - if (!line) return p2.line; - if (!p2.line) return false; - int d = ((const std::string &) file).compare((const std::string &) p2.file); - if (d < 0) return true; - if (d > 0) return false; - if (line < p2.line) return true; - if (line > p2.line) return false; - return column < p2.column; + const auto idx = offsets.add({line, column}).second; + if (origins.empty() || origins.back().idx != origin.idx) { + origin.idx = idx; + origins.push_back(origin); + } + return PosIdx(idx + 1); + } + + Pos operator[](PosIdx p) const + { + if (p.id == 0 || p.id > offsets.size()) + return {}; + const auto idx = p.id - 1; + /* we want the last key <= idx, so we'll take prev(first key > idx). + this is guaranteed to never rewind origin.begin because the first + key is always 0. */ + const auto pastOrigin = std::upper_bound( + origins.begin(), origins.end(), Origin(idx), + [] (const auto & a, const auto & b) { return a.idx < b.idx; }); + const auto origin = *std::prev(pastOrigin); + const auto offset = offsets[idx]; + return {origin.file, origin.origin, offset.line, offset.column}; } }; -extern Pos noPos; +inline PosIdx noPos = {}; std::ostream & operator << (std::ostream & str, const Pos & pos); @@ -79,7 +142,7 @@ struct Expr { virtual ~Expr() { }; virtual void show(std::ostream & str) const; - virtual void bindVars(const StaticEnv & env); + virtual void bindVars(const PosTable & pt, const StaticEnv & env); virtual void eval(EvalState & state, Env & env, Value & v); virtual Value * maybeThunk(EvalState & state, Env & env); virtual void setName(Symbol & name); @@ -90,7 +153,7 @@ std::ostream & operator << (std::ostream & str, const Expr & e); #define COMMON_METHODS \ void show(std::ostream & str) const; \ void eval(EvalState & state, Env & env, Value & v); \ - void bindVars(const StaticEnv & env); + void bindVars(const PosTable & pt, const StaticEnv & env); struct ExprInt : Expr { @@ -133,7 +196,7 @@ typedef uint32_t Displacement; struct ExprVar : Expr { - Pos pos; + PosIdx pos; Symbol name; /* Whether the variable comes from an environment (e.g. a rec, let @@ -150,18 +213,18 @@ struct ExprVar : Expr Displacement displ; ExprVar(const Symbol & name) : name(name) { }; - ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { }; + ExprVar(const PosIdx & pos, const Symbol & name) : pos(pos), name(name) { }; COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); }; struct ExprSelect : Expr { - Pos pos; + PosIdx pos; Expr * e, * def; AttrPath attrPath; - ExprSelect(const Pos & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; - ExprSelect(const Pos & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; + ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; + ExprSelect(const PosIdx & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; COMMON_METHODS }; @@ -176,13 +239,13 @@ struct ExprOpHasAttr : Expr struct ExprAttrs : Expr { bool recursive; - Pos pos; + PosIdx pos; struct AttrDef { bool inherited; Expr * e; - Pos pos; + PosIdx pos; Displacement displ; // displacement - AttrDef(Expr * e, const Pos & pos, bool inherited=false) + AttrDef(Expr * e, const PosIdx & pos, bool inherited=false) : inherited(inherited), e(e), pos(pos) { }; AttrDef() { }; }; @@ -190,14 +253,14 @@ struct ExprAttrs : Expr AttrDefs attrs; struct DynamicAttrDef { Expr * nameExpr, * valueExpr; - Pos pos; - DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos) + PosIdx pos; + DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const PosIdx & pos) : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { }; }; typedef std::vector DynamicAttrDefs; DynamicAttrDefs dynamicAttrs; - ExprAttrs(const Pos &pos) : recursive(false), pos(pos) { }; - ExprAttrs() : recursive(false), pos(noPos) { }; + ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { }; + ExprAttrs() : recursive(false) { }; COMMON_METHODS }; @@ -210,10 +273,10 @@ struct ExprList : Expr struct Formal { - Pos pos; + PosIdx pos; Symbol name; Expr * def; - Formal(const Pos & pos, const Symbol & name, Expr * def) : pos(pos), name(name), def(def) { }; + Formal(const PosIdx & pos, const Symbol & name, Expr * def) : pos(pos), name(name), def(def) { }; }; struct Formals @@ -241,17 +304,17 @@ struct Formals struct ExprLambda : Expr { - Pos pos; + PosIdx pos; Symbol name; Symbol arg; Formals * formals; Expr * body; - ExprLambda(const Pos & pos, const Symbol & arg, Formals * formals, Expr * body) + ExprLambda(const PosIdx & pos, const Symbol & arg, Formals * formals, Expr * body) : pos(pos), arg(arg), formals(formals), body(body) { }; void setName(Symbol & name); - std::string showNamePos() const; + std::string showNamePos(const PosTable & pt) const; inline bool hasFormals() const { return formals != nullptr; } COMMON_METHODS }; @@ -260,8 +323,8 @@ struct ExprCall : Expr { Expr * fun; std::vector args; - Pos pos; - ExprCall(const Pos & pos, Expr * fun, std::vector && args) + PosIdx pos; + ExprCall(const PosIdx & pos, Expr * fun, std::vector && args) : fun(fun), args(args), pos(pos) { } COMMON_METHODS @@ -277,26 +340,26 @@ struct ExprLet : Expr struct ExprWith : Expr { - Pos pos; + PosIdx pos; Expr * attrs, * body; size_t prevWith; - ExprWith(const Pos & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; + ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; COMMON_METHODS }; struct ExprIf : Expr { - Pos pos; + PosIdx pos; Expr * cond, * then, * else_; - ExprIf(const Pos & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { }; + ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { }; COMMON_METHODS }; struct ExprAssert : Expr { - Pos pos; + PosIdx pos; Expr * cond, * body; - ExprAssert(const Pos & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { }; + ExprAssert(const PosIdx & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { }; COMMON_METHODS }; @@ -310,17 +373,17 @@ struct ExprOpNot : Expr #define MakeBinOp(name, s) \ struct name : Expr \ { \ - Pos pos; \ + PosIdx pos; \ Expr * e1, * e2; \ name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \ - name(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \ + name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \ void show(std::ostream & str) const \ { \ str << "(" << *e1 << " " s " " << *e2 << ")"; \ } \ - void bindVars(const StaticEnv & env) \ + void bindVars(const PosTable & pt, const StaticEnv & env) \ { \ - e1->bindVars(env); e2->bindVars(env); \ + e1->bindVars(pt, env); e2->bindVars(pt, env); \ } \ void eval(EvalState & state, Env & env, Value & v); \ }; @@ -335,18 +398,18 @@ MakeBinOp(ExprOpConcatLists, "++") struct ExprConcatStrings : Expr { - Pos pos; + PosIdx pos; bool forceString; - std::vector > * es; - ExprConcatStrings(const Pos & pos, bool forceString, std::vector > * es) + std::vector > * es; + ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector > * es) : pos(pos), forceString(forceString), es(es) { }; COMMON_METHODS }; struct ExprPos : Expr { - Pos pos; - ExprPos(const Pos & pos) : pos(pos) { }; + PosIdx pos; + ExprPos(const PosIdx & pos) : pos(pos) { }; COMMON_METHODS }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 49c401603..0052a8070 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -32,12 +32,12 @@ namespace nix { SymbolTable & symbols; Expr * result; Path basePath; - Symbol file; - FileOrigin origin; + PosTable::Origin origin; std::optional error; - ParseData(EvalState & state) + ParseData(EvalState & state, PosTable::Origin origin) : state(state) , symbols(state.symbols) + , origin(std::move(origin)) { }; }; @@ -96,7 +96,7 @@ static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos) static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, - Expr * e, const Pos & pos) + Expr * e, const PosIdx pos, const nix::PosTable & pt) { AttrPath::iterator i; // All attrpaths have at least one attr @@ -109,10 +109,10 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, if (j != attrs->attrs.end()) { if (!j->second.inherited) { ExprAttrs * attrs2 = dynamic_cast(j->second.e); - if (!attrs2) dupAttr(attrPath, pos, j->second.pos); + if (!attrs2) dupAttr(attrPath, pt[pos], pt[j->second.pos]); attrs = attrs2; } else - dupAttr(attrPath, pos, j->second.pos); + dupAttr(attrPath, pt[pos], pt[j->second.pos]); } else { ExprAttrs * nested = new ExprAttrs; attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos); @@ -139,11 +139,11 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, for (auto & ad : ae->attrs) { auto j2 = jAttrs->attrs.find(ad.first); if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error. - dupAttr(ad.first, j2->second.pos, ad.second.pos); + dupAttr(ad.first, pt[j2->second.pos], pt[ad.second.pos]); jAttrs->attrs.emplace(ad.first, ad.second); } } else { - dupAttr(attrPath, pos, j->second.pos); + dupAttr(attrPath, pt[pos], pt[j->second.pos]); } } else { // This attr path is not defined. Let's create it. @@ -157,14 +157,14 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, static Formals * toFormals(ParseData & data, ParserFormals * formals, - Pos pos = noPos, Symbol arg = {}) + PosIdx pos = noPos, Symbol arg = {}) { std::sort(formals->formals.begin(), formals->formals.end(), [] (const auto & a, const auto & b) { return std::tie(a.name, a.pos) < std::tie(b.name, b.pos); }); - std::optional> duplicate; + std::optional> duplicate; for (size_t i = 0; i + 1 < formals->formals.size(); i++) { if (formals->formals[i].name != formals->formals[i + 1].name) continue; @@ -174,7 +174,7 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals, if (duplicate) throw ParseError({ .msg = hintfmt("duplicate formal function argument '%1%'", duplicate->first), - .errPos = duplicate->second + .errPos = data.state.positions[duplicate->second] }); Formals result; @@ -184,7 +184,7 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals, if (arg.set() && result.has(arg)) throw ParseError({ .msg = hintfmt("duplicate formal function argument '%1%'", arg), - .errPos = pos + .errPos = data.state.positions[pos] }); delete formals; @@ -192,8 +192,8 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals, } -static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, - std::vector > > & es) +static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols, + std::vector > > & es) { if (es.empty()) return new ExprString(""); @@ -233,7 +233,7 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, } /* Strip spaces from each line. */ - std::vector > * es2 = new std::vector >; + auto * es2 = new std::vector >; atStartOfLine = true; size_t curDropped = 0; size_t n = es.size(); @@ -284,9 +284,9 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, } -static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data) +static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data) { - return Pos(data->origin, data->file, loc.first_line, loc.first_column); + return data->state.positions.add(data->origin, loc.first_line, loc.first_column); } #define CUR_POS makeCurPos(*yylocp, data) @@ -299,7 +299,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err { data->error = { .msg = hintfmt(error), - .errPos = makeCurPos(*loc, data) + .errPos = data->state.positions[makeCurPos(*loc, data)] }; } @@ -320,8 +320,8 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err StringToken uri; StringToken str; std::vector * attrNames; - std::vector > * string_parts; - std::vector > > * ind_string_parts; + std::vector > * string_parts; + std::vector > > * ind_string_parts; } %type start expr expr_function expr_if expr_op @@ -388,7 +388,7 @@ expr_function { if (!$2->dynamicAttrs.empty()) throw ParseError({ .msg = hintfmt("dynamic attributes not allowed in let"), - .errPos = CUR_POS + .errPos = data->state.positions[CUR_POS] }); $$ = new ExprLet($2, $4); } @@ -415,7 +415,7 @@ expr_op | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); } | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } | expr_op '+' expr_op - { $$ = new ExprConcatStrings(CUR_POS, false, new std::vector >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } + { $$ = new ExprConcatStrings(CUR_POS, false, new std::vector >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } | expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); } | expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); } | expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); } @@ -477,7 +477,7 @@ expr_simple if (noURLLiterals) throw ParseError({ .msg = hintfmt("URL literals are disabled"), - .errPos = CUR_POS + .errPos = data->state.positions[CUR_POS] }); $$ = new ExprString(std::string($1)); } @@ -503,9 +503,9 @@ string_parts_interpolated : string_parts_interpolated STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), new ExprString(std::string($2))); } | string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); } - | DOLLAR_CURLY expr '}' { $$ = new std::vector >; $$->emplace_back(makeCurPos(@1, data), $2); } + | DOLLAR_CURLY expr '}' { $$ = new std::vector >; $$->emplace_back(makeCurPos(@1, data), $2); } | STR DOLLAR_CURLY expr '}' { - $$ = new std::vector >; + $$ = new std::vector >; $$->emplace_back(makeCurPos(@1, data), new ExprString(std::string($1))); $$->emplace_back(makeCurPos(@2, data), $3); } @@ -528,17 +528,18 @@ path_start ind_string_parts : ind_string_parts IND_STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $2); } | ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); } - | { $$ = new std::vector > >; } + | { $$ = new std::vector > >; } ; binds - : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); } + : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data), data->state.positions); } | binds INHERIT attrs ';' { $$ = $1; for (auto & i : *$3) { if ($$->attrs.find(i.symbol) != $$->attrs.end()) - dupAttr(i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos); - Pos pos = makeCurPos(@3, data); + dupAttr(i.symbol, data->state.positions[makeCurPos(@3, data)], + data->state.positions[$$->attrs[i.symbol].pos]); + auto pos = makeCurPos(@3, data); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true)); } } @@ -547,7 +548,8 @@ binds /* !!! Should ensure sharing of the expression in $4. */ for (auto & i : *$6) { if ($$->attrs.find(i.symbol) != $$->attrs.end()) - dupAttr(i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos); + dupAttr(i.symbol, data->state.positions[makeCurPos(@6, data)], + data->state.positions[$$->attrs[i.symbol].pos]); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data))); } } @@ -565,7 +567,7 @@ attrs } else throw ParseError({ .msg = hintfmt("dynamic attributes not allowed in inherit"), - .errPos = makeCurPos(@2, data) + .errPos = data->state.positions[makeCurPos(@2, data)] }); } | { $$ = new AttrPath; } @@ -646,19 +648,19 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, const PathView path, const PathView basePath, StaticEnv & staticEnv) { yyscan_t scanner; - ParseData data(*this); - data.origin = origin; + Symbol file; switch (origin) { case foFile: - data.file = data.symbols.create(path); + file = symbols.create(path); break; case foStdin: case foString: - data.file = data.symbols.create(text); + file = symbols.create(text); break; default: assert(false); } + ParseData data(*this, {file, origin}); data.basePath = basePath; yylex_init(&scanner); @@ -668,7 +670,7 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, if (res) throw ParseError(data.error.value()); - data.result->bindVars(staticEnv); + data.result->bindVars(positions, staticEnv); return data.result; } @@ -760,7 +762,7 @@ Path EvalState::findFile(const std::string_view path) } -Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const Pos & pos) +Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos) { for (auto & i : searchPath) { std::string suffix; @@ -787,7 +789,7 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c ? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)" : "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)", path), - .errPos = pos + .errPos = positions[pos] }); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 3ebc2f1df..9cdcfd0b9 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -96,7 +96,7 @@ struct RealisePathFlags { bool checkForPureEval = true; }; -static Path realisePath(EvalState & state, const Pos & pos, Value & v, const RealisePathFlags flags = {}) +static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {}) { PathSet context; @@ -105,7 +105,7 @@ static Path realisePath(EvalState & state, const Pos & pos, Value & v, const Rea try { return state.coerceToPath(pos, v, context); } catch (Error & e) { - e.addTrace(pos, "while realising the context of a path"); + e.addTrace(state.positions[pos], "while realising the context of a path"); throw; } }(); @@ -119,7 +119,7 @@ static Path realisePath(EvalState & state, const Pos & pos, Value & v, const Rea ? state.checkSourcePath(realPath) : realPath; } catch (Error & e) { - e.addTrace(pos, "while realising the context of path '%s'", path); + e.addTrace(state.positions[pos], "while realising the context of path '%s'", path); throw; } } @@ -157,7 +157,7 @@ static void mkOutputString( /* Load and evaluate an expression from path specified by the argument. */ -static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vScope, Value & v) +static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v) { auto path = realisePath(state, pos, vPath); @@ -237,7 +237,7 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS static RegisterPrimOp primop_scopedImport(RegisterPrimOp::Info { .name = "scopedImport", .arity = 2, - .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) { import(state, pos, *args[1], args[0], v); } @@ -299,7 +299,7 @@ static RegisterPrimOp primop_import({ (The function argument doesn’t have to be called `x` in `foo.nix`; any name would work.) )", - .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) { import(state, pos, *args[0], nullptr, v); } @@ -310,7 +310,7 @@ static RegisterPrimOp primop_import({ extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v); /* Load a ValueInitializer from a DSO and return whatever it initializes */ -void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto path = realisePath(state, pos, *args[0]); @@ -338,7 +338,7 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value /* Execute a program and parse its output */ -void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); auto elems = args[0]->listElems(); @@ -346,7 +346,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) if (count == 0) { throw EvalError({ .msg = hintfmt("at least one argument to 'exec' required"), - .errPos = pos + .errPos = state.positions[pos] }); } PathSet context; @@ -361,29 +361,30 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v) throw EvalError({ .msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid", program, e.path), - .errPos = pos + .errPos = state.positions[pos] }); } auto output = runProgram(program, true, commandArgs); Expr * parsed; try { - parsed = state.parseExprFromString(std::move(output), pos.file); + auto base = state.positions[pos]; + parsed = state.parseExprFromString(std::move(output), base.file); } catch (Error & e) { - e.addTrace(pos, "While parsing the output from '%1%'", program); + e.addTrace(state.positions[pos], "While parsing the output from '%1%'", program); throw; } try { state.eval(parsed, v); } catch (Error & e) { - e.addTrace(pos, "While evaluating the output from '%1%'", program); + e.addTrace(state.positions[pos], "While evaluating the output from '%1%'", program); throw; } } /* Return a string representing the type of the expression. */ -static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_typeOf(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); std::string t; @@ -417,7 +418,7 @@ static RegisterPrimOp primop_typeOf({ }); /* Determine whether the argument is the null value. */ -static void prim_isNull(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isNull(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nNull); @@ -437,7 +438,7 @@ static RegisterPrimOp primop_isNull({ }); /* Determine whether the argument is a function. */ -static void prim_isFunction(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isFunction(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nFunction); @@ -453,7 +454,7 @@ static RegisterPrimOp primop_isFunction({ }); /* Determine whether the argument is an integer. */ -static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isInt(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nInt); @@ -469,7 +470,7 @@ static RegisterPrimOp primop_isInt({ }); /* Determine whether the argument is a float. */ -static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isFloat(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nFloat); @@ -485,7 +486,7 @@ static RegisterPrimOp primop_isFloat({ }); /* Determine whether the argument is a string. */ -static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isString(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nString); @@ -501,7 +502,7 @@ static RegisterPrimOp primop_isString({ }); /* Determine whether the argument is a Boolean. */ -static void prim_isBool(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isBool(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nBool); @@ -517,7 +518,7 @@ static RegisterPrimOp primop_isBool({ }); /* Determine whether the argument is a path. */ -static void prim_isPath(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isPath(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nPath); @@ -585,7 +586,7 @@ static Bindings::iterator getAttr( std::string_view funcName, Symbol attrSym, Bindings * attrSet, - const Pos & pos) + const PosIdx pos) { Bindings::iterator value = attrSet->find(attrSym); if (value == attrSet->end()) { @@ -595,21 +596,21 @@ static Bindings::iterator getAttr( funcName ); - Pos aPos = *attrSet->pos; - if (aPos == noPos) { + auto aPos = attrSet->pos; + if (!aPos) { throw TypeError({ .msg = errorMsg, - .errPos = pos, + .errPos = state.positions[pos], }); } else { auto e = TypeError({ .msg = errorMsg, - .errPos = aPos, + .errPos = state.positions[aPos], }); // Adding another trace for the function name to make it clear // which call received wrong arguments. - e.addTrace(pos, hintfmt("while invoking '%s'", funcName)); + e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName)); throw e; } } @@ -617,7 +618,7 @@ static Bindings::iterator getAttr( return value; } -static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -666,7 +667,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar if (key == e->attrs->end()) throw EvalError({ .msg = hintfmt("attribute 'key' required"), - .errPos = pos + .errPos = state.positions[pos] }); state.forceValue(*key->value, pos); @@ -729,7 +730,7 @@ static RegisterPrimOp primop_abort({ .doc = R"( Abort Nix expression evaluation and print the error message *s*. )", - .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context).toOwned(); @@ -747,7 +748,7 @@ static RegisterPrimOp primop_throw({ derivations, a derivation that throws an error is silently skipped (which is not the case for `abort`). )", - .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) + .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context).toOwned(); @@ -755,7 +756,7 @@ static RegisterPrimOp primop_throw({ } }); -static void prim_addErrorContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { try { state.forceValue(*args[1], pos); @@ -773,7 +774,7 @@ static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info { .fun = prim_addErrorContext, }); -static void prim_ceil(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_ceil(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto value = state.forceFloat(*args[0], args[0]->determinePos(pos)); v.mkInt(ceil(value)); @@ -792,7 +793,7 @@ static RegisterPrimOp primop_ceil({ .fun = prim_ceil, }); -static void prim_floor(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_floor(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto value = state.forceFloat(*args[0], args[0]->determinePos(pos)); v.mkInt(floor(value)); @@ -813,7 +814,7 @@ static RegisterPrimOp primop_floor({ /* Try evaluating the argument. Success => {success=true; value=something;}, * else => {success=false; value=false;} */ -static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto attrs = state.buildBindings(2); try { @@ -849,7 +850,7 @@ static RegisterPrimOp primop_tryEval({ }); /* Return an environment variable. Use with care. */ -static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_getEnv(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::string name(state.forceStringNoCtx(*args[0], pos)); v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or("")); @@ -873,7 +874,7 @@ static RegisterPrimOp primop_getEnv({ }); /* Evaluate the first argument, then return the second argument. */ -static void prim_seq(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_seq(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -892,7 +893,7 @@ static RegisterPrimOp primop_seq({ /* Evaluate the first argument deeply (i.e. recursing into lists and attrsets), then return the second argument. */ -static void prim_deepSeq(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_deepSeq(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValueDeep(*args[0]); state.forceValue(*args[1], pos); @@ -912,7 +913,7 @@ static RegisterPrimOp primop_deepSeq({ /* Evaluate the first expression and print it on standard error. Then return the second expression. Useful for debugging. */ -static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); if (args[0]->type() == nString) @@ -947,7 +948,7 @@ static RegisterPrimOp primop_trace({ derivation; `drvPath' containing the path of the Nix expression; and `type' set to `derivation' to indicate that this is a derivation. */ -static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -961,11 +962,11 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * ); std::string drvName; - Pos & posDrvName(*attr->pos); + const auto posDrvName = attr->pos; try { drvName = state.forceStringNoCtx(*attr->value, pos); } catch (Error & e) { - e.addTrace(posDrvName, "while evaluating the derivation attribute 'name'"); + e.addTrace(state.positions[posDrvName], "while evaluating the derivation attribute 'name'"); throw; } @@ -1008,7 +1009,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * else throw EvalError({ .msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); }; @@ -1018,7 +1019,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * if (outputs.find(j) != outputs.end()) throw EvalError({ .msg = hintfmt("duplicate derivation output '%1%'", j), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); /* !!! Check whether j is a valid attribute name. */ @@ -1028,14 +1029,14 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * if (j == "drv") throw EvalError({ .msg = hintfmt("invalid derivation output name 'drv'" ), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); outputs.insert(j); } if (outputs.empty()) throw EvalError({ .msg = hintfmt("derivation cannot have an empty set of outputs"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); }; @@ -1099,7 +1100,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * } } else { - auto s = state.coerceToString(*i->pos, *i->value, context, true).toOwned(); + auto s = state.coerceToString(i->pos, *i->value, context, true).toOwned(); drv.env.emplace(key, s); if (i->name == state.sBuilder) drv.builder = std::move(s); else if (i->name == state.sSystem) drv.platform = std::move(s); @@ -1113,7 +1114,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * } } catch (Error & e) { - e.addTrace(posDrvName, + e.addTrace(state.positions[posDrvName], "while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName); throw; @@ -1163,20 +1164,20 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * if (drv.builder == "") throw EvalError({ .msg = hintfmt("required attribute 'builder' missing"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); if (drv.platform == "") throw EvalError({ .msg = hintfmt("required attribute 'system' missing"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); /* Check whether the derivation name is valid. */ if (isDerivation(drvName)) throw EvalError({ .msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); if (outputHash) { @@ -1187,7 +1188,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * if (outputs.size() != 1 || *(outputs.begin()) != "out") throw Error({ .msg = hintfmt("multiple outputs are not supported in fixed-output derivations"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo)); @@ -1208,7 +1209,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * if (contentAddressed && isImpure) throw EvalError({ .msg = hintfmt("derivation cannot be both content-addressed and impure"), - .errPos = posDrvName + .errPos = state.positions[posDrvName] }); auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256); @@ -1300,7 +1301,7 @@ static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info { time, any occurrence of this string in an derivation attribute will be replaced with the concrete path in the Nix store of the output ‘out’. */ -static void prim_placeholder(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_placeholder(EvalState & state, const PosIdx pos, Value * * args, Value & v) { v.mkString(hashPlaceholder(state.forceStringNoCtx(*args[0], pos))); } @@ -1323,7 +1324,7 @@ static RegisterPrimOp primop_placeholder({ /* Convert the argument to a path. !!! obsolete? */ -static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; Path path = state.coerceToPath(pos, *args[0], context); @@ -1348,12 +1349,12 @@ static RegisterPrimOp primop_toPath({ /nix/store/newhash-oldhash-oldname. In the past, `toPath' had special case behaviour for store paths, but that created weird corner cases. */ -static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v) { if (evalSettings.pureEval) throw EvalError({ .msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"), - .errPos = pos + .errPos = state.positions[pos] }); PathSet context; @@ -1365,7 +1366,7 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V if (!state.store->isInStore(path)) throw EvalError({ .msg = hintfmt("path '%1%' is not in the Nix store", path), - .errPos = pos + .errPos = state.positions[pos] }); auto path2 = state.store->toStorePath(path).first; if (!settings.readOnlyMode) @@ -1392,7 +1393,7 @@ static RegisterPrimOp primop_storePath({ .fun = prim_storePath, }); -static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v) { /* We don’t check the path right now, because we don’t want to throw if the path isn’t allowed, but just return false (and we @@ -1424,7 +1425,7 @@ static RegisterPrimOp primop_pathExists({ /* Return the base name of the given string, i.e., everything following the last slash. */ -static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false)), context); @@ -1444,7 +1445,7 @@ static RegisterPrimOp primop_baseNameOf({ /* Return the directory of the given path, i.e., everything before the last slash. Return either a path or a string depending on the type of the argument. */ -static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto path = state.coerceToString(pos, *args[0], context, false, false); @@ -1464,7 +1465,7 @@ static RegisterPrimOp primop_dirOf({ }); /* Return the contents of a file as a string. */ -static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto path = realisePath(state, pos, *args[0]); auto s = readFile(path); @@ -1492,7 +1493,7 @@ static RegisterPrimOp primop_readFile({ /* Find a file in the Nix search path. Used to implement paths, which are desugared to 'findFile __nixPath "x"'. */ -static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); @@ -1523,7 +1524,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va } catch (InvalidPathError & e) { throw EvalError({ .msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path), - .errPos = pos + .errPos = state.positions[pos] }); } @@ -1543,14 +1544,14 @@ static RegisterPrimOp primop_findFile(RegisterPrimOp::Info { }); /* Return the cryptographic hash of a file in base-16. */ -static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto type = state.forceStringNoCtx(*args[0], pos); std::optional ht = parseHashType(type); if (!ht) throw Error({ .msg = hintfmt("unknown hash type '%1%'", type), - .errPos = pos + .errPos = state.positions[pos] }); auto path = realisePath(state, pos, *args[1]); @@ -1570,7 +1571,7 @@ static RegisterPrimOp primop_hashFile({ }); /* Read a directory (without . or ..) */ -static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto path = realisePath(state, pos, *args[0]); @@ -1619,7 +1620,7 @@ static RegisterPrimOp primop_readDir({ /* Convert the argument (which can be any Nix expression) to an XML representation returned in a string. Not all Nix expressions can be sensibly or completely represented (e.g., functions). */ -static void prim_toXML(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_toXML(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::ostringstream out; PathSet context; @@ -1727,7 +1728,7 @@ static RegisterPrimOp primop_toXML({ /* Convert the argument (which can be any Nix expression) to a JSON string. Not all Nix expressions can be sensibly or completely represented (e.g., functions). */ -static void prim_toJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::ostringstream out; PathSet context; @@ -1750,13 +1751,13 @@ static RegisterPrimOp primop_toJSON({ }); /* Parse a JSON string to a value. */ -static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fromJSON(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto s = state.forceStringNoCtx(*args[0], pos); try { parseJSON(state, s, v); } catch (JSONParseError &e) { - e.addTrace(pos, "while decoding a JSON string"); + e.addTrace(state.positions[pos], "while decoding a JSON string"); throw; } } @@ -1778,7 +1779,7 @@ static RegisterPrimOp primop_fromJSON({ /* Store a string in the Nix store as a source file that can be used as an input by derivations. */ -static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; std::string name(state.forceStringNoCtx(*args[0], pos)); @@ -1793,7 +1794,7 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu "in 'toFile': the file named '%1%' must not contain a reference " "to a derivation but contains (%2%)", name, path), - .errPos = pos + .errPos = state.positions[pos] }); refs.insert(state.store->parseStorePath(path)); } @@ -1889,7 +1890,7 @@ static RegisterPrimOp primop_toFile({ static void addPath( EvalState & state, - const Pos & pos, + const PosIdx pos, const std::string & name, Path path, Value * filterFun, @@ -1956,13 +1957,13 @@ static void addPath( } else state.allowAndSetStorePathString(*expectedStorePath, v); } catch (Error & e) { - e.addTrace(pos, "while adding path '%s'", path); + e.addTrace(state.positions[pos], "while adding path '%s'", path); throw; } } -static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; Path path = state.coerceToPath(pos, *args[1], context); @@ -1973,7 +1974,7 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args .msg = hintfmt( "first argument in call to 'filterSource' is not a function but %1%", showType(*args[0])), - .errPos = pos + .errPos = state.positions[pos] }); addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context); @@ -2034,7 +2035,7 @@ static RegisterPrimOp primop_filterSource({ .fun = prim_filterSource, }); -static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); Path path; @@ -2047,26 +2048,26 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value for (auto & attr : *args[0]->attrs) { auto & n(attr.name); if (n == "path") - path = state.coerceToPath(*attr.pos, *attr.value, context); + path = state.coerceToPath(attr.pos, *attr.value, context); else if (attr.name == state.sName) - name = state.forceStringNoCtx(*attr.value, *attr.pos); + name = state.forceStringNoCtx(*attr.value, attr.pos); else if (n == "filter") { state.forceValue(*attr.value, pos); filterFun = attr.value; } else if (n == "recursive") - method = FileIngestionMethod { state.forceBool(*attr.value, *attr.pos) }; + method = FileIngestionMethod { state.forceBool(*attr.value, attr.pos) }; else if (n == "sha256") - expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); + expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256); else throw EvalError({ .msg = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name), - .errPos = *attr.pos + .errPos = state.positions[attr.pos] }); } if (path.empty()) throw EvalError({ .msg = hintfmt("'path' required"), - .errPos = pos + .errPos = state.positions[pos] }); if (name.empty()) name = baseNameOf(path); @@ -2117,7 +2118,7 @@ static RegisterPrimOp primop_path({ /* Return the names of the attributes in a set as a sorted list of strings. */ -static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_attrNames(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -2144,7 +2145,7 @@ static RegisterPrimOp primop_attrNames({ /* Return the values of the attributes in a set as a list, in the same order as attrNames. */ -static void prim_attrValues(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_attrValues(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -2175,7 +2176,7 @@ static RegisterPrimOp primop_attrValues({ }); /* Dynamic version of the `.' operator. */ -void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto attr = state.forceStringNoCtx(*args[0], pos); state.forceAttrs(*args[1], pos); @@ -2187,7 +2188,7 @@ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) pos ); // !!! add to stack trace? - if (state.countCalls && *i->pos != noPos) state.attrSelects[*i->pos]++; + if (state.countCalls && i->pos) state.attrSelects[i->pos]++; state.forceValue(*i->value, pos); v = *i->value; } @@ -2205,7 +2206,7 @@ static RegisterPrimOp primop_getAttr({ }); /* Return position information of the specified attribute. */ -static void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto attr = state.forceStringNoCtx(*args[0], pos); state.forceAttrs(*args[1], pos); @@ -2223,7 +2224,7 @@ static RegisterPrimOp primop_unsafeGetAttrPos(RegisterPrimOp::Info { }); /* Dynamic version of the `?' operator. */ -static void prim_hasAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_hasAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto attr = state.forceStringNoCtx(*args[0], pos); state.forceAttrs(*args[1], pos); @@ -2242,7 +2243,7 @@ static RegisterPrimOp primop_hasAttr({ }); /* Determine whether the argument is a set. */ -static void prim_isAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nAttrs); @@ -2257,7 +2258,7 @@ static RegisterPrimOp primop_isAttrs({ .fun = prim_isAttrs, }); -static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_removeAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); state.forceList(*args[1], pos); @@ -2305,7 +2306,7 @@ static RegisterPrimOp primop_removeAttrs({ "nameN"; value = valueN;}] is transformed to {name1 = value1; ... nameN = valueN;}. In case of duplicate occurrences of the same name, the first takes precedence. */ -static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); @@ -2324,7 +2325,7 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, pos ); - auto name = state.forceStringNoCtx(*j->value, *j->pos); + auto name = state.forceStringNoCtx(*j->value, j->pos); Symbol sym = state.symbols.create(name); if (seen.insert(sym).second) { @@ -2367,7 +2368,7 @@ static RegisterPrimOp primop_listToAttrs({ .fun = prim_listToAttrs, }); -static void prim_intersectAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); state.forceAttrs(*args[1], pos); @@ -2393,7 +2394,7 @@ static RegisterPrimOp primop_intersectAttrs({ .fun = prim_intersectAttrs, }); -static void prim_catAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_catAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { Symbol attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos)); state.forceList(*args[1], pos); @@ -2430,7 +2431,7 @@ static RegisterPrimOp primop_catAttrs({ .fun = prim_catAttrs, }); -static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) { @@ -2440,7 +2441,7 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args if (!args[0]->isLambda()) throw TypeError({ .msg = hintfmt("'functionArgs' requires a function"), - .errPos = pos + .errPos = state.positions[pos] }); if (!args[0]->lambda.fun->hasFormals()) { @@ -2451,7 +2452,7 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args auto attrs = state.buildBindings(args[0]->lambda.fun->formals->formals.size()); for (auto & i : args[0]->lambda.fun->formals->formals) // !!! should optimise booleans (allocate only once) - attrs.alloc(i.name, ptr(&i.pos)).mkBool(i.def); + attrs.alloc(i.name, i.pos).mkBool(i.def); v.mkAttrs(attrs); } @@ -2473,7 +2474,7 @@ static RegisterPrimOp primop_functionArgs({ }); /* */ -static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_mapAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[1], pos); @@ -2505,7 +2506,7 @@ static RegisterPrimOp primop_mapAttrs({ .fun = prim_mapAttrs, }); -static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * args, Value & v) { // we will first count how many values are present for each given key. // we then allocate a single attrset and pre-populate it with lists of @@ -2528,7 +2529,7 @@ static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args for (auto & attr : *vElem->attrs) attrsSeen[attr.name].first++; } catch (TypeError & e) { - e.addTrace(pos, hintfmt("while invoking '%s'", "zipAttrsWith")); + e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith")); throw; } } @@ -2597,7 +2598,7 @@ static RegisterPrimOp primop_zipAttrsWith({ /* Determine whether the argument is a list. */ -static void prim_isList(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_isList(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); v.mkBool(args[0]->type() == nList); @@ -2612,20 +2613,20 @@ static RegisterPrimOp primop_isList({ .fun = prim_isList, }); -static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Value & v) +static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Value & v) { state.forceList(list, pos); if (n < 0 || (unsigned int) n >= list.listSize()) throw Error({ .msg = hintfmt("list index %1% is out of bounds", n), - .errPos = pos + .errPos = state.positions[pos] }); state.forceValue(*list.listElems()[n], pos); v = *list.listElems()[n]; } /* Return the n-1'th element of a list. */ -static void prim_elemAt(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_elemAt(EvalState & state, const PosIdx pos, Value * * args, Value & v) { elemAt(state, pos, *args[0], state.forceInt(*args[1], pos), v); } @@ -2641,7 +2642,7 @@ static RegisterPrimOp primop_elemAt({ }); /* Return the first element of a list. */ -static void prim_head(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_head(EvalState & state, const PosIdx pos, Value * * args, Value & v) { elemAt(state, pos, *args[0], 0, v); } @@ -2660,13 +2661,13 @@ static RegisterPrimOp primop_head({ /* Return a list consisting of everything but the first element of a list. Warning: this function takes O(n) time, so you probably don't want to use it! */ -static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); if (args[0]->listSize() == 0) throw Error({ .msg = hintfmt("'tail' called on an empty list"), - .errPos = pos + .errPos = state.positions[pos] }); state.mkList(v, args[0]->listSize() - 1); @@ -2691,7 +2692,7 @@ static RegisterPrimOp primop_tail({ }); /* Apply a function to every element of a list. */ -static void prim_map(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_map(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[1], pos); @@ -2721,7 +2722,7 @@ static RegisterPrimOp primop_map({ /* Filter a list using a predicate; that is, return a list containing every element from the list for which the predicate function returns true. */ -static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_filter(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -2759,7 +2760,7 @@ static RegisterPrimOp primop_filter({ }); /* Return true if a list contains a given element. */ -static void prim_elem(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_elem(EvalState & state, const PosIdx pos, Value * * args, Value & v) { bool res = false; state.forceList(*args[1], pos); @@ -2782,7 +2783,7 @@ static RegisterPrimOp primop_elem({ }); /* Concatenate a list of lists. */ -static void prim_concatLists(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_concatLists(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); state.concatLists(v, args[0]->listSize(), args[0]->listElems(), pos); @@ -2798,7 +2799,7 @@ static RegisterPrimOp primop_concatLists({ }); /* Return the length of a list. This is an O(1) time operation. */ -static void prim_length(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_length(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); v.mkInt(args[0]->listSize()); @@ -2815,7 +2816,7 @@ static RegisterPrimOp primop_length({ /* Reduce a list by applying a binary operator, from left to right. The operator is applied strictly. */ -static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[2], pos); @@ -2848,7 +2849,7 @@ static RegisterPrimOp primop_foldlStrict({ .fun = prim_foldlStrict, }); -static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * args, Value & v) +static void anyOrAll(bool any, EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -2867,7 +2868,7 @@ static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * arg } -static void prim_any(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_any(EvalState & state, const PosIdx pos, Value * * args, Value & v) { anyOrAll(true, state, pos, args, v); } @@ -2882,7 +2883,7 @@ static RegisterPrimOp primop_any({ .fun = prim_any, }); -static void prim_all(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_all(EvalState & state, const PosIdx pos, Value * * args, Value & v) { anyOrAll(false, state, pos, args, v); } @@ -2897,14 +2898,14 @@ static RegisterPrimOp primop_all({ .fun = prim_all, }); -static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto len = state.forceInt(*args[1], pos); if (len < 0) throw EvalError({ .msg = hintfmt("cannot create list of size %1%", len), - .errPos = pos + .errPos = state.positions[pos] }); state.mkList(v, len); @@ -2932,10 +2933,10 @@ static RegisterPrimOp primop_genList({ .fun = prim_genList, }); -static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v); +static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v); -static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -2986,7 +2987,7 @@ static RegisterPrimOp primop_sort({ .fun = prim_sort, }); -static void prim_partition(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_partition(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -3046,7 +3047,7 @@ static RegisterPrimOp primop_partition({ .fun = prim_partition, }); -static void prim_groupBy(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_groupBy(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -3098,7 +3099,7 @@ static RegisterPrimOp primop_groupBy({ .fun = prim_groupBy, }); -static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -3113,7 +3114,7 @@ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, V try { state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos))); } catch (TypeError &e) { - e.addTrace(pos, hintfmt("while invoking '%s'", "concatMap")); + e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap")); throw; } len += lists[n].listSize(); @@ -3145,7 +3146,7 @@ static RegisterPrimOp primop_concatMap({ *************************************************************/ -static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_add(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3164,7 +3165,7 @@ static RegisterPrimOp primop_add({ .fun = prim_add, }); -static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_sub(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3183,7 +3184,7 @@ static RegisterPrimOp primop_sub({ .fun = prim_sub, }); -static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_mul(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3202,7 +3203,7 @@ static RegisterPrimOp primop_mul({ .fun = prim_mul, }); -static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3211,7 +3212,7 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & if (f2 == 0) throw EvalError({ .msg = hintfmt("division by zero"), - .errPos = pos + .errPos = state.positions[pos] }); if (args[0]->type() == nFloat || args[1]->type() == nFloat) { @@ -3223,7 +3224,7 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value & if (i1 == std::numeric_limits::min() && i2 == -1) throw EvalError({ .msg = hintfmt("overflow in integer division"), - .errPos = pos + .errPos = state.positions[pos] }); v.mkInt(i1 / i2); @@ -3239,7 +3240,7 @@ static RegisterPrimOp primop_div({ .fun = prim_div, }); -static void prim_bitAnd(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_bitAnd(EvalState & state, const PosIdx pos, Value * * args, Value & v) { v.mkInt(state.forceInt(*args[0], pos) & state.forceInt(*args[1], pos)); } @@ -3253,7 +3254,7 @@ static RegisterPrimOp primop_bitAnd({ .fun = prim_bitAnd, }); -static void prim_bitOr(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_bitOr(EvalState & state, const PosIdx pos, Value * * args, Value & v) { v.mkInt(state.forceInt(*args[0], pos) | state.forceInt(*args[1], pos)); } @@ -3267,7 +3268,7 @@ static RegisterPrimOp primop_bitOr({ .fun = prim_bitOr, }); -static void prim_bitXor(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_bitXor(EvalState & state, const PosIdx pos, Value * * args, Value & v) { v.mkInt(state.forceInt(*args[0], pos) ^ state.forceInt(*args[1], pos)); } @@ -3281,7 +3282,7 @@ static RegisterPrimOp primop_bitXor({ .fun = prim_bitXor, }); -static void prim_lessThan(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceValue(*args[0], pos); state.forceValue(*args[1], pos); @@ -3309,7 +3310,7 @@ static RegisterPrimOp primop_lessThan({ /* Convert the argument to a string. Paths are *not* copied to the store, so `toString /foo/bar' yields `"/foo/bar"', not `"/nix/store/whatever..."'. */ -static void prim_toString(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context, true, false); @@ -3344,7 +3345,7 @@ static RegisterPrimOp primop_toString({ at character position `min(start, stringLength str)' inclusive and ending at `min(start + len, stringLength str)'. `start' must be non-negative. */ -static void prim_substring(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_substring(EvalState & state, const PosIdx pos, Value * * args, Value & v) { int start = state.forceInt(*args[0], pos); int len = state.forceInt(*args[1], pos); @@ -3354,7 +3355,7 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V if (start < 0) throw EvalError({ .msg = hintfmt("negative start position in 'substring'"), - .errPos = pos + .errPos = state.positions[pos] }); v.mkString((unsigned int) start >= s->size() ? "" : s->substr(start, len), context); @@ -3380,7 +3381,7 @@ static RegisterPrimOp primop_substring({ .fun = prim_substring, }); -static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_stringLength(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context); @@ -3398,14 +3399,14 @@ static RegisterPrimOp primop_stringLength({ }); /* Return the cryptographic hash of a string in base-16. */ -static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto type = state.forceStringNoCtx(*args[0], pos); std::optional ht = parseHashType(type); if (!ht) throw Error({ .msg = hintfmt("unknown hash type '%1%'", type), - .errPos = pos + .errPos = state.positions[pos] }); PathSet context; // discarded @@ -3446,7 +3447,7 @@ std::shared_ptr makeRegexCache() return std::make_shared(); } -void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto re = state.forceStringNoCtx(*args[0], pos); @@ -3478,12 +3479,12 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v) // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ throw EvalError({ .msg = hintfmt("memory limit exceeded by regular expression '%s'", re), - .errPos = pos + .errPos = state.positions[pos] }); } else { throw EvalError({ .msg = hintfmt("invalid regular expression '%s'", re), - .errPos = pos + .errPos = state.positions[pos] }); } } @@ -3527,7 +3528,7 @@ static RegisterPrimOp primop_match({ /* Split a string with a regular expression, and return a list of the non-matching parts interleaved by the lists of the matching groups. */ -void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v) +void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto re = state.forceStringNoCtx(*args[0], pos); @@ -3583,12 +3584,12 @@ void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v) // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ throw EvalError({ .msg = hintfmt("memory limit exceeded by regular expression '%s'", re), - .errPos = pos + .errPos = state.positions[pos] }); } else { throw EvalError({ .msg = hintfmt("invalid regular expression '%s'", re), - .errPos = pos + .errPos = state.positions[pos] }); } } @@ -3631,7 +3632,7 @@ static RegisterPrimOp primop_split({ .fun = prim_split, }); -static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_concatStringsSep(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; @@ -3661,14 +3662,14 @@ static RegisterPrimOp primop_concatStringsSep({ .fun = prim_concatStringsSep, }); -static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceList(*args[0], pos); state.forceList(*args[1], pos); if (args[0]->listSize() != args[1]->listSize()) throw EvalError({ .msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"), - .errPos = pos + .errPos = state.positions[pos] }); std::vector from; @@ -3741,7 +3742,7 @@ static RegisterPrimOp primop_replaceStrings({ *************************************************************/ -static void prim_parseDrvName(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_parseDrvName(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto name = state.forceStringNoCtx(*args[0], pos); DrvName parsed(name); @@ -3765,7 +3766,7 @@ static RegisterPrimOp primop_parseDrvName({ .fun = prim_parseDrvName, }); -static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_compareVersions(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto version1 = state.forceStringNoCtx(*args[0], pos); auto version2 = state.forceStringNoCtx(*args[1], pos); @@ -3785,7 +3786,7 @@ static RegisterPrimOp primop_compareVersions({ .fun = prim_compareVersions, }); -static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_splitVersion(EvalState & state, const PosIdx pos, Value * * args, Value & v) { auto version = state.forceStringNoCtx(*args[0], pos); auto iter = version.cbegin(); diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh index 905bd0366..1cfb4356b 100644 --- a/src/libexpr/primops.hh +++ b/src/libexpr/primops.hh @@ -38,9 +38,9 @@ struct RegisterPrimOp them. */ /* Load a ValueInitializer from a DSO and return whatever it initializes */ -void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v); +void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Value & v); /* Execute a program and parse its output */ -void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v); +void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v); } diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index cc74c7f58..5521586ee 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -5,7 +5,7 @@ namespace nix { -static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context); @@ -15,7 +15,7 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext); -static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; state.forceString(*args[0], context, pos); @@ -31,7 +31,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext); source-only deployment). This primop marks the string context so that builtins.derivation adds the path to drv.inputSrcs rather than drv.inputDrvs. */ -static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto s = state.coerceToString(pos, *args[0], context); @@ -65,7 +65,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutpu Note that for a given path any combination of the above attributes may be present. */ -static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { struct ContextInfo { bool path = false; @@ -134,7 +134,7 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext); See the commentary above unsafeGetContext for details of the context representation. */ -static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) { PathSet context; auto orig = state.forceString(*args[0], context, pos); @@ -147,24 +147,24 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg if (!state.store->isStorePath(i.name)) throw EvalError({ .msg = hintfmt("Context key '%s' is not a store path", i.name), - .errPos = *i.pos + .errPos = state.positions[i.pos] }); if (!settings.readOnlyMode) state.store->ensurePath(state.store->parseStorePath(i.name)); - state.forceAttrs(*i.value, *i.pos); + state.forceAttrs(*i.value, i.pos); auto iter = i.value->attrs->find(sPath); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos)) + if (state.forceBool(*iter->value, iter->pos)) context.insert(i.name); } iter = i.value->attrs->find(sAllOutputs); if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, *iter->pos)) { + if (state.forceBool(*iter->value, iter->pos)) { if (!isDerivation(i.name)) { throw EvalError({ .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), - .errPos = *i.pos + .errPos = state.positions[i.pos] }); } context.insert("=" + std::string(i.name)); @@ -173,15 +173,15 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg iter = i.value->attrs->find(state.sOutputs); if (iter != i.value->attrs->end()) { - state.forceList(*iter->value, *iter->pos); + state.forceList(*iter->value, iter->pos); if (iter->value->listSize() && !isDerivation(i.name)) { throw EvalError({ .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), - .errPos = *i.pos + .errPos = state.positions[i.pos] }); } for (auto elem : iter->value->listItems()) { - auto name = state.forceStringNoCtx(*elem, *iter->pos); + auto name = state.forceStringNoCtx(*elem, iter->pos); context.insert(concatStrings("!", name, "!", i.name)); } } diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 821eba698..90e9e6230 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -5,7 +5,7 @@ namespace nix { -static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) { state.forceAttrs(*args[0], pos); @@ -17,38 +17,38 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args for (auto & attr : *args[0]->attrs) { if (attr.name == "fromPath") { PathSet context; - fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context); + fromPath = state.coerceToStorePath(attr.pos, *attr.value, context); } else if (attr.name == "toPath") { - state.forceValue(*attr.value, *attr.pos); + state.forceValue(*attr.value, attr.pos); toCA = true; if (attr.value->type() != nString || attr.value->string.s != std::string("")) { PathSet context; - toPath = state.coerceToStorePath(*attr.pos, *attr.value, context); + toPath = state.coerceToStorePath(attr.pos, *attr.value, context); } } else if (attr.name == "fromStore") - fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos); + fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos); else throw Error({ .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name), - .errPos = pos + .errPos = state.positions[pos] }); } if (!fromPath) throw Error({ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"), - .errPos = pos + .errPos = state.positions[pos] }); if (!fromStoreUrl) throw Error({ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"), - .errPos = pos + .errPos = state.positions[pos] }); auto parsedURL = parseURL(*fromStoreUrl); @@ -58,13 +58,13 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args !(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file")) throw Error({ .msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"), - .errPos = pos + .errPos = state.positions[pos] }); if (!parsedURL.query.empty()) throw Error({ .msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl), - .errPos = pos + .errPos = state.positions[pos] }); auto fromStore = openStore(parsedURL.to_string()); @@ -80,7 +80,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args state.store->printStorePath(*fromPath), state.store->printStorePath(i->second), state.store->printStorePath(*toPath)), - .errPos = pos + .errPos = state.positions[pos] }); if (!toPath) throw Error({ @@ -89,7 +89,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args "please set this in the 'toPath' attribute passed to 'fetchClosure'", state.store->printStorePath(*fromPath), state.store->printStorePath(i->second)), - .errPos = pos + .errPos = state.positions[pos] }); } } else { @@ -105,7 +105,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args throw Error({ .msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't", state.store->printStorePath(*toPath)), - .errPos = pos + .errPos = state.positions[pos] }); } diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index b7f715859..252492446 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -7,7 +7,7 @@ namespace nix { -static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * args, Value & v) { std::string url; std::optional rev; @@ -24,29 +24,29 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar for (auto & attr : *args[0]->attrs) { std::string_view n(attr.name); if (n == "url") - url = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned(); + url = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned(); else if (n == "rev") { // Ugly: unlike fetchGit, here the "rev" attribute can // be both a revision or a branch/tag name. - auto value = state.forceStringNoCtx(*attr.value, *attr.pos); + auto value = state.forceStringNoCtx(*attr.value, attr.pos); if (std::regex_match(value.begin(), value.end(), revRegex)) rev = Hash::parseAny(value, htSHA1); else ref = value; } else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos); + name = state.forceStringNoCtx(*attr.value, attr.pos); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), - .errPos = *attr.pos + .errPos = state.positions[attr.pos] }); } if (url.empty()) throw EvalError({ .msg = hintfmt("'url' argument required"), - .errPos = pos + .errPos = state.positions[pos] }); } else diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 42c98e312..cdcae97b6 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -90,7 +90,7 @@ struct FetchTreeParams { static void fetchTree( EvalState & state, - const Pos & pos, + const PosIdx pos, Value * * args, Value & v, std::optional type, @@ -110,22 +110,22 @@ static void fetchTree( if (type) throw Error({ .msg = hintfmt("unexpected attribute 'type'"), - .errPos = pos + .errPos = state.positions[pos] }); - type = state.forceStringNoCtx(*aType->value, *aType->pos); + type = state.forceStringNoCtx(*aType->value, aType->pos); } else if (!type) throw Error({ .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), - .errPos = pos + .errPos = state.positions[pos] }); attrs.emplace("type", type.value()); for (auto & attr : *args[0]->attrs) { if (attr.name == state.sType) continue; - state.forceValue(*attr.value, *attr.pos); + state.forceValue(*attr.value, attr.pos); if (attr.value->type() == nPath || attr.value->type() == nString) { - auto s = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned(); + auto s = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned(); attrs.emplace(attr.name, attr.name == "url" ? type == "git" @@ -146,7 +146,7 @@ static void fetchTree( if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) throw Error({ .msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"), - .errPos = pos + .errPos = state.positions[pos] }); input = fetchers::Input::fromAttrs(std::move(attrs)); @@ -167,7 +167,7 @@ static void fetchTree( input = lookupInRegistries(state.store, input).first; if (evalSettings.pureEval && !input.isLocked()) - throw Error("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", pos); + throw Error("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]); auto [tree, input2] = input.fetch(state.store); @@ -176,7 +176,7 @@ static void fetchTree( emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false); } -static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v) { settings.requireExperimentalFeature(Xp::Flakes); fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false }); @@ -185,7 +185,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V // FIXME: document static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree); -static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, +static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v, const std::string & who, bool unpack, std::string name) { std::optional url; @@ -200,22 +200,22 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, for (auto & attr : *args[0]->attrs) { std::string n(attr.name); if (n == "url") - url = state.forceStringNoCtx(*attr.value, *attr.pos); + url = state.forceStringNoCtx(*attr.value, attr.pos); else if (n == "sha256") - expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); + expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256); else if (n == "name") - name = state.forceStringNoCtx(*attr.value, *attr.pos); + name = state.forceStringNoCtx(*attr.value, attr.pos); else throw EvalError({ .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), - .errPos = *attr.pos + .errPos = state.positions[attr.pos] }); } if (!url) throw EvalError({ .msg = hintfmt("'url' argument required"), - .errPos = pos + .errPos = state.positions[pos] }); } else url = state.forceStringNoCtx(*args[0], pos); @@ -262,7 +262,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, state.allowAndSetStorePathString(storePath, v); } -static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, Value & v) { fetch(state, pos, args, v, "fetchurl", false, ""); } @@ -278,7 +278,7 @@ static RegisterPrimOp primop_fetchurl({ .fun = prim_fetchurl, }); -static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value * * args, Value & v) { fetch(state, pos, args, v, "fetchTarball", true, "source"); } @@ -329,7 +329,7 @@ static RegisterPrimOp primop_fetchTarball({ .fun = prim_fetchTarball, }); -static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Value & v) +static void prim_fetchGit(EvalState & state, const PosIdx pos, Value * * args, Value & v) { fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true }); } diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index dd4280030..9753e2ac9 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -5,7 +5,7 @@ namespace nix { -static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & val) +static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val) { auto toml = state.forceStringNoCtx(*args[0], pos); @@ -73,7 +73,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va } catch (std::exception & e) { // TODO: toml::syntax_error throw EvalError({ .msg = hintfmt("while parsing a TOML string: %s", e.what()), - .errPos = pos + .errPos = state.positions[pos] }); } } diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 7b35abca2..307934292 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -10,7 +10,7 @@ namespace nix { void printValueAsJSON(EvalState & state, bool strict, - Value & v, const Pos & pos, JSONPlaceholder & out, PathSet & context) + Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context) { checkInterrupt(); @@ -54,10 +54,10 @@ void printValueAsJSON(EvalState & state, bool strict, for (auto & j : names) { Attr & a(*v.attrs->find(state.symbols.create(j))); auto placeholder(obj.placeholder(j)); - printValueAsJSON(state, strict, *a.value, *a.pos, placeholder, context); + printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context); } } else - printValueAsJSON(state, strict, *i->value, *i->pos, out, context); + printValueAsJSON(state, strict, *i->value, i->pos, out, context); break; } @@ -82,15 +82,15 @@ void printValueAsJSON(EvalState & state, bool strict, case nFunction: auto e = TypeError({ .msg = hintfmt("cannot convert %1% to JSON", showType(v)), - .errPos = v.determinePos(pos) + .errPos = state.positions[v.determinePos(pos)] }); - e.addTrace(pos, hintfmt("message for the trace")); + e.addTrace(state.positions[pos], hintfmt("message for the trace")); throw e; } } void printValueAsJSON(EvalState & state, bool strict, - Value & v, const Pos & pos, std::ostream & str, PathSet & context) + Value & v, const PosIdx pos, std::ostream & str, PathSet & context) { JSONPlaceholder out(str); printValueAsJSON(state, strict, v, pos, out, context); diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh index c2f797b29..c020a817a 100644 --- a/src/libexpr/value-to-json.hh +++ b/src/libexpr/value-to-json.hh @@ -11,9 +11,9 @@ namespace nix { class JSONPlaceholder; void printValueAsJSON(EvalState & state, bool strict, - Value & v, const Pos & pos, JSONPlaceholder & out, PathSet & context); + Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context); void printValueAsJSON(EvalState & state, bool strict, - Value & v, const Pos & pos, std::ostream & str, PathSet & context); + Value & v, const PosIdx pos, std::ostream & str, PathSet & context); } diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc index 7f8edcba6..d1e0c4778 100644 --- a/src/libexpr/value-to-xml.cc +++ b/src/libexpr/value-to-xml.cc @@ -19,7 +19,7 @@ static XMLAttrs singletonAttrs(const std::string & name, const std::string & val static void printValueAsXML(EvalState & state, bool strict, bool location, Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, - const Pos & pos); + const PosIdx pos); static void posToXML(XMLAttrs & xmlAttrs, const Pos & pos) @@ -43,18 +43,18 @@ static void showAttrs(EvalState & state, bool strict, bool location, XMLAttrs xmlAttrs; xmlAttrs["name"] = i; - if (location && a.pos != ptr(&noPos)) posToXML(xmlAttrs, *a.pos); + if (location && a.pos) posToXML(xmlAttrs, state.positions[a.pos]); XMLOpenElement _(doc, "attr", xmlAttrs); printValueAsXML(state, strict, location, - *a.value, doc, context, drvsSeen, *a.pos); + *a.value, doc, context, drvsSeen, a.pos); } } static void printValueAsXML(EvalState & state, bool strict, bool location, Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, - const Pos & pos) + const PosIdx pos) { checkInterrupt(); @@ -93,14 +93,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, Path drvPath; a = v.attrs->find(state.sDrvPath); if (a != v.attrs->end()) { - if (strict) state.forceValue(*a->value, *a->pos); + if (strict) state.forceValue(*a->value, a->pos); if (a->value->type() == nString) xmlAttrs["drvPath"] = drvPath = a->value->string.s; } a = v.attrs->find(state.sOutPath); if (a != v.attrs->end()) { - if (strict) state.forceValue(*a->value, *a->pos); + if (strict) state.forceValue(*a->value, a->pos); if (a->value->type() == nString) xmlAttrs["outPath"] = a->value->string.s; } @@ -134,7 +134,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, break; } XMLAttrs xmlAttrs; - if (location) posToXML(xmlAttrs, v.lambda.fun->pos); + if (location) posToXML(xmlAttrs, state.positions[v.lambda.fun->pos]); XMLOpenElement _(doc, "function", xmlAttrs); if (v.lambda.fun->hasFormals()) { @@ -166,14 +166,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, void ExternalValueBase::printValueAsXML(EvalState & state, bool strict, bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, - const Pos & pos) const + const PosIdx pos) const { doc.writeEmptyElement("unevaluated"); } void printValueAsXML(EvalState & state, bool strict, bool location, - Value & v, std::ostream & out, PathSet & context, const Pos & pos) + Value & v, std::ostream & out, PathSet & context, const PosIdx pos) { XMLWriter doc(true, out); XMLOpenElement root(doc, "expr"); diff --git a/src/libexpr/value-to-xml.hh b/src/libexpr/value-to-xml.hh index cc778a2cb..506f32b6b 100644 --- a/src/libexpr/value-to-xml.hh +++ b/src/libexpr/value-to-xml.hh @@ -9,6 +9,6 @@ namespace nix { void printValueAsXML(EvalState & state, bool strict, bool location, - Value & v, std::ostream & out, PathSet & context, const Pos & pos); + Value & v, std::ostream & out, PathSet & context, const PosIdx pos); } diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 3d07c3198..dc875615c 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -56,6 +56,7 @@ struct Expr; struct ExprLambda; struct PrimOp; class Symbol; +class PosIdx; struct Pos; class StorePath; class Store; @@ -103,7 +104,7 @@ class ExternalValueBase /* Print the value as XML. Defaults to unevaluated */ virtual void printValueAsXML(EvalState & state, bool strict, bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, - const Pos & pos) const; + const PosIdx pos) const; virtual ~ExternalValueBase() { @@ -368,7 +369,7 @@ public: return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size; } - Pos determinePos(const Pos & pos) const; + PosIdx determinePos(const PosIdx pos) const; /* Check whether forcing this value requires a trivial amount of computation. In particular, function applications are diff --git a/src/libutil/types.hh b/src/libutil/types.hh index 00ba567c6..22bc2b8dd 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -102,4 +103,55 @@ public: Ptr operator->() const { return Ptr(**this); } }; +/* Provides an indexable container like vector<> with memory overhead + guarantees like list<> by allocating storage in chunks of ChunkSize + elements instead of using a contiguous memory allocation like vector<> + does. Not using a single vector that is resized reduces memory overhead + on large data sets by on average (growth factor)/2, mostly + eliminates copies within the vector during resizing, and provides stable + references to its elements. */ +template +class ChunkedVector { +private: + uint32_t size_ = 0; + std::vector> chunks; + + /* keep this out of the ::add hot path */ + [[gnu::noinline]] + auto & addChunk() + { + if (size_ >= std::numeric_limits::max() - ChunkSize) + abort(); + chunks.emplace_back(); + chunks.back().reserve(ChunkSize); + return chunks.back(); + } + +public: + ChunkedVector(uint32_t reserve) + { + chunks.reserve(reserve); + addChunk(); + } + + uint32_t size() const { return size_; } + + std::pair add(T value) + { + const auto idx = size_++; + auto & chunk = [&] () -> auto & { + if (auto & back = chunks.back(); back.size() < ChunkSize) + return back; + return addChunk(); + }(); + auto & result = chunk.emplace_back(std::move(value)); + return {result, idx}; + } + + const T & operator[](uint32_t idx) const + { + return chunks[idx / ChunkSize][idx % ChunkSize]; + } +}; + } diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc index 78692b9c6..156181634 100644 --- a/src/nix-env/user-env.cc +++ b/src/nix-env/user-env.cc @@ -134,9 +134,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems, state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); }); PathSet context; Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath)); - auto topLevelDrv = state.coerceToStorePath(*aDrvPath.pos, *aDrvPath.value, context); + auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context); Attr & aOutPath(*topLevel.attrs->find(state.sOutPath)); - auto topLevelOut = state.coerceToStorePath(*aOutPath.pos, *aOutPath.value, context); + auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context); /* Realise the resulting store expression. */ debug("building user environment"); diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc index ee91e8ed0..2421adf4e 100644 --- a/src/nix/bundle.cc +++ b/src/nix/bundle.cc @@ -97,13 +97,13 @@ struct CmdBundle : InstallableCommand throw Error("the bundler '%s' does not produce a derivation", bundler.what()); PathSet context2; - auto drvPath = evalState->coerceToStorePath(*attr1->pos, *attr1->value, context2); + auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2); auto attr2 = vRes->attrs->get(evalState->sOutPath); if (!attr2) throw Error("the bundler '%s' does not produce a derivation", bundler.what()); - auto outPath = evalState->coerceToStorePath(*attr2->pos, *attr2->value, context2); + auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2); store->buildPaths({ DerivedPath::Built { drvPath } }); @@ -113,7 +113,7 @@ struct CmdBundle : InstallableCommand auto * attr = vRes->attrs->get(evalState->sName); if (!attr) throw Error("attribute 'name' missing"); - outLink = evalState->forceStringNoCtx(*attr->value, *attr->pos); + outLink = evalState->forceStringNoCtx(*attr->value, attr->pos); } // TODO: will crash if not a localFSStore? diff --git a/src/nix/edit.cc b/src/nix/edit.cc index ffe79af89..76a134b1f 100644 --- a/src/nix/edit.cc +++ b/src/nix/edit.cc @@ -28,9 +28,9 @@ struct CmdEdit : InstallableCommand { auto state = getEvalState(); - auto [v, pos] = installable->toValue(*state); - const auto [file, line] = [&] { + auto [v, pos] = installable->toValue(*state); + try { return findPackageFilename(*state, *v, installable->what()); } catch (NoPositionInfo &) { diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 733b93661..81474c2d3 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -77,9 +77,9 @@ struct CmdEval : MixJSON, InstallableCommand if (pathExists(*writeTo)) throw Error("path '%s' already exists", *writeTo); - std::function recurse; + std::function recurse; - recurse = [&](Value & v, const Pos & pos, const Path & path) + recurse = [&](Value & v, const PosIdx pos, const Path & path) { state->forceValue(v, pos); if (v.type() == nString) @@ -92,14 +92,16 @@ struct CmdEval : MixJSON, InstallableCommand try { if (attr.name == "." || attr.name == "..") throw Error("invalid file name '%s'", attr.name); - recurse(*attr.value, *attr.pos, path + "/" + std::string(attr.name)); + recurse(*attr.value, attr.pos, path + "/" + std::string(attr.name)); } catch (Error & e) { - e.addTrace(*attr.pos, hintfmt("while evaluating the attribute '%s'", attr.name)); + e.addTrace( + state->positions[attr.pos], + hintfmt("while evaluating the attribute '%s'", attr.name)); throw; } } else - throw TypeError("value at '%s' is not a string or an attribute set", pos); + throw TypeError("value at '%s' is not a string or an attribute set", state->positions[pos]); }; recurse(*v, pos, *writeTo); diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 04b23ed0f..23e5cd24e 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -123,7 +123,7 @@ struct CmdFlakeLock : FlakeCommand }; static void enumerateOutputs(EvalState & state, Value & vFlake, - std::function callback) + std::function callback) { auto pos = vFlake.determinePos(noPos); state.forceAttrs(vFlake, pos); @@ -139,11 +139,11 @@ static void enumerateOutputs(EvalState & state, Value & vFlake, else. This way we can disable IFD for hydraJobs and then enable it for other outputs. */ if (auto attr = aOutputs->value->attrs->get(sHydraJobs)) - callback(attr->name, *attr->value, *attr->pos); + callback(attr->name, *attr->value, attr->pos); for (auto & attr : *aOutputs->value->attrs) { if (attr.name != sHydraJobs) - callback(attr.name, *attr.value, *attr.pos); + callback(attr.name, *attr.value, attr.pos); } } @@ -315,13 +315,17 @@ struct CmdFlakeCheck : FlakeCommand // FIXME: rewrite to use EvalCache. - auto checkSystemName = [&](const std::string & system, const Pos & pos) { - // FIXME: what's the format of "system"? - if (system.find('-') == std::string::npos) - reportError(Error("'%s' is not a valid system type, at %s", system, pos)); + auto resolve = [&] (PosIdx p) { + return state->positions[p]; }; - auto checkDerivation = [&](const std::string & attrPath, Value & v, const Pos & pos) -> std::optional { + auto checkSystemName = [&](const std::string & system, const PosIdx pos) { + // FIXME: what's the format of "system"? + if (system.find('-') == std::string::npos) + reportError(Error("'%s' is not a valid system type, at %s", system, resolve(pos))); + }; + + auto checkDerivation = [&](const std::string & attrPath, Value & v, const PosIdx pos) -> std::optional { try { auto drvInfo = getDerivation(*state, v, false); if (!drvInfo) @@ -329,7 +333,7 @@ struct CmdFlakeCheck : FlakeCommand // FIXME: check meta attributes return drvInfo->queryDrvPath(); } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the derivation '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the derivation '%s'", attrPath)); reportError(e); } return std::nullopt; @@ -337,7 +341,7 @@ struct CmdFlakeCheck : FlakeCommand std::vector drvPaths; - auto checkApp = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkApp = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { #if 0 // FIXME @@ -348,12 +352,12 @@ struct CmdFlakeCheck : FlakeCommand } #endif } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the app definition '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the app definition '%s'", attrPath)); reportError(e); } }; - auto checkOverlay = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkOverlay = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { state->forceValue(v, pos); if (!v.isLambda() @@ -368,12 +372,12 @@ struct CmdFlakeCheck : FlakeCommand // FIXME: if we have a 'nixpkgs' input, use it to // evaluate the overlay. } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the overlay '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the overlay '%s'", attrPath)); reportError(e); } }; - auto checkModule = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkModule = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { state->forceValue(v, pos); if (v.isLambda()) { @@ -382,9 +386,11 @@ struct CmdFlakeCheck : FlakeCommand } else if (v.type() == nAttrs) { for (auto & attr : *v.attrs) try { - state->forceValue(*attr.value, *attr.pos); + state->forceValue(*attr.value, attr.pos); } catch (Error & e) { - e.addTrace(*attr.pos, hintfmt("while evaluating the option '%s'", attr.name)); + e.addTrace( + state->positions[attr.pos], + hintfmt("while evaluating the option '%s'", attr.name)); throw; } } else @@ -392,14 +398,14 @@ struct CmdFlakeCheck : FlakeCommand // FIXME: if we have a 'nixpkgs' input, use it to // check the module. } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the NixOS module '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the NixOS module '%s'", attrPath)); reportError(e); } }; - std::function checkHydraJobs; + std::function checkHydraJobs; - checkHydraJobs = [&](const std::string & attrPath, Value & v, const Pos & pos) { + checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { state->forceAttrs(v, pos); @@ -407,23 +413,23 @@ struct CmdFlakeCheck : FlakeCommand throw Error("jobset should not be a derivation at top-level"); for (auto & attr : *v.attrs) { - state->forceAttrs(*attr.value, *attr.pos); + state->forceAttrs(*attr.value, attr.pos); auto attrPath2 = attrPath + "." + (std::string) attr.name; if (state->isDerivation(*attr.value)) { Activity act(*logger, lvlChatty, actUnknown, fmt("checking Hydra job '%s'", attrPath2)); - checkDerivation(attrPath2, *attr.value, *attr.pos); + checkDerivation(attrPath2, *attr.value, attr.pos); } else - checkHydraJobs(attrPath2, *attr.value, *attr.pos); + checkHydraJobs(attrPath2, *attr.value, attr.pos); } } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the Hydra jobset '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the Hydra jobset '%s'", attrPath)); reportError(e); } }; - auto checkNixOSConfiguration = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkNixOSConfiguration = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { Activity act(*logger, lvlChatty, actUnknown, fmt("checking NixOS configuration '%s'", attrPath)); @@ -433,12 +439,12 @@ struct CmdFlakeCheck : FlakeCommand if (!state->isDerivation(*vToplevel)) throw Error("attribute 'config.system.build.toplevel' is not a derivation"); } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the NixOS configuration '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the NixOS configuration '%s'", attrPath)); reportError(e); } }; - auto checkTemplate = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkTemplate = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { Activity act(*logger, lvlChatty, actUnknown, fmt("checking template '%s'", attrPath)); @@ -448,7 +454,7 @@ struct CmdFlakeCheck : FlakeCommand if (auto attr = v.attrs->get(state->symbols.create("path"))) { if (attr->name == state->symbols.create("path")) { PathSet context; - auto path = state->coerceToPath(*attr->pos, *attr->value, context); + auto path = state->coerceToPath(attr->pos, *attr->value, context); if (!store->isInStore(path)) throw Error("template '%s' has a bad 'path' attribute"); // TODO: recursively check the flake in 'path'. @@ -457,7 +463,7 @@ struct CmdFlakeCheck : FlakeCommand throw Error("template '%s' lacks attribute 'path'", attrPath); if (auto attr = v.attrs->get(state->symbols.create("description"))) - state->forceStringNoCtx(*attr->value, *attr->pos); + state->forceStringNoCtx(*attr->value, attr->pos); else throw Error("template '%s' lacks attribute 'description'", attrPath); @@ -467,19 +473,19 @@ struct CmdFlakeCheck : FlakeCommand throw Error("template '%s' has unsupported attribute '%s'", attrPath, name); } } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the template '%s'", attrPath)); reportError(e); } }; - auto checkBundler = [&](const std::string & attrPath, Value & v, const Pos & pos) { + auto checkBundler = [&](const std::string & attrPath, Value & v, const PosIdx pos) { try { state->forceValue(v, pos); if (!v.isLambda()) throw Error("bundler must be a function"); // TODO: check types of inputs/outputs? } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath)); + e.addTrace(resolve(pos), hintfmt("while checking the template '%s'", attrPath)); reportError(e); } }; @@ -492,7 +498,7 @@ struct CmdFlakeCheck : FlakeCommand enumerateOutputs(*state, *vFlake, - [&](const std::string & name, Value & vOutput, const Pos & pos) { + [&](const std::string & name, Value & vOutput, const PosIdx pos) { Activity act(*logger, lvlChatty, actUnknown, fmt("checking flake output '%s'", name)); @@ -516,12 +522,12 @@ struct CmdFlakeCheck : FlakeCommand if (name == "checks") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + checkSystemName(attr.name, attr.pos); + state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) { auto drvPath = checkDerivation( fmt("%s.%s.%s", name, attr.name, attr2.name), - *attr2.value, *attr2.pos); + *attr2.value, attr2.pos); if (drvPath && (std::string) attr.name == settings.thisSystem.get()) drvPaths.push_back(DerivedPath::Built{*drvPath}); } @@ -531,61 +537,61 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "formatter") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); + checkSystemName(attr.name, attr.pos); checkApp( fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + *attr.value, attr.pos); } } else if (name == "packages" || name == "devShells") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + checkSystemName(attr.name, attr.pos); + state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) checkDerivation( fmt("%s.%s.%s", name, attr.name, attr2.name), - *attr2.value, *attr2.pos); + *attr2.value, attr2.pos); } } else if (name == "apps") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + checkSystemName(attr.name, attr.pos); + state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) checkApp( fmt("%s.%s.%s", name, attr.name, attr2.name), - *attr2.value, *attr2.pos); + *attr2.value, attr2.pos); } } else if (name == "defaultPackage" || name == "devShell") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); + checkSystemName(attr.name, attr.pos); checkDerivation( fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + *attr.value, attr.pos); } } else if (name == "defaultApp") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); + checkSystemName(attr.name, attr.pos); checkApp( fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + *attr.value, attr.pos); } } else if (name == "legacyPackages") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); + checkSystemName(attr.name, attr.pos); // FIXME: do getDerivations? } } @@ -597,7 +603,7 @@ struct CmdFlakeCheck : FlakeCommand state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) checkOverlay(fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + *attr.value, attr.pos); } else if (name == "nixosModule") @@ -607,14 +613,14 @@ struct CmdFlakeCheck : FlakeCommand state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) checkModule(fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + *attr.value, attr.pos); } else if (name == "nixosConfigurations") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) checkNixOSConfiguration(fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + *attr.value, attr.pos); } else if (name == "hydraJobs") @@ -627,28 +633,28 @@ struct CmdFlakeCheck : FlakeCommand state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) checkTemplate(fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + *attr.value, attr.pos); } else if (name == "defaultBundler") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); + checkSystemName(attr.name, attr.pos); checkBundler( fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + *attr.value, attr.pos); } } else if (name == "bundlers") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, *attr.pos); - state->forceAttrs(*attr.value, *attr.pos); + checkSystemName(attr.name, attr.pos); + state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) { checkBundler( fmt("%s.%s.%s", name, attr.name, attr2.name), - *attr2.value, *attr2.pos); + *attr2.value, attr2.pos); } } } @@ -657,7 +663,7 @@ struct CmdFlakeCheck : FlakeCommand warn("unknown flake output '%s'", name); } catch (Error & e) { - e.addTrace(pos, hintfmt("while checking flake output '%s'", name)); + e.addTrace(resolve(pos), hintfmt("while checking flake output '%s'", name)); reportError(e); } }); diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 391255ce9..fd1b95afa 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -467,7 +467,8 @@ bool NixRepl::processLine(std::string line) auto filename = state->coerceToString(noPos, v, context); return {state->symbols.create(*filename), 0}; } else if (v.isLambda()) { - return {v.lambda.fun->pos.file, v.lambda.fun->pos.line}; + auto pos = state->positions[v.lambda.fun->pos]; + return {pos.file, pos.line}; } else { // assume it's a derivation return findPackageFilename(*state, v, arg); @@ -498,7 +499,7 @@ bool NixRepl::processLine(std::string line) Value v, f, result; evalString(arg, v); evalString("drv: (import {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f); - state->callFunction(f, v, result, Pos()); + state->callFunction(f, v, result, PosIdx()); StorePath drvPath = getDerivationPath(result); runNix("nix-shell", {state->store->printStorePath(drvPath)}); @@ -799,7 +800,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m 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)); + str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context)); else str << "???"; str << "»"; @@ -861,7 +862,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m case nFunction: if (v.isLambda()) { std::ostringstream s; - s << v.lambda.fun->pos; + 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; diff --git a/tests/plugins/plugintest.cc b/tests/plugins/plugintest.cc index cd7c9f8b1..04b791021 100644 --- a/tests/plugins/plugintest.cc +++ b/tests/plugins/plugintest.cc @@ -13,7 +13,7 @@ MySettings mySettings; static GlobalConfig::Register rs(&mySettings); -static void prim_anotherNull (EvalState & state, const Pos & pos, Value ** args, Value & v) +static void prim_anotherNull (EvalState & state, const PosIdx pos, Value ** args, Value & v) { if (mySettings.settingSet) v.mkNull(); From 00a32802328b58daa7af48ccac60f6154ef05639 Mon Sep 17 00:00:00 2001 From: pennae Date: Sat, 5 Mar 2022 17:31:50 +0100 Subject: [PATCH 25/79] don't use Symbol in Pos to represent a path PosTable deduplicates origin information, so using symbols for paths is no longer necessary. moving away from path Symbols also reduces the usage of symbols for things that are not keys in attribute sets, which will become important in the future when we turn symbols into indices as well. --- src/libexpr/eval.cc | 4 ++-- src/libexpr/eval.hh | 2 ++ src/libexpr/nixexpr.hh | 6 +++--- src/libexpr/parser.y | 6 +++--- src/libexpr/primops.cc | 2 +- src/libutil/error.hh | 6 +----- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index e6314f63e..39f1a625f 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -934,7 +934,7 @@ void EvalState::mkThunk_(Value & v, Expr * expr) void EvalState::mkPos(Value & v, PosIdx p) { auto pos = positions[p]; - if (pos.file.set()) { + if (!pos.file.empty()) { auto attrs = buildBindings(3); attrs.alloc(sFile).mkString(pos.file); attrs.alloc(sLine).mkInt(pos.line); @@ -1296,7 +1296,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) } catch (Error & e) { auto pos2r = state.positions[pos2]; - if (pos2 && pos2r.file != state.sDerivationNix) + if (pos2 && pos2r.file != state.derivationNixPath) state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'", showAttrPath(state, env, attrPath)); throw; diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index b05e8d5d0..71d6e7e7f 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -75,6 +75,8 @@ public: SymbolTable symbols; PosTable positions; + static inline std::string derivationNixPath = "//builtin/derivation.nix"; + const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, sFile, sLine, sColumn, sFunctor, sToString, diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index d9392cff5..2e12f0b4f 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -26,7 +26,7 @@ MakeError(RestrictedPathError, Error); struct Pos { - Symbol file; + std::string file; FileOrigin origin; uint32_t line; uint32_t column; @@ -64,10 +64,10 @@ public: explicit Origin(uint32_t idx): idx(idx), file{}, origin{} {} public: - const Symbol file; + const std::string file; const FileOrigin origin; - Origin(Symbol file, FileOrigin origin): file(file), origin(origin) {} + Origin(std::string file, FileOrigin origin): file(std::move(file)), origin(origin) {} }; struct Offset { diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 0052a8070..1f950a057 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -648,14 +648,14 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, const PathView path, const PathView basePath, StaticEnv & staticEnv) { yyscan_t scanner; - Symbol file; + std::string file; switch (origin) { case foFile: - file = symbols.create(path); + file = path; break; case foStdin: case foString: - file = symbols.create(text); + file = text; break; default: assert(false); diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 9cdcfd0b9..81fb5acfb 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -3915,7 +3915,7 @@ void EvalState::createBaseEnv() /* Add a wrapper around the derivation primop that computes the `drvPath' and `outPath' attributes lazily. */ - sDerivationNix = symbols.create("//builtin/derivation.nix"); + sDerivationNix = symbols.create(derivationNixPath); auto vDerivation = allocValue(); addConstant("derivation", vDerivation); diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 348018f57..f4706e3ed 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -87,11 +87,7 @@ struct ErrPos { origin = pos.origin; line = pos.line; column = pos.column; - // is file symbol null? - if (pos.file.set()) - file = pos.file; - else - file = ""; + file = pos.file; return *this; } From 8775be33931ec3b1cad97035ff3d5370a97178a1 Mon Sep 17 00:00:00 2001 From: pennae Date: Sat, 5 Mar 2022 14:40:24 +0100 Subject: [PATCH 26/79] store Symbols in a table as well, like positions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this slightly increases the amount of memory used for any given symbol, but this increase is more than made up for if the symbol is referenced more than once in the EvalState that holds it. on average every symbol should be referenced at least twice (once to introduce a binding, once to use it), so we expect no increase in memory on average. symbol tables are limited to 2³² entries like position tables, and similar arguments apply to why overflow is not likely: 2³² symbols would require as many string instances (at 24 bytes each) and map entries (at 24 bytes or more each, assuming that the map holds on average at most one item per bucket as the docs say). a full symbol table would require at least 192GB of memory just for symbols, which is well out of reach. (an ofborg eval of nixpks today creates less than a million symbols!) --- src/libcmd/installables.cc | 4 +- src/libexpr/attr-path.cc | 4 +- src/libexpr/attr-set.cc | 4 +- src/libexpr/attr-set.hh | 19 +- src/libexpr/eval-cache.cc | 28 +-- src/libexpr/eval-cache.hh | 8 +- src/libexpr/eval.cc | 84 ++++---- src/libexpr/eval.hh | 15 +- src/libexpr/flake/flake.cc | 44 ++-- src/libexpr/get-drvs.cc | 12 +- src/libexpr/nixexpr.cc | 283 ++++++++++++++----------- src/libexpr/nixexpr.hh | 68 +++--- src/libexpr/parser.y | 56 +++-- src/libexpr/primops.cc | 39 ++-- src/libexpr/primops/context.cc | 23 +- src/libexpr/primops/fetchClosure.cc | 10 +- src/libexpr/primops/fetchMercurial.cc | 4 +- src/libexpr/primops/fetchTree.cc | 14 +- src/libexpr/symbol-table.hh | 77 +++---- src/libexpr/value-to-json.cc | 2 +- src/libexpr/value-to-xml.cc | 16 +- src/libexpr/value.hh | 16 +- src/libutil/types.hh | 8 + src/nix-env/nix-env.cc | 2 +- src/nix-env/user-env.cc | 2 +- src/nix-instantiate/nix-instantiate.cc | 6 +- src/nix/app.cc | 6 +- src/nix/eval.cc | 14 +- src/nix/flake.cc | 80 +++---- src/nix/main.cc | 2 +- src/nix/repl.cc | 21 +- src/nix/search.cc | 2 +- 32 files changed, 525 insertions(+), 448 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 6197f4be4..c81f76a22 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -235,7 +235,7 @@ void SourceExprCommand::completeInstallable(std::string_view prefix) if (v2.type() == nAttrs) { for (auto & i : *v2.attrs) { - std::string name = i.name; + std::string name = state->symbols[i.name]; if (name.find(searchWord) == 0) { if (prefix_ == "") completions->add(name); @@ -600,7 +600,7 @@ std::tuple InstallableF auto drvInfo = DerivationInfo { std::move(drvPath), - attr->getAttr(state->sOutputName)->getString() + attr->getAttr("outputName")->getString() }; return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)}; diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index 1c12dfbe2..f63caeb10 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -36,7 +36,7 @@ std::vector parseAttrPath(EvalState & state, std::string_view s) { std::vector res; for (auto & a : parseAttrPath(s)) - res.push_back(state.symbols.create(a)); + res.emplace_back(a); return res; } @@ -77,7 +77,7 @@ std::pair findAlongAttrPath(EvalState & state, const std::strin if (a == v->attrs->end()) { std::set attrNames; for (auto & attr : *v->attrs) - attrNames.insert(attr.name); + attrNames.insert(state.symbols[attr.name]); auto suggestions = Suggestions::bestMatches(attrNames, attr); throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath); diff --git a/src/libexpr/attr-set.cc b/src/libexpr/attr-set.cc index 61996eae4..1d17ef7e4 100644 --- a/src/libexpr/attr-set.cc +++ b/src/libexpr/attr-set.cc @@ -26,7 +26,7 @@ Bindings * EvalState::allocBindings(size_t capacity) /* Create a new attribute named 'name' on an existing attribute set stored in 'vAttrs' and return the newly allocated Value which is associated with this attribute. */ -Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name) +Value * EvalState::allocAttr(Value & vAttrs, const SymbolIdx & name) { Value * v = allocValue(); vAttrs.attrs->push_back(Attr(name, v)); @@ -40,7 +40,7 @@ Value * EvalState::allocAttr(Value & vAttrs, std::string_view name) } -Value & BindingsBuilder::alloc(const Symbol & name, PosIdx pos) +Value & BindingsBuilder::alloc(const SymbolIdx & name, PosIdx pos) { auto value = state.allocValue(); bindings->push_back(Attr(name, value, pos)); diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh index 23d68dda2..c42aba0f6 100644 --- a/src/libexpr/attr-set.hh +++ b/src/libexpr/attr-set.hh @@ -15,10 +15,10 @@ struct Value; /* Map one attribute name to its value. */ struct Attr { - Symbol name; + SymbolIdx name; Value * value; PosIdx pos; - Attr(Symbol name, Value * value, PosIdx pos = noPos) + Attr(SymbolIdx name, Value * value, PosIdx pos = noPos) : name(name), value(value), pos(pos) { }; Attr() { }; bool operator < (const Attr & a) const @@ -57,7 +57,7 @@ public: attrs[size_++] = attr; } - iterator find(const Symbol & name) + iterator find(const SymbolIdx & name) { Attr key(name, 0); iterator i = std::lower_bound(begin(), end(), key); @@ -65,7 +65,7 @@ public: return end(); } - Attr * get(const Symbol & name) + Attr * get(const SymbolIdx & name) { Attr key(name, 0); iterator i = std::lower_bound(begin(), end(), key); @@ -86,14 +86,15 @@ public: size_t capacity() { return capacity_; } /* Returns the attributes in lexicographically sorted order. */ - std::vector lexicographicOrder() const + std::vector lexicographicOrder(const SymbolTable & symbols) const { std::vector res; res.reserve(size_); for (size_t n = 0; n < size_; n++) res.emplace_back(&attrs[n]); - std::sort(res.begin(), res.end(), [](const Attr * a, const Attr * b) { - return (const std::string &) a->name < (const std::string &) b->name; + std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) { + std::string_view sa = symbols[a->name], sb = symbols[b->name]; + return sa < sb; }); return res; } @@ -118,7 +119,7 @@ public: : bindings(bindings), state(state) { } - void insert(Symbol name, Value * value, PosIdx pos = noPos) + void insert(SymbolIdx name, Value * value, PosIdx pos = noPos) { insert(Attr(name, value, pos)); } @@ -133,7 +134,7 @@ public: bindings->push_back(attr); } - Value & alloc(const Symbol & name, PosIdx pos = noPos); + Value & alloc(const SymbolIdx & name, PosIdx pos = noPos); Value & alloc(std::string_view name, PosIdx pos = noPos); diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 7d3fd01a4..0d2160efd 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -253,7 +253,7 @@ struct AttrDb std::vector attrs; auto queryAttributes(state->queryAttributes.use()(rowId)); while (queryAttributes.next()) - attrs.push_back(symbols.create(queryAttributes.getStr(0))); + attrs.emplace_back(queryAttributes.getStr(0)); return {{rowId, attrs}}; } case AttrType::String: { @@ -325,7 +325,7 @@ AttrCursor::AttrCursor( AttrKey AttrCursor::getKey() { if (!parent) - return {0, root->state.sEpsilon}; + return {0, {""}}; if (!parent->first->cachedValue) { parent->first->cachedValue = root->db->getAttr( parent->first->getKey(), root->state.symbols); @@ -340,7 +340,7 @@ Value & AttrCursor::getValue() if (parent) { auto & vParent = parent->first->getValue(); root->state.forceAttrs(vParent, noPos); - auto attr = vParent.attrs->get(parent->second); + auto attr = vParent.attrs->get(root->state.symbols.create(parent->second)); if (!attr) throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr()); _value = allocRootValue(attr->value); @@ -419,7 +419,7 @@ Suggestions AttrCursor::getSuggestionsForAttr(Symbol name) return Suggestions::bestMatches(strAttrNames, name); } -std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErrors) +std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool forceErrors) { if (root->db) { if (!cachedValue) @@ -461,10 +461,10 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErro for (auto & attr : *v.attrs) { if (root->db) - root->db->setPlaceholder({cachedValue->first, attr.name}); + root->db->setPlaceholder({cachedValue->first, root->state.symbols[attr.name]}); } - auto attr = v.attrs->get(name); + auto attr = v.attrs->get(root->state.symbols.create(name)); if (!attr) { if (root->db) { @@ -486,12 +486,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErro root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2)); } -std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name) -{ - return maybeGetAttr(root->state.symbols.create(name)); -} - -ref AttrCursor::getAttr(Symbol name, bool forceErrors) +ref AttrCursor::getAttr(std::string_view name, bool forceErrors) { auto p = maybeGetAttr(name, forceErrors); if (!p) @@ -499,11 +494,6 @@ ref AttrCursor::getAttr(Symbol name, bool forceErrors) return ref(p); } -ref AttrCursor::getAttr(std::string_view name) -{ - return getAttr(root->state.symbols.create(name)); -} - OrSuggestions> AttrCursor::findAlongAttrPath(const std::vector & attrPath, bool force) { auto res = shared_from_this(); @@ -616,7 +606,7 @@ std::vector AttrCursor::getAttrs() std::vector attrs; for (auto & attr : *getValue().attrs) - attrs.push_back(attr.name); + attrs.push_back(root->state.symbols[attr.name]); std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) { return (const std::string &) a < (const std::string &) b; }); @@ -635,7 +625,7 @@ bool AttrCursor::isDerivation() StorePath AttrCursor::forceDerivation() { - auto aDrvPath = getAttr(root->state.sDrvPath, true); + auto aDrvPath = getAttr("drvPath", true); auto drvPath = root->state.store->parseStorePath(aDrvPath->getString()); if (!root->state.store->isValidPath(drvPath) && !settings.readOnlyMode) { /* The eval cache contains 'drvPath', but the actual path has diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh index b0709ebc2..f4481c72a 100644 --- a/src/libexpr/eval-cache.hh +++ b/src/libexpr/eval-cache.hh @@ -96,13 +96,9 @@ public: Suggestions getSuggestionsForAttr(Symbol name); - std::shared_ptr maybeGetAttr(Symbol name, bool forceErrors = false); + std::shared_ptr maybeGetAttr(std::string_view name, bool forceErrors = false); - std::shared_ptr maybeGetAttr(std::string_view name); - - ref getAttr(Symbol name, bool forceErrors = false); - - ref getAttr(std::string_view name); + ref getAttr(std::string_view name, bool forceErrors = false); /* Get an attribute along a chain of attrsets. Note that this does not auto-call functors or functions. */ diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 39f1a625f..8fc144a84 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -96,7 +96,8 @@ RootValue allocRootValue(Value * v) } -void Value::print(std::ostream & str, std::set * seen) const +void Value::print(const SymbolTable & symbols, std::ostream & str, + std::set * seen) const { checkInterrupt(); @@ -129,9 +130,9 @@ void Value::print(std::ostream & str, std::set * seen) const str << "«repeated»"; else { str << "{ "; - for (auto & i : attrs->lexicographicOrder()) { - str << i->name << " = "; - i->value->print(str, seen); + for (auto & i : attrs->lexicographicOrder(symbols)) { + str << symbols[i->name] << " = "; + i->value->print(symbols, str, seen); str << "; "; } str << "}"; @@ -146,7 +147,7 @@ void Value::print(std::ostream & str, std::set * seen) const else { str << "[ "; for (auto v2 : listItems()) { - v2->print(str, seen); + v2->print(symbols, str, seen); str << " "; } str << "]"; @@ -177,17 +178,18 @@ void Value::print(std::ostream & str, std::set * seen) const } -void Value::print(std::ostream & str, bool showRepeated) const +void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepeated) const { std::set seen; - print(str, showRepeated ? nullptr : &seen); + print(symbols, str, showRepeated ? nullptr : &seen); } -std::ostream & operator << (std::ostream & str, const Value & v) +std::string printValue(const EvalState & state, const Value & v) { - v.print(str, false); - return str; + std::ostringstream out; + v.print(state.symbols, out); + return out.str(); } @@ -306,9 +308,9 @@ static BoehmGCStackAllocator boehmGCStackAllocator; #endif -static Symbol getName(const AttrName & name, EvalState & state, Env & env) +static SymbolIdx getName(const AttrName & name, EvalState & state, Env & env) { - if (name.symbol.set()) { + if (name.symbol) { return name.symbol; } else { Value nameValue; @@ -639,7 +641,7 @@ Value * EvalState::addPrimOp(const std::string & name, size_t arity, PrimOpFun primOp) { auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name; - Symbol sym = symbols.create(name2); + auto sym = symbols.create(name2); /* Hack to make constants lazy: turn them into a application of the primop to a dummy value. */ @@ -673,7 +675,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp) return addConstant(primOp.name, v); } - Symbol envName = symbols.create(primOp.name); + auto envName = symbols.create(primOp.name); if (hasPrefix(primOp.name, "__")) primOp.name = primOp.name.substr(2); @@ -767,11 +769,11 @@ void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::stri }); } -void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol & sym, const PosIdx p2) const +void EvalState::throwEvalError(const PosIdx p1, const char * s, const SymbolIdx sym, const PosIdx p2) const { // p1 is where the error occurred; p2 is a position mentioned in the message. throw EvalError({ - .msg = hintfmt(s, sym, positions[p2]), + .msg = hintfmt(s, symbols[sym], positions[p2]), .errPos = positions[p1] }); } @@ -785,19 +787,19 @@ void EvalState::throwTypeError(const PosIdx pos, const char * s) const } void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, - const Symbol & s2) const + const SymbolIdx s2) const { throw TypeError({ - .msg = hintfmt(s, fun.showNamePos(positions), s2), + .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), .errPos = positions[pos] }); } void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, - const ExprLambda & fun, const Symbol & s2) const + const ExprLambda & fun, const SymbolIdx s2) const { throw TypeError(ErrorInfo { - .msg = hintfmt(s, fun.showNamePos(positions), s2), + .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), .errPos = positions[pos], .suggestions = suggestions, }); @@ -901,7 +903,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) return j->value; } if (!env->prevWith) - throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name); + throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name]); for (size_t l = env->prevWith; l; --l, env = env->up) ; } } @@ -1187,7 +1189,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) if (nameVal.type() == nNull) continue; state.forceStringNoCtx(nameVal); - Symbol nameSym = state.symbols.create(nameVal.string.s); + auto nameSym = state.symbols.create(nameVal.string.s); Bindings::iterator j = v.attrs->find(nameSym); if (j != v.attrs->end()) state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos); @@ -1243,10 +1245,12 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a for (auto & i : attrPath) { if (!first) out << '.'; else first = false; try { - out << getName(i, state, env); + out << state.symbols[getName(i, state, env)]; } catch (Error & e) { - assert(!i.symbol.set()); - out << "\"${" << *i.expr << "}\""; + assert(!i.symbol); + out << "\"${"; + i.expr->show(state.symbols, out); + out << "}\""; } } return out.str(); @@ -1266,7 +1270,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) for (auto & i : attrPath) { state.nrLookups++; Bindings::iterator j; - Symbol name = getName(i, state, env); + auto name = getName(i, state, env); if (def) { state.forceValue(*vAttrs, pos); if (vAttrs->type() != nAttrs || @@ -1280,11 +1284,11 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { std::set allAttrNames; for (auto & attr : *vAttrs->attrs) - allAttrNames.insert(attr.name); + allAttrNames.insert(state.symbols[attr.name]); state.throwEvalError( pos, - Suggestions::bestMatches(allAttrNames, name), - "attribute '%1%' missing", name); + Suggestions::bestMatches(allAttrNames, state.symbols[name]), + "attribute '%1%' missing", state.symbols[name]); } } vAttrs = j->value; @@ -1316,7 +1320,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) for (auto & i : attrPath) { state.forceValue(*vAttrs, noPos); Bindings::iterator j; - Symbol name = getName(i, state, env); + auto name = getName(i, state, env); if (vAttrs->type() != nAttrs || (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { @@ -1366,7 +1370,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & ExprLambda & lambda(*vCur.lambda.fun); auto size = - (!lambda.arg.set() ? 0 : 1) + + (!lambda.arg ? 0 : 1) + (lambda.hasFormals() ? lambda.formals->formals.size() : 0); Env & env2(allocEnv(size)); env2.up = vCur.lambda.env; @@ -1379,7 +1383,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & else { forceAttrs(*args[0], pos); - if (lambda.arg.set()) + if (lambda.arg) env2.values[displ++] = args[0]; /* For each formal argument, get the actual argument. If @@ -1407,10 +1411,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & if (!lambda.formals->has(i.name)) { std::set formalNames; for (auto & formal : lambda.formals->formals) - formalNames.insert(formal.name); + formalNames.insert(symbols[formal.name]); throwTypeError( pos, - Suggestions::bestMatches(formalNames, i.name), + Suggestions::bestMatches(formalNames, symbols[i.name]), "%1% called with unexpected argument '%2%'", lambda, i.name); @@ -1428,8 +1432,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & } catch (Error & e) { if (loggerSettings.showTrace.get()) { addErrorTrace(e, lambda.pos, "while evaluating %s", - (lambda.name.set() - ? "'" + (const std::string &) lambda.name + "'" + (lambda.name + ? concatStrings("'", symbols[lambda.name], "'") : "anonymous lambda")); addErrorTrace(e, pos, "from call site%s", ""); } @@ -1578,7 +1582,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/#ss-functions.)", i.name); +https://nixos.org/manual/nix/stable/#ss-functions.)", symbols[i.name]); } } @@ -1610,7 +1614,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v) { if (!state.evalBool(env, cond, pos)) { std::ostringstream out; - cond->show(out); + cond->show(state.symbols, out); state.throwAssertionError(pos, "assertion '%1%' failed", out.str()); } body->eval(state, env, v); @@ -1844,7 +1848,7 @@ void EvalState::forceValueDeep(Value & v) try { recurse(*i.value); } catch (Error & e) { - addErrorTrace(e, i.pos, "while evaluating the attribute '%1%'", i.name); + addErrorTrace(e, i.pos, "while evaluating the attribute '%1%'", symbols[i.name]); throw; } } @@ -2267,7 +2271,7 @@ void EvalState::printStats() auto list = topObj.list("functions"); for (auto & i : functionCalls) { auto obj = list.object(); - if (i.first->name.set()) + if (i.first->name) obj.attr("name", (const std::string &) i.first->name); else obj.attr("name", nullptr); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 71d6e7e7f..59c9c4873 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -53,7 +53,8 @@ void copyContext(const Value & v, PathSet & context); typedef std::map SrcToStore; -std::ostream & operator << (std::ostream & str, const Value & v); +std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v); +std::string printValue(const EvalState & state, const Value & v); typedef std::pair SearchPathElem; @@ -77,7 +78,7 @@ public: static inline std::string derivationNixPath = "//builtin/derivation.nix"; - const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, + const SymbolIdx sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, sFile, sLine, sColumn, sFunctor, sToString, sRight, sWrong, sStructuredAttrs, sBuilder, sArgs, @@ -86,7 +87,7 @@ public: sRecurseForDerivations, sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath, sPrefix; - Symbol sDerivationNix; + SymbolIdx sDerivationNix; /* If set, force copying files to the Nix store even if they already exist there. */ @@ -268,14 +269,14 @@ public: [[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const; [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx p1, const char * s, const Symbol & sym, const PosIdx p2) const; + void throwEvalError(const PosIdx p1, const char * s, const SymbolIdx sym, const PosIdx p2) const; [[gnu::noinline, gnu::noreturn]] void throwTypeError(const PosIdx pos, const char * s) const; [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol & s2) const; + void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const SymbolIdx s2) const; [[gnu::noinline, gnu::noreturn]] void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, - const ExprLambda & fun, const Symbol & s2) const; + const ExprLambda & fun, const SymbolIdx s2) const; [[gnu::noinline, gnu::noreturn]] void throwTypeError(const char * s, const Value & v) const; [[gnu::noinline, gnu::noreturn]] @@ -391,7 +392,7 @@ public: inline Value * allocValue(); inline Env & allocEnv(size_t size); - Value * allocAttr(Value & vAttrs, const Symbol & name); + Value * allocAttr(Value & vAttrs, const SymbolIdx & name); Value * allocAttr(Value & vAttrs, std::string_view name); Bindings * allocBindings(size_t capacity); diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 44fb8317a..cbf4f0a6f 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -127,21 +127,23 @@ static FlakeInput parseFlakeInput(EvalState & state, } else { switch (attr.value->type()) { case nString: - attrs.emplace(attr.name, attr.value->string.s); + attrs.emplace(state.symbols[attr.name], attr.value->string.s); break; case nBool: - attrs.emplace(attr.name, Explicit { attr.value->boolean }); + attrs.emplace(state.symbols[attr.name], Explicit { attr.value->boolean }); break; case nInt: - attrs.emplace(attr.name, (long unsigned int)attr.value->integer); + attrs.emplace(state.symbols[attr.name], (long unsigned int)attr.value->integer); break; default: throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", - attr.name, showType(*attr.value)); + state.symbols[attr.name], showType(*attr.value)); } } } catch (Error & e) { - e.addTrace(state.positions[attr.pos], hintfmt("in flake attribute '%s'", attr.name)); + e.addTrace( + state.positions[attr.pos], + hintfmt("in flake attribute '%s'", state.symbols[attr.name])); throw; } } @@ -176,9 +178,9 @@ static std::map parseFlakeInputs( expectType(state, nAttrs, *value, pos); for (nix::Attr & inputAttr : *(*value).attrs) { - inputs.emplace(inputAttr.name, + inputs.emplace(state.symbols[inputAttr.name], parseFlakeInput(state, - inputAttr.name, + state.symbols[inputAttr.name], inputAttr.value, inputAttr.pos, baseDir, @@ -218,7 +220,7 @@ static Flake getFlake( Value vInfo; state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack - expectType(state, nAttrs, vInfo, state.positions.add({state.symbols.create(flakeFile), foFile}, 0, 0)); + expectType(state, nAttrs, vInfo, state.positions.add({flakeFile, foFile}, 0, 0)); if (auto description = vInfo.attrs->get(state.sDescription)) { expectType(state, nString, *description->value, description->pos); @@ -238,8 +240,8 @@ static Flake getFlake( if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) { for (auto & formal : outputs->value->lambda.fun->formals->formals) { if (formal.name != state.sSelf) - flake.inputs.emplace(formal.name, FlakeInput { - .ref = parseFlakeRef(formal.name) + flake.inputs.emplace(state.symbols[formal.name], FlakeInput { + .ref = parseFlakeRef(state.symbols[formal.name]) }); } } @@ -255,30 +257,36 @@ static Flake getFlake( for (auto & setting : *nixConfig->value->attrs) { forceTrivialValue(state, *setting.value, setting.pos); if (setting.value->type() == nString) - flake.config.settings.insert({setting.name, std::string(state.forceStringNoCtx(*setting.value, setting.pos))}); + flake.config.settings.emplace( + state.symbols[setting.name], + std::string(state.forceStringNoCtx(*setting.value, setting.pos))); else if (setting.value->type() == nPath) { PathSet emptyContext = {}; flake.config.settings.emplace( - setting.name, + state.symbols[setting.name], state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true) .toOwned()); } else if (setting.value->type() == nInt) - flake.config.settings.insert({setting.name, state.forceInt(*setting.value, setting.pos)}); + flake.config.settings.emplace( + state.symbols[setting.name], + state.forceInt(*setting.value, setting.pos)); else if (setting.value->type() == nBool) - flake.config.settings.insert({setting.name, Explicit { state.forceBool(*setting.value, setting.pos) }}); + flake.config.settings.emplace( + state.symbols[setting.name], + Explicit { state.forceBool(*setting.value, setting.pos) }); else if (setting.value->type() == nList) { std::vector ss; for (auto elem : setting.value->listItems()) { if (elem->type() != nString) throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected", - setting.name, showType(*setting.value)); + state.symbols[setting.name], showType(*setting.value)); ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos)); } - flake.config.settings.insert({setting.name, ss}); + flake.config.settings.emplace(state.symbols[setting.name], ss); } else throw TypeError("flake configuration setting '%s' is %s", - setting.name, showType(*setting.value)); + state.symbols[setting.name], showType(*setting.value)); } } @@ -288,7 +296,7 @@ static Flake getFlake( attr.name != sOutputs && attr.name != sNixConfig) throw Error("flake '%s' has an unsupported attribute '%s', at %s", - lockedRef, attr.name, state.positions[attr.pos]); + lockedRef, state.symbols[attr.name], state.positions[attr.pos]); } return flake; diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index afb35586d..11f2b279d 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -179,7 +179,7 @@ StringSet DrvInfo::queryMetaNames() StringSet res; if (!getMeta()) return res; for (auto & i : *meta) - res.insert(i.name); + res.emplace(state->symbols[i.name]); return res; } @@ -269,7 +269,7 @@ void DrvInfo::setMeta(const std::string & name, Value * v) { getMeta(); auto attrs = state->buildBindings(1 + (meta ? meta->size() : 0)); - Symbol sym = state->symbols.create(name); + auto sym = state->symbols.create(name); if (meta) for (auto i : *meta) if (i.name != sym) @@ -356,11 +356,11 @@ static void getDerivations(EvalState & state, Value & vIn, there are names clashes between derivations, the derivation bound to the attribute with the "lower" name should take precedence). */ - for (auto & i : v.attrs->lexicographicOrder()) { - debug("evaluating attribute '%1%'", i->name); - if (!std::regex_match(std::string(i->name), attrRegex)) + for (auto & i : v.attrs->lexicographicOrder(state.symbols)) { + debug("evaluating attribute '%1%'", state.symbols[i->name]); + if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex)) continue; - std::string pathPrefix2 = addToPath(pathPrefix, i->name); + std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]); if (combineChannels) getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) { diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 4138977ea..9fee3cb58 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -1,5 +1,7 @@ #include "nixexpr.hh" #include "derivations.hh" +#include "eval.hh" +#include "symbol-table.hh" #include "util.hh" #include @@ -10,12 +12,6 @@ namespace nix { /* Displaying abstract syntax trees. */ -std::ostream & operator << (std::ostream & str, const Expr & e) -{ - e.show(str); - return str; -} - static void showString(std::ostream & str, std::string_view s) { str << '"'; @@ -54,81 +50,101 @@ static void showId(std::ostream & str, std::string_view s) std::ostream & operator << (std::ostream & str, const Symbol & sym) { - showId(str, *sym.s); + showId(str, sym.s); return str; } -void Expr::show(std::ostream & str) const +void Expr::show(const SymbolTable & symbols, std::ostream & str) const { abort(); } -void ExprInt::show(std::ostream & str) const +void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const { str << n; } -void ExprFloat::show(std::ostream & str) const +void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const { str << nf; } -void ExprString::show(std::ostream & str) const +void ExprString::show(const SymbolTable & symbols, std::ostream & str) const { showString(str, s); } -void ExprPath::show(std::ostream & str) const +void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const { str << s; } -void ExprVar::show(std::ostream & str) const +void ExprVar::show(const SymbolTable & symbols, std::ostream & str) const { - str << name; + str << symbols[name]; } -void ExprSelect::show(std::ostream & str) const +void ExprSelect::show(const SymbolTable & symbols, std::ostream & str) const { - str << "(" << *e << ")." << showAttrPath(attrPath); - if (def) str << " or (" << *def << ")"; + str << "("; + e->show(symbols, str); + str << ")." << showAttrPath(symbols, attrPath); + if (def) { + str << " or ("; + def->show(symbols, str); + str << ")"; + } } -void ExprOpHasAttr::show(std::ostream & str) const +void ExprOpHasAttr::show(const SymbolTable & symbols, std::ostream & str) const { - str << "((" << *e << ") ? " << showAttrPath(attrPath) << ")"; + str << "(("; + e->show(symbols, str); + str << ") ? " << showAttrPath(symbols, attrPath) << ")"; } -void ExprAttrs::show(std::ostream & str) const +void ExprAttrs::show(const SymbolTable & symbols, std::ostream & str) const { if (recursive) str << "rec "; str << "{ "; typedef const decltype(attrs)::value_type * Attr; std::vector sorted; for (auto & i : attrs) sorted.push_back(&i); - std::sort(sorted.begin(), sorted.end(), [](Attr a, Attr b) { - return (const std::string &) a->first < (const std::string &) b->first; - }); + std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) { + std::string_view sa = symbols[a->first], sb = symbols[b->first]; + return sa < sb; + }); for (auto & i : sorted) { if (i->second.inherited) - str << "inherit " << i->first << " " << "; "; - else - str << i->first << " = " << *i->second.e << "; "; + str << "inherit " << symbols[i->first] << " " << "; "; + else { + str << symbols[i->first] << " = "; + i->second.e->show(symbols, str); + str << "; "; + } + } + for (auto & i : dynamicAttrs) { + str << "\"${"; + i.nameExpr->show(symbols, str); + str << "}\" = "; + i.valueExpr->show(symbols, str); + str << "; "; } - for (auto & i : dynamicAttrs) - str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; "; str << "}"; } -void ExprList::show(std::ostream & str) const +void ExprList::show(const SymbolTable & symbols, std::ostream & str) const { str << "[ "; - for (auto & i : elems) - str << "(" << *i << ") "; + for (auto & i : elems) { + str << "("; + i->show(symbols, str); + str << ") "; + } str << "]"; } -void ExprLambda::show(std::ostream & str) const +void ExprLambda::show(const SymbolTable & symbols, std::ostream & str) const { str << "("; if (hasFormals()) { @@ -136,74 +152,100 @@ void ExprLambda::show(std::ostream & str) const bool first = true; for (auto & i : formals->formals) { if (first) first = false; else str << ", "; - str << i.name; - if (i.def) str << " ? " << *i.def; + str << symbols[i.name]; + if (i.def) { + str << " ? "; + i.def->show(symbols, str); + } } if (formals->ellipsis) { if (!first) str << ", "; str << "..."; } str << " }"; - if (arg.set()) str << " @ "; + if (arg) str << " @ "; } - if (arg.set()) str << arg; - str << ": " << *body << ")"; + if (arg) str << symbols[arg]; + str << ": "; + body->show(symbols, str); + str << ")"; } -void ExprCall::show(std::ostream & str) const +void ExprCall::show(const SymbolTable & symbols, std::ostream & str) const { - str << '(' << *fun; + str << '('; + fun->show(symbols, str); for (auto e : args) { str << ' '; - str << *e; + e->show(symbols, str); } str << ')'; } -void ExprLet::show(std::ostream & str) const +void ExprLet::show(const SymbolTable & symbols, std::ostream & str) const { str << "(let "; for (auto & i : attrs->attrs) if (i.second.inherited) { - str << "inherit " << i.first << "; "; + str << "inherit " << symbols[i.first] << "; "; } - else - str << i.first << " = " << *i.second.e << "; "; - str << "in " << *body << ")"; + else { + str << symbols[i.first] << " = "; + i.second.e->show(symbols, str); + str << "; "; + } + str << "in "; + body->show(symbols, str); + str << ")"; } -void ExprWith::show(std::ostream & str) const +void ExprWith::show(const SymbolTable & symbols, std::ostream & str) const { - str << "(with " << *attrs << "; " << *body << ")"; + str << "(with "; + attrs->show(symbols, str); + str << "; "; + body->show(symbols, str); + str << ")"; } -void ExprIf::show(std::ostream & str) const +void ExprIf::show(const SymbolTable & symbols, std::ostream & str) const { - str << "(if " << *cond << " then " << *then << " else " << *else_ << ")"; + str << "(if "; + cond->show(symbols, str); + str << " then "; + then->show(symbols, str); + str << " else "; + else_->show(symbols, str); + str << ")"; } -void ExprAssert::show(std::ostream & str) const +void ExprAssert::show(const SymbolTable & symbols, std::ostream & str) const { - str << "assert " << *cond << "; " << *body; + str << "assert "; + cond->show(symbols, str); + str << "; "; + body->show(symbols, str); } -void ExprOpNot::show(std::ostream & str) const +void ExprOpNot::show(const SymbolTable & symbols, std::ostream & str) const { - str << "(! " << *e << ")"; + str << "(! "; + e->show(symbols, str); + str << ")"; } -void ExprConcatStrings::show(std::ostream & str) const +void ExprConcatStrings::show(const SymbolTable & symbols, std::ostream & str) const { bool first = true; str << "("; for (auto & i : *es) { if (first) first = false; else str << " + "; - str << *i.second; + i.second->show(symbols, str); } str << ")"; } -void ExprPos::show(std::ostream & str) const +void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const { str << "__curPos"; } @@ -234,16 +276,19 @@ std::ostream & operator << (std::ostream & str, const Pos & pos) } -std::string showAttrPath(const AttrPath & attrPath) +std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath) { std::ostringstream out; bool first = true; for (auto & i : attrPath) { if (!first) out << '.'; else first = false; - if (i.symbol.set()) - out << i.symbol; - else - out << "\"${" << *i.expr << "}\""; + if (i.symbol) + out << symbols[i.symbol]; + else { + out << "\"${"; + i.expr->show(symbols, out); + out << "}\""; + } } return out.str(); } @@ -252,28 +297,28 @@ std::string showAttrPath(const AttrPath & attrPath) /* Computing levels/displacements for variables. */ -void Expr::bindVars(const PosTable & pt, const StaticEnv & env) +void Expr::bindVars(const EvalState & es, const StaticEnv & env) { abort(); } -void ExprInt::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprInt::bindVars(const EvalState & es, const StaticEnv & env) { } -void ExprFloat::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprFloat::bindVars(const EvalState & es, const StaticEnv & env) { } -void ExprString::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprString::bindVars(const EvalState & es, const StaticEnv & env) { } -void ExprPath::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprPath::bindVars(const EvalState & es, const StaticEnv & env) { } -void ExprVar::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprVar::bindVars(const EvalState & es, const StaticEnv & env) { /* Check whether the variable appears in the environment. If so, set its level and displacement. */ @@ -299,31 +344,31 @@ void ExprVar::bindVars(const PosTable & pt, const StaticEnv & env) "undefined variable" error now. */ if (withLevel == -1) throw UndefinedVarError({ - .msg = hintfmt("undefined variable '%1%'", name), - .errPos = pt[pos] + .msg = hintfmt("undefined variable '%1%'", es.symbols[name]), + .errPos = es.positions[pos] }); fromWith = true; this->level = withLevel; } -void ExprSelect::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprSelect::bindVars(const EvalState & es, const StaticEnv & env) { - e->bindVars(pt, env); - if (def) def->bindVars(pt, env); + e->bindVars(es, env); + if (def) def->bindVars(es, env); for (auto & i : attrPath) - if (!i.symbol.set()) - i.expr->bindVars(pt, env); + if (!i.symbol) + i.expr->bindVars(es, env); } -void ExprOpHasAttr::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprOpHasAttr::bindVars(const EvalState & es, const StaticEnv & env) { - e->bindVars(pt, env); + e->bindVars(es, env); for (auto & i : attrPath) - if (!i.symbol.set()) - i.expr->bindVars(pt, env); + if (!i.symbol) + i.expr->bindVars(es, env); } -void ExprAttrs::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprAttrs::bindVars(const EvalState & es, const StaticEnv & env) { const StaticEnv * dynamicEnv = &env; StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0); @@ -338,35 +383,35 @@ void ExprAttrs::bindVars(const PosTable & pt, const StaticEnv & env) // No need to sort newEnv since attrs is in sorted order. for (auto & i : attrs) - i.second.e->bindVars(pt, i.second.inherited ? env : newEnv); + i.second.e->bindVars(es, i.second.inherited ? env : newEnv); } else for (auto & i : attrs) - i.second.e->bindVars(pt, env); + i.second.e->bindVars(es, env); for (auto & i : dynamicAttrs) { - i.nameExpr->bindVars(pt, *dynamicEnv); - i.valueExpr->bindVars(pt, *dynamicEnv); + i.nameExpr->bindVars(es, *dynamicEnv); + i.valueExpr->bindVars(es, *dynamicEnv); } } -void ExprList::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprList::bindVars(const EvalState & es, const StaticEnv & env) { for (auto & i : elems) - i->bindVars(pt, env); + i->bindVars(es, env); } -void ExprLambda::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprLambda::bindVars(const EvalState & es, const StaticEnv & env) { StaticEnv newEnv( false, &env, (hasFormals() ? formals->formals.size() : 0) + - (!arg.set() ? 0 : 1)); + (!arg ? 0 : 1)); Displacement displ = 0; - if (arg.set()) newEnv.vars.emplace_back(arg, displ++); + if (arg) newEnv.vars.emplace_back(arg, displ++); if (hasFormals()) { for (auto & i : formals->formals) @@ -375,20 +420,20 @@ void ExprLambda::bindVars(const PosTable & pt, const StaticEnv & env) newEnv.sort(); for (auto & i : formals->formals) - if (i.def) i.def->bindVars(pt, newEnv); + if (i.def) i.def->bindVars(es, newEnv); } - body->bindVars(pt, newEnv); + body->bindVars(es, newEnv); } -void ExprCall::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprCall::bindVars(const EvalState & es, const StaticEnv & env) { - fun->bindVars(pt, env); + fun->bindVars(es, env); for (auto e : args) - e->bindVars(pt, env); + e->bindVars(es, env); } -void ExprLet::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprLet::bindVars(const EvalState & es, const StaticEnv & env) { StaticEnv newEnv(false, &env, attrs->attrs.size()); @@ -399,12 +444,12 @@ void ExprLet::bindVars(const PosTable & pt, const StaticEnv & env) // No need to sort newEnv since attrs->attrs is in sorted order. for (auto & i : attrs->attrs) - i.second.e->bindVars(pt, i.second.inherited ? env : newEnv); + i.second.e->bindVars(es, i.second.inherited ? env : newEnv); - body->bindVars(pt, newEnv); + body->bindVars(es, newEnv); } -void ExprWith::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprWith::bindVars(const EvalState & es, const StaticEnv & env) { /* Does this `with' have an enclosing `with'? If so, record its level so that `lookupVar' can look up variables in the previous @@ -418,57 +463,60 @@ void ExprWith::bindVars(const PosTable & pt, const StaticEnv & env) break; } - attrs->bindVars(pt, env); + attrs->bindVars(es, env); StaticEnv newEnv(true, &env); - body->bindVars(pt, newEnv); + body->bindVars(es, newEnv); } -void ExprIf::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprIf::bindVars(const EvalState & es, const StaticEnv & env) { - cond->bindVars(pt, env); - then->bindVars(pt, env); - else_->bindVars(pt, env); + cond->bindVars(es, env); + then->bindVars(es, env); + else_->bindVars(es, env); } -void ExprAssert::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprAssert::bindVars(const EvalState & es, const StaticEnv & env) { - cond->bindVars(pt, env); - body->bindVars(pt, env); + cond->bindVars(es, env); + body->bindVars(es, env); } -void ExprOpNot::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprOpNot::bindVars(const EvalState & es, const StaticEnv & env) { - e->bindVars(pt, env); + e->bindVars(es, env); } -void ExprConcatStrings::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprConcatStrings::bindVars(const EvalState & es, const StaticEnv & env) { - for (auto & i : *es) - i.second->bindVars(pt, env); + for (auto & i : *this->es) + i.second->bindVars(es, env); } -void ExprPos::bindVars(const PosTable & pt, const StaticEnv & env) +void ExprPos::bindVars(const EvalState & es, const StaticEnv & env) { } /* Storing function names. */ -void Expr::setName(Symbol & name) +void Expr::setName(SymbolIdx name) { } -void ExprLambda::setName(Symbol & name) +void ExprLambda::setName(SymbolIdx name) { this->name = name; body->setName(name); } -std::string ExprLambda::showNamePos(const PosTable & pt) const +std::string ExprLambda::showNamePos(const EvalState & state) const { - return fmt("%1% at %2%", name.set() ? "'" + (std::string) name + "'" : "anonymous function", pt[pos]); + std::string id(name + ? concatStrings("'", state.symbols[name], "'") + : "anonymous function"); + return fmt("%1% at %2%", id, state.positions[pos]); } @@ -478,8 +526,7 @@ std::string ExprLambda::showNamePos(const PosTable & pt) const size_t SymbolTable::totalSize() const { size_t n = 0; - for (auto & i : store) - n += i.size(); + dump([&] (const Symbol & s) { n += std::string_view(s).size(); }); return n; } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 2e12f0b4f..1a387d4fd 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -125,15 +125,15 @@ struct StaticEnv; /* An attribute path is a sequence of attribute names. */ struct AttrName { - Symbol symbol; + SymbolIdx symbol; Expr * expr; - AttrName(const Symbol & s) : symbol(s) {}; + AttrName(const SymbolIdx & s) : symbol(s) {}; AttrName(Expr * e) : expr(e) {}; }; typedef std::vector AttrPath; -std::string showAttrPath(const AttrPath & attrPath); +std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath); /* Abstract syntax of Nix expressions. */ @@ -141,19 +141,17 @@ std::string showAttrPath(const AttrPath & attrPath); struct Expr { virtual ~Expr() { }; - virtual void show(std::ostream & str) const; - virtual void bindVars(const PosTable & pt, const StaticEnv & env); + virtual void show(const SymbolTable & symbols, std::ostream & str) const; + virtual void bindVars(const EvalState & es, const StaticEnv & env); virtual void eval(EvalState & state, Env & env, Value & v); virtual Value * maybeThunk(EvalState & state, Env & env); - virtual void setName(Symbol & name); + virtual void setName(SymbolIdx name); }; -std::ostream & operator << (std::ostream & str, const Expr & e); - #define COMMON_METHODS \ - void show(std::ostream & str) const; \ + void show(const SymbolTable & symbols, std::ostream & str) const; \ void eval(EvalState & state, Env & env, Value & v); \ - void bindVars(const PosTable & pt, const StaticEnv & env); + void bindVars(const EvalState & es, const StaticEnv & env); struct ExprInt : Expr { @@ -197,7 +195,7 @@ typedef uint32_t Displacement; struct ExprVar : Expr { PosIdx pos; - Symbol name; + SymbolIdx name; /* Whether the variable comes from an environment (e.g. a rec, let or function argument) or from a "with". */ @@ -212,8 +210,8 @@ struct ExprVar : Expr Level level; Displacement displ; - ExprVar(const Symbol & name) : name(name) { }; - ExprVar(const PosIdx & pos, const Symbol & name) : pos(pos), name(name) { }; + ExprVar(const SymbolIdx & name) : name(name) { }; + ExprVar(const PosIdx & pos, const SymbolIdx & name) : pos(pos), name(name) { }; COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); }; @@ -224,7 +222,7 @@ struct ExprSelect : Expr Expr * e, * def; AttrPath attrPath; ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; - ExprSelect(const PosIdx & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; + ExprSelect(const PosIdx & pos, Expr * e, const SymbolIdx & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; COMMON_METHODS }; @@ -249,7 +247,7 @@ struct ExprAttrs : Expr : inherited(inherited), e(e), pos(pos) { }; AttrDef() { }; }; - typedef std::map AttrDefs; + typedef std::map AttrDefs; AttrDefs attrs; struct DynamicAttrDef { Expr * nameExpr, * valueExpr; @@ -274,9 +272,8 @@ struct ExprList : Expr struct Formal { PosIdx pos; - Symbol name; + SymbolIdx name; Expr * def; - Formal(const PosIdx & pos, const Symbol & name, Expr * def) : pos(pos), name(name), def(def) { }; }; struct Formals @@ -285,18 +282,19 @@ struct Formals Formals_ formals; bool ellipsis; - bool has(Symbol arg) const { + bool has(SymbolIdx arg) const { auto it = std::lower_bound(formals.begin(), formals.end(), arg, - [] (const Formal & f, const Symbol & sym) { return f.name < sym; }); + [] (const Formal & f, const SymbolIdx & sym) { return f.name < sym; }); return it != formals.end() && it->name == arg; } - std::vector lexicographicOrder() const + std::vector lexicographicOrder(const SymbolTable & symbols) const { std::vector result(formals.begin(), formals.end()); std::sort(result.begin(), result.end(), - [] (const Formal & a, const Formal & b) { - return std::string_view(a.name) < std::string_view(b.name); + [&] (const Formal & a, const Formal & b) { + std::string_view sa = symbols[a.name], sb = symbols[b.name]; + return sa < sb; }); return result; } @@ -305,16 +303,20 @@ struct Formals struct ExprLambda : Expr { PosIdx pos; - Symbol name; - Symbol arg; + SymbolIdx name; + SymbolIdx arg; Formals * formals; Expr * body; - ExprLambda(const PosIdx & pos, const Symbol & arg, Formals * formals, Expr * body) + ExprLambda(PosIdx pos, SymbolIdx arg, Formals * formals, Expr * body) : pos(pos), arg(arg), formals(formals), body(body) { }; - void setName(Symbol & name); - std::string showNamePos(const PosTable & pt) const; + ExprLambda(PosIdx pos, Formals * formals, Expr * body) + : pos(pos), formals(formals), body(body) + { + } + void setName(SymbolIdx name); + std::string showNamePos(const EvalState & state) const; inline bool hasFormals() const { return formals != nullptr; } COMMON_METHODS }; @@ -377,13 +379,13 @@ struct ExprOpNot : Expr Expr * e1, * e2; \ name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \ name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \ - void show(std::ostream & str) const \ + void show(const SymbolTable & symbols, std::ostream & str) const \ { \ - str << "(" << *e1 << " " s " " << *e2 << ")"; \ + str << "("; e1->show(symbols, str); str << " " s " "; e2->show(symbols, str); str << ")"; \ } \ - void bindVars(const PosTable & pt, const StaticEnv & env) \ + void bindVars(const EvalState & es, const StaticEnv & env) \ { \ - e1->bindVars(pt, env); e2->bindVars(pt, env); \ + e1->bindVars(es, env); e2->bindVars(es, env); \ } \ void eval(EvalState & state, Env & env, Value & v); \ }; @@ -423,7 +425,7 @@ struct StaticEnv const StaticEnv * up; // Note: these must be in sorted order. - typedef std::vector> Vars; + typedef std::vector> Vars; Vars vars; StaticEnv(bool isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) { @@ -447,7 +449,7 @@ struct StaticEnv vars.erase(it, end); } - Vars::const_iterator find(const Symbol & name) const + Vars::const_iterator find(const SymbolIdx & name) const { Vars::value_type key(name, 0); auto i = std::lower_bound(vars.begin(), vars.end(), key); diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 1f950a057..b73fd1786 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -77,26 +77,26 @@ using namespace nix; namespace nix { -static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos) +static void dupAttr(const EvalState & state, const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos) { throw ParseError({ .msg = hintfmt("attribute '%1%' already defined at %2%", - showAttrPath(attrPath), prevPos), - .errPos = pos + showAttrPath(state.symbols, attrPath), state.positions[prevPos]), + .errPos = state.positions[pos] }); } -static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos) +static void dupAttr(const EvalState & state, SymbolIdx attr, const PosIdx pos, const PosIdx prevPos) { throw ParseError({ - .msg = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos), - .errPos = pos + .msg = hintfmt("attribute '%1%' already defined at %2%", state.symbols[attr], state.positions[prevPos]), + .errPos = state.positions[pos] }); } static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, - Expr * e, const PosIdx pos, const nix::PosTable & pt) + Expr * e, const PosIdx pos, const nix::EvalState & state) { AttrPath::iterator i; // All attrpaths have at least one attr @@ -104,15 +104,15 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, // Checking attrPath validity. // =========================== for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) { - if (i->symbol.set()) { + if (i->symbol) { ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); if (j != attrs->attrs.end()) { if (!j->second.inherited) { ExprAttrs * attrs2 = dynamic_cast(j->second.e); - if (!attrs2) dupAttr(attrPath, pt[pos], pt[j->second.pos]); + if (!attrs2) dupAttr(state, attrPath, pos, j->second.pos); attrs = attrs2; } else - dupAttr(attrPath, pt[pos], pt[j->second.pos]); + dupAttr(state, attrPath, pos, j->second.pos); } else { ExprAttrs * nested = new ExprAttrs; attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos); @@ -126,7 +126,7 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, } // Expr insertion. // ========================== - if (i->symbol.set()) { + if (i->symbol) { ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); if (j != attrs->attrs.end()) { // This attr path is already defined. However, if both @@ -139,11 +139,11 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, for (auto & ad : ae->attrs) { auto j2 = jAttrs->attrs.find(ad.first); if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error. - dupAttr(ad.first, pt[j2->second.pos], pt[ad.second.pos]); + dupAttr(state, ad.first, j2->second.pos, ad.second.pos); jAttrs->attrs.emplace(ad.first, ad.second); } } else { - dupAttr(attrPath, pt[pos], pt[j->second.pos]); + dupAttr(state, attrPath, pos, j->second.pos); } } else { // This attr path is not defined. Let's create it. @@ -157,14 +157,14 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, static Formals * toFormals(ParseData & data, ParserFormals * formals, - PosIdx pos = noPos, Symbol arg = {}) + PosIdx pos = noPos, SymbolIdx arg = {}) { std::sort(formals->formals.begin(), formals->formals.end(), [] (const auto & a, const auto & b) { return std::tie(a.name, a.pos) < std::tie(b.name, b.pos); }); - std::optional> duplicate; + std::optional> duplicate; for (size_t i = 0; i + 1 < formals->formals.size(); i++) { if (formals->formals[i].name != formals->formals[i + 1].name) continue; @@ -173,7 +173,7 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals, } if (duplicate) throw ParseError({ - .msg = hintfmt("duplicate formal function argument '%1%'", duplicate->first), + .msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[duplicate->first]), .errPos = data.state.positions[duplicate->second] }); @@ -181,9 +181,9 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals, result.ellipsis = formals->ellipsis; result.formals = std::move(formals->formals); - if (arg.set() && result.has(arg)) + if (arg && result.has(arg)) throw ParseError({ - .msg = hintfmt("duplicate formal function argument '%1%'", arg), + .msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[arg]), .errPos = data.state.positions[pos] }); @@ -369,15 +369,15 @@ expr_function : ID ':' expr_function { $$ = new ExprLambda(CUR_POS, data->symbols.create($1), 0, $3); } | '{' formals '}' ':' expr_function - { $$ = new ExprLambda(CUR_POS, {}, toFormals(*data, $2), $5); } + { $$ = new ExprLambda(CUR_POS, toFormals(*data, $2), $5); } | '{' formals '}' '@' ID ':' expr_function { - Symbol arg = data->symbols.create($5); + auto arg = data->symbols.create($5); $$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $2, CUR_POS, arg), $7); } | ID '@' '{' formals '}' ':' expr_function { - Symbol arg = data->symbols.create($1); + auto arg = data->symbols.create($1); $$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $4, CUR_POS, arg), $7); } | ASSERT expr ';' expr_function @@ -532,13 +532,12 @@ ind_string_parts ; binds - : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data), data->state.positions); } + : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data), data->state); } | binds INHERIT attrs ';' { $$ = $1; for (auto & i : *$3) { if ($$->attrs.find(i.symbol) != $$->attrs.end()) - dupAttr(i.symbol, data->state.positions[makeCurPos(@3, data)], - data->state.positions[$$->attrs[i.symbol].pos]); + dupAttr(data->state, i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos); auto pos = makeCurPos(@3, data); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true)); } @@ -548,8 +547,7 @@ binds /* !!! Should ensure sharing of the expression in $4. */ for (auto & i : *$6) { if ($$->attrs.find(i.symbol) != $$->attrs.end()) - dupAttr(i.symbol, data->state.positions[makeCurPos(@6, data)], - data->state.positions[$$->attrs[i.symbol].pos]); + dupAttr(data->state, i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data))); } } @@ -623,8 +621,8 @@ formals ; formal - : ID { $$ = new Formal(CUR_POS, data->symbols.create($1), 0); } - | ID '?' expr { $$ = new Formal(CUR_POS, data->symbols.create($1), $3); } + : ID { $$ = new Formal{CUR_POS, data->symbols.create($1), 0}; } + | ID '?' expr { $$ = new Formal{CUR_POS, data->symbols.create($1), $3}; } ; %% @@ -670,7 +668,7 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, if (res) throw ParseError(data.error.value()); - data.result->bindVars(positions, staticEnv); + data.result->bindVars(*this, staticEnv); return data.result; } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 81fb5acfb..5892fe3b1 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -403,7 +403,7 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value * * args, Val case nFloat: t = "float"; break; case nThunk: abort(); } - v.mkString(state.symbols.create(t)); + v.mkString(t); } static RegisterPrimOp primop_typeOf({ @@ -584,7 +584,7 @@ typedef std::list ValueList; static Bindings::iterator getAttr( EvalState & state, std::string_view funcName, - Symbol attrSym, + SymbolIdx attrSym, Bindings * attrSet, const PosIdx pos) { @@ -592,7 +592,7 @@ static Bindings::iterator getAttr( if (value == attrSet->end()) { hintformat errorMsg = hintfmt( "attribute '%s' missing for call to '%s'", - attrSym, + state.symbols[attrSym], funcName ); @@ -919,7 +919,7 @@ static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Valu if (args[0]->type() == nString) printError("trace: %1%", args[0]->string.s); else - printError("trace: %1%", *args[0]); + printError("trace: %1%", printValue(state, *args[0])); state.forceValue(*args[1], pos); v = *args[1]; } @@ -998,9 +998,9 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * StringSet outputs; outputs.insert("out"); - for (auto & i : args[0]->attrs->lexicographicOrder()) { + for (auto & i : args[0]->attrs->lexicographicOrder(state.symbols)) { if (i->name == state.sIgnoreNulls) continue; - const std::string & key = i->name; + const std::string & key = state.symbols[i->name]; vomit("processing attribute '%1%'", key); auto handleHashMode = [&](const std::string_view s) { @@ -2046,7 +2046,7 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value PathSet context; for (auto & attr : *args[0]->attrs) { - auto & n(attr.name); + auto & n(state.symbols[attr.name]); if (n == "path") path = state.coerceToPath(attr.pos, *attr.value, context); else if (attr.name == state.sName) @@ -2060,7 +2060,7 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256); else throw EvalError({ - .msg = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name), + .msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]), .errPos = state.positions[attr.pos] }); } @@ -2126,7 +2126,7 @@ static void prim_attrNames(EvalState & state, const PosIdx pos, Value * * args, size_t n = 0; for (auto & i : *args[0]->attrs) - (v.listElems()[n++] = state.allocValue())->mkString(i.name); + (v.listElems()[n++] = state.allocValue())->mkString(state.symbols[i.name]); std::sort(v.listElems(), v.listElems() + n, [](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; }); @@ -2156,8 +2156,9 @@ static void prim_attrValues(EvalState & state, const PosIdx pos, Value * * args, v.listElems()[n++] = (Value *) &i; std::sort(v.listElems(), v.listElems() + n, - [](Value * v1, Value * v2) { - std::string_view s1 = ((Attr *) v1)->name, s2 = ((Attr *) v2)->name; + [&](Value * v1, Value * v2) { + std::string_view s1 = state.symbols[((Attr *) v1)->name], + s2 = state.symbols[((Attr *) v2)->name]; return s1 < s2; }); @@ -2312,7 +2313,7 @@ static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args auto attrs = state.buildBindings(args[0]->listSize()); - std::set seen; + std::set seen; for (auto v2 : args[0]->listItems()) { state.forceAttrs(*v2, pos); @@ -2327,7 +2328,7 @@ static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args auto name = state.forceStringNoCtx(*j->value, j->pos); - Symbol sym = state.symbols.create(name); + auto sym = state.symbols.create(name); if (seen.insert(sym).second) { Bindings::iterator j2 = getAttr( state, @@ -2396,7 +2397,7 @@ static RegisterPrimOp primop_intersectAttrs({ static void prim_catAttrs(EvalState & state, const PosIdx pos, Value * * args, Value & v) { - Symbol attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos)); + auto attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos)); state.forceList(*args[1], pos); Value * res[args[1]->listSize()]; @@ -2483,7 +2484,7 @@ static void prim_mapAttrs(EvalState & state, const PosIdx pos, Value * * args, V for (auto & i : *args[1]->attrs) { Value * vName = state.allocValue(); Value * vFun2 = state.allocValue(); - vName->mkString(i.name); + vName->mkString(state.symbols[i.name]); vFun2->mkApp(args[0], vName); attrs.alloc(i.name).mkApp(vFun2, i.value); } @@ -2515,7 +2516,7 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg // attribute with the merge function application. this way we need not // use (slightly slower) temporary storage the GC does not know about. - std::map> attrsSeen; + std::map> attrsSeen; state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); @@ -2550,7 +2551,7 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg for (auto & attr : *v.attrs) { auto name = state.allocValue(); - name->mkString(attr.name); + name->mkString(state.symbols[attr.name]); auto call1 = state.allocValue(); call1->mkApp(args[0], name); auto call2 = state.allocValue(); @@ -3058,7 +3059,7 @@ static void prim_groupBy(EvalState & state, const PosIdx pos, Value * * args, Va Value res; state.callFunction(*args[0], *vElem, res, pos); auto name = state.forceStringNoCtx(res, pos); - Symbol sym = state.symbols.create(name); + auto sym = state.symbols.create(name); auto vector = attrs.try_emplace(sym, ValueVector()).first; vector->second.push_back(vElem); } @@ -3932,7 +3933,7 @@ void EvalState::createBaseEnv() // the parser needs two NUL bytes as terminators; one of them // is implied by being a C string. "\0"; - eval(parse(code, sizeof(code), foFile, sDerivationNix, "/", staticBaseEnv), *vDerivation); + eval(parse(code, sizeof(code), foFile, derivationNixPath, "/", staticBaseEnv), *vDerivation); } diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index 5521586ee..979136984 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -144,45 +144,46 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar auto sPath = state.symbols.create("path"); auto sAllOutputs = state.symbols.create("allOutputs"); for (auto & i : *args[1]->attrs) { - if (!state.store->isStorePath(i.name)) + const auto & name = state.symbols[i.name]; + if (!state.store->isStorePath(name)) throw EvalError({ - .msg = hintfmt("Context key '%s' is not a store path", i.name), + .msg = hintfmt("Context key '%s' is not a store path", name), .errPos = state.positions[i.pos] }); if (!settings.readOnlyMode) - state.store->ensurePath(state.store->parseStorePath(i.name)); + state.store->ensurePath(state.store->parseStorePath(name)); state.forceAttrs(*i.value, i.pos); auto iter = i.value->attrs->find(sPath); if (iter != i.value->attrs->end()) { if (state.forceBool(*iter->value, iter->pos)) - context.insert(i.name); + context.emplace(name); } iter = i.value->attrs->find(sAllOutputs); if (iter != i.value->attrs->end()) { if (state.forceBool(*iter->value, iter->pos)) { - if (!isDerivation(i.name)) { + if (!isDerivation(name)) { throw EvalError({ - .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), + .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", name), .errPos = state.positions[i.pos] }); } - context.insert("=" + std::string(i.name)); + context.insert(concatStrings("=", name)); } } iter = i.value->attrs->find(state.sOutputs); if (iter != i.value->attrs->end()) { state.forceList(*iter->value, iter->pos); - if (iter->value->listSize() && !isDerivation(i.name)) { + if (iter->value->listSize() && !isDerivation(name)) { throw EvalError({ - .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), + .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", name), .errPos = state.positions[i.pos] }); } for (auto elem : iter->value->listItems()) { - auto name = state.forceStringNoCtx(*elem, iter->pos); - context.insert(concatStrings("!", name, "!", i.name)); + auto outputName = state.forceStringNoCtx(*elem, iter->pos); + context.insert(concatStrings("!", outputName, "!", name)); } } } diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 90e9e6230..662c9652e 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -15,12 +15,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg std::optional toPath; for (auto & attr : *args[0]->attrs) { - if (attr.name == "fromPath") { + const auto & attrName = state.symbols[attr.name]; + + if (attrName == "fromPath") { PathSet context; fromPath = state.coerceToStorePath(attr.pos, *attr.value, context); } - else if (attr.name == "toPath") { + else if (attrName == "toPath") { state.forceValue(*attr.value, attr.pos); toCA = true; if (attr.value->type() != nString || attr.value->string.s != std::string("")) { @@ -29,12 +31,12 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg } } - else if (attr.name == "fromStore") + else if (attrName == "fromStore") fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos); else throw Error({ - .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name), + .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName), .errPos = state.positions[pos] }); } diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index 252492446..249c0934e 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -22,7 +22,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a state.forceAttrs(*args[0], pos); for (auto & attr : *args[0]->attrs) { - std::string_view n(attr.name); + std::string_view n(state.symbols[attr.name]); if (n == "url") url = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned(); else if (n == "rev") { @@ -38,7 +38,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a name = state.forceStringNoCtx(*attr.value, attr.pos); else throw EvalError({ - .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), + .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]), .errPos = state.positions[attr.pos] }); } diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index cdcae97b6..d7c3c9918 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -126,20 +126,20 @@ static void fetchTree( state.forceValue(*attr.value, attr.pos); if (attr.value->type() == nPath || attr.value->type() == nString) { auto s = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned(); - attrs.emplace(attr.name, - attr.name == "url" + attrs.emplace(state.symbols[attr.name], + state.symbols[attr.name] == "url" ? type == "git" ? fixURIForGit(s, state) : fixURI(s, state) : s); } else if (attr.value->type() == nBool) - attrs.emplace(attr.name, Explicit{attr.value->boolean}); + attrs.emplace(state.symbols[attr.name], Explicit{attr.value->boolean}); else if (attr.value->type() == nInt) - attrs.emplace(attr.name, uint64_t(attr.value->integer)); + attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer)); else throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", - attr.name, showType(*attr.value)); + state.symbols[attr.name], showType(*attr.value)); } if (!params.allowNameArgument) @@ -198,7 +198,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v state.forceAttrs(*args[0], pos); for (auto & attr : *args[0]->attrs) { - std::string n(attr.name); + std::string_view n(state.symbols[attr.name]); if (n == "url") url = state.forceStringNoCtx(*attr.value, attr.pos); else if (n == "sha256") @@ -207,7 +207,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v name = state.forceStringNoCtx(*attr.value, attr.pos); else throw EvalError({ - .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), + .msg = hintfmt("unsupported argument '%s' to '%s'", n, who), .errPos = state.positions[attr.pos] }); } diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh index 297605295..d0cd841a0 100644 --- a/src/libexpr/symbol-table.hh +++ b/src/libexpr/symbol-table.hh @@ -16,46 +16,25 @@ namespace nix { class Symbol { -private: - const std::string * s; // pointer into SymbolTable - Symbol(const std::string * s) : s(s) { }; friend class SymbolTable; +private: + std::string s; public: - Symbol() : s(0) { }; - - bool operator == (const Symbol & s2) const - { - return s == s2.s; - } + Symbol(std::string_view s) : s(s) { } // FIXME: remove bool operator == (std::string_view s2) const { - return s->compare(s2) == 0; - } - - bool operator != (const Symbol & s2) const - { - return s != s2.s; - } - - bool operator < (const Symbol & s2) const - { - return s < s2.s; + return s == s2; } operator const std::string & () const { - return *s; + return s; } operator const std::string_view () const - { - return *s; - } - - bool set() const { return s; } @@ -63,38 +42,64 @@ public: friend std::ostream & operator << (std::ostream & str, const Symbol & sym); }; +class SymbolIdx +{ + friend class SymbolTable; + +private: + uint32_t id; + + explicit SymbolIdx(uint32_t id): id(id) {} + +public: + SymbolIdx() : id(0) {} + + explicit operator bool() const { return id > 0; } + + bool operator<(const SymbolIdx other) const { return id < other.id; } + bool operator==(const SymbolIdx other) const { return id == other.id; } + bool operator!=(const SymbolIdx other) const { return id != other.id; } +}; + class SymbolTable { private: - std::unordered_map symbols; - std::list store; + std::unordered_map> symbols; + ChunkedVector store{16}; public: - Symbol create(std::string_view s) + SymbolIdx create(std::string_view s) { // Most symbols are looked up more than once, so we trade off insertion performance // for lookup performance. // TODO: could probably be done more efficiently with transparent Hash and Equals // on the original implementation using unordered_set auto it = symbols.find(s); - if (it != symbols.end()) return it->second; + if (it != symbols.end()) return SymbolIdx(it->second.second + 1); - auto & rawSym = store.emplace_back(s); - return symbols.emplace(rawSym, Symbol(&rawSym)).first->second; + const auto & [rawSym, idx] = store.add(s); + symbols.emplace(rawSym, std::make_pair(&rawSym, idx)); + return SymbolIdx(idx + 1); + } + + const Symbol & operator[](SymbolIdx s) const + { + if (s.id == 0 || s.id > store.size()) + abort(); + return store[s.id - 1]; } size_t size() const { - return symbols.size(); + return store.size(); } size_t totalSize() const; template - void dump(T callback) + void dump(T callback) const { - for (auto & s : store) - callback(s); + store.forEach(callback); } }; diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 307934292..68235ad11 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -50,7 +50,7 @@ void printValueAsJSON(EvalState & state, bool strict, auto obj(out.object()); StringSet names; for (auto & j : *v.attrs) - names.insert(j.name); + names.emplace(state.symbols[j.name]); for (auto & j : names) { Attr & a(*v.attrs->find(state.symbols.create(j))); auto placeholder(obj.placeholder(j)); diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc index d1e0c4778..7c3bf9492 100644 --- a/src/libexpr/value-to-xml.cc +++ b/src/libexpr/value-to-xml.cc @@ -22,7 +22,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, const PosIdx pos); -static void posToXML(XMLAttrs & xmlAttrs, const Pos & pos) +static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos) { xmlAttrs["path"] = pos.file; xmlAttrs["line"] = (format("%1%") % pos.line).str(); @@ -36,14 +36,14 @@ static void showAttrs(EvalState & state, bool strict, bool location, StringSet names; for (auto & i : attrs) - names.insert(i.name); + names.emplace(state.symbols[i.name]); for (auto & i : names) { Attr & a(*attrs.find(state.symbols.create(i))); XMLAttrs xmlAttrs; xmlAttrs["name"] = i; - if (location && a.pos) posToXML(xmlAttrs, state.positions[a.pos]); + if (location && a.pos) posToXML(state, xmlAttrs, state.positions[a.pos]); XMLOpenElement _(doc, "attr", xmlAttrs); printValueAsXML(state, strict, location, @@ -134,18 +134,18 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, break; } XMLAttrs xmlAttrs; - if (location) posToXML(xmlAttrs, state.positions[v.lambda.fun->pos]); + if (location) posToXML(state, xmlAttrs, state.positions[v.lambda.fun->pos]); XMLOpenElement _(doc, "function", xmlAttrs); if (v.lambda.fun->hasFormals()) { XMLAttrs attrs; - if (v.lambda.fun->arg.set()) attrs["name"] = v.lambda.fun->arg; + if (v.lambda.fun->arg) attrs["name"] = state.symbols[v.lambda.fun->arg]; if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1"; XMLOpenElement _(doc, "attrspat", attrs); - for (auto & i : v.lambda.fun->formals->lexicographicOrder()) - doc.writeEmptyElement("attr", singletonAttrs("name", i.name)); + for (auto & i : v.lambda.fun->formals->lexicographicOrder(state.symbols)) + doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name])); } else - doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg)); + doc.writeEmptyElement("varpat", singletonAttrs("name", state.symbols[v.lambda.fun->arg])); break; } diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index dc875615c..c46dd4b73 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -55,7 +55,7 @@ struct Env; struct Expr; struct ExprLambda; struct PrimOp; -class Symbol; +class SymbolIdx; class PosIdx; struct Pos; class StorePath; @@ -121,11 +121,11 @@ private: friend std::string showType(const Value & v); - void print(std::ostream & str, std::set * seen) const; + void print(const SymbolTable & symbols, std::ostream & str, std::set * seen) const; public: - void print(std::ostream & str, bool showRepeated = false) const; + void print(const SymbolTable & symbols, std::ostream & str, bool showRepeated = false) const; // Functions needed to distinguish the type // These should be removed eventually, by putting the functionality that's @@ -253,7 +253,7 @@ public: inline void mkString(const Symbol & s) { - mkString(((const std::string &) s).c_str()); + mkString(std::string_view(s).data()); } inline void mkPath(const char * s) @@ -410,12 +410,12 @@ public: #if HAVE_BOEHMGC typedef std::vector > ValueVector; -typedef std::map, traceable_allocator > > ValueMap; -typedef std::map, traceable_allocator > > ValueVectorMap; +typedef std::map, traceable_allocator > > ValueMap; +typedef std::map, traceable_allocator > > ValueVectorMap; #else typedef std::vector ValueVector; -typedef std::map ValueMap; -typedef std::map ValueVectorMap; +typedef std::map ValueMap; +typedef std::map ValueVectorMap; #endif diff --git a/src/libutil/types.hh b/src/libutil/types.hh index 22bc2b8dd..bd1dd8bee 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -152,6 +152,14 @@ public: { return chunks[idx / ChunkSize][idx % ChunkSize]; } + + template + void forEach(Fn fn) const + { + for (const auto & c : chunks) + for (const auto & e : c) + fn(e); + } }; } diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 9a68899cd..96f3c3b26 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -1241,7 +1241,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) Attr & a(*attrs.find(i.name)); if(a.value->type() != nString) continue; XMLAttrs attrs3; - attrs3["type"] = i.name; + attrs3["type"] = globals.state->symbols[i.name]; attrs3["value"] = a.value->string.s; xml.writeEmptyElement("string", attrs3); } diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc index 156181634..4b1202be3 100644 --- a/src/nix-env/user-env.cc +++ b/src/nix-env/user-env.cc @@ -106,7 +106,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems, the store; we need it for future modifications of the environment. */ std::ostringstream str; - manifest.print(str, true); + manifest.print(state.symbols, str, true); auto manifestFile = state.store->addTextToStore("env-manifest.nix", str.str(), references); diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc index 3ec0e6e7c..d3144e131 100644 --- a/src/nix-instantiate/nix-instantiate.cc +++ b/src/nix-instantiate/nix-instantiate.cc @@ -31,7 +31,8 @@ void processExpr(EvalState & state, const Strings & attrPaths, bool evalOnly, OutputKind output, bool location, Expr * e) { if (parseOnly) { - std::cout << format("%1%\n") % *e; + e->show(state.symbols, std::cout); + std::cout << "\n"; return; } @@ -55,7 +56,8 @@ void processExpr(EvalState & state, const Strings & attrPaths, printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context); else { if (strict) state.forceValueDeep(vRes); - std::cout << vRes << std::endl; + vRes.print(state.symbols, std::cout); + std::cout << std::endl; } } else { DrvInfos drvs; diff --git a/src/nix/app.cc b/src/nix/app.cc index df7303e15..95ac1cf5c 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -85,9 +85,9 @@ UnresolvedApp Installable::toApp(EvalState & state) else if (type == "derivation") { auto drvPath = cursor->forceDerivation(); - auto outPath = cursor->getAttr(state.sOutPath)->getString(); - auto outputName = cursor->getAttr(state.sOutputName)->getString(); - auto name = cursor->getAttr(state.sName)->getString(); + auto outPath = cursor->getAttr("outPath")->getString(); + auto outputName = cursor->getAttr("outputName")->getString(); + auto name = cursor->getAttr("name")->getString(); auto aPname = cursor->maybeGetAttr("pname"); auto aMeta = cursor->maybeGetAttr("meta"); auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr; diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 81474c2d3..967dc8519 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -88,17 +88,19 @@ struct CmdEval : MixJSON, InstallableCommand else if (v.type() == nAttrs) { if (mkdir(path.c_str(), 0777) == -1) throw SysError("creating directory '%s'", path); - for (auto & attr : *v.attrs) + for (auto & attr : *v.attrs) { + std::string_view name = state->symbols[attr.name]; try { - if (attr.name == "." || attr.name == "..") - throw Error("invalid file name '%s'", attr.name); - recurse(*attr.value, attr.pos, path + "/" + std::string(attr.name)); + if (name == "." || name == "..") + throw Error("invalid file name '%s'", name); + recurse(*attr.value, attr.pos, concatStrings(path, "/", name)); } catch (Error & e) { e.addTrace( state->positions[attr.pos], - hintfmt("while evaluating the attribute '%s'", attr.name)); + hintfmt("while evaluating the attribute '%s'", name)); throw; } + } } else throw TypeError("value at '%s' is not a string or an attribute set", state->positions[pos]); @@ -119,7 +121,7 @@ struct CmdEval : MixJSON, InstallableCommand else { state->forceValueDeep(*v); - logger->cout("%s", *v); + logger->cout("%s", printValue(*state, *v)); } } }; diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 23e5cd24e..c8d8461e4 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -139,11 +139,11 @@ static void enumerateOutputs(EvalState & state, Value & vFlake, else. This way we can disable IFD for hydraJobs and then enable it for other outputs. */ if (auto attr = aOutputs->value->attrs->get(sHydraJobs)) - callback(attr->name, *attr->value, attr->pos); + callback(state.symbols[attr->name], *attr->value, attr->pos); for (auto & attr : *aOutputs->value->attrs) { if (attr.name != sHydraJobs) - callback(attr.name, *attr.value, attr.pos); + callback(state.symbols[attr.name], *attr.value, attr.pos); } } @@ -254,14 +254,6 @@ struct CmdFlakeInfo : CmdFlakeMetadata } }; -static bool argHasName(std::string_view arg, std::string_view expected) -{ - return - arg == expected - || arg == "_" - || (hasPrefix(arg, "_") && arg.substr(1) == expected); -} - struct CmdFlakeCheck : FlakeCommand { bool build = true; @@ -319,6 +311,14 @@ struct CmdFlakeCheck : FlakeCommand return state->positions[p]; }; + auto argHasName = [&] (SymbolIdx arg, std::string_view expected) { + std::string_view name = state->symbols[arg]; + return + name == expected + || name == "_" + || (hasPrefix(name, "_") && name.substr(1) == expected); + }; + auto checkSystemName = [&](const std::string & system, const PosIdx pos) { // FIXME: what's the format of "system"? if (system.find('-') == std::string::npos) @@ -390,7 +390,7 @@ struct CmdFlakeCheck : FlakeCommand } catch (Error & e) { e.addTrace( state->positions[attr.pos], - hintfmt("while evaluating the option '%s'", attr.name)); + hintfmt("while evaluating the option '%s'", state->symbols[attr.name])); throw; } } else @@ -414,7 +414,7 @@ struct CmdFlakeCheck : FlakeCommand for (auto & attr : *v.attrs) { state->forceAttrs(*attr.value, attr.pos); - auto attrPath2 = attrPath + "." + (std::string) attr.name; + auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]); if (state->isDerivation(*attr.value)) { Activity act(*logger, lvlChatty, actUnknown, fmt("checking Hydra job '%s'", attrPath2)); @@ -468,7 +468,7 @@ struct CmdFlakeCheck : FlakeCommand throw Error("template '%s' lacks attribute 'description'", attrPath); for (auto & attr : *v.attrs) { - std::string name(attr.name); + std::string_view name(state->symbols[attr.name]); if (name != "path" && name != "description" && name != "welcomeText") throw Error("template '%s' has unsupported attribute '%s'", attrPath, name); } @@ -522,13 +522,14 @@ struct CmdFlakeCheck : FlakeCommand if (name == "checks") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) { auto drvPath = checkDerivation( - fmt("%s.%s.%s", name, attr.name, attr2.name), + fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), *attr2.value, attr2.pos); - if (drvPath && (std::string) attr.name == settings.thisSystem.get()) + if (drvPath && attr_name == settings.thisSystem.get()) drvPaths.push_back(DerivedPath::Built{*drvPath}); } } @@ -537,9 +538,10 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "formatter") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); checkApp( - fmt("%s.%s", name, attr.name), + fmt("%s.%s", name, attr_name), *attr.value, attr.pos); } } @@ -547,11 +549,12 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "packages" || name == "devShells") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) checkDerivation( - fmt("%s.%s.%s", name, attr.name, attr2.name), + fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), *attr2.value, attr2.pos); } } @@ -559,11 +562,12 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "apps") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) checkApp( - fmt("%s.%s.%s", name, attr.name, attr2.name), + fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), *attr2.value, attr2.pos); } } @@ -571,9 +575,10 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "defaultPackage" || name == "devShell") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); checkDerivation( - fmt("%s.%s", name, attr.name), + fmt("%s.%s", name, attr_name), *attr.value, attr.pos); } } @@ -581,9 +586,10 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "defaultApp") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); checkApp( - fmt("%s.%s", name, attr.name), + fmt("%s.%s", name, attr_name), *attr.value, attr.pos); } } @@ -591,7 +597,7 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "legacyPackages") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, attr.pos); + checkSystemName(state->symbols[attr.name], attr.pos); // FIXME: do getDerivations? } } @@ -602,7 +608,7 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "overlays") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) - checkOverlay(fmt("%s.%s", name, attr.name), + checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]), *attr.value, attr.pos); } @@ -612,14 +618,14 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "nixosModules") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) - checkModule(fmt("%s.%s", name, attr.name), + checkModule(fmt("%s.%s", name, state->symbols[attr.name]), *attr.value, attr.pos); } else if (name == "nixosConfigurations") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) - checkNixOSConfiguration(fmt("%s.%s", name, attr.name), + checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]), *attr.value, attr.pos); } @@ -632,16 +638,17 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "templates") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) - checkTemplate(fmt("%s.%s", name, attr.name), + checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]), *attr.value, attr.pos); } else if (name == "defaultBundler") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); checkBundler( - fmt("%s.%s", name, attr.name), + fmt("%s.%s", name, attr_name), *attr.value, attr.pos); } } @@ -649,11 +656,12 @@ struct CmdFlakeCheck : FlakeCommand else if (name == "bundlers") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { - checkSystemName(attr.name, attr.pos); + const auto & attr_name = state->symbols[attr.name]; + checkSystemName(attr_name, attr.pos); state->forceAttrs(*attr.value, attr.pos); for (auto & attr2 : *attr.value->attrs) { checkBundler( - fmt("%s.%s.%s", name, attr.name, attr2.name), + fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), *attr2.value, attr2.pos); } } @@ -1000,7 +1008,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON auto showDerivation = [&]() { - auto name = visitor.getAttr(state->sName)->getString(); + auto name = visitor.getAttr("name")->getString(); if (json) { std::optional description; if (auto aMeta = visitor.maybeGetAttr("meta")) { diff --git a/src/nix/main.cc b/src/nix/main.cc index 6198681e7..6d0f6ce6e 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -302,7 +302,7 @@ void mainWrapped(int argc, char * * argv) b["arity"] = primOp->arity; b["args"] = primOp->args; b["doc"] = trim(stripIndentation(primOp->doc)); - res[(std::string) builtin.name] = std::move(b); + res[state.symbols[builtin.name]] = std::move(b); } std::cout << res.dump() << "\n"; return; diff --git a/src/nix/repl.cc b/src/nix/repl.cc index fd1b95afa..998ff7328 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -73,7 +73,7 @@ struct NixRepl void initEnv(); void reloadFiles(); void addAttrsToScope(Value & attrs); - void addVarToScope(const Symbol & name, Value & v); + void addVarToScope(const SymbolIdx name, Value & v); Expr * parseString(std::string s); void evalString(std::string s, Value & v); @@ -347,9 +347,9 @@ StringSet NixRepl::completePrefix(const std::string & prefix) state->forceAttrs(v, noPos); for (auto & i : *v.attrs) { - std::string name = i.name; + std::string_view name = state->symbols[i.name]; if (name.substr(0, cur2.size()) != cur2) continue; - completions.insert(prev + expr + "." + name); + completions.insert(concatStrings(prev, expr, ".", name)); } } catch (ParseError & e) { @@ -464,8 +464,9 @@ bool NixRepl::processLine(std::string line) const auto [file, line] = [&] () -> std::pair { if (v.type() == nPath || v.type() == nString) { PathSet context; - auto filename = state->coerceToString(noPos, v, context); - return {state->symbols.create(*filename), 0}; + auto filename = state->coerceToString(noPos, v, context).toOwned(); + state->symbols.create(filename); + return {filename, 0}; } else if (v.isLambda()) { auto pos = state->positions[v.lambda.fun->pos]; return {pos.file, pos.line}; @@ -672,7 +673,7 @@ void NixRepl::initEnv() varNames.clear(); for (auto & i : state->staticBaseEnv.vars) - varNames.insert(i.first); + varNames.emplace(state->symbols[i.first]); } @@ -702,7 +703,7 @@ void NixRepl::addAttrsToScope(Value & attrs) for (auto & i : *attrs.attrs) { staticEnv.vars.emplace_back(i.name, displ); env->values[displ++] = i.value; - varNames.insert((std::string) i.name); + varNames.emplace(state->symbols[i.name]); } staticEnv.sort(); staticEnv.deduplicate(); @@ -710,7 +711,7 @@ void NixRepl::addAttrsToScope(Value & attrs) } -void NixRepl::addVarToScope(const Symbol & name, Value & v) +void NixRepl::addVarToScope(const SymbolIdx name, Value & v) { if (displ >= envSize) throw Error("environment full; cannot add more variables"); @@ -719,7 +720,7 @@ void NixRepl::addVarToScope(const Symbol & name, Value & v) staticEnv.vars.emplace_back(name, displ); staticEnv.sort(); env->values[displ++] = &v; - varNames.insert((std::string) name); + varNames.emplace(state->symbols[name]); } @@ -812,7 +813,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m typedef std::map Sorted; Sorted sorted; for (auto & i : *v.attrs) - sorted[i.name] = i.value; + sorted.emplace(state->symbols[i.name], i.value); for (auto & i : sorted) { if (isVarName(i.first)) diff --git a/src/nix/search.cc b/src/nix/search.cc index e284de95c..8b1e9ae6c 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -154,7 +154,7 @@ struct CmdSearch : InstallableCommand, MixJSON recurse(); else if (attrPath[0] == "legacyPackages" && attrPath.size() > 2) { - auto attr = cursor.maybeGetAttr(state->sRecurseForDerivations); + auto attr = cursor.maybeGetAttr("recurseForDerivations"); if (attr && attr->getBool()) recurse(); } From 8168a4cf4a4ecadd6868ea285cdb5729eb3e81bc Mon Sep 17 00:00:00 2001 From: pennae Date: Tue, 8 Mar 2022 15:26:17 +0100 Subject: [PATCH 27/79] shrink Attr by 8 bytes on 64bit machines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit with position and symbol tables in place we can now shrink Attr by a full pointer with some simple field reordering. since Attr is a very hot struct this has substantial impact on memory use, decreasing GC allocations and heap size by 10-15% each. we also get a ~15% performance improvement due to reduced GC loading. pure parsing has taken a hit over the branch base because positions are now slightly more expensive to create, but overall we get a noticeable improvement. before (on memory-friendliness): Benchmark 1: nix search --no-eval-cache --offline ../nixpkgs hello Time (mean ± σ): 6.960 s ± 0.028 s [User: 5.832 s, System: 0.897 s] Range (min … max): 6.886 s … 7.005 s 20 runs Benchmark 2: nix eval -f ../nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix Time (mean ± σ): 328.1 ms ± 1.7 ms [User: 295.8 ms, System: 32.2 ms] Range (min … max): 324.9 ms … 331.2 ms 20 runs Benchmark 3: nix eval --raw --impure --expr 'with import {}; system' Time (mean ± σ): 2.688 s ± 0.029 s [User: 2.365 s, System: 0.238 s] Range (min … max): 2.642 s … 2.742 s 20 runs after: Benchmark 1: nix search --no-eval-cache --offline ../nixpkgs hello Time (mean ± σ): 6.902 s ± 0.039 s [User: 5.844 s, System: 0.783 s] Range (min … max): 6.820 s … 6.956 s 20 runs Benchmark 2: nix eval -f ../nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix Time (mean ± σ): 330.7 ms ± 2.2 ms [User: 300.6 ms, System: 30.0 ms] Range (min … max): 327.5 ms … 334.5 ms 20 runs Benchmark 3: nix eval --raw --impure --expr 'with import {}; system' Time (mean ± σ): 2.330 s ± 0.027 s [User: 2.040 s, System: 0.234 s] Range (min … max): 2.272 s … 2.383 s 20 runs --- src/libexpr/attr-set.hh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh index c42aba0f6..3bf23eeb2 100644 --- a/src/libexpr/attr-set.hh +++ b/src/libexpr/attr-set.hh @@ -15,11 +15,15 @@ struct Value; /* Map one attribute name to its value. */ struct Attr { + /* the placement of `name` and `pos` in this struct is important. + both of them are uint32 wrappers, they are next to each other + to make sure that Attr has no padding on 64 bit machines. that + way we keep Attr size at two words with no wasted space. */ SymbolIdx name; - Value * value; PosIdx pos; + Value * value; Attr(SymbolIdx name, Value * value, PosIdx pos = noPos) - : name(name), value(value), pos(pos) { }; + : name(name), pos(pos), value(value) { }; Attr() { }; bool operator < (const Attr & a) const { @@ -27,6 +31,11 @@ struct Attr } }; +static_assert(sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *), + "performance of the evaluator is highly sensitive to the size of Attr. " + "avoid introducing any padding into Attr if at all possible, and do not " + "introduce new fields that need not be present for almost every instance."); + /* Bindings contains all the attributes of an attribute set. It is defined by its size and its capacity, the capacity being the number of Attr elements allocated after this structure, while the size corresponds to From 8adaa6acb5a513e010d262386271ef39c418ea7f Mon Sep 17 00:00:00 2001 From: pennae Date: Tue, 5 Apr 2022 18:37:38 +0200 Subject: [PATCH 28/79] remove pos it's no longer needed now that positions aren't really pointers any more. --- src/libutil/ref.hh | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/src/libutil/ref.hh b/src/libutil/ref.hh index 347b81f73..f9578afc7 100644 --- a/src/libutil/ref.hh +++ b/src/libutil/ref.hh @@ -99,47 +99,4 @@ make_ref(Args&&... args) return ref(p); } - -/* A non-nullable pointer. - This is similar to a C++ "& reference", but mutable. - This is similar to ref but backed by a regular pointer instead of a smart pointer. - */ -template -class ptr { -private: - T * p; - -public: - ptr(const ptr & r) - : p(r.p) - { } - - explicit ptr(T * p) - : p(p) - { - if (!p) - throw std::invalid_argument("null pointer cast to ptr"); - } - - T* operator ->() const - { - return &*p; - } - - T& operator *() const - { - return *p; - } - - bool operator == (const ptr & other) const - { - return p == other.p; - } - - bool operator != (const ptr & other) const - { - return p != other.p; - } -}; - } From f25112d3832b93a2bc8abe7936e6355dae9a25d5 Mon Sep 17 00:00:00 2001 From: Tom Bereknyei Date: Thu, 21 Apr 2022 16:41:37 -0400 Subject: [PATCH 29/79] fix: builtins.toFile adds path to allowedPaths The produced path is then allowed be imported or utilized elsewhere: ``` assert (43 == import (builtins.toFile "source" "43")); "good" ``` This will still fail on write-only stores. --- doc/manual/src/release-notes/rl-next.md | 4 ++++ src/libexpr/primops.cc | 7 ++++--- tests/eval.sh | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 3e2998c6c..f16ae901c 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -2,3 +2,7 @@ * `nix repl` has a new build-'n-link (`:bl`) command that builds a derivation while creating GC root symlinks. + +* The path produced by `builtins.toFile` is now allowed to be imported or read + even with restricted evaluation. Note that this will not work with a + read-only store. diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 73817dbdd..a93ac8a30 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1798,15 +1798,16 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu refs.insert(state.store->parseStorePath(path)); } - auto storePath = state.store->printStorePath(settings.readOnlyMode + auto storePath = settings.readOnlyMode ? state.store->computeStorePathForText(name, contents, refs) - : state.store->addTextToStore(name, contents, refs, state.repair)); + : state.store->addTextToStore(name, contents, refs, state.repair); /* Note: we don't need to add `context' to the context of the result, since `storePath' itself has references to the paths used in args[1]. */ - v.mkString(storePath, {storePath}); + /* Add the output of this to the allowed paths. */ + state.allowAndSetStorePathString(storePath, v); } static RegisterPrimOp primop_toFile({ diff --git a/tests/eval.sh b/tests/eval.sh index 2e5ceb969..d74976019 100644 --- a/tests/eval.sh +++ b/tests/eval.sh @@ -20,6 +20,8 @@ nix eval --expr 'assert 1 + 2 == 3; true' [[ $(nix eval attr --json -f "./eval.nix") == '{"foo":"bar"}' ]] [[ $(nix eval int -f - < "./eval.nix") == 123 ]] +# Check if toFile can be utilized during restricted eval +[[ $(nix eval --restrict-eval --expr 'import (builtins.toFile "source" "42")') == 42 ]] nix-instantiate --eval -E 'assert 1 + 2 == 3; true' [[ $(nix-instantiate -A int --eval "./eval.nix") == 123 ]] From 7ca6fbc8caf30d0f8b0dca9a294022d3e37c4082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Fri, 22 Apr 2022 10:01:02 +0200 Subject: [PATCH 30/79] Move ChunkedVector to its own header --- src/libexpr/nixexpr.hh | 1 + src/libexpr/symbol-table.hh | 1 + src/libutil/chunked-vector.hh | 68 +++++++++++++++++++++++++++++++++++ src/libutil/types.hh | 59 ------------------------------ 4 files changed, 70 insertions(+), 59 deletions(-) create mode 100644 src/libutil/chunked-vector.hh diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 1a387d4fd..217c7e74d 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -6,6 +6,7 @@ #include "value.hh" #include "symbol-table.hh" #include "error.hh" +#include "chunked-vector.hh" namespace nix { diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh index d0cd841a0..b6ad60f68 100644 --- a/src/libexpr/symbol-table.hh +++ b/src/libexpr/symbol-table.hh @@ -5,6 +5,7 @@ #include #include "types.hh" +#include "chunked-vector.hh" namespace nix { diff --git a/src/libutil/chunked-vector.hh b/src/libutil/chunked-vector.hh new file mode 100644 index 000000000..f15af9cd7 --- /dev/null +++ b/src/libutil/chunked-vector.hh @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include + +namespace nix { + +/* Provides an indexable container like vector<> with memory overhead + guarantees like list<> by allocating storage in chunks of ChunkSize + elements instead of using a contiguous memory allocation like vector<> + does. Not using a single vector that is resized reduces memory overhead + on large data sets by on average (growth factor)/2, mostly + eliminates copies within the vector during resizing, and provides stable + references to its elements. */ +template +class ChunkedVector { +private: + uint32_t size_ = 0; + std::vector> chunks; + + /* keep this out of the ::add hot path */ + [[gnu::noinline]] + auto & addChunk() + { + if (size_ >= std::numeric_limits::max() - ChunkSize) + abort(); + chunks.emplace_back(); + chunks.back().reserve(ChunkSize); + return chunks.back(); + } + +public: + ChunkedVector(uint32_t reserve) + { + chunks.reserve(reserve); + addChunk(); + } + + uint32_t size() const { return size_; } + + std::pair add(T value) + { + const auto idx = size_++; + auto & chunk = [&] () -> auto & { + if (auto & back = chunks.back(); back.size() < ChunkSize) + return back; + return addChunk(); + }(); + auto & result = chunk.emplace_back(std::move(value)); + return {result, idx}; + } + + const T & operator[](uint32_t idx) const + { + return chunks[idx / ChunkSize][idx % ChunkSize]; + } + + template + void forEach(Fn fn) const + { + for (const auto & c : chunks) + for (const auto & e : c) + fn(e); + } +}; +} diff --git a/src/libutil/types.hh b/src/libutil/types.hh index bd1dd8bee..6bcbd7e1d 100644 --- a/src/libutil/types.hh +++ b/src/libutil/types.hh @@ -103,63 +103,4 @@ public: Ptr operator->() const { return Ptr(**this); } }; -/* Provides an indexable container like vector<> with memory overhead - guarantees like list<> by allocating storage in chunks of ChunkSize - elements instead of using a contiguous memory allocation like vector<> - does. Not using a single vector that is resized reduces memory overhead - on large data sets by on average (growth factor)/2, mostly - eliminates copies within the vector during resizing, and provides stable - references to its elements. */ -template -class ChunkedVector { -private: - uint32_t size_ = 0; - std::vector> chunks; - - /* keep this out of the ::add hot path */ - [[gnu::noinline]] - auto & addChunk() - { - if (size_ >= std::numeric_limits::max() - ChunkSize) - abort(); - chunks.emplace_back(); - chunks.back().reserve(ChunkSize); - return chunks.back(); - } - -public: - ChunkedVector(uint32_t reserve) - { - chunks.reserve(reserve); - addChunk(); - } - - uint32_t size() const { return size_; } - - std::pair add(T value) - { - const auto idx = size_++; - auto & chunk = [&] () -> auto & { - if (auto & back = chunks.back(); back.size() < ChunkSize) - return back; - return addChunk(); - }(); - auto & result = chunk.emplace_back(std::move(value)); - return {result, idx}; - } - - const T & operator[](uint32_t idx) const - { - return chunks[idx / ChunkSize][idx % ChunkSize]; - } - - template - void forEach(Fn fn) const - { - for (const auto & c : chunks) - for (const auto & e : c) - fn(e); - } -}; - } From 484badfa096db4c001f66eccbe00b70471f2e767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Fri, 22 Apr 2022 10:01:10 +0200 Subject: [PATCH 31/79] Add some tests for ChunkedVector --- src/libutil/tests/chunked-vector.cc | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/libutil/tests/chunked-vector.cc diff --git a/src/libutil/tests/chunked-vector.cc b/src/libutil/tests/chunked-vector.cc new file mode 100644 index 000000000..868d11f6f --- /dev/null +++ b/src/libutil/tests/chunked-vector.cc @@ -0,0 +1,54 @@ +#include "chunked-vector.hh" + +#include + +namespace nix { + TEST(ChunkedVector, InitEmpty) { + auto v = ChunkedVector(100); + ASSERT_EQ(v.size(), 0); + } + + TEST(ChunkedVector, GrowsCorrectly) { + auto v = ChunkedVector(100); + for (auto i = 1; i < 20; i++) { + v.add(i); + ASSERT_EQ(v.size(), i); + } + } + + TEST(ChunkedVector, AddAndGet) { + auto v = ChunkedVector(100); + for (auto i = 1; i < 20; i++) { + auto [i2, idx] = v.add(i); + auto & i3 = v[idx]; + ASSERT_EQ(i, i2); + ASSERT_EQ(&i2, &i3); + } + } + + TEST(ChunkedVector, ForEach) { + auto v = ChunkedVector(100); + for (auto i = 1; i < 20; i++) { + v.add(i); + } + int count = 0; + v.forEach([&count](int elt) { + count++; + }); + ASSERT_EQ(count, v.size()); + } + + TEST(ChunkedVector, OverflowOK) { + // Similar to the AddAndGet, but intentionnally use a small + // initial ChunkedVector to force it to overflow + auto v = ChunkedVector(2); + for (auto i = 1; i < 20; i++) { + auto [i2, idx] = v.add(i); + auto & i3 = v[idx]; + ASSERT_EQ(i, i2); + ASSERT_EQ(&i2, &i3); + } + } + +} + From 7b889f31eac512c548fe56a73cd57d00d4fb89c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Fri, 22 Apr 2022 10:56:01 +0200 Subject: [PATCH 32/79] Fix the darwin build Looks like the auto-merge is indeed quite broken and merges even when the CI fails --- src/libutil/chunked-vector.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libutil/chunked-vector.hh b/src/libutil/chunked-vector.hh index f15af9cd7..0a4f0b400 100644 --- a/src/libutil/chunked-vector.hh +++ b/src/libutil/chunked-vector.hh @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include From 0256e5578e97a11db66207e1f8e231db115c91f8 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sat, 23 Apr 2022 23:04:35 +0200 Subject: [PATCH 33/79] libfetchers/git: hardcode `--git-dir` To demonstrate the problem: * You need a `git` at 2.33.3 in your $PATH * An expression like this in a git repository: ``` nix { outputs = { self, nixpkgs }: { packages.foo.x86_64-linux = with nixpkgs.legacyPackages.x86_64-linux; runCommand "snens" { } '' echo ${(builtins.fetchGit ./.).lastModifiedDate} > $out ''; }; } ``` Now, when instantiating the package via `builtins.getFlake`, it fails on Nix 2.7 like this: $ nix-instantiate -E '(builtins.getFlake "'"$(pwd)"'").packages.foo.x86_64-linux' fatal: unsafe repository ('/nix/store/a7j3125km4h8l0p71q6ssfkxamfh5d61-source' is owned by someone else) To add an exception for this directory, call: git config --global --add safe.directory /nix/store/a7j3125km4h8l0p71q6ssfkxamfh5d61-source error: program 'git' failed with exit code 128 (use '--show-trace' to show detailed location information) This breaks e.g. `nixops`-deployments using flakes with similar expressions as shown above. The cause for this is that `git(1)` tries to find the highest `.git`-directory in the directory tree and if it finds a such a directory, but with another owning user (root vs. the user who evaluates the expression), it fails as above. This was changed recently to fix CVE-2022-24765[1]. By explicitly specifying `--git-dir`, Git assumes to be in the top-level directory and doesn't attempt to look for a `.git`-directory in the parent directories and thus the code-path leading to said error is never reached. [1] https://lore.kernel.org/git/xmqqv8veb5i6.fsf@gitster.g/ --- src/libfetchers/git.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 34b1342a0..219a5ca7a 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -23,7 +23,7 @@ const std::string gitInitialBranch = "__nix_dummy_branch"; static std::string readHead(const Path & path) { - return chomp(runProgram("git", true, { "-C", path, "rev-parse", "--abbrev-ref", "HEAD" })); + return chomp(runProgram("git", true, { "-C", path, "--git-dir", ".git", "rev-parse", "--abbrev-ref", "HEAD" })); } static bool isNotDotGitDirectory(const Path & path) @@ -152,11 +152,11 @@ struct GitInputScheme : InputScheme assert(sourcePath); runProgram("git", true, - { "-C", *sourcePath, "add", "--force", "--intent-to-add", "--", std::string(file) }); + { "-C", *sourcePath, "--git-dir", ".git", "add", "--force", "--intent-to-add", "--", std::string(file) }); if (commitMsg) runProgram("git", true, - { "-C", *sourcePath, "commit", std::string(file), "-m", *commitMsg }); + { "-C", *sourcePath, "--git-dir", ".git", "commit", std::string(file), "-m", *commitMsg }); } std::pair getActualUrl(const Input & input) const @@ -259,7 +259,7 @@ struct GitInputScheme : InputScheme if (hasHead) { // Using git diff is preferrable over lower-level operations here, // because its conceptually simpler and we only need the exit code anyways. - auto gitDiffOpts = Strings({ "-C", actualUrl, "diff", "HEAD", "--quiet"}); + auto gitDiffOpts = Strings({ "-C", actualUrl, "--git-dir", ".git", "diff", "HEAD", "--quiet"}); if (!submodules) { // Changes in submodules should only make the tree dirty // when those submodules will be copied as well. @@ -284,7 +284,7 @@ struct GitInputScheme : InputScheme if (fetchSettings.warnDirty) warn("Git tree '%s' is dirty", actualUrl); - auto gitOpts = Strings({ "-C", actualUrl, "ls-files", "-z" }); + auto gitOpts = Strings({ "-C", actualUrl, "--git-dir", ".git", "ls-files", "-z" }); if (submodules) gitOpts.emplace_back("--recurse-submodules"); @@ -314,7 +314,7 @@ struct GitInputScheme : InputScheme // modified dirty file? input.attrs.insert_or_assign( "lastModified", - hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); + hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "--git-dir", ".git", "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); return {std::move(storePath), input}; } @@ -335,7 +335,7 @@ struct GitInputScheme : InputScheme if (!input.getRev()) input.attrs.insert_or_assign("rev", - Hash::parseAny(chomp(runProgram("git", true, { "-C", actualUrl, "rev-parse", *input.getRef() })), htSHA1).gitRev()); + Hash::parseAny(chomp(runProgram("git", true, { "-C", actualUrl, "--git-dir", ".git", "rev-parse", *input.getRef() })), htSHA1).gitRev()); repoDir = actualUrl; From d1f5356311bff3cb7840a82e6b35aeb66a1f6416 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 24 Apr 2022 18:06:36 +0200 Subject: [PATCH 34/79] libfetchers/git: fix for nixos-rebuild The `--git-dir=` must be `.` in some cases (for cached repos that are "bare" repos in `~/.cache/nix/gitv3`). With this fix we can add `--git-dir` to each `git`-invokation needed for `nixos-rebuild`. --- src/libfetchers/git.cc | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 219a5ca7a..af40990e5 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -21,6 +21,15 @@ namespace nix::fetchers { // old version of git, which will ignore unrecognized `-c` options. const std::string gitInitialBranch = "__nix_dummy_branch"; +static std::string getGitDir() +{ + auto gitDir = getEnv("GIT_DIR"); + if (!gitDir) { + return ".git"; + } + return *gitDir; +} + static std::string readHead(const Path & path) { return chomp(runProgram("git", true, { "-C", path, "--git-dir", ".git", "rev-parse", "--abbrev-ref", "HEAD" })); @@ -150,13 +159,14 @@ struct GitInputScheme : InputScheme { auto sourcePath = getSourcePath(input); assert(sourcePath); + auto gitDir = getGitDir(); runProgram("git", true, - { "-C", *sourcePath, "--git-dir", ".git", "add", "--force", "--intent-to-add", "--", std::string(file) }); + { "-C", *sourcePath, "--git-dir", gitDir, "add", "--force", "--intent-to-add", "--", std::string(file) }); if (commitMsg) runProgram("git", true, - { "-C", *sourcePath, "--git-dir", ".git", "commit", std::string(file), "-m", *commitMsg }); + { "-C", *sourcePath, "--git-dir", gitDir, "commit", std::string(file), "-m", *commitMsg }); } std::pair getActualUrl(const Input & input) const @@ -175,6 +185,7 @@ struct GitInputScheme : InputScheme std::pair fetch(ref store, const Input & _input) override { Input input(_input); + auto gitDir = getGitDir(); std::string name = input.getName(); @@ -237,7 +248,7 @@ struct GitInputScheme : InputScheme since that is the refrence we want to use later on. */ auto result = runProgram(RunOptions { .program = "git", - .args = { "-C", actualUrl, "--git-dir=.git", "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" }, + .args = { "-C", actualUrl, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" }, .environment = env, .mergeStderrToStdout = true }); @@ -259,7 +270,7 @@ struct GitInputScheme : InputScheme if (hasHead) { // Using git diff is preferrable over lower-level operations here, // because its conceptually simpler and we only need the exit code anyways. - auto gitDiffOpts = Strings({ "-C", actualUrl, "--git-dir", ".git", "diff", "HEAD", "--quiet"}); + auto gitDiffOpts = Strings({ "-C", actualUrl, "--git-dir", gitDir, "diff", "HEAD", "--quiet"}); if (!submodules) { // Changes in submodules should only make the tree dirty // when those submodules will be copied as well. @@ -284,7 +295,7 @@ struct GitInputScheme : InputScheme if (fetchSettings.warnDirty) warn("Git tree '%s' is dirty", actualUrl); - auto gitOpts = Strings({ "-C", actualUrl, "--git-dir", ".git", "ls-files", "-z" }); + auto gitOpts = Strings({ "-C", actualUrl, "--git-dir", gitDir, "ls-files", "-z" }); if (submodules) gitOpts.emplace_back("--recurse-submodules"); @@ -314,7 +325,7 @@ struct GitInputScheme : InputScheme // modified dirty file? input.attrs.insert_or_assign( "lastModified", - hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "--git-dir", ".git", "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); + hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); return {std::move(storePath), input}; } @@ -335,7 +346,7 @@ struct GitInputScheme : InputScheme if (!input.getRev()) input.attrs.insert_or_assign("rev", - Hash::parseAny(chomp(runProgram("git", true, { "-C", actualUrl, "--git-dir", ".git", "rev-parse", *input.getRef() })), htSHA1).gitRev()); + Hash::parseAny(chomp(runProgram("git", true, { "-C", actualUrl, "--git-dir", gitDir, "rev-parse", *input.getRef() })), htSHA1).gitRev()); repoDir = actualUrl; @@ -351,6 +362,7 @@ struct GitInputScheme : InputScheme Path cacheDir = getCacheDir() + "/nix/gitv3/" + hashString(htSHA256, actualUrl).to_string(Base32, false); repoDir = cacheDir; + gitDir = "."; createDirs(dirOf(cacheDir)); PathLocks cacheDirLock({cacheDir + ".lock"}); @@ -427,7 +439,7 @@ struct GitInputScheme : InputScheme // cache dir lock is removed at scope end; we will only use read-only operations on specific revisions in the remainder } - bool isShallow = chomp(runProgram("git", true, { "-C", repoDir, "rev-parse", "--is-shallow-repository" })) == "true"; + bool isShallow = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-parse", "--is-shallow-repository" })) == "true"; if (isShallow && !shallow) throw Error("'%s' is a shallow Git repository, but a non-shallow repository is needed", actualUrl); From c67d8876c34bdcfbc0d3ea9e68e68dc90fbdaeac Mon Sep 17 00:00:00 2001 From: midchildan Date: Mon, 25 Apr 2022 01:23:49 +0900 Subject: [PATCH 35/79] feat: add integration with zsh's run-help --- doc/manual/src/release-notes/rl-next.md | 5 ++ misc/zsh/local.mk | 1 + misc/zsh/run-help-nix | 97 +++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 misc/zsh/run-help-nix diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 06c2583c9..7b3ad4e58 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1,5 +1,10 @@ # Release X.Y (202?-??-??) +* Nix now provides better integration with zsh's run-help feature. It is now + included in the Nix installation in the form of an autoloadable shell + function, run-help-nix. It picks up Nix subcommands from the currently typed + in command and directs the user to the associated man pages. + * `nix repl` has a new build-'n-link (`:bl`) command that builds a derivation while creating GC root symlinks. diff --git a/misc/zsh/local.mk b/misc/zsh/local.mk index 418fb1377..0b4e294fb 100644 --- a/misc/zsh/local.mk +++ b/misc/zsh/local.mk @@ -1 +1,2 @@ $(eval $(call install-file-as, $(d)/completion.zsh, $(datarootdir)/zsh/site-functions/_nix, 0644)) +$(eval $(call install-file-as, $(d)/run-help-nix, $(datarootdir)/zsh/site-functions/run-help-nix, 0644)) diff --git a/misc/zsh/run-help-nix b/misc/zsh/run-help-nix new file mode 100644 index 000000000..534b51ec2 --- /dev/null +++ b/misc/zsh/run-help-nix @@ -0,0 +1,97 @@ +emulate -L zsh + +# run-help is a zsh widget that can be bound to a key. It mainly looks up the +# man page for the currently typed in command. +# +# Although run-help works for any command without requiring special support, +# it can only deduce the right man page based solely on the name of the +# command. Programs like Nix provide better integration with run-help by +# helping zsh identify Nix subcommands and their corresponding man pages. This +# is what this function does. +# +# To actually use run-help on zsh, place the following lines in your .zshrc: +# +# (( $+aliases[run-help] )) && unalias run-help +# autoload -Uz run-help run-help-nix +# +# Then also assign run-help to any key of choice: +# +# bindkey '^[h' run-help + +if (( $# == 0 )); then + man nix + return +fi + +while [[ "$#" != 0 && "$1" == -* ]]; do + shift +done + +case "$1" in + flake) + case "$2" in + archive|check|clone|info|init|lock|metadata|new|prefetch|show|update) + man "nix3-$1-$2" ;; + *) + man "nix3-$1" ;; + esac ;; + hash) + case "$2" in + file|path|to-base16|to-base32|to-base64|to-sri) + man "nix3-$1-$2" ;; + *) + man "nix3-$1" ;; + esac ;; + key) + case "$2" in + convert-secret-to-public|generate-secret) + man "nix3-$1-$2" ;; + *) + man "nix3-$1" ;; + esac ;; + nar) + case "$2" in + cat|dump-path|ls) + man "nix3-$1-$2" ;; + *) + man "nix3-$1" ;; + esac ;; + profile) + case "$2" in + diff-closures|history|install|list|remove|rollback|upgrade|wipe-history) + man "nix3-$1-$2" ;; + *) + man "nix3-$1" ;; + esac ;; + realisation) + case "$2" in + info) + man "nix3-$1-$2" ;; + *) + man "nix3-$1" ;; + esac ;; + registry) + case "$2" in + add|list|pin|remove) + man "nix3-$1-$2" ;; + *) + man "nix3-$1" ;; + esac ;; + store) + case "$2" in + add-file|add-path|cat|copy-sigs|delete|diff-closures|dump-path|gc|ls) + ;& # fallthrough + make-content-addressable|optimise|ping|prefetch-file|repair|sign|verify) + man "nix3-$1-$2" ;; + *) + man "nix3-$1" ;; + esac ;; + *) + if man -w "nix3-$1" >/dev/null 2>&1; then + man "nix3-$1" + else + man nix + fi ;; +esac + +return $? From 0f7c7ab97bef68339e480a018425de8b3f05ec43 Mon Sep 17 00:00:00 2001 From: midchildan Date: Mon, 25 Apr 2022 02:34:59 +0900 Subject: [PATCH 36/79] fix: typo make-content-addressable -> make-content-addressed --- misc/zsh/run-help-nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/zsh/run-help-nix b/misc/zsh/run-help-nix index 534b51ec2..45588470b 100644 --- a/misc/zsh/run-help-nix +++ b/misc/zsh/run-help-nix @@ -81,7 +81,7 @@ case "$1" in case "$2" in add-file|add-path|cat|copy-sigs|delete|diff-closures|dump-path|gc|ls) ;& # fallthrough - make-content-addressable|optimise|ping|prefetch-file|repair|sign|verify) + make-content-addressed|optimise|ping|prefetch-file|repair|sign|verify) man "nix3-$1-$2" ;; *) man "nix3-$1" ;; From a385e51a086006c0f7d7c4bc6ed910dbe5375c4d Mon Sep 17 00:00:00 2001 From: pennae Date: Fri, 22 Apr 2022 21:45:39 +0200 Subject: [PATCH 37/79] rename SymbolIdx -> Symbol, Symbol -> SymbolStr after #6218 `Symbol` no longer confers a uniqueness invariant on the string it wraps, it is now possible to create multiple symbols that compare equal but whose string contents have different addresses. this guarantee is now only provided by `SymbolIdx`, leaving `Symbol` only as a string wrapper that knows about the intricacies of how symbols need to be formatted for output. this change renames `SymbolIdx` to `Symbol` to restore the previous semantics of `Symbol` to that name. we also keep the wrapper type and rename it to `SymbolStr` instead of returning plain strings from lookups into the symbol table because symbols are formatted for output in many places. theoretically we do not need `SymbolStr`, only a function that formats a string for output as a symbol, but having to wrap every symbol that appears in a message into eg `formatSymbol()` is error-prone and inconvient. --- src/libcmd/installables.cc | 6 +-- src/libexpr/attr-path.cc | 2 +- src/libexpr/attr-set.cc | 4 +- src/libexpr/attr-set.hh | 12 +++--- src/libexpr/eval-cache.cc | 46 ++++++++++++----------- src/libexpr/eval-cache.hh | 2 +- src/libexpr/eval.cc | 8 ++-- src/libexpr/eval.hh | 12 +++--- src/libexpr/nixexpr.cc | 19 ++++------ src/libexpr/nixexpr.hh | 34 ++++++++--------- src/libexpr/parser.y | 6 +-- src/libexpr/primops.cc | 8 ++-- src/libexpr/symbol-table.hh | 60 ++++++++++++++++++------------ src/libexpr/value.hh | 15 +++----- src/nix/app.cc | 2 +- src/nix/flake.cc | 74 +++++++++++++++++++------------------ src/nix/repl.cc | 4 +- src/nix/search.cc | 16 ++++---- 18 files changed, 171 insertions(+), 159 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index c81f76a22..693b5e2a2 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -291,7 +291,7 @@ void completeFlakeRefWithFragment( std::string lastAttr; if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) { - lastAttr = attrPath.back(); + lastAttr = evalState->symbols[attrPath.back()]; attrPath.pop_back(); } @@ -299,11 +299,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))); } } } diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc index f63caeb10..94ab60f9a 100644 --- a/src/libexpr/attr-path.cc +++ b/src/libexpr/attr-path.cc @@ -36,7 +36,7 @@ std::vector parseAttrPath(EvalState & state, std::string_view s) { std::vector res; for (auto & a : parseAttrPath(s)) - res.emplace_back(a); + res.push_back(state.symbols.create(a)); return res; } diff --git a/src/libexpr/attr-set.cc b/src/libexpr/attr-set.cc index 1d17ef7e4..61996eae4 100644 --- a/src/libexpr/attr-set.cc +++ b/src/libexpr/attr-set.cc @@ -26,7 +26,7 @@ Bindings * EvalState::allocBindings(size_t capacity) /* Create a new attribute named 'name' on an existing attribute set stored in 'vAttrs' and return the newly allocated Value which is associated with this attribute. */ -Value * EvalState::allocAttr(Value & vAttrs, const SymbolIdx & name) +Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name) { Value * v = allocValue(); vAttrs.attrs->push_back(Attr(name, v)); @@ -40,7 +40,7 @@ Value * EvalState::allocAttr(Value & vAttrs, std::string_view name) } -Value & BindingsBuilder::alloc(const SymbolIdx & name, PosIdx pos) +Value & BindingsBuilder::alloc(const Symbol & name, PosIdx pos) { auto value = state.allocValue(); bindings->push_back(Attr(name, value, pos)); diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh index 3bf23eeb2..31251efc4 100644 --- a/src/libexpr/attr-set.hh +++ b/src/libexpr/attr-set.hh @@ -19,10 +19,10 @@ struct Attr both of them are uint32 wrappers, they are next to each other to make sure that Attr has no padding on 64 bit machines. that way we keep Attr size at two words with no wasted space. */ - SymbolIdx name; + Symbol name; PosIdx pos; Value * value; - Attr(SymbolIdx name, Value * value, PosIdx pos = noPos) + Attr(Symbol name, Value * value, PosIdx pos = noPos) : name(name), pos(pos), value(value) { }; Attr() { }; bool operator < (const Attr & a) const @@ -66,7 +66,7 @@ public: attrs[size_++] = attr; } - iterator find(const SymbolIdx & name) + iterator find(const Symbol & name) { Attr key(name, 0); iterator i = std::lower_bound(begin(), end(), key); @@ -74,7 +74,7 @@ public: return end(); } - Attr * get(const SymbolIdx & name) + Attr * get(const Symbol & name) { Attr key(name, 0); iterator i = std::lower_bound(begin(), end(), key); @@ -128,7 +128,7 @@ public: : bindings(bindings), state(state) { } - void insert(SymbolIdx name, Value * value, PosIdx pos = noPos) + void insert(Symbol name, Value * value, PosIdx pos = noPos) { insert(Attr(name, value, pos)); } @@ -143,7 +143,7 @@ public: bindings->push_back(attr); } - Value & alloc(const SymbolIdx & name, PosIdx pos = noPos); + Value & alloc(const Symbol & name, PosIdx pos = noPos); Value & alloc(std::string_view name, PosIdx pos = noPos); diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 0d2160efd..00e80ae3b 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -92,6 +92,7 @@ struct AttrDb AttrId setAttrs( AttrKey key, + const SymbolTable & symbols, const std::vector & attrs) { return doSQLite([&]() @@ -110,7 +111,7 @@ struct AttrDb for (auto & attr : attrs) state->insertAttribute.use() (rowId) - (attr) + (symbols[attr]) (AttrType::Placeholder) (0, false).exec(); @@ -253,7 +254,7 @@ struct AttrDb std::vector attrs; auto queryAttributes(state->queryAttributes.use()(rowId)); while (queryAttributes.next()) - attrs.emplace_back(queryAttributes.getStr(0)); + attrs.emplace_back(symbols.create(queryAttributes.getStr(0))); return {{rowId, attrs}}; } case AttrType::String: { @@ -331,7 +332,7 @@ AttrKey AttrCursor::getKey() parent->first->getKey(), root->state.symbols); assert(parent->first->cachedValue); } - return {parent->first->cachedValue->first, parent->second}; + return {parent->first->cachedValue->first, root->state.symbols[parent->second]}; } Value & AttrCursor::getValue() @@ -340,7 +341,7 @@ Value & AttrCursor::getValue() if (parent) { auto & vParent = parent->first->getValue(); root->state.forceAttrs(vParent, noPos); - auto attr = vParent.attrs->get(root->state.symbols.create(parent->second)); + auto attr = vParent.attrs->get(parent->second); if (!attr) throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr()); _value = allocRootValue(attr->value); @@ -369,12 +370,12 @@ std::vector AttrCursor::getAttrPath(Symbol name) const std::string AttrCursor::getAttrPathStr() const { - return concatStringsSep(".", getAttrPath()); + return concatStringsSep(".", root->state.symbols.resolve(getAttrPath())); } std::string AttrCursor::getAttrPathStr(Symbol name) const { - return concatStringsSep(".", getAttrPath(name)); + return concatStringsSep(".", root->state.symbols.resolve(getAttrPath(name))); } Value & AttrCursor::forceValue() @@ -414,9 +415,9 @@ Suggestions AttrCursor::getSuggestionsForAttr(Symbol name) auto attrNames = getAttrs(); std::set strAttrNames; for (auto & name : attrNames) - strAttrNames.insert(std::string(name)); + strAttrNames.insert(root->state.symbols[name]); - return Suggestions::bestMatches(strAttrNames, name); + return Suggestions::bestMatches(strAttrNames, root->state.symbols[name]); } std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool forceErrors) @@ -428,11 +429,11 @@ std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool if (cachedValue) { if (auto attrs = std::get_if>(&cachedValue->second)) { for (auto & attr : *attrs) - if (attr == name) - return std::make_shared(root, std::make_pair(shared_from_this(), name)); + if (root->state.symbols[attr] == name) + return std::make_shared(root, std::make_pair(shared_from_this(), attr)); return nullptr; } else if (std::get_if(&cachedValue->second)) { - auto attr = root->db->getAttr({cachedValue->first, name}, root->state.symbols); + auto attr = root->db->getAttr({cachedValue->first, std::string(name)}, root->state.symbols); if (attr) { if (std::get_if(&attr->second)) return nullptr; @@ -440,10 +441,10 @@ std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool if (forceErrors) debug("reevaluating failed cached attribute '%s'"); else - throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(name)); + throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(root->state.symbols.create(name))); } else return std::make_shared(root, - std::make_pair(shared_from_this(), name), nullptr, std::move(attr)); + std::make_pair(shared_from_this(), root->state.symbols.create(name)), nullptr, std::move(attr)); } // Incomplete attrset, so need to fall thru and // evaluate to see whether 'name' exists @@ -470,7 +471,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool if (root->db) { if (!cachedValue) cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()}; - root->db->setMissing({cachedValue->first, name}); + root->db->setMissing({cachedValue->first, std::string(name)}); } return nullptr; } @@ -479,18 +480,18 @@ std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool if (root->db) { if (!cachedValue) cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()}; - cachedValue2 = {root->db->setPlaceholder({cachedValue->first, name}), placeholder_t()}; + cachedValue2 = {root->db->setPlaceholder({cachedValue->first, std::string(name)}), placeholder_t()}; } return make_ref( - root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2)); + root, std::make_pair(shared_from_this(), root->state.symbols.create(name)), attr->value, std::move(cachedValue2)); } ref AttrCursor::getAttr(std::string_view name, bool forceErrors) { auto p = maybeGetAttr(name, forceErrors); if (!p) - throw Error("attribute '%s' does not exist", getAttrPathStr(name)); + throw Error("attribute '%s' does not exist", getAttrPathStr(root->state.symbols.create(name))); return ref(p); } @@ -498,7 +499,7 @@ OrSuggestions> AttrCursor::findAlongAttrPath(const std::vectormaybeGetAttr(attr, force); + auto child = res->maybeGetAttr(root->state.symbols[attr], force); if (!child) { auto suggestions = res->getSuggestionsForAttr(attr); return OrSuggestions>::failed(suggestions); @@ -606,13 +607,14 @@ std::vector AttrCursor::getAttrs() std::vector attrs; for (auto & attr : *getValue().attrs) - attrs.push_back(root->state.symbols[attr.name]); - std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) { - return (const std::string &) a < (const std::string &) b; + attrs.push_back(attr.name); + std::sort(attrs.begin(), attrs.end(), [&](Symbol a, Symbol b) { + std::string_view sa = root->state.symbols[a], sb = root->state.symbols[b]; + return sa < sb; }); if (root->db) - cachedValue = {root->db->setAttrs(getKey(), attrs), attrs}; + cachedValue = {root->db->setAttrs(getKey(), root->state.symbols, attrs), attrs}; return attrs; } diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh index f4481c72a..288ecd7e3 100644 --- a/src/libexpr/eval-cache.hh +++ b/src/libexpr/eval-cache.hh @@ -51,7 +51,7 @@ struct missing_t {}; struct misc_t {}; struct failed_t {}; typedef uint64_t AttrId; -typedef std::pair AttrKey; +typedef std::pair AttrKey; typedef std::pair string_t; typedef std::variant< diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 8fc144a84..ef167087b 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -308,7 +308,7 @@ static BoehmGCStackAllocator boehmGCStackAllocator; #endif -static SymbolIdx getName(const AttrName & name, EvalState & state, Env & env) +static Symbol getName(const AttrName & name, EvalState & state, Env & env) { if (name.symbol) { return name.symbol; @@ -769,7 +769,7 @@ void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::stri }); } -void EvalState::throwEvalError(const PosIdx p1, const char * s, const SymbolIdx sym, const PosIdx p2) const +void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2) const { // p1 is where the error occurred; p2 is a position mentioned in the message. throw EvalError({ @@ -787,7 +787,7 @@ void EvalState::throwTypeError(const PosIdx pos, const char * s) const } void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, - const SymbolIdx s2) const + const Symbol s2) const { throw TypeError({ .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), @@ -796,7 +796,7 @@ void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambd } void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, - const ExprLambda & fun, const SymbolIdx s2) const + const ExprLambda & fun, const Symbol s2) const { throw TypeError(ErrorInfo { .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 59c9c4873..5990b97b6 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -78,7 +78,7 @@ public: static inline std::string derivationNixPath = "//builtin/derivation.nix"; - const SymbolIdx sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, + const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, sFile, sLine, sColumn, sFunctor, sToString, sRight, sWrong, sStructuredAttrs, sBuilder, sArgs, @@ -87,7 +87,7 @@ public: sRecurseForDerivations, sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath, sPrefix; - SymbolIdx sDerivationNix; + Symbol sDerivationNix; /* If set, force copying files to the Nix store even if they already exist there. */ @@ -269,14 +269,14 @@ public: [[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const; [[gnu::noinline, gnu::noreturn]] - void throwEvalError(const PosIdx p1, const char * s, const SymbolIdx sym, const PosIdx p2) const; + void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2) const; [[gnu::noinline, gnu::noreturn]] void throwTypeError(const PosIdx pos, const char * s) const; [[gnu::noinline, gnu::noreturn]] - void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const SymbolIdx s2) const; + void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2) const; [[gnu::noinline, gnu::noreturn]] void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, - const ExprLambda & fun, const SymbolIdx s2) const; + const ExprLambda & fun, const Symbol s2) const; [[gnu::noinline, gnu::noreturn]] void throwTypeError(const char * s, const Value & v) const; [[gnu::noinline, gnu::noreturn]] @@ -392,7 +392,7 @@ public: inline Value * allocValue(); inline Env & allocEnv(size_t size); - Value * allocAttr(Value & vAttrs, const SymbolIdx & name); + Value * allocAttr(Value & vAttrs, const Symbol & name); Value * allocAttr(Value & vAttrs, std::string_view name); Bindings * allocBindings(size_t capacity); diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 9fee3cb58..c529fdc89 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -24,8 +24,10 @@ static void showString(std::ostream & str, std::string_view s) str << '"'; } -static void showId(std::ostream & str, std::string_view s) +std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol) { + std::string_view s = symbol; + if (s.empty()) str << "\"\""; else if (s == "if") // FIXME: handle other keywords @@ -34,7 +36,7 @@ static void showId(std::ostream & str, std::string_view s) char c = s[0]; if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) { showString(str, s); - return; + return str; } for (auto c : s) if (!((c >= 'a' && c <= 'z') || @@ -42,15 +44,10 @@ static void showId(std::ostream & str, std::string_view s) (c >= '0' && c <= '9') || c == '_' || c == '\'' || c == '-')) { showString(str, s); - return; + return str; } str << s; } -} - -std::ostream & operator << (std::ostream & str, const Symbol & sym) -{ - showId(str, sym.s); return str; } @@ -499,12 +496,12 @@ void ExprPos::bindVars(const EvalState & es, const StaticEnv & env) /* Storing function names. */ -void Expr::setName(SymbolIdx name) +void Expr::setName(Symbol name) { } -void ExprLambda::setName(SymbolIdx name) +void ExprLambda::setName(Symbol name) { this->name = name; body->setName(name); @@ -526,7 +523,7 @@ std::string ExprLambda::showNamePos(const EvalState & state) const size_t SymbolTable::totalSize() const { size_t n = 0; - dump([&] (const Symbol & s) { n += std::string_view(s).size(); }); + dump([&] (const std::string & s) { n += s.size(); }); return n; } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 217c7e74d..cba099f9c 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -126,9 +126,9 @@ struct StaticEnv; /* An attribute path is a sequence of attribute names. */ struct AttrName { - SymbolIdx symbol; + Symbol symbol; Expr * expr; - AttrName(const SymbolIdx & s) : symbol(s) {}; + AttrName(const Symbol & s) : symbol(s) {}; AttrName(Expr * e) : expr(e) {}; }; @@ -146,7 +146,7 @@ struct Expr virtual void bindVars(const EvalState & es, const StaticEnv & env); virtual void eval(EvalState & state, Env & env, Value & v); virtual Value * maybeThunk(EvalState & state, Env & env); - virtual void setName(SymbolIdx name); + virtual void setName(Symbol name); }; #define COMMON_METHODS \ @@ -196,7 +196,7 @@ typedef uint32_t Displacement; struct ExprVar : Expr { PosIdx pos; - SymbolIdx name; + Symbol name; /* Whether the variable comes from an environment (e.g. a rec, let or function argument) or from a "with". */ @@ -211,8 +211,8 @@ struct ExprVar : Expr Level level; Displacement displ; - ExprVar(const SymbolIdx & name) : name(name) { }; - ExprVar(const PosIdx & pos, const SymbolIdx & name) : pos(pos), name(name) { }; + ExprVar(const Symbol & name) : name(name) { }; + ExprVar(const PosIdx & pos, const Symbol & name) : pos(pos), name(name) { }; COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); }; @@ -223,7 +223,7 @@ struct ExprSelect : Expr Expr * e, * def; AttrPath attrPath; ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; - ExprSelect(const PosIdx & pos, Expr * e, const SymbolIdx & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; + ExprSelect(const PosIdx & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; COMMON_METHODS }; @@ -248,7 +248,7 @@ struct ExprAttrs : Expr : inherited(inherited), e(e), pos(pos) { }; AttrDef() { }; }; - typedef std::map AttrDefs; + typedef std::map AttrDefs; AttrDefs attrs; struct DynamicAttrDef { Expr * nameExpr, * valueExpr; @@ -273,7 +273,7 @@ struct ExprList : Expr struct Formal { PosIdx pos; - SymbolIdx name; + Symbol name; Expr * def; }; @@ -283,9 +283,9 @@ struct Formals Formals_ formals; bool ellipsis; - bool has(SymbolIdx arg) const { + bool has(Symbol arg) const { auto it = std::lower_bound(formals.begin(), formals.end(), arg, - [] (const Formal & f, const SymbolIdx & sym) { return f.name < sym; }); + [] (const Formal & f, const Symbol & sym) { return f.name < sym; }); return it != formals.end() && it->name == arg; } @@ -304,11 +304,11 @@ struct Formals struct ExprLambda : Expr { PosIdx pos; - SymbolIdx name; - SymbolIdx arg; + Symbol name; + Symbol arg; Formals * formals; Expr * body; - ExprLambda(PosIdx pos, SymbolIdx arg, Formals * formals, Expr * body) + ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body) : pos(pos), arg(arg), formals(formals), body(body) { }; @@ -316,7 +316,7 @@ struct ExprLambda : Expr : pos(pos), formals(formals), body(body) { } - void setName(SymbolIdx name); + void setName(Symbol name); std::string showNamePos(const EvalState & state) const; inline bool hasFormals() const { return formals != nullptr; } COMMON_METHODS @@ -426,7 +426,7 @@ struct StaticEnv const StaticEnv * up; // Note: these must be in sorted order. - typedef std::vector> Vars; + typedef std::vector> Vars; Vars vars; StaticEnv(bool isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) { @@ -450,7 +450,7 @@ struct StaticEnv vars.erase(it, end); } - Vars::const_iterator find(const SymbolIdx & name) const + Vars::const_iterator find(const Symbol & name) const { Vars::value_type key(name, 0); auto i = std::lower_bound(vars.begin(), vars.end(), key); diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index b73fd1786..be0598b75 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -86,7 +86,7 @@ static void dupAttr(const EvalState & state, const AttrPath & attrPath, const Po }); } -static void dupAttr(const EvalState & state, SymbolIdx attr, const PosIdx pos, const PosIdx prevPos) +static void dupAttr(const EvalState & state, Symbol attr, const PosIdx pos, const PosIdx prevPos) { throw ParseError({ .msg = hintfmt("attribute '%1%' already defined at %2%", state.symbols[attr], state.positions[prevPos]), @@ -157,14 +157,14 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, static Formals * toFormals(ParseData & data, ParserFormals * formals, - PosIdx pos = noPos, SymbolIdx arg = {}) + PosIdx pos = noPos, Symbol arg = {}) { std::sort(formals->formals.begin(), formals->formals.end(), [] (const auto & a, const auto & b) { return std::tie(a.name, a.pos) < std::tie(b.name, b.pos); }); - std::optional> duplicate; + std::optional> duplicate; for (size_t i = 0; i + 1 < formals->formals.size(); i++) { if (formals->formals[i].name != formals->formals[i + 1].name) continue; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 36a67a39d..40e9f7091 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -584,7 +584,7 @@ typedef std::list ValueList; static Bindings::iterator getAttr( EvalState & state, std::string_view funcName, - SymbolIdx attrSym, + Symbol attrSym, Bindings * attrSet, const PosIdx pos) { @@ -2047,7 +2047,7 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value PathSet context; for (auto & attr : *args[0]->attrs) { - auto & n(state.symbols[attr.name]); + auto n = state.symbols[attr.name]; if (n == "path") path = state.coerceToPath(attr.pos, *attr.value, context); else if (attr.name == state.sName) @@ -2314,7 +2314,7 @@ static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value * * args auto attrs = state.buildBindings(args[0]->listSize()); - std::set seen; + std::set seen; for (auto v2 : args[0]->listItems()) { state.forceAttrs(*v2, pos); @@ -2517,7 +2517,7 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg // attribute with the merge function application. this way we need not // use (slightly slower) temporary storage the GC does not know about. - std::map> attrsSeen; + std::map> attrsSeen; state.forceFunction(*args[0], pos); state.forceList(*args[1], pos); diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh index b6ad60f68..63fb25d73 100644 --- a/src/libexpr/symbol-table.hh +++ b/src/libexpr/symbol-table.hh @@ -12,82 +12,94 @@ namespace nix { /* Symbol table used by the parser and evaluator to represent and look up identifiers and attributes efficiently. SymbolTable::create() converts a string into a symbol. Symbols have the property that - they can be compared efficiently (using a pointer equality test), + they can be compared efficiently (using an equality test), because the symbol table stores only one copy of each string. */ -class Symbol +/* This class mainly exists to give us an operator<< for ostreams. We could also + return plain strings from SymbolTable, but then we'd have to wrap every + instance of a symbol that is fmt()ed, which is inconvenient and error-prone. */ +class SymbolStr { friend class SymbolTable; + private: - std::string s; + const std::string * s; + + explicit SymbolStr(const std::string & symbol): s(&symbol) {} public: - Symbol(std::string_view s) : s(s) { } - - // FIXME: remove bool operator == (std::string_view s2) const { - return s == s2; + return *s == s2; } operator const std::string & () const { - return s; + return *s; } operator const std::string_view () const { - return s; + return *s; } - friend std::ostream & operator << (std::ostream & str, const Symbol & sym); + friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol); }; -class SymbolIdx +class Symbol { friend class SymbolTable; private: uint32_t id; - explicit SymbolIdx(uint32_t id): id(id) {} + explicit Symbol(uint32_t id): id(id) {} public: - SymbolIdx() : id(0) {} + Symbol() : id(0) {} explicit operator bool() const { return id > 0; } - bool operator<(const SymbolIdx other) const { return id < other.id; } - bool operator==(const SymbolIdx other) const { return id == other.id; } - bool operator!=(const SymbolIdx other) const { return id != other.id; } + bool operator<(const Symbol other) const { return id < other.id; } + bool operator==(const Symbol other) const { return id == other.id; } + bool operator!=(const Symbol other) const { return id != other.id; } }; class SymbolTable { private: - std::unordered_map> symbols; - ChunkedVector store{16}; + std::unordered_map> symbols; + ChunkedVector store{16}; public: - SymbolIdx create(std::string_view s) + Symbol create(std::string_view s) { // Most symbols are looked up more than once, so we trade off insertion performance // for lookup performance. // TODO: could probably be done more efficiently with transparent Hash and Equals // on the original implementation using unordered_set auto it = symbols.find(s); - if (it != symbols.end()) return SymbolIdx(it->second.second + 1); + if (it != symbols.end()) return Symbol(it->second.second + 1); - const auto & [rawSym, idx] = store.add(s); + const auto & [rawSym, idx] = store.add(std::string(s)); symbols.emplace(rawSym, std::make_pair(&rawSym, idx)); - return SymbolIdx(idx + 1); + return Symbol(idx + 1); } - const Symbol & operator[](SymbolIdx s) const + std::vector resolve(const std::vector & symbols) const + { + std::vector result; + result.reserve(symbols.size()); + for (auto sym : symbols) + result.push_back((*this)[sym]); + return result; + } + + SymbolStr operator[](Symbol s) const { if (s.id == 0 || s.id > store.size()) abort(); - return store[s.id - 1]; + return SymbolStr(store[s.id - 1]); } size_t size() const diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index c46dd4b73..58a8a56a0 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -55,7 +55,7 @@ struct Env; struct Expr; struct ExprLambda; struct PrimOp; -class SymbolIdx; +class Symbol; class PosIdx; struct Pos; class StorePath; @@ -251,11 +251,6 @@ public: void mkStringMove(const char * s, const PathSet & context); - inline void mkString(const Symbol & s) - { - mkString(std::string_view(s).data()); - } - inline void mkPath(const char * s) { clearValue(); @@ -410,12 +405,12 @@ public: #if HAVE_BOEHMGC typedef std::vector > ValueVector; -typedef std::map, traceable_allocator > > ValueMap; -typedef std::map, traceable_allocator > > ValueVectorMap; +typedef std::map, traceable_allocator > > ValueMap; +typedef std::map, traceable_allocator > > ValueVectorMap; #else typedef std::vector ValueVector; -typedef std::map ValueMap; -typedef std::map ValueVectorMap; +typedef std::map ValueMap; +typedef std::map ValueVectorMap; #endif diff --git a/src/nix/app.cc b/src/nix/app.cc index 95ac1cf5c..eec53ad7c 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -66,7 +66,7 @@ UnresolvedApp Installable::toApp(EvalState & state) auto type = cursor->getAttr("type")->getString(); - std::string expected = !attrPath.empty() && attrPath[0] == "apps" ? "app" : "derivation"; + std::string expected = !attrPath.empty() && state.symbols[attrPath[0]] == "apps" ? "app" : "derivation"; if (type != expected) throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected); diff --git a/src/nix/flake.cc b/src/nix/flake.cc index c8d8461e4..2c4a64c85 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -311,7 +311,7 @@ struct CmdFlakeCheck : FlakeCommand return state->positions[p]; }; - auto argHasName = [&] (SymbolIdx arg, std::string_view expected) { + auto argHasName = [&] (Symbol arg, std::string_view expected) { std::string_view name = state->symbols[arg]; return name == expected @@ -986,8 +986,11 @@ struct CmdFlakeShow : FlakeCommand, MixJSON { auto j = nlohmann::json::object(); + auto attrPathS = state->symbols.resolve(attrPath); + Activity act(*logger, lvlInfo, actUnknown, - fmt("evaluating '%s'", concatStringsSep(".", attrPath))); + fmt("evaluating '%s'", concatStringsSep(".", attrPathS))); + try { auto recurse = [&]() { @@ -995,14 +998,15 @@ struct CmdFlakeShow : FlakeCommand, MixJSON logger->cout("%s", headerPrefix); auto attrs = visitor.getAttrs(); for (const auto & [i, attr] : enumerate(attrs)) { + const auto & attrName = state->symbols[attr]; bool last = i + 1 == attrs.size(); - auto visitor2 = visitor.getAttr(attr); + auto visitor2 = visitor.getAttr(attrName); auto attrPath2(attrPath); attrPath2.push_back(attr); auto j2 = visit(*visitor2, attrPath2, - fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attr), + fmt(ANSI_GREEN "%s%s" ANSI_NORMAL ANSI_BOLD "%s" ANSI_NORMAL, nextPrefix, last ? treeLast : treeConn, attrName), nextPrefix + (last ? treeNull : treeLine)); - if (json) j.emplace(attr, std::move(j2)); + if (json) j.emplace(attrName, std::move(j2)); } }; @@ -1022,10 +1026,10 @@ struct CmdFlakeShow : FlakeCommand, MixJSON } else { logger->cout("%s: %s '%s'", headerPrefix, - attrPath.size() == 2 && attrPath[0] == "devShell" ? "development environment" : - attrPath.size() >= 2 && attrPath[0] == "devShells" ? "development environment" : - attrPath.size() == 3 && attrPath[0] == "checks" ? "derivation" : - attrPath.size() >= 1 && attrPath[0] == "hydraJobs" ? "derivation" : + attrPath.size() == 2 && attrPathS[0] == "devShell" ? "development environment" : + attrPath.size() >= 2 && attrPathS[0] == "devShells" ? "development environment" : + attrPath.size() == 3 && attrPathS[0] == "checks" ? "derivation" : + attrPath.size() >= 1 && attrPathS[0] == "hydraJobs" ? "derivation" : "package", name); } @@ -1033,27 +1037,27 @@ struct CmdFlakeShow : FlakeCommand, MixJSON if (attrPath.size() == 0 || (attrPath.size() == 1 && ( - attrPath[0] == "defaultPackage" - || attrPath[0] == "devShell" - || attrPath[0] == "formatter" - || attrPath[0] == "nixosConfigurations" - || attrPath[0] == "nixosModules" - || attrPath[0] == "defaultApp" - || attrPath[0] == "templates" - || attrPath[0] == "overlays")) + attrPathS[0] == "defaultPackage" + || attrPathS[0] == "devShell" + || attrPathS[0] == "formatter" + || attrPathS[0] == "nixosConfigurations" + || attrPathS[0] == "nixosModules" + || attrPathS[0] == "defaultApp" + || attrPathS[0] == "templates" + || attrPathS[0] == "overlays")) || ((attrPath.size() == 1 || attrPath.size() == 2) - && (attrPath[0] == "checks" - || attrPath[0] == "packages" - || attrPath[0] == "devShells" - || attrPath[0] == "apps")) + && (attrPathS[0] == "checks" + || attrPathS[0] == "packages" + || attrPathS[0] == "devShells" + || attrPathS[0] == "apps")) ) { recurse(); } else if ( - (attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell" || attrPath[0] == "formatter")) - || (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages" || attrPath[0] == "devShells")) + (attrPath.size() == 2 && (attrPathS[0] == "defaultPackage" || attrPathS[0] == "devShell" || attrPathS[0] == "formatter")) + || (attrPath.size() == 3 && (attrPathS[0] == "checks" || attrPathS[0] == "packages" || attrPathS[0] == "devShells")) ) { if (visitor.isDerivation()) @@ -1062,14 +1066,14 @@ struct CmdFlakeShow : FlakeCommand, MixJSON throw Error("expected a derivation"); } - else if (attrPath.size() > 0 && attrPath[0] == "hydraJobs") { + else if (attrPath.size() > 0 && attrPathS[0] == "hydraJobs") { if (visitor.isDerivation()) showDerivation(); else recurse(); } - else if (attrPath.size() > 0 && attrPath[0] == "legacyPackages") { + else if (attrPath.size() > 0 && attrPathS[0] == "legacyPackages") { if (attrPath.size() == 1) recurse(); else if (!showLegacy) @@ -1084,8 +1088,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON } else if ( - (attrPath.size() == 2 && attrPath[0] == "defaultApp") || - (attrPath.size() == 3 && attrPath[0] == "apps")) + (attrPath.size() == 2 && attrPathS[0] == "defaultApp") || + (attrPath.size() == 3 && attrPathS[0] == "apps")) { auto aType = visitor.maybeGetAttr("type"); if (!aType || aType->getString() != "app") @@ -1098,8 +1102,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON } else if ( - (attrPath.size() == 1 && attrPath[0] == "defaultTemplate") || - (attrPath.size() == 2 && attrPath[0] == "templates")) + (attrPath.size() == 1 && attrPathS[0] == "defaultTemplate") || + (attrPath.size() == 2 && attrPathS[0] == "templates")) { auto description = visitor.getAttr("description")->getString(); if (json) { @@ -1112,11 +1116,11 @@ struct CmdFlakeShow : FlakeCommand, MixJSON else { auto [type, description] = - (attrPath.size() == 1 && attrPath[0] == "overlay") - || (attrPath.size() == 2 && attrPath[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") : - attrPath.size() == 2 && attrPath[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") : - (attrPath.size() == 1 && attrPath[0] == "nixosModule") - || (attrPath.size() == 2 && attrPath[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") : + (attrPath.size() == 1 && attrPathS[0] == "overlay") + || (attrPath.size() == 2 && attrPathS[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") : + attrPath.size() == 2 && attrPathS[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") : + (attrPath.size() == 1 && attrPathS[0] == "nixosModule") + || (attrPath.size() == 2 && attrPathS[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") : std::make_pair("unknown", "unknown"); if (json) { j.emplace("type", type); @@ -1125,7 +1129,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON } } } catch (EvalError & e) { - if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages")) + if (!(attrPath.size() > 0 && attrPathS[0] == "legacyPackages")) throw; } diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 998ff7328..2967632ed 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -73,7 +73,7 @@ struct NixRepl void initEnv(); void reloadFiles(); void addAttrsToScope(Value & attrs); - void addVarToScope(const SymbolIdx name, Value & v); + void addVarToScope(const Symbol name, Value & v); Expr * parseString(std::string s); void evalString(std::string s, Value & v); @@ -711,7 +711,7 @@ void NixRepl::addAttrsToScope(Value & attrs) } -void NixRepl::addVarToScope(const SymbolIdx name, Value & v) +void NixRepl::addVarToScope(const Symbol name, Value & v) { if (displ >= envSize) throw Error("environment full; cannot add more variables"); diff --git a/src/nix/search.cc b/src/nix/search.cc index 8b1e9ae6c..6febc0a55 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -77,13 +77,15 @@ struct CmdSearch : InstallableCommand, MixJSON visit = [&](eval_cache::AttrCursor & cursor, const std::vector & attrPath, bool initialRecurse) { + auto attrPathS = state->symbols.resolve(attrPath); + Activity act(*logger, lvlInfo, actUnknown, - fmt("evaluating '%s'", concatStringsSep(".", attrPath))); + fmt("evaluating '%s'", concatStringsSep(".", attrPathS))); try { auto recurse = [&]() { for (const auto & attr : cursor.getAttrs()) { - auto cursor2 = cursor.getAttr(attr); + auto cursor2 = cursor.getAttr(state->symbols[attr]); auto attrPath2(attrPath); attrPath2.push_back(attr); visit(*cursor2, attrPath2, false); @@ -97,7 +99,7 @@ struct CmdSearch : InstallableCommand, MixJSON auto aDescription = aMeta ? aMeta->maybeGetAttr("description") : nullptr; auto description = aDescription ? aDescription->getString() : ""; std::replace(description.begin(), description.end(), '\n', ' '); - auto attrPath2 = concatStringsSep(".", attrPath); + auto attrPath2 = concatStringsSep(".", attrPathS); std::vector attrPathMatches; std::vector descriptionMatches; @@ -146,21 +148,21 @@ struct CmdSearch : InstallableCommand, MixJSON else if ( attrPath.size() == 0 - || (attrPath[0] == "legacyPackages" && attrPath.size() <= 2) - || (attrPath[0] == "packages" && attrPath.size() <= 2)) + || (attrPathS[0] == "legacyPackages" && attrPath.size() <= 2) + || (attrPathS[0] == "packages" && attrPath.size() <= 2)) recurse(); else if (initialRecurse) recurse(); - else if (attrPath[0] == "legacyPackages" && attrPath.size() > 2) { + else if (attrPathS[0] == "legacyPackages" && attrPath.size() > 2) { auto attr = cursor.maybeGetAttr("recurseForDerivations"); if (attr && attr->getBool()) recurse(); } } catch (EvalError & e) { - if (!(attrPath.size() > 0 && attrPath[0] == "legacyPackages")) + if (!(attrPath.size() > 0 && attrPathS[0] == "legacyPackages")) throw; } }; From fab731a9d4622b1ecd7dea01895d9fbaf83301ea Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 26 Apr 2022 13:23:32 +0200 Subject: [PATCH 38/79] Don't pass Symbol by reference Since Symbol is just an integer, passing it by const reference is never advantageous. --- src/libexpr/attr-set.cc | 4 ++-- src/libexpr/attr-set.hh | 6 +++--- src/libexpr/eval.hh | 2 +- src/libexpr/nixexpr.hh | 13 +++++++------ 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/libexpr/attr-set.cc b/src/libexpr/attr-set.cc index 61996eae4..877116f1f 100644 --- a/src/libexpr/attr-set.cc +++ b/src/libexpr/attr-set.cc @@ -26,7 +26,7 @@ Bindings * EvalState::allocBindings(size_t capacity) /* Create a new attribute named 'name' on an existing attribute set stored in 'vAttrs' and return the newly allocated Value which is associated with this attribute. */ -Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name) +Value * EvalState::allocAttr(Value & vAttrs, Symbol name) { Value * v = allocValue(); vAttrs.attrs->push_back(Attr(name, v)); @@ -40,7 +40,7 @@ Value * EvalState::allocAttr(Value & vAttrs, std::string_view name) } -Value & BindingsBuilder::alloc(const Symbol & name, PosIdx pos) +Value & BindingsBuilder::alloc(Symbol name, PosIdx pos) { auto value = state.allocValue(); bindings->push_back(Attr(name, value, pos)); diff --git a/src/libexpr/attr-set.hh b/src/libexpr/attr-set.hh index 31251efc4..dcc73b506 100644 --- a/src/libexpr/attr-set.hh +++ b/src/libexpr/attr-set.hh @@ -66,7 +66,7 @@ public: attrs[size_++] = attr; } - iterator find(const Symbol & name) + iterator find(Symbol name) { Attr key(name, 0); iterator i = std::lower_bound(begin(), end(), key); @@ -74,7 +74,7 @@ public: return end(); } - Attr * get(const Symbol & name) + Attr * get(Symbol name) { Attr key(name, 0); iterator i = std::lower_bound(begin(), end(), key); @@ -143,7 +143,7 @@ public: bindings->push_back(attr); } - Value & alloc(const Symbol & name, PosIdx pos = noPos); + Value & alloc(Symbol name, PosIdx pos = noPos); Value & alloc(std::string_view name, PosIdx pos = noPos); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 5990b97b6..6c418f2ae 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -392,7 +392,7 @@ public: inline Value * allocValue(); inline Env & allocEnv(size_t size); - Value * allocAttr(Value & vAttrs, const Symbol & name); + Value * allocAttr(Value & vAttrs, Symbol name); Value * allocAttr(Value & vAttrs, std::string_view name); Bindings * allocBindings(size_t capacity); diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index cba099f9c..5df69e000 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -128,7 +128,7 @@ struct AttrName { Symbol symbol; Expr * expr; - AttrName(const Symbol & s) : symbol(s) {}; + AttrName(Symbol s) : symbol(s) {}; AttrName(Expr * e) : expr(e) {}; }; @@ -211,8 +211,8 @@ struct ExprVar : Expr Level level; Displacement displ; - ExprVar(const Symbol & name) : name(name) { }; - ExprVar(const PosIdx & pos, const Symbol & name) : pos(pos), name(name) { }; + ExprVar(Symbol name) : name(name) { }; + ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { }; COMMON_METHODS Value * maybeThunk(EvalState & state, Env & env); }; @@ -223,7 +223,7 @@ struct ExprSelect : Expr Expr * e, * def; AttrPath attrPath; ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; - ExprSelect(const PosIdx & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; + ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; COMMON_METHODS }; @@ -283,7 +283,8 @@ struct Formals Formals_ formals; bool ellipsis; - bool has(Symbol arg) const { + bool has(Symbol arg) const + { auto it = std::lower_bound(formals.begin(), formals.end(), arg, [] (const Formal & f, const Symbol & sym) { return f.name < sym; }); return it != formals.end() && it->name == arg; @@ -450,7 +451,7 @@ struct StaticEnv vars.erase(it, end); } - Vars::const_iterator find(const Symbol & name) const + Vars::const_iterator find(Symbol name) const { Vars::value_type key(name, 0); auto i = std::lower_bound(vars.begin(), vars.end(), key); From 474695975dde60f582ca0b2fb72c17f664e22876 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 26 Apr 2022 14:01:21 +0200 Subject: [PATCH 39/79] EvalCache: Revert to using symbols in getAttr() --- src/libcmd/installables.cc | 2 +- src/libexpr/eval-cache.cc | 38 +++++++++++++++++++++++++------------- src/libexpr/eval-cache.hh | 8 ++++++-- src/nix/app.cc | 6 +++--- src/nix/flake.cc | 2 +- src/nix/search.cc | 2 +- 6 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 693b5e2a2..e3210d18d 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -600,7 +600,7 @@ std::tuple InstallableF auto drvInfo = DerivationInfo { std::move(drvPath), - attr->getAttr("outputName")->getString() + attr->getAttr(state->sOutputName)->getString() }; return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)}; diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 00e80ae3b..e90c9bff5 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -420,8 +420,10 @@ Suggestions AttrCursor::getSuggestionsForAttr(Symbol name) return Suggestions::bestMatches(strAttrNames, root->state.symbols[name]); } -std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool forceErrors) +std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErrors) { + auto nameS = root->state.symbols[name]; + if (root->db) { if (!cachedValue) cachedValue = root->db->getAttr(getKey(), root->state.symbols); @@ -429,11 +431,11 @@ std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool if (cachedValue) { if (auto attrs = std::get_if>(&cachedValue->second)) { for (auto & attr : *attrs) - if (root->state.symbols[attr] == name) + if (attr == name) return std::make_shared(root, std::make_pair(shared_from_this(), attr)); return nullptr; } else if (std::get_if(&cachedValue->second)) { - auto attr = root->db->getAttr({cachedValue->first, std::string(name)}, root->state.symbols); + auto attr = root->db->getAttr({cachedValue->first, nameS}, root->state.symbols); if (attr) { if (std::get_if(&attr->second)) return nullptr; @@ -441,10 +443,10 @@ std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool if (forceErrors) debug("reevaluating failed cached attribute '%s'"); else - throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(root->state.symbols.create(name))); + throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(name)); } else return std::make_shared(root, - std::make_pair(shared_from_this(), root->state.symbols.create(name)), nullptr, std::move(attr)); + std::make_pair(shared_from_this(), name), nullptr, std::move(attr)); } // Incomplete attrset, so need to fall thru and // evaluate to see whether 'name' exists @@ -465,13 +467,13 @@ std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool root->db->setPlaceholder({cachedValue->first, root->state.symbols[attr.name]}); } - auto attr = v.attrs->get(root->state.symbols.create(name)); + auto attr = v.attrs->get(name); if (!attr) { if (root->db) { if (!cachedValue) cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()}; - root->db->setMissing({cachedValue->first, std::string(name)}); + root->db->setMissing({cachedValue->first, nameS}); } return nullptr; } @@ -480,26 +482,36 @@ std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name, bool if (root->db) { if (!cachedValue) cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()}; - cachedValue2 = {root->db->setPlaceholder({cachedValue->first, std::string(name)}), placeholder_t()}; + cachedValue2 = {root->db->setPlaceholder({cachedValue->first, nameS}), placeholder_t()}; } return make_ref( - root, std::make_pair(shared_from_this(), root->state.symbols.create(name)), attr->value, std::move(cachedValue2)); + root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2)); } -ref AttrCursor::getAttr(std::string_view name, bool forceErrors) +std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name) +{ + return maybeGetAttr(root->state.symbols.create(name)); +} + +ref AttrCursor::getAttr(Symbol name, bool forceErrors) { auto p = maybeGetAttr(name, forceErrors); if (!p) - throw Error("attribute '%s' does not exist", getAttrPathStr(root->state.symbols.create(name))); + throw Error("attribute '%s' does not exist", getAttrPathStr(name)); return ref(p); } +ref AttrCursor::getAttr(std::string_view name) +{ + return getAttr(root->state.symbols.create(name)); +} + OrSuggestions> AttrCursor::findAlongAttrPath(const std::vector & attrPath, bool force) { auto res = shared_from_this(); for (auto & attr : attrPath) { - auto child = res->maybeGetAttr(root->state.symbols[attr], force); + auto child = res->maybeGetAttr(attr, force); if (!child) { auto suggestions = res->getSuggestionsForAttr(attr); return OrSuggestions>::failed(suggestions); @@ -627,7 +639,7 @@ bool AttrCursor::isDerivation() StorePath AttrCursor::forceDerivation() { - auto aDrvPath = getAttr("drvPath", true); + auto aDrvPath = getAttr(root->state.sDrvPath, true); auto drvPath = root->state.store->parseStorePath(aDrvPath->getString()); if (!root->state.store->isValidPath(drvPath) && !settings.readOnlyMode) { /* The eval cache contains 'drvPath', but the actual path has diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh index 288ecd7e3..0ba2e4ed0 100644 --- a/src/libexpr/eval-cache.hh +++ b/src/libexpr/eval-cache.hh @@ -96,9 +96,13 @@ public: Suggestions getSuggestionsForAttr(Symbol name); - std::shared_ptr maybeGetAttr(std::string_view name, bool forceErrors = false); + std::shared_ptr maybeGetAttr(Symbol name, bool forceErrors = false); - ref getAttr(std::string_view name, bool forceErrors = false); + std::shared_ptr maybeGetAttr(std::string_view name); + + ref getAttr(Symbol name, bool forceErrors = false); + + ref getAttr(std::string_view name); /* Get an attribute along a chain of attrsets. Note that this does not auto-call functors or functions. */ diff --git a/src/nix/app.cc b/src/nix/app.cc index eec53ad7c..cce84d026 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -85,9 +85,9 @@ UnresolvedApp Installable::toApp(EvalState & state) else if (type == "derivation") { auto drvPath = cursor->forceDerivation(); - auto outPath = cursor->getAttr("outPath")->getString(); - auto outputName = cursor->getAttr("outputName")->getString(); - auto name = cursor->getAttr("name")->getString(); + auto outPath = cursor->getAttr(state.sOutPath)->getString(); + auto outputName = cursor->getAttr(state.sOutputName)->getString(); + auto name = cursor->getAttr(state.sName)->getString(); auto aPname = cursor->maybeGetAttr("pname"); auto aMeta = cursor->maybeGetAttr("meta"); auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr; diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 2c4a64c85..040c1c7af 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -1012,7 +1012,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON auto showDerivation = [&]() { - auto name = visitor.getAttr("name")->getString(); + auto name = visitor.getAttr(state->sName)->getString(); if (json) { std::optional description; if (auto aMeta = visitor.maybeGetAttr("meta")) { diff --git a/src/nix/search.cc b/src/nix/search.cc index 6febc0a55..76451f810 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -156,7 +156,7 @@ struct CmdSearch : InstallableCommand, MixJSON recurse(); else if (attrPathS[0] == "legacyPackages" && attrPath.size() > 2) { - auto attr = cursor.maybeGetAttr("recurseForDerivations"); + auto attr = cursor.maybeGetAttr(state->sRecurseForDerivations); if (attr && attr->getBool()) recurse(); } From b12c33510cb09f7d8300d7f3c762a84b8688780f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 26 Apr 2022 14:16:20 +0200 Subject: [PATCH 40/79] EvalCache AttrKey: Use Symbol instead of std::string --- src/libexpr/eval-cache.cc | 69 +++++++++++++++++++------------------ src/libexpr/eval-cache.hh | 2 +- src/libexpr/symbol-table.hh | 2 ++ 3 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index e90c9bff5..6fb4bf266 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -35,9 +35,15 @@ struct AttrDb std::unique_ptr> _state; - AttrDb(const Store & cfg, const Hash & fingerprint) + SymbolTable & symbols; + + AttrDb( + const Store & cfg, + const Hash & fingerprint, + SymbolTable & symbols) : cfg(cfg) , _state(std::make_unique>()) + , symbols(symbols) { auto state(_state->lock()); @@ -92,7 +98,6 @@ struct AttrDb AttrId setAttrs( AttrKey key, - const SymbolTable & symbols, const std::vector & attrs) { return doSQLite([&]() @@ -101,7 +106,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::FullAttrs) (0, false).exec(); @@ -136,14 +141,14 @@ struct AttrDb } state->insertAttributeWithContext.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::String) (s) (ctx).exec(); } else { state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::String) (s).exec(); } @@ -162,7 +167,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Bool) (b ? 1 : 0).exec(); @@ -178,7 +183,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Placeholder) (0, false).exec(); @@ -194,7 +199,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Missing) (0, false).exec(); @@ -210,7 +215,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Misc) (0, false).exec(); @@ -226,7 +231,7 @@ struct AttrDb state->insertAttribute.use() (key.first) - (key.second) + (symbols[key.second]) (AttrType::Failed) (0, false).exec(); @@ -234,13 +239,11 @@ struct AttrDb }); } - std::optional> getAttr( - AttrKey key, - SymbolTable & symbols) + std::optional> getAttr(AttrKey key) { auto state(_state->lock()); - auto queryAttribute(state->queryAttribute.use()(key.first)(key.second)); + auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second])); if (!queryAttribute.next()) return {}; auto rowId = (AttrType) queryAttribute.getInt(0); @@ -278,10 +281,13 @@ struct AttrDb } }; -static std::shared_ptr makeAttrDb(const Store & cfg, const Hash & fingerprint) +static std::shared_ptr makeAttrDb( + const Store & cfg, + const Hash & fingerprint, + SymbolTable & symbols) { try { - return std::make_shared(cfg, fingerprint); + return std::make_shared(cfg, fingerprint, symbols); } catch (SQLiteError &) { ignoreException(); return nullptr; @@ -292,7 +298,7 @@ EvalCache::EvalCache( std::optional> useCache, EvalState & state, RootLoader rootLoader) - : db(useCache ? makeAttrDb(*state.store, *useCache) : nullptr) + : db(useCache ? makeAttrDb(*state.store, *useCache, state.symbols) : nullptr) , state(state) , rootLoader(rootLoader) { @@ -326,13 +332,12 @@ AttrCursor::AttrCursor( AttrKey AttrCursor::getKey() { if (!parent) - return {0, {""}}; + return {0, root->state.sEpsilon}; if (!parent->first->cachedValue) { - parent->first->cachedValue = root->db->getAttr( - parent->first->getKey(), root->state.symbols); + parent->first->cachedValue = root->db->getAttr(parent->first->getKey()); assert(parent->first->cachedValue); } - return {parent->first->cachedValue->first, root->state.symbols[parent->second]}; + return {parent->first->cachedValue->first, parent->second}; } Value & AttrCursor::getValue() @@ -422,11 +427,9 @@ Suggestions AttrCursor::getSuggestionsForAttr(Symbol name) std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErrors) { - auto nameS = root->state.symbols[name]; - if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue) { if (auto attrs = std::get_if>(&cachedValue->second)) { @@ -435,7 +438,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErro return std::make_shared(root, std::make_pair(shared_from_this(), attr)); return nullptr; } else if (std::get_if(&cachedValue->second)) { - auto attr = root->db->getAttr({cachedValue->first, nameS}, root->state.symbols); + auto attr = root->db->getAttr({cachedValue->first, name}); if (attr) { if (std::get_if(&attr->second)) return nullptr; @@ -464,7 +467,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErro for (auto & attr : *v.attrs) { if (root->db) - root->db->setPlaceholder({cachedValue->first, root->state.symbols[attr.name]}); + root->db->setPlaceholder({cachedValue->first, attr.name}); } auto attr = v.attrs->get(name); @@ -473,7 +476,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErro if (root->db) { if (!cachedValue) cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()}; - root->db->setMissing({cachedValue->first, nameS}); + root->db->setMissing({cachedValue->first, name}); } return nullptr; } @@ -482,7 +485,7 @@ std::shared_ptr AttrCursor::maybeGetAttr(Symbol name, bool forceErro if (root->db) { if (!cachedValue) cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()}; - cachedValue2 = {root->db->setPlaceholder({cachedValue->first, nameS}), placeholder_t()}; + cachedValue2 = {root->db->setPlaceholder({cachedValue->first, name}), placeholder_t()}; } return make_ref( @@ -525,7 +528,7 @@ std::string AttrCursor::getString() { if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if(&cachedValue->second)) { if (auto s = std::get_if(&cachedValue->second)) { debug("using cached string attribute '%s'", getAttrPathStr()); @@ -547,7 +550,7 @@ string_t AttrCursor::getStringWithContext() { if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if(&cachedValue->second)) { if (auto s = std::get_if(&cachedValue->second)) { bool valid = true; @@ -580,7 +583,7 @@ bool AttrCursor::getBool() { if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if(&cachedValue->second)) { if (auto b = std::get_if(&cachedValue->second)) { debug("using cached Boolean attribute '%s'", getAttrPathStr()); @@ -602,7 +605,7 @@ std::vector AttrCursor::getAttrs() { if (root->db) { if (!cachedValue) - cachedValue = root->db->getAttr(getKey(), root->state.symbols); + cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if(&cachedValue->second)) { if (auto attrs = std::get_if>(&cachedValue->second)) { debug("using cached attrset attribute '%s'", getAttrPathStr()); @@ -626,7 +629,7 @@ std::vector AttrCursor::getAttrs() }); if (root->db) - cachedValue = {root->db->setAttrs(getKey(), root->state.symbols, attrs), attrs}; + cachedValue = {root->db->setAttrs(getKey(), attrs), attrs}; return attrs; } diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh index 0ba2e4ed0..b0709ebc2 100644 --- a/src/libexpr/eval-cache.hh +++ b/src/libexpr/eval-cache.hh @@ -51,7 +51,7 @@ struct missing_t {}; struct misc_t {}; struct failed_t {}; typedef uint64_t AttrId; -typedef std::pair AttrKey; +typedef std::pair AttrKey; typedef std::pair string_t; typedef std::variant< diff --git a/src/libexpr/symbol-table.hh b/src/libexpr/symbol-table.hh index 63fb25d73..288c15602 100644 --- a/src/libexpr/symbol-table.hh +++ b/src/libexpr/symbol-table.hh @@ -72,12 +72,14 @@ private: ChunkedVector store{16}; public: + Symbol create(std::string_view s) { // Most symbols are looked up more than once, so we trade off insertion performance // for lookup performance. // TODO: could probably be done more efficiently with transparent Hash and Equals // on the original implementation using unordered_set + // FIXME: make this thread-safe. auto it = symbols.find(s); if (it != symbols.end()) return Symbol(it->second.second + 1); From 1ddabe1a0120787ff5bbdba5383222a6eb59c219 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 Apr 2022 16:39:47 +0200 Subject: [PATCH 41/79] nix: Respect meta.outputsToInstall, and use all outputs by default 'nix profile install' will now install all outputs listed in the package's meta.outputsToInstall attribute, or all outputs if that attribute doesn't exist. This makes it behave consistently with nix-env. Fixes #6385. Furthermore, for consistency, all other 'nix' commands do this as well. E.g. 'nix build' will build and symlink the outputs in meta.outputsToInstall, defaulting to all outputs. Previously, it only built/symlinked the first output. Note that this means that selecting a specific output using attrpath selection (e.g. 'nix build nixpkgs#libxml2.dev') no longer works. A subsequent PR will add a way to specify the desired outputs explicitly. --- src/libcmd/installables.cc | 33 ++++++++++++++++----- src/libcmd/installables.hh | 2 +- src/libexpr/eval-cache.cc | 54 +++++++++++++++++++++++++++++++++- src/libexpr/eval-cache.hh | 6 +++- src/nix/app.cc | 2 +- src/nix/flake.cc | 4 +-- src/nix/search.cc | 6 ++-- tests/build.sh | 11 ++----- tests/ca/content-addressed.nix | 2 +- tests/ca/substitute.sh | 3 +- 10 files changed, 96 insertions(+), 27 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index e3210d18d..e2ee47dea 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -440,10 +440,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); } @@ -497,7 +495,13 @@ std::vector InstallableAttrPath::toDerivations auto drvPath = drvInfo.queryDrvPath(); if (!drvPath) throw Error("'%s' is not a derivation", what()); - res.push_back({ *drvPath, drvInfo.queryOutputName() }); + std::set outputsToInstall; + for (auto & output : drvInfo.queryOutputs(false, true)) + outputsToInstall.insert(output.first); + res.push_back(DerivationInfo { + .drvPath = *drvPath, + .outputsToInstall = std::move(outputsToInstall) + }); } return res; @@ -598,9 +602,24 @@ std::tuple InstallableF auto drvPath = attr->forceDerivation(); + std::set outputsToInstall; + + if (auto aMeta = attr->maybeGetAttr(state->sMeta)) + if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall")) + for (auto & s : aOutputsToInstall->getListOfStrings()) + outputsToInstall.insert(s); + + if (outputsToInstall.empty()) + if (auto aOutputs = attr->maybeGetAttr(state->sOutputs)) + for (auto & s : aOutputs->getListOfStrings()) + outputsToInstall.insert(s); + + if (outputsToInstall.empty()) + outputsToInstall.insert("out"); + auto drvInfo = DerivationInfo { - std::move(drvPath), - attr->getAttr(state->sOutputName)->getString() + .drvPath = std::move(drvPath), + .outputsToInstall = std::move(outputsToInstall), }; return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)}; diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index de8b08525..3c2c33549 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -141,7 +141,7 @@ struct InstallableValue : Installable struct DerivationInfo { StorePath drvPath; - std::string outputName; + std::set outputsToInstall; }; virtual std::vector toDerivations() = 0; diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 6fb4bf266..3a92f2815 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -175,6 +175,24 @@ struct AttrDb }); } + AttrId setListOfStrings( + AttrKey key, + const std::vector & l) + { + return doSQLite([&]() + { + auto state(_state->lock()); + + state->insertAttribute.use() + (key.first) + (symbols[key.second]) + (AttrType::ListOfStrings) + (concatStringsSep("\t", l)).exec(); + + return state->db.getLastInsertedRowId(); + }); + } + AttrId setPlaceholder(AttrKey key) { return doSQLite([&]() @@ -269,6 +287,8 @@ struct AttrDb } case AttrType::Bool: return {{rowId, queryAttribute.getInt(2) != 0}}; + case AttrType::ListOfStrings: + return {{rowId, tokenizeString>(queryAttribute.getStr(2), "\t")}}; case AttrType::Missing: return {{rowId, missing_t()}}; case AttrType::Misc: @@ -385,7 +405,7 @@ std::string AttrCursor::getAttrPathStr(Symbol name) const Value & AttrCursor::forceValue() { - debug("evaluating uncached attribute %s", getAttrPathStr()); + debug("evaluating uncached attribute '%s'", getAttrPathStr()); auto & v = getValue(); @@ -601,6 +621,38 @@ bool AttrCursor::getBool() return v.boolean; } +std::vector AttrCursor::getListOfStrings() +{ + if (root->db) { + if (!cachedValue) + cachedValue = root->db->getAttr(getKey()); + if (cachedValue && !std::get_if(&cachedValue->second)) { + if (auto l = std::get_if>(&cachedValue->second)) { + debug("using cached list of strings attribute '%s'", getAttrPathStr()); + return *l; + } else + throw TypeError("'%s' is not a list of strings", getAttrPathStr()); + } + } + + debug("evaluating uncached attribute '%s'", getAttrPathStr()); + + auto & v = getValue(); + root->state.forceValue(v, noPos); + + if (v.type() != nList) + throw TypeError("'%s' is not a list", getAttrPathStr()); + + std::vector res; + + for (auto & elem : v.listItems()) + res.push_back(std::string(root->state.forceStringNoCtx(*elem))); + + cachedValue = {root->db->setListOfStrings(getKey(), res), res}; + + return res; +} + std::vector AttrCursor::getAttrs() { if (root->db) { diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh index b0709ebc2..636e293ad 100644 --- a/src/libexpr/eval-cache.hh +++ b/src/libexpr/eval-cache.hh @@ -44,6 +44,7 @@ enum AttrType { Misc = 4, Failed = 5, Bool = 6, + ListOfStrings = 7, }; struct placeholder_t {}; @@ -61,7 +62,8 @@ typedef std::variant< missing_t, misc_t, failed_t, - bool + bool, + std::vector > AttrValue; class AttrCursor : public std::enable_shared_from_this @@ -114,6 +116,8 @@ public: bool getBool(); + std::vector getListOfStrings(); + std::vector getAttrs(); bool isDerivation(); diff --git a/src/nix/app.cc b/src/nix/app.cc index cce84d026..821964f86 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -89,7 +89,7 @@ UnresolvedApp Installable::toApp(EvalState & state) auto outputName = cursor->getAttr(state.sOutputName)->getString(); auto name = cursor->getAttr(state.sName)->getString(); auto aPname = cursor->maybeGetAttr("pname"); - auto aMeta = cursor->maybeGetAttr("meta"); + auto aMeta = cursor->maybeGetAttr(state.sMeta); auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr; auto mainProgram = aMainProgram diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 040c1c7af..6a34ca67b 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -1015,8 +1015,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON auto name = visitor.getAttr(state->sName)->getString(); if (json) { std::optional description; - if (auto aMeta = visitor.maybeGetAttr("meta")) { - if (auto aDescription = aMeta->maybeGetAttr("description")) + if (auto aMeta = visitor.maybeGetAttr(state->sMeta)) { + if (auto aDescription = aMeta->maybeGetAttr(state->sDescription)) description = aDescription->getString(); } j.emplace("type", "derivation"); diff --git a/src/nix/search.cc b/src/nix/search.cc index 76451f810..87dc1c0de 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -93,10 +93,10 @@ struct CmdSearch : InstallableCommand, MixJSON }; if (cursor.isDerivation()) { - DrvName name(cursor.getAttr("name")->getString()); + DrvName name(cursor.getAttr(state->sName)->getString()); - auto aMeta = cursor.maybeGetAttr("meta"); - auto aDescription = aMeta ? aMeta->maybeGetAttr("description") : nullptr; + auto aMeta = cursor.maybeGetAttr(state->sMeta); + auto aDescription = aMeta ? aMeta->maybeGetAttr(state->sDescription) : nullptr; auto description = aDescription ? aDescription->getString() : ""; std::replace(description.begin(), description.end(), '\n', ' '); auto attrPath2 = concatStringsSep(".", attrPathS); diff --git a/tests/build.sh b/tests/build.sh index 13a0f42be..339155991 100644 --- a/tests/build.sh +++ b/tests/build.sh @@ -2,15 +2,8 @@ source common.sh clearStore -# Make sure that 'nix build' only returns the outputs we asked for. -nix build -f multiple-outputs.nix --json a --no-link | jq --exit-status ' - (.[0] | - (.drvPath | match(".*multiple-outputs-a.drv")) and - (.outputs | keys | length == 1) and - (.outputs.first | match(".*multiple-outputs-a-first"))) -' - -nix build -f multiple-outputs.nix --json a.all b.all --no-link | jq --exit-status ' +# Make sure that 'nix build' returns all outputs by default. +nix build -f multiple-outputs.nix --json a b --no-link | jq --exit-status ' (.[0] | (.drvPath | match(".*multiple-outputs-a.drv")) and (.outputs | keys | length == 2) and diff --git a/tests/ca/content-addressed.nix b/tests/ca/content-addressed.nix index 1be3eeb6e..31c144ae0 100644 --- a/tests/ca/content-addressed.nix +++ b/tests/ca/content-addressed.nix @@ -23,7 +23,7 @@ rec { }; rootCA = mkCADerivation { name = "rootCA"; - outputs = [ "out" "dev" "foo"]; + outputs = [ "out" "dev" "foo" ]; buildCommand = '' echo "building a CA derivation" echo "The seed is ${toString seed}" diff --git a/tests/ca/substitute.sh b/tests/ca/substitute.sh index 3d9001bb8..819f3fd85 100644 --- a/tests/ca/substitute.sh +++ b/tests/ca/substitute.sh @@ -25,7 +25,8 @@ buildDrvs --substitute --substituters $REMOTE_STORE --no-require-sigs -j0 transi # Check that the thing we’ve just substituted has its realisation stored nix realisation info --file ./content-addressed.nix transitivelyDependentCA # Check that its dependencies have it too -nix realisation info --file ./content-addressed.nix dependentCA rootCA +nix realisation info --file ./content-addressed.nix dependentCA +# nix realisation info --file ./content-addressed.nix rootCA --outputs out # Same thing, but # 1. With non-ca derivations From 6e0a2b971bfe75a581a91e5092553f2ecc9c0b6f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 Apr 2022 16:50:24 +0200 Subject: [PATCH 42/79] Add a test for outputsToInstall --- tests/nix-profile.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/nix-profile.sh b/tests/nix-profile.sh index fad62b993..814192252 100644 --- a/tests/nix-profile.sh +++ b/tests/nix-profile.sh @@ -17,6 +17,7 @@ cat > $flake1Dir/flake.nix < $flake1Dir/flake.nix < 1.0" nix profile diff-closures | grep 'env-manifest.nix: ε → ∅' @@ -55,7 +61,7 @@ printf NixOS > $flake1Dir/who printf 2.0 > $flake1Dir/version nix profile upgrade 1 [[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello NixOS" ]] -nix profile history | grep "packages.$system.default: 1.0 -> 2.0" +nix profile history | grep "packages.$system.default: 1.0, 1.0-man -> 2.0, 2.0-man" # Test 'history', 'diff-closures'. nix profile diff-closures @@ -86,7 +92,7 @@ nix profile wipe-history printf true > $flake1Dir/ca.nix printf 3.0 > $flake1Dir/version nix profile upgrade 0 -nix profile history | grep "packages.$system.default: 1.0 -> 3.0" +nix profile history | grep "packages.$system.default: 1.0, 1.0-man -> 3.0, 3.0-man" # Test new install of CA package. nix profile remove 0 From 13d8400ac511dd36dcfd74c35737d093cf52bba3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 Apr 2022 16:51:12 +0200 Subject: [PATCH 43/79] Remove obsolete FIXME --- src/nix/profile.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nix/profile.cc b/src/nix/profile.cc index b151e48d6..52c918016 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -67,7 +67,6 @@ struct ProfileElement ref store, const BuiltPaths & builtPaths) { - // FIXME: respect meta.outputsToInstall storePaths.clear(); for (auto & buildable : builtPaths) { std::visit(overloaded { From 717298c7491af6ee0e2544258bc7267c8328be58 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 26 Apr 2022 17:17:51 +0200 Subject: [PATCH 44/79] Bump eval cache schema version --- src/libexpr/eval-cache.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 3a92f2815..a1cb162ee 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -47,7 +47,7 @@ struct AttrDb { auto state(_state->lock()); - Path cacheDir = getCacheDir() + "/nix/eval-cache-v2"; + Path cacheDir = getCacheDir() + "/nix/eval-cache-v3"; createDirs(cacheDir); Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite"; From 68d51ae01278f314283523120713e745ef184011 Mon Sep 17 00:00:00 2001 From: midchildan Date: Wed, 27 Apr 2022 01:56:10 +0900 Subject: [PATCH 45/79] refactor: don't hardcode nix subcommands in run-help-nix --- misc/zsh/run-help-nix | 87 ++++++++----------------------------------- 1 file changed, 16 insertions(+), 71 deletions(-) diff --git a/misc/zsh/run-help-nix b/misc/zsh/run-help-nix index 45588470b..f91a304eb 100644 --- a/misc/zsh/run-help-nix +++ b/misc/zsh/run-help-nix @@ -18,80 +18,25 @@ emulate -L zsh # # bindkey '^[h' run-help -if (( $# == 0 )); then - man nix - return -fi - while [[ "$#" != 0 && "$1" == -* ]]; do shift done -case "$1" in - flake) - case "$2" in - archive|check|clone|info|init|lock|metadata|new|prefetch|show|update) - man "nix3-$1-$2" ;; - *) - man "nix3-$1" ;; - esac ;; - hash) - case "$2" in - file|path|to-base16|to-base32|to-base64|to-sri) - man "nix3-$1-$2" ;; - *) - man "nix3-$1" ;; - esac ;; - key) - case "$2" in - convert-secret-to-public|generate-secret) - man "nix3-$1-$2" ;; - *) - man "nix3-$1" ;; - esac ;; - nar) - case "$2" in - cat|dump-path|ls) - man "nix3-$1-$2" ;; - *) - man "nix3-$1" ;; - esac ;; - profile) - case "$2" in - diff-closures|history|install|list|remove|rollback|upgrade|wipe-history) - man "nix3-$1-$2" ;; - *) - man "nix3-$1" ;; - esac ;; - realisation) - case "$2" in - info) - man "nix3-$1-$2" ;; - *) - man "nix3-$1" ;; - esac ;; - registry) - case "$2" in - add|list|pin|remove) - man "nix3-$1-$2" ;; - *) - man "nix3-$1" ;; - esac ;; - store) - case "$2" in - add-file|add-path|cat|copy-sigs|delete|diff-closures|dump-path|gc|ls) - ;& # fallthrough - make-content-addressed|optimise|ping|prefetch-file|repair|sign|verify) - man "nix3-$1-$2" ;; - *) - man "nix3-$1" ;; - esac ;; - *) - if man -w "nix3-$1" >/dev/null 2>&1; then - man "nix3-$1" - else - man nix - fi ;; -esac +local -a subcommands; subcommands=( nix3 ) + +local arg +for arg in "$@"; do + if man -w "${(j:-:)subcommands}-$arg" >/dev/null 2>&1; then + subcommands+="$arg" + else + break + fi +done + +if (( $#subcommands > 1 )); then + man "${(j:-:)subcommands}" +else + man nix +fi return $? From 4a9623b129730d3af3e99773a9f2a53395435311 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 28 Apr 2022 13:36:01 +0200 Subject: [PATCH 46/79] Fix passing $OUT_PATHS to the post-build hook Fixes #6446. --- src/libstore/build/derivation-goal.cc | 5 ++--- tests/post-hook.sh | 6 +++--- tests/push-to-store.sh | 4 ++++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 53f212c1d..d095a0f02 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -786,8 +786,7 @@ void runPostBuildHook( Store & store, Logger & logger, const StorePath & drvPath, - StorePathSet outputPaths -) + const StorePathSet & outputPaths) { auto hook = settings.postBuildHook; if (hook == "") @@ -906,7 +905,7 @@ void DerivationGoal::buildDone() auto builtOutputs = registerOutputs(); StorePathSet outputPaths; - for (auto & [_, output] : buildResult.builtOutputs) + for (auto & [_, output] : builtOutputs) outputPaths.insert(output.outPath); runPostBuildHook( worker.store, diff --git a/tests/post-hook.sh b/tests/post-hook.sh index 049e40749..4eff5f511 100644 --- a/tests/post-hook.sh +++ b/tests/post-hook.sh @@ -9,12 +9,12 @@ echo 'require-sigs = false' >> $NIX_CONF_DIR/nix.conf restartDaemon -# Build the dependencies and push them to the remote store +# Build the dependencies and push them to the remote store. nix-build -o $TEST_ROOT/result dependencies.nix --post-build-hook $PWD/push-to-store.sh clearStore -# Ensure that we the remote store contains both the runtime and buildtime -# closure of what we've just built +# Ensure that the remote store contains both the runtime and build-time +# closure of what we've just built. nix copy --from "$REMOTE_STORE" --no-require-sigs -f dependencies.nix nix copy --from "$REMOTE_STORE" --no-require-sigs -f dependencies.nix input1_drv diff --git a/tests/push-to-store.sh b/tests/push-to-store.sh index 25352c751..b1495c9e2 100755 --- a/tests/push-to-store.sh +++ b/tests/push-to-store.sh @@ -1,6 +1,10 @@ #!/bin/sh set -x +set -e + +[ -n "$OUT_PATHS" ] +[ -n "$DRV_PATH" ] echo Pushing "$OUT_PATHS" to "$REMOTE_STORE" printf "%s" "$DRV_PATH" | xargs nix copy --to "$REMOTE_STORE" --no-require-sigs From d77d813017d9bb5c82040d4a7749f80582422d7e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 28 Apr 2022 14:24:17 +0200 Subject: [PATCH 47/79] Shut up clang warning --- src/nix/fmt.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nix/fmt.cc b/src/nix/fmt.cc index e5d44bd38..6f6a4a632 100644 --- a/src/nix/fmt.cc +++ b/src/nix/fmt.cc @@ -26,7 +26,8 @@ struct CmdFmt : SourceExprCommand { Strings getDefaultFlakeAttrPathPrefixes() override { return Strings{}; } - void run(ref store) { + void run(ref store) override + { auto evalState = getEvalState(); auto evalStore = getEvalStore(); From cb5361628d07d7b84b83786e98d05ac316f606e3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 28 Apr 2022 14:36:48 +0200 Subject: [PATCH 48/79] Add libcxxStdenv devshell --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 87b00edf4..dd3a25e9e 100644 --- a/flake.nix +++ b/flake.nix @@ -23,7 +23,7 @@ crossSystems = [ "armv6l-linux" "armv7l-linux" ]; - stdenvs = [ "gccStdenv" "clangStdenv" "clang11Stdenv" "stdenv" ]; + stdenvs = [ "gccStdenv" "clangStdenv" "clang11Stdenv" "stdenv" "libcxxStdenv" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); forAllSystemsAndStdenvs = f: forAllSystems (system: From 70a30dbc00bfeb8b3f5f9b137bf3176e8a0b9298 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 28 Apr 2022 14:37:05 +0200 Subject: [PATCH 49/79] Fix libcxx build Fixes #6458. --- src/libstore/s3.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libstore/s3.hh b/src/libstore/s3.hh index 3f55c74db..cdb3e5908 100644 --- a/src/libstore/s3.hh +++ b/src/libstore/s3.hh @@ -5,6 +5,7 @@ #include "ref.hh" #include +#include namespace Aws { namespace Client { class ClientConfiguration; } } namespace Aws { namespace S3 { class S3Client; } } From 143b73f52dabb35cd56551c24caef95466bce488 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Fri, 29 Apr 2022 11:14:08 +0200 Subject: [PATCH 50/79] tests: Distinguish crashes from expected failures --- tests/lang.sh | 58 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/tests/lang.sh b/tests/lang.sh index 61bb444ba..6cb4b6e2b 100644 --- a/tests/lang.sh +++ b/tests/lang.sh @@ -10,32 +10,49 @@ nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" (throw set +x fail=0 +just_failed=0 + +safe_expect () { + expected=$1 name=$2 msg=$3 cmd=$4 + just_failed=0 + res=0 + eval "$cmd" || res=$? + + if [ $res -ge 2 ]; then + [ $res -le 128 ] || { echo "FAIL: $name returned unexpected code '$res' (should be 0 or 1)"; just_failed=1; } + [ $res -gt 128 ] || { echo "FAIL: $name crashed with $res ($(kill -l $(($res - 128))))"; just_failed=1; } + elif [ $expected = "fail" ]; then + [ $res -ne 0 ] || { echo "FAIL: $name $msg"; just_failed=1; } + elif [ $expected = "pass" ]; then + [ $res -eq 0 ] || { echo "FAIL: $name $msg"; just_failed=1; } + else + echo "usage: safe_expect [pass|fail] name msg command" + exit 1 + fi + + [ $just_failed -eq 0 ] || fail=1 +} + for i in lang/parse-fail-*.nix; do echo "parsing $i (should fail)"; i=$(basename $i .nix) - if nix-instantiate --parse - < lang/$i.nix; then - echo "FAIL: $i shouldn't parse" - fail=1 - fi + safe_expect fail "$i" "shouldn't parse" \ + "nix-instantiate --parse - < lang/$i.nix" done for i in lang/parse-okay-*.nix; do echo "parsing $i (should succeed)"; i=$(basename $i .nix) - if ! nix-instantiate --parse - < lang/$i.nix > lang/$i.out; then - echo "FAIL: $i should parse" - fail=1 - fi + safe_expect pass "$i" "should parse" \ + "nix-instantiate --parse - < lang/$i.nix > lang/$i.out" done for i in lang/eval-fail-*.nix; do echo "evaluating $i (should fail)"; i=$(basename $i .nix) - if nix-instantiate --eval lang/$i.nix; then - echo "FAIL: $i shouldn't evaluate" - fail=1 - fi + safe_expect fail "$i" "shouldn't evaluate" \ + "nix-instantiate --eval lang/$i.nix" done for i in lang/eval-okay-*.nix; do @@ -45,23 +62,20 @@ for i in lang/eval-okay-*.nix; do if test -e lang/$i.exp; then flags= if test -e lang/$i.flags; then - flags=$(cat lang/$i.flags) + flags='$'"(cat lang/$i.flags)" # because vi does not highlight "\$(...)" properly ;-). fi - if ! NIX_PATH=lang/dir3:lang/dir4 nix-instantiate $flags --eval --strict lang/$i.nix > lang/$i.out; then - echo "FAIL: $i should evaluate" - fail=1 - elif ! diff lang/$i.out lang/$i.exp; then + safe_expect pass "$i" "should evaluate" \ + "NIX_PATH=lang/dir3:lang/dir4 nix-instantiate $flags --eval --strict lang/$i.nix > lang/$i.out" + if [ $just_failed -eq 0 ] && ! diff lang/$i.out lang/$i.exp; then echo "FAIL: evaluation result of $i not as expected" fail=1 fi fi if test -e lang/$i.exp.xml; then - if ! nix-instantiate --eval --xml --no-location --strict \ - lang/$i.nix > lang/$i.out.xml; then - echo "FAIL: $i should evaluate" - fail=1 - elif ! cmp -s lang/$i.out.xml lang/$i.exp.xml; then + safe_expect pass "$i" "should evaluate" \ + "nix-instantiate --eval --xml --no-location --strict lang/$i.nix > lang/$i.out.xml" + if [ $just_failed -eq 0 ] && ! cmp -s lang/$i.out.xml lang/$i.exp.xml; then echo "FAIL: XML evaluation result of $i not as expected" fail=1 fi From 401e60f2893d689772f716cbd800ee2ee0309362 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Tue, 2 Mar 2021 20:13:20 -0500 Subject: [PATCH 51/79] Resolve reference for remote repository Resolves the HEAD reference from the remote repository instead of assuming "master". --- src/libfetchers/git.cc | 104 ++++++++++++++++++++++++++++++++++++++--- tests/flakes.sh | 9 +++- 2 files changed, 106 insertions(+), 7 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index af40990e5..6b83638f6 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -5,9 +5,11 @@ #include "store-api.hh" #include "url-parts.hh" #include "pathlocks.hh" +#include "util.hh" #include "fetch-settings.hh" +#include #include #include @@ -19,7 +21,7 @@ namespace nix::fetchers { // The value itself does not matter, since we always fetch a specific revision or branch. // It is set with `-c init.defaultBranch=` instead of `--initial-branch=` to stay compatible with // old version of git, which will ignore unrecognized `-c` options. -const std::string gitInitialBranch = "__nix_dummy_branch"; +static const std::string gitInitialBranch = "__nix_dummy_branch"; static std::string getGitDir() { @@ -30,9 +32,92 @@ static std::string getGitDir() return *gitDir; } -static std::string readHead(const Path & path) +static bool isCacheFileWithinTtl(const time_t now, const struct stat& st) { + return st.st_mtime + settings.tarballTtl > now; +} + +static Path getCachePath(std::string key) { + return getCacheDir() + "/nix/gitv3/" + + hashString(htSHA256, key).to_string(Base32, false); +} + +// Returns the name of the HEAD branch. +// +// Returns the head branch name as reported by git ls-remote --symref, e.g., if +// ls-remote returns the output below, "main" is returned based on the ref line. +// +// ref: refs/heads/main HEAD +// ... +static std::optional readHead(const Path & path) { - return chomp(runProgram("git", true, { "-C", path, "--git-dir", ".git", "rev-parse", "--abbrev-ref", "HEAD" })); + auto [exit_code, output] = runProgram(RunOptions { + .program = "git", + .args = {"ls-remote", "--symref", path}, + }); + if (exit_code != 0) { + return std::nullopt; + } + + // Matches the common case when HEAD points to a branch, e.g.: + // "ref: refs/heads/main HEAD". + const static std::regex head_ref_regex("^ref:\\s*([^\\s]+)\\s*HEAD$"); + // Matches when HEAD points directly at a commit, e.g.: + // "71abcd... HEAD". + const static std::regex head_rev_regex("^([^\\s]+)\\s*HEAD$"); + + for (const auto & line : tokenizeString>(output, "\n")) { + std::smatch match; + if (std::regex_match(line, match, head_ref_regex)) { + debug("resolved HEAD ref '%s' for repo '%s'", match[1], path); + return match[1]; + } else if (std::regex_match(line, match, head_rev_regex)) { + debug("resolved HEAD ref '%s' for repo '%s'", match[1], path); + return match[1]; + } + } + + return std::nullopt; +} + +static std::optional readHeadCached(std::string actualUrl) +{ + // Create a cache path to store the branch of the HEAD ref. Append something + // in front of the URL to prevent collision with the repository itself. + Path cachePath = getCachePath("|" + actualUrl); + time_t now = time(0); + struct stat st; + std::optional cachedRef; + if (stat(cachePath.c_str(), &st) == 0 && + // The file may be empty, because writeFile() is not atomic. + st.st_size > 0) { + + // The cached ref is persisted unconditionally (see below). + cachedRef = readFile(cachePath); + if (isCacheFileWithinTtl(now, st)) { + debug("using cached HEAD ref '%s' for repo '%s'", *cachedRef, actualUrl); + return cachedRef; + } + } + + auto ref = readHead(actualUrl); + + if (ref) { + debug("storing cached HEAD ref '%s' for repo '%s' at '%s'", *ref, actualUrl, cachePath); + createDirs(dirOf(cachePath)); + writeFile(cachePath, *ref); + return ref; + } + + if (cachedRef) { + // If the cached git ref is expired in fetch() below, and the 'git fetch' + // fails, it falls back to continuing with the most recent version. + // This function must behave the same way, so we return the expired + // cached ref here. + warn("could not get HEAD ref for repository '%s'; using expired cached ref '%s'", actualUrl, *cachedRef); + return *cachedRef; + } + + return std::nullopt; } static bool isNotDotGitDirectory(const Path & path) @@ -331,7 +416,14 @@ struct GitInputScheme : InputScheme } } - if (!input.getRef()) input.attrs.insert_or_assign("ref", isLocal ? readHead(actualUrl) : "master"); + if (!input.getRef()) { + auto head = isLocal ? readHead(actualUrl) : readHeadCached(actualUrl); + if (!head) { + warn("could not read HEAD ref from repo at '%s', using 'master'", actualUrl); + head = "master"; + } + input.attrs.insert_or_assign("ref", *head); + } Attrs unlockedAttrs({ {"type", cacheType}, @@ -360,7 +452,7 @@ struct GitInputScheme : InputScheme } } - Path cacheDir = getCacheDir() + "/nix/gitv3/" + hashString(htSHA256, actualUrl).to_string(Base32, false); + Path cacheDir = getCachePath(actualUrl); repoDir = cacheDir; gitDir = "."; @@ -400,7 +492,7 @@ struct GitInputScheme : InputScheme git fetch to update the local ref to the remote ref. */ struct stat st; doFetch = stat(localRefFile.c_str(), &st) != 0 || - (uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now; + !isCacheFileWithinTtl(now, st); } } diff --git a/tests/flakes.sh b/tests/flakes.sh index 46e6a7982..24601784f 100644 --- a/tests/flakes.sh +++ b/tests/flakes.sh @@ -31,7 +31,14 @@ flakeFollowsE=$TEST_ROOT/follows/flakeA/flakeE for repo in $flake1Dir $flake2Dir $flake3Dir $flake7Dir $templatesDir $nonFlakeDir $flakeA $flakeB $flakeFollowsA; do rm -rf $repo $repo.tmp mkdir -p $repo - git -C $repo init + + # Give one repo a non-master initial branch. + extraArgs= + if [[ $repo == $flake2Dir ]]; then + extraArgs="--initial-branch=main" + fi + + git -C $repo init $extraArgs git -C $repo config user.email "foobar@example.com" git -C $repo config user.name "Foobar" done From 05a3fbac5a99e5738b951443afe17b680d5a7ead Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Tue, 20 Apr 2021 07:52:50 -0400 Subject: [PATCH 52/79] Test fetchGit with non-'master' remote repo --- tests/fetchGit.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh index 9179e2071..166bccfc7 100644 --- a/tests/fetchGit.sh +++ b/tests/fetchGit.sh @@ -161,6 +161,14 @@ path4=$(nix eval --impure --raw --expr "(builtins.fetchGit $repo).outPath") [[ $(cat $path4/hello) = dev ]] [[ $path3 = $path4 ]] +# Using remote path with branch other than 'master' should fetch the HEAD revision. +# (--tarball-ttl 0 to prevent using the cached repo above) +export _NIX_FORCE_HTTP=1 +path4=$(nix eval --tarball-ttl 0 --impure --raw --expr "(builtins.fetchGit $repo).outPath") +[[ $(cat $path4/hello) = dev ]] +[[ $path3 = $path4 ]] +unset _NIX_FORCE_HTTP + # Confirm same as 'dev' branch path5=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; ref = \"dev\"; }).outPath") [[ $path3 = $path5 ]] From de54e1cd3fce6eb456048b6a346a0be2b88660ae Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Sat, 29 Jan 2022 14:12:01 -0500 Subject: [PATCH 53/79] Refactor fetching of dirty workdir Extract the handling of a local dirty workdir to a helper function. --- src/libfetchers/git.cc | 224 ++++++++++++++++++++++------------------- 1 file changed, 123 insertions(+), 101 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 6b83638f6..00a7b85d8 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -16,14 +16,15 @@ using namespace std::string_literals; namespace nix::fetchers { +namespace { // Explicit initial branch of our bare repo to suppress warnings from new version of git. // The value itself does not matter, since we always fetch a specific revision or branch. // It is set with `-c init.defaultBranch=` instead of `--initial-branch=` to stay compatible with // old version of git, which will ignore unrecognized `-c` options. -static const std::string gitInitialBranch = "__nix_dummy_branch"; +const std::string gitInitialBranch = "__nix_dummy_branch"; -static std::string getGitDir() +std::string getGitDir() { auto gitDir = getEnv("GIT_DIR"); if (!gitDir) { @@ -32,11 +33,11 @@ static std::string getGitDir() return *gitDir; } -static bool isCacheFileWithinTtl(const time_t now, const struct stat& st) { +bool isCacheFileWithinTtl(const time_t now, const struct stat& st) { return st.st_mtime + settings.tarballTtl > now; } -static Path getCachePath(std::string key) { +Path getCachePath(std::string key) { return getCacheDir() + "/nix/gitv3/" + hashString(htSHA256, key).to_string(Base32, false); } @@ -48,7 +49,7 @@ static Path getCachePath(std::string key) { // // ref: refs/heads/main HEAD // ... -static std::optional readHead(const Path & path) +std::optional readHead(const Path & path) { auto [exit_code, output] = runProgram(RunOptions { .program = "git", @@ -79,7 +80,7 @@ static std::optional readHead(const Path & path) return std::nullopt; } -static std::optional readHeadCached(std::string actualUrl) +std::optional readHeadCached(std::string actualUrl) { // Create a cache path to store the branch of the HEAD ref. Append something // in front of the URL to prevent collision with the repository itself. @@ -120,11 +121,120 @@ static std::optional readHeadCached(std::string actualUrl) return std::nullopt; } -static bool isNotDotGitDirectory(const Path & path) +bool isNotDotGitDirectory(const Path & path) { return baseNameOf(path) != ".git"; } +struct WorkdirInfo +{ + bool clean = false; + bool hasHead = false; +}; + +// Returns whether a git workdir is clean and has commits. +WorkdirInfo getWorkdirInfo(const Input & input, const Path & workdir) +{ + const bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false); + auto gitDir = getGitDir(); + + auto env = getEnv(); + // Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong + // that way unknown errors can lead to a failure instead of continuing through the wrong code path + env["LC_ALL"] = "C"; + + /* Check whether HEAD points to something that looks like a commit, + since that is the refrence we want to use later on. */ + auto result = runProgram(RunOptions { + .program = "git", + .args = { "-C", workdir, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" }, + .environment = env, + .mergeStderrToStdout = true + }); + auto exitCode = WEXITSTATUS(result.first); + auto errorMessage = result.second; + + if (errorMessage.find("fatal: not a git repository") != std::string::npos) { + throw Error("'%s' is not a Git repository", workdir); + } else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) { + // indicates that the repo does not have any commits + // we want to proceed and will consider it dirty later + } else if (exitCode != 0) { + // any other errors should lead to a failure + throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", workdir, exitCode, errorMessage); + } + + bool clean = false; + bool hasHead = exitCode == 0; + + try { + if (hasHead) { + // Using git diff is preferrable over lower-level operations here, + // because its conceptually simpler and we only need the exit code anyways. + auto gitDiffOpts = Strings({ "-C", workdir, "diff", "HEAD", "--quiet"}); + if (!submodules) { + // Changes in submodules should only make the tree dirty + // when those submodules will be copied as well. + gitDiffOpts.emplace_back("--ignore-submodules"); + } + gitDiffOpts.emplace_back("--"); + runProgram("git", true, gitDiffOpts); + + clean = true; + } + } catch (ExecError & e) { + if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw; + } + + return WorkdirInfo { .clean = clean, .hasHead = hasHead }; +} + +std::pair fetchFromWorkdir(ref store, Input & input, const Path & workdir, const WorkdirInfo & workdirInfo) +{ + const bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false); + + if (!fetchSettings.allowDirty) + throw Error("Git tree '%s' is dirty", workdir); + + if (fetchSettings.warnDirty) + warn("Git tree '%s' is dirty", workdir); + + auto gitOpts = Strings({ "-C", workdir, "ls-files", "-z" }); + if (submodules) + gitOpts.emplace_back("--recurse-submodules"); + + auto files = tokenizeString>( + runProgram("git", true, gitOpts), "\0"s); + + Path actualPath(absPath(workdir)); + + PathFilter filter = [&](const Path & p) -> bool { + assert(hasPrefix(p, actualPath)); + std::string file(p, actualPath.size() + 1); + + auto st = lstat(p); + + if (S_ISDIR(st.st_mode)) { + auto prefix = file + "/"; + auto i = files.lower_bound(prefix); + return i != files.end() && hasPrefix(*i, prefix); + } + + return files.count(file); + }; + + auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter); + + // FIXME: maybe we should use the timestamp of the last + // modified dirty file? + input.attrs.insert_or_assign( + "lastModified", + workdirInfo.hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); + + return {std::move(storePath), input}; +} +} // end namespace + struct GitInputScheme : InputScheme { std::optional inputFromURL(const ParsedURL & url) override @@ -319,100 +429,12 @@ struct GitInputScheme : InputScheme auto [isLocal, actualUrl_] = getActualUrl(input); auto actualUrl = actualUrl_; // work around clang bug - // If this is a local directory and no ref or revision is - // given, then allow the use of an unclean working tree. + /* If this is a local directory and no ref or revision is given, + allow fetching directly from a dirty workdir. */ if (!input.getRef() && !input.getRev() && isLocal) { - bool clean = false; - - auto env = getEnv(); - // Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong - // that way unknown errors can lead to a failure instead of continuing through the wrong code path - env["LC_ALL"] = "C"; - - /* Check whether HEAD points to something that looks like a commit, - since that is the refrence we want to use later on. */ - auto result = runProgram(RunOptions { - .program = "git", - .args = { "-C", actualUrl, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" }, - .environment = env, - .mergeStderrToStdout = true - }); - auto exitCode = WEXITSTATUS(result.first); - auto errorMessage = result.second; - - if (errorMessage.find("fatal: not a git repository") != std::string::npos) { - throw Error("'%s' is not a Git repository", actualUrl); - } else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) { - // indicates that the repo does not have any commits - // we want to proceed and will consider it dirty later - } else if (exitCode != 0) { - // any other errors should lead to a failure - throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", actualUrl, exitCode, errorMessage); - } - - bool hasHead = exitCode == 0; - try { - if (hasHead) { - // Using git diff is preferrable over lower-level operations here, - // because its conceptually simpler and we only need the exit code anyways. - auto gitDiffOpts = Strings({ "-C", actualUrl, "--git-dir", gitDir, "diff", "HEAD", "--quiet"}); - if (!submodules) { - // Changes in submodules should only make the tree dirty - // when those submodules will be copied as well. - gitDiffOpts.emplace_back("--ignore-submodules"); - } - gitDiffOpts.emplace_back("--"); - runProgram("git", true, gitDiffOpts); - - clean = true; - } - } catch (ExecError & e) { - if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw; - } - - if (!clean) { - - /* This is an unclean working tree. So copy all tracked files. */ - - if (!fetchSettings.allowDirty) - throw Error("Git tree '%s' is dirty", actualUrl); - - if (fetchSettings.warnDirty) - warn("Git tree '%s' is dirty", actualUrl); - - auto gitOpts = Strings({ "-C", actualUrl, "--git-dir", gitDir, "ls-files", "-z" }); - if (submodules) - gitOpts.emplace_back("--recurse-submodules"); - - auto files = tokenizeString>( - runProgram("git", true, gitOpts), "\0"s); - - Path actualPath(absPath(actualUrl)); - - PathFilter filter = [&](const Path & p) -> bool { - assert(hasPrefix(p, actualPath)); - std::string file(p, actualPath.size() + 1); - - auto st = lstat(p); - - if (S_ISDIR(st.st_mode)) { - auto prefix = file + "/"; - auto i = files.lower_bound(prefix); - return i != files.end() && hasPrefix(*i, prefix); - } - - return files.count(file); - }; - - auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter); - - // FIXME: maybe we should use the timestamp of the last - // modified dirty file? - input.attrs.insert_or_assign( - "lastModified", - hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); - - return {std::move(storePath), input}; + auto workdirInfo = getWorkdirInfo(input, actualUrl); + if (!workdirInfo.clean) { + return fetchFromWorkdir(store, input, actualUrl, workdirInfo); } } @@ -425,7 +447,7 @@ struct GitInputScheme : InputScheme input.attrs.insert_or_assign("ref", *head); } - Attrs unlockedAttrs({ + const Attrs unlockedAttrs({ {"type", cacheType}, {"name", name}, {"url", actualUrl}, From 1203e489263fe867d0a1ae3fd9270f40c8a1c24e Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Sat, 29 Jan 2022 14:22:55 -0500 Subject: [PATCH 54/79] Store cached head in cached git repo The previous head caching implementation stored two paths in the local cache; one for the cached git repo and another textfile containing the resolved HEAD ref. This commit instead stores the resolved HEAD by setting the HEAD ref in the local cache appropriately. --- src/libfetchers/git.cc | 89 +++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 31 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 00a7b85d8..968cd642a 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -10,6 +10,7 @@ #include "fetch-settings.hh" #include +#include #include #include @@ -37,7 +38,19 @@ bool isCacheFileWithinTtl(const time_t now, const struct stat& st) { return st.st_mtime + settings.tarballTtl > now; } -Path getCachePath(std::string key) { +bool touchCacheFile(const Path& path, const time_t& touch_time) +{ + struct timeval times[2]; + times[0].tv_sec = touch_time; + times[0].tv_usec = 0; + times[1].tv_sec = touch_time; + times[1].tv_usec = 0; + + return lutimes(path.c_str(), times) == 0; +} + +Path getCachePath(std::string key) +{ return getCacheDir() + "/nix/gitv3/" + hashString(htSHA256, key).to_string(Base32, false); } @@ -80,32 +93,42 @@ std::optional readHead(const Path & path) return std::nullopt; } -std::optional readHeadCached(std::string actualUrl) +// Persist the HEAD ref from the remote repo in the local cached repo. +bool storeCachedHead(const std::string& actualUrl, const std::string& headRef) +{ + Path cacheDir = getCachePath(actualUrl); + try { + runProgram("git", true, { "-C", cacheDir, "symbolic-ref", "--", "HEAD", headRef }); + } catch (ExecError &e) { + if (!WIFEXITED(e.status)) throw; + return false; + } + /* No need to touch refs/HEAD, because `git symbolic-ref` updates the mtime. */ + return true; +} + +std::optional readHeadCached(const std::string& actualUrl) { // Create a cache path to store the branch of the HEAD ref. Append something // in front of the URL to prevent collision with the repository itself. - Path cachePath = getCachePath("|" + actualUrl); + Path cacheDir = getCachePath(actualUrl); + Path headRefFile = cacheDir + "/HEAD"; + time_t now = time(0); struct stat st; std::optional cachedRef; - if (stat(cachePath.c_str(), &st) == 0 && - // The file may be empty, because writeFile() is not atomic. - st.st_size > 0) { - - // The cached ref is persisted unconditionally (see below). - cachedRef = readFile(cachePath); - if (isCacheFileWithinTtl(now, st)) { + if (stat(headRefFile.c_str(), &st) == 0) { + cachedRef = readHead(cacheDir); + if (cachedRef != std::nullopt && + *cachedRef != gitInitialBranch && + isCacheFileWithinTtl(now, st)) { debug("using cached HEAD ref '%s' for repo '%s'", *cachedRef, actualUrl); return cachedRef; } } auto ref = readHead(actualUrl); - if (ref) { - debug("storing cached HEAD ref '%s' for repo '%s' at '%s'", *ref, actualUrl, cachePath); - createDirs(dirOf(cachePath)); - writeFile(cachePath, *ref); return ref; } @@ -438,15 +461,6 @@ struct GitInputScheme : InputScheme } } - if (!input.getRef()) { - auto head = isLocal ? readHead(actualUrl) : readHeadCached(actualUrl); - if (!head) { - warn("could not read HEAD ref from repo at '%s', using 'master'", actualUrl); - head = "master"; - } - input.attrs.insert_or_assign("ref", *head); - } - const Attrs unlockedAttrs({ {"type", cacheType}, {"name", name}, @@ -457,14 +471,30 @@ struct GitInputScheme : InputScheme Path repoDir; if (isLocal) { + if (!input.getRef()) { + auto head = readHead(actualUrl); + if (!head) { + warn("could not read HEAD ref from repo at '%s', using 'master'", actualUrl); + head = "master"; + } + input.attrs.insert_or_assign("ref", *head); + } if (!input.getRev()) input.attrs.insert_or_assign("rev", Hash::parseAny(chomp(runProgram("git", true, { "-C", actualUrl, "--git-dir", gitDir, "rev-parse", *input.getRef() })), htSHA1).gitRev()); repoDir = actualUrl; - } else { + const bool useHeadRef = !input.getRef(); + if (useHeadRef) { + auto head = readHeadCached(actualUrl); + if (!head) { + warn("could not read HEAD ref from repo at '%s', using 'master'", actualUrl); + head = "master"; + } + input.attrs.insert_or_assign("ref", *head); + } if (auto res = getCache()->lookup(store, unlockedAttrs)) { auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), htSHA1); @@ -538,13 +568,10 @@ struct GitInputScheme : InputScheme warn("could not update local clone of Git repository '%s'; continuing with the most recent version", actualUrl); } - struct timeval times[2]; - times[0].tv_sec = now; - times[0].tv_usec = 0; - times[1].tv_sec = now; - times[1].tv_usec = 0; - - utimes(localRefFile.c_str(), times); + if (!touchCacheFile(localRefFile, now)) + warn("could not update mtime for file '%s': %s", localRefFile, strerror(errno)); + if (useHeadRef && !storeCachedHead(actualUrl, *input.getRef())) + warn("could not update cached head '%s' for '%s'", *input.getRef(), actualUrl); } if (!input.getRev()) From c21afd684cb5f59337b879684728884fd8275ce4 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Sun, 30 Jan 2022 11:30:57 -0500 Subject: [PATCH 55/79] Update `nix flake` documentation of `ref` handling Update the documentation about how `ref` is resolved if it is not specified. Add a note about special handling of local workdirs with `git+file`. --- src/nix/flake.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/nix/flake.md b/src/nix/flake.md index 7d179a6c4..c8251eb74 100644 --- a/src/nix/flake.md +++ b/src/nix/flake.md @@ -153,7 +153,7 @@ Currently the `type` attribute can be one of the following: git(+http|+https|+ssh|+git|+file|):(//)?(\?)? ``` - The `ref` attribute defaults to `master`. + The `ref` attribute defaults to resolving the `HEAD` reference. The `rev` attribute must denote a commit that exists in the branch or tag specified by the `ref` attribute, since Nix doesn't do a full @@ -161,6 +161,11 @@ Currently the `type` attribute can be one of the following: doesn't allow fetching a `rev` without a known `ref`). The default is the commit currently pointed to by `ref`. + When `git+file` is used without specifying `ref` or `rev`, files are + fetched directly from the local `path` as long as they have been added + to the Git repository. If there are uncommitted changes, the reference + is treated as dirty and a warning is printed. + For example, the following are valid Git flake references: * `git+https://example.org/my/repo` From 9bf296c970bf33b7ed53d7e2d8fbe44197482518 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Fri, 29 Apr 2022 18:30:00 -0400 Subject: [PATCH 56/79] Extract git reference parsing to a shared library These utility functions can be shared between the git and github fetchers. --- src/libfetchers/git-utils.cc | 25 +++++++++++++++++++++++++ src/libfetchers/git-utils.hh | 23 +++++++++++++++++++++++ src/libfetchers/git.cc | 29 +++++++++++------------------ src/libfetchers/github.cc | 28 +++++++++++----------------- 4 files changed, 70 insertions(+), 35 deletions(-) create mode 100644 src/libfetchers/git-utils.cc create mode 100644 src/libfetchers/git-utils.hh diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc new file mode 100644 index 000000000..060077098 --- /dev/null +++ b/src/libfetchers/git-utils.cc @@ -0,0 +1,25 @@ +#include "git-utils.hh" + +#include + +std::optional parseListReferenceHeadRef(std::string_view line) { + const static std::regex head_ref_regex("^ref: ([^\\s]+)\\t+HEAD$"); + std::match_results match; + if (std::regex_match(line.cbegin(), line.cend(), match, head_ref_regex)) { + return match[1]; + } else { + return std::nullopt; + } +} + +std::optional parseListReferenceForRev(std::string_view rev, std::string_view line) { + const static std::regex rev_regex("^([^\\t]+)\\t+(.*)$"); + std::match_results match; + if (!std::regex_match(line.cbegin(), line.cend(), match, rev_regex)) { + return std::nullopt; + } + if (rev != match[2].str()) { + return std::nullopt; + } + return match[1]; +} diff --git a/src/libfetchers/git-utils.hh b/src/libfetchers/git-utils.hh new file mode 100644 index 000000000..946a68a9e --- /dev/null +++ b/src/libfetchers/git-utils.hh @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +// Parses the HEAD ref as reported by `git ls-remote --symref` +// +// Returns the head branch name as reported by `git ls-remote --symref`, e.g., if +// ls-remote returns the output below, "main" is returned based on the ref line. +// +// ref: refs/heads/main HEAD +// +// If the repository is in 'detached head' state (HEAD is pointing to a rev +// instead of a branch), parseListReferenceForRev("HEAD") may be used instead. +std::optional parseListReferenceHeadRef(std::string_view line); + +// Parses a reference line from `git ls-remote --symref`, e.g., +// parseListReferenceForRev("refs/heads/master", line) will return 6926... +// given the line below. +// +// 6926beab444c33fb57b21819b6642d032016bb1e refs/heads/master +std::optional parseListReferenceForRev(std::string_view rev, std::string_view line); diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 968cd642a..9d4348cf1 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -6,6 +6,7 @@ #include "url-parts.hh" #include "pathlocks.hh" #include "util.hh" +#include "git-utils.hh" #include "fetch-settings.hh" @@ -69,27 +70,19 @@ std::optional readHead(const Path & path) .args = {"ls-remote", "--symref", path}, }); if (exit_code != 0) { - return std::nullopt; + return std::nullopt; } - // Matches the common case when HEAD points to a branch, e.g.: - // "ref: refs/heads/main HEAD". - const static std::regex head_ref_regex("^ref:\\s*([^\\s]+)\\s*HEAD$"); - // Matches when HEAD points directly at a commit, e.g.: - // "71abcd... HEAD". - const static std::regex head_rev_regex("^([^\\s]+)\\s*HEAD$"); - - for (const auto & line : tokenizeString>(output, "\n")) { - std::smatch match; - if (std::regex_match(line, match, head_ref_regex)) { - debug("resolved HEAD ref '%s' for repo '%s'", match[1], path); - return match[1]; - } else if (std::regex_match(line, match, head_rev_regex)) { - debug("resolved HEAD ref '%s' for repo '%s'", match[1], path); - return match[1]; - } + std::string_view line = output; + line = line.substr(0, line.find("\n")); + if (const auto ref = parseListReferenceHeadRef(line); ref) { + debug("resolved HEAD ref '%s' for repo '%s'", *ref, path); + return *ref; + } + if (const auto rev = parseListReferenceForRev("HEAD", line); rev) { + debug("resolved HEAD rev '%s' for repo '%s'", *rev, path); + return *rev; } - return std::nullopt; } diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index 58b6e7c04..1bdf2759f 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -4,7 +4,7 @@ #include "store-api.hh" #include "types.hh" #include "url-parts.hh" - +#include "git-utils.hh" #include "fetchers.hh" #include "fetch-settings.hh" @@ -383,35 +383,29 @@ struct SourceHutInputScheme : GitArchiveInputScheme std::string line; getline(is, line); - auto ref_index = line.find("ref: "); - if (ref_index == std::string::npos) { + auto r = parseListReferenceHeadRef(line); + if (!r) { throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref); } - - ref_uri = line.substr(ref_index+5, line.length()-1); - } else + ref_uri = *r; + } else { ref_uri = fmt("refs/(heads|tags)/%s", ref); + } auto file = store->toRealPath( downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath); std::ifstream is(file); std::string line; - std::string id; - while(getline(is, line)) { - // Append $ to avoid partial name matches - std::regex pattern(fmt("%s$", ref_uri)); - - if (std::regex_search(line, pattern)) { - id = line.substr(0, line.find('\t')); - break; - } + std::optional id; + while(!id && getline(is, line)) { + id = parseListReferenceForRev(ref_uri, line); } - if(id.empty()) + if(!id) throw BadURL("in '%d', couldn't find ref '%d'", input.to_string(), ref); - auto rev = Hash::parseAny(id, htSHA1); + auto rev = Hash::parseAny(*id, htSHA1); debug("HEAD revision for '%s' is %s", fmt("%s/%s", base_url, ref), rev.gitRev()); return rev; } From 1849e6a1f64734c488c2b1469249d65ce08cef93 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sat, 30 Apr 2022 15:56:12 +0200 Subject: [PATCH 57/79] libfetchers/git: fix every occasion of a permission error I'm afraid I missed a few problematic `git(1)`-calls while implementing PR #6440, sorry for that! Upon investigating what went wrong, I realized that I only tested against the "cached"-case by accident because my git-checkout with my system's flake was apparently cached during my debugging. I managed to trigger the original issue again by running: $ git commit --allow-empty -m "tmp" $ sudo nixos-rebuild switch --flake .# -L --builders '' Since `repoDir` points to the checkout that's potentially owned by another user, I decided to add `--git-dir` to each call affecting `repoDir`. Since the `tmpDir` for the temporary submodule-checkout is created by Nix itself, it doesn't seem to be an issue. Sorry for that, it should be fine now. --- src/libfetchers/git.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index af40990e5..2ff27795d 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -383,7 +383,7 @@ struct GitInputScheme : InputScheme repo. */ if (input.getRev()) { try { - runProgram("git", true, { "-C", repoDir, "cat-file", "-e", input.getRev()->gitRev() }); + runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "cat-file", "-e", input.getRev()->gitRev() }); doFetch = false; } catch (ExecError & e) { if (WIFEXITED(e.status)) { @@ -418,7 +418,7 @@ struct GitInputScheme : InputScheme : ref == "HEAD" ? *ref : "refs/heads/" + *ref; - runProgram("git", true, { "-C", repoDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) }); + runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) }); } catch (Error & e) { if (!pathExists(localRefFile)) throw; warn("could not update local clone of Git repository '%s'; continuing with the most recent version", actualUrl); @@ -459,7 +459,7 @@ struct GitInputScheme : InputScheme auto result = runProgram(RunOptions { .program = "git", - .args = { "-C", repoDir, "cat-file", "commit", input.getRev()->gitRev() }, + .args = { "-C", repoDir, "--git-dir", gitDir, "cat-file", "commit", input.getRev()->gitRev() }, .mergeStderrToStdout = true }); if (WEXITSTATUS(result.first) == 128 @@ -498,7 +498,7 @@ struct GitInputScheme : InputScheme auto source = sinkToSource([&](Sink & sink) { runProgram2({ .program = "git", - .args = { "-C", repoDir, "archive", input.getRev()->gitRev() }, + .args = { "-C", repoDir, "--git-dir", gitDir, "archive", input.getRev()->gitRev() }, .standardOut = &sink }); }); @@ -508,7 +508,7 @@ struct GitInputScheme : InputScheme auto storePath = store->addToStore(name, tmpDir, FileIngestionMethod::Recursive, htSHA256, filter); - auto lastModified = std::stoull(runProgram("git", true, { "-C", repoDir, "log", "-1", "--format=%ct", "--no-show-signature", input.getRev()->gitRev() })); + auto lastModified = std::stoull(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", input.getRev()->gitRev() })); Attrs infoAttrs({ {"rev", input.getRev()->gitRev()}, @@ -517,7 +517,7 @@ struct GitInputScheme : InputScheme if (!shallow) infoAttrs.insert_or_assign("revCount", - std::stoull(runProgram("git", true, { "-C", repoDir, "rev-list", "--count", input.getRev()->gitRev() }))); + std::stoull(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-list", "--count", input.getRev()->gitRev() }))); if (!_input.getRev()) getCache()->add( From dde71899dd075a2d6479c8b953041b470bdfc19b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 2 May 2022 09:38:51 +0200 Subject: [PATCH 58/79] tests: Don't create tests/result https://hydra.nixos.org/log/lns780srkka4dv7r69mn4zfy6fdij4yr-nix-2.9.0pre20220428_4bb111c.drv --- tests/build-remote.sh | 3 ++- tests/fetchClosure.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/build-remote.sh b/tests/build-remote.sh index d1da134dc..e73c37ea4 100644 --- a/tests/build-remote.sh +++ b/tests/build-remote.sh @@ -34,7 +34,7 @@ outPath=$(readlink -f $TEST_ROOT/result) grep 'FOO BAR BAZ' $TEST_ROOT/machine0/$outPath -testPrintOutPath=$(nix build -L -v -f $file --print-out-paths --max-jobs 0 \ +testPrintOutPath=$(nix build -L -v -f $file --no-link --print-out-paths --max-jobs 0 \ --arg busybox $busybox \ --store $TEST_ROOT/machine0 \ --builders "$(join_by '; ' "${builders[@]}")" @@ -72,6 +72,7 @@ fi # Behavior of keep-failed out="$(nix-build 2>&1 failing.nix \ + --no-out-link \ --builders "$(join_by '; ' "${builders[@]}")" \ --keep-failed \ --store $TEST_ROOT/machine0 \ diff --git a/tests/fetchClosure.sh b/tests/fetchClosure.sh index 96e4bb741..44050c878 100644 --- a/tests/fetchClosure.sh +++ b/tests/fetchClosure.sh @@ -7,7 +7,7 @@ clearStore clearCacheCache # Initialize binary cache. -nonCaPath=$(nix build --json --file ./dependencies.nix | jq -r .[].outputs.out) +nonCaPath=$(nix build --json --file ./dependencies.nix --no-link | jq -r .[].outputs.out) caPath=$(nix store make-content-addressed --json $nonCaPath | jq -r '.rewrites | map(.) | .[]') nix copy --to file://$cacheDir $nonCaPath From 61289ceee3417cd2aa8e8db9fe117a5813969aed Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 2 May 2022 13:37:53 +0200 Subject: [PATCH 59/79] Style fixes --- src/libfetchers/git-utils.cc | 6 ++++-- src/libfetchers/git.cc | 9 +++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 060077098..b2d6b7893 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -2,7 +2,8 @@ #include -std::optional parseListReferenceHeadRef(std::string_view line) { +std::optional parseListReferenceHeadRef(std::string_view line) +{ const static std::regex head_ref_regex("^ref: ([^\\s]+)\\t+HEAD$"); std::match_results match; if (std::regex_match(line.cbegin(), line.cend(), match, head_ref_regex)) { @@ -12,7 +13,8 @@ std::optional parseListReferenceHeadRef(std::string_view line) { } } -std::optional parseListReferenceForRev(std::string_view rev, std::string_view line) { +std::optional parseListReferenceForRev(std::string_view rev, std::string_view line) +{ const static std::regex rev_regex("^([^\\t]+)\\t+(.*)$"); std::match_results match; if (!std::regex_match(line.cbegin(), line.cend(), match, rev_regex)) { diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 2b81900fa..266246fe9 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -28,14 +28,11 @@ const std::string gitInitialBranch = "__nix_dummy_branch"; std::string getGitDir() { - auto gitDir = getEnv("GIT_DIR"); - if (!gitDir) { - return ".git"; - } - return *gitDir; + return getEnv("GIT_DIR").value_or(".git"); } -bool isCacheFileWithinTtl(const time_t now, const struct stat& st) { +bool isCacheFileWithinTtl(const time_t now, const struct stat & st) +{ return st.st_mtime + settings.tarballTtl > now; } From 4845886bed18bb12dbfb7bf909b24cc49abd8da6 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Mon, 2 May 2022 14:22:00 +0200 Subject: [PATCH 60/79] Revert "tests: Distinguish crashes from expected failures" This reverts commit 143b73f52dabb35cd56551c24caef95466bce488. --- tests/lang.sh | 58 +++++++++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/tests/lang.sh b/tests/lang.sh index 6cb4b6e2b..61bb444ba 100644 --- a/tests/lang.sh +++ b/tests/lang.sh @@ -10,49 +10,32 @@ nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" (throw set +x fail=0 -just_failed=0 - -safe_expect () { - expected=$1 name=$2 msg=$3 cmd=$4 - just_failed=0 - res=0 - eval "$cmd" || res=$? - - if [ $res -ge 2 ]; then - [ $res -le 128 ] || { echo "FAIL: $name returned unexpected code '$res' (should be 0 or 1)"; just_failed=1; } - [ $res -gt 128 ] || { echo "FAIL: $name crashed with $res ($(kill -l $(($res - 128))))"; just_failed=1; } - elif [ $expected = "fail" ]; then - [ $res -ne 0 ] || { echo "FAIL: $name $msg"; just_failed=1; } - elif [ $expected = "pass" ]; then - [ $res -eq 0 ] || { echo "FAIL: $name $msg"; just_failed=1; } - else - echo "usage: safe_expect [pass|fail] name msg command" - exit 1 - fi - - [ $just_failed -eq 0 ] || fail=1 -} - for i in lang/parse-fail-*.nix; do echo "parsing $i (should fail)"; i=$(basename $i .nix) - safe_expect fail "$i" "shouldn't parse" \ - "nix-instantiate --parse - < lang/$i.nix" + if nix-instantiate --parse - < lang/$i.nix; then + echo "FAIL: $i shouldn't parse" + fail=1 + fi done for i in lang/parse-okay-*.nix; do echo "parsing $i (should succeed)"; i=$(basename $i .nix) - safe_expect pass "$i" "should parse" \ - "nix-instantiate --parse - < lang/$i.nix > lang/$i.out" + if ! nix-instantiate --parse - < lang/$i.nix > lang/$i.out; then + echo "FAIL: $i should parse" + fail=1 + fi done for i in lang/eval-fail-*.nix; do echo "evaluating $i (should fail)"; i=$(basename $i .nix) - safe_expect fail "$i" "shouldn't evaluate" \ - "nix-instantiate --eval lang/$i.nix" + if nix-instantiate --eval lang/$i.nix; then + echo "FAIL: $i shouldn't evaluate" + fail=1 + fi done for i in lang/eval-okay-*.nix; do @@ -62,20 +45,23 @@ for i in lang/eval-okay-*.nix; do if test -e lang/$i.exp; then flags= if test -e lang/$i.flags; then - flags='$'"(cat lang/$i.flags)" # because vi does not highlight "\$(...)" properly ;-). + flags=$(cat lang/$i.flags) fi - safe_expect pass "$i" "should evaluate" \ - "NIX_PATH=lang/dir3:lang/dir4 nix-instantiate $flags --eval --strict lang/$i.nix > lang/$i.out" - if [ $just_failed -eq 0 ] && ! diff lang/$i.out lang/$i.exp; then + if ! NIX_PATH=lang/dir3:lang/dir4 nix-instantiate $flags --eval --strict lang/$i.nix > lang/$i.out; then + echo "FAIL: $i should evaluate" + fail=1 + elif ! diff lang/$i.out lang/$i.exp; then echo "FAIL: evaluation result of $i not as expected" fail=1 fi fi if test -e lang/$i.exp.xml; then - safe_expect pass "$i" "should evaluate" \ - "nix-instantiate --eval --xml --no-location --strict lang/$i.nix > lang/$i.out.xml" - if [ $just_failed -eq 0 ] && ! cmp -s lang/$i.out.xml lang/$i.exp.xml; then + if ! nix-instantiate --eval --xml --no-location --strict \ + lang/$i.nix > lang/$i.out.xml; then + echo "FAIL: $i should evaluate" + fail=1 + elif ! cmp -s lang/$i.out.xml lang/$i.exp.xml; then echo "FAIL: XML evaluation result of $i not as expected" fail=1 fi From 275f8561eb80a0f341fa2a03d731b239b1783da0 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Mon, 2 May 2022 15:12:50 +0200 Subject: [PATCH 61/79] tests/lang: Distinguish crashes from expected failures --- tests/common.sh.in | 11 ++++++----- tests/lang.sh | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/common.sh.in b/tests/common.sh.in index 8ce28d318..6cb579e0d 100644 --- a/tests/common.sh.in +++ b/tests/common.sh.in @@ -157,11 +157,12 @@ expect() { local expected res expected="$1" shift - set +e - "$@" - res="$?" - set -e - [[ $res -eq $expected ]] + "$@" || res="$?" + if [[ $res -ne $expected ]]; then + echo "Expected '$expected' but got '$res' while running '$*'" + return 1 + fi + return 0 } needLocalStore() { diff --git a/tests/lang.sh b/tests/lang.sh index 61bb444ba..f09eaeb31 100644 --- a/tests/lang.sh +++ b/tests/lang.sh @@ -4,6 +4,7 @@ export TEST_VAR=foo # for eval-okay-getenv.nix export NIX_REMOTE=dummy:// nix-instantiate --eval -E 'builtins.trace "Hello" 123' 2>&1 | grep -q Hello +nix-instantiate --eval -E 'builtins.addErrorContext "Hello" 123' 2>&1 (! nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" 123' 2>&1 | grep -q Hello) nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" (throw "Foo")' 2>&1 | grep -q Hello @@ -14,7 +15,7 @@ fail=0 for i in lang/parse-fail-*.nix; do echo "parsing $i (should fail)"; i=$(basename $i .nix) - if nix-instantiate --parse - < lang/$i.nix; then + if ! expect 1 nix-instantiate --parse - < lang/$i.nix; then echo "FAIL: $i shouldn't parse" fail=1 fi @@ -23,7 +24,7 @@ done for i in lang/parse-okay-*.nix; do echo "parsing $i (should succeed)"; i=$(basename $i .nix) - if ! nix-instantiate --parse - < lang/$i.nix > lang/$i.out; then + if ! expect 0 nix-instantiate --parse - < lang/$i.nix > lang/$i.out; then echo "FAIL: $i should parse" fail=1 fi @@ -32,7 +33,7 @@ done for i in lang/eval-fail-*.nix; do echo "evaluating $i (should fail)"; i=$(basename $i .nix) - if nix-instantiate --eval lang/$i.nix; then + if ! expect 1 nix-instantiate --eval lang/$i.nix; then echo "FAIL: $i shouldn't evaluate" fail=1 fi @@ -47,7 +48,7 @@ for i in lang/eval-okay-*.nix; do if test -e lang/$i.flags; then flags=$(cat lang/$i.flags) fi - if ! NIX_PATH=lang/dir3:lang/dir4 nix-instantiate $flags --eval --strict lang/$i.nix > lang/$i.out; then + if ! expect 0 env NIX_PATH=lang/dir3:lang/dir4 nix-instantiate $flags --eval --strict lang/$i.nix > lang/$i.out; then echo "FAIL: $i should evaluate" fail=1 elif ! diff lang/$i.out lang/$i.exp; then @@ -57,7 +58,7 @@ for i in lang/eval-okay-*.nix; do fi if test -e lang/$i.exp.xml; then - if ! nix-instantiate --eval --xml --no-location --strict \ + if ! expect 0 nix-instantiate --eval --xml --no-location --strict \ lang/$i.nix > lang/$i.out.xml; then echo "FAIL: $i should evaluate" fail=1 From 4a79cba5118f29b896f3d50164beacd4901ab01f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 22 Apr 2022 15:17:01 +0200 Subject: [PATCH 62/79] Allow selecting derivation outputs using 'installable!outputs' E.g. 'nixpkgs#glibc^dev,static' or 'nixpkgs#glibc^*'. --- doc/manual/src/release-notes/rl-next.md | 10 ++++++ src/libcmd/installables.cc | 46 ++++++++++++++++++++----- src/libcmd/installables.hh | 2 ++ src/libexpr/flake/flakeref.cc | 11 ++++++ src/libexpr/flake/flakeref.hh | 8 +++++ src/libstore/path-with-outputs.cc | 16 +++++++++ src/libstore/path-with-outputs.hh | 12 +++++++ src/libstore/tests/path-with-outputs.cc | 46 +++++++++++++++++++++++++ src/nix/bundle.cc | 4 +-- src/nix/develop.cc | 1 + src/nix/flake.cc | 2 +- src/nix/nix.md | 45 ++++++++++++++++++++++++ src/nix/profile.cc | 1 + tests/build.sh | 41 ++++++++++++++++++++++ tests/multiple-outputs.nix | 7 ++++ tests/shell-hello.nix | 11 +++++- tests/shell.sh | 4 +++ 17 files changed, 255 insertions(+), 12 deletions(-) create mode 100644 src/libstore/tests/path-with-outputs.cc diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 7b3ad4e58..efd893662 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -14,3 +14,13 @@ * `nix build` has a new `--print-out-paths` flag to print the resulting output paths. This matches the default behaviour of `nix-build`. + +* You can now specify which outputs of a derivation `nix` should + operate on using the syntax `installable^outputs`, + e.g. `nixpkgs#glibc^dev,static` or `nixpkgs#glibc^*`. By default, + `nix` will use the outputs specified by the derivation's + `meta.outputsToInstall` attribute if it exists, or all outputs + otherwise. + + Selecting derivation outputs using the attribute selection syntax + (e.g. `nixpkgs#glibc.dev`) no longer works. diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index e2ee47dea..55a5e91e9 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -464,9 +464,19 @@ struct InstallableAttrPath : InstallableValue SourceExprCommand & cmd; RootValue v; std::string attrPath; + OutputsSpec outputsSpec; - InstallableAttrPath(ref state, SourceExprCommand & cmd, Value * v, const std::string & attrPath) - : InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath) + InstallableAttrPath( + ref 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; } @@ -495,9 +505,15 @@ std::vector InstallableAttrPath::toDerivations auto drvPath = drvInfo.queryDrvPath(); if (!drvPath) throw Error("'%s' is not a derivation", what()); + std::set outputsToInstall; - for (auto & output : drvInfo.queryOutputs(false, true)) - outputsToInstall.insert(output.first); + + if (auto outputNames = std::get_if(&outputsSpec)) + outputsToInstall = *outputNames; + else + for (auto & output : drvInfo.queryOutputs(false, std::get_if(&outputsSpec))) + outputsToInstall.insert(output.first); + res.push_back(DerivationInfo { .drvPath = *drvPath, .outputsToInstall = std::move(outputsToInstall) @@ -578,6 +594,7 @@ InstallableFlake::InstallableFlake( ref state, FlakeRef && flakeRef, std::string_view fragment, + OutputsSpec outputsSpec, Strings attrPaths, Strings prefixes, const flake::LockFlags & lockFlags) @@ -585,6 +602,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()) @@ -609,14 +627,19 @@ std::tuple InstallableF for (auto & s : aOutputsToInstall->getListOfStrings()) outputsToInstall.insert(s); - if (outputsToInstall.empty()) + if (outputsToInstall.empty() || std::get_if(&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(&outputsSpec)) + outputsToInstall = *outputNames; + auto drvInfo = DerivationInfo { .drvPath = std::move(drvPath), .outputsToInstall = std::move(outputsToInstall), @@ -742,8 +765,14 @@ std::vector> SourceExprCommand::parseInstallables( state->eval(e, *vFile); } - for (auto & s : ss) - result.push_back(std::make_shared(state, *this, vFile, s == "." ? "" : s)); + for (auto & s : ss) { + auto [prefix, outputsSpec] = parseOutputsSpec(s); + result.push_back( + std::make_shared( + state, *this, vFile, + prefix == "." ? "" : prefix, + outputsSpec)); + } } else { @@ -762,12 +791,13 @@ std::vector> SourceExprCommand::parseInstallables( } try { - auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath(".")); + auto [flakeRef, fragment, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(s, absPath(".")); result.push_back(std::make_shared( this, getEvalState(), std::move(flakeRef), fragment, + outputsSpec, getDefaultFlakeAttrPaths(), getDefaultFlakeAttrPathPrefixes(), lockFlags)); diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index 3c2c33549..1a5a96153 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -156,6 +156,7 @@ struct InstallableFlake : InstallableValue FlakeRef flakeRef; Strings attrPaths; Strings prefixes; + OutputsSpec outputsSpec; const flake::LockFlags & lockFlags; mutable std::shared_ptr _lockedFlake; @@ -164,6 +165,7 @@ struct InstallableFlake : InstallableValue ref state, FlakeRef && flakeRef, std::string_view fragment, + OutputsSpec outputsSpec, Strings attrPaths, Strings prefixes, const flake::LockFlags & lockFlags); diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc index c1eae413f..1dcc4555a 100644 --- a/src/libexpr/flake/flakeref.cc +++ b/src/libexpr/flake/flakeref.cc @@ -238,4 +238,15 @@ std::pair FlakeRef::fetchTree(ref store) const return {std::move(tree), FlakeRef(std::move(lockedInput), subdir)}; } +std::tuple parseFlakeRefWithFragmentAndOutputsSpec( + const std::string & url, + const std::optional & baseDir, + bool allowMissing, + bool isFlake) +{ + auto [prefix, outputsSpec] = parseOutputsSpec(url); + auto [flakeRef, fragment] = parseFlakeRefWithFragment(prefix, baseDir, allowMissing, isFlake); + return {std::move(flakeRef), fragment, outputsSpec}; +} + } diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh index 1fddfd9a0..a9182f4bf 100644 --- a/src/libexpr/flake/flakeref.hh +++ b/src/libexpr/flake/flakeref.hh @@ -3,6 +3,7 @@ #include "types.hh" #include "hash.hh" #include "fetchers.hh" +#include "path-with-outputs.hh" #include @@ -79,4 +80,11 @@ std::pair parseFlakeRefWithFragment( std::optional> maybeParseFlakeRefWithFragment( const std::string & url, const std::optional & baseDir = {}); +std::tuple parseFlakeRefWithFragmentAndOutputsSpec( + const std::string & url, + const std::optional & baseDir = {}, + bool allowMissing = false, + bool isFlake = true); + + } diff --git a/src/libstore/path-with-outputs.cc b/src/libstore/path-with-outputs.cc index 078c117bd..7d180a0f6 100644 --- a/src/libstore/path-with-outputs.cc +++ b/src/libstore/path-with-outputs.cc @@ -1,6 +1,8 @@ #include "path-with-outputs.hh" #include "store-api.hh" +#include + namespace nix { std::string StorePathWithOutputs::to_string(const Store & store) const @@ -68,4 +70,18 @@ StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std: return StorePathWithOutputs { store.followLinksToStorePath(path), std::move(outputs) }; } +std::pair parseOutputsSpec(const std::string & s) +{ + static std::regex regex(R"((.*)\^((\*)|([a-z]+(,[a-z]+)*)))"); + + std::smatch match; + if (!std::regex_match(s, match, regex)) + return {s, DefaultOutputs()}; + + if (match[3].matched) + return {match[1], AllOutputs()}; + + return {match[1], tokenizeString(match[4].str(), ",")}; +} + } diff --git a/src/libstore/path-with-outputs.hh b/src/libstore/path-with-outputs.hh index 4c4023dcb..e4235d197 100644 --- a/src/libstore/path-with-outputs.hh +++ b/src/libstore/path-with-outputs.hh @@ -32,4 +32,16 @@ StorePathWithOutputs parsePathWithOutputs(const Store & store, std::string_view StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std::string_view pathWithOutputs); +typedef std::set OutputNames; + +struct AllOutputs { }; + +struct DefaultOutputs { }; + +typedef std::variant OutputsSpec; + +/* Parse a string of the form 'prefix^output1,...outputN' or + 'prefix^*', returning the prefix and the outputs spec. */ +std::pair parseOutputsSpec(const std::string & s); + } diff --git a/src/libstore/tests/path-with-outputs.cc b/src/libstore/tests/path-with-outputs.cc new file mode 100644 index 000000000..350ea7ffd --- /dev/null +++ b/src/libstore/tests/path-with-outputs.cc @@ -0,0 +1,46 @@ +#include "path-with-outputs.hh" + +#include + +namespace nix { + +TEST(parseOutputsSpec, basic) +{ + { + auto [prefix, outputsSpec] = parseOutputsSpec("foo"); + ASSERT_EQ(prefix, "foo"); + ASSERT_TRUE(std::get_if(&outputsSpec)); + } + + { + auto [prefix, outputsSpec] = parseOutputsSpec("foo^*"); + ASSERT_EQ(prefix, "foo"); + ASSERT_TRUE(std::get_if(&outputsSpec)); + } + + { + auto [prefix, outputsSpec] = parseOutputsSpec("foo^out"); + ASSERT_EQ(prefix, "foo"); + ASSERT_TRUE(std::get(outputsSpec) == OutputNames({"out"})); + } + + { + auto [prefix, outputsSpec] = parseOutputsSpec("foo^out,bin"); + ASSERT_EQ(prefix, "foo"); + ASSERT_TRUE(std::get(outputsSpec) == OutputNames({"out", "bin"})); + } + + { + auto [prefix, outputsSpec] = parseOutputsSpec("foo^bar^out,bin"); + ASSERT_EQ(prefix, "foo^bar"); + ASSERT_TRUE(std::get(outputsSpec) == OutputNames({"out", "bin"})); + } + + { + auto [prefix, outputsSpec] = parseOutputsSpec("foo^&*()"); + ASSERT_EQ(prefix, "foo^&*()"); + ASSERT_TRUE(std::get_if(&outputsSpec)); + } +} + +} diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc index 2421adf4e..2e48e4c74 100644 --- a/src/nix/bundle.cc +++ b/src/nix/bundle.cc @@ -75,10 +75,10 @@ struct CmdBundle : InstallableCommand auto val = installable->toValue(*evalState).first; - auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath(".")); + auto [bundlerFlakeRef, bundlerName, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(bundler, absPath(".")); const flake::LockFlags lockFlags{ .writeLockFile = false }; InstallableFlake bundler{this, - evalState, std::move(bundlerFlakeRef), bundlerName, + evalState, std::move(bundlerFlakeRef), bundlerName, outputsSpec, {"bundlers." + settings.thisSystem.get() + ".default", "defaultBundler." + settings.thisSystem.get() }, diff --git a/src/nix/develop.cc b/src/nix/develop.cc index 7fc74d34e..1190b8348 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -507,6 +507,7 @@ struct CmdDevelop : Common, MixEnvironment state, installable->nixpkgsFlakeRef(), "bashInteractive", + DefaultOutputs(), Strings{}, Strings{"legacyPackages." + settings.thisSystem.get() + "."}, nixpkgsLockFlags); diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 6a34ca67b..1938ce4e6 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -724,7 +724,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath(".")); auto installable = InstallableFlake(nullptr, - evalState, std::move(templateFlakeRef), templateName, + evalState, std::move(templateFlakeRef), templateName, DefaultOutputs(), defaultTemplateAttrPaths, defaultTemplateAttrPathsPrefixes, lockFlags); diff --git a/src/nix/nix.md b/src/nix/nix.md index 0dacadee6..d48682a94 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -146,6 +146,51 @@ For most commands, if no installable is specified, the default is `.`, i.e. Nix will operate on the default flake output attribute of the flake in the current directory. +## Derivation output selection + +Derivations can have multiple outputs, each corresponding to a +different store path. For instance, a package can have a `bin` output +that contains programs, and a `dev` output that provides development +artifacts like C/C++ header files. The outputs on which `nix` commands +operate are determined as follows: + +* You can explicitly specify the desired outputs using the syntax + *installable*`^`*output1*`,`*...*`,`*outputN*. For example, you can + obtain the `dev` and `static` outputs of the `glibc` package: + + ```console + # nix build 'nixpkgs#glibc^dev,static' + # ls ./result-dev/include/ ./result-static/lib/ + … + ``` + +* You can also specify that *all* outputs should be used using the + syntax *installable*`^*`. For example, the following shows the size + of all outputs of the `glibc` package in the binary cache: + + ```console + # nix path-info -S --eval-store auto --store https://cache.nixos.org 'nixpkgs#glibc^*' + /nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123 33208200 + /nix/store/851dp95qqiisjifi639r0zzg5l465ny4-glibc-2.33-123-bin 36142896 + /nix/store/kdgs3q6r7xdff1p7a9hnjr43xw2404z7-glibc-2.33-123-debug 155787312 + /nix/store/n4xa8h6pbmqmwnq0mmsz08l38abb06zc-glibc-2.33-123-static 42488328 + /nix/store/q6580lr01jpcsqs4r5arlh4ki2c1m9rv-glibc-2.33-123-dev 44200560 + ``` + +* If you didn't specify the desired outputs, but the derivation has an + attribute `meta.outputsToInstall`, Nix will use those outputs. For + example, since the package `nixpkgs#libxml2` has this attribute: + + ```console + # nix eval 'nixpkgs#libxml2.meta.outputsToInstall' + [ "bin" "man" ] + ``` + + a command like `nix shell nixpkgs#libxml2` will provide only those + two outputs by default. + +* Otherwise, Nix will use all outputs of the derivation. + # Nix stores Most `nix` subcommands operate on a *Nix store*. diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 52c918016..78c8af80c 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -443,6 +443,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf getEvalState(), FlakeRef(element.source->originalRef), "", + DefaultOutputs(), // FIXME Strings{element.source->attrPath}, Strings{}, lockFlags); diff --git a/tests/build.sh b/tests/build.sh index 339155991..ff16d1603 100644 --- a/tests/build.sh +++ b/tests/build.sh @@ -2,6 +2,8 @@ source common.sh clearStore +set -o pipefail + # Make sure that 'nix build' returns all outputs by default. nix build -f multiple-outputs.nix --json a b --no-link | jq --exit-status ' (.[0] | @@ -15,6 +17,45 @@ nix build -f multiple-outputs.nix --json a b --no-link | jq --exit-status ' (.outputs.out | match(".*multiple-outputs-b"))) ' +# Test output selection using the '^' syntax. +nix build -f multiple-outputs.nix --json a^first --no-link | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | keys == ["first"])) +' + +nix build -f multiple-outputs.nix --json a^second,first --no-link | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | keys == ["first", "second"])) +' + +nix build -f multiple-outputs.nix --json 'a^*' --no-link | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | keys == ["first", "second"])) +' + +# Test that 'outputsToInstall' is respected by default. +nix build -f multiple-outputs.nix --json e --no-link | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-e.drv")) and + (.outputs | keys == ["a", "b"])) +' + +# But not when it's overriden. +nix build -f multiple-outputs.nix --json e^a --no-link | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-e.drv")) and + (.outputs | keys == ["a"])) +' + +nix build -f multiple-outputs.nix --json 'e^*' --no-link | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-e.drv")) and + (.outputs | keys == ["a", "b", "c"])) +' + testNormalization () { clearStore outPath=$(nix-build ./simple.nix --no-out-link) diff --git a/tests/multiple-outputs.nix b/tests/multiple-outputs.nix index b915493f7..624a5dade 100644 --- a/tests/multiple-outputs.nix +++ b/tests/multiple-outputs.nix @@ -80,4 +80,11 @@ rec { ''; }).a; + e = mkDerivation { + name = "multiple-outputs-e"; + outputs = [ "a" "b" "c" ]; + meta.outputsToInstall = [ "a" "b" ]; + buildCommand = "mkdir $a $b $c"; + }; + } diff --git a/tests/shell-hello.nix b/tests/shell-hello.nix index 77dcbd2a9..3fdd3501d 100644 --- a/tests/shell-hello.nix +++ b/tests/shell-hello.nix @@ -3,15 +3,24 @@ with import ./config.nix; { hello = mkDerivation { name = "hello"; + outputs = [ "out" "dev" ]; + meta.outputsToInstall = [ "out" ]; buildCommand = '' - mkdir -p $out/bin + mkdir -p $out/bin $dev/bin + cat > $out/bin/hello < $dev/bin/hello2 < Date: Tue, 3 May 2022 14:37:28 +0200 Subject: [PATCH 63/79] nix profile: Support overriding outputs --- src/libstore/path-with-outputs.cc | 40 ++++++++++++++++++++++++++++ src/libstore/path-with-outputs.hh | 14 ++++++++-- src/libutil/experimental-features.cc | 6 +++-- src/libutil/experimental-features.hh | 4 +-- src/nix/profile-install.md | 7 +++++ src/nix/profile.cc | 30 ++++++++++++--------- tests/nix-profile.sh | 19 +++++++++++++ 7 files changed, 101 insertions(+), 19 deletions(-) diff --git a/src/libstore/path-with-outputs.cc b/src/libstore/path-with-outputs.cc index 7d180a0f6..d6d67ea05 100644 --- a/src/libstore/path-with-outputs.cc +++ b/src/libstore/path-with-outputs.cc @@ -1,5 +1,6 @@ #include "path-with-outputs.hh" #include "store-api.hh" +#include "nlohmann/json.hpp" #include @@ -84,4 +85,43 @@ std::pair parseOutputsSpec(const std::string & s) return {match[1], tokenizeString(match[4].str(), ",")}; } +std::string printOutputsSpec(const OutputsSpec & outputsSpec) +{ + if (std::get_if(&outputsSpec)) + return ""; + + if (std::get_if(&outputsSpec)) + return "^*"; + + if (auto outputNames = std::get_if(&outputsSpec)) + return "^" + concatStringsSep(",", *outputNames); + + assert(false); +} + +void to_json(nlohmann::json & json, const OutputsSpec & outputsSpec) +{ + if (std::get_if(&outputsSpec)) + json = nullptr; + + else if (std::get_if(&outputsSpec)) + json = std::vector({"*"}); + + else if (auto outputNames = std::get_if(&outputsSpec)) + json = *outputNames; +} + +void from_json(const nlohmann::json & json, OutputsSpec & outputsSpec) +{ + if (json.is_null()) + outputsSpec = DefaultOutputs(); + else { + auto names = json.get(); + if (names == OutputNames({"*"})) + outputsSpec = AllOutputs(); + else + outputsSpec = names; + } +} + } diff --git a/src/libstore/path-with-outputs.hh b/src/libstore/path-with-outputs.hh index e4235d197..0cb5eb223 100644 --- a/src/libstore/path-with-outputs.hh +++ b/src/libstore/path-with-outputs.hh @@ -4,6 +4,7 @@ #include "path.hh" #include "derived-path.hh" +#include "nlohmann/json_fwd.hpp" namespace nix { @@ -34,9 +35,13 @@ StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std: typedef std::set OutputNames; -struct AllOutputs { }; +struct AllOutputs { + bool operator < (const AllOutputs & _) const { return false; } +}; -struct DefaultOutputs { }; +struct DefaultOutputs { + bool operator < (const DefaultOutputs & _) const { return false; } +}; typedef std::variant OutputsSpec; @@ -44,4 +49,9 @@ typedef std::variant OutputsSpec; 'prefix^*', returning the prefix and the outputs spec. */ std::pair parseOutputsSpec(const std::string & s); +std::string printOutputsSpec(const OutputsSpec & outputsSpec); + +void to_json(nlohmann::json &, const OutputsSpec &); +void from_json(const nlohmann::json &, OutputsSpec &); + } diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index df37edf57..9326cd08c 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -58,11 +58,13 @@ std::ostream & operator <<(std::ostream & str, const ExperimentalFeature & featu return str << showExperimentalFeature(feature); } -void to_json(nlohmann::json& j, const ExperimentalFeature& feature) { +void to_json(nlohmann::json & j, const ExperimentalFeature & feature) +{ j = showExperimentalFeature(feature); } -void from_json(const nlohmann::json& j, ExperimentalFeature& feature) { +void from_json(const nlohmann::json & j, ExperimentalFeature & feature) +{ const std::string input = j; const auto parsed = parseExperimentalFeature(input); diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index a6d080094..57512830c 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -55,7 +55,7 @@ public: * Semi-magic conversion to and from json. * See the nlohmann/json readme for more details. */ -void to_json(nlohmann::json&, const ExperimentalFeature&); -void from_json(const nlohmann::json&, ExperimentalFeature&); +void to_json(nlohmann::json &, const ExperimentalFeature &); +void from_json(const nlohmann::json &, ExperimentalFeature &); } diff --git a/src/nix/profile-install.md b/src/nix/profile-install.md index e3009491e..aed414963 100644 --- a/src/nix/profile-install.md +++ b/src/nix/profile-install.md @@ -20,6 +20,13 @@ R""( # nix profile install nixpkgs/d73407e8e6002646acfdef0e39ace088bacc83da#hello ``` +* Install a specific output of a package: + + ```console + # nix profile install nixpkgs#bash^man + ``` + + # Description This command adds *installables* to a Nix profile. diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 78c8af80c..685776bec 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -22,13 +22,13 @@ struct ProfileElementSource // FIXME: record original attrpath. FlakeRef resolvedRef; std::string attrPath; - // FIXME: output names + OutputsSpec outputs; bool operator < (const ProfileElementSource & other) const { return - std::pair(originalRef.to_string(), attrPath) < - std::pair(other.originalRef.to_string(), other.attrPath); + std::tuple(originalRef.to_string(), attrPath, outputs) < + std::tuple(other.originalRef.to_string(), other.attrPath, other.outputs); } }; @@ -42,7 +42,7 @@ struct ProfileElement std::string describe() const { if (source) - return fmt("%s#%s", source->originalRef, source->attrPath); + return fmt("%s#%s%s", source->originalRef, source->attrPath, printOutputsSpec(source->outputs)); StringSet names; for (auto & path : storePaths) names.insert(DrvName(path.name()).name); @@ -98,7 +98,7 @@ struct ProfileManifest auto version = json.value("version", 0); std::string sUrl; std::string sOriginalUrl; - switch(version){ + switch (version) { case 1: sUrl = "uri"; sOriginalUrl = "originalUri"; @@ -116,11 +116,12 @@ struct ProfileManifest for (auto & p : e["storePaths"]) element.storePaths.insert(state.store->parseStorePath((std::string) p)); element.active = e["active"]; - if (e.value(sUrl,"") != "") { - element.source = ProfileElementSource{ + if (e.value(sUrl, "") != "") { + element.source = ProfileElementSource { parseFlakeRef(e[sOriginalUrl]), parseFlakeRef(e[sUrl]), - e["attrPath"] + e["attrPath"], + e["outputs"].get() }; } elements.emplace_back(std::move(element)); @@ -156,6 +157,7 @@ struct ProfileManifest obj["originalUrl"] = element.source->originalRef.to_string(); obj["url"] = element.source->resolvedRef.to_string(); obj["attrPath"] = element.source->attrPath; + obj["outputs"] = element.source->outputs; } array.push_back(obj); } @@ -283,10 +285,11 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile if (auto installable2 = std::dynamic_pointer_cast(installable)) { // FIXME: make build() return this? auto [attrPath, resolvedRef, drv] = installable2->toDerivation(); - element.source = ProfileElementSource{ + element.source = ProfileElementSource { installable2->flakeRef, resolvedRef, attrPath, + installable2->outputsSpec }; } @@ -443,7 +446,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf getEvalState(), FlakeRef(element.source->originalRef), "", - DefaultOutputs(), // FIXME + element.source->outputs, Strings{element.source->attrPath}, Strings{}, lockFlags); @@ -455,10 +458,11 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf printInfo("upgrading '%s' from flake '%s' to '%s'", element.source->attrPath, element.source->resolvedRef, resolvedRef); - element.source = ProfileElementSource{ + element.source = ProfileElementSource { installable->flakeRef, resolvedRef, attrPath, + installable->outputsSpec }; installables.push_back(installable); @@ -514,8 +518,8 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro for (size_t i = 0; i < manifest.elements.size(); ++i) { auto & element(manifest.elements[i]); logger->cout("%d %s %s %s", i, - element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath : "-", - element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath : "-", + element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-", + element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-", concatStringsSep(" ", store->printStorePathSet(element.storePaths))); } } diff --git a/tests/nix-profile.sh b/tests/nix-profile.sh index 814192252..f8da3d929 100644 --- a/tests/nix-profile.sh +++ b/tests/nix-profile.sh @@ -101,3 +101,22 @@ printf Utrecht > $flake1Dir/who nix profile install $flake1Dir [[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Utrecht" ]] [[ $(nix path-info --json $(realpath $TEST_HOME/.nix-profile/bin/hello) | jq -r .[].ca) =~ fixed:r:sha256: ]] + +# Override the outputs. +nix profile remove 0 1 +nix profile install "$flake1Dir^*" +[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Utrecht" ]] +[ -e $TEST_HOME/.nix-profile/share/man ] +[ -e $TEST_HOME/.nix-profile/include ] + +printf Nix > $flake1Dir/who +nix profile upgrade 0 +[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Nix" ]] +[ -e $TEST_HOME/.nix-profile/share/man ] +[ -e $TEST_HOME/.nix-profile/include ] + +nix profile remove 0 +nix profile install "$flake1Dir^man" +(! [ -e $TEST_HOME/.nix-profile/bin/hello ]) +[ -e $TEST_HOME/.nix-profile/share/man ] +(! [ -e $TEST_HOME/.nix-profile/include ]) From 1385b2007804c8a0370f2a6555045a00e34b07c7 Mon Sep 17 00:00:00 2001 From: Alain Zscheile Date: Wed, 4 May 2022 07:44:32 +0200 Subject: [PATCH 64/79] Get rid of most `.at` calls (#6393) Use one of `get` or `getOr` instead which will either return a null-pointer (with a nicer error message) or a default value when the key is missing. --- src/libcmd/installables.cc | 12 +- src/libexpr/flake/config.cc | 11 +- src/libexpr/flake/flakeref.cc | 6 +- src/libexpr/get-drvs.cc | 2 +- src/libexpr/primops.cc | 16 ++- src/libstore/build/derivation-goal.cc | 33 +++-- src/libstore/build/local-derivation-goal.cc | 115 ++++++++++-------- src/libstore/build/worker.cc | 5 +- src/libstore/builtins/fetchurl.cc | 2 +- src/libstore/derivations.cc | 13 +- src/libstore/derived-path.cc | 23 ++-- src/libstore/filetransfer.cc | 8 +- src/libstore/local-store.cc | 6 +- src/libstore/misc.cc | 9 +- src/libstore/remote-store.cc | 17 +-- src/libstore/store-api.cc | 2 +- src/libutil/experimental-features.cc | 4 +- src/libutil/tests/tests.cc | 20 ++- src/libutil/util.cc | 14 +++ src/libutil/util.hh | 27 +++- src/nix-build/nix-build.cc | 2 +- .../resolve-system-dependencies.cc | 2 +- 22 files changed, 231 insertions(+), 118 deletions(-) mode change 100755 => 100644 src/nix-build/nix-build.cc diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 55a5e91e9..a94e60aca 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -871,12 +871,13 @@ std::vector, 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( @@ -887,10 +888,11 @@ std::vector, BuiltPath>> Installable::bui } 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 }}); diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc index a811e59a1..92ec27046 100644 --- a/src/libexpr/flake/config.cc +++ b/src/libexpr/flake/config.cc @@ -50,13 +50,11 @@ void ConfigFile::apply() else assert(false); - if (!whitelist.count(baseName)) { - auto trustedList = readTrustedList(); - + if (!whitelist.count(baseName) && !nix::fetchSettings.acceptFlakeConfig) { bool trusted = false; - if (nix::fetchSettings.acceptFlakeConfig){ - trusted = true; - } else if (auto saved = get(get(trustedList, name).value_or(std::map()), valueS)) { + auto trustedList = readTrustedList(); + auto tlname = get(trustedList, name); + if (auto saved = tlname ? get(*tlname, valueS) : nullptr) { trusted = *saved; warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS); } else { @@ -69,7 +67,6 @@ void ConfigFile::apply() writeTrustedList(trustedList); } } - if (!trusted) { warn("ignoring untrusted flake configuration setting '%s'", name); continue; diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc index 1dcc4555a..eede493f8 100644 --- a/src/libexpr/flake/flakeref.cc +++ b/src/libexpr/flake/flakeref.cc @@ -176,7 +176,7 @@ std::pair parseFlakeRefWithFragment( parsedURL.query.insert_or_assign("shallow", "1"); return std::make_pair( - FlakeRef(Input::fromURL(parsedURL), get(parsedURL.query, "dir").value_or("")), + FlakeRef(Input::fromURL(parsedURL), getOr(parsedURL.query, "dir", "")), fragment); } @@ -189,7 +189,7 @@ std::pair parseFlakeRefWithFragment( if (!hasPrefix(path, "/")) throw BadURL("flake reference '%s' is not an absolute path", url); auto query = decodeQuery(match[2]); - path = canonPath(path + "/" + get(query, "dir").value_or("")); + path = canonPath(path + "/" + getOr(query, "dir", "")); } fetchers::Attrs attrs; @@ -208,7 +208,7 @@ std::pair parseFlakeRefWithFragment( input.parent = baseDir; return std::make_pair( - FlakeRef(std::move(input), get(parsedURL.query, "dir").value_or("")), + FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")), fragment); } } diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 11f2b279d..d616b3921 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -34,7 +34,7 @@ DrvInfo::DrvInfo(EvalState & state, ref store, const std::string & drvPat outputName = selectedOutputs.empty() - ? get(drv.env, "outputName").value_or("out") + ? getOr(drv.env, "outputName", "out") : *selectedOutputs.begin(); auto i = drv.outputs.find(outputName); diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 40e9f7091..a62d11e4e 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -68,14 +68,15 @@ StringMap EvalState::realiseContext(const PathSet & context) /* Get all the output paths corresponding to the placeholders we had */ for (auto & [drvPath, outputs] : drvs) { - auto outputPaths = store->queryDerivationOutputMap(drvPath); + const auto outputPaths = store->queryDerivationOutputMap(drvPath); for (auto & outputName : outputs) { - if (outputPaths.count(outputName) == 0) + auto outputPath = get(outputPaths, outputName); + if (!outputPath) throw Error("derivation '%s' does not have an output named '%s'", store->printStorePath(drvPath), outputName); res.insert_or_assign( downstreamPlaceholder(*store, drvPath, outputName), - store->printStorePath(outputPaths.at(outputName)) + store->printStorePath(*outputPath) ); } } @@ -1249,8 +1250,13 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * switch (hashModulo.kind) { case DrvHash::Kind::Regular: for (auto & i : outputs) { - auto h = hashModulo.hashes.at(i); - auto outPath = state.store->makeOutputPath(i, h, drvName); + auto h = get(hashModulo.hashes, i); + if (!h) + throw AssertionError({ + .msg = hintfmt("derivation produced no hash for output '%s'", i), + .errPos = state.positions[posDrvName], + }); + auto outPath = state.store->makeOutputPath(i, *h, drvName); drv.env[i] = state.store->printStorePath(outPath); drv.outputs.insert_or_assign( i, diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index d095a0f02..3fff2385f 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -984,21 +984,28 @@ void DerivationGoal::resolvedFinished() realWantedOutputs = resolvedDrv.outputNames(); for (auto & wantedOutput : realWantedOutputs) { - assert(initialOutputs.count(wantedOutput) != 0); - assert(resolvedHashes.count(wantedOutput) != 0); - auto realisation = resolvedResult.builtOutputs.at( - DrvOutput { resolvedHashes.at(wantedOutput), wantedOutput }); + auto initialOutput = get(initialOutputs, wantedOutput); + auto resolvedHash = get(resolvedHashes, wantedOutput); + if ((!initialOutput) || (!resolvedHash)) + throw Error( + "derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,resolve)", + worker.store.printStorePath(drvPath), wantedOutput); + auto realisation = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput }); + if (!realisation) + throw Error( + "derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,realisation)", + worker.store.printStorePath(resolvedDrvGoal->drvPath), wantedOutput); if (drv->type().isPure()) { - auto newRealisation = realisation; - newRealisation.id = DrvOutput { initialOutputs.at(wantedOutput).outputHash, wantedOutput }; + auto newRealisation = *realisation; + newRealisation.id = DrvOutput { initialOutput->outputHash, wantedOutput }; newRealisation.signatures.clear(); if (!drv->type().isFixed()) - newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath); + newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath); signRealisation(newRealisation); worker.store.registerDrvOutput(newRealisation); } - outputPaths.insert(realisation.outPath); - builtOutputs.emplace(realisation.id, realisation); + outputPaths.insert(realisation->outPath); + builtOutputs.emplace(realisation->id, *realisation); } runPostBuildHook( @@ -1294,7 +1301,11 @@ std::pair DerivationGoal::checkPathValidity() DrvOutputs validOutputs; for (auto & i : queryPartialDerivationOutputMap()) { - InitialOutput & info = initialOutputs.at(i.first); + auto initialOutput = get(initialOutputs, i.first); + if (!initialOutput) + // this is an invalid output, gets catched with (!wantedOutputsLeft.empty()) + continue; + auto & info = *initialOutput; info.wanted = wantOutput(i.first, wantedOutputs); if (info.wanted) wantedOutputsLeft.erase(i.first); @@ -1309,7 +1320,7 @@ std::pair DerivationGoal::checkPathValidity() : PathStatus::Corrupt, }; } - auto drvOutput = DrvOutput{initialOutputs.at(i.first).outputHash, i.first}; + auto drvOutput = DrvOutput{info.outputHash, i.first}; if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (auto real = worker.store.queryRealisation(drvOutput)) { info.known = { diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 4c91fa4fb..7f44276bd 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -482,7 +482,7 @@ void LocalDerivationGoal::startBuilder() temporary build directory. The text files have the format used by `nix-store --register-validity'. However, the deriver fields are left empty. */ - auto s = get(drv->env, "exportReferencesGraph").value_or(""); + auto s = getOr(drv->env, "exportReferencesGraph", ""); Strings ss = tokenizeString(s); if (ss.size() % 2 != 0) throw BuildError("odd number of tokens in 'exportReferencesGraph': '%1%'", s); @@ -989,7 +989,7 @@ void LocalDerivationGoal::initTmpDir() { there is no size constraint). */ if (!parsedDrv->getStructuredAttrs()) { - StringSet passAsFile = tokenizeString(get(drv->env, "passAsFile").value_or("")); + StringSet passAsFile = tokenizeString(getOr(drv->env, "passAsFile", "")); for (auto & i : drv->env) { if (passAsFile.find(i.first) == passAsFile.end()) { env[i.first] = i.second; @@ -2128,12 +2128,22 @@ DrvOutputs LocalDerivationGoal::registerOutputs() std::map> outputReferencesIfUnregistered; std::map outputStats; for (auto & [outputName, _] : drv->outputs) { - auto actualPath = toRealPathChroot(worker.store.printStorePath(scratchOutputs.at(outputName))); + auto scratchOutput = get(scratchOutputs, outputName); + if (!scratchOutput) + throw BuildError( + "builder for '%s' has no scratch output for '%s'", + worker.store.printStorePath(drvPath), outputName); + auto actualPath = toRealPathChroot(worker.store.printStorePath(*scratchOutput)); outputsToSort.insert(outputName); /* Updated wanted info to remove the outputs we definitely don't need to register */ - auto & initialInfo = initialOutputs.at(outputName); + auto initialOutput = get(initialOutputs, outputName); + if (!initialOutput) + throw BuildError( + "builder for '%s' has no initial output for '%s'", + worker.store.printStorePath(drvPath), outputName); + auto & initialInfo = *initialOutput; /* Don't register if already valid, and not checking */ initialInfo.wanted = buildMode == bmCheck @@ -2185,6 +2195,11 @@ DrvOutputs LocalDerivationGoal::registerOutputs() auto sortedOutputNames = topoSort(outputsToSort, {[&](const std::string & name) { + auto orifu = get(outputReferencesIfUnregistered, name); + if (!orifu) + throw BuildError( + "no output reference for '%s' in build of '%s'", + name, worker.store.printStorePath(drvPath)); return std::visit(overloaded { /* Since we'll use the already installed versions of these, we can treat them as leaves and ignore any references they @@ -2199,7 +2214,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() referencedOutputs.insert(o); return referencedOutputs; }, - }, outputReferencesIfUnregistered.at(name)); + }, *orifu); }}, {[&](const std::string & path, const std::string & parent) { // TODO with more -vvvv also show the temporary paths for manual inspection. @@ -2213,9 +2228,10 @@ DrvOutputs LocalDerivationGoal::registerOutputs() OutputPathMap finalOutputs; for (auto & outputName : sortedOutputNames) { - auto output = drv->outputs.at(outputName); - auto & scratchPath = scratchOutputs.at(outputName); - auto actualPath = toRealPathChroot(worker.store.printStorePath(scratchPath)); + auto output = get(drv->outputs, outputName); + auto scratchPath = get(scratchOutputs, outputName); + assert(output && scratchPath); + auto actualPath = toRealPathChroot(worker.store.printStorePath(*scratchPath)); auto finish = [&](StorePath finalStorePath) { /* Store the final path */ @@ -2223,10 +2239,13 @@ DrvOutputs LocalDerivationGoal::registerOutputs() /* The rewrite rule will be used in downstream outputs that refer to use. This is why the topological sort is essential to do first before this for loop. */ - if (scratchPath != finalStorePath) - outputRewrites[std::string { scratchPath.hashPart() }] = std::string { finalStorePath.hashPart() }; + if (*scratchPath != finalStorePath) + outputRewrites[std::string { scratchPath->hashPart() }] = std::string { finalStorePath.hashPart() }; }; + auto orifu = get(outputReferencesIfUnregistered, outputName); + assert(orifu); + std::optional referencesOpt = std::visit(overloaded { [&](const AlreadyRegistered & skippedFinalPath) -> std::optional { finish(skippedFinalPath.path); @@ -2235,7 +2254,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() [&](const PerhapsNeedToRegister & r) -> std::optional { return r.refs; }, - }, outputReferencesIfUnregistered.at(outputName)); + }, *orifu); if (!referencesOpt) continue; @@ -2268,25 +2287,29 @@ DrvOutputs LocalDerivationGoal::registerOutputs() for (auto & r : references) { auto name = r.name(); auto origHash = std::string { r.hashPart() }; - if (r == scratchPath) + if (r == *scratchPath) { res.first = true; - else if (outputRewrites.count(origHash) == 0) - res.second.insert(r); - else { - std::string newRef = outputRewrites.at(origHash); + } else if (auto outputRewrite = get(outputRewrites, origHash)) { + std::string newRef = *outputRewrite; newRef += '-'; newRef += name; res.second.insert(StorePath { newRef }); + } else { + res.second.insert(r); } } return res; }; auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo { - auto & st = outputStats.at(outputName); + auto st = get(outputStats, outputName); + if (!st) + throw BuildError( + "output path %1% without valid stats info", + actualPath); if (outputHash.method == FileIngestionMethod::Flat) { /* The output path should be a regular file without execute permission. */ - if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0) + if (!S_ISREG(st->st_mode) || (st->st_mode & S_IXUSR) != 0) throw BuildError( "output path '%1%' should be a non-executable regular file " "since recursive hashing is not enabled (outputHashMode=flat)", @@ -2294,7 +2317,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() } rewriteOutput(); /* FIXME optimize and deduplicate with addToStore */ - std::string oldHashPart { scratchPath.hashPart() }; + std::string oldHashPart { scratchPath->hashPart() }; HashModuloSink caSink { outputHash.hashType, oldHashPart }; switch (outputHash.method) { case FileIngestionMethod::Recursive: @@ -2313,7 +2336,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() outputPathName(drv->name, outputName), refs.second, refs.first); - if (scratchPath != finalPath) { + if (*scratchPath != finalPath) { // Also rewrite the output path auto source = sinkToSource([&](Sink & nextSink) { StringSink sink; @@ -2354,9 +2377,9 @@ DrvOutputs LocalDerivationGoal::registerOutputs() auto requiredFinalPath = output.path; /* Preemptively add rewrite rule for final hash, as that is what the NAR hash will use rather than normalized-self references */ - if (scratchPath != requiredFinalPath) + if (*scratchPath != requiredFinalPath) outputRewrites.insert_or_assign( - std::string { scratchPath.hashPart() }, + std::string { scratchPath->hashPart() }, std::string { requiredFinalPath.hashPart() }); rewriteOutput(); auto narHashAndSize = hashPath(htSHA256, actualPath); @@ -2409,7 +2432,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() }); }, - }, output.raw()); + }, output->raw()); /* FIXME: set proper permissions in restorePath() so we don't have to do another traversal. */ @@ -2425,7 +2448,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() derivations. */ PathLocks dynamicOutputLock; dynamicOutputLock.setDeletion(true); - auto optFixedPath = output.path(worker.store, drv->name, outputName); + auto optFixedPath = output->path(worker.store, drv->name, outputName); if (!optFixedPath || worker.store.printStorePath(*optFixedPath) != finalDestPath) { @@ -2491,11 +2514,10 @@ DrvOutputs LocalDerivationGoal::registerOutputs() /* For debugging, print out the referenced and unreferenced paths. */ for (auto & i : inputPaths) { - auto j = references.find(i); - if (j == references.end()) - debug("unreferenced input: '%1%'", worker.store.printStorePath(i)); - else + if (references.count(i)) debug("referenced input: '%1%'", worker.store.printStorePath(i)); + else + debug("unreferenced input: '%1%'", worker.store.printStorePath(i)); } if (curRound == nrRounds) { @@ -2612,9 +2634,11 @@ DrvOutputs LocalDerivationGoal::registerOutputs() DrvOutputs builtOutputs; for (auto & [outputName, newInfo] : infos) { + auto oldinfo = get(initialOutputs, outputName); + assert(oldinfo); auto thisRealisation = Realisation { .id = DrvOutput { - initialOutputs.at(outputName).outputHash, + oldinfo->outputHash, outputName }, .outPath = newInfo.path @@ -2710,9 +2734,10 @@ void LocalDerivationGoal::checkOutputs(const std::mappath); + else + throw BuildError("derivation contains an illegal reference specifier '%s'", i); } auto used = recursive @@ -2751,24 +2776,18 @@ void LocalDerivationGoal::checkOutputs(const std::mapgetStructuredAttrs()) { - auto outputChecks = structuredAttrs->find("outputChecks"); - if (outputChecks != structuredAttrs->end()) { - auto output = outputChecks->find(outputName); - - if (output != outputChecks->end()) { + if (auto outputChecks = get(*structuredAttrs, "outputChecks")) { + if (auto output = get(*outputChecks, outputName)) { Checks checks; - auto maxSize = output->find("maxSize"); - if (maxSize != output->end()) + if (auto maxSize = get(*output, "maxSize")) checks.maxSize = maxSize->get(); - auto maxClosureSize = output->find("maxClosureSize"); - if (maxClosureSize != output->end()) + if (auto maxClosureSize = get(*output, "maxClosureSize")) checks.maxClosureSize = maxClosureSize->get(); - auto get = [&](const std::string & name) -> std::optional { - auto i = output->find(name); - if (i != output->end()) { + auto get_ = [&](const std::string & name) -> std::optional { + if (auto i = get(*output, name)) { Strings res; for (auto j = i->begin(); j != i->end(); ++j) { if (!j->is_string()) @@ -2781,10 +2800,10 @@ void LocalDerivationGoal::checkOutputs(const std::map fds2(j->fds); std::vector buffer(4096); for (auto & k : fds2) { - if (pollStatus.at(fdToPollStatus.at(k)).revents) { + const auto fdPollStatusId = get(fdToPollStatus, k); + assert(fdPollStatusId); + assert(*fdPollStatusId < pollStatus.size()); + if (pollStatus.at(*fdPollStatusId).revents) { ssize_t rd = ::read(k, buffer.data(), buffer.size()); // FIXME: is there a cleaner way to handle pt close // than EIO? Is this even standard? diff --git a/src/libstore/builtins/fetchurl.cc b/src/libstore/builtins/fetchurl.cc index af3dfc409..7d7924d77 100644 --- a/src/libstore/builtins/fetchurl.cc +++ b/src/libstore/builtins/fetchurl.cc @@ -24,7 +24,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData) Path storePath = getAttr("out"); auto mainUrl = getAttr("url"); - bool unpack = get(drv.env, "unpack").value_or("") == "1"; + bool unpack = getOr(drv.env, "unpack", "") == "1"; /* Note: have to use a fresh fileTransfer here because we're in a forked process. */ diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 1c695de82..fe99c3c5e 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -661,8 +661,10 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut if (res.kind == DrvHash::Kind::Deferred) kind = DrvHash::Kind::Deferred; for (auto & outputName : inputOutputs) { - const auto h = res.hashes.at(outputName); - inputs2[h.to_string(Base16, false)].insert(outputName); + const auto h = get(res.hashes, outputName); + if (!h) + throw Error("no hash for output '%s' of derivation '%s'", outputName, drv.name); + inputs2[h->to_string(Base16, false)].insert(outputName); } } @@ -836,8 +838,11 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String auto hashModulo = hashDerivationModulo(store, Derivation(drv), true); for (auto & [outputName, output] : drv.outputs) { if (std::holds_alternative(output.raw())) { - auto & h = hashModulo.hashes.at(outputName); - auto outPath = store.makeOutputPath(outputName, h, drv.name); + auto h = get(hashModulo.hashes, outputName); + if (!h) + throw Error("derivation '%s' output '%s' has no hash (derivations.cc/rewriteDerivation)", + drv.name, outputName); + auto outPath = store.makeOutputPath(outputName, *h, drv.name); drv.env[outputName] = store.printStorePath(outPath); output = DerivationOutput::InputAddressed { .path = std::move(outPath), diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 319b1c790..44587ae78 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -4,6 +4,8 @@ #include +#include + namespace nix { nlohmann::json DerivedPath::Opaque::toJSON(ref store) const { @@ -17,12 +19,12 @@ nlohmann::json DerivedPath::Built::toJSON(ref store) const { res["drvPath"] = store->printStorePath(drvPath); // Fallback for the input-addressed derivation case: We expect to always be // able to print the output paths, so let’s do it - auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath); + const auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath); for (const auto& output : outputs) { - if (knownOutputs.at(output)) - res["outputs"][output] = store->printStorePath(knownOutputs.at(output).value()); - else - res["outputs"][output] = nullptr; + auto knownOutput = get(knownOutputs, output); + res["outputs"][output] = (knownOutput && *knownOutput) + ? store->printStorePath(**knownOutput) + : nullptr; } return res; } @@ -123,10 +125,15 @@ RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const for (auto& [outputName, outputPath] : p.outputs) { if (settings.isExperimentalFeatureEnabled( Xp::CaDerivations)) { + auto drvOutput = get(drvHashes, outputName); + if (!drvOutput) + throw Error( + "the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)", + store.printStorePath(p.drvPath), outputName); auto thisRealisation = store.queryRealisation( - DrvOutput{drvHashes.at(outputName), outputName}); - assert(thisRealisation); // We’ve built it, so we must h - // ve the realisation + DrvOutput{*drvOutput, outputName}); + assert(thisRealisation); // We’ve built it, so we must + // have the realisation res.insert(*thisRealisation); } else { res.insert(outputPath); diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index 529a41891..8454ad7d2 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -692,10 +692,10 @@ struct curlFileTransfer : public FileTransfer #if ENABLE_S3 auto [bucketName, key, params] = parseS3Uri(request.uri); - std::string profile = get(params, "profile").value_or(""); - std::string region = get(params, "region").value_or(Aws::Region::US_EAST_1); - std::string scheme = get(params, "scheme").value_or(""); - std::string endpoint = get(params, "endpoint").value_or(""); + std::string profile = getOr(params, "profile", ""); + std::string region = getOr(params, "region", Aws::Region::US_EAST_1); + std::string scheme = getOr(params, "scheme", ""); + std::string endpoint = getOr(params, "endpoint", ""); S3Helper s3Helper(profile, region, scheme, endpoint); diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 5cc5c91cc..eba3b0fa5 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -718,7 +718,11 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat // somewhat expensive so we do lazily hashesModulo = hashDerivationModulo(*this, drv, true); } - StorePath recomputed = makeOutputPath(i.first, hashesModulo->hashes.at(i.first), drvName); + auto currentOutputHash = get(hashesModulo->hashes, i.first); + if (!currentOutputHash) + throw Error("derivation '%s' has unexpected output '%s' (local-store / hashesModulo) named '%s'", + printStorePath(drvPath), printStorePath(doia.path), i.first); + StorePath recomputed = makeOutputPath(i.first, *currentOutputHash, drvName); if (doia.path != recomputed) throw Error("derivation '%s' has incorrect output '%s', should be '%s'", printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed)); diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc index 2bbd7aa70..fb985c97b 100644 --- a/src/libstore/misc.cc +++ b/src/libstore/misc.cc @@ -278,11 +278,16 @@ std::map drvOutputReferences( std::set inputRealisations; for (const auto & [inputDrv, outputNames] : drv.inputDrvs) { - auto outputHashes = + const auto outputHashes = staticOutputHashes(store, store.readDerivation(inputDrv)); for (const auto & outputName : outputNames) { + auto outputHash = get(outputHashes, outputName); + if (!outputHash) + throw Error( + "output '%s' of derivation '%s' isn't realised", outputName, + store.printStorePath(inputDrv)); auto thisRealisation = store.queryRealisation( - DrvOutput{outputHashes.at(outputName), outputName}); + DrvOutput{*outputHash, outputName}); if (!thisRealisation) throw Error( "output '%s' of derivation '%s' isn't built", outputName, diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index 347e32094..14aeba75c 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -853,15 +853,15 @@ std::vector RemoteStore::buildPathsWithResults( OutputPathMap outputs; auto drv = evalStore->readDerivation(bfd.drvPath); - auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive - auto drvOutputs = drv.outputsAndOptPaths(*this); + const auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive + const auto drvOutputs = drv.outputsAndOptPaths(*this); 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'", printStorePath(bfd.drvPath), output); - auto outputId = - DrvOutput{outputHashes.at(output), output}; + auto outputId = DrvOutput{ *outputHash, output }; if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { auto realisation = queryRealisation(outputId); @@ -874,13 +874,14 @@ std::vector RemoteStore::buildPathsWithResults( } else { // If ca-derivations isn't enabled, assume that // the output path is statically known. - assert(drvOutputs.count(output)); - assert(drvOutputs.at(output).second); + const auto drvOutput = get(drvOutputs, output); + assert(drvOutput); + assert(drvOutput->second); res.builtOutputs.emplace( outputId, Realisation { .id = outputId, - .outPath = *drvOutputs.at(output).second + .outPath = *drvOutput->second, }); } } diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 59937be4d..8861274a2 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -1314,7 +1314,7 @@ static bool isNonUriPath(const std::string & spec) { std::shared_ptr openFromNonUri(const std::string & uri, const Store::Params & params) { if (uri == "" || uri == "auto") { - auto stateDir = get(params, "state").value_or(settings.nixStateDir); + auto stateDir = getOr(params, "state", settings.nixStateDir); if (access(stateDir.c_str(), R_OK | W_OK) == 0) return std::make_shared(params); else if (pathExists(settings.nixDaemonSocketFile)) diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index 9326cd08c..315de64a4 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -35,7 +35,9 @@ const std::optional parseExperimentalFeature(const std::str std::string_view showExperimentalFeature(const ExperimentalFeature feature) { - return stringifiedXpFeatures.at(feature); + const auto ret = get(stringifiedXpFeatures, feature); + assert(ret); + return *ret; } std::set parseFeatures(const std::set & rawFeatures) diff --git a/src/libutil/tests/tests.cc b/src/libutil/tests/tests.cc index 92972ed14..6e325db98 100644 --- a/src/libutil/tests/tests.cc +++ b/src/libutil/tests/tests.cc @@ -548,7 +548,7 @@ namespace nix { TEST(get, emptyContainer) { StringMap s = { }; - auto expected = std::nullopt; + auto expected = nullptr; ASSERT_EQ(get(s, "one"), expected); } @@ -559,7 +559,23 @@ namespace nix { s["two"] = "er"; auto expected = "yi"; - ASSERT_EQ(get(s, "one"), expected); + ASSERT_EQ(*get(s, "one"), expected); + } + + TEST(getOr, emptyContainer) { + StringMap s = { }; + auto expected = "yi"; + + ASSERT_EQ(getOr(s, "one", "yi"), expected); + } + + TEST(getOr, getFromContainer) { + StringMap s; + s["one"] = "yi"; + s["two"] = "er"; + auto expected = "yi"; + + ASSERT_EQ(getOr(s, "one", "nope"), expected); } /* ---------------------------------------------------------------------------- diff --git a/src/libutil/util.cc b/src/libutil/util.cc index b49c1e466..8bc99f531 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1586,6 +1586,20 @@ std::string stripIndentation(std::string_view s) } +const nlohmann::json * get(const nlohmann::json & map, const std::string & key) +{ + auto i = map.find(key); + if (i == map.end()) return nullptr; + return &*i; +} + +nlohmann::json * get(nlohmann::json & map, const std::string & key) +{ + auto i = map.find(key); + if (i == map.end()) return nullptr; + return &*i; +} + ////////////////////////////////////////////////////////////////////// diff --git a/src/libutil/util.hh b/src/libutil/util.hh index a1d0e0e6b..6319ced47 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -543,13 +543,34 @@ std::string stripIndentation(std::string_view s); /* Get a value for the specified key from an associate container. */ template -std::optional get(const T & map, const typename T::key_type & key) +const typename T::mapped_type * get(const T & map, const typename T::key_type & key) { auto i = map.find(key); - if (i == map.end()) return {}; - return std::optional(i->second); + if (i == map.end()) return nullptr; + return &i->second; } +template +typename T::mapped_type * get(T & map, const typename T::key_type & key) +{ + auto i = map.find(key); + if (i == map.end()) return nullptr; + return &i->second; +} + +const nlohmann::json * get(const nlohmann::json & map, const std::string & key); +nlohmann::json * get(nlohmann::json & map, const std::string & key); + +/* Get a value for the specified key from an associate container, or a default value if the key isn't present. */ +template +const typename T::mapped_type & getOr(T & map, + const typename T::key_type & key, + const typename T::mapped_type & defaultValue) +{ + auto i = map.find(key); + if (i == map.end()) return defaultValue; + return i->second; +} /* Remove and return the first item from a container. */ template diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc old mode 100755 new mode 100644 index faa8c078f..519855ea3 --- a/src/nix-build/nix-build.cc +++ b/src/nix-build/nix-build.cc @@ -440,7 +440,7 @@ static void main_nix_build(int argc, char * * argv) env["NIX_STORE"] = store->storeDir; env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores); - auto passAsFile = tokenizeString(get(drv.env, "passAsFile").value_or("")); + auto passAsFile = tokenizeString(getOr(drv.env, "passAsFile", "")); bool keepTmp = false; int fileNr = 0; diff --git a/src/resolve-system-dependencies/resolve-system-dependencies.cc b/src/resolve-system-dependencies/resolve-system-dependencies.cc index 4dd691981..c6023eb03 100644 --- a/src/resolve-system-dependencies/resolve-system-dependencies.cc +++ b/src/resolve-system-dependencies/resolve-system-dependencies.cc @@ -176,7 +176,7 @@ int main(int argc, char ** argv) impurePaths.insert(argv[2]); else { auto drv = store->derivationFromPath(store->parseStorePath(argv[1])); - impurePaths = tokenizeString(get(drv.env, "__impureHostDeps").value_or("")); + impurePaths = tokenizeString(getOr(drv.env, "__impureHostDeps", "")); impurePaths.insert("/usr/lib/libSystem.dylib"); } From 3e87c8e62b3eacecde4e9f467b5463fbbdaa6a3f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 4 May 2022 11:22:06 +0200 Subject: [PATCH 65/79] Move json stuff out of util.cc --- src/libstore/build/local-derivation-goal.cc | 3 +-- src/libutil/json-utils.hh | 21 +++++++++++++++++++++ src/libutil/util.cc | 15 --------------- src/libutil/util.hh | 3 --- 4 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 src/libutil/json-utils.hh diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 7f44276bd..3ac9c20f9 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -14,6 +14,7 @@ #include "worker-protocol.hh" #include "topo-sort.hh" #include "callback.hh" +#include "json-utils.hh" #include #include @@ -56,8 +57,6 @@ #include #include -#include - namespace nix { void handleDiffHook( diff --git a/src/libutil/json-utils.hh b/src/libutil/json-utils.hh new file mode 100644 index 000000000..b8a031227 --- /dev/null +++ b/src/libutil/json-utils.hh @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace nix { + +const nlohmann::json * get(const nlohmann::json & map, const std::string & key) +{ + auto i = map.find(key); + if (i == map.end()) return nullptr; + return &*i; +} + +nlohmann::json * get(nlohmann::json & map, const std::string & key) +{ + auto i = map.find(key); + if (i == map.end()) return nullptr; + return &*i; +} + +} diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 8bc99f531..d4d78329d 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1586,23 +1586,8 @@ std::string stripIndentation(std::string_view s) } -const nlohmann::json * get(const nlohmann::json & map, const std::string & key) -{ - auto i = map.find(key); - if (i == map.end()) return nullptr; - return &*i; -} - -nlohmann::json * get(nlohmann::json & map, const std::string & key) -{ - auto i = map.find(key); - if (i == map.end()) return nullptr; - return &*i; -} - ////////////////////////////////////////////////////////////////////// - static Sync> windowSize{{0, 0}}; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 6319ced47..09ccfa591 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -558,9 +558,6 @@ typename T::mapped_type * get(T & map, const typename T::key_type & key) return &i->second; } -const nlohmann::json * get(const nlohmann::json & map, const std::string & key); -nlohmann::json * get(nlohmann::json & map, const std::string & key); - /* Get a value for the specified key from an associate container, or a default value if the key isn't present. */ template const typename T::mapped_type & getOr(T & map, From 107613ad2b2b61ef92edf9ee53ec71ad664be71b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 4 May 2022 11:31:39 +0200 Subject: [PATCH 66/79] Fix compiler warning --- src/libstore/build/worker.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc index e7c01a737..b192fbc77 100644 --- a/src/libstore/build/worker.cc +++ b/src/libstore/build/worker.cc @@ -350,7 +350,7 @@ void Worker::waitForInput() become `available'. Note that `available' (i.e., non-blocking) includes EOF. */ std::vector pollStatus; - std::map fdToPollStatus; + std::map fdToPollStatus; for (auto & i : children) { for (auto & j : i.fds) { pollStatus.push_back((struct pollfd) { .fd = j, .events = POLLIN }); From e68676e6c859815f40079b6340399d82cc1913b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Wed, 4 May 2022 14:32:21 +0200 Subject: [PATCH 67/79] Fix the parsing of the sourcehut refs file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since a26be9f3b89be2ee90c6358250b9889b37f95cf8, the same parser is used to parse the result of sourcehut’s `HEAD` endpoint (coming from [git dumb protocol]) and the output of `git ls-remote`. However, they are very slightly different (the former doesn’t specify the current reference since it’s implied to be `HEAD`). Unify both, and make the parser a bit more robust and understandable (by making it more typed and adding tests for it) [git dumb protocol]: https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols#_the_dumb_protocol --- src/libfetchers/git-utils.cc | 27 ------------------------ src/libfetchers/git-utils.hh | 23 --------------------- src/libfetchers/git.cc | 19 +++++++++-------- src/libfetchers/github.cc | 12 ++++++----- src/libutil/git.cc | 25 ++++++++++++++++++++++ src/libutil/git.hh | 40 ++++++++++++++++++++++++++++++++++++ src/libutil/tests/git.cc | 33 +++++++++++++++++++++++++++++ 7 files changed, 116 insertions(+), 63 deletions(-) delete mode 100644 src/libfetchers/git-utils.cc delete mode 100644 src/libfetchers/git-utils.hh create mode 100644 src/libutil/git.cc create mode 100644 src/libutil/git.hh create mode 100644 src/libutil/tests/git.cc diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc deleted file mode 100644 index b2d6b7893..000000000 --- a/src/libfetchers/git-utils.cc +++ /dev/null @@ -1,27 +0,0 @@ -#include "git-utils.hh" - -#include - -std::optional parseListReferenceHeadRef(std::string_view line) -{ - const static std::regex head_ref_regex("^ref: ([^\\s]+)\\t+HEAD$"); - std::match_results match; - if (std::regex_match(line.cbegin(), line.cend(), match, head_ref_regex)) { - return match[1]; - } else { - return std::nullopt; - } -} - -std::optional parseListReferenceForRev(std::string_view rev, std::string_view line) -{ - const static std::regex rev_regex("^([^\\t]+)\\t+(.*)$"); - std::match_results match; - if (!std::regex_match(line.cbegin(), line.cend(), match, rev_regex)) { - return std::nullopt; - } - if (rev != match[2].str()) { - return std::nullopt; - } - return match[1]; -} diff --git a/src/libfetchers/git-utils.hh b/src/libfetchers/git-utils.hh deleted file mode 100644 index 946a68a9e..000000000 --- a/src/libfetchers/git-utils.hh +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include -#include - -// Parses the HEAD ref as reported by `git ls-remote --symref` -// -// Returns the head branch name as reported by `git ls-remote --symref`, e.g., if -// ls-remote returns the output below, "main" is returned based on the ref line. -// -// ref: refs/heads/main HEAD -// -// If the repository is in 'detached head' state (HEAD is pointing to a rev -// instead of a branch), parseListReferenceForRev("HEAD") may be used instead. -std::optional parseListReferenceHeadRef(std::string_view line); - -// Parses a reference line from `git ls-remote --symref`, e.g., -// parseListReferenceForRev("refs/heads/master", line) will return 6926... -// given the line below. -// -// 6926beab444c33fb57b21819b6642d032016bb1e refs/heads/master -std::optional parseListReferenceForRev(std::string_view rev, std::string_view line); diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 266246fe9..d23a820a4 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -6,7 +6,7 @@ #include "url-parts.hh" #include "pathlocks.hh" #include "util.hh" -#include "git-utils.hh" +#include "git.hh" #include "fetch-settings.hh" @@ -72,13 +72,16 @@ std::optional readHead(const Path & path) std::string_view line = output; line = line.substr(0, line.find("\n")); - if (const auto ref = parseListReferenceHeadRef(line); ref) { - debug("resolved HEAD ref '%s' for repo '%s'", *ref, path); - return *ref; - } - if (const auto rev = parseListReferenceForRev("HEAD", line); rev) { - debug("resolved HEAD rev '%s' for repo '%s'", *rev, path); - return *rev; + if (const auto parseResult = git::parseLsRemoteLine(line)) { + switch (parseResult->kind) { + case git::LsRemoteRefLine::Kind::Symbolic: + debug("resolved HEAD ref '%s' for repo '%s'", parseResult->target, path); + break; + case git::LsRemoteRefLine::Kind::Object: + debug("resolved HEAD rev '%s' for repo '%s'", parseResult->target, path); + break; + } + return parseResult->target; } return std::nullopt; } diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index 1bdf2759f..a1084c984 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -4,7 +4,7 @@ #include "store-api.hh" #include "types.hh" #include "url-parts.hh" -#include "git-utils.hh" +#include "git.hh" #include "fetchers.hh" #include "fetch-settings.hh" @@ -383,11 +383,11 @@ struct SourceHutInputScheme : GitArchiveInputScheme std::string line; getline(is, line); - auto r = parseListReferenceHeadRef(line); - if (!r) { + auto remoteLine = git::parseLsRemoteLine(line); + if (!remoteLine) { throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref); } - ref_uri = *r; + ref_uri = remoteLine->target; } else { ref_uri = fmt("refs/(heads|tags)/%s", ref); } @@ -399,7 +399,9 @@ struct SourceHutInputScheme : GitArchiveInputScheme std::string line; std::optional id; while(!id && getline(is, line)) { - id = parseListReferenceForRev(ref_uri, line); + auto parsedLine = git::parseLsRemoteLine(line); + if (parsedLine && parsedLine->reference == ref_uri) + id = parsedLine->target; } if(!id) diff --git a/src/libutil/git.cc b/src/libutil/git.cc new file mode 100644 index 000000000..f35c2fdb7 --- /dev/null +++ b/src/libutil/git.cc @@ -0,0 +1,25 @@ +#include "git.hh" + +#include + +namespace nix { +namespace git { + +std::optional parseLsRemoteLine(std::string_view line) +{ + const static std::regex line_regex("^(ref: *)?([^\\s]+)(?:\\t+(.*))?$"); + std::match_results match; + if (!std::regex_match(line.cbegin(), line.cend(), match, line_regex)) + return std::nullopt; + + return LsRemoteRefLine { + .kind = match[1].length() == 0 + ? LsRemoteRefLine::Kind::Object + : LsRemoteRefLine::Kind::Symbolic, + .target = match[2], + .reference = match[3].length() == 0 ? std::nullopt : std::optional{ match[3] } + }; +} + +} +} diff --git a/src/libutil/git.hh b/src/libutil/git.hh new file mode 100644 index 000000000..cb13ef0e5 --- /dev/null +++ b/src/libutil/git.hh @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace nix { + +namespace git { + +// A line from the output of `git ls-remote --symref`. +// +// These can be of two kinds: +// +// - Symbolic references of the form +// +// ref: {target} {reference} +// +// where {target} is itself a reference and {reference} is optional +// +// - Object references of the form +// +// {target} {reference} +// +// where {target} is a commit id and {reference} is mandatory +struct LsRemoteRefLine { + enum struct Kind { + Symbolic, + Object + }; + Kind kind; + std::string target; + std::optional reference; +}; + +std::optional parseLsRemoteLine(std::string_view line); + +} + +} diff --git a/src/libutil/tests/git.cc b/src/libutil/tests/git.cc new file mode 100644 index 000000000..5b5715fc2 --- /dev/null +++ b/src/libutil/tests/git.cc @@ -0,0 +1,33 @@ +#include "git.hh" +#include + +namespace nix { + + TEST(GitLsRemote, parseSymrefLineWithReference) { + auto line = "ref: refs/head/main HEAD"; + auto res = git::parseLsRemoteLine(line); + ASSERT_TRUE(res.has_value()); + ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic); + ASSERT_EQ(res->target, "refs/head/main"); + ASSERT_EQ(res->reference, "HEAD"); + } + + TEST(GitLsRemote, parseSymrefLineWithNoReference) { + auto line = "ref: refs/head/main"; + auto res = git::parseLsRemoteLine(line); + ASSERT_TRUE(res.has_value()); + ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic); + ASSERT_EQ(res->target, "refs/head/main"); + ASSERT_EQ(res->reference, std::nullopt); + } + + TEST(GitLsRemote, parseObjectRefLine) { + auto line = "abc123 refs/head/main"; + auto res = git::parseLsRemoteLine(line); + ASSERT_TRUE(res.has_value()); + ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Object); + ASSERT_EQ(res->target, "abc123"); + ASSERT_EQ(res->reference, "refs/head/main"); + } +} + From b3ed32d0fd3dedd1428c069d20a35e1f8a26d566 Mon Sep 17 00:00:00 2001 From: Alexander Shpilkin Date: Wed, 4 May 2022 22:13:49 +0300 Subject: [PATCH 68/79] Add forgotten null check --- src/libexpr/eval-cache.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index a1cb162ee..0eb4bc79e 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -648,7 +648,8 @@ std::vector AttrCursor::getListOfStrings() for (auto & elem : v.listItems()) res.push_back(std::string(root->state.forceStringNoCtx(*elem))); - cachedValue = {root->db->setListOfStrings(getKey(), res), res}; + if (root->db) + cachedValue = {root->db->setListOfStrings(getKey(), res), res}; return res; } From 7388cd55bfec2bf11b8ae4f26660000d7ab75e62 Mon Sep 17 00:00:00 2001 From: thkoch2001 <94641+thkoch2001@users.noreply.github.com> Date: Wed, 4 May 2022 16:41:29 +0300 Subject: [PATCH 69/79] Change json example to be original Closes: #3391 --- tests/lang/eval-okay-fromjson.nix | 49 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/tests/lang/eval-okay-fromjson.nix b/tests/lang/eval-okay-fromjson.nix index 102ee82b5..e1c0f86cc 100644 --- a/tests/lang/eval-okay-fromjson.nix +++ b/tests/lang/eval-okay-fromjson.nix @@ -1,36 +1,35 @@ -# RFC 7159, section 13. builtins.fromJSON '' { - "Image": { - "Width": 800, - "Height": 600, - "Title": "View from 15th Floor", - "Thumbnail": { - "Url": "http://www.example.com/image/481989943", - "Height": 125, - "Width": 100 + "Video": { + "Title": "The Penguin Chronicles", + "Width": 1920, + "Height": 1080, + "EmbeddedData": [3.14159, 23493,null, true ,false, -10], + "Thumb": { + "Url": "http://www.example.com/video/5678931", + "Width": 200, + "Height": 250 }, - "Animated" : false, - "IDs": [116, 943, 234, 38793, true ,false,null, -100], - "Latitude": 37.7668, - "Longitude": -122.3959 + "Subtitle" : false, + "Latitude": 46.2051, + "Longitude": 6.0723 } } '' == - { Image = - { Width = 800; - Height = 600; - Title = "View from 15th Floor"; - Thumbnail = - { Url = http://www.example.com/image/481989943; - Height = 125; - Width = 100; + { Video = + { Title = "The Penguin Chronicles"; + Width = 1920; + Height = 1080; + EmbeddedData = [ 3.14159 23493 null true false (0-10) ]; + Thumb = + { Url = "http://www.example.com/video/5678931"; + Width = 200; + Height = 250; }; - Animated = false; - IDs = [ 116 943 234 38793 true false null (0-100) ]; - Latitude = 37.7668; - Longitude = -122.3959; + Subtitle = false; + Latitude = 46.2051; + Longitude = 6.0723; }; } From 4c5aa8520c501cae904b9dc3a2c1fc90d2f0af83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Thu, 5 May 2022 14:53:59 +0200 Subject: [PATCH 70/79] Make sure that `nix build` works in `--impure` mode Regression test for --- tests/build.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/build.sh b/tests/build.sh index ff16d1603..fc6825e25 100644 --- a/tests/build.sh +++ b/tests/build.sh @@ -56,6 +56,13 @@ nix build -f multiple-outputs.nix --json 'e^*' --no-link | jq --exit-status ' (.outputs | keys == ["a", "b", "c"])) ' +# Make sure that `--impure` works (regression test for https://github.com/NixOS/nix/issues/6488) +nix build --impure -f multiple-outputs.nix --json e --no-link | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-e.drv")) and + (.outputs | keys == ["a", "b"])) +' + testNormalization () { clearStore outPath=$(nix-build ./simple.nix --no-out-link) From b470218d9a89bc29116853260a01af6cab450dae Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 6 May 2022 13:14:49 +0200 Subject: [PATCH 71/79] renderMarkdownToTerminal(): Avoid line overflow Lowdown doesn't respect '.cols' exactly (maybe because of the whitespace in front of each line), so adjust .cols a bit. --- src/libcmd/markdown.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libcmd/markdown.cc b/src/libcmd/markdown.cc index 29bb4d31e..71f9c8dff 100644 --- a/src/libcmd/markdown.cc +++ b/src/libcmd/markdown.cc @@ -9,10 +9,12 @@ 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, From 059ae7f6c4b491d728714207c082a03d94c06744 Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Fri, 6 May 2022 18:05:27 +0200 Subject: [PATCH 72/79] Add unit tests for libexpr (#5377) * libexpr: fix builtins.split example The example was previously indicating that multiple whitespaces would be collapsed into a single captured whitespace. That isn't true and was likely a mistake when being documented initially. * Fix segfault on unitilized list when printing value Since lists are just chunks of memory the individual elements in the list might be unitilized when a programming error happens within Nix. In this case the values are null-initialized (at least with Boehm GC) and we can avoid a nullptr deref when printing them. I ran into this issue while ensuring that new expression tests would show the actual value on an assertion failure. This is unlikely to cause any runtime performance regressions as printing values is not really in the hot path (unless the repl is the primary use case). * Add operator<< for ValueTypes * Add libexpr tests This introduces tests for libexpr that evalulate various trivial Nix language expressions and primop invocations that should be good smoke tests wheter or not the implementation is behaving as expected. --- .gitignore | 1 + Makefile | 1 + src/libexpr/eval.cc | 10 +- src/libexpr/eval.hh | 1 + src/libexpr/primops.cc | 2 +- src/libexpr/tests/json.cc | 68 +++ src/libexpr/tests/libexprtests.hh | 136 +++++ src/libexpr/tests/local.mk | 15 + src/libexpr/tests/primops.cc | 839 ++++++++++++++++++++++++++++++ src/libexpr/tests/trivial.cc | 196 +++++++ 10 files changed, 1267 insertions(+), 2 deletions(-) create mode 100644 src/libexpr/tests/json.cc create mode 100644 src/libexpr/tests/libexprtests.hh create mode 100644 src/libexpr/tests/local.mk create mode 100644 src/libexpr/tests/primops.cc create mode 100644 src/libexpr/tests/trivial.cc diff --git a/.gitignore b/.gitignore index db363aefd..ba8e95191 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ perl/Makefile.config /src/libexpr/parser-tab.hh /src/libexpr/parser-tab.output /src/libexpr/nix.tbl +/src/libexpr/tests/libexpr-tests # /src/libstore/ *.gen.* diff --git a/Makefile b/Makefile index 5040d2884..33f47ca85 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ makefiles = \ src/libfetchers/local.mk \ src/libmain/local.mk \ src/libexpr/local.mk \ + src/libexpr/tests/local.mk \ src/libcmd/local.mk \ src/nix/local.mk \ src/resolve-system-dependencies/local.mk \ diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index ef167087b..d29ff5543 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -147,7 +147,10 @@ void Value::print(const SymbolTable & symbols, std::ostream & str, else { str << "[ "; for (auto v2 : listItems()) { - v2->print(symbols, str, seen); + if (v2) + v2->print(symbols, str, seen); + else + str << "(nullptr)"; str << " "; } str << "]"; @@ -184,6 +187,11 @@ void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepe print(symbols, str, showRepeated ? nullptr : &seen); } +// Pretty print types for assertion errors +std::ostream & operator << (std::ostream & os, const ValueType t) { + os << showType(t); + return os; +} std::string printValue(const EvalState & state, const Value & v) { diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 6c418f2ae..774bc17bb 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -55,6 +55,7 @@ typedef std::map SrcToStore; std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v); std::string printValue(const EvalState & state, const Value & v); +std::ostream & operator << (std::ostream & os, const ValueType t); typedef std::pair SearchPathElem; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index a62d11e4e..28fea276e 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -3632,7 +3632,7 @@ static RegisterPrimOp primop_split({ Evaluates to `[ "" [ "a" null ] "b" [ null "c" ] "" ]`. ```nix - builtins.split "([[:upper:]]+)" " FOO " + builtins.split "([[:upper:]]+)" " FOO " ``` Evaluates to `[ " " [ "FOO" ] " " ]`. diff --git a/src/libexpr/tests/json.cc b/src/libexpr/tests/json.cc new file mode 100644 index 000000000..f1ea1b197 --- /dev/null +++ b/src/libexpr/tests/json.cc @@ -0,0 +1,68 @@ +#include "libexprtests.hh" +#include "value-to-json.hh" + +namespace nix { +// Testing the conversion to JSON + + class JSONValueTest : public LibExprTest { + protected: + std::string getJSONValue(Value& value) { + std::stringstream ss; + PathSet ps; + printValueAsJSON(state, true, value, noPos, ss, ps); + return ss.str(); + } + }; + + TEST_F(JSONValueTest, null) { + Value v; + v.mkNull(); + ASSERT_EQ(getJSONValue(v), "null"); + } + + TEST_F(JSONValueTest, BoolFalse) { + Value v; + v.mkBool(false); + ASSERT_EQ(getJSONValue(v),"false"); + } + + TEST_F(JSONValueTest, BoolTrue) { + Value v; + v.mkBool(true); + ASSERT_EQ(getJSONValue(v), "true"); + } + + TEST_F(JSONValueTest, IntPositive) { + Value v; + v.mkInt(100); + ASSERT_EQ(getJSONValue(v), "100"); + } + + TEST_F(JSONValueTest, IntNegative) { + Value v; + v.mkInt(-100); + ASSERT_EQ(getJSONValue(v), "-100"); + } + + TEST_F(JSONValueTest, String) { + Value v; + v.mkString("test"); + ASSERT_EQ(getJSONValue(v), "\"test\""); + } + + TEST_F(JSONValueTest, StringQuotes) { + Value v; + + v.mkString("test\""); + ASSERT_EQ(getJSONValue(v), "\"test\\\"\""); + } + + // The dummy store doesn't support writing files. Fails with this exception message: + // C++ exception with description "error: operation 'addToStoreFromDump' is + // not supported by store 'dummy'" thrown in the test body. + TEST_F(JSONValueTest, DISABLED_Path) { + Value v; + v.mkPath("test"); + ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\""); + } +} /* namespace nix */ diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexprtests.hh new file mode 100644 index 000000000..4f6915882 --- /dev/null +++ b/src/libexpr/tests/libexprtests.hh @@ -0,0 +1,136 @@ +#include +#include + +#include "value.hh" +#include "nixexpr.hh" +#include "eval.hh" +#include "eval-inline.hh" +#include "store-api.hh" + + +namespace nix { + class LibExprTest : public ::testing::Test { + public: + static void SetUpTestSuite() { + initGC(); + } + + protected: + LibExprTest() + : store(openStore("dummy://")) + , state({}, store) + { + } + Value eval(std::string input, bool forceValue = true) { + Value v; + Expr * e = state.parseExprFromString(input, ""); + assert(e); + state.eval(e, v); + if (forceValue) + state.forceValue(v, noPos); + return v; + } + + Symbol createSymbol(const char * value) { + return state.symbols.create(value); + } + + ref store; + EvalState state; + }; + + MATCHER(IsListType, "") { + return arg != nList; + } + + MATCHER(IsList, "") { + return arg.type() == nList; + } + + MATCHER(IsString, "") { + return arg.type() == nString; + } + + MATCHER(IsNull, "") { + return arg.type() == nNull; + } + + MATCHER(IsThunk, "") { + return arg.type() == nThunk; + } + + MATCHER(IsAttrs, "") { + return arg.type() == nAttrs; + } + + MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s)) { + if (arg.type() != nString) { + return false; + } + return std::string_view(arg.string.s) == s; + } + + MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v)) { + if (arg.type() != nInt) { + return false; + } + return arg.integer == v; + } + + MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v)) { + if (arg.type() != nFloat) { + return false; + } + return arg.fpoint == v; + } + + MATCHER(IsTrue, "") { + if (arg.type() != nBool) { + return false; + } + return arg.boolean == true; + } + + MATCHER(IsFalse, "") { + if (arg.type() != nBool) { + return false; + } + return arg.boolean == false; + } + + MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) { + if (arg.type() != nPath) { + *result_listener << "Expected a path got " << arg.type(); + return false; + } else if (std::string_view(arg.string.s) != p) { + *result_listener << "Expected a path that equals \"" << p << "\" but got: " << arg.string.s; + return false; + } + return true; + } + + + MATCHER_P(IsListOfSize, n, fmt("Is a list of size [%1%]", n)) { + if (arg.type() != nList) { + *result_listener << "Expected list got " << arg.type(); + return false; + } else if (arg.listSize() != (size_t)n) { + *result_listener << "Expected as list of size " << n << " got " << arg.listSize(); + return false; + } + return true; + } + + MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) { + if (arg.type() != nAttrs) { + *result_listener << "Expexted set got " << arg.type(); + return false; + } else if (arg.attrs->size() != (size_t)n) { + *result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size(); + return false; + } + return true; + } + + +} /* namespace nix */ diff --git a/src/libexpr/tests/local.mk b/src/libexpr/tests/local.mk new file mode 100644 index 000000000..cb1c4a977 --- /dev/null +++ b/src/libexpr/tests/local.mk @@ -0,0 +1,15 @@ +check: libexpr-tests_RUN + +programs += libexpr-tests + +libexpr-tests_DIR := $(d) + +libexpr-tests_INSTALL_DIR := + +libexpr-tests_SOURCES := $(wildcard $(d)/*.cc) + +libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests + +libexpr-tests_LIBS = libexpr libutil libstore + +libexpr-tests_LDFLAGS := $(GTEST_LIBS) -lgmock diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc new file mode 100644 index 000000000..f65b6593d --- /dev/null +++ b/src/libexpr/tests/primops.cc @@ -0,0 +1,839 @@ +#include +#include + +#include "libexprtests.hh" + +namespace nix { + class CaptureLogger : public Logger + { + std::ostringstream oss; + + public: + CaptureLogger() {} + + std::string get() const { + return oss.str(); + } + + void log(Verbosity lvl, const FormatOrString & fs) override { + oss << fs.s << std::endl; + } + + void logEI(const ErrorInfo & ei) override { + showErrorInfo(oss, ei, loggerSettings.showTrace.get()); + } + }; + + class CaptureLogging { + Logger * oldLogger; + std::unique_ptr tempLogger; + public: + CaptureLogging() : tempLogger(std::make_unique()) { + oldLogger = logger; + logger = tempLogger.get(); + } + + ~CaptureLogging() { + logger = oldLogger; + } + + std::string get() const { + return tempLogger->get(); + } + }; + + + // Testing eval of PrimOp's + class PrimOpTest : public LibExprTest {}; + + + TEST_F(PrimOpTest, throw) { + ASSERT_THROW(eval("throw \"foo\""), ThrownError); + } + + TEST_F(PrimOpTest, abort) { + ASSERT_THROW(eval("abort \"abort\""), Abort); + } + + TEST_F(PrimOpTest, ceil) { + auto v = eval("builtins.ceil 1.9"); + ASSERT_THAT(v, IsIntEq(2)); + } + + TEST_F(PrimOpTest, floor) { + auto v = eval("builtins.floor 1.9"); + ASSERT_THAT(v, IsIntEq(1)); + } + + TEST_F(PrimOpTest, tryEvalFailure) { + auto v = eval("builtins.tryEval (throw \"\")"); + ASSERT_THAT(v, IsAttrsOfSize(2)); + auto s = createSymbol("success"); + auto p = v.attrs->get(s); + ASSERT_NE(p, nullptr); + ASSERT_THAT(*p->value, IsFalse()); + } + + TEST_F(PrimOpTest, tryEvalSuccess) { + auto v = eval("builtins.tryEval 123"); + ASSERT_THAT(v, IsAttrs()); + auto s = createSymbol("success"); + auto p = v.attrs->get(s); + ASSERT_NE(p, nullptr); + ASSERT_THAT(*p->value, IsTrue()); + s = createSymbol("value"); + p = v.attrs->get(s); + ASSERT_NE(p, nullptr); + ASSERT_THAT(*p->value, IsIntEq(123)); + } + + TEST_F(PrimOpTest, getEnv) { + setenv("_NIX_UNIT_TEST_ENV_VALUE", "test value", 1); + auto v = eval("builtins.getEnv \"_NIX_UNIT_TEST_ENV_VALUE\""); + ASSERT_THAT(v, IsStringEq("test value")); + } + + TEST_F(PrimOpTest, seq) { + ASSERT_THROW(eval("let x = throw \"test\"; in builtins.seq x { }"), ThrownError); + } + + TEST_F(PrimOpTest, seqNotDeep) { + auto v = eval("let x = { z = throw \"test\"; }; in builtins.seq x { }"); + ASSERT_THAT(v, IsAttrs()); + } + + TEST_F(PrimOpTest, deepSeq) { + ASSERT_THROW(eval("let x = { z = throw \"test\"; }; in builtins.deepSeq x { }"), ThrownError); + } + + TEST_F(PrimOpTest, trace) { + CaptureLogging l; + auto v = eval("builtins.trace \"test string 123\" 123"); + ASSERT_THAT(v, IsIntEq(123)); + auto text = l.get(); + ASSERT_NE(text.find("test string 123"), std::string::npos); + } + + TEST_F(PrimOpTest, placeholder) { + auto v = eval("builtins.placeholder \"out\""); + ASSERT_THAT(v, IsStringEq("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9")); + } + + TEST_F(PrimOpTest, baseNameOf) { + auto v = eval("builtins.baseNameOf /some/path"); + ASSERT_THAT(v, IsStringEq("path")); + } + + TEST_F(PrimOpTest, dirOf) { + auto v = eval("builtins.dirOf /some/path"); + ASSERT_THAT(v, IsPathEq("/some")); + } + + TEST_F(PrimOpTest, attrValues) { + auto v = eval("builtins.attrValues { x = \"foo\"; a = 1; }"); + ASSERT_THAT(v, IsListOfSize(2)); + ASSERT_THAT(*v.listElems()[0], IsIntEq(1)); + ASSERT_THAT(*v.listElems()[1], IsStringEq("foo")); + } + + TEST_F(PrimOpTest, getAttr) { + auto v = eval("builtins.getAttr \"x\" { x = \"foo\"; }"); + ASSERT_THAT(v, IsStringEq("foo")); + } + + TEST_F(PrimOpTest, getAttrNotFound) { + // FIXME: TypeError is really bad here, also the error wording is worse + // than on Nix <=2.3 + ASSERT_THROW(eval("builtins.getAttr \"y\" { }"), TypeError); + } + + TEST_F(PrimOpTest, unsafeGetAttrPos) { + // The `y` attribute is at position + const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }"; + auto v = eval(expr); + ASSERT_THAT(v, IsAttrsOfSize(3)); + + auto file = v.attrs->find(createSymbol("file")); + ASSERT_NE(file, nullptr); + // FIXME: The file when running these tests is the input string?!? + ASSERT_THAT(*file->value, IsStringEq(expr)); + + auto line = v.attrs->find(createSymbol("line")); + ASSERT_NE(line, nullptr); + ASSERT_THAT(*line->value, IsIntEq(1)); + + auto column = v.attrs->find(createSymbol("column")); + ASSERT_NE(column, nullptr); + ASSERT_THAT(*column->value, IsIntEq(33)); + } + + TEST_F(PrimOpTest, hasAttr) { + auto v = eval("builtins.hasAttr \"x\" { x = 1; }"); + ASSERT_THAT(v, IsTrue()); + } + + TEST_F(PrimOpTest, hasAttrNotFound) { + auto v = eval("builtins.hasAttr \"x\" { }"); + ASSERT_THAT(v, IsFalse()); + } + + TEST_F(PrimOpTest, isAttrs) { + auto v = eval("builtins.isAttrs {}"); + ASSERT_THAT(v, IsTrue()); + } + + TEST_F(PrimOpTest, isAttrsFalse) { + auto v = eval("builtins.isAttrs null"); + ASSERT_THAT(v, IsFalse()); + } + + TEST_F(PrimOpTest, removeAttrs) { + auto v = eval("builtins.removeAttrs { x = 1; } [\"x\"]"); + ASSERT_THAT(v, IsAttrsOfSize(0)); + } + + TEST_F(PrimOpTest, removeAttrsRetains) { + auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]"); + ASSERT_THAT(v, IsAttrsOfSize(1)); + ASSERT_NE(v.attrs->find(createSymbol("y")), nullptr); + } + + TEST_F(PrimOpTest, listToAttrsEmptyList) { + auto v = eval("builtins.listToAttrs []"); + ASSERT_THAT(v, IsAttrsOfSize(0)); + ASSERT_EQ(v.type(), nAttrs); + ASSERT_EQ(v.attrs->size(), 0); + } + + TEST_F(PrimOpTest, listToAttrsNotFieldName) { + ASSERT_THROW(eval("builtins.listToAttrs [{}]"), Error); + } + + TEST_F(PrimOpTest, listToAttrs) { + auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]"); + ASSERT_THAT(v, IsAttrsOfSize(1)); + auto key = v.attrs->find(createSymbol("key")); + ASSERT_NE(key, nullptr); + ASSERT_THAT(*key->value, IsIntEq(123)); + } + + TEST_F(PrimOpTest, intersectAttrs) { + auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }"); + ASSERT_THAT(v, IsAttrsOfSize(1)); + auto b = v.attrs->find(createSymbol("b")); + ASSERT_NE(b, nullptr); + ASSERT_THAT(*b->value, IsIntEq(3)); + } + + TEST_F(PrimOpTest, catAttrs) { + auto v = eval("builtins.catAttrs \"a\" [{a = 1;} {b = 0;} {a = 2;}]"); + ASSERT_THAT(v, IsListOfSize(2)); + ASSERT_THAT(*v.listElems()[0], IsIntEq(1)); + ASSERT_THAT(*v.listElems()[1], IsIntEq(2)); + } + + TEST_F(PrimOpTest, functionArgs) { + auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)"); + ASSERT_THAT(v, IsAttrsOfSize(2)); + + auto x = v.attrs->find(createSymbol("x")); + ASSERT_NE(x, nullptr); + ASSERT_THAT(*x->value, IsFalse()); + + auto y = v.attrs->find(createSymbol("y")); + ASSERT_NE(y, nullptr); + ASSERT_THAT(*y->value, IsTrue()); + } + + TEST_F(PrimOpTest, mapAttrs) { + auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }"); + ASSERT_THAT(v, IsAttrsOfSize(2)); + + auto a = v.attrs->find(createSymbol("a")); + ASSERT_NE(a, nullptr); + ASSERT_THAT(*a->value, IsThunk()); + state.forceValue(*a->value, noPos); + ASSERT_THAT(*a->value, IsIntEq(10)); + + auto b = v.attrs->find(createSymbol("b")); + ASSERT_NE(b, nullptr); + ASSERT_THAT(*b->value, IsThunk()); + state.forceValue(*b->value, noPos); + ASSERT_THAT(*b->value, IsIntEq(20)); + } + + TEST_F(PrimOpTest, isList) { + auto v = eval("builtins.isList []"); + ASSERT_THAT(v, IsTrue()); + } + + TEST_F(PrimOpTest, isListFalse) { + auto v = eval("builtins.isList null"); + ASSERT_THAT(v, IsFalse()); + } + + TEST_F(PrimOpTest, elemtAt) { + auto v = eval("builtins.elemAt [0 1 2 3] 3"); + ASSERT_THAT(v, IsIntEq(3)); + } + + TEST_F(PrimOpTest, elemtAtOutOfBounds) { + ASSERT_THROW(eval("builtins.elemAt [0 1 2 3] 5"), Error); + } + + TEST_F(PrimOpTest, head) { + auto v = eval("builtins.head [ 3 2 1 0 ]"); + ASSERT_THAT(v, IsIntEq(3)); + } + + TEST_F(PrimOpTest, headEmpty) { + ASSERT_THROW(eval("builtins.head [ ]"), Error); + } + + TEST_F(PrimOpTest, headWrongType) { + ASSERT_THROW(eval("builtins.head { }"), Error); + } + + TEST_F(PrimOpTest, tail) { + auto v = eval("builtins.tail [ 3 2 1 0 ]"); + ASSERT_THAT(v, IsListOfSize(3)); + for (const auto [n, elem] : enumerate(v.listItems())) + ASSERT_THAT(*elem, IsIntEq(2 - static_cast(n))); + } + + TEST_F(PrimOpTest, tailEmpty) { + ASSERT_THROW(eval("builtins.tail []"), Error); + } + + TEST_F(PrimOpTest, map) { + auto v = eval("map (x: \"foo\" + x) [ \"bar\" \"bla\" \"abc\" ]"); + ASSERT_THAT(v, IsListOfSize(3)); + auto elem = v.listElems()[0]; + ASSERT_THAT(*elem, IsThunk()); + state.forceValue(*elem, noPos); + ASSERT_THAT(*elem, IsStringEq("foobar")); + + elem = v.listElems()[1]; + ASSERT_THAT(*elem, IsThunk()); + state.forceValue(*elem, noPos); + ASSERT_THAT(*elem, IsStringEq("foobla")); + + elem = v.listElems()[2]; + ASSERT_THAT(*elem, IsThunk()); + state.forceValue(*elem, noPos); + ASSERT_THAT(*elem, IsStringEq("fooabc")); + } + + TEST_F(PrimOpTest, filter) { + auto v = eval("builtins.filter (x: x == 2) [ 3 2 3 2 3 2 ]"); + ASSERT_THAT(v, IsListOfSize(3)); + for (const auto elem : v.listItems()) + ASSERT_THAT(*elem, IsIntEq(2)); + } + + TEST_F(PrimOpTest, elemTrue) { + auto v = eval("builtins.elem 3 [ 1 2 3 4 5 ]"); + ASSERT_THAT(v, IsTrue()); + } + + TEST_F(PrimOpTest, elemFalse) { + auto v = eval("builtins.elem 6 [ 1 2 3 4 5 ]"); + ASSERT_THAT(v, IsFalse()); + } + + TEST_F(PrimOpTest, concatLists) { + auto v = eval("builtins.concatLists [[1 2] [3 4]]"); + ASSERT_THAT(v, IsListOfSize(4)); + for (const auto [i, elem] : enumerate(v.listItems())) + ASSERT_THAT(*elem, IsIntEq(static_cast(i)+1)); + } + + TEST_F(PrimOpTest, length) { + auto v = eval("builtins.length [ 1 2 3 ]"); + ASSERT_THAT(v, IsIntEq(3)); + } + + TEST_F(PrimOpTest, foldStrict) { + auto v = eval("builtins.foldl' (a: b: a + b) 0 [1 2 3]"); + ASSERT_THAT(v, IsIntEq(6)); + } + + TEST_F(PrimOpTest, anyTrue) { + auto v = eval("builtins.any (x: x == 2) [ 1 2 3 ]"); + ASSERT_THAT(v, IsTrue()); + } + + TEST_F(PrimOpTest, anyFalse) { + auto v = eval("builtins.any (x: x == 5) [ 1 2 3 ]"); + ASSERT_THAT(v, IsFalse()); + } + + TEST_F(PrimOpTest, allTrue) { + auto v = eval("builtins.all (x: x > 0) [ 1 2 3 ]"); + ASSERT_THAT(v, IsTrue()); + } + + TEST_F(PrimOpTest, allFalse) { + auto v = eval("builtins.all (x: x <= 0) [ 1 2 3 ]"); + ASSERT_THAT(v, IsFalse()); + } + + TEST_F(PrimOpTest, genList) { + auto v = eval("builtins.genList (x: x + 1) 3"); + ASSERT_EQ(v.type(), nList); + ASSERT_EQ(v.listSize(), 3); + for (const auto [i, elem] : enumerate(v.listItems())) { + ASSERT_THAT(*elem, IsThunk()); + state.forceValue(*elem, noPos); + ASSERT_THAT(*elem, IsIntEq(static_cast(i)+1)); + } + } + + TEST_F(PrimOpTest, sortLessThan) { + auto v = eval("builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]"); + ASSERT_EQ(v.type(), nList); + ASSERT_EQ(v.listSize(), 6); + + const std::vector numbers = { 42, 77, 147, 249, 483, 526 }; + for (const auto [n, elem] : enumerate(v.listItems())) + ASSERT_THAT(*elem, IsIntEq(numbers[n])); + } + + TEST_F(PrimOpTest, partition) { + auto v = eval("builtins.partition (x: x > 10) [1 23 9 3 42]"); + ASSERT_THAT(v, IsAttrsOfSize(2)); + + auto right = v.attrs->get(createSymbol("right")); + ASSERT_NE(right, nullptr); + ASSERT_THAT(*right->value, IsListOfSize(2)); + ASSERT_THAT(*right->value->listElems()[0], IsIntEq(23)); + ASSERT_THAT(*right->value->listElems()[1], IsIntEq(42)); + + auto wrong = v.attrs->get(createSymbol("wrong")); + ASSERT_NE(wrong, nullptr); + ASSERT_EQ(wrong->value->type(), nList); + ASSERT_EQ(wrong->value->listSize(), 3); + ASSERT_THAT(*wrong->value, IsListOfSize(3)); + ASSERT_THAT(*wrong->value->listElems()[0], IsIntEq(1)); + ASSERT_THAT(*wrong->value->listElems()[1], IsIntEq(9)); + ASSERT_THAT(*wrong->value->listElems()[2], IsIntEq(3)); + } + + TEST_F(PrimOpTest, concatMap) { + auto v = eval("builtins.concatMap (x: x ++ [0]) [ [1 2] [3 4] ]"); + ASSERT_EQ(v.type(), nList); + ASSERT_EQ(v.listSize(), 6); + + const std::vector numbers = { 1, 2, 0, 3, 4, 0 }; + for (const auto [n, elem] : enumerate(v.listItems())) + ASSERT_THAT(*elem, IsIntEq(numbers[n])); + } + + TEST_F(PrimOpTest, addInt) { + auto v = eval("builtins.add 3 5"); + ASSERT_THAT(v, IsIntEq(8)); + } + + TEST_F(PrimOpTest, addFloat) { + auto v = eval("builtins.add 3.0 5.0"); + ASSERT_THAT(v, IsFloatEq(8.0)); + } + + TEST_F(PrimOpTest, addFloatToInt) { + auto v = eval("builtins.add 3.0 5"); + ASSERT_THAT(v, IsFloatEq(8.0)); + + v = eval("builtins.add 3 5.0"); + ASSERT_THAT(v, IsFloatEq(8.0)); + } + + TEST_F(PrimOpTest, subInt) { + auto v = eval("builtins.sub 5 2"); + ASSERT_THAT(v, IsIntEq(3)); + } + + TEST_F(PrimOpTest, subFloat) { + auto v = eval("builtins.sub 5.0 2.0"); + ASSERT_THAT(v, IsFloatEq(3.0)); + } + + TEST_F(PrimOpTest, subFloatFromInt) { + auto v = eval("builtins.sub 5.0 2"); + ASSERT_THAT(v, IsFloatEq(3.0)); + + v = eval("builtins.sub 4 2.0"); + ASSERT_THAT(v, IsFloatEq(2.0)); + } + + TEST_F(PrimOpTest, mulInt) { + auto v = eval("builtins.mul 3 5"); + ASSERT_THAT(v, IsIntEq(15)); + } + + TEST_F(PrimOpTest, mulFloat) { + auto v = eval("builtins.mul 3.0 5.0"); + ASSERT_THAT(v, IsFloatEq(15.0)); + } + + TEST_F(PrimOpTest, mulFloatMixed) { + auto v = eval("builtins.mul 3 5.0"); + ASSERT_THAT(v, IsFloatEq(15.0)); + + v = eval("builtins.mul 2.0 5"); + ASSERT_THAT(v, IsFloatEq(10.0)); + } + + TEST_F(PrimOpTest, divInt) { + auto v = eval("builtins.div 5 (-1)"); + ASSERT_THAT(v, IsIntEq(-5)); + } + + TEST_F(PrimOpTest, divIntZero) { + ASSERT_THROW(eval("builtins.div 5 0"), EvalError); + } + + TEST_F(PrimOpTest, divFloat) { + auto v = eval("builtins.div 5.0 (-1)"); + ASSERT_THAT(v, IsFloatEq(-5.0)); + } + + TEST_F(PrimOpTest, divFloatZero) { + ASSERT_THROW(eval("builtins.div 5.0 0.0"), EvalError); + } + + TEST_F(PrimOpTest, bitOr) { + auto v = eval("builtins.bitOr 1 2"); + ASSERT_THAT(v, IsIntEq(3)); + } + + TEST_F(PrimOpTest, bitXor) { + auto v = eval("builtins.bitXor 3 2"); + ASSERT_THAT(v, IsIntEq(1)); + } + + TEST_F(PrimOpTest, lessThanFalse) { + auto v = eval("builtins.lessThan 3 1"); + ASSERT_THAT(v, IsFalse()); + } + + TEST_F(PrimOpTest, lessThanTrue) { + auto v = eval("builtins.lessThan 1 3"); + ASSERT_THAT(v, IsTrue()); + } + + TEST_F(PrimOpTest, toStringAttrsThrows) { + ASSERT_THROW(eval("builtins.toString {}"), EvalError); + } + + TEST_F(PrimOpTest, toStringLambdaThrows) { + ASSERT_THROW(eval("builtins.toString (x: x)"), EvalError); + } + + class ToStringPrimOpTest : + public PrimOpTest, + public testing::WithParamInterface> + {}; + + TEST_P(ToStringPrimOpTest, toString) { + const auto [input, output] = GetParam(); + auto v = eval(input); + ASSERT_THAT(v, IsStringEq(output)); + } + +#define CASE(input, output) (std::make_tuple(std::string_view("builtins.toString " #input), std::string_view(output))) + INSTANTIATE_TEST_SUITE_P( + toString, + ToStringPrimOpTest, + testing::Values( + CASE("foo", "foo"), + CASE(1, "1"), + CASE([1 2 3], "1 2 3"), + CASE(.123, "0.123000"), + CASE(true, "1"), + CASE(false, ""), + CASE(null, ""), + CASE({ v = "bar"; __toString = self: self.v; }, "bar"), + CASE({ v = "bar"; __toString = self: self.v; outPath = "foo"; }, "bar"), + CASE({ outPath = "foo"; }, "foo"), + CASE(./test, "/test") + ) + ); +#undef CASE + + TEST_F(PrimOpTest, substring){ + auto v = eval("builtins.substring 0 3 \"nixos\""); + ASSERT_THAT(v, IsStringEq("nix")); + } + + TEST_F(PrimOpTest, substringSmallerString){ + auto v = eval("builtins.substring 0 3 \"n\""); + ASSERT_THAT(v, IsStringEq("n")); + } + + TEST_F(PrimOpTest, substringEmptyString){ + auto v = eval("builtins.substring 1 3 \"\""); + ASSERT_THAT(v, IsStringEq("")); + } + + TEST_F(PrimOpTest, stringLength) { + auto v = eval("builtins.stringLength \"123\""); + ASSERT_THAT(v, IsIntEq(3)); + } + TEST_F(PrimOpTest, hashStringMd5) { + auto v = eval("builtins.hashString \"md5\" \"asdf\""); + ASSERT_THAT(v, IsStringEq("912ec803b2ce49e4a541068d495ab570")); + } + + TEST_F(PrimOpTest, hashStringSha1) { + auto v = eval("builtins.hashString \"sha1\" \"asdf\""); + ASSERT_THAT(v, IsStringEq("3da541559918a808c2402bba5012f6c60b27661c")); + } + + TEST_F(PrimOpTest, hashStringSha256) { + auto v = eval("builtins.hashString \"sha256\" \"asdf\""); + ASSERT_THAT(v, IsStringEq("f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b")); + } + + TEST_F(PrimOpTest, hashStringSha512) { + auto v = eval("builtins.hashString \"sha512\" \"asdf\""); + ASSERT_THAT(v, IsStringEq("401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1")); + } + + TEST_F(PrimOpTest, hashStringInvalidHashType) { + ASSERT_THROW(eval("builtins.hashString \"foobar\" \"asdf\""), Error); + } + + TEST_F(PrimOpTest, nixPath) { + auto v = eval("builtins.nixPath"); + ASSERT_EQ(v.type(), nList); + // We can't test much more as currently the EvalSettings are a global + // that we can't easily swap / replace + } + + TEST_F(PrimOpTest, langVersion) { + auto v = eval("builtins.langVersion"); + ASSERT_EQ(v.type(), nInt); + } + + TEST_F(PrimOpTest, storeDir) { + auto v = eval("builtins.storeDir"); + ASSERT_THAT(v, IsStringEq("/nix/store")); + } + + TEST_F(PrimOpTest, nixVersion) { + auto v = eval("builtins.nixVersion"); + ASSERT_THAT(v, IsStringEq(nixVersion)); + } + + TEST_F(PrimOpTest, currentSystem) { + auto v = eval("builtins.currentSystem"); + ASSERT_THAT(v, IsStringEq(settings.thisSystem.get())); + } + + TEST_F(PrimOpTest, derivation) { + auto v = eval("derivation"); + ASSERT_EQ(v.type(), nFunction); + ASSERT_TRUE(v.isLambda()); + ASSERT_NE(v.lambda.fun, nullptr); + ASSERT_TRUE(v.lambda.fun->hasFormals()); + } + + TEST_F(PrimOpTest, currentTime) { + auto v = eval("builtins.currentTime"); + ASSERT_EQ(v.type(), nInt); + ASSERT_TRUE(v.integer > 0); + } + + TEST_F(PrimOpTest, splitVersion) { + auto v = eval("builtins.splitVersion \"1.2.3git\""); + ASSERT_THAT(v, IsListOfSize(4)); + + const std::vector strings = { "1", "2", "3", "git" }; + for (const auto [n, p] : enumerate(v.listItems())) + ASSERT_THAT(*p, IsStringEq(strings[n])); + } + + class CompareVersionsPrimOpTest : + public PrimOpTest, + public testing::WithParamInterface> + {}; + + TEST_P(CompareVersionsPrimOpTest, compareVersions) { + auto [expression, expectation] = GetParam(); + auto v = eval(expression); + ASSERT_THAT(v, IsIntEq(expectation)); + } + +#define CASE(a, b, expected) (std::make_tuple("builtins.compareVersions \"" #a "\" \"" #b "\"", expected)) + INSTANTIATE_TEST_SUITE_P( + compareVersions, + CompareVersionsPrimOpTest, + testing::Values( + // The first two are weird cases. Intuition tells they should + // be the same but they aren't. + CASE(1.0, 1.0.0, -1), + CASE(1.0.0, 1.0, 1), + // the following are from the nix-env manual: + CASE(1.0, 2.3, -1), + CASE(2.1, 2.3, -1), + CASE(2.3, 2.3, 0), + CASE(2.5, 2.3, 1), + CASE(3.1, 2.3, 1), + CASE(2.3.1, 2.3, 1), + CASE(2.3.1, 2.3a, 1), + CASE(2.3pre1, 2.3, -1), + CASE(2.3pre3, 2.3pre12, -1), + CASE(2.3a, 2.3c, -1), + CASE(2.3pre1, 2.3c, -1), + CASE(2.3pre1, 2.3q, -1) + ) + ); +#undef CASE + + + class ParseDrvNamePrimOpTest : + public PrimOpTest, + public testing::WithParamInterface> + {}; + + TEST_P(ParseDrvNamePrimOpTest, parseDrvName) { + auto [input, expectedName, expectedVersion] = GetParam(); + const auto expr = fmt("builtins.parseDrvName \"%1%\"", input); + auto v = eval(expr); + ASSERT_THAT(v, IsAttrsOfSize(2)); + + auto name = v.attrs->find(createSymbol("name")); + ASSERT_TRUE(name); + ASSERT_THAT(*name->value, IsStringEq(expectedName)); + + auto version = v.attrs->find(createSymbol("version")); + ASSERT_TRUE(version); + ASSERT_THAT(*version->value, IsStringEq(expectedVersion)); + } + + INSTANTIATE_TEST_SUITE_P( + parseDrvName, + ParseDrvNamePrimOpTest, + testing::Values( + std::make_tuple("nix-0.12pre12876", "nix", "0.12pre12876"), + std::make_tuple("a-b-c-1234pre5+git", "a-b-c", "1234pre5+git") + ) + ); + + TEST_F(PrimOpTest, replaceStrings) { + // FIXME: add a test that verifies the string context is as expected + auto v = eval("builtins.replaceStrings [\"oo\" \"a\"] [\"a\" \"i\"] \"foobar\""); + ASSERT_EQ(v.type(), nString); + ASSERT_EQ(v.string.s, std::string_view("fabir")); + } + + TEST_F(PrimOpTest, concatStringsSep) { + // FIXME: add a test that verifies the string context is as expected + auto v = eval("builtins.concatStringsSep \"%\" [\"foo\" \"bar\" \"baz\"]"); + ASSERT_EQ(v.type(), nString); + ASSERT_EQ(std::string_view(v.string.s), "foo%bar%baz"); + } + + TEST_F(PrimOpTest, split1) { + // v = [ "" [ "a" ] "c" ] + auto v = eval("builtins.split \"(a)b\" \"abc\""); + ASSERT_THAT(v, IsListOfSize(3)); + + ASSERT_THAT(*v.listElems()[0], IsStringEq("")); + + ASSERT_THAT(*v.listElems()[1], IsListOfSize(1)); + ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a")); + + ASSERT_THAT(*v.listElems()[2], IsStringEq("c")); + } + + TEST_F(PrimOpTest, split2) { + // v is expected to be a list [ "" [ "a" ] "b" [ "c"] "" ] + auto v = eval("builtins.split \"([ac])\" \"abc\""); + ASSERT_THAT(v, IsListOfSize(5)); + + ASSERT_THAT(*v.listElems()[0], IsStringEq("")); + + ASSERT_THAT(*v.listElems()[1], IsListOfSize(1)); + ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a")); + + ASSERT_THAT(*v.listElems()[2], IsStringEq("b")); + + ASSERT_THAT(*v.listElems()[3], IsListOfSize(1)); + ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsStringEq("c")); + + ASSERT_THAT(*v.listElems()[4], IsStringEq("")); + } + + TEST_F(PrimOpTest, split3) { + auto v = eval("builtins.split \"(a)|(c)\" \"abc\""); + ASSERT_THAT(v, IsListOfSize(5)); + + // First list element + ASSERT_THAT(*v.listElems()[0], IsStringEq("")); + + // 2nd list element is a list [ "" null ] + ASSERT_THAT(*v.listElems()[1], IsListOfSize(2)); + ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a")); + ASSERT_THAT(*v.listElems()[1]->listElems()[1], IsNull()); + + // 3rd element + ASSERT_THAT(*v.listElems()[2], IsStringEq("b")); + + // 4th element is a list: [ null "c" ] + ASSERT_THAT(*v.listElems()[3], IsListOfSize(2)); + ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsNull()); + ASSERT_THAT(*v.listElems()[3]->listElems()[1], IsStringEq("c")); + + // 5th element is the empty string + ASSERT_THAT(*v.listElems()[4], IsStringEq("")); + } + + TEST_F(PrimOpTest, split4) { + auto v = eval("builtins.split \"([[:upper:]]+)\" \" FOO \""); + ASSERT_THAT(v, IsListOfSize(3)); + auto first = v.listElems()[0]; + auto second = v.listElems()[1]; + auto third = v.listElems()[2]; + + ASSERT_THAT(*first, IsStringEq(" ")); + + ASSERT_THAT(*second, IsListOfSize(1)); + ASSERT_THAT(*second->listElems()[0], IsStringEq("FOO")); + + ASSERT_THAT(*third, IsStringEq(" ")); + } + + TEST_F(PrimOpTest, match1) { + auto v = eval("builtins.match \"ab\" \"abc\""); + ASSERT_THAT(v, IsNull()); + } + + TEST_F(PrimOpTest, match2) { + auto v = eval("builtins.match \"abc\" \"abc\""); + ASSERT_THAT(v, IsListOfSize(0)); + } + + TEST_F(PrimOpTest, match3) { + auto v = eval("builtins.match \"a(b)(c)\" \"abc\""); + ASSERT_THAT(v, IsListOfSize(2)); + ASSERT_THAT(*v.listElems()[0], IsStringEq("b")); + ASSERT_THAT(*v.listElems()[1], IsStringEq("c")); + } + + TEST_F(PrimOpTest, match4) { + auto v = eval("builtins.match \"[[:space:]]+([[:upper:]]+)[[:space:]]+\" \" FOO \""); + ASSERT_THAT(v, IsListOfSize(1)); + ASSERT_THAT(*v.listElems()[0], IsStringEq("FOO")); + } + + TEST_F(PrimOpTest, attrNames) { + auto v = eval("builtins.attrNames { x = 1; y = 2; z = 3; a = 2; }"); + ASSERT_THAT(v, IsListOfSize(4)); + + // ensure that the list is sorted + const std::vector expected { "a", "x", "y", "z" }; + for (const auto [n, elem] : enumerate(v.listItems())) + ASSERT_THAT(*elem, IsStringEq(expected[n])); + } +} /* namespace nix */ diff --git a/src/libexpr/tests/trivial.cc b/src/libexpr/tests/trivial.cc new file mode 100644 index 000000000..8ce276e52 --- /dev/null +++ b/src/libexpr/tests/trivial.cc @@ -0,0 +1,196 @@ +#include "libexprtests.hh" + +namespace nix { + // Testing of trivial expressions + class TrivialExpressionTest : public LibExprTest {}; + + TEST_F(TrivialExpressionTest, true) { + auto v = eval("true"); + ASSERT_THAT(v, IsTrue()); + } + + TEST_F(TrivialExpressionTest, false) { + auto v = eval("false"); + ASSERT_THAT(v, IsFalse()); + } + + TEST_F(TrivialExpressionTest, null) { + auto v = eval("null"); + ASSERT_THAT(v, IsNull()); + } + + TEST_F(TrivialExpressionTest, 1) { + auto v = eval("1"); + ASSERT_THAT(v, IsIntEq(1)); + } + + TEST_F(TrivialExpressionTest, 1plus1) { + auto v = eval("1+1"); + ASSERT_THAT(v, IsIntEq(2)); + } + + TEST_F(TrivialExpressionTest, minus1) { + auto v = eval("-1"); + ASSERT_THAT(v, IsIntEq(-1)); + } + + TEST_F(TrivialExpressionTest, 1minus1) { + auto v = eval("1-1"); + ASSERT_THAT(v, IsIntEq(0)); + } + + TEST_F(TrivialExpressionTest, lambdaAdd) { + auto v = eval("let add = a: b: a + b; in add 1 2"); + ASSERT_THAT(v, IsIntEq(3)); + } + + TEST_F(TrivialExpressionTest, list) { + auto v = eval("[]"); + ASSERT_THAT(v, IsListOfSize(0)); + } + + TEST_F(TrivialExpressionTest, attrs) { + auto v = eval("{}"); + ASSERT_THAT(v, IsAttrsOfSize(0)); + } + + TEST_F(TrivialExpressionTest, float) { + auto v = eval("1.234"); + ASSERT_THAT(v, IsFloatEq(1.234)); + } + + TEST_F(TrivialExpressionTest, updateAttrs) { + auto v = eval("{ a = 1; } // { b = 2; a = 3; }"); + ASSERT_THAT(v, IsAttrsOfSize(2)); + auto a = v.attrs->find(createSymbol("a")); + ASSERT_NE(a, nullptr); + ASSERT_THAT(*a->value, IsIntEq(3)); + + auto b = v.attrs->find(createSymbol("b")); + ASSERT_NE(b, nullptr); + ASSERT_THAT(*b->value, IsIntEq(2)); + } + + TEST_F(TrivialExpressionTest, hasAttrOpFalse) { + auto v = eval("{} ? a"); + ASSERT_THAT(v, IsFalse()); + } + + TEST_F(TrivialExpressionTest, hasAttrOpTrue) { + auto v = eval("{ a = 123; } ? a"); + ASSERT_THAT(v, IsTrue()); + } + + TEST_F(TrivialExpressionTest, withFound) { + auto v = eval("with { a = 23; }; a"); + ASSERT_THAT(v, IsIntEq(23)); + } + + TEST_F(TrivialExpressionTest, withNotFound) { + ASSERT_THROW(eval("with {}; a"), Error); + } + + TEST_F(TrivialExpressionTest, withOverride) { + auto v = eval("with { a = 23; }; with { a = 42; }; a"); + ASSERT_THAT(v, IsIntEq(42)); + } + + TEST_F(TrivialExpressionTest, letOverWith) { + auto v = eval("let a = 23; in with { a = 1; }; a"); + ASSERT_THAT(v, IsIntEq(23)); + } + + TEST_F(TrivialExpressionTest, multipleLet) { + auto v = eval("let a = 23; in let a = 42; in a"); + ASSERT_THAT(v, IsIntEq(42)); + } + + TEST_F(TrivialExpressionTest, defaultFunctionArgs) { + auto v = eval("({ a ? 123 }: a) {}"); + ASSERT_THAT(v, IsIntEq(123)); + } + + TEST_F(TrivialExpressionTest, defaultFunctionArgsOverride) { + auto v = eval("({ a ? 123 }: a) { a = 5; }"); + ASSERT_THAT(v, IsIntEq(5)); + } + + TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureBack) { + auto v = eval("({ a ? 123 }@args: args) {}"); + ASSERT_THAT(v, IsAttrsOfSize(0)); + } + + TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureFront) { + auto v = eval("(args@{ a ? 123 }: args) {}"); + ASSERT_THAT(v, IsAttrsOfSize(0)); + } + + TEST_F(TrivialExpressionTest, assertThrows) { + ASSERT_THROW(eval("let x = arg: assert arg == 1; 123; in x 2"), Error); + } + + TEST_F(TrivialExpressionTest, assertPassed) { + auto v = eval("let x = arg: assert arg == 1; 123; in x 1"); + ASSERT_THAT(v, IsIntEq(123)); + } + + class AttrSetMergeTrvialExpressionTest : + public TrivialExpressionTest, + public testing::WithParamInterface + {}; + + TEST_P(AttrSetMergeTrvialExpressionTest, attrsetMergeLazy) { + // Usually Nix rejects duplicate keys in an attrset but it does allow + // so if it is an attribute set that contains disjoint sets of keys. + // The below is equivalent to `{a.b = 1; a.c = 2; }`. + // The attribute set `a` will be a Thunk at first as the attribuets + // have to be merged (or otherwise computed) and that is done in a lazy + // manner. + + auto expr = GetParam(); + auto v = eval(expr); + ASSERT_THAT(v, IsAttrsOfSize(1)); + + auto a = v.attrs->find(createSymbol("a")); + ASSERT_NE(a, nullptr); + + ASSERT_THAT(*a->value, IsThunk()); + state.forceValue(*a->value, noPos); + + ASSERT_THAT(*a->value, IsAttrsOfSize(2)); + + auto b = a->value->attrs->find(createSymbol("b")); + ASSERT_NE(b, nullptr); + ASSERT_THAT(*b->value, IsIntEq(1)); + + auto c = a->value->attrs->find(createSymbol("c")); + ASSERT_NE(c, nullptr); + ASSERT_THAT(*c->value, IsIntEq(2)); + } + + INSTANTIATE_TEST_SUITE_P( + attrsetMergeLazy, + AttrSetMergeTrvialExpressionTest, + testing::Values( + "{ a.b = 1; a.c = 2; }", + "{ a = { b = 1; }; a = { c = 2; }; }" + ) + ); + + TEST_F(TrivialExpressionTest, functor) { + auto v = eval("{ __functor = self: arg: self.v + arg; v = 10; } 5"); + ASSERT_THAT(v, IsIntEq(15)); + } + + TEST_F(TrivialExpressionTest, bindOr) { + auto v = eval("{ or = 1; }"); + ASSERT_THAT(v, IsAttrsOfSize(1)); + auto b = v.attrs->find(createSymbol("or")); + ASSERT_NE(b, nullptr); + ASSERT_THAT(*b->value, IsIntEq(1)); + } + + TEST_F(TrivialExpressionTest, orCantBeUsed) { + ASSERT_THROW(eval("let or = 1; in or"), Error); + } +} /* namespace nix */ From 7a3d5b2ff0e39ae0d7b393f454671a08da56776b Mon Sep 17 00:00:00 2001 From: Daniel Fullmer Date: Thu, 5 May 2022 10:46:48 -0700 Subject: [PATCH 73/79] Use correct URL for GitHub Enterprise For GitHub Enterprise, the API is accessed through a slightly different URL. See [1], where it says: > Use http(s)://[hostname]/api/v3 to access the API for GitHub > Enterprise Server. Also tested working on a GHE instance. [1] https://docs.github.com/en/enterprise-server@3.3/rest/guides/getting-started-with-the-rest-api --- src/libfetchers/github.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index 1bdf2759f..50b3150ee 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -243,7 +243,10 @@ struct GitHubInputScheme : GitArchiveInputScheme Hash getRevFromRef(nix::ref store, const Input & input) const override { auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); - auto url = fmt("https://api.%s/repos/%s/%s/commits/%s", // FIXME: check + auto url = fmt( + host == "github.com" + ? "https://api.%s/repos/%s/%s/commits/%s" + : "https://%s/api/v3/repos/%s/%s/commits/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef()); Headers headers = makeHeadersWithAuthTokens(host); @@ -262,7 +265,10 @@ struct GitHubInputScheme : GitArchiveInputScheme // FIXME: use regular /archive URLs instead? api.github.com // might have stricter rate limits. auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); - auto url = fmt("https://api.%s/repos/%s/%s/tarball/%s", // FIXME: check if this is correct for self hosted instances + auto url = fmt( + host == "github.com" + ? "https://api.%s/repos/%s/%s/commits/%s" + : "https://%s/api/v3/repos/%s/%s/commits/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), input.getRev()->to_string(Base16, false)); From 59d9551c25aa50cdfe65f6ce5cf9c07f9ff42736 Mon Sep 17 00:00:00 2001 From: Jan Tojnar Date: Sun, 8 May 2022 18:59:00 +0200 Subject: [PATCH 74/79] libexpr: Fix manual link in error message It was changed to the old manual in https://github.com/NixOS/nix/commit/8895fa70a4b05ddebbb5a23ea96464d5e01345fb --- src/libexpr/eval.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index d29ff5543..8d67691f0 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1590,7 +1590,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/#ss-functions.)", symbols[i.name]); +https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name]); } } From c060e93b3c48c5d6cfde88ee9080a10f73f00fe4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 22:01:15 +0000 Subject: [PATCH 75/79] Bump docker/login-action from 1 to 2 Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 2. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v1...v2) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d01ef4768..aae5b93e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,7 @@ jobs: - run: docker tag nix:$NIX_VERSION nixos/nix:$NIX_VERSION - run: docker tag nix:$NIX_VERSION nixos/nix:master - name: Login to Docker Hub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From a9cbc2857f4dd3af738b76edea00d692fbcee63c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 10 May 2022 16:42:35 +0200 Subject: [PATCH 76/79] nix develop: Find bin/bash in the bashInteractive outputs --- src/nix/develop.cc | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/nix/develop.cc b/src/nix/develop.cc index 1190b8348..3a99fff6f 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -512,9 +512,20 @@ struct CmdDevelop : Common, MixEnvironment Strings{"legacyPackages." + settings.thisSystem.get() + "."}, nixpkgsLockFlags); - shell = store->printStorePath( - Installable::toStorePath(getEvalStore(), store, Realise::Outputs, OperateOn::Output, bashInstallable)) - + "/bin/bash"; + bool found = false; + + for (auto & path : Installable::toStorePaths(getEvalStore(), store, Realise::Outputs, OperateOn::Output, {bashInstallable})) { + auto s = store->printStorePath(path) + "/bin/bash"; + if (pathExists(s)) { + shell = s; + found = true; + break; + } + } + + if (!found) + throw Error("package 'nixpkgs#bashInteractive' does not provide a 'bin/bash'"); + } catch (Error &) { ignoreException(); } From 54457382f948bff30e2879a7d9047616e134ac5b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 11 May 2022 11:36:56 +0200 Subject: [PATCH 77/79] Fix static build https://hydra.nixos.org/build/176211267 --- src/libexpr/tests/local.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/tests/local.mk b/src/libexpr/tests/local.mk index cb1c4a977..b95980cab 100644 --- a/src/libexpr/tests/local.mk +++ b/src/libexpr/tests/local.mk @@ -10,6 +10,6 @@ libexpr-tests_SOURCES := $(wildcard $(d)/*.cc) libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests -libexpr-tests_LIBS = libexpr libutil libstore +libexpr-tests_LIBS = libexpr libutil libstore libfetchers libexpr-tests_LDFLAGS := $(GTEST_LIBS) -lgmock From 1461e6cdda06f7f461114cce5b415f6d50381311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Wed, 11 May 2022 12:55:31 +0200 Subject: [PATCH 78/79] Stop the logger properly in legacy commands Ensures the logger is stopped on exit in legacy commands. Without this, when using `nix-build --log-format bar` and stopping nix with CTRL+C, the bar is not cleared from the screen. --- src/nix-build/nix-build.cc | 4 ---- src/nix-env/nix-env.cc | 2 -- src/nix-store/nix-store.cc | 2 -- src/nix/main.cc | 4 ++-- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc index 519855ea3..426f23905 100644 --- a/src/nix-build/nix-build.cc +++ b/src/nix-build/nix-build.cc @@ -543,8 +543,6 @@ static void main_nix_build(int argc, char * * argv) restoreProcessContext(); - logger->stop(); - execvp(shell->c_str(), argPtrs.data()); throw SysError("executing shell '%s'", *shell); @@ -603,8 +601,6 @@ static void main_nix_build(int argc, char * * argv) outPaths.push_back(outputPath); } - logger->stop(); - for (auto & path : outPaths) std::cout << store->printStorePath(path) << '\n'; } diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 96f3c3b26..c412bb814 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -1489,8 +1489,6 @@ static int main_nix_env(int argc, char * * argv) globals.state->printStats(); - logger->stop(); - return 0; } } diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 153b84137..9163eefd0 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -1095,8 +1095,6 @@ static int main_nix_store(int argc, char * * argv) op(opFlags, opArgs); - logger->stop(); - return 0; } } diff --git a/src/nix/main.cc b/src/nix/main.cc index 6d0f6ce6e..dadb54306 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -261,6 +261,8 @@ void mainWrapped(int argc, char * * argv) } #endif + Finally f([] { logger->stop(); }); + programPath = argv[0]; auto programName = std::string(baseNameOf(programPath)); @@ -279,8 +281,6 @@ void mainWrapped(int argc, char * * argv) verbosity = lvlInfo; } - Finally f([] { logger->stop(); }); - NixArgs args; if (argc == 2 && std::string(argv[1]) == "__dump-args") { From 831e2743ea0bac57ebccb73f532667a194b938d7 Mon Sep 17 00:00:00 2001 From: Norbert Melzer Date: Thu, 12 May 2022 00:56:39 +0200 Subject: [PATCH 79/79] fix GitHub URL template --- src/libfetchers/github.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index 0721a13f2..0631fb6e8 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -267,8 +267,8 @@ struct GitHubInputScheme : GitArchiveInputScheme auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); auto url = fmt( host == "github.com" - ? "https://api.%s/repos/%s/%s/commits/%s" - : "https://%s/api/v3/repos/%s/%s/commits/%s", + ? "https://api.%s/repos/%s/%s/tarball/%s" + : "https://%s/api/v3/repos/%s/%s/tarball/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), input.getRev()->to_string(Base16, false));