From 400d70a3a9ab494b2eebf5522789f89d16d000b2 Mon Sep 17 00:00:00 2001 From: toonn <toonn@toonn.io> Date: Tue, 22 Feb 2022 16:28:24 +0100 Subject: [PATCH 001/171] doc: Add detailed uninstall section for macOS The multi-user installation on macOS, which is now the only option, has gotten complicated enough that it discourages some users from checking Nix out for fear of being left with a "dirty" system. Detailed uninstallation instructions should make this less of an issue. --- .../src/installation/installing-binary.md | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index 4367654a2..ba90ce280 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -84,7 +84,9 @@ The installer will modify `/etc/bashrc`, and `/etc/zshrc` if they exist. The installer will first back up these files with a `.backup-before-nix` extension. The installer will also create `/etc/profile.d/nix.sh`. -You can uninstall Nix with the following commands: +## Uninstalling + +### Linux ```console sudo rm -rf /etc/profile/nix.sh /etc/nix /nix ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels @@ -95,15 +97,87 @@ sudo systemctl stop nix-daemon.service sudo systemctl disable nix-daemon.socket sudo systemctl disable nix-daemon.service sudo systemctl daemon-reload - -# If you are on macOS, you will need to run: -sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist -sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist ``` There may also be references to Nix in `/etc/profile`, `/etc/bashrc`, and `/etc/zshrc` which you may remove. +### macOS + +1. Edit `/etc/zshrc` and `/etc/bashrc` to remove the lines sourcing + `nix-daemon.sh`, which should look like this: + + ```bash + # Nix + if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then + . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' + fi + # End Nix + ``` + + If these files haven't been altered since installing Nix you can simply put + the backups back in place: + + ```console + sudo mv /etc/zshrc.backup-before-nix /etc/zshrc + sudo mv /etc/bashrc.backup-before-nix /etc/bashrc + ``` + + This will stop shells from sourcing the file and bringing everything you + installed using Nix in scope. + +2. Stop and remove the Nix daemon services: + + ```console + sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist + sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist + sudo launchctl unload /Library/LaunchDaemons/org.nixos.activate-system.plist + sudo rm /Library/LaunchDaemons/org.nixos.activate-system.plist + ``` + + This stops the Nix daemon and prevents it from being started next time you + boot the system. + +3. Remove the `nixbld` group and the `_nixbuildN` users: + + ```console + sudo dscl . -delete /Groups/nixbld + for u in $(sudo dscl . -list /Users | grep _nixbld); do sudo dscl . -delete /Users/$u; done + ``` + + This will remove all the build users that no longer serve a purpose. + +4. Edit fstab using `sudo vifs` to remove the line mounting the Nix Store + volume on `/nix`, which looks like this, + `LABEL=Nix\040Store /nix apfs rw,nobrowse`. This will prevent automatic + mounting of the Nix Store volume. + +5. Edit `/etc/synthetic.conf` to remove the `nix` line. If this is the only + line in the file you can remove it entirely, `sudo rm /etc/synthetic.conf`. + This will prevent the creation of the empty `/nix` directory to provide a + mountpoint for the Nix Store volume. + +6. Remove the files Nix added to your system: + + ```console + sudo rm -rf /etc/nix /var/root/.nix-profile /var/root/.nix-defexpr /var/root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels + ``` + + This gets rid of any data Nix may have created except for the store which is + removed next. + +7. Remove the Nix Store volume: + + ```console + sudo diskutil apfs deleteVolume /nix + ``` + + This will remove the Nix Store volume and everything that was added to the + store. + +The change to `/etc/synthetic.conf` will only take effect after a reboot but +you shouldn't have any traces of Nix left on your system. + # macOS Installation <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a> <!-- Note: anchors above to catch permalinks to old explanations --> From 064cad7e9fec88a2906dd0f87084278ec119f3af Mon Sep 17 00:00:00 2001 From: toonn <toonn@toonn.io> Date: Fri, 25 Feb 2022 10:32:45 +0100 Subject: [PATCH 002/171] doc: Add macOS uninstall note about /nix Clarify that `/nix` being present after the uninstall is normal and it will only disappear after a reboot. Co-authored-by: Travis A. Everett <travis.a.everett@gmail.com> --- doc/manual/src/installation/installing-binary.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index ba90ce280..ea5c9febe 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -175,8 +175,16 @@ and `/etc/zshrc` which you may remove. This will remove the Nix Store volume and everything that was added to the store. -The change to `/etc/synthetic.conf` will only take effect after a reboot but -you shouldn't have any traces of Nix left on your system. +> **Note** +> +> After you complete the steps here, you will still have an empty `/nix` +> directory. This is an expected sign of a successful uninstall. The empty +> `/nix` directory will disappear the next time you reboot. +> +> You do not have to reboot to finish uninstalling Nix. The uninstall is +> complete. macOS (Catalina+) directly controls root directories and its +> read-only root will prevent you from manually deleting the empty `/nix` +> mountpoint. # macOS Installation <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a> <!-- Note: anchors above to catch permalinks to old explanations --> From 2df23e2b3e9dd456a42b93264ccd5390e88d8542 Mon Sep 17 00:00:00 2001 From: toonn <toonn@toonn.io> Date: Fri, 25 Feb 2022 10:50:01 +0100 Subject: [PATCH 003/171] doc: Drop nix-darwin service from macOS uninstall --- doc/manual/src/installation/installing-binary.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index ea5c9febe..eb184c07b 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -131,8 +131,6 @@ and `/etc/zshrc` which you may remove. ```console sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist - sudo launchctl unload /Library/LaunchDaemons/org.nixos.activate-system.plist - sudo rm /Library/LaunchDaemons/org.nixos.activate-system.plist ``` This stops the Nix daemon and prevents it from being started next time you From 947d4761b35ffa5f689ad22d631696d35d28b941 Mon Sep 17 00:00:00 2001 From: toonn <toonn@toonn.io> Date: Sat, 26 Feb 2022 14:16:35 +0100 Subject: [PATCH 004/171] doc: Add removal of darwin-store LaunchDaemon The uninstall instructions used to accidentally remove the nix-darwin LaunchDaemon, this was dropped. However, the original intent was to remove the Store volume mounting LaunchDaemon. --- doc/manual/src/installation/installing-binary.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index eb184c07b..e5fb50088 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -131,6 +131,8 @@ and `/etc/zshrc` which you may remove. ```console sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist + sudo launchctl unload /Library/LaunchDaemons/org.nixos.darwin-store.plist + sudo rm /Library/LaunchDaemons/org.nixos.darwin-store.plist ``` This stops the Nix daemon and prevents it from being started next time you From 7f5cf87d563a6f8b756c4ef985a6cc5b3b9dbcf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= <n@monade.li> Date: Fri, 18 Feb 2022 13:19:57 +0100 Subject: [PATCH 005/171] Accept and discard fragments in getFlakeRefForCompletion Otherwise trying to complete `nix build foo#bar --update-input <Tab>` fails with "unexpected fragment" --- src/libcmd/command.hh | 2 +- src/libcmd/installables.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 0f6125f11..f1ef9e69c 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -134,7 +134,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand std::optional<FlakeRef> getFlakeRefForCompletion() override { - return parseFlakeRef(_installable, absPath(".")); + return parseFlakeRefWithFragment(_installable, absPath(".")).first; } private: diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 3209456bf..1af0675c6 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -905,10 +905,10 @@ std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion() { if (_installables.empty()) { if (useDefaultInstallables()) - return parseFlakeRef(".", absPath(".")); + return parseFlakeRefWithFragment(".", absPath(".")).first; return {}; } - return parseFlakeRef(_installables.front(), absPath(".")); + return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first; } InstallableCommand::InstallableCommand() From 7ddcb3920629bfaa0caf17b54eb63f3f951ba485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= <n@monade.li> Date: Sat, 19 Feb 2022 18:36:02 +0100 Subject: [PATCH 006/171] Add shell completion for --override-input --- src/libcmd/installables.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 1af0675c6..13e85b1bb 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -98,6 +98,14 @@ MixFlakeOptions::MixFlakeOptions() lockFlags.inputOverrides.insert_or_assign( flake::parseInputPath(inputPath), parseFlakeRef(flakeRef, absPath("."), true)); + }}, + .completer = {[&](size_t n, std::string_view prefix) { + if (n == 0) { + if (auto flakeRef = getFlakeRefForCompletion()) + completeFlakeInputPath(getEvalState(), *flakeRef, prefix); + } else if (n == 1) { + completeFlakeRef(getEvalState()->store, prefix); + } }} }); From 5f06a91bf77e45c580202324ba2a5f0338a78b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= <n@monade.li> Date: Sat, 19 Feb 2022 18:51:18 +0100 Subject: [PATCH 007/171] Fix completion of nested attributes in completeInstallable Without this, completing `nix eval -f file.nix foo.<Tab>` suggests `bar` instead of `foo.bar`, which messes up the command --- src/libcmd/installables.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 13e85b1bb..c4fefb971 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -187,6 +187,8 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() void SourceExprCommand::completeInstallable(std::string_view prefix) { if (file) { + completionType = ctAttrs; + evalSettings.pureEval = false; auto state = getEvalState(); Expr *e = state->parseExprFromFile( @@ -215,13 +217,14 @@ void SourceExprCommand::completeInstallable(std::string_view prefix) Value v2; state->autoCallFunction(*autoArgs, v1, v2); - completionType = ctAttrs; - if (v2.type() == nAttrs) { for (auto & i : *v2.attrs) { std::string name = i.name; if (name.find(searchWord) == 0) { - completions->add(i.name); + if (prefix_ == "") + completions->add(name); + else + completions->add(prefix_ + "." + name); } } } @@ -249,6 +252,8 @@ void completeFlakeRefWithFragment( if (hash == std::string::npos) { completeFlakeRef(evalState->store, prefix); } else { + completionType = ctAttrs; + auto fragment = prefix.substr(hash + 1); auto flakeRefS = std::string(prefix.substr(0, hash)); // FIXME: do tilde expansion. @@ -264,8 +269,6 @@ void completeFlakeRefWithFragment( flake. */ attrPathPrefixes.push_back(""); - completionType = ctAttrs; - for (auto & attrPathPrefixS : attrPathPrefixes) { auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS); auto attrPathS = attrPathPrefixS + std::string(fragment); From a6d7cd418385e20feab8d7260a7251f218a0d5bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= <n@monade.li> Date: Fri, 18 Feb 2022 13:24:39 +0100 Subject: [PATCH 008/171] Ensure the completion marker is not processed beyond completion I was surprised to see an error mentioning ___COMPLETE___ when trying to complete a flag argument that had no completer implemented --- src/libutil/args.cc | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/libutil/args.cc b/src/libutil/args.cc index f970c0e9e..38c748be0 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -127,11 +127,11 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) if (flag.handler.arity == ArityAny) break; throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); } - if (flag.completer) - if (auto prefix = needsCompletion(*pos)) { - anyCompleted = true; + if (auto prefix = needsCompletion(*pos)) { + anyCompleted = true; + if (flag.completer) flag.completer(n, *prefix); - } + } args.push_back(*pos++); } if (!anyCompleted) @@ -146,6 +146,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end) && hasPrefix(name, std::string(*prefix, 2))) completions->add("--" + name, flag->description); } + return false; } auto i = longFlags.find(std::string(*pos, 2)); if (i == longFlags.end()) return false; @@ -187,10 +188,12 @@ bool Args::processArgs(const Strings & args, bool finish) { std::vector<std::string> ss; for (const auto &[n, s] : enumerate(args)) { - ss.push_back(s); - if (exp.completer) - if (auto prefix = needsCompletion(s)) + if (auto prefix = needsCompletion(s)) { + ss.push_back(*prefix); + if (exp.completer) exp.completer(n, *prefix); + } else + ss.push_back(s); } exp.handler.fun(ss); expectedArgs.pop_front(); @@ -322,16 +325,16 @@ MultiCommand::MultiCommand(const Commands & commands_) .optional = true, .handler = {[=](std::string s) { assert(!command); - if (auto prefix = needsCompletion(s)) { - for (auto & [name, command] : commands) - if (hasPrefix(name, *prefix)) - completions->add(name); - } auto i = commands.find(s); if (i == commands.end()) throw UsageError("'%s' is not a recognised command", s); command = {s, i->second()}; command->second->parent = this; + }}, + .completer = {[&](size_t, std::string_view prefix) { + for (auto & [name, command] : commands) + if (hasPrefix(name, prefix)) + completions->add(name); }} }); From 5461ff532d6169be86af703b15cfb49569732cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= <n@monade.li> Date: Fri, 18 Feb 2022 13:26:40 +0100 Subject: [PATCH 009/171] Make completeDir follow symlinks Allows completing `nix why-depends /run/cur<Tab>` to /run/current-system --- src/libutil/args.cc | 2 +- src/libutil/util.cc | 9 +++++++++ src/libutil/util.hh | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 38c748be0..b60c609a6 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -290,7 +290,7 @@ static void _completePath(std::string_view prefix, bool onlyDirs) if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { for (size_t i = 0; i < globbuf.gl_pathc; ++i) { if (onlyDirs) { - auto st = lstat(globbuf.gl_pathv[i]); + auto st = stat(globbuf.gl_pathv[i]); if (!S_ISDIR(st.st_mode)) continue; } completions->add(globbuf.gl_pathv[i]); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index b833038a9..deaa17a7f 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -215,6 +215,15 @@ bool isDirOrInDir(std::string_view path, std::string_view dir) } +struct stat stat(const Path & path) +{ + struct stat st; + if (stat(path.c_str(), &st)) + throw SysError("getting status of '%1%'", path); + return st; +} + + struct stat lstat(const Path & path) { struct stat st; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 20591952d..94ae8ab7d 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -77,6 +77,7 @@ bool isInDir(std::string_view path, std::string_view dir); bool isDirOrInDir(std::string_view path, std::string_view dir); /* Get status of `path'. */ +struct stat stat(const Path & path); struct stat lstat(const Path & path); /* Return true iff the given path exists. */ From 55c6906701ee7fc7e915f7889fea86957b020f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= <n@monade.li> Date: Sat, 19 Feb 2022 14:26:34 +0100 Subject: [PATCH 010/171] Perform tilde expansion when completing flake fragments Allows completing `nix build ~/flake#<Tab>`. We can implement expansion for `~user` later if needed. Not using wordexp(3) since that expands way too much. --- misc/bash/completion.sh | 2 +- src/libcmd/installables.cc | 4 ++-- src/libutil/args.cc | 7 ++++--- src/libutil/util.cc | 11 +++++++++++ src/libutil/util.hh | 3 +++ 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/misc/bash/completion.sh b/misc/bash/completion.sh index 045053dee..9af695f5a 100644 --- a/misc/bash/completion.sh +++ b/misc/bash/completion.sh @@ -15,7 +15,7 @@ function _complete_nix { else COMPREPLY+=("$completion") fi - done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}" 2>/dev/null) + done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}" 2>/dev/null) __ltrim_colon_completions "$cur" } diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index c4fefb971..a0b8d0af5 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -1,4 +1,5 @@ #include "installables.hh" +#include "util.hh" #include "command.hh" #include "attr-path.hh" #include "common-eval-args.hh" @@ -256,8 +257,7 @@ void completeFlakeRefWithFragment( auto fragment = prefix.substr(hash + 1); auto flakeRefS = std::string(prefix.substr(0, hash)); - // FIXME: do tilde expansion. - auto flakeRef = parseFlakeRef(flakeRefS, absPath(".")); + auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath(".")); auto evalCache = openEvalCache(*evalState, std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags))); diff --git a/src/libutil/args.cc b/src/libutil/args.cc index b60c609a6..10f01af5b 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -282,12 +282,13 @@ static void _completePath(std::string_view prefix, bool onlyDirs) { completionType = ctFilenames; glob_t globbuf; - int flags = GLOB_NOESCAPE | GLOB_TILDE; + int flags = GLOB_NOESCAPE; #ifdef GLOB_ONLYDIR if (onlyDirs) flags |= GLOB_ONLYDIR; #endif - if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { + // using expandTilde here instead of GLOB_TILDE(_CHECK) so that ~<Tab> expands to /home/user/ + if (glob((expandTilde(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) { for (size_t i = 0; i < globbuf.gl_pathc; ++i) { if (onlyDirs) { auto st = stat(globbuf.gl_pathv[i]); @@ -295,8 +296,8 @@ static void _completePath(std::string_view prefix, bool onlyDirs) } completions->add(globbuf.gl_pathv[i]); } - globfree(&globbuf); } + globfree(&globbuf); } void completePath(size_t, std::string_view prefix) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index deaa17a7f..d9db24397 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -200,6 +200,17 @@ std::string_view baseNameOf(std::string_view path) } +std::string expandTilde(std::string_view path) +{ + // TODO: expand ~user ? + auto tilde = path.substr(0, 2); + if (tilde == "~/" || tilde == "~") + return getHome() + std::string(path.substr(1)); + else + return std::string(path); +} + + bool isInDir(std::string_view path, std::string_view dir) { return path.substr(0, 1) == "/" diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 94ae8ab7d..a1d0e0e6b 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -68,6 +68,9 @@ Path dirOf(const PathView path); following the final `/' (trailing slashes are removed). */ std::string_view baseNameOf(std::string_view path); +/* Perform tilde expansion on a path. */ +std::string expandTilde(std::string_view path); + /* Check whether 'path' is a descendant of 'dir'. Both paths must be canonicalized. */ bool isInDir(std::string_view path, std::string_view dir); From da7d8daa77c2cce5805947a012060b02be9e4a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= <n@monade.li> Date: Sat, 19 Feb 2022 16:59:52 +0100 Subject: [PATCH 011/171] Add shell completion for --override-flake Requires moving the MixEvalArgs class from libexpr to libcmd because that's where completeFlakeRef is. --- src/{libexpr => libcmd}/common-eval-args.cc | 4 ++++ src/{libexpr => libcmd}/common-eval-args.hh | 0 2 files changed, 4 insertions(+) rename src/{libexpr => libcmd}/common-eval-args.cc (95%) rename src/{libexpr => libcmd}/common-eval-args.hh (100%) diff --git a/src/libexpr/common-eval-args.cc b/src/libcmd/common-eval-args.cc similarity index 95% rename from src/libexpr/common-eval-args.cc rename to src/libcmd/common-eval-args.cc index e50ff244c..5b6e82388 100644 --- a/src/libexpr/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -7,6 +7,7 @@ #include "registry.hh" #include "flake/flakeref.hh" #include "store-api.hh" +#include "command.hh" namespace nix { @@ -59,6 +60,9 @@ MixEvalArgs::MixEvalArgs() fetchers::Attrs extraAttrs; if (to.subdir != "") extraAttrs["dir"] = to.subdir; fetchers::overrideRegistry(from.input, to.input, extraAttrs); + }}, + .completer = {[&](size_t, std::string_view prefix) { + completeFlakeRef(openStore(), prefix); }} }); diff --git a/src/libexpr/common-eval-args.hh b/src/libcmd/common-eval-args.hh similarity index 100% rename from src/libexpr/common-eval-args.hh rename to src/libcmd/common-eval-args.hh From 2191dab65726012b057402e13132dd7a062d8440 Mon Sep 17 00:00:00 2001 From: Kevin Amado <kamadorueda@gmail.com> Date: Fri, 11 Mar 2022 08:57:28 -0500 Subject: [PATCH 012/171] nix-fmt: add command --- src/nix/flake.cc | 12 ++++++++++ src/nix/fmt.cc | 53 +++++++++++++++++++++++++++++++++++++++++++++ src/nix/fmt.md | 53 +++++++++++++++++++++++++++++++++++++++++++++ tests/fmt.sh | 28 ++++++++++++++++++++++++ tests/fmt.simple.sh | 1 + tests/local.mk | 1 + 6 files changed, 148 insertions(+) create mode 100644 src/nix/fmt.cc create mode 100644 src/nix/fmt.md create mode 100644 tests/fmt.sh create mode 100755 tests/fmt.simple.sh diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 47a380238..9830ce841 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -527,6 +527,16 @@ struct CmdFlakeCheck : FlakeCommand } } + else if (name == "formatter") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) { + checkSystemName(attr.name, *attr.pos); + checkApp( + fmt("%s.%s", name, attr.name), + *attr.value, *attr.pos); + } + } + else if (name == "packages" || name == "devShells") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { @@ -1010,6 +1020,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON || (attrPath.size() == 1 && ( attrPath[0] == "defaultPackage" || attrPath[0] == "devShell" + || attrPath[0] == "formatter" || attrPath[0] == "nixosConfigurations" || attrPath[0] == "nixosModules" || attrPath[0] == "defaultApp" @@ -1059,6 +1070,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON else if ( (attrPath.size() == 2 && attrPath[0] == "defaultApp") || + (attrPath.size() == 2 && attrPath[0] == "formatter") || (attrPath.size() == 3 && attrPath[0] == "apps")) { auto aType = visitor.maybeGetAttr("type"); diff --git a/src/nix/fmt.cc b/src/nix/fmt.cc new file mode 100644 index 000000000..e5d44bd38 --- /dev/null +++ b/src/nix/fmt.cc @@ -0,0 +1,53 @@ +#include "command.hh" +#include "run.hh" + +using namespace nix; + +struct CmdFmt : SourceExprCommand { + std::vector<std::string> args; + + CmdFmt() { expectArgs({.label = "args", .handler = {&args}}); } + + std::string description() override { + return "reformat your code in the standard style"; + } + + std::string doc() override { + return + #include "fmt.md" + ; + } + + Category category() override { return catSecondary; } + + Strings getDefaultFlakeAttrPaths() override { + return Strings{"formatter." + settings.thisSystem.get()}; + } + + Strings getDefaultFlakeAttrPathPrefixes() override { return Strings{}; } + + void run(ref<Store> store) { + auto evalState = getEvalState(); + auto evalStore = getEvalStore(); + + auto installable = parseInstallable(store, "."); + auto app = installable->toApp(*evalState).resolve(evalStore, store); + + Strings programArgs{app.program}; + + // Propagate arguments from the CLI + if (args.empty()) { + // Format the current flake out of the box + programArgs.push_back("."); + } else { + // User wants more power, let them decide which paths to include/exclude + for (auto &i : args) { + programArgs.push_back(i); + } + } + + runProgramInStore(store, app.program, programArgs); + }; +}; + +static auto r2 = registerCommand<CmdFmt>("fmt"); diff --git a/src/nix/fmt.md b/src/nix/fmt.md new file mode 100644 index 000000000..1c78bb36f --- /dev/null +++ b/src/nix/fmt.md @@ -0,0 +1,53 @@ +R""( + +# Examples + +With [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt): + +```nix +# flake.nix +{ + outputs = { nixpkgs, self }: { + formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixpkgs-fmt; + }; +} +``` + +- Format the current flake: `$ nix fmt` + +- Format a specific folder or file: `$ nix fmt ./folder ./file.nix` + +With [nixfmt](https://github.com/serokell/nixfmt): + +```nix +# flake.nix +{ + outputs = { nixpkgs, self }: { + formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt; + }; +} +``` + +- Format specific files: `$ nix fmt ./file1.nix ./file2.nix` + +With [Alejandra](https://github.com/kamadorueda/alejandra): + +```nix +# flake.nix +{ + outputs = { nixpkgs, self }: { + formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.alejandra; + }; +} +``` + +- Format the current flake: `$ nix fmt` + +- Format a specific folder or file: `$ nix fmt ./folder ./file.nix` + +# Description + +`nix fmt` will rewrite all Nix files (\*.nix) to a canonical format +using the formatter specified in your flake. + +)"" diff --git a/tests/fmt.sh b/tests/fmt.sh new file mode 100644 index 000000000..7df1c82d3 --- /dev/null +++ b/tests/fmt.sh @@ -0,0 +1,28 @@ +source common.sh + +set -o pipefail + +clearStore +rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local + +cp ./simple.nix ./simple.builder.sh ./fmt.simple.sh ./config.nix $TEST_HOME + +cd $TEST_HOME + +nix fmt --help | grep "Format" + +cat << EOF > flake.nix +{ + outputs = _: { + formatter.$system = { + type = "app"; + program = ./fmt.simple.sh; + }; + }; +} +EOF +nix fmt ./file ./folder | grep 'Formatting: ./file ./folder' +nix flake check +nix flake show | grep -P 'x86_64-linux|x86_64-darwin' + +clearStore diff --git a/tests/fmt.simple.sh b/tests/fmt.simple.sh new file mode 100755 index 000000000..4c8c67ebb --- /dev/null +++ b/tests/fmt.simple.sh @@ -0,0 +1 @@ +echo Formatting: "${@}" diff --git a/tests/local.mk b/tests/local.mk index 8032fc38a..b42a5bccb 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -77,6 +77,7 @@ nix_tests = \ post-hook.sh \ function-trace.sh \ flake-local-settings.sh \ + fmt.sh \ eval-store.sh \ why-depends.sh \ import-derivation.sh \ From 91adfb8894b4b8183c2948712d56a97bb9d93d9f Mon Sep 17 00:00:00 2001 From: John Ericson <John.Ericson@Obsidian.Systems> Date: Wed, 10 Mar 2021 02:20:32 +0000 Subject: [PATCH 013/171] Create some type aliases for string Contexts --- src/libexpr/eval-cache.cc | 2 +- src/libexpr/eval-cache.hh | 2 +- src/libexpr/eval.cc | 6 +++--- src/libexpr/eval.hh | 2 +- src/libexpr/primops/context.cc | 2 +- src/libexpr/value.hh | 4 +++- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 188223957..7e5b5c9c4 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -254,7 +254,7 @@ struct AttrDb return {{rowId, attrs}}; } case AttrType::String: { - std::vector<std::pair<Path, std::string>> context; + NixStringContext context; if (!queryAttribute.isNull(3)) for (auto & s : tokenizeString<std::vector<std::string>>(queryAttribute.getStr(3), ";")) context.push_back(decodeContext(s)); diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh index 40f1d4ffc..c9a9bf471 100644 --- a/src/libexpr/eval-cache.hh +++ b/src/libexpr/eval-cache.hh @@ -52,7 +52,7 @@ struct misc_t {}; struct failed_t {}; typedef uint64_t AttrId; typedef std::pair<AttrId, Symbol> AttrKey; -typedef std::pair<std::string, std::vector<std::pair<Path, std::string>>> string_t; +typedef std::pair<std::string, NixStringContext> string_t; typedef std::variant< std::vector<Symbol>, diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index c65ef9738..126e0af8c 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1903,7 +1903,7 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos) /* Decode a context string ‘!<name>!<path>’ into a pair <path, name>. */ -std::pair<std::string, std::string> decodeContext(std::string_view s) +NixStringContextElem decodeContext(std::string_view s) { if (s.at(0) == '!') { size_t index = s.find("!", 1); @@ -1921,9 +1921,9 @@ void copyContext(const Value & v, PathSet & context) } -std::vector<std::pair<Path, std::string>> Value::getContext() +NixStringContext Value::getContext() { - std::vector<std::pair<Path, std::string>> res; + NixStringContext res; assert(internalType == tString); if (string.context) for (const char * * p = string.context; *p; ++p) diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index f1e00bae7..18480f290 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -430,7 +430,7 @@ std::string showType(const Value & v); /* Decode a context string ‘!<name>!<path>’ into a pair <path, name>. */ -std::pair<std::string, std::string> decodeContext(std::string_view s); +NixStringContextElem decodeContext(std::string_view s); /* If `path' refers to a directory, then append "/default.nix". */ Path resolveExprPath(Path path); diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index 3701bd442..ad4de3840 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -82,7 +82,7 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, drv = std::string(p, 1); path = &drv; } else if (p.at(0) == '!') { - std::pair<std::string, std::string> ctx = decodeContext(p); + NixStringContextElem ctx = decodeContext(p); drv = ctx.first; output = ctx.second; path = &drv; diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index d0fa93e92..ee9cb832a 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -64,6 +64,8 @@ class JSONPlaceholder; typedef int64_t NixInt; typedef double NixFloat; +typedef std::pair<Path, std::string> NixStringContextElem; +typedef std::vector<NixStringContextElem> NixStringContext; /* External values must descend from ExternalValueBase, so that * type-agnostic nix functions (e.g. showType) can be implemented @@ -368,7 +370,7 @@ public: non-trivial. */ bool isTrivial() const; - std::vector<std::pair<Path, std::string>> getContext(); + NixStringContext getContext(); auto listItems() { From 197feed51dd770929ba8f6f12aec50ed8597a747 Mon Sep 17 00:00:00 2001 From: John Ericson <John.Ericson@Obsidian.Systems> Date: Thu, 17 Mar 2022 22:29:15 +0000 Subject: [PATCH 014/171] Clean up `DerivationOutput`, and headers 1. `DerivationOutput` now as the `std::variant` as a base class. And the variants are given hierarchical names under `DerivationOutput`. In 8e0d0689be797f9e42f9b43b06f50c1af7f20b4a @matthewbauer and I didn't know a better idiom, and so we made it a field. But this sort of "newtype" is anoying for literals downstream. Since then we leaned the base class, inherit the constructors trick, e.g. used in `DerivedPath`. Switching to use that makes this more ergonomic, and consistent. 2. `store-api.hh` and `derivations.hh` are now independent. In bcde5456cc3295061a0726881c3e441444dd6680 I swapped the dependency, but I now know it is better to just keep on using incomplete types as much as possible for faster compilation and good separation of concerns. --- src/libexpr/get-drvs.cc | 1 + src/libexpr/primops.cc | 36 ++++------ src/libexpr/primops/context.cc | 1 + src/libstore/build/local-derivation-goal.cc | 14 ++-- src/libstore/derivations.cc | 80 +++++++++------------ src/libstore/derivations.hh | 35 +++++---- src/libstore/derived-path.cc | 1 + src/libstore/local-store.cc | 10 +-- src/libstore/misc.cc | 2 +- src/libstore/parsed-derivations.hh | 1 + src/libstore/repair-flag.hh | 7 ++ src/libstore/store-api.cc | 1 + src/libstore/store-api.hh | 4 +- src/nix/app.cc | 1 + src/nix/develop.cc | 12 ++-- src/nix/show-derivation.cc | 10 +-- 16 files changed, 113 insertions(+), 103 deletions(-) create mode 100644 src/libstore/repair-flag.hh diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 7f2ecb4f7..bb7e77b61 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -1,6 +1,7 @@ #include "get-drvs.hh" #include "util.hh" #include "eval-inline.hh" +#include "derivations.hh" #include "store-api.hh" #include "path-with-outputs.hh" diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 0980e75f4..4f9b6f0e0 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1163,26 +1163,24 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * auto outPath = state.store->makeFixedOutputPath(ingestionMethod, h, drvName); drv.env["out"] = state.store->printStorePath(outPath); - drv.outputs.insert_or_assign("out", DerivationOutput { - .output = DerivationOutputCAFixed { - .hash = FixedOutputHash { - .method = ingestionMethod, - .hash = std::move(h), - }, + drv.outputs.insert_or_assign("out", + DerivationOutput::CAFixed { + .hash = FixedOutputHash { + .method = ingestionMethod, + .hash = std::move(h), }, - }); + }); } else if (contentAddressed) { HashType ht = parseHashType(outputHashAlgo); for (auto & i : outputs) { drv.env[i] = hashPlaceholder(i); - drv.outputs.insert_or_assign(i, DerivationOutput { - .output = DerivationOutputCAFloating { + drv.outputs.insert_or_assign(i, + DerivationOutput::CAFloating { .method = ingestionMethod, .hashType = ht, - }, - }); + }); } } @@ -1196,10 +1194,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * for (auto & i : outputs) { drv.env[i] = ""; drv.outputs.insert_or_assign(i, - DerivationOutput { - .output = DerivationOutputInputAddressed { - .path = StorePath::dummy, - }, + DerivationOutput::InputAddressed { + .path = StorePath::dummy, }); } @@ -1213,9 +1209,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * case DrvHash::Kind::Deferred: for (auto & i : outputs) { drv.outputs.insert_or_assign(i, - DerivationOutput { - .output = DerivationOutputDeferred{}, - }); + DerivationOutput::Deferred { }); } break; case DrvHash::Kind::Regular: @@ -1223,10 +1217,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * auto outPath = state.store->makeOutputPath(i, h, drvName); drv.env[i] = state.store->printStorePath(outPath); drv.outputs.insert_or_assign(i, - DerivationOutput { - .output = DerivationOutputInputAddressed { - .path = std::move(outPath), - }, + DerivationOutput::InputAddressed { + .path = std::move(outPath), }); } break; diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index 3701bd442..324394d10 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -1,5 +1,6 @@ #include "primops.hh" #include "eval-inline.hh" +#include "derivations.hh" #include "store-api.hh" namespace nix { diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 4e763e570..8a301d4ac 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -2279,7 +2279,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() return res; }; - auto newInfoFromCA = [&](const DerivationOutputCAFloating outputHash) -> ValidPathInfo { + auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo { auto & st = outputStats.at(outputName); if (outputHash.method == FileIngestionMethod::Flat) { /* The output path should be a regular file without execute permission. */ @@ -2346,7 +2346,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() ValidPathInfo newInfo = std::visit(overloaded { - [&](const DerivationOutputInputAddressed & output) { + [&](const DerivationOutput::InputAddressed & output) { /* input-addressed case */ auto requiredFinalPath = output.path; /* Preemptively add rewrite rule for final hash, as that is @@ -2366,8 +2366,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs() return newInfo0; }, - [&](const DerivationOutputCAFixed & dof) { - auto newInfo0 = newInfoFromCA(DerivationOutputCAFloating { + [&](const DerivationOutput::CAFixed & dof) { + auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating { .method = dof.hash.method, .hashType = dof.hash.hash.type, }); @@ -2389,17 +2389,17 @@ DrvOutputs LocalDerivationGoal::registerOutputs() return newInfo0; }, - [&](DerivationOutputCAFloating & dof) { + [&](const DerivationOutput::CAFloating & dof) { return newInfoFromCA(dof); }, - [&](DerivationOutputDeferred) -> ValidPathInfo { + [&](const DerivationOutput::Deferred &) -> ValidPathInfo { // No derivation should reach that point without having been // rewritten first assert(false); }, - }, output.output); + }, output.raw()); /* FIXME: set proper permissions in restorePath() so we don't have to do another traversal. */ diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 1fe45bd87..3dd3a78d8 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -11,25 +11,25 @@ namespace nix { std::optional<StorePath> DerivationOutput::path(const Store & store, std::string_view drvName, std::string_view outputName) const { return std::visit(overloaded { - [](const DerivationOutputInputAddressed & doi) -> std::optional<StorePath> { + [](const DerivationOutput::InputAddressed & doi) -> std::optional<StorePath> { return { doi.path }; }, - [&](const DerivationOutputCAFixed & dof) -> std::optional<StorePath> { + [&](const DerivationOutput::CAFixed & dof) -> std::optional<StorePath> { return { dof.path(store, drvName, outputName) }; }, - [](const DerivationOutputCAFloating & dof) -> std::optional<StorePath> { + [](const DerivationOutput::CAFloating & dof) -> std::optional<StorePath> { return std::nullopt; }, - [](const DerivationOutputDeferred &) -> std::optional<StorePath> { + [](const DerivationOutput::Deferred &) -> std::optional<StorePath> { return std::nullopt; }, - }, output); + }, raw()); } -StorePath DerivationOutputCAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const { +StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const { return store.makeFixedOutputPath( hash.method, hash.hash, outputPathName(drvName, outputName)); @@ -179,35 +179,27 @@ static DerivationOutput parseDerivationOutput(const Store & store, const auto hashType = parseHashType(hashAlgo); if (hash != "") { validatePath(pathS); - return DerivationOutput { - .output = DerivationOutputCAFixed { - .hash = FixedOutputHash { - .method = std::move(method), - .hash = Hash::parseNonSRIUnprefixed(hash, hashType), - }, + return DerivationOutput::CAFixed { + .hash = FixedOutputHash { + .method = std::move(method), + .hash = Hash::parseNonSRIUnprefixed(hash, hashType), }, }; } else { settings.requireExperimentalFeature(Xp::CaDerivations); assert(pathS == ""); - return DerivationOutput { - .output = DerivationOutputCAFloating { - .method = std::move(method), - .hashType = std::move(hashType), - }, + return DerivationOutput::CAFloating { + .method = std::move(method), + .hashType = std::move(hashType), }; } } else { if (pathS == "") { - return DerivationOutput { - .output = DerivationOutputDeferred { } - }; + return DerivationOutput::Deferred { }; } validatePath(pathS); - return DerivationOutput { - .output = DerivationOutputInputAddressed { - .path = store.parseStorePath(pathS), - } + return DerivationOutput::InputAddressed { + .path = store.parseStorePath(pathS), }; } } @@ -335,27 +327,27 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs, if (first) first = false; else s += ','; s += '('; printUnquotedString(s, i.first); std::visit(overloaded { - [&](const DerivationOutputInputAddressed & doi) { + [&](const DerivationOutput::InputAddressed & doi) { s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(doi.path)); s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, ""); }, - [&](const DerivationOutputCAFixed & dof) { + [&](const DerivationOutput::CAFixed & dof) { s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first))); s += ','; printUnquotedString(s, dof.hash.printMethodAlgo()); s += ','; printUnquotedString(s, dof.hash.hash.to_string(Base16, false)); }, - [&](const DerivationOutputCAFloating & dof) { + [&](const DerivationOutput::CAFloating & dof) { s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType)); s += ','; printUnquotedString(s, ""); }, - [&](const DerivationOutputDeferred &) { + [&](const DerivationOutput::Deferred &) { s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, ""); } - }, i.second.output); + }, i.second.raw()); s += ')'; } @@ -423,13 +415,13 @@ DerivationType BasicDerivation::type() const std::optional<HashType> floatingHashType; for (auto & i : outputs) { std::visit(overloaded { - [&](const DerivationOutputInputAddressed &) { + [&](const DerivationOutput::InputAddressed &) { inputAddressedOutputs.insert(i.first); }, - [&](const DerivationOutputCAFixed &) { + [&](const DerivationOutput::CAFixed &) { fixedCAOutputs.insert(i.first); }, - [&](const DerivationOutputCAFloating & dof) { + [&](const DerivationOutput::CAFloating & dof) { floatingCAOutputs.insert(i.first); if (!floatingHashType) { floatingHashType = dof.hashType; @@ -438,10 +430,10 @@ DerivationType BasicDerivation::type() const throw Error("All floating outputs must use the same hash type"); } }, - [&](const DerivationOutputDeferred &) { + [&](const DerivationOutput::Deferred &) { deferredIAOutputs.insert(i.first); }, - }, i.second.output); + }, i.second.raw()); } if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { @@ -516,7 +508,7 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m case DerivationType::CAFixed: { std::map<std::string, Hash> outputHashes; for (const auto & i : drv.outputs) { - auto & dof = std::get<DerivationOutputCAFixed>(i.second.output); + auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw()); auto hash = hashString(htSHA256, "fixed:out:" + dof.hash.printMethodAlgo() + ":" + dof.hash.hash.to_string(Base16, false) + ":" @@ -672,27 +664,27 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr for (auto & i : drv.outputs) { out << i.first; std::visit(overloaded { - [&](const DerivationOutputInputAddressed & doi) { + [&](const DerivationOutput::InputAddressed & doi) { out << store.printStorePath(doi.path) << "" << ""; }, - [&](const DerivationOutputCAFixed & dof) { + [&](const DerivationOutput::CAFixed & dof) { out << store.printStorePath(dof.path(store, drv.name, i.first)) << dof.hash.printMethodAlgo() << dof.hash.hash.to_string(Base16, false); }, - [&](const DerivationOutputCAFloating & dof) { + [&](const DerivationOutput::CAFloating & dof) { out << "" << (makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType)) << ""; }, - [&](const DerivationOutputDeferred &) { + [&](const DerivationOutput::Deferred &) { out << "" << "" << ""; }, - }, i.second.output); + }, i.second.raw()); } worker_proto::write(store, out, drv.inputSrcs); out << drv.platform << drv.builder << drv.args; @@ -740,14 +732,12 @@ 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<DerivationOutputDeferred>(output.output)) { + if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) { auto & h = hashModulo.requireNoFixedNonDeferred(); auto outPath = store.makeOutputPath(outputName, h, drv.name); drv.env[outputName] = store.printStorePath(outPath); - output = DerivationOutput { - .output = DerivationOutputInputAddressed { - .path = std::move(outPath), - }, + output = DerivationOutput::InputAddressed { + .path = std::move(outPath), }; } } diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index 2fb18d7f7..6b92b2f21 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -4,6 +4,7 @@ #include "types.hh" #include "hash.hh" #include "content-address.hh" +#include "repair-flag.hh" #include "sync.hh" #include <map> @@ -44,19 +45,31 @@ struct DerivationOutputCAFloating */ struct DerivationOutputDeferred {}; -struct DerivationOutput +typedef std::variant< + DerivationOutputInputAddressed, + DerivationOutputCAFixed, + DerivationOutputCAFloating, + DerivationOutputDeferred +> _DerivationOutputRaw; + +struct DerivationOutput : _DerivationOutputRaw { - std::variant< - DerivationOutputInputAddressed, - DerivationOutputCAFixed, - DerivationOutputCAFloating, - DerivationOutputDeferred - > output; + using Raw = _DerivationOutputRaw; + using Raw::Raw; + + using InputAddressed = DerivationOutputInputAddressed; + using CAFixed = DerivationOutputCAFixed; + using CAFloating = DerivationOutputCAFloating; + using Deferred = DerivationOutputDeferred; /* Note, when you use this function you should make sure that you're passing the right derivation name. When in doubt, you should use the safer interface provided by BasicDerivation::outputsAndOptPaths */ std::optional<StorePath> path(const Store & store, std::string_view drvName, std::string_view outputName) const; + + inline const Raw & raw() const { + return static_cast<const Raw &>(*this); + } }; typedef std::map<std::string, DerivationOutput> DerivationOutputs; @@ -150,8 +163,6 @@ struct Derivation : BasicDerivation class Store; -enum RepairFlag : bool { NoRepair = false, Repair = true }; - /* Write a derivation to the Nix store, and return its path. */ StorePath writeDerivation(Store & store, const Derivation & drv, @@ -197,10 +208,10 @@ typedef std::variant< DrvHash, // Fixed-output derivation hashes CaOutputHashes -> DrvHashModuloRaw; +> _DrvHashModuloRaw; -struct DrvHashModulo : DrvHashModuloRaw { - using Raw = DrvHashModuloRaw; +struct DrvHashModulo : _DrvHashModuloRaw { + using Raw = _DrvHashModuloRaw; using Raw::Raw; /* Get hash, throwing if it is per-output CA hashes or a diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 0183bda35..319b1c790 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -1,4 +1,5 @@ #include "derived-path.hh" +#include "derivations.hh" #include "store-api.hh" #include <nlohmann/json.hpp> diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 9230be15a..75726a1ab 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -698,7 +698,7 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat std::optional<Hash> h; for (auto & i : drv.outputs) { std::visit(overloaded { - [&](const DerivationOutputInputAddressed & doia) { + [&](const DerivationOutput::InputAddressed & doia) { if (!h) { // somewhat expensive so we do lazily auto h0 = hashDerivationModulo(*this, drv, true); @@ -710,16 +710,16 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed)); envHasRightPath(doia.path, i.first); }, - [&](const DerivationOutputCAFixed & dof) { + [&](const DerivationOutput::CAFixed & dof) { StorePath path = makeFixedOutputPath(dof.hash.method, dof.hash.hash, drvName); envHasRightPath(path, i.first); }, - [&](const DerivationOutputCAFloating &) { + [&](const DerivationOutput::CAFloating &) { /* Nothing to check */ }, - [&](const DerivationOutputDeferred &) { + [&](const DerivationOutput::Deferred &) { }, - }, i.second.output); + }, i.second.raw()); } } diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc index 6409874ff..1f0bae7fe 100644 --- a/src/libstore/misc.cc +++ b/src/libstore/misc.cc @@ -87,7 +87,7 @@ std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv) { auto out = drv.outputs.find("out"); if (out != drv.outputs.end()) { - if (auto v = std::get_if<DerivationOutputCAFixed>(&out->second.output)) + if (const auto * v = std::get_if<DerivationOutput::CAFixed>(&out->second.raw())) return v->hash; } return std::nullopt; diff --git a/src/libstore/parsed-derivations.hh b/src/libstore/parsed-derivations.hh index effcf099d..95bec21e8 100644 --- a/src/libstore/parsed-derivations.hh +++ b/src/libstore/parsed-derivations.hh @@ -1,5 +1,6 @@ #pragma once +#include "derivations.hh" #include "store-api.hh" #include <nlohmann/json_fwd.hpp> diff --git a/src/libstore/repair-flag.hh b/src/libstore/repair-flag.hh new file mode 100644 index 000000000..a13cda312 --- /dev/null +++ b/src/libstore/repair-flag.hh @@ -0,0 +1,7 @@ +#pragma once + +namespace nix { + +enum RepairFlag : bool { NoRepair = false, Repair = true }; + +} diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 86fa6a211..59937be4d 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -1,6 +1,7 @@ #include "crypto.hh" #include "fs-accessor.hh" #include "globals.hh" +#include "derivations.hh" #include "store-api.hh" #include "util.hh" #include "nar-info-disk-cache.hh" diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index 635a82a2a..0c8a4db56 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -10,8 +10,8 @@ #include "sync.hh" #include "globals.hh" #include "config.hh" -#include "derivations.hh" #include "path-info.hh" +#include "repair-flag.hh" #include <atomic> #include <limits> @@ -62,6 +62,8 @@ MakeError(BadStorePath, Error); MakeError(InvalidStoreURI, Error); +struct BasicDerivation; +struct Derivation; class FSAccessor; class NarInfoDiskCache; class Store; diff --git a/src/nix/app.cc b/src/nix/app.cc index 2563180fb..30e138a96 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -4,6 +4,7 @@ #include "eval-cache.hh" #include "names.hh" #include "command.hh" +#include "derivations.hh" namespace nix { diff --git a/src/nix/develop.cc b/src/nix/develop.cc index dafcafd79..6baf539c3 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -196,14 +196,14 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore drv.inputSrcs.insert(std::move(getEnvShPath)); if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { for (auto & output : drv.outputs) { - output.second = { - .output = DerivationOutputDeferred{}, - }; + output.second = DerivationOutput::Deferred {}, drv.env[output.first] = hashPlaceholder(output.first); } } else { for (auto & output : drv.outputs) { - output.second = { .output = DerivationOutputInputAddressed { .path = StorePath::dummy } }; + output.second = DerivationOutput::InputAddressed { + .path = StorePath::dummy, + }; drv.env[output.first] = ""; } auto h0 = hashDerivationModulo(*evalStore, drv, true); @@ -211,7 +211,9 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore for (auto & output : drv.outputs) { auto outPath = store->makeOutputPath(output.first, h, drv.name); - output.second = { .output = DerivationOutputInputAddressed { .path = outPath } }; + output.second = DerivationOutput::InputAddressed { + .path = outPath, + }; drv.env[output.first] = store->printStorePath(outPath); } } diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc index 61a02c9b3..0d9655732 100644 --- a/src/nix/show-derivation.cc +++ b/src/nix/show-derivation.cc @@ -65,19 +65,19 @@ struct CmdShowDerivation : InstallablesCommand auto & outputName = _outputName; // work around clang bug auto outputObj { outputsObj.object(outputName) }; std::visit(overloaded { - [&](const DerivationOutputInputAddressed & doi) { + [&](const DerivationOutput::InputAddressed & doi) { outputObj.attr("path", store->printStorePath(doi.path)); }, - [&](const DerivationOutputCAFixed & dof) { + [&](const DerivationOutput::CAFixed & dof) { outputObj.attr("path", store->printStorePath(dof.path(*store, drv.name, outputName))); outputObj.attr("hashAlgo", dof.hash.printMethodAlgo()); outputObj.attr("hash", dof.hash.hash.to_string(Base16, false)); }, - [&](const DerivationOutputCAFloating & dof) { + [&](const DerivationOutput::CAFloating & dof) { outputObj.attr("hashAlgo", makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType)); }, - [&](const DerivationOutputDeferred &) {}, - }, output.output); + [&](const DerivationOutput::Deferred &) {}, + }, output.raw()); } } From 8496be7defed3584c9fcd3b5d905fb84a9515d6f Mon Sep 17 00:00:00 2001 From: John Ericson <John.Ericson@Obsidian.Systems> Date: Fri, 18 Mar 2022 02:07:31 +0000 Subject: [PATCH 015/171] Use Deferred when building an input-addressed drv Easier than using dummy path with input addressed. --- src/libexpr/primops.cc | 22 ++++++++-------------- src/nix/develop.cc | 4 +--- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 4f9b6f0e0..489bee826 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1194,9 +1194,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * for (auto & i : outputs) { drv.env[i] = ""; drv.outputs.insert_or_assign(i, - DerivationOutput::InputAddressed { - .path = StorePath::dummy, - }); + DerivationOutput::Deferred { }); } // Regular, non-CA derivation should always return a single hash and not @@ -1207,19 +1205,15 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * auto & h = drvHash.hash; switch (drvHash.kind) { case DrvHash::Kind::Deferred: - for (auto & i : outputs) { - drv.outputs.insert_or_assign(i, - DerivationOutput::Deferred { }); - } + /* Outputs already deferred, nothing to do */ break; case DrvHash::Kind::Regular: - for (auto & i : outputs) { - auto outPath = state.store->makeOutputPath(i, h, drvName); - drv.env[i] = state.store->printStorePath(outPath); - drv.outputs.insert_or_assign(i, - DerivationOutput::InputAddressed { - .path = std::move(outPath), - }); + for (auto & [outputName, output] : drv.outputs) { + auto outPath = state.store->makeOutputPath(outputName, h, drvName); + drv.env[outputName] = state.store->printStorePath(outPath); + output = DerivationOutput::InputAddressed { + .path = std::move(outPath), + }; } break; } diff --git a/src/nix/develop.cc b/src/nix/develop.cc index 6baf539c3..d2f9b5a6a 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -201,9 +201,7 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore } } else { for (auto & output : drv.outputs) { - output.second = DerivationOutput::InputAddressed { - .path = StorePath::dummy, - }; + output.second = DerivationOutput::Deferred { }; drv.env[output.first] = ""; } auto h0 = hashDerivationModulo(*evalStore, drv, true); From 049fae155a18784ca59d194bf3e579fadbc3b48f Mon Sep 17 00:00:00 2001 From: John Ericson <John.Ericson@Obsidian.Systems> Date: Fri, 18 Mar 2022 02:15:32 +0000 Subject: [PATCH 016/171] Avoid some pointless copying of drvs --- src/libexpr/primops.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 489bee826..2dc33103d 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1199,7 +1199,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * // Regular, non-CA derivation should always return a single hash and not // hash per output. - auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true); + auto hashModulo = hashDerivationModulo(*state.store, drv, true); std::visit(overloaded { [&](const DrvHash & drvHash) { auto & h = drvHash.hash; @@ -1240,7 +1240,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * However, we don't bother doing this for floating CA derivations because their "hash modulo" is indeterminate until built. */ if (drv.type() != DerivationType::CAFloating) { - auto h = hashDerivationModulo(*state.store, Derivation(drv), false); + auto h = hashDerivationModulo(*state.store, drv, false); drvHashes.lock()->insert_or_assign(drvPath, h); } From a544ed7684bdf5fa3c0b78d40913f5be3f73f5a7 Mon Sep 17 00:00:00 2001 From: John Ericson <John.Ericson@Obsidian.Systems> Date: Fri, 18 Mar 2022 00:36:52 +0000 Subject: [PATCH 017/171] Generalize `DerivationType` in preparation for impure derivations --- src/build-remote/build-remote.cc | 2 +- src/libexpr/primops.cc | 7 +- src/libstore/build/derivation-goal.cc | 35 ++++-- src/libstore/build/local-derivation-goal.cc | 14 +-- src/libstore/daemon.cc | 8 +- src/libstore/derivations.cc | 112 +++++++++++--------- src/libstore/derivations.hh | 62 +++++++---- src/libstore/local-store.cc | 1 + src/libstore/parsed-derivations.cc | 2 +- 9 files changed, 148 insertions(+), 95 deletions(-) diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc index 8c9133c17..ff8ba2724 100644 --- a/src/build-remote/build-remote.cc +++ b/src/build-remote/build-remote.cc @@ -300,7 +300,7 @@ connected: std::set<Realisation> missingRealisations; StorePathSet missingPaths; - if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) { + if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) { for (auto & outputName : wantedOutputs) { auto thisOutputHash = outputHashes.at(outputName); auto thisOutputId = DrvOutput{ thisOutputHash, outputName }; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 2dc33103d..bfa18f898 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1235,11 +1235,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * /* Optimisation, but required in read-only mode! because in that case we don't actually write store derivations, so we can't - read them later. - - However, we don't bother doing this for floating CA derivations because - their "hash modulo" is indeterminate until built. */ - if (drv.type() != DerivationType::CAFloating) { + read them later. */ + { auto h = hashDerivationModulo(*state.store, drv, false); drvHashes.lock()->insert_or_assign(drvPath, h); } diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index afed9bf16..40c445836 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -204,7 +204,7 @@ void DerivationGoal::haveDerivation() { trace("have derivation"); - if (drv->type() == DerivationType::CAFloating) + if (!drv->type().hasKnownOutputPaths()) settings.requireExperimentalFeature(Xp::CaDerivations); retrySubstitution = false; @@ -440,9 +440,28 @@ void DerivationGoal::inputsRealised() if (useDerivation) { auto & fullDrv = *dynamic_cast<Derivation *>(drv.get()); - if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && - ((!fullDrv.inputDrvs.empty() && derivationIsCA(fullDrv.type())) - || fullDrv.type() == DerivationType::DeferredInputAddressed)) { + auto drvType = fullDrv.type(); + bool resolveDrv = std::visit(overloaded { + [&](const DerivationType::InputAddressed & ia) { + /* must resolve if deferred. */ + return ia.deferred; + }, + [&](const DerivationType::ContentAddressed & ca) { + return !fullDrv.inputDrvs.empty() && ( + ca.fixed + /* Can optionally resolve if fixed, which is good + for avoiding unnecessary rebuilds. */ + ? settings.isExperimentalFeatureEnabled(Xp::CaDerivations) + /* Must resolve if floating and there are any inputs + drvs. */ + : true); + }, + }, drvType.raw()); + + if (resolveDrv) + { + settings.requireExperimentalFeature(Xp::CaDerivations); + /* We are be able to resolve this derivation based on the now-known results of dependencies. If so, we become a stub goal aliasing that resolved derivation goal */ @@ -501,7 +520,7 @@ void DerivationGoal::inputsRealised() /* Don't repeat fixed-output derivations since they're already verified by their output hash.*/ - nrRounds = derivationIsFixed(derivationType) ? 1 : settings.buildRepeat + 1; + nrRounds = derivationType.isFixed() ? 1 : settings.buildRepeat + 1; /* Okay, try to build. Note that here we don't wait for a build slot to become available, since we don't need one if there is a @@ -908,7 +927,7 @@ void DerivationGoal::buildDone() st = dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic : statusOk(status) ? BuildResult::OutputRejected : - derivationIsImpure(derivationType) || diskFull ? BuildResult::TransientFailure : + derivationType.isImpure() || diskFull ? BuildResult::TransientFailure : BuildResult::PermanentFailure; } @@ -1221,7 +1240,7 @@ void DerivationGoal::flushLine() std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap() { - if (!useDerivation || drv->type() != DerivationType::CAFloating) { + if (!useDerivation || drv->type().hasKnownOutputPaths()) { std::map<std::string, std::optional<StorePath>> res; for (auto & [name, output] : drv->outputs) res.insert_or_assign(name, output.path(worker.store, drv->name, name)); @@ -1233,7 +1252,7 @@ std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDeri OutputPathMap DerivationGoal::queryDerivationOutputMap() { - if (!useDerivation || drv->type() != DerivationType::CAFloating) { + if (!useDerivation || drv->type().hasKnownOutputPaths()) { OutputPathMap res; for (auto & [name, output] : drv->outputsAndOptPaths(worker.store)) res.insert_or_assign(name, *output.second); diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 8a301d4ac..75e7e6ca3 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -395,7 +395,7 @@ void LocalDerivationGoal::startBuilder() else if (settings.sandboxMode == smDisabled) useChroot = false; else if (settings.sandboxMode == smRelaxed) - useChroot = !(derivationIsImpure(derivationType)) && !noChroot; + useChroot = !(derivationType.isImpure()) && !noChroot; } auto & localStore = getLocalStore(); @@ -608,7 +608,7 @@ void LocalDerivationGoal::startBuilder() "nogroup:x:65534:\n", sandboxGid())); /* Create /etc/hosts with localhost entry. */ - if (!(derivationIsImpure(derivationType))) + if (!(derivationType.isImpure())) writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n"); /* Make the closure of the inputs available in the chroot, @@ -796,7 +796,7 @@ void LocalDerivationGoal::startBuilder() us. */ - if (!(derivationIsImpure(derivationType))) + if (!(derivationType.isImpure())) privateNetwork = true; userNamespaceSync.create(); @@ -1049,7 +1049,7 @@ void LocalDerivationGoal::initEnv() derivation, tell the builder, so that for instance `fetchurl' can skip checking the output. On older Nixes, this environment variable won't be set, so `fetchurl' will do the check. */ - if (derivationIsFixed(derivationType)) env["NIX_OUTPUT_CHECKED"] = "1"; + if (derivationType.isFixed()) env["NIX_OUTPUT_CHECKED"] = "1"; /* *Only* if this is a fixed-output derivation, propagate the values of the environment variables specified in the @@ -1060,7 +1060,7 @@ void LocalDerivationGoal::initEnv() to the builder is generally impure, but the output of fixed-output derivations is by definition pure (since we already know the cryptographic hash of the output). */ - if (derivationIsImpure(derivationType)) { + if (derivationType.isImpure()) { for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings())) env[i] = getEnv(i).value_or(""); } @@ -1674,7 +1674,7 @@ void LocalDerivationGoal::runChild() /* Fixed-output derivations typically need to access the network, so give them access to /etc/resolv.conf and so on. */ - if (derivationIsImpure(derivationType)) { + if (derivationType.isImpure()) { // Only use nss functions to resolve hosts and // services. Don’t use it for anything else that may // be configured for this system. This limits the @@ -1918,7 +1918,7 @@ void LocalDerivationGoal::runChild() sandboxProfile += "(import \"sandbox-defaults.sb\")\n"; - if (derivationIsImpure(derivationType)) + if (derivationType.isImpure()) sandboxProfile += "(import \"sandbox-network.sb\")\n"; /* Add the output paths we'll use at build-time to the chroot */ diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc index 9f21ecf36..de69b50ee 100644 --- a/src/libstore/daemon.cc +++ b/src/libstore/daemon.cc @@ -560,6 +560,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store, BuildMode buildMode = (BuildMode) readInt(from); logger->startWork(); + auto drvType = drv.type(); + /* Content-addressed derivations are trustless because their output paths are verified by their content alone, so any derivation is free to try to produce such a path. @@ -592,12 +594,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store, derivations, we throw out the precomputed output paths and just store the hashes, so there aren't two competing sources of truth an attacker could exploit. */ - if (drv.type() == DerivationType::InputAddressed && !trusted) + if (!(drvType.isCA() || trusted)) throw Error("you are not privileged to build input-addressed derivations"); /* Make sure that the non-input-addressed derivations that got this far are in fact content-addressed if we don't trust them. */ - assert(derivationIsCA(drv.type()) || trusted); + assert(drvType.isCA() || trusted); /* Recompute the derivation path when we cannot trust the original. */ if (!trusted) { @@ -606,7 +608,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store, original not-necessarily-resolved derivation to verify the drv derivation as adequate claim to the input-addressed output paths. */ - assert(derivationIsCA(drv.type())); + assert(drvType.isCA()); Derivation drv2; static_cast<BasicDerivation &>(drv2) = drv; diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 3dd3a78d8..7fed80387 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -36,47 +36,46 @@ StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view } -bool derivationIsCA(DerivationType dt) { - switch (dt) { - case DerivationType::InputAddressed: return false; - case DerivationType::CAFixed: return true; - case DerivationType::CAFloating: return true; - case DerivationType::DeferredInputAddressed: return false; - }; - // Since enums can have non-variant values, but making a `default:` would - // disable exhaustiveness warnings. - assert(false); +bool DerivationType::isCA() const { + /* Normally we do the full `std::visit` to make sure we have + exhaustively handled all variants, but so long as there is a + variant called `ContentAddressed`, it must be the only one for + which `isCA` is true for this to make sense!. */ + return std::holds_alternative<ContentAddressed>(raw()); } -bool derivationIsFixed(DerivationType dt) { - switch (dt) { - case DerivationType::InputAddressed: return false; - case DerivationType::CAFixed: return true; - case DerivationType::CAFloating: return false; - case DerivationType::DeferredInputAddressed: return false; - }; - assert(false); +bool DerivationType::isFixed() const { + return std::visit(overloaded { + [](const InputAddressed & ia) { + return false; + }, + [](const ContentAddressed & ca) { + return ca.fixed; + }, + }, raw()); } -bool derivationHasKnownOutputPaths(DerivationType dt) { - switch (dt) { - case DerivationType::InputAddressed: return true; - case DerivationType::CAFixed: return true; - case DerivationType::CAFloating: return false; - case DerivationType::DeferredInputAddressed: return false; - }; - assert(false); +bool DerivationType::hasKnownOutputPaths() const { + return std::visit(overloaded { + [](const InputAddressed & ia) { + return !ia.deferred; + }, + [](const ContentAddressed & ca) { + return ca.fixed; + }, + }, raw()); } -bool derivationIsImpure(DerivationType dt) { - switch (dt) { - case DerivationType::InputAddressed: return false; - case DerivationType::CAFixed: return true; - case DerivationType::CAFloating: return false; - case DerivationType::DeferredInputAddressed: return false; - }; - assert(false); +bool DerivationType::isImpure() const { + return std::visit(overloaded { + [](const InputAddressed & ia) { + return false; + }, + [](const ContentAddressed & ca) { + return !ca.pure; + }, + }, raw()); } @@ -439,18 +438,28 @@ DerivationType BasicDerivation::type() const if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { throw Error("Must have at least one output"); } else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { - return DerivationType::InputAddressed; + return DerivationType::InputAddressed { + .deferred = false, + }; } else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { if (fixedCAOutputs.size() > 1) // FIXME: Experimental feature? throw Error("Only one fixed output is allowed for now"); if (*fixedCAOutputs.begin() != "out") throw Error("Single fixed output must be named \"out\""); - return DerivationType::CAFixed; + return DerivationType::ContentAddressed { + .pure = false, + .fixed = true, + }; } else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) { - return DerivationType::CAFloating; + return DerivationType::ContentAddressed { + .pure = true, + .fixed = false, + }; } else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) { - return DerivationType::DeferredInputAddressed; + return DerivationType::InputAddressed { + .deferred = true, + }; } else { throw Error("Can't mix derivation output types"); } @@ -502,10 +511,10 @@ static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath & */ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs) { - auto kind = DrvHash::Kind::Regular; + auto type = drv.type(); + /* Return a fixed hash for fixed-output derivations. */ - switch (drv.type()) { - case DerivationType::CAFixed: { + if (type.isFixed()) { std::map<std::string, Hash> outputHashes; for (const auto & i : drv.outputs) { auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw()); @@ -517,14 +526,19 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m } return outputHashes; } - case DerivationType::CAFloating: - kind = DrvHash::Kind::Deferred; - break; - case DerivationType::InputAddressed: - break; - case DerivationType::DeferredInputAddressed: - break; - } + + auto kind = std::visit(overloaded { + [](const DerivationType::InputAddressed & ia) { + /* This might be a "pesimistically" deferred output, so we don't + "taint" the kind yet. */ + return DrvHash::Kind::Regular; + }, + [](const DerivationType::ContentAddressed & ca) { + return ca.fixed + ? DrvHash::Kind::Regular + : DrvHash::Kind::Deferred; + }, + }, drv.type().raw()); /* For other derivations, replace the inputs paths with recursive calls to this function. */ diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index 6b92b2f21..8dea90abf 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -85,30 +85,50 @@ typedef std::map<std::string, std::pair<DerivationOutput, std::optional<StorePat output IDs we are interested in. */ typedef std::map<StorePath, StringSet> DerivationInputs; -enum struct DerivationType : uint8_t { - InputAddressed, - DeferredInputAddressed, - CAFixed, - CAFloating, +struct DerivationType_InputAddressed { + bool deferred; }; -/* Do the outputs of the derivation have paths calculated from their content, - or from the derivation itself? */ -bool derivationIsCA(DerivationType); +struct DerivationType_ContentAddressed { + bool pure; + bool fixed; +}; -/* Is the content of the outputs fixed a-priori via a hash? Never true for - non-CA derivations. */ -bool derivationIsFixed(DerivationType); +typedef std::variant< + DerivationType_InputAddressed, + DerivationType_ContentAddressed +> _DerivationTypeRaw; -/* Is the derivation impure and needs to access non-deterministic resources, or - pure and can be sandboxed? Note that whether or not we actually sandbox the - derivation is controlled separately. Never true for non-CA derivations. */ -bool derivationIsImpure(DerivationType); +struct DerivationType : _DerivationTypeRaw { + using Raw = _DerivationTypeRaw; + using Raw::Raw; + using InputAddressed = DerivationType_InputAddressed; + using ContentAddressed = DerivationType_ContentAddressed; -/* Does the derivation knows its own output paths? - * Only true when there's no floating-ca derivation involved in the closure. - */ -bool derivationHasKnownOutputPaths(DerivationType); + + /* Do the outputs of the derivation have paths calculated from their content, + or from the derivation itself? */ + bool isCA() const; + + /* Is the content of the outputs fixed a-priori via a hash? Never true for + non-CA derivations. */ + bool isFixed() const; + + /* Is the derivation impure and needs to access non-deterministic resources, or + pure and can be sandboxed? Note that whether or not we actually sandbox the + derivation is controlled separately. Never true for non-CA derivations. */ + bool isImpure() const; + + /* Does the derivation knows its own output paths? + Only true when there's no floating-ca derivation involved in the + closure, or if fixed output. + */ + bool hasKnownOutputPaths() const; + + inline const Raw & raw() const { + return static_cast<const Raw &>(*this); + } +}; struct BasicDerivation { @@ -189,11 +209,11 @@ typedef std::map<std::string, Hash> CaOutputHashes; struct DrvHash { Hash hash; - enum struct Kind { + enum struct Kind: bool { // Statically determined derivations. // This hash will be directly used to compute the output paths Regular, - // Floating-output derivations (and their dependencies). + // Floating-output derivations (and their reverse dependencies). Deferred, }; diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 75726a1ab..46a547db1 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -718,6 +718,7 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat /* Nothing to check */ }, [&](const DerivationOutput::Deferred &) { + /* Nothing to check */ }, }, i.second.raw()); } diff --git a/src/libstore/parsed-derivations.cc b/src/libstore/parsed-derivations.cc index 8c65053e4..f2288a04e 100644 --- a/src/libstore/parsed-derivations.cc +++ b/src/libstore/parsed-derivations.cc @@ -93,7 +93,7 @@ StringSet ParsedDerivation::getRequiredSystemFeatures() const StringSet res; for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings())) res.insert(i); - if (!derivationHasKnownOutputPaths(drv.type())) + if (!drv.type().hasKnownOutputPaths()) res.insert("ca-derivations"); return res; } From d60f3cf6e9c904912199ea64156fea295494430a Mon Sep 17 00:00:00 2001 From: Florian Klink <flokli@flokli.de> Date: Thu, 17 Mar 2022 22:59:43 +0100 Subject: [PATCH 018/171] nix-daemon.conf.in: add tmpfiles file to create nix/daemon-socket directory nix-daemon.socket is used to socket-activate nix-daemon.service when /nix/var/nix/daemon-socket/socket is accessed. In container usecases, sometimes /nix/var/nix/daemon-socket is bind-mounted read-only into the container. In these cases, we want to skip starting nix-daemon.socket. However, since systemd 250, `ConditionPathIsReadWrite` is also not met if /nix/var/nix/daemon-socket doesn't exist at all. This means, a regular NixOS system will skip starting nix-daemon.socket: > [ 237.187747] systemd[1]: Nix Daemon Socket was skipped because of a failed condition check (ConditionPathIsReadWrite=/nix/var/nix/daemon-socket). To prevent this from happening, ship a tmpfiles file that'll cause the directory to be created if it doesn't exist already. In the case of NixOS, we can just add Nix to `systemd.tmpfiles.packages` and have these files picked up automatically. --- misc/systemd/local.mk | 3 ++- misc/systemd/nix-daemon.conf.in | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 misc/systemd/nix-daemon.conf.in diff --git a/misc/systemd/local.mk b/misc/systemd/local.mk index 1fa037485..76121a0f9 100644 --- a/misc/systemd/local.mk +++ b/misc/systemd/local.mk @@ -1,7 +1,8 @@ ifdef HOST_LINUX $(foreach n, nix-daemon.socket nix-daemon.service, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644))) + $(foreach n, nix-daemon.conf, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/tmpfiles.d, 0644))) - clean-files += $(d)/nix-daemon.socket $(d)/nix-daemon.service + clean-files += $(d)/nix-daemon.socket $(d)/nix-daemon.service $(d)/nix-daemon.conf endif diff --git a/misc/systemd/nix-daemon.conf.in b/misc/systemd/nix-daemon.conf.in new file mode 100644 index 000000000..e7b264234 --- /dev/null +++ b/misc/systemd/nix-daemon.conf.in @@ -0,0 +1 @@ +d @localstatedir@/nix/daemon-socket 0755 root root - - From 4d6a3806d24b54f06ddc0cf234ac993db028cf29 Mon Sep 17 00:00:00 2001 From: John Ericson <John.Ericson@Obsidian.Systems> Date: Sat, 12 Mar 2022 00:28:00 +0000 Subject: [PATCH 019/171] Decode string context straight to using `StorePath`s I gather decoding happens on demand, so I hope don't think this should have any perf implications one way or the other. --- src/libexpr/eval-cache.cc | 19 +++++++++++-------- src/libexpr/eval.cc | 19 ++++++++++++++----- src/libexpr/eval.hh | 4 ++-- src/libexpr/primops.cc | 8 ++++---- src/libexpr/primops/context.cc | 4 ++-- src/libexpr/value.hh | 6 ++++-- src/nix/app.cc | 2 +- 7 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 7e5b5c9c4..54fa9b741 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -21,6 +21,8 @@ struct AttrDb { std::atomic_bool failed{false}; + const Store & cfg; + struct State { SQLite db; @@ -33,8 +35,9 @@ struct AttrDb std::unique_ptr<Sync<State>> _state; - AttrDb(const Hash & fingerprint) - : _state(std::make_unique<Sync<State>>()) + AttrDb(const Store & cfg, const Hash & fingerprint) + : cfg(cfg) + , _state(std::make_unique<Sync<State>>()) { auto state(_state->lock()); @@ -257,7 +260,7 @@ struct AttrDb NixStringContext context; if (!queryAttribute.isNull(3)) for (auto & s : tokenizeString<std::vector<std::string>>(queryAttribute.getStr(3), ";")) - context.push_back(decodeContext(s)); + context.push_back(decodeContext(cfg, s)); return {{rowId, string_t{queryAttribute.getStr(2), context}}}; } case AttrType::Bool: @@ -274,10 +277,10 @@ struct AttrDb } }; -static std::shared_ptr<AttrDb> makeAttrDb(const Hash & fingerprint) +static std::shared_ptr<AttrDb> makeAttrDb(const Store & cfg, const Hash & fingerprint) { try { - return std::make_shared<AttrDb>(fingerprint); + return std::make_shared<AttrDb>(cfg, fingerprint); } catch (SQLiteError &) { ignoreException(); return nullptr; @@ -288,7 +291,7 @@ EvalCache::EvalCache( std::optional<std::reference_wrapper<const Hash>> useCache, EvalState & state, RootLoader rootLoader) - : db(useCache ? makeAttrDb(*useCache) : nullptr) + : db(useCache ? makeAttrDb(*state.store, *useCache) : nullptr) , state(state) , rootLoader(rootLoader) { @@ -546,7 +549,7 @@ string_t AttrCursor::getStringWithContext() if (auto s = std::get_if<string_t>(&cachedValue->second)) { bool valid = true; for (auto & c : s->second) { - if (!root->state.store->isValidPath(root->state.store->parseStorePath(c.first))) { + if (!root->state.store->isValidPath(c.first)) { valid = false; break; } @@ -563,7 +566,7 @@ string_t AttrCursor::getStringWithContext() auto & v = forceValue(); if (v.type() == nString) - return {v.string.s, v.getContext()}; + return {v.string.s, v.getContext(*root->state.store)}; else if (v.type() == nPath) return {v.path, {}}; else diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 126e0af8c..f7911e32b 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1903,13 +1903,22 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos) /* Decode a context string ‘!<name>!<path>’ into a pair <path, name>. */ -NixStringContextElem decodeContext(std::string_view s) +NixStringContextElem decodeContext(const Store & store, std::string_view s) { if (s.at(0) == '!') { size_t index = s.find("!", 1); - return {std::string(s.substr(index + 1)), std::string(s.substr(1, index - 1))}; + return { + store.parseStorePath(s.substr(index + 1)), + std::string(s.substr(1, index - 1)), + }; } else - return {s.at(0) == '/' ? std::string(s) : std::string(s.substr(1)), ""}; + return { + store.parseStorePath( + s.at(0) == '/' + ? s + : s.substr(1)), + "", + }; } @@ -1921,13 +1930,13 @@ void copyContext(const Value & v, PathSet & context) } -NixStringContext Value::getContext() +NixStringContext Value::getContext(const Store & store) { NixStringContext res; assert(internalType == tString); if (string.context) for (const char * * p = string.context; *p; ++p) - res.push_back(decodeContext(*p)); + res.push_back(decodeContext(store, *p)); return res; } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 18480f290..198a62ad2 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -17,7 +17,7 @@ namespace nix { -class Store; +struct Store; class EvalState; class StorePath; enum RepairFlag : bool; @@ -430,7 +430,7 @@ std::string showType(const Value & v); /* Decode a context string ‘!<name>!<path>’ into a pair <path, name>. */ -NixStringContextElem decodeContext(std::string_view s); +NixStringContextElem decodeContext(const Store & store, std::string_view s); /* If `path' refers to a directory, then append "/default.nix". */ Path resolveExprPath(Path path); diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 3124025aa..566cbdfdb 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -43,8 +43,8 @@ StringMap EvalState::realiseContext(const PathSet & context) StringMap res; for (auto & i : context) { - auto [ctxS, outputName] = decodeContext(i); - auto ctx = store->parseStorePath(ctxS); + auto [ctx, outputName] = decodeContext(*store, i); + auto ctxS = store->printStorePath(ctx); if (!store->isValidPath(ctx)) throw InvalidPathError(store->printStorePath(ctx)); if (!outputName.empty() && ctx.isDerivation()) { @@ -1118,8 +1118,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * /* Handle derivation outputs of the form ‘!<name>!<path>’. */ else if (path.at(0) == '!') { - auto ctx = decodeContext(path); - drv.inputDrvs[state.store->parseStorePath(ctx.first)].insert(ctx.second); + auto ctx = decodeContext(*state.store, path); + drv.inputDrvs[ctx.first].insert(ctx.second); } /* Otherwise it's a source file. */ diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index ad4de3840..6ec5e4a74 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -82,8 +82,8 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, drv = std::string(p, 1); path = &drv; } else if (p.at(0) == '!') { - NixStringContextElem ctx = decodeContext(p); - drv = ctx.first; + NixStringContextElem ctx = decodeContext(*state.store, p); + drv = state.store->printStorePath(ctx.first); output = ctx.second; path = &drv; } diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index ee9cb832a..d413060f9 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -57,6 +57,8 @@ struct ExprLambda; struct PrimOp; class Symbol; struct Pos; +class StorePath; +class Store; class EvalState; class XMLWriter; class JSONPlaceholder; @@ -64,7 +66,7 @@ class JSONPlaceholder; typedef int64_t NixInt; typedef double NixFloat; -typedef std::pair<Path, std::string> NixStringContextElem; +typedef std::pair<StorePath, std::string> NixStringContextElem; typedef std::vector<NixStringContextElem> NixStringContext; /* External values must descend from ExternalValueBase, so that @@ -370,7 +372,7 @@ public: non-trivial. */ bool isTrivial() const; - NixStringContext getContext(); + NixStringContext getContext(const Store &); auto listItems() { diff --git a/src/nix/app.cc b/src/nix/app.cc index 2563180fb..a4e0df06a 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -70,7 +70,7 @@ UnresolvedApp Installable::toApp(EvalState & state) std::vector<StorePathWithOutputs> context2; for (auto & [path, name] : context) - context2.push_back({state.store->parseStorePath(path), {name}}); + context2.push_back({path, {name}}); return UnresolvedApp{App { .context = std::move(context2), From 345a8ee0cb56775c57c3b7da99b0726290241e18 Mon Sep 17 00:00:00 2001 From: Gabriel Fontes <eu@misterio.me> Date: Sat, 19 Mar 2022 10:56:13 -0300 Subject: [PATCH 020/171] Fix sourcehut tag ref resolving --- src/libfetchers/github.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index a1430f087..d52ea649d 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -390,7 +390,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme ref_uri = line.substr(ref_index+5, line.length()-1); } else - ref_uri = fmt("refs/heads/%s", ref); + ref_uri = fmt("refs/(heads|tags)/%s", ref); auto file = store->toRealPath( downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath); @@ -399,9 +399,9 @@ struct SourceHutInputScheme : GitArchiveInputScheme std::string line; std::string id; while(getline(is, line)) { - auto index = line.find(ref_uri); - if (index != std::string::npos) { - id = line.substr(0, index-1); + std::regex pattern(ref_uri); + if (std::regex_search(line, pattern)) { + id = line.substr(0, line.find('\t')); break; } } From 9720797f695f7646744e9cd020fa79679441814f Mon Sep 17 00:00:00 2001 From: Gabriel Fontes <eu@misterio.me> Date: Sat, 19 Mar 2022 11:04:04 -0300 Subject: [PATCH 021/171] Don't partial match sourcehut refs --- src/libfetchers/github.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index d52ea649d..58b6e7c04 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -399,7 +399,9 @@ struct SourceHutInputScheme : GitArchiveInputScheme std::string line; std::string id; while(getline(is, line)) { - std::regex pattern(ref_uri); + // 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; From 31544b93ffa985661541309ec2360dfb7b527a80 Mon Sep 17 00:00:00 2001 From: Gabriel Fontes <eu@misterio.me> Date: Sat, 19 Mar 2022 11:38:45 -0300 Subject: [PATCH 022/171] Fix sourcehut integration test The new implementation relies on tab separting the hash and ref (this is how sourcehut does it). This fixes the integration test to use a tab instead of a space. --- tests/sourcehut-flakes.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sourcehut-flakes.nix b/tests/sourcehut-flakes.nix index d1d89d149..6a1930904 100644 --- a/tests/sourcehut-flakes.nix +++ b/tests/sourcehut-flakes.nix @@ -59,7 +59,7 @@ let echo 'ref: refs/heads/master' > $out/HEAD mkdir -p $out/info - echo '${nixpkgs.rev} refs/heads/master' > $out/info/refs + echo -e '${nixpkgs.rev}\trefs/heads/master' > $out/info/refs ''; in From 0b42afe02794a446d20066c0e9931b9c759d36e2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 10:48:02 +0100 Subject: [PATCH 023/171] buildProfile(): Ignore manifest.{nix,json} If a package installs a file named manifest.json, it caused nix-env to consider the profile a new-style profile created by 'nix profile'. Fixes #6032. --- src/libstore/builtins/buildenv.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libstore/builtins/buildenv.cc b/src/libstore/builtins/buildenv.cc index 25d015cb9..6f6ad57cb 100644 --- a/src/libstore/builtins/buildenv.cc +++ b/src/libstore/builtins/buildenv.cc @@ -47,9 +47,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, throw; } - /* The files below are special-cased to that they don't show up - * in user profiles, either because they are useless, or - * because they would cauase pointless collisions (e.g., each + /* The files below are special-cased to that they don't show + * up in user profiles, either because they are useless, or + * because they would cause pointless collisions (e.g., each * Python package brings its own * `$out/lib/pythonX.Y/site-packages/easy-install.pth'.) */ @@ -57,7 +57,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, hasSuffix(srcFile, "/nix-support") || hasSuffix(srcFile, "/perllocal.pod") || hasSuffix(srcFile, "/info/dir") || - hasSuffix(srcFile, "/log")) + hasSuffix(srcFile, "/log") || + hasSuffix(srcFile, "/manifest.nix") || + hasSuffix(srcFile, "/manifest.json")) continue; else if (S_ISDIR(srcSt.st_mode)) { From 732296ddc078f9cce8ffeb3131fef8898330b6ae Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 13:00:27 +0100 Subject: [PATCH 024/171] =?UTF-8?q?printValue():=20<REPEAT>=20->=20=C2=ABr?= =?UTF-8?q?epeated=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures that it doesn't get parsed as a valid Nix expression. --- src/libexpr/eval.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index f7911e32b..333ad68eb 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -126,7 +126,7 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value & break; case tAttrs: { if (!v.attrs->empty() && !seen.insert(v.attrs).second) - str << "<REPEAT>"; + str << "«repeated»"; else { str << "{ "; for (auto & i : v.attrs->lexicographicOrder()) { @@ -142,7 +142,7 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value & case tList2: case tListN: if (v.listSize() && !seen.insert(v.listElems()).second) - str << "<REPEAT>"; + str << "«repeated»"; else { str << "[ "; for (auto v2 : v.listItems()) { From a0259a21a416301573d9d6b994d6b5fb62a1bcf3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 13:18:11 +0100 Subject: [PATCH 025/171] Don't hide repeated values while generating manifest.nix Fixes #6243. --- src/libexpr/eval.cc | 38 ++++++++++++++++++++++---------------- src/libexpr/value.hh | 5 ++++- src/nix-env/user-env.cc | 4 +++- tests/user-envs.nix | 3 +++ 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 333ad68eb..3ec5ed202 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -96,20 +96,20 @@ RootValue allocRootValue(Value * v) } -void printValue(std::ostream & str, std::set<const void *> & seen, const Value & v) +void Value::print(std::ostream & str, std::set<const void *> * seen) const { checkInterrupt(); - switch (v.internalType) { + switch (internalType) { case tInt: - str << v.integer; + str << integer; break; case tBool: - str << (v.boolean ? "true" : "false"); + str << (boolean ? "true" : "false"); break; case tString: str << "\""; - for (const char * i = v.string.s; *i; i++) + for (const char * i = string.s; *i; i++) if (*i == '\"' || *i == '\\') str << "\\" << *i; else if (*i == '\n') str << "\\n"; else if (*i == '\r') str << "\\r"; @@ -119,19 +119,19 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value & str << "\""; break; case tPath: - str << v.path; // !!! escaping? + str << path; // !!! escaping? break; case tNull: str << "null"; break; case tAttrs: { - if (!v.attrs->empty() && !seen.insert(v.attrs).second) + if (seen && !attrs->empty() && !seen->insert(attrs).second) str << "«repeated»"; else { str << "{ "; - for (auto & i : v.attrs->lexicographicOrder()) { + for (auto & i : attrs->lexicographicOrder()) { str << i->name << " = "; - printValue(str, seen, *i->value); + i->value->print(str, seen); str << "; "; } str << "}"; @@ -141,12 +141,12 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value & case tList1: case tList2: case tListN: - if (v.listSize() && !seen.insert(v.listElems()).second) + if (seen && listSize() && !seen->insert(listElems()).second) str << "«repeated»"; else { str << "[ "; - for (auto v2 : v.listItems()) { - printValue(str, seen, *v2); + for (auto v2 : listItems()) { + v2->print(str, seen); str << " "; } str << "]"; @@ -166,10 +166,10 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value & str << "<PRIMOP-APP>"; break; case tExternal: - str << *v.external; + str << *external; break; case tFloat: - str << v.fpoint; + str << fpoint; break; default: abort(); @@ -177,10 +177,16 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value & } -std::ostream & operator << (std::ostream & str, const Value & v) +void Value::print(std::ostream & str, bool showRepeated) const { std::set<const void *> seen; - printValue(str, seen, v); + print(str, showRepeated ? nullptr : &seen); +} + + +std::ostream & operator << (std::ostream & str, const Value & v) +{ + v.print(str, false); return str; } diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index d413060f9..3d07c3198 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -119,10 +119,13 @@ private: InternalType internalType; friend std::string showType(const Value & v); - friend void printValue(std::ostream & str, std::set<const void *> & seen, const Value & v); + + void print(std::ostream & str, std::set<const void *> * seen) const; public: + void print(std::ostream & str, bool showRepeated = false) const; + // Functions needed to distinguish the type // These should be removed eventually, by putting the functionality that's // needed by callers into methods of this type diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc index bcc7736ac..78692b9c6 100644 --- a/src/nix-env/user-env.cc +++ b/src/nix-env/user-env.cc @@ -105,8 +105,10 @@ bool createUserEnv(EvalState & state, DrvInfos & elems, /* Also write a copy of the list of user environment elements to the store; we need it for future modifications of the environment. */ + std::ostringstream str; + manifest.print(str, true); auto manifestFile = state.store->addTextToStore("env-manifest.nix", - fmt("%s", manifest), references); + str.str(), references); /* Get the environment builder expression. */ Value envBuilder; diff --git a/tests/user-envs.nix b/tests/user-envs.nix index 6ac896ed8..46f8b51dd 100644 --- a/tests/user-envs.nix +++ b/tests/user-envs.nix @@ -8,6 +8,8 @@ assert foo == "foo"; let + platforms = let x = "foobar"; in [ x x ]; + makeDrv = name: progName: (mkDerivation { name = assert progName != "fail"; name; inherit progName system; @@ -15,6 +17,7 @@ let } // { meta = { description = "A silly test package with some \${escaped anti-quotation} in it"; + inherit platforms; }; }); From 3b776cb0a7574bf11c20fe0a1d91ec1d21d8ebeb Mon Sep 17 00:00:00 2001 From: Hideaki Kawai <kayhide@gmail.com> Date: Tue, 22 Mar 2022 23:18:02 +0900 Subject: [PATCH 026/171] nix edit: support kakoune --- src/libcmd/command.cc | 3 ++- src/nix/edit.md | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index dc8fa9e5a..a53b029b7 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -204,7 +204,8 @@ Strings editorFor(const Pos & pos) if (pos.line > 0 && ( editor.find("emacs") != std::string::npos || editor.find("nano") != std::string::npos || - editor.find("vim") != 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); return args; diff --git a/src/nix/edit.md b/src/nix/edit.md index 80563d06b..89bd09abf 100644 --- a/src/nix/edit.md +++ b/src/nix/edit.md @@ -24,8 +24,8 @@ this attribute to the location of the definition of the `meta.description`, `version` or `name` derivation attributes. The editor to invoke is specified by the `EDITOR` environment -variable. It defaults to `cat`. If the editor is `emacs`, `nano` or -`vim`, it is passed the line number of the derivation using the -argument `+<lineno>`. +variable. It defaults to `cat`. If the editor is `emacs`, `nano`, +`vim` or `kak`, it is passed the line number of the derivation using +the argument `+<lineno>`. )"" From 67af5f7eda1dade544cc15397a7d0d35d0913cb2 Mon Sep 17 00:00:00 2001 From: Florian Klink <flokli@flokli.de> Date: Tue, 22 Mar 2022 17:06:09 +0100 Subject: [PATCH 027/171] scripts/install-systemd-multi-user.sh: install /etc/tmpfiles.d/nix-daemon.conf, too While `create_directories()` from install-multi-user.sh seems to already create parts of the directory structure, it's marked as deprecated, and it won't hurt also copying over the tmpfiles config and have it execute once. --- scripts/install-systemd-multi-user.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/install-systemd-multi-user.sh b/scripts/install-systemd-multi-user.sh index f4a2dfc5d..24884a023 100755 --- a/scripts/install-systemd-multi-user.sh +++ b/scripts/install-systemd-multi-user.sh @@ -9,6 +9,8 @@ readonly SERVICE_DEST=/etc/systemd/system/nix-daemon.service readonly SOCKET_SRC=/lib/systemd/system/nix-daemon.socket readonly SOCKET_DEST=/etc/systemd/system/nix-daemon.socket +readonly TMPFILES_SRC=/lib/tmpfiles.d/nix-daemon.conf +readonly TMPFILES_DEST=/etc/tmpfiles.d/nix-daemon.conf # Path for the systemd override unit file to contain the proxy settings readonly SERVICE_OVERRIDE=${SERVICE_DEST}.d/override.conf @@ -83,6 +85,13 @@ EOF poly_configure_nix_daemon_service() { if [ -e /run/systemd/system ]; then task "Setting up the nix-daemon systemd service" + + _sudo "to create the nix-daemon tmpfiles config" \ + ln -sfn /nix/var/nix/profiles/default/$TMPFILES_SRC $TMPFILES_DEST + + _sudo "to run systemd-tmpfiles once to pick that path up" \ + sytemd-tmpfiles create --prefix=/nix/var/nix + _sudo "to set up the nix-daemon service" \ systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC" From 9174d884d750b7b49a571bd55275f0883c2dabda Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich <slyich@gmail.com> Date: Thu, 24 Mar 2022 08:10:33 +0000 Subject: [PATCH 028/171] lexer: add error location to lexer errors Before the change lexter errors did not report the location: $ nix build -f. mc error: path has a trailing slash (use '--show-trace' to show detailed location information) Note that it's not clear what file generates the error. After the change location is reported: $ src/nix/nix --extra-experimental-features nix-command build -f ~/nm mc error: path has a trailing slash at .../pkgs/development/libraries/glib/default.nix:54:18: 53| }; 54| src = /tmp/foo/; | ^ 55| (use '--show-trace' to show detailed location information) Here we see both problematic file and the string itself. --- src/libexpr/lexer.l | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index e276b0467..d574121b0 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -28,6 +28,13 @@ using namespace nix; namespace nix { +static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data) +{ + return Pos(data->origin, data->file, loc.first_line, loc.first_column); +} + +#define CUR_POS makeCurPos(*yylloc, data) + // backup to recover from yyless(0) YYLTYPE prev_yylloc; @@ -37,7 +44,6 @@ static void initLoc(YYLTYPE * loc) loc->first_column = loc->last_column = 1; } - static void adjustLoc(YYLTYPE * loc, const char * s, size_t len) { prev_yylloc = *loc; @@ -147,14 +153,20 @@ or { return OR_KW; } try { yylval->n = boost::lexical_cast<int64_t>(yytext); } catch (const boost::bad_lexical_cast &) { - throw ParseError("invalid integer '%1%'", yytext); + throw ParseError({ + .msg = hintfmt("invalid integer '%1%'", yytext), + .errPos = CUR_POS, + }); } return INT; } {FLOAT} { errno = 0; yylval->nf = strtod(yytext, 0); if (errno != 0) - throw ParseError("invalid float '%1%'", yytext); + throw ParseError({ + .msg = hintfmt("invalid float '%1%'", yytext), + .errPos = CUR_POS, + }); return FLOAT; } @@ -280,7 +292,10 @@ or { return OR_KW; } <INPATH_SLASH>{ANY} | <INPATH_SLASH><<EOF>> { - throw ParseError("path has a trailing slash"); + throw ParseError({ + .msg = hintfmt("path has a trailing slash"), + .errPos = CUR_POS, + }); } {SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; } From 4546a007a46fe12b77a49ae8753ab186d0b55fdd Mon Sep 17 00:00:00 2001 From: Rok Garbas <rok@garbas.si> Date: Thu, 24 Mar 2022 12:28:38 +0100 Subject: [PATCH 029/171] Fix flake profile use of originalUrl vs. originalUri Fixes #5872 --- src/nix/profile.cc | 5 +++-- src/nix/profile.md | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/nix/profile.cc b/src/nix/profile.cc index a8ff9c78a..da990ddc8 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -107,8 +107,9 @@ struct ProfileManifest element.storePaths.insert(state.store->parseStorePath((std::string) p)); element.active = e["active"]; if (e.value("uri", "") != "") { + auto originalUrl = e.value("originalUrl", e["originalUri"]); element.source = ProfileElementSource{ - parseFlakeRef(e["originalUri"]), + parseFlakeRef(originalUrl), parseFlakeRef(e["uri"]), e["attrPath"] }; @@ -143,7 +144,7 @@ struct ProfileManifest obj["storePaths"] = paths; obj["active"] = element.active; if (element.source) { - obj["originalUri"] = element.source->originalRef.to_string(); + obj["originalUrl"] = element.source->originalRef.to_string(); obj["uri"] = element.source->resolvedRef.to_string(); obj["attrPath"] = element.source->attrPath; } diff --git a/src/nix/profile.md b/src/nix/profile.md index 0a4ff2fa9..8dade051d 100644 --- a/src/nix/profile.md +++ b/src/nix/profile.md @@ -70,7 +70,7 @@ are installed in this version of the profile. It looks like this: { "active": true, "attrPath": "legacyPackages.x86_64-linux.zoom-us", - "originalUri": "flake:nixpkgs", + "originalUrl": "flake:nixpkgs", "storePaths": [ "/nix/store/wbhg2ga8f3h87s9h5k0slxk0m81m4cxl-zoom-us-5.3.469451.0927" ], @@ -84,11 +84,11 @@ are installed in this version of the profile. It looks like this: Each object in the array `elements` denotes an installed package and has the following fields: -* `originalUri`: The [flake reference](./nix3-flake.md) specified by +* `originalUrl`: The [flake reference](./nix3-flake.md) specified by the user at the time of installation (e.g. `nixpkgs`). This is also the flake reference that will be used by `nix profile upgrade`. -* `uri`: The immutable flake reference to which `originalUri` +* `uri`: The immutable flake reference to which `originalUrl` resolved. * `attrPath`: The flake output attribute that provided this From bb0c4b9f25b6d064d91aedd71940f14b1b624291 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 24 Mar 2022 12:46:46 +0100 Subject: [PATCH 030/171] install-multi-user.sh: Preserve symlinks We need to pass -P to ensure that symlinks are copied correctly. Fixes #6303. --- scripts/install-multi-user.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh index 5f2c93b7f..69b6676ea 100644 --- a/scripts/install-multi-user.sh +++ b/scripts/install-multi-user.sh @@ -739,7 +739,7 @@ install_from_extracted_nix() { cd "$EXTRACTED_NIX_PATH" _sudo "to copy the basic Nix files to the new store at $NIX_ROOT/store" \ - cp -RLp ./store/* "$NIX_ROOT/store/" + cp -RPp ./store/* "$NIX_ROOT/store/" _sudo "to make the new store non-writable at $NIX_ROOT/store" \ chmod -R ugo-w "$NIX_ROOT/store/" From 0736f3651d56d3e0c3b742fe0d90958c90e9c968 Mon Sep 17 00:00:00 2001 From: Tom Bereknyei <tomberek@gmail.com> Date: Wed, 23 Mar 2022 22:54:43 -0400 Subject: [PATCH 031/171] docs: genericClosure --- src/libexpr/primops.cc | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index bfa18f898..e7e224b3a 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -694,7 +694,32 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info { .name = "__genericClosure", + .args = {"attrset"}, .arity = 1, + .doc = R"( + Take an *attrset* with values named `startSet` and `operator` in order to + return a *list of attrsets* by starting with the `startSet`, recursively + applying the `operator` function to each element. The *attrsets* in the + `startSet` and produced by the `operator` must each contain value named + `key` which are comparable to each other. The result is produced by + repeatedly calling the operator for each element encountered with a + unique key, terminating when no new elements are produced. For example, + + ``` + builtins.genericClosure { + startSet = [ {key = 5;} ]; + operator = item: [{ + key = if (item.key / 2 ) * 2 == item.key + then item.key / 2 + else 3 * item.key + 1; + }]; + } + ``` + evaluates to + ``` + [ { key = 5; } { key = 16; } { key = 8; } { key = 4; } { key = 2; } { key = 1; } ] + ``` + )", .fun = prim_genericClosure, }); From f4bafc412fac79ce07c89f8d3ab9bd1c32f7b9cd Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Mon, 21 Mar 2022 11:31:01 +0100 Subject: [PATCH 032/171] Add builtins.fetchClosure This allows closures to be imported at evaluation time, without requiring the user to configure substituters. E.g. builtins.fetchClosure { storePath = /nix/store/f89g6yi63m1ywfxj96whv5sxsm74w5ka-python3.9-sqlparse-0.4.2; from = "https://cache.ngi0.nixos.org"; } --- src/libexpr/primops/fetchClosure.cc | 62 +++++++++++++++++++++++++++++ src/libexpr/primops/fetchTree.cc | 4 +- 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/libexpr/primops/fetchClosure.cc diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc new file mode 100644 index 000000000..4bcc716db --- /dev/null +++ b/src/libexpr/primops/fetchClosure.cc @@ -0,0 +1,62 @@ +#include "primops.hh" +#include "store-api.hh" + +namespace nix { + +static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) +{ + state.forceAttrs(*args[0], pos); + + std::optional<StorePath> storePath; + std::optional<std::string> from; + + for (auto & attr : *args[0]->attrs) { + if (attr.name == "storePath") { + PathSet context; + storePath = state.coerceToStorePath(*attr.pos, *attr.value, context); + } + + else if (attr.name == "from") + from = state.forceStringNoCtx(*attr.value, *attr.pos); + + else + throw Error({ + .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name), + .errPos = pos + }); + } + + if (!storePath) + throw Error({ + .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "storePath"), + .errPos = pos + }); + + if (!from) + throw Error({ + .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "from"), + .errPos = pos + }); + + // FIXME: only allow some "trusted" store types (like BinaryCacheStore). + auto srcStore = openStore(*from); + + copyClosure(*srcStore, *state.store, RealisedPath::Set { *storePath }); + + auto storePathS = state.store->printStorePath(*storePath); + + v.mkString(storePathS, {storePathS}); + + // FIXME: in pure mode, require a CA path or a NAR hash of the + // top-level path. +} + +static RegisterPrimOp primop_fetchClosure({ + .name = "__fetchClosure", + .args = {"args"}, + .doc = R"( + )", + .fun = prim_fetchClosure, +}); + +} diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 9c2da2178..42c98e312 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -145,7 +145,7 @@ static void fetchTree( if (!params.allowNameArgument) if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) throw Error({ - .msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"), + .msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"), .errPos = pos }); @@ -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 Pos & pos, Value * * args, Value & v) { fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true }); } From 41659418cfeb7a33fc76716a1847f79ae13d9b0c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Mon, 21 Mar 2022 14:00:54 +0100 Subject: [PATCH 033/171] fetchClosure: Require a CA path in pure mode --- src/libexpr/primops/fetchClosure.cc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 4bcc716db..22a4649a4 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -43,12 +43,19 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args copyClosure(*srcStore, *state.store, RealisedPath::Set { *storePath }); + /* In pure mode, require a CA path. */ + if (evalSettings.pureEval) { + auto info = state.store->queryPathInfo(*storePath); + if (!info->isContentAddressed(*state.store)) + throw Error({ + .msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't", + state.store->printStorePath(*storePath)), + .errPos = pos + }); + } + auto storePathS = state.store->printStorePath(*storePath); - v.mkString(storePathS, {storePathS}); - - // FIXME: in pure mode, require a CA path or a NAR hash of the - // top-level path. } static RegisterPrimOp primop_fetchClosure({ From 7f6fe8ca1d41bceef32790aa0313aa62ae2b65fb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Mon, 21 Mar 2022 14:34:45 +0100 Subject: [PATCH 034/171] Rename --- src/libexpr/primops/fetchClosure.cc | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 22a4649a4..8e60b2ccc 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -7,17 +7,17 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args { state.forceAttrs(*args[0], pos); - std::optional<StorePath> storePath; - std::optional<std::string> from; + std::optional<std::string> fromStoreUrl; + std::optional<StorePath> fromPath; for (auto & attr : *args[0]->attrs) { - if (attr.name == "storePath") { + if (attr.name == "fromPath") { PathSet context; - storePath = state.coerceToStorePath(*attr.pos, *attr.value, context); + fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context); } - else if (attr.name == "from") - from = state.forceStringNoCtx(*attr.value, *attr.pos); + else if (attr.name == "fromStore") + fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos); else throw Error({ @@ -26,36 +26,36 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args }); } - if (!storePath) + if (!fromPath) throw Error({ - .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "storePath"), + .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"), .errPos = pos }); - if (!from) + if (!fromStoreUrl) throw Error({ - .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "from"), + .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"), .errPos = pos }); // FIXME: only allow some "trusted" store types (like BinaryCacheStore). - auto srcStore = openStore(*from); + auto fromStore = openStore(*fromStoreUrl); - copyClosure(*srcStore, *state.store, RealisedPath::Set { *storePath }); + copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath }); /* In pure mode, require a CA path. */ if (evalSettings.pureEval) { - auto info = state.store->queryPathInfo(*storePath); + auto info = state.store->queryPathInfo(*fromPath); if (!info->isContentAddressed(*state.store)) throw Error({ .msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't", - state.store->printStorePath(*storePath)), + state.store->printStorePath(*fromPath)), .errPos = pos }); } - auto storePathS = state.store->printStorePath(*storePath); - v.mkString(storePathS, {storePathS}); + auto fromPathS = state.store->printStorePath(*fromPath); + v.mkString(fromPathS, {fromPathS}); } static RegisterPrimOp primop_fetchClosure({ From 545c2d0d8cbac86c169a6dd049c1ed9c3913774d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 21:14:58 +0100 Subject: [PATCH 035/171] fetchClosure: Allow a path to be rewritten to CA on the fly The advantage is that the resulting closure doesn't need to be signed, so you don't need to configure any binary cache keys on the client. --- src/libexpr/primops/fetchClosure.cc | 46 ++++++++++++-- src/libstore/make-content-addressed.cc | 79 ++++++++++++++++++++++++ src/libstore/make-content-addressed.hh | 12 ++++ src/nix/make-content-addressable.cc | 85 ++++++-------------------- 4 files changed, 150 insertions(+), 72 deletions(-) create mode 100644 src/libstore/make-content-addressed.cc create mode 100644 src/libstore/make-content-addressed.hh diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 8e60b2ccc..56fd53ed5 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -1,5 +1,6 @@ #include "primops.hh" #include "store-api.hh" +#include "make-content-addressed.hh" namespace nix { @@ -9,6 +10,8 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args std::optional<std::string> fromStoreUrl; std::optional<StorePath> fromPath; + bool toCA = false; + std::optional<StorePath> toPath; for (auto & attr : *args[0]->attrs) { if (attr.name == "fromPath") { @@ -16,6 +19,15 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context); } + else if (attr.name == "toPath") { + 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); + } + } + else if (attr.name == "fromStore") fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos); @@ -41,21 +53,45 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args // FIXME: only allow some "trusted" store types (like BinaryCacheStore). auto fromStore = openStore(*fromStoreUrl); - copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath }); + if (toCA) { + auto remappings = makeContentAddressed(*fromStore, *state.store, { *fromPath }); + auto i = remappings.find(*fromPath); + assert(i != remappings.end()); + if (toPath && *toPath != i->second) + throw Error({ + .msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected", + state.store->printStorePath(*fromPath), + state.store->printStorePath(i->second), + state.store->printStorePath(*toPath)), + .errPos = pos + }); + if (!toPath) + throw Error({ + .msg = hintfmt( + "rewriting '%s' to content-addressed form yielded '%s'; " + "please set this in the 'toPath' attribute passed to 'fetchClosure'", + state.store->printStorePath(*fromPath), + state.store->printStorePath(i->second)), + .errPos = pos + }); + } else { + copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath }); + toPath = fromPath; + } /* In pure mode, require a CA path. */ if (evalSettings.pureEval) { - auto info = state.store->queryPathInfo(*fromPath); + auto info = state.store->queryPathInfo(*toPath); if (!info->isContentAddressed(*state.store)) throw Error({ .msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't", - state.store->printStorePath(*fromPath)), + state.store->printStorePath(*toPath)), .errPos = pos }); } - auto fromPathS = state.store->printStorePath(*fromPath); - v.mkString(fromPathS, {fromPathS}); + auto toPathS = state.store->printStorePath(*toPath); + v.mkString(toPathS, {toPathS}); } static RegisterPrimOp primop_fetchClosure({ diff --git a/src/libstore/make-content-addressed.cc b/src/libstore/make-content-addressed.cc new file mode 100644 index 000000000..0b95ff37c --- /dev/null +++ b/src/libstore/make-content-addressed.cc @@ -0,0 +1,79 @@ +#include "make-content-addressed.hh" +#include "references.hh" + +namespace nix { + +std::map<StorePath, StorePath> makeContentAddressed( + Store & srcStore, + Store & dstStore, + const StorePathSet & storePaths) +{ + // FIXME: use closure of storePaths. + + auto paths = srcStore.topoSortPaths(storePaths); + + std::reverse(paths.begin(), paths.end()); + + std::map<StorePath, StorePath> remappings; + + for (auto & path : paths) { + auto pathS = srcStore.printStorePath(path); + auto oldInfo = srcStore.queryPathInfo(path); + std::string oldHashPart(path.hashPart()); + + StringSink sink; + srcStore.narFromPath(path, sink); + + StringMap rewrites; + + StorePathSet references; + bool hasSelfReference = false; + for (auto & ref : oldInfo->references) { + if (ref == path) + hasSelfReference = true; + else { + auto i = remappings.find(ref); + auto replacement = i != remappings.end() ? i->second : ref; + // FIXME: warn about unremapped paths? + if (replacement != ref) + rewrites.insert_or_assign(srcStore.printStorePath(ref), srcStore.printStorePath(replacement)); + references.insert(std::move(replacement)); + } + } + + sink.s = rewriteStrings(sink.s, rewrites); + + HashModuloSink hashModuloSink(htSHA256, oldHashPart); + hashModuloSink(sink.s); + + auto narHash = hashModuloSink.finish().first; + + ValidPathInfo info { + dstStore.makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, path.name(), references, hasSelfReference), + narHash, + }; + info.references = std::move(references); + if (hasSelfReference) info.references.insert(info.path); + info.narSize = sink.s.size(); + info.ca = FixedOutputHash { + .method = FileIngestionMethod::Recursive, + .hash = info.narHash, + }; + + printInfo("rewrote '%s' to '%s'", pathS, srcStore.printStorePath(info.path)); + + auto source = sinkToSource([&](Sink & nextSink) { + RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), nextSink); + rsink2(sink.s); + rsink2.flush(); + }); + + dstStore.addToStore(info, *source); + + remappings.insert_or_assign(std::move(path), std::move(info.path)); + } + + return remappings; +} + +} diff --git a/src/libstore/make-content-addressed.hh b/src/libstore/make-content-addressed.hh new file mode 100644 index 000000000..c4a66ed41 --- /dev/null +++ b/src/libstore/make-content-addressed.hh @@ -0,0 +1,12 @@ +#pragma once + +#include "store-api.hh" + +namespace nix { + +std::map<StorePath, StorePath> makeContentAddressed( + Store & srcStore, + Store & dstStore, + const StorePathSet & storePaths); + +} diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressable.cc index 2e75a3b61..a8579ea7c 100644 --- a/src/nix/make-content-addressable.cc +++ b/src/nix/make-content-addressable.cc @@ -1,6 +1,6 @@ #include "command.hh" #include "store-api.hh" -#include "references.hh" +#include "make-content-addressed.hh" #include "common-args.hh" #include "json.hh" @@ -27,74 +27,25 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON void run(ref<Store> store, StorePaths && storePaths) override { - auto paths = store->topoSortPaths(StorePathSet(storePaths.begin(), storePaths.end())); + auto remappings = makeContentAddressed(*store, *store, + StorePathSet(storePaths.begin(), storePaths.end())); - std::reverse(paths.begin(), paths.end()); - - std::map<StorePath, StorePath> remappings; - - auto jsonRoot = json ? std::make_unique<JSONObject>(std::cout) : nullptr; - auto jsonRewrites = json ? std::make_unique<JSONObject>(jsonRoot->object("rewrites")) : nullptr; - - for (auto & path : paths) { - auto pathS = store->printStorePath(path); - auto oldInfo = store->queryPathInfo(path); - std::string oldHashPart(path.hashPart()); - - StringSink sink; - store->narFromPath(path, sink); - - StringMap rewrites; - - StorePathSet references; - bool hasSelfReference = false; - for (auto & ref : oldInfo->references) { - if (ref == path) - hasSelfReference = true; - else { - auto i = remappings.find(ref); - auto replacement = i != remappings.end() ? i->second : ref; - // FIXME: warn about unremapped paths? - if (replacement != ref) - rewrites.insert_or_assign(store->printStorePath(ref), store->printStorePath(replacement)); - references.insert(std::move(replacement)); - } + if (json) { + JSONObject jsonRoot(std::cout); + JSONObject jsonRewrites(jsonRoot.object("rewrites")); + for (auto & path : storePaths) { + auto i = remappings.find(path); + assert(i != remappings.end()); + jsonRewrites.attr(store->printStorePath(path), store->printStorePath(i->second)); + } + } else { + for (auto & path : storePaths) { + auto i = remappings.find(path); + assert(i != remappings.end()); + notice("rewrote '%s' to '%s'", + store->printStorePath(path), + store->printStorePath(i->second)); } - - sink.s = rewriteStrings(sink.s, rewrites); - - HashModuloSink hashModuloSink(htSHA256, oldHashPart); - hashModuloSink(sink.s); - - auto narHash = hashModuloSink.finish().first; - - ValidPathInfo info { - store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, path.name(), references, hasSelfReference), - narHash, - }; - info.references = std::move(references); - if (hasSelfReference) info.references.insert(info.path); - info.narSize = sink.s.size(); - info.ca = FixedOutputHash { - .method = FileIngestionMethod::Recursive, - .hash = info.narHash, - }; - - if (!json) - notice("rewrote '%s' to '%s'", pathS, store->printStorePath(info.path)); - - auto source = sinkToSource([&](Sink & nextSink) { - RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), nextSink); - rsink2(sink.s); - rsink2.flush(); - }); - - store->addToStore(info, *source); - - if (json) - jsonRewrites->attr(store->printStorePath(path), store->printStorePath(info.path)); - - remappings.insert_or_assign(std::move(path), std::move(info.path)); } } }; From f18607549ce38545b1d754ed93f3b7c5417970d8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 21:47:50 +0100 Subject: [PATCH 036/171] Fix makeContentAddressed() on self-references LocalStore::addToStore() since 79ae9e4558cbefd743f28a5e73110c2303b03a85 expects a regular NAR hash, rather than a NAR hash modulo self-references. Fixes #6300. Also, makeContentAddressed() now rewrites the entire closure (so 'nix store make-content-addressable' no longer needs '-r'). See #6301. --- src/libstore/make-content-addressed.cc | 35 +++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/libstore/make-content-addressed.cc b/src/libstore/make-content-addressed.cc index 0b95ff37c..fc11fcb27 100644 --- a/src/libstore/make-content-addressed.cc +++ b/src/libstore/make-content-addressed.cc @@ -8,9 +8,10 @@ std::map<StorePath, StorePath> makeContentAddressed( Store & dstStore, const StorePathSet & storePaths) { - // FIXME: use closure of storePaths. + StorePathSet closure; + srcStore.computeFSClosure(storePaths, closure); - auto paths = srcStore.topoSortPaths(storePaths); + auto paths = srcStore.topoSortPaths(closure); std::reverse(paths.begin(), paths.end()); @@ -46,29 +47,29 @@ std::map<StorePath, StorePath> makeContentAddressed( HashModuloSink hashModuloSink(htSHA256, oldHashPart); hashModuloSink(sink.s); - auto narHash = hashModuloSink.finish().first; + auto narModuloHash = hashModuloSink.finish().first; - ValidPathInfo info { - dstStore.makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, path.name(), references, hasSelfReference), - narHash, - }; + auto dstPath = dstStore.makeFixedOutputPath( + FileIngestionMethod::Recursive, narModuloHash, path.name(), references, hasSelfReference); + + printInfo("rewroting '%s' to '%s'", pathS, srcStore.printStorePath(dstPath)); + + StringSink sink2; + RewritingSink rsink2(oldHashPart, std::string(dstPath.hashPart()), sink2); + rsink2(sink.s); + rsink2.flush(); + + ValidPathInfo info { dstPath, hashString(htSHA256, sink2.s) }; info.references = std::move(references); if (hasSelfReference) info.references.insert(info.path); info.narSize = sink.s.size(); info.ca = FixedOutputHash { .method = FileIngestionMethod::Recursive, - .hash = info.narHash, + .hash = narModuloHash, }; - printInfo("rewrote '%s' to '%s'", pathS, srcStore.printStorePath(info.path)); - - auto source = sinkToSource([&](Sink & nextSink) { - RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), nextSink); - rsink2(sink.s); - rsink2.flush(); - }); - - dstStore.addToStore(info, *source); + StringSource source(sink2.s); + dstStore.addToStore(info, source); remappings.insert_or_assign(std::move(path), std::move(info.path)); } From 5acaf13d3564f689e5461f29a9cc5958809d5e93 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 21:54:49 +0100 Subject: [PATCH 037/171] Rename 'nix store make-content-addressable' to 'nix store make-content-addressed' --- doc/manual/src/release-notes/rl-next.md | 3 +++ src/nix/main.cc | 2 +- ...e-content-addressable.cc => make-content-addressed.cc} | 8 ++++---- ...e-content-addressable.md => make-content-addressed.md} | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) rename src/nix/{make-content-addressable.cc => make-content-addressed.cc} (83%) rename src/nix/{make-content-addressable.md => make-content-addressed.md} (94%) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index c9753f9aa..8fbc605e7 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1,3 +1,6 @@ # Release X.Y (202?-??-??) * Various nix commands can now read expressions from stdin with `--file -`. + +* `nix store make-content-addressable` has been renamed to `nix store + make-content-addressed`. diff --git a/src/nix/main.cc b/src/nix/main.cc index b923f2535..9bc6c15fa 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -117,7 +117,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs {"hash-path", {"hash", "path"}}, {"ls-nar", {"nar", "ls"}}, {"ls-store", {"store", "ls"}}, - {"make-content-addressable", {"store", "make-content-addressable"}}, + {"make-content-addressable", {"store", "make-content-addressed"}}, {"optimise-store", {"store", "optimise"}}, {"ping-store", {"store", "ping"}}, {"sign-paths", {"store", "sign"}}, diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressed.cc similarity index 83% rename from src/nix/make-content-addressable.cc rename to src/nix/make-content-addressed.cc index a8579ea7c..dc0447cb8 100644 --- a/src/nix/make-content-addressable.cc +++ b/src/nix/make-content-addressed.cc @@ -6,9 +6,9 @@ using namespace nix; -struct CmdMakeContentAddressable : StorePathsCommand, MixJSON +struct CmdMakeContentAddressed : StorePathsCommand, MixJSON { - CmdMakeContentAddressable() + CmdMakeContentAddressed() { realiseMode = Realise::Outputs; } @@ -21,7 +21,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON std::string doc() override { return - #include "make-content-addressable.md" + #include "make-content-addressed.md" ; } @@ -50,4 +50,4 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON } }; -static auto rCmdMakeContentAddressable = registerCommand2<CmdMakeContentAddressable>({"store", "make-content-addressable"}); +static auto rCmdMakeContentAddressed = registerCommand2<CmdMakeContentAddressed>({"store", "make-content-addressed"}); diff --git a/src/nix/make-content-addressable.md b/src/nix/make-content-addressed.md similarity index 94% rename from src/nix/make-content-addressable.md rename to src/nix/make-content-addressed.md index 3dd847edc..215683e6d 100644 --- a/src/nix/make-content-addressable.md +++ b/src/nix/make-content-addressed.md @@ -5,7 +5,7 @@ R""( * Create a content-addressed representation of the closure of GNU Hello: ```console - # nix store make-content-addressable -r nixpkgs#hello + # nix store make-content-addressed nixpkgs#hello … rewrote '/nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10' to '/nix/store/5skmmcb9svys5lj3kbsrjg7vf2irid63-hello-2.10' ``` @@ -29,7 +29,7 @@ R""( system closure: ```console - # nix store make-content-addressable -r /run/current-system + # nix store make-content-addressed /run/current-system ``` # Description From 7ffda0af6effbf32c8668f34cc3f0448c58bc3c1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 22:01:20 +0100 Subject: [PATCH 038/171] fetchClosure: Skip makeContentAddressed() if toPath is already valid --- src/libexpr/primops/fetchClosure.cc | 42 +++++++++++++++-------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 56fd53ed5..c3f07b6d6 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -54,26 +54,28 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args auto fromStore = openStore(*fromStoreUrl); if (toCA) { - auto remappings = makeContentAddressed(*fromStore, *state.store, { *fromPath }); - auto i = remappings.find(*fromPath); - assert(i != remappings.end()); - if (toPath && *toPath != i->second) - throw Error({ - .msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected", - state.store->printStorePath(*fromPath), - state.store->printStorePath(i->second), - state.store->printStorePath(*toPath)), - .errPos = pos - }); - if (!toPath) - throw Error({ - .msg = hintfmt( - "rewriting '%s' to content-addressed form yielded '%s'; " - "please set this in the 'toPath' attribute passed to 'fetchClosure'", - state.store->printStorePath(*fromPath), - state.store->printStorePath(i->second)), - .errPos = pos - }); + if (!toPath || !state.store->isValidPath(*toPath)) { + auto remappings = makeContentAddressed(*fromStore, *state.store, { *fromPath }); + auto i = remappings.find(*fromPath); + assert(i != remappings.end()); + if (toPath && *toPath != i->second) + throw Error({ + .msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected", + state.store->printStorePath(*fromPath), + state.store->printStorePath(i->second), + state.store->printStorePath(*toPath)), + .errPos = pos + }); + if (!toPath) + throw Error({ + .msg = hintfmt( + "rewriting '%s' to content-addressed form yielded '%s'; " + "please set this in the 'toPath' attribute passed to 'fetchClosure'", + state.store->printStorePath(*fromPath), + state.store->printStorePath(i->second)), + .errPos = pos + }); + } } else { copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath }); toPath = fromPath; From 4120930ac19ab7296818fdc1d1389e7799168867 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 22:47:33 +0100 Subject: [PATCH 039/171] fetchClosure: Only allow some "safe" store types --- src/libexpr/primops/fetchClosure.cc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index c3f07b6d6..247bceb07 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -1,6 +1,7 @@ #include "primops.hh" #include "store-api.hh" #include "make-content-addressed.hh" +#include "url.hh" namespace nix { @@ -50,8 +51,15 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args .errPos = pos }); - // FIXME: only allow some "trusted" store types (like BinaryCacheStore). - auto fromStore = openStore(*fromStoreUrl); + auto parsedURL = parseURL(*fromStoreUrl); + + if (parsedURL.scheme != "http" && parsedURL.scheme != "https") + throw Error({ + .msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"), + .errPos = pos + }); + + auto fromStore = openStore(parsedURL.to_string()); if (toCA) { if (!toPath || !state.store->isValidPath(*toPath)) { From 28186b7044dca513e6e07c3e66b7de2143543ae4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 23:19:21 +0100 Subject: [PATCH 040/171] Add a test for fetchClosure and 'nix store make-content-addressed' --- src/libexpr/primops/fetchClosure.cc | 4 +- src/libstore/make-content-addressed.cc | 2 +- tests/binary-cache.sh | 2 +- tests/fetchClosure.sh | 57 ++++++++++++++++++++++++++ tests/local.mk | 3 +- 5 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 tests/fetchClosure.sh diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 247bceb07..47e2d2bf2 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -53,7 +53,9 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args auto parsedURL = parseURL(*fromStoreUrl); - if (parsedURL.scheme != "http" && parsedURL.scheme != "https") + if (parsedURL.scheme != "http" && + parsedURL.scheme != "https" && + !(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file")) throw Error({ .msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"), .errPos = pos diff --git a/src/libstore/make-content-addressed.cc b/src/libstore/make-content-addressed.cc index fc11fcb27..64d172918 100644 --- a/src/libstore/make-content-addressed.cc +++ b/src/libstore/make-content-addressed.cc @@ -52,7 +52,7 @@ std::map<StorePath, StorePath> makeContentAddressed( auto dstPath = dstStore.makeFixedOutputPath( FileIngestionMethod::Recursive, narModuloHash, path.name(), references, hasSelfReference); - printInfo("rewroting '%s' to '%s'", pathS, srcStore.printStorePath(dstPath)); + printInfo("rewriting '%s' to '%s'", pathS, srcStore.printStorePath(dstPath)); StringSink sink2; RewritingSink rsink2(oldHashPart, std::string(dstPath.hashPart()), sink2); diff --git a/tests/binary-cache.sh b/tests/binary-cache.sh index 2368884f7..0361ac6a8 100644 --- a/tests/binary-cache.sh +++ b/tests/binary-cache.sh @@ -1,6 +1,6 @@ source common.sh -needLocalStore "“--no-require-sigs” can’t be used with the daemon" +needLocalStore "'--no-require-sigs' can’t be used with the daemon" # We can produce drvs directly into the binary cache clearStore diff --git a/tests/fetchClosure.sh b/tests/fetchClosure.sh new file mode 100644 index 000000000..811d44af9 --- /dev/null +++ b/tests/fetchClosure.sh @@ -0,0 +1,57 @@ +source common.sh + +needLocalStore "'--no-require-sigs' can’t be used with the daemon" + +clearStore +clearCacheCache + +# Initialize binary cache. +nonCaPath=$(nix build --json --file ./dependencies.nix | jq -r .[].outputs.out) +caPath=$(nix store make-content-addressed --json $nonCaPath | jq -r '.rewrites | map(.) | .[]') +nix copy --to file://$cacheDir $nonCaPath + +# Test basic fetchClosure rewriting from non-CA to CA. +clearStore + +[ ! -e $nonCaPath ] +[ ! -e $caPath ] + +[[ $(nix eval -v --raw --expr " + builtins.fetchClosure { + fromStore = \"file://$cacheDir\"; + fromPath = $nonCaPath; + toPath = $caPath; + } +") = $caPath ]] + +[ ! -e $nonCaPath ] +[ -e $caPath ] + +# In impure mode, we can use non-CA paths. +[[ $(nix eval --raw --no-require-sigs --impure --expr " + builtins.fetchClosure { + fromStore = \"file://$cacheDir\"; + fromPath = $nonCaPath; + } +") = $nonCaPath ]] + +[ -e $nonCaPath ] + +# 'toPath' set to empty string should fail but print the expected path. +nix eval -v --json --expr " + builtins.fetchClosure { + fromStore = \"file://$cacheDir\"; + fromPath = $nonCaPath; + toPath = \"\"; + } +" 2>&1 | grep "error: rewriting.*$nonCaPath.*yielded.*$caPath" + +# If fromPath is CA, then toPath isn't needed. +nix copy --to file://$cacheDir $caPath + +[[ $(nix eval -v --raw --expr " + builtins.fetchClosure { + fromStore = \"file://$cacheDir\"; + fromPath = $caPath; + } +") = $caPath ]] diff --git a/tests/local.mk b/tests/local.mk index c686be049..97971dd76 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -96,7 +96,8 @@ nix_tests = \ describe-stores.sh \ nix-profile.sh \ suggestions.sh \ - store-ping.sh + store-ping.sh \ + fetchClosure.sh ifeq ($(HAVE_LIBCPUID), 1) nix_tests += compute-levels.sh From 98658ae9d25e5e715f9349fbbb79d0ba31bb810c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 23:31:48 +0100 Subject: [PATCH 041/171] Document fetchClosure --- doc/manual/src/release-notes/rl-next.md | 7 ++++++ src/libexpr/primops/fetchClosure.cc | 33 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 8fbc605e7..33eaa8a2c 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -4,3 +4,10 @@ * `nix store make-content-addressable` has been renamed to `nix store make-content-addressed`. + +* New builtin function `builtins.fetchClosure` that copies a closure + from a binary cache at evaluation time and rewrites it to + content-addressed form (if it isn't already). Like + `builtins.storePath`, this allows importing pre-built store paths; + the difference is that it doesn't require the user to configure + binary caches and trusted public keys. diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 47e2d2bf2..045e97dbd 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -110,6 +110,39 @@ static RegisterPrimOp primop_fetchClosure({ .name = "__fetchClosure", .args = {"args"}, .doc = R"( + Fetch a Nix store closure from a binary cache, rewriting it into + content-addressed form. For example, + + ```nix + builtins.fetchClosure { + fromStore = "https://cache.nixos.org"; + fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1; + toPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1; + } + ``` + + fetches `/nix/store/r2jd...` from the specified binary cache, + and rewrites it into the content-addressed store path + `/nix/store/ldbh...`. + + If `fromPath` is already content-addressed, or if you are + allowing impure evaluation (`--impure`), then `toPath` may be + omitted. + + To find out the correct value for `toPath` given a `fromPath`, + you can use `nix store make-content-addressed`: + + ```console + # nix store make-content-addressed /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1 + rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1' + ``` + + This function is similar to `builtins.storePath` in that it + allows you to use a previously built store path in a Nix + expression. However, it is more reproducible because it requires + specifying a binary cache from which the path can be fetched. + Also, requiring a content-addressed final store path avoids the + need for users to configure binary cache public keys. )", .fun = prim_fetchClosure, }); From e5f7029ba49a486c1b0c8011be79b8b41be20935 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 22 Mar 2022 23:40:04 +0100 Subject: [PATCH 042/171] nix store make-content-addressed: Support --from / --to --- src/libexpr/primops/fetchClosure.cc | 2 +- src/nix/make-content-addressed.cc | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 045e97dbd..3e07d1d18 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -133,7 +133,7 @@ static RegisterPrimOp primop_fetchClosure({ you can use `nix store make-content-addressed`: ```console - # nix store make-content-addressed /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1 + # nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1 rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1' ``` diff --git a/src/nix/make-content-addressed.cc b/src/nix/make-content-addressed.cc index dc0447cb8..34860c38f 100644 --- a/src/nix/make-content-addressed.cc +++ b/src/nix/make-content-addressed.cc @@ -6,7 +6,7 @@ using namespace nix; -struct CmdMakeContentAddressed : StorePathsCommand, MixJSON +struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand, MixJSON { CmdMakeContentAddressed() { @@ -25,9 +25,11 @@ struct CmdMakeContentAddressed : StorePathsCommand, MixJSON ; } - void run(ref<Store> store, StorePaths && storePaths) override + void run(ref<Store> srcStore, StorePaths && storePaths) override { - auto remappings = makeContentAddressed(*store, *store, + auto dstStore = dstUri.empty() ? openStore() : openStore(dstUri); + + auto remappings = makeContentAddressed(*srcStore, *dstStore, StorePathSet(storePaths.begin(), storePaths.end())); if (json) { @@ -36,15 +38,15 @@ struct CmdMakeContentAddressed : StorePathsCommand, MixJSON for (auto & path : storePaths) { auto i = remappings.find(path); assert(i != remappings.end()); - jsonRewrites.attr(store->printStorePath(path), store->printStorePath(i->second)); + jsonRewrites.attr(srcStore->printStorePath(path), srcStore->printStorePath(i->second)); } } else { for (auto & path : storePaths) { auto i = remappings.find(path); assert(i != remappings.end()); notice("rewrote '%s' to '%s'", - store->printStorePath(path), - store->printStorePath(i->second)); + srcStore->printStorePath(path), + srcStore->printStorePath(i->second)); } } } From f902f3c2cbfa1a78bec8d135297abe244452e794 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 24 Mar 2022 21:33:20 +0100 Subject: [PATCH 043/171] Add experimental feature 'fetch-closure' --- src/libexpr/primops/fetchClosure.cc | 2 ++ src/libutil/experimental-features.cc | 1 + src/libutil/experimental-features.hh | 3 ++- tests/fetchClosure.sh | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 3e07d1d18..47f40ef33 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -7,6 +7,8 @@ namespace nix { static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) { + settings.requireExperimentalFeature(Xp::FetchClosure); + state.forceAttrs(*args[0], pos); std::optional<std::string> fromStoreUrl; diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index b49f47e1d..01f318fa3 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -11,6 +11,7 @@ std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = { { Xp::NixCommand, "nix-command" }, { Xp::RecursiveNix, "recursive-nix" }, { Xp::NoUrlLiterals, "no-url-literals" }, + { Xp::FetchClosure, "fetch-closure" }, }; const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name) diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index 291a58e32..b5140dcfe 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -19,7 +19,8 @@ enum struct ExperimentalFeature Flakes, NixCommand, RecursiveNix, - NoUrlLiterals + NoUrlLiterals, + FetchClosure, }; /** diff --git a/tests/fetchClosure.sh b/tests/fetchClosure.sh index 811d44af9..0c905ac43 100644 --- a/tests/fetchClosure.sh +++ b/tests/fetchClosure.sh @@ -1,5 +1,6 @@ source common.sh +enableFeatures "fetch-closure" needLocalStore "'--no-require-sigs' can’t be used with the daemon" clearStore From c85467a1b65d2a8907fcab3303572f76c071776d Mon Sep 17 00:00:00 2001 From: Maximilian Bosch <maximilian@mbosch.me> Date: Sun, 6 Feb 2022 14:47:07 +0100 Subject: [PATCH 044/171] Revert "TarArchive: Small refactoring" This reverts commit 50a35860ee9237d341948437c5f70a7f0987d393. With this change Nix fails to open bzip2 logfiles that were created from builds with no stdout/stderr. --- src/libutil/tarfile.cc | 26 ++++++++++++++------------ src/libutil/tarfile.hh | 3 --- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc index 790bc943a..a7db58559 100644 --- a/src/libutil/tarfile.cc +++ b/src/libutil/tarfile.cc @@ -39,30 +39,32 @@ void TarArchive::check(int err, const std::string & reason) throw Error(reason, archive_error_string(this->archive)); } -TarArchive::TarArchive(Source & source, bool raw) - : source(&source), buffer(4096) +TarArchive::TarArchive(Source & source, bool raw) : buffer(4096) { - init(); - if (!raw) + this->archive = archive_read_new(); + this->source = &source; + + if (!raw) { + archive_read_support_filter_all(archive); archive_read_support_format_all(archive); - else + } else { + archive_read_support_filter_all(archive); archive_read_support_format_raw(archive); + archive_read_support_format_empty(archive); + } check(archive_read_open(archive, (void *)this, callback_open, callback_read, callback_close), "Failed to open archive (%s)"); } + TarArchive::TarArchive(const Path & path) { - init(); + this->archive = archive_read_new(); + + archive_read_support_filter_all(archive); archive_read_support_format_all(archive); check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s"); } -void TarArchive::init() -{ - archive = archive_read_new(); - archive_read_support_filter_all(archive); -} - void TarArchive::close() { check(archive_read_close(this->archive), "Failed to close archive (%s)"); diff --git a/src/libutil/tarfile.hh b/src/libutil/tarfile.hh index f107a7e2e..4d9141fd4 100644 --- a/src/libutil/tarfile.hh +++ b/src/libutil/tarfile.hh @@ -17,13 +17,10 @@ struct TarArchive { // disable copy constructor TarArchive(const TarArchive &) = delete; - void init(); - void close(); ~TarArchive(); }; - void unpackTarfile(Source & source, const Path & destDir); void unpackTarfile(const Path & tarFile, const Path & destDir); From d02e34ef06e24f98404d7a50130ddced15a12279 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch <maximilian@mbosch.me> Date: Sun, 6 Feb 2022 14:52:04 +0100 Subject: [PATCH 045/171] Implement regression test for empty logs loaded via `nix log` --- tests/logging.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/logging.sh b/tests/logging.sh index c894ad3ff..1481b9b36 100644 --- a/tests/logging.sh +++ b/tests/logging.sh @@ -13,3 +13,14 @@ rm -rf $NIX_LOG_DIR (! nix-store -l $path) nix-build dependencies.nix --no-out-link --compress-build-log [ "$(nix-store -l $path)" = FOO ] + +# test whether empty logs work fine with `nix log`. +builder="$(mktemp)" +echo -e "#!/bin/sh\nmkdir \$out" > "$builder" +outp="$(nix-build -E \ + 'with import ./config.nix; mkDerivation { name = "fnord"; builder = '"$builder"'; }' \ + --out-link "$(mktemp -d)/result")" + +test -d "$outp" + +nix log "$outp" From 175c78591b1877c280936cc663b76e47f207dd77 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 24 Mar 2022 23:09:43 +0100 Subject: [PATCH 046/171] Random cleanup --- src/libstore/build/goal.hh | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/libstore/build/goal.hh b/src/libstore/build/goal.hh index 07c752bb9..35121c5d9 100644 --- a/src/libstore/build/goal.hh +++ b/src/libstore/build/goal.hh @@ -40,21 +40,21 @@ struct Goal : public std::enable_shared_from_this<Goal> WeakGoals waiters; /* Number of goals we are/were waiting for that have failed. */ - unsigned int nrFailed; + size_t nrFailed = 0; /* Number of substitution goals we are/were waiting for that failed because there are no substituters. */ - unsigned int nrNoSubstituters; + size_t nrNoSubstituters = 0; /* Number of substitution goals we are/were waiting for that failed because they had unsubstitutable references. */ - unsigned int nrIncompleteClosure; + size_t nrIncompleteClosure = 0; /* Name of this goal for debugging purposes. */ std::string name; /* Whether the goal is finished. */ - ExitCode exitCode; + ExitCode exitCode = ecBusy; /* Build result. */ BuildResult buildResult; @@ -65,10 +65,7 @@ struct Goal : public std::enable_shared_from_this<Goal> Goal(Worker & worker, DerivedPath path) : worker(worker) , buildResult { .path = std::move(path) } - { - nrFailed = nrNoSubstituters = nrIncompleteClosure = 0; - exitCode = ecBusy; - } + { } virtual ~Goal() { From 09796c026376b49a8bc5bb3a2865db015d3dfb06 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 24 Mar 2022 23:24:10 +0100 Subject: [PATCH 047/171] Random cleanup --- src/libstore/build/goal.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/build/goal.cc b/src/libstore/build/goal.cc index d2420b107..58e805f55 100644 --- a/src/libstore/build/goal.cc +++ b/src/libstore/build/goal.cc @@ -28,7 +28,7 @@ void Goal::addWaitee(GoalPtr waitee) void Goal::waiteeDone(GoalPtr waitee, ExitCode result) { - assert(waitees.find(waitee) != waitees.end()); + assert(waitees.count(waitee)); waitees.erase(waitee); trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size())); From fe5509df9a58359a67da8c72ed8721cb3a33371d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 24 Mar 2022 23:24:48 +0100 Subject: [PATCH 048/171] Only return wanted outputs --- src/libstore/build/derivation-goal.cc | 5 +---- src/libstore/build/local-derivation-goal.cc | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 40c445836..7dc5737fb 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -311,14 +311,11 @@ void DerivationGoal::outputsSubstitutionTried() gaveUpOnSubstitution(); } + /* At least one of the output paths could not be produced using a substitute. So we have to build instead. */ void DerivationGoal::gaveUpOnSubstitution() { - /* Make sure checkPathValidity() from now on checks all - outputs. */ - wantedOutputs.clear(); - /* The inputs must be built before we can build this goal. */ if (useDerivation) for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 75e7e6ca3..ed83c4770 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -2613,7 +2613,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs() signRealisation(thisRealisation); worker.store.registerDrvOutput(thisRealisation); } - builtOutputs.emplace(thisRealisation.id, thisRealisation); + if (wantedOutputs.empty() || wantedOutputs.count(outputName)) + builtOutputs.emplace(thisRealisation.id, thisRealisation); } return builtOutputs; From 540d7e33d879e21d34de0935292a65bf27f5e312 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 24 Mar 2022 23:25:12 +0100 Subject: [PATCH 049/171] Retry substitution after an incomplete closure only once This avoids an infinite loop in the final test in tests/binary-cache.sh. I think this was only not triggered previously by accident (because we were clearing wantedOutputs in between). --- src/libstore/build/derivation-goal.cc | 5 ++--- src/libstore/build/derivation-goal.hh | 8 ++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 7dc5737fb..3d1c4fbc1 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -207,8 +207,6 @@ void DerivationGoal::haveDerivation() if (!drv->type().hasKnownOutputPaths()) settings.requireExperimentalFeature(Xp::CaDerivations); - retrySubstitution = false; - for (auto & i : drv->outputsAndOptPaths(worker.store)) if (i.second.second) worker.store.addTempRoot(*i.second.second); @@ -423,7 +421,8 @@ void DerivationGoal::inputsRealised() return; } - if (retrySubstitution) { + if (retrySubstitution && !retriedSubstitution) { + retriedSubstitution = true; haveDerivation(); return; } diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh index ea2db89b2..f556b6f25 100644 --- a/src/libstore/build/derivation-goal.hh +++ b/src/libstore/build/derivation-goal.hh @@ -61,8 +61,12 @@ struct DerivationGoal : public Goal bool needRestart = false; /* Whether to retry substituting the outputs after building the - inputs. */ - bool retrySubstitution; + inputs. This is done in case of an incomplete closure. */ + bool retrySubstitution = false; + + /* Whether we've retried substitution, in which case we won't try + again. */ + bool retriedSubstitution = false; /* The derivation stored at drvPath. */ std::unique_ptr<Derivation> drv; From 187dc080a2ab3403c1b1b47b3face205a4a67fb4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 24 Mar 2022 23:36:14 +0100 Subject: [PATCH 050/171] tests/build.sh: Test that 'nix build' only prints wanted outputs --- tests/build.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/build.sh b/tests/build.sh index c77f620f7..13a0f42be 100644 --- a/tests/build.sh +++ b/tests/build.sh @@ -1,15 +1,27 @@ source common.sh -expectedJSONRegex='\[\{"drvPath":".*multiple-outputs-a.drv","outputs":\{"first":".*multiple-outputs-a-first","second":".*multiple-outputs-a-second"}},\{"drvPath":".*multiple-outputs-b.drv","outputs":\{"out":".*multiple-outputs-b"}}]' +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 ' (.[0] | (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | keys | length == 2) and (.outputs.first | match(".*multiple-outputs-a-first")) and (.outputs.second | match(".*multiple-outputs-a-second"))) and (.[1] | (.drvPath | match(".*multiple-outputs-b.drv")) and + (.outputs | keys | length == 1) and (.outputs.out | match(".*multiple-outputs-b"))) ' + testNormalization () { clearStore outPath=$(nix-build ./simple.nix --no-out-link) From cbcb69a39ce7ceb300d5a5588258b7f8c0c0664d Mon Sep 17 00:00:00 2001 From: polykernel <81340136+polykernel@users.noreply.github.com> Date: Sun, 20 Mar 2022 11:39:36 -0400 Subject: [PATCH 051/171] nix: allow whitespace characters before command in repl Before this change, processLine always uses the first character as the start of the line. This cause whitespaces to matter at the beginning of the line whereas it does not matter anywhere else. This commit trims leading white spaces of the string line so that subsequent operations can be performed on the string without explicitly tracking starting and ending indices of the string. --- src/nix/repl.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 916353d8c..1f9d4fb4e 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -396,6 +396,7 @@ StorePath NixRepl::getDerivationPath(Value & v) { bool NixRepl::processLine(std::string line) { + line = trim(line); if (line == "") return true; _isInterrupted = false; From 50c229ad9aeece3b4728fedb741d8ef7adb2b2c1 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Fri, 25 Mar 2022 08:02:49 +0100 Subject: [PATCH 052/171] Use wantOutput Co-authored-by: John Ericson <git@JohnEricson.me> --- src/libstore/build/local-derivation-goal.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index ed83c4770..b176f318b 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -2613,7 +2613,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs() signRealisation(thisRealisation); worker.store.registerDrvOutput(thisRealisation); } - if (wantedOutputs.empty() || wantedOutputs.count(outputName)) + if (wantOutput(outputName, wantedOutputs)) builtOutputs.emplace(thisRealisation.id, thisRealisation); } From 86b05ccd54f2e98ac2b5cef3bcecb29ed6ec4fd8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Fri, 25 Mar 2022 14:04:18 +0100 Subject: [PATCH 053/171] Only provide builtin.{getFlake,fetchClosure} is the corresponding experimental feature is enabled This allows writing fallback code like if builtins ? fetchClosure then builtins.fetchClose { ... } else builtins.storePath ... --- doc/manual/src/release-notes/rl-next.md | 9 ++++++--- src/libexpr/eval.cc | 17 ----------------- src/libexpr/eval.hh | 6 ------ src/libexpr/flake/flake.cc | 9 ++++++--- src/libexpr/primops.cc | 20 ++++++++++++-------- src/libexpr/primops.hh | 2 ++ src/libexpr/primops/fetchClosure.cc | 6 ++++-- src/nix/main.cc | 1 + 8 files changed, 31 insertions(+), 39 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 33eaa8a2c..2ec864ee4 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -5,9 +5,12 @@ * `nix store make-content-addressable` has been renamed to `nix store make-content-addressed`. -* New builtin function `builtins.fetchClosure` that copies a closure - from a binary cache at evaluation time and rewrites it to - content-addressed form (if it isn't already). Like +* New experimental builtin function `builtins.fetchClosure` that + copies a closure from a binary cache at evaluation time and rewrites + it to content-addressed form (if it isn't already). Like `builtins.storePath`, this allows importing pre-built store paths; the difference is that it doesn't require the user to configure binary caches and trusted public keys. + + This function is only available if you enable the experimental + feature `fetch-closure`. diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 3ec5ed202..437c7fc53 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -507,23 +507,6 @@ EvalState::~EvalState() } -void EvalState::requireExperimentalFeatureOnEvaluation( - const ExperimentalFeature & feature, - const std::string_view fName, - const Pos & pos) -{ - if (!settings.isExperimentalFeatureEnabled(feature)) { - throw EvalError({ - .msg = hintfmt( - "Cannot call '%2%' because experimental Nix feature '%1%' is disabled. You can enable it via '--extra-experimental-features %1%'.", - feature, - fName - ), - .errPos = pos - }); - } -} - void EvalState::allowPath(const Path & path) { if (allowedPaths) diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 198a62ad2..ab46b448c 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -149,12 +149,6 @@ public: std::shared_ptr<Store> buildStore = nullptr); ~EvalState(); - void requireExperimentalFeatureOnEvaluation( - const ExperimentalFeature &, - const std::string_view fName, - const Pos & pos - ); - void addToSearchPath(const std::string & s); SearchPath getSearchPath() { return searchPath; } diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 6a1aca40d..fdcfb18d7 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -706,8 +706,6 @@ void callFlake(EvalState & state, static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) { - state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos); - std::string flakeRefS(state.forceStringNoCtx(*args[0], pos)); auto flakeRef = parseFlakeRef(flakeRefS, {}, true); if (evalSettings.pureEval && !flakeRef.input.isLocked()) @@ -723,7 +721,12 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va v); } -static RegisterPrimOp r2("__getFlake", 1, prim_getFlake); +static RegisterPrimOp r2({ + .name = "__getFlake", + .args = {"args"}, + .fun = prim_getFlake, + .experimentalFeature = Xp::Flakes, +}); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index f5d9aa9ae..f3eb5e925 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -3814,7 +3814,7 @@ RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun) .name = name, .args = {}, .arity = arity, - .fun = fun + .fun = fun, }); } @@ -3886,13 +3886,17 @@ void EvalState::createBaseEnv() if (RegisterPrimOp::primOps) for (auto & primOp : *RegisterPrimOp::primOps) - addPrimOp({ - .fun = primOp.fun, - .arity = std::max(primOp.args.size(), primOp.arity), - .name = symbols.create(primOp.name), - .args = primOp.args, - .doc = primOp.doc, - }); + if (!primOp.experimentalFeature + || settings.isExperimentalFeatureEnabled(*primOp.experimentalFeature)) + { + addPrimOp({ + .fun = primOp.fun, + .arity = std::max(primOp.args.size(), primOp.arity), + .name = symbols.create(primOp.name), + .args = primOp.args, + .doc = primOp.doc, + }); + } /* Add a wrapper around the derivation primop that computes the `drvPath' and `outPath' attributes lazily. */ diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh index 5b16e075f..905bd0366 100644 --- a/src/libexpr/primops.hh +++ b/src/libexpr/primops.hh @@ -16,6 +16,7 @@ struct RegisterPrimOp size_t arity = 0; const char * doc; PrimOpFun fun; + std::optional<ExperimentalFeature> experimentalFeature; }; typedef std::vector<Info> PrimOps; @@ -35,6 +36,7 @@ struct RegisterPrimOp /* These primops are disabled without enableNativeCode, but plugins may wish to use them in limited contexts without globally enabling them. */ + /* Load a ValueInitializer from a DSO and return whatever it initializes */ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v); diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index 47f40ef33..efeb93daf 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -7,8 +7,6 @@ namespace nix { static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) { - settings.requireExperimentalFeature(Xp::FetchClosure); - state.forceAttrs(*args[0], pos); std::optional<std::string> fromStoreUrl; @@ -145,8 +143,12 @@ static RegisterPrimOp primop_fetchClosure({ specifying a binary cache from which the path can be fetched. Also, requiring a content-addressed final store path avoids the need for users to configure binary cache public keys. + + This function is only available if you enable the experimental + feature `fetch-closure`. )", .fun = prim_fetchClosure, + .experimentalFeature = Xp::FetchClosure, }); } diff --git a/src/nix/main.cc b/src/nix/main.cc index 9bc6c15fa..6198681e7 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -289,6 +289,7 @@ void mainWrapped(int argc, char * * argv) } if (argc == 2 && std::string(argv[1]) == "__dump-builtins") { + settings.experimentalFeatures = {Xp::Flakes, Xp::FetchClosure}; evalSettings.pureEval = false; EvalState state({}, openStore("dummy://")); auto res = nlohmann::json::object(); From 8c363eb3eb3db96287a10eb5fcde23986a96164f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Fri, 25 Mar 2022 14:19:55 +0100 Subject: [PATCH 054/171] Document getFlake Fixes #5523. --- src/libexpr/flake/flake.cc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index fdcfb18d7..22257c6b3 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -724,6 +724,24 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va static RegisterPrimOp r2({ .name = "__getFlake", .args = {"args"}, + .doc = R"( + Fetch a flake from a flake reference, and return its output attributes and some metadata. For example: + + ```nix + (builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix + ``` + + Unless impure evaluation is allowed (`--impure`), the flake reference + must be "locked", e.g. contain a Git revision or content hash. An + example of an unlocked usage is: + + ```nix + (builtins.getFlake "github:edolstra/dwarffs").rev + ``` + + This function is only available if you enable the experimental feature + `flakes`. + )", .fun = prim_getFlake, .experimentalFeature = Xp::Flakes, }); From fc35b11a7cde047cd799a6c83b0da312b713e0fb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Fri, 25 Mar 2022 15:22:22 +0100 Subject: [PATCH 055/171] Fix mismatched tag warning on clang --- src/libexpr/eval.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index ab46b448c..e7915dd99 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -17,7 +17,7 @@ namespace nix { -struct Store; +class Store; class EvalState; class StorePath; enum RepairFlag : bool; From 247d2cb661218762a8a1fc0ee475a8ca856fcf17 Mon Sep 17 00:00:00 2001 From: Artturin <Artturin@artturin.com> Date: Sat, 26 Mar 2022 00:58:19 +0200 Subject: [PATCH 056/171] scripts/install-systemd-multi-user.sh: fix typo sytemd-tmpfiles -> systemd-tmpfiles --- scripts/install-systemd-multi-user.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install-systemd-multi-user.sh b/scripts/install-systemd-multi-user.sh index 24884a023..1d92c5388 100755 --- a/scripts/install-systemd-multi-user.sh +++ b/scripts/install-systemd-multi-user.sh @@ -90,7 +90,7 @@ poly_configure_nix_daemon_service() { ln -sfn /nix/var/nix/profiles/default/$TMPFILES_SRC $TMPFILES_DEST _sudo "to run systemd-tmpfiles once to pick that path up" \ - sytemd-tmpfiles create --prefix=/nix/var/nix + systemd-tmpfiles create --prefix=/nix/var/nix _sudo "to set up the nix-daemon service" \ systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC" From 679b3b32c952eed3c3be6c450c8189948e5645cd Mon Sep 17 00:00:00 2001 From: Erik Arvstedt <erik.arvstedt@gmail.com> Date: Sat, 26 Mar 2022 11:32:37 +0100 Subject: [PATCH 057/171] Minor comment fix --- src/libcmd/installables.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 784117569..2dc6c3ff8 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -935,7 +935,7 @@ InstallablesCommand::InstallablesCommand() void InstallablesCommand::prepare() { if (_installables.empty() && useDefaultInstallables()) - // FIXME: commands like "nix install" should not have a + // FIXME: commands like "nix profile install" should not have a // default, probably. _installables.push_back("."); installables = parseInstallables(getStore(), _installables); From 16860a0328094c04c3e877089119a56f9ddfbcd6 Mon Sep 17 00:00:00 2001 From: Erik Arvstedt <erik.arvstedt@gmail.com> Date: Sat, 26 Mar 2022 11:32:38 +0100 Subject: [PATCH 058/171] nix eval: Add option `read-only` --- src/libcmd/command.hh | 5 +++-- src/libcmd/installables.cc | 21 +++++++++++++++++++-- src/nix/eval.cc | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 0f6125f11..15bc13c6e 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -85,11 +85,12 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions { std::optional<Path> file; std::optional<std::string> expr; + bool readOnlyMode = false; // FIXME: move this; not all commands (e.g. 'nix run') use it. OperateOn operateOn = OperateOn::Output; - SourceExprCommand(); + SourceExprCommand(bool supportReadOnlyMode = false); std::vector<std::shared_ptr<Installable>> parseInstallables( ref<Store> store, std::vector<std::string> ss); @@ -128,7 +129,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand { std::shared_ptr<Installable> installable; - InstallableCommand(); + InstallableCommand(bool supportReadOnlyMode = false); void prepare() override; diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 2dc6c3ff8..e5d69ae14 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -1,3 +1,4 @@ +#include "globals.hh" #include "installables.hh" #include "command.hh" #include "attr-path.hh" @@ -129,7 +130,7 @@ MixFlakeOptions::MixFlakeOptions() }); } -SourceExprCommand::SourceExprCommand() +SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode) { addFlag({ .longName = "file", @@ -157,6 +158,17 @@ SourceExprCommand::SourceExprCommand() .category = installablesCategory, .handler = {&operateOn, OperateOn::Derivation}, }); + + if (supportReadOnlyMode) { + addFlag({ + .longName = "read-only", + .description = + "Do not instantiate each evaluated derivation. " + "This improves performance, but can cause errors when accessing " + "store paths of derivations during evaluation.", + .handler = {&readOnlyMode, true}, + }); + } } Strings SourceExprCommand::getDefaultFlakeAttrPaths() @@ -687,6 +699,10 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables( { std::vector<std::shared_ptr<Installable>> result; + if (readOnlyMode) { + settings.readOnlyMode = true; + } + if (file || expr) { if (file && expr) throw UsageError("'--file' and '--expr' are exclusive"); @@ -951,7 +967,8 @@ std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion() return parseFlakeRef(_installables.front(), absPath(".")); } -InstallableCommand::InstallableCommand() +InstallableCommand::InstallableCommand(bool supportReadOnlyMode) + : SourceExprCommand(supportReadOnlyMode) { expectArgs({ .label = "installable", diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 8cd04d5fe..733b93661 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -16,7 +16,7 @@ struct CmdEval : MixJSON, InstallableCommand std::optional<std::string> apply; std::optional<Path> writeTo; - CmdEval() + CmdEval() : InstallableCommand(true /* supportReadOnlyMode */) { addFlag({ .longName = "raw", From 057f9ee1900312f42efe6c5cebb02b07b4ff2131 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Mon, 28 Mar 2022 14:21:35 +0200 Subject: [PATCH 059/171] nix profile install: Don't use queryDerivationOutputMap() Instead get the outputs from Installable::build(). This will also allow 'nix profile install' to support impure derivations. Fixes #6286. --- src/libcmd/installables.cc | 139 ++++++++++++++++++++--------------- src/libcmd/installables.hh | 12 +-- src/libstore/derived-path.hh | 6 ++ src/nix/profile.cc | 35 ++++++--- 4 files changed, 116 insertions(+), 76 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 784117569..955bbe6fb 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -756,55 +756,20 @@ std::shared_ptr<Installable> SourceExprCommand::parseInstallable( return installables.front(); } -BuiltPaths getBuiltPaths(ref<Store> evalStore, ref<Store> store, const DerivedPaths & hopefullyBuiltPaths) +BuiltPaths Installable::build( + ref<Store> evalStore, + ref<Store> store, + Realise mode, + const std::vector<std::shared_ptr<Installable>> & installables, + BuildMode bMode) { BuiltPaths res; - for (const auto & b : hopefullyBuiltPaths) - std::visit( - overloaded{ - [&](const DerivedPath::Opaque & bo) { - res.push_back(BuiltPath::Opaque{bo.path}); - }, - [&](const DerivedPath::Built & bfd) { - OutputPathMap outputs; - auto drv = evalStore->readDerivation(bfd.drvPath); - auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive - auto drvOutputs = drv.outputsAndOptPaths(*store); - for (auto & output : bfd.outputs) { - if (!outputHashes.count(output)) - throw Error( - "the derivation '%s' doesn't have an output named '%s'", - store->printStorePath(bfd.drvPath), output); - if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { - auto outputId = - DrvOutput{outputHashes.at(output), output}; - auto realisation = - store->queryRealisation(outputId); - if (!realisation) - throw Error( - "cannot operate on an output of unbuilt " - "content-addressed derivation '%s'", - outputId.to_string()); - outputs.insert_or_assign( - output, realisation->outPath); - } else { - // If ca-derivations isn't enabled, assume that - // the output path is statically known. - assert(drvOutputs.count(output)); - assert(drvOutputs.at(output).second); - outputs.insert_or_assign( - output, *drvOutputs.at(output).second); - } - } - res.push_back(BuiltPath::Built{bfd.drvPath, outputs}); - }, - }, - b.raw()); - + for (auto & [_, builtPath] : build2(evalStore, store, mode, installables, bMode)) + res.push_back(builtPath); return res; } -BuiltPaths Installable::build( +std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::build2( ref<Store> evalStore, ref<Store> store, Realise mode, @@ -815,39 +780,93 @@ BuiltPaths Installable::build( settings.readOnlyMode = true; std::vector<DerivedPath> pathsToBuild; + std::map<DerivedPath, std::vector<std::shared_ptr<Installable>>> backmap; for (auto & i : installables) { - auto b = i->toDerivedPaths(); - pathsToBuild.insert(pathsToBuild.end(), b.begin(), b.end()); + for (auto b : i->toDerivedPaths()) { + pathsToBuild.push_back(b); + backmap[b].push_back(i); + } } + std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> res; + switch (mode) { + case Realise::Nothing: case Realise::Derivation: printMissing(store, pathsToBuild, lvlError); - return getBuiltPaths(evalStore, store, pathsToBuild); + + for (auto & path : pathsToBuild) { + for (auto & installable : backmap[path]) { + std::visit(overloaded { + [&](const DerivedPath::Built & bfd) { + OutputPathMap outputs; + auto drv = evalStore->readDerivation(bfd.drvPath); + auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive + auto drvOutputs = drv.outputsAndOptPaths(*store); + for (auto & output : bfd.outputs) { + if (!outputHashes.count(output)) + 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 }; + auto realisation = store->queryRealisation(outputId); + if (!realisation) + throw Error( + "cannot operate on an output of unbuilt " + "content-addressed derivation '%s'", + outputId.to_string()); + outputs.insert_or_assign(output, realisation->outPath); + } else { + // If ca-derivations isn't enabled, assume that + // the output path is statically known. + assert(drvOutputs.count(output)); + assert(drvOutputs.at(output).second); + outputs.insert_or_assign( + output, *drvOutputs.at(output).second); + } + } + res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }}); + }, + [&](const DerivedPath::Opaque & bo) { + res.push_back({installable, BuiltPath::Opaque { bo.path }}); + }, + }, path.raw()); + } + } + + break; + case Realise::Outputs: { - BuiltPaths res; for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) { if (!buildResult.success()) buildResult.rethrow(); - std::visit(overloaded { - [&](const DerivedPath::Built & bfd) { - std::map<std::string, StorePath> outputs; - for (auto & path : buildResult.builtOutputs) - outputs.emplace(path.first.outputName, path.second.outPath); - res.push_back(BuiltPath::Built { bfd.drvPath, outputs }); - }, - [&](const DerivedPath::Opaque & bo) { - res.push_back(BuiltPath::Opaque { bo.path }); - }, - }, buildResult.path.raw()); + + for (auto & installable : backmap[buildResult.path]) { + std::visit(overloaded { + [&](const DerivedPath::Built & bfd) { + std::map<std::string, StorePath> outputs; + for (auto & path : buildResult.builtOutputs) + outputs.emplace(path.first.outputName, path.second.outPath); + res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }}); + }, + [&](const DerivedPath::Opaque & bo) { + res.push_back({installable, BuiltPath::Opaque { bo.path }}); + }, + }, buildResult.path.raw()); + } } - return res; + + break; } + default: assert(false); } + + return res; } BuiltPaths Installable::toBuiltPaths( diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index e172b71b0..f4bf0d406 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -98,6 +98,13 @@ struct Installable const std::vector<std::shared_ptr<Installable>> & installables, BuildMode bMode = bmNormal); + static std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> build2( + ref<Store> evalStore, + ref<Store> store, + Realise mode, + const std::vector<std::shared_ptr<Installable>> & installables, + BuildMode bMode = bmNormal); + static std::set<StorePath> toStorePaths( ref<Store> evalStore, ref<Store> store, @@ -185,9 +192,4 @@ ref<eval_cache::EvalCache> openEvalCache( EvalState & state, std::shared_ptr<flake::LockedFlake> lockedFlake); -BuiltPaths getBuiltPaths( - ref<Store> evalStore, - ref<Store> store, - const DerivedPaths & hopefullyBuiltPaths); - } diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh index 8ca0882a4..24a0ae773 100644 --- a/src/libstore/derived-path.hh +++ b/src/libstore/derived-path.hh @@ -25,6 +25,9 @@ struct DerivedPathOpaque { nlohmann::json toJSON(ref<Store> store) const; std::string to_string(const Store & store) const; static DerivedPathOpaque parse(const Store & store, std::string_view); + + bool operator < (const DerivedPathOpaque & b) const + { return path < b.path; } }; /** @@ -46,6 +49,9 @@ struct DerivedPathBuilt { std::string to_string(const Store & store) const; static DerivedPathBuilt parse(const Store & store, std::string_view); nlohmann::json toJSON(ref<Store> store) const; + + bool operator < (const DerivedPathBuilt & b) const + { return std::make_pair(drvPath, outputs) < std::make_pair(b.drvPath, b.outputs); } }; using _DerivedPathRaw = std::variant< diff --git a/src/nix/profile.cc b/src/nix/profile.cc index da990ddc8..f35947ddb 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -62,22 +62,21 @@ struct ProfileElement return std::tuple(describe(), storePaths) < std::tuple(other.describe(), other.storePaths); } - void updateStorePaths(ref<Store> evalStore, ref<Store> store, Installable & installable) + void updateStorePaths( + ref<Store> evalStore, + ref<Store> store, + const BuiltPaths & builtPaths) { // FIXME: respect meta.outputsToInstall storePaths.clear(); - for (auto & buildable : getBuiltPaths(evalStore, store, installable.toDerivedPaths())) { + for (auto & buildable : builtPaths) { std::visit(overloaded { [&](const BuiltPath::Opaque & bo) { storePaths.insert(bo.path); }, [&](const BuiltPath::Built & bfd) { - // TODO: Why are we querying if we know the output - // names already? Is it just to figure out what the - // default one is? - for (auto & output : store->queryDerivationOutputMap(bfd.drvPath)) { + for (auto & output : bfd.outputs) storePaths.insert(output.second); - } }, }, buildable.raw()); } @@ -236,6 +235,16 @@ struct ProfileManifest } }; +static std::map<Installable *, BuiltPaths> +builtPathsPerInstallable( + const std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> & builtPaths) +{ + std::map<Installable *, BuiltPaths> res; + for (auto & [installable, builtPath] : builtPaths) + res[installable.get()].push_back(builtPath); + return res; +} + struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile { std::string description() override @@ -254,7 +263,9 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile { ProfileManifest manifest(*getEvalState(), *profile); - auto builtPaths = Installable::build(getEvalStore(), store, Realise::Outputs, installables, bmNormal); + auto builtPaths = builtPathsPerInstallable( + Installable::build2( + getEvalStore(), store, Realise::Outputs, installables, bmNormal)); for (auto & installable : installables) { ProfileElement element; @@ -269,7 +280,7 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile }; } - element.updateStorePaths(getEvalStore(), store, *installable); + element.updateStorePaths(getEvalStore(), store, builtPaths[installable.get()]); manifest.elements.push_back(std::move(element)); } @@ -457,12 +468,14 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf warn ("Use 'nix profile list' to see the current profile."); } - auto builtPaths = Installable::build(getEvalStore(), store, Realise::Outputs, installables, bmNormal); + auto builtPaths = builtPathsPerInstallable( + Installable::build2( + getEvalStore(), store, Realise::Outputs, installables, bmNormal)); for (size_t i = 0; i < installables.size(); ++i) { auto & installable = installables.at(i); auto & element = manifest.elements[indices.at(i)]; - element.updateStorePaths(getEvalStore(), store, *installable); + element.updateStorePaths(getEvalStore(), store, builtPaths[installable.get()]); } updateProfile(manifest.build(store)); From b266fd53dda9c303c55ceb55752c2117011fce69 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Mon, 28 Mar 2022 14:58:38 +0200 Subject: [PATCH 060/171] nix {run,shell}: Print a better error message if the store is not local Closes #6317 --- src/nix/run.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/nix/run.cc b/src/nix/run.cc index 033263c36..23e893fbf 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -38,9 +38,12 @@ void runProgramInStore(ref<Store> store, unshare(CLONE_NEWUSER) doesn't work in a multithreaded program (which "nix" is), so we exec() a single-threaded helper program (chrootHelper() below) to do the work. */ - auto store2 = store.dynamic_pointer_cast<LocalStore>(); + auto store2 = store.dynamic_pointer_cast<LocalFSStore>(); - if (store2 && store->storeDir != store2->getRealStoreDir()) { + if (!store2) + throw Error("store '%s' is not a local store so it does not support command execution", store->getUri()); + + if (store->storeDir != store2->getRealStoreDir()) { Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program }; for (auto & arg : args) helperArgs.push_back(arg); From 390269ed8784b1a73a3310e63eb96a4b62861654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <theophane@hufschmitt.net> Date: Wed, 16 Mar 2022 14:21:09 +0100 Subject: [PATCH 061/171] Simplify the handling of the hash modulo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than having four different but very similar types of hashes, make only one, with a tag indicating whether it corresponds to a regular of deferred derivation. This implies a slight logical change: The original Nix+multiple-outputs model assumed only one hash-modulo per derivation. Adding multiple-outputs CA derivations changed this as these have one hash-modulo per output. This change is now treating each derivation as having one hash modulo per output. This obviously means that we internally loose the guaranty that all the outputs of input-addressed derivations have the same hash modulo. But it turns out that it doesn’t matter because there’s nothing in the code taking advantage of that fact (and it probably shouldn’t anyways). The upside is that it is now much easier to work with these hashes, and we can get rid of a lot of useless `std::visit{ overloaded`. Co-authored-by: John Ericson <John.Ericson@Obsidian.Systems> --- src/libexpr/primops.cc | 48 ++++++++++------------- src/libstore/derivations.cc | 77 +++++++++++-------------------------- src/libstore/derivations.hh | 36 ++++------------- src/libstore/local-store.cc | 9 ++--- src/nix/develop.cc | 4 +- 5 files changed, 56 insertions(+), 118 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index f3eb5e925..9f549e52f 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1222,34 +1222,26 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * DerivationOutput::Deferred { }); } - // Regular, non-CA derivation should always return a single hash and not - // hash per output. - auto hashModulo = hashDerivationModulo(*state.store, drv, true); - std::visit(overloaded { - [&](const DrvHash & drvHash) { - auto & h = drvHash.hash; - switch (drvHash.kind) { - case DrvHash::Kind::Deferred: - /* Outputs already deferred, nothing to do */ - break; - case DrvHash::Kind::Regular: - for (auto & [outputName, output] : drv.outputs) { - auto outPath = state.store->makeOutputPath(outputName, h, drvName); - drv.env[outputName] = state.store->printStorePath(outPath); - output = DerivationOutput::InputAddressed { - .path = std::move(outPath), - }; - } - break; - } - }, - [&](const CaOutputHashes &) { - // Shouldn't happen as the toplevel derivation is not CA. - assert(false); - }, - }, - hashModulo.raw()); - + auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true); + 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); + drv.env[i] = state.store->printStorePath(outPath); + drv.outputs.insert_or_assign( + i, + DerivationOutputInputAddressed { + .path = std::move(outPath), + }); + } + break; + ; + case DrvHash::Kind::Deferred: + for (auto & i : outputs) { + drv.outputs.insert_or_assign(i, DerivationOutputDeferred {}); + } + } } /* Write the resulting term into the Nix store directory. */ diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 7fed80387..85d75523f 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -474,7 +474,7 @@ Sync<DrvHashes> drvHashes; /* Look up the derivation by value and memoize the `hashDerivationModulo` call. */ -static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath & drvPath) +static const DrvHash pathDerivationModulo(Store & store, const StorePath & drvPath) { { auto hashes = drvHashes.lock(); @@ -509,7 +509,7 @@ static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath & don't leak the provenance of fixed outputs, reducing pointless cache misses as the build itself won't know this. */ -DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs) +DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs) { auto type = drv.type(); @@ -524,7 +524,10 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m + store.printStorePath(dof.path(store, drv.name, i.first))); outputHashes.insert_or_assign(i.first, std::move(hash)); } - return outputHashes; + return DrvHash{ + .hashes = outputHashes, + .kind = DrvHash::Kind::Regular, + }; } auto kind = std::visit(overloaded { @@ -540,65 +543,36 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m }, }, drv.type().raw()); - /* For other derivations, replace the inputs paths with recursive - calls to this function. */ std::map<std::string, StringSet> inputs2; for (auto & [drvPath, inputOutputs0] : drv.inputDrvs) { // Avoid lambda capture restriction with standard / Clang auto & inputOutputs = inputOutputs0; const auto & res = pathDerivationModulo(store, drvPath); - std::visit(overloaded { - // Regular non-CA derivation, replace derivation - [&](const DrvHash & drvHash) { - kind |= drvHash.kind; - inputs2.insert_or_assign(drvHash.hash.to_string(Base16, false), inputOutputs); - }, - // CA derivation's output hashes - [&](const CaOutputHashes & outputHashes) { - std::set<std::string> justOut = { "out" }; - for (auto & output : inputOutputs) { - /* Put each one in with a single "out" output.. */ - const auto h = outputHashes.at(output); - inputs2.insert_or_assign( - h.to_string(Base16, false), - justOut); - } - }, - }, res.raw()); + 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); + } } auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2)); - return DrvHash { .hash = hash, .kind = kind }; -} - - -void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept -{ - switch (other) { - case DrvHash::Kind::Regular: - break; - case DrvHash::Kind::Deferred: - self = other; - break; + std::map<std::string, Hash> outputHashes; + for (const auto & [outputName, _] : drv.outputs) { + outputHashes.insert_or_assign(outputName, hash); } + + return DrvHash { + .hashes = outputHashes, + .kind = kind, + }; } std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation & drv) { - std::map<std::string, Hash> res; - std::visit(overloaded { - [&](const DrvHash & drvHash) { - for (auto & outputName : drv.outputNames()) { - res.insert({outputName, drvHash.hash}); - } - }, - [&](const CaOutputHashes & outputHashes) { - res = outputHashes; - }, - }, hashDerivationModulo(store, drv, true).raw()); - return res; + return hashDerivationModulo(store, drv, true).hashes; } @@ -747,7 +721,7 @@ 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<DerivationOutput::Deferred>(output.raw())) { - auto & h = hashModulo.requireNoFixedNonDeferred(); + auto & h = hashModulo.hashes.at(outputName); auto outPath = store.makeOutputPath(outputName, h, drv.name); drv.env[outputName] = store.printStorePath(outPath); output = DerivationOutput::InputAddressed { @@ -758,13 +732,6 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String } -const Hash & DrvHashModulo::requireNoFixedNonDeferred() const { - auto * drvHashOpt = std::get_if<DrvHash>(&raw()); - assert(drvHashOpt); - assert(drvHashOpt->kind == DrvHash::Kind::Regular); - return drvHashOpt->hash; -} - static bool tryResolveInput( Store & store, StorePathSet & inputSrcs, StringMap & inputRewrites, const StorePath & inputDrv, const StringSet & inputOutputs) diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index 8dea90abf..63ea5ef76 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -202,12 +202,14 @@ bool isDerivation(const std::string & fileName); the output name is "out". */ std::string outputPathName(std::string_view drvName, std::string_view outputName); -// known CA drv's output hashes, current just for fixed-output derivations -// whose output hashes are always known since they are fixed up-front. -typedef std::map<std::string, Hash> CaOutputHashes; +// The hashes modulo of a derivation. +// +// Each output is given a hash, although in practice only the content-addressed +// derivations (fixed-output or not) will have a different hash for each +// output. struct DrvHash { - Hash hash; + std::map<std::string, Hash> hashes; enum struct Kind: bool { // Statically determined derivations. @@ -222,28 +224,6 @@ struct DrvHash { void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept; -typedef std::variant< - // Regular normalized derivation hash, and whether it was deferred (because - // an ancestor derivation is a floating content addressed derivation). - DrvHash, - // Fixed-output derivation hashes - CaOutputHashes -> _DrvHashModuloRaw; - -struct DrvHashModulo : _DrvHashModuloRaw { - using Raw = _DrvHashModuloRaw; - using Raw::Raw; - - /* Get hash, throwing if it is per-output CA hashes or a - deferred Drv hash. - */ - const Hash & requireNoFixedNonDeferred() const; - - inline const Raw & raw() const { - return static_cast<const Raw &>(*this); - } -}; - /* Returns hashes with the details of fixed-output subderivations expunged. @@ -267,7 +247,7 @@ struct DrvHashModulo : _DrvHashModuloRaw { ATerm, after subderivations have been likewise expunged from that derivation. */ -DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs); +DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs); /* Return a map associating each output to a hash that uniquely identifies its @@ -276,7 +256,7 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv); /* Memoisation of hashDerivationModulo(). */ -typedef std::map<StorePath, DrvHashModulo> DrvHashes; +typedef std::map<StorePath, DrvHash> DrvHashes; // FIXME: global, though at least thread-safe. extern Sync<DrvHashes> drvHashes; diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 46a547db1..60fe53af1 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -695,16 +695,15 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat // combinations that are currently prohibited. drv.type(); - std::optional<Hash> h; + std::optional<DrvHash> hashesModulo; for (auto & i : drv.outputs) { std::visit(overloaded { [&](const DerivationOutput::InputAddressed & doia) { - if (!h) { + if (!hashesModulo) { // somewhat expensive so we do lazily - auto h0 = hashDerivationModulo(*this, drv, true); - h = h0.requireNoFixedNonDeferred(); + hashesModulo = hashDerivationModulo(*this, drv, true); } - StorePath recomputed = makeOutputPath(i.first, *h, drvName); + StorePath recomputed = makeOutputPath(i.first, hashesModulo->hashes.at(i.first), 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/nix/develop.cc b/src/nix/develop.cc index d2f9b5a6a..7fc74d34e 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -204,10 +204,10 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore output.second = DerivationOutput::Deferred { }; drv.env[output.first] = ""; } - auto h0 = hashDerivationModulo(*evalStore, drv, true); - const Hash & h = h0.requireNoFixedNonDeferred(); + auto hashesModulo = hashDerivationModulo(*evalStore, drv, true); for (auto & output : drv.outputs) { + Hash h = hashesModulo.hashes.at(output.first); auto outPath = store->makeOutputPath(output.first, h, drv.name); output.second = DerivationOutput::InputAddressed { .path = outPath, From 3b26dd51ff0d7b51ec90141bfe2d05b52a4ecfd4 Mon Sep 17 00:00:00 2001 From: Graham Christensen <graham@grahamc.com> Date: Tue, 29 Mar 2022 21:05:57 -0400 Subject: [PATCH 062/171] nix-daemon.service: require mounts for /nix/var/nix/db Users may want to mount a filesystem just for the Nix database, with the filesystem's parameters specially tuned for sqlite. For example, on ZFS you might set the recordsize to 64k after changing the database's page size to 65536. --- misc/systemd/nix-daemon.service.in | 1 + 1 file changed, 1 insertion(+) diff --git a/misc/systemd/nix-daemon.service.in b/misc/systemd/nix-daemon.service.in index b4badf2ba..24d894898 100644 --- a/misc/systemd/nix-daemon.service.in +++ b/misc/systemd/nix-daemon.service.in @@ -3,6 +3,7 @@ Description=Nix Daemon Documentation=man:nix-daemon https://nixos.org/manual RequiresMountsFor=@storedir@ RequiresMountsFor=@localstatedir@ +RequiresMountsFor=@localstatedir@/nix/db ConditionPathIsReadWrite=@localstatedir@/nix/daemon-socket [Service] From 8dee15cd31f634183c32649e9e62e576e12e5cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <theophane@hufschmitt.net> Date: Wed, 30 Mar 2022 11:42:47 +0200 Subject: [PATCH 063/171] =?UTF-8?q?Don=E2=80=99t=20create=20a=20file=20in?= =?UTF-8?q?=20the=20worktree=20in=20the=20fetchPath=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/fetchPath.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/fetchPath.sh b/tests/fetchPath.sh index 8f17638e9..29be38ce2 100644 --- a/tests/fetchPath.sh +++ b/tests/fetchPath.sh @@ -1,6 +1,6 @@ source common.sh -touch foo -t 202211111111 +touch $TEST_ROOT/foo -t 202211111111 # We only check whether 2022-11-1* **:**:** is the last modified date since # `lastModified` is transformed into UTC in `builtins.fetchTarball`. -[[ "$(nix eval --impure --raw --expr "(builtins.fetchTree \"path://$PWD/foo\").lastModifiedDate")" =~ 2022111.* ]] +[[ "$(nix eval --impure --raw --expr "(builtins.fetchTree \"path://$TEST_ROOT/foo\").lastModifiedDate")" =~ 2022111.* ]] From 87f867ef62e7c391955fb1ca3f78bfd532e6666b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <theophane@hufschmitt.net> Date: Wed, 30 Mar 2022 11:43:08 +0200 Subject: [PATCH 064/171] Gitignore the generated systemd nix-daemon conf file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4b290425a..58e7377fb 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ perl/Makefile.config /misc/systemd/nix-daemon.service /misc/systemd/nix-daemon.socket +/misc/systemd/nix-daemon.conf /misc/upstart/nix-daemon.conf /src/resolve-system-dependencies/resolve-system-dependencies From fa83b865a2cfd21d7e63eedb206c1e07c8178965 Mon Sep 17 00:00:00 2001 From: Daniel Pauls <daniel1708.de+git@gmail.com> Date: Wed, 30 Mar 2022 15:41:25 +0200 Subject: [PATCH 065/171] libexpr: Throw the correct error in toJSON BaseError::addTrace(...) returns a BaseError, but we want to throw a TypeError instead. Fixes #6336. --- src/libexpr/value-to-json.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 517da4c01..7b35abca2 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -84,7 +84,8 @@ void printValueAsJSON(EvalState & state, bool strict, .msg = hintfmt("cannot convert %1% to JSON", showType(v)), .errPos = v.determinePos(pos) }); - throw e.addTrace(pos, hintfmt("message for the trace")); + e.addTrace(pos, hintfmt("message for the trace")); + throw e; } } From 629edd43ba7550be835660fe5df3b65cc4a515c7 Mon Sep 17 00:00:00 2001 From: Daniel Pauls <daniel1708.de+git@gmail.com> Date: Wed, 30 Mar 2022 17:30:47 +0200 Subject: [PATCH 066/171] libutil: Change return value of addTrace to void The return value of BaseError::addTrace(...) is never used and error-prone as subclasses calling it will return a BaseError instead of the subclass. This commit changes its return value to be void. --- src/libutil/error.cc | 3 +-- src/libutil/error.hh | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libutil/error.cc b/src/libutil/error.cc index b2dfb35b2..02bc5caa5 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -9,10 +9,9 @@ namespace nix { const std::string nativeSystem = SYSTEM; -BaseError & BaseError::addTrace(std::optional<ErrPos> e, hintformat hint) +void BaseError::addTrace(std::optional<ErrPos> e, hintformat hint) { err.traces.push_front(Trace { .pos = e, .hint = hint }); - return *this; } // c++ std::exception descendants must have a 'const char* what()' function. diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 93b789f0b..6a757f9ad 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -175,12 +175,12 @@ public: const ErrorInfo & info() const { calcWhat(); return err; } template<typename... Args> - BaseError & addTrace(std::optional<ErrPos> e, const std::string & fs, const Args & ... args) + void addTrace(std::optional<ErrPos> e, const std::string & fs, const Args & ... args) { - return addTrace(e, hintfmt(fs, args...)); + addTrace(e, hintfmt(fs, args...)); } - BaseError & addTrace(std::optional<ErrPos> e, hintformat hint); + void addTrace(std::optional<ErrPos> e, hintformat hint); bool hasTrace() const { return !err.traces.empty(); } }; From d77823b502ebc358d33a7719375677fed92291c8 Mon Sep 17 00:00:00 2001 From: Tom Bereknyei <tomberek@gmail.com> Date: Wed, 30 Mar 2022 16:10:42 -0400 Subject: [PATCH 067/171] bundler: update default bundler to support new bundler API --- src/nix/bundle.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc index 7ed558dee..81fb8464a 100644 --- a/src/nix/bundle.cc +++ b/src/nix/bundle.cc @@ -9,7 +9,7 @@ using namespace nix; struct CmdBundle : InstallableCommand { - std::string bundler = "github:matthewbauer/nix-bundle"; + std::string bundler = "github:NixOS/bundlers"; std::optional<Path> outLink; CmdBundle() From 50f9f335c92a88e8c9bd3421e6befbcd06cac4ec Mon Sep 17 00:00:00 2001 From: Tom Bereknyei <tomberek@gmail.com> Date: Wed, 30 Mar 2022 16:35:26 -0400 Subject: [PATCH 068/171] profile!: consistent use of url/uri. create new version --- src/nix/profile.cc | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/nix/profile.cc b/src/nix/profile.cc index f35947ddb..b151e48d6 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -97,19 +97,30 @@ struct ProfileManifest auto json = nlohmann::json::parse(readFile(manifestPath)); auto version = json.value("version", 0); - if (version != 1) - throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version); + std::string sUrl; + std::string sOriginalUrl; + switch(version){ + case 1: + sUrl = "uri"; + sOriginalUrl = "originalUri"; + break; + case 2: + sUrl = "url"; + sOriginalUrl = "originalUrl"; + break; + default: + throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version); + } for (auto & e : json["elements"]) { ProfileElement element; for (auto & p : e["storePaths"]) element.storePaths.insert(state.store->parseStorePath((std::string) p)); element.active = e["active"]; - if (e.value("uri", "") != "") { - auto originalUrl = e.value("originalUrl", e["originalUri"]); + if (e.value(sUrl,"") != "") { element.source = ProfileElementSource{ - parseFlakeRef(originalUrl), - parseFlakeRef(e["uri"]), + parseFlakeRef(e[sOriginalUrl]), + parseFlakeRef(e[sUrl]), e["attrPath"] }; } @@ -144,13 +155,13 @@ struct ProfileManifest obj["active"] = element.active; if (element.source) { obj["originalUrl"] = element.source->originalRef.to_string(); - obj["uri"] = element.source->resolvedRef.to_string(); + obj["url"] = element.source->resolvedRef.to_string(); obj["attrPath"] = element.source->attrPath; } array.push_back(obj); } nlohmann::json json; - json["version"] = 1; + json["version"] = 2; json["elements"] = array; return json.dump(); } From 28309352d991f50c9d8b54a5a0ee99995a1a5297 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 31 Mar 2022 10:39:53 +0200 Subject: [PATCH 069/171] replaceEnv(): Pass newEnv by reference --- src/libutil/util.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 70eaf4f9c..59e3aad6d 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -71,13 +71,11 @@ void clearEnv() unsetenv(name.first.c_str()); } -void replaceEnv(std::map<std::string, std::string> newEnv) +void replaceEnv(const std::map<std::string, std::string> & newEnv) { clearEnv(); - for (auto newEnvVar : newEnv) - { + for (auto & newEnvVar : newEnv) setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1); - } } From 5cd72598feaff3c4bbcc7304a4844768f64a1ee0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Wed, 30 Mar 2022 16:31:01 +0200 Subject: [PATCH 070/171] Add support for impure derivations Impure derivations are derivations that can produce a different result every time they're built. Example: stdenv.mkDerivation { name = "impure"; __impure = true; # marks this derivation as impure outputHashAlgo = "sha256"; outputHashMode = "recursive"; buildCommand = "date > $out"; }; Some important characteristics: * This requires the 'impure-derivations' experimental feature. * Impure derivations are not "cached". Thus, running "nix-build" on the example above multiple times will cause a rebuild every time. * They are implemented similar to CA derivations, i.e. the output is moved to a content-addressed path in the store. The difference is that we don't register a realisation in the Nix database. * Pure derivations are not allowed to depend on impure derivations. In the future fixed-output derivations will be allowed to depend on impure derivations, thus forming an "impurity barrier" in the dependency graph. * When sandboxing is enabled, impure derivations can access the network in the same way as fixed-output derivations. In relaxed sandboxing mode, they can access the local filesystem. --- src/libexpr/eval.cc | 1 + src/libexpr/eval.hh | 2 +- src/libexpr/primops.cc | 32 ++- src/libstore/build/derivation-goal.cc | 182 +++++++++------ src/libstore/build/derivation-goal.hh | 7 + src/libstore/build/local-derivation-goal.cc | 21 +- src/libstore/derivations.cc | 235 +++++++++++++++----- src/libstore/derivations.hh | 52 ++++- src/libstore/local-store.cc | 3 + src/libstore/path.cc | 9 + src/libstore/path.hh | 2 + src/libutil/experimental-features.cc | 1 + src/libutil/experimental-features.hh | 1 + src/nix/show-derivation.cc | 4 + tests/impure-derivations.nix | 46 ++++ tests/impure-derivations.sh | 39 ++++ tests/local.mk | 3 +- 17 files changed, 486 insertions(+), 154 deletions(-) create mode 100644 tests/impure-derivations.nix create mode 100644 tests/impure-derivations.sh diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 437c7fc53..b87e06ef5 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -436,6 +436,7 @@ EvalState::EvalState( , sBuilder(symbols.create("builder")) , sArgs(symbols.create("args")) , sContentAddressed(symbols.create("__contentAddressed")) + , sImpure(symbols.create("__impure")) , sOutputHash(symbols.create("outputHash")) , sOutputHashAlgo(symbols.create("outputHashAlgo")) , sOutputHashMode(symbols.create("outputHashMode")) diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index e7915dd99..7ed376e8d 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -78,7 +78,7 @@ public: sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, sFile, sLine, sColumn, sFunctor, sToString, sRight, sWrong, sStructuredAttrs, sBuilder, sArgs, - sContentAddressed, + sContentAddressed, sImpure, sOutputHash, sOutputHashAlgo, sOutputHashMode, sRecurseForDerivations, sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath, diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 9f549e52f..eaf04320e 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -989,6 +989,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * PathSet context; bool contentAddressed = false; + bool isImpure = false; std::optional<std::string> outputHash; std::string outputHashAlgo; auto ingestionMethod = FileIngestionMethod::Flat; @@ -1051,6 +1052,12 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * settings.requireExperimentalFeature(Xp::CaDerivations); } + else if (i->name == state.sImpure) { + isImpure = state.forceBool(*i->value, pos); + if (isImpure) + settings.requireExperimentalFeature(Xp::ImpureDerivations); + } + /* The `args' attribute is special: it supplies the command-line arguments to the builder. */ else if (i->name == state.sArgs) { @@ -1197,15 +1204,28 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * }); } - else if (contentAddressed) { + else if (contentAddressed || isImpure) { + if (contentAddressed && isImpure) + throw EvalError({ + .msg = hintfmt("derivation cannot be both content-addressed and impure"), + .errPos = posDrvName + }); + HashType ht = parseHashType(outputHashAlgo); for (auto & i : outputs) { drv.env[i] = hashPlaceholder(i); - drv.outputs.insert_or_assign(i, - DerivationOutput::CAFloating { - .method = ingestionMethod, - .hashType = ht, - }); + if (isImpure) + drv.outputs.insert_or_assign(i, + DerivationOutput::Impure { + .method = ingestionMethod, + .hashType = ht, + }); + else + drv.outputs.insert_or_assign(i, + DerivationOutput::CAFloating { + .method = ingestionMethod, + .hashType = ht, + }); } } diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 3d1c4fbc1..2f3490829 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -204,9 +204,34 @@ void DerivationGoal::haveDerivation() { trace("have derivation"); + parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv); + if (!drv->type().hasKnownOutputPaths()) settings.requireExperimentalFeature(Xp::CaDerivations); + if (!drv->type().isPure()) { + settings.requireExperimentalFeature(Xp::ImpureDerivations); + + for (auto & [outputName, output] : drv->outputs) { + auto randomPath = StorePath::random(outputPathName(drv->name, outputName)); + assert(!worker.store.isValidPath(randomPath)); + initialOutputs.insert({ + outputName, + InitialOutput { + .wanted = true, + .outputHash = impureOutputHash, + .known = InitialOutputStatus { + .path = randomPath, + .status = PathStatus::Absent + } + } + }); + } + + gaveUpOnSubstitution(); + return; + } + for (auto & i : drv->outputsAndOptPaths(worker.store)) if (i.second.second) worker.store.addTempRoot(*i.second.second); @@ -230,9 +255,6 @@ void DerivationGoal::haveDerivation() return; } - parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv); - - /* We are first going to try to create the invalid output paths through substitutes. If that doesn't work, we'll build them. */ @@ -266,6 +288,8 @@ void DerivationGoal::outputsSubstitutionTried() { trace("all outputs substituted (maybe)"); + assert(drv->type().isPure()); + if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) { done(BuildResult::TransientFailure, {}, Error("some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ", @@ -315,9 +339,21 @@ void DerivationGoal::outputsSubstitutionTried() void DerivationGoal::gaveUpOnSubstitution() { /* The inputs must be built before we can build this goal. */ + inputDrvOutputs.clear(); if (useDerivation) - for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) + for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) { + /* Ensure that pure derivations don't depend on impure + derivations. */ + if (drv->type().isPure()) { + auto inputDrv = worker.evalStore.readDerivation(i.first); + if (!inputDrv.type().isPure()) + throw Error("pure derivation '%s' depends on impure derivation '%s'", + worker.store.printStorePath(drvPath), + worker.store.printStorePath(i.first)); + } + addWaitee(worker.makeDerivationGoal(i.first, i.second, buildMode == bmRepair ? bmRepair : bmNormal)); + } /* Copy the input sources from the eval store to the build store. */ @@ -345,6 +381,8 @@ void DerivationGoal::gaveUpOnSubstitution() void DerivationGoal::repairClosure() { + assert(drv->type().isPure()); + /* If we're repairing, we now know that our own outputs are valid. Now check whether the other paths in the outputs closure are good. If not, then start derivation goals for the derivations @@ -452,22 +490,24 @@ void DerivationGoal::inputsRealised() drvs. */ : true); }, + [&](const DerivationType::Impure &) { + return true; + } }, drvType.raw()); - if (resolveDrv) - { + if (resolveDrv && !fullDrv.inputDrvs.empty()) { settings.requireExperimentalFeature(Xp::CaDerivations); /* We are be able to resolve this derivation based on the - now-known results of dependencies. If so, we become a stub goal - aliasing that resolved derivation goal */ - std::optional attempt = fullDrv.tryResolve(worker.store); + now-known results of dependencies. If so, we become a + stub goal aliasing that resolved derivation goal. */ + std::optional attempt = fullDrv.tryResolve(worker.store, inputDrvOutputs); assert(attempt); Derivation drvResolved { *std::move(attempt) }; auto pathResolved = writeDerivation(worker.store, drvResolved); - auto msg = fmt("Resolved derivation: '%s' -> '%s'", + auto msg = fmt("resolved derivation: '%s' -> '%s'", worker.store.printStorePath(drvPath), worker.store.printStorePath(pathResolved)); act = std::make_unique<Activity>(*logger, lvlInfo, actBuildWaiting, msg, @@ -488,21 +528,13 @@ void DerivationGoal::inputsRealised() /* Add the relevant output closures of the input derivation `i' as input paths. Only add the closures of output paths that are specified as inputs. */ - assert(worker.evalStore.isValidPath(drvPath)); - auto outputs = worker.evalStore.queryPartialDerivationOutputMap(depDrvPath); - for (auto & j : wantedDepOutputs) { - if (outputs.count(j) > 0) { - auto optRealizedInput = outputs.at(j); - if (!optRealizedInput) - throw Error( - "derivation '%s' requires output '%s' from input derivation '%s', which is supposedly realized already, yet we still don't know what path corresponds to that output", - worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath)); - worker.store.computeFSClosure(*optRealizedInput, inputPaths); - } else + for (auto & j : wantedDepOutputs) + if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) + worker.store.computeFSClosure(*outPath, inputPaths); + else throw Error( "derivation '%s' requires non-existent output '%s' from input derivation '%s'", worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath)); - } } } @@ -923,7 +955,7 @@ void DerivationGoal::buildDone() st = dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic : statusOk(status) ? BuildResult::OutputRejected : - derivationType.isImpure() || diskFull ? BuildResult::TransientFailure : + derivationType.needsNetworkAccess() || diskFull ? BuildResult::TransientFailure : BuildResult::PermanentFailure; } @@ -934,60 +966,52 @@ void DerivationGoal::buildDone() void DerivationGoal::resolvedFinished() { + trace("resolved derivation finished"); + assert(resolvedDrvGoal); auto resolvedDrv = *resolvedDrvGoal->drv; - - auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv); - - StorePathSet outputPaths; - - // `wantedOutputs` might be empty, which means “all the outputs” - auto realWantedOutputs = wantedOutputs; - if (realWantedOutputs.empty()) - realWantedOutputs = resolvedDrv.outputNames(); + auto & resolvedResult = resolvedDrvGoal->buildResult; DrvOutputs builtOutputs; - for (auto & wantedOutput : realWantedOutputs) { - assert(initialOutputs.count(wantedOutput) != 0); - assert(resolvedHashes.count(wantedOutput) != 0); - auto realisation = worker.store.queryRealisation( - DrvOutput{resolvedHashes.at(wantedOutput), wantedOutput} - ); - // We've just built it, but maybe the build failed, in which case the - // realisation won't be there - if (realisation) { - auto newRealisation = *realisation; - newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput}; - newRealisation.signatures.clear(); - newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath); - signRealisation(newRealisation); - worker.store.registerDrvOutput(newRealisation); - outputPaths.insert(realisation->outPath); - builtOutputs.emplace(realisation->id, *realisation); - } else { - // If we don't have a realisation, then it must mean that something - // failed when building the resolved drv - assert(!buildResult.success()); + if (resolvedResult.success()) { + auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv); + + StorePathSet outputPaths; + + // `wantedOutputs` might be empty, which means “all the outputs” + auto realWantedOutputs = wantedOutputs; + if (realWantedOutputs.empty()) + 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 }); + if (drv->type().isPure()) { + auto newRealisation = realisation; + newRealisation.id = DrvOutput { initialOutputs.at(wantedOutput).outputHash, wantedOutput }; + newRealisation.signatures.clear(); + newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath); + signRealisation(newRealisation); + worker.store.registerDrvOutput(newRealisation); + } + outputPaths.insert(realisation.outPath); + builtOutputs.emplace(realisation.id, realisation); } + + runPostBuildHook( + worker.store, + *logger, + drvPath, + outputPaths + ); } - runPostBuildHook( - worker.store, - *logger, - drvPath, - outputPaths - ); - - auto status = [&]() { - auto & resolvedResult = resolvedDrvGoal->buildResult; - switch (resolvedResult.status) { - case BuildResult::AlreadyValid: - return BuildResult::ResolvesToAlreadyValid; - default: - return resolvedResult.status; - } - }(); + auto status = resolvedResult.status; + if (status == BuildResult::AlreadyValid) + status = BuildResult::ResolvesToAlreadyValid; done(status, std::move(builtOutputs)); } @@ -1236,6 +1260,7 @@ void DerivationGoal::flushLine() std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap() { + assert(drv->type().isPure()); if (!useDerivation || drv->type().hasKnownOutputPaths()) { std::map<std::string, std::optional<StorePath>> res; for (auto & [name, output] : drv->outputs) @@ -1248,6 +1273,7 @@ std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDeri OutputPathMap DerivationGoal::queryDerivationOutputMap() { + assert(drv->type().isPure()); if (!useDerivation || drv->type().hasKnownOutputPaths()) { OutputPathMap res; for (auto & [name, output] : drv->outputsAndOptPaths(worker.store)) @@ -1261,6 +1287,8 @@ OutputPathMap DerivationGoal::queryDerivationOutputMap() std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity() { + if (!drv->type().isPure()) return { false, {} }; + bool checkHash = buildMode == bmRepair; auto wantedOutputsLeft = wantedOutputs; DrvOutputs validOutputs; @@ -1304,6 +1332,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity() if (info.wanted && info.known && info.known->isValid()) validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path }); } + // If we requested all the outputs via the empty set, we are always fine. // If we requested specific elements, the loop above removes all the valid // ones, so any that are left must be invalid. @@ -1343,7 +1372,6 @@ void DerivationGoal::done( if (ex) // FIXME: strip: "error: " buildResult.errorMsg = ex->what(); - amDone(buildResult.success() ? ecSuccess : ecFailed, ex); if (buildResult.status == BuildResult::TimedOut) worker.timedOut = true; if (buildResult.status == BuildResult::PermanentFailure) @@ -1370,7 +1398,21 @@ void DerivationGoal::done( fs.open(traceBuiltOutputsFile, std::fstream::out); fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl; } + + amDone(buildResult.success() ? ecSuccess : ecFailed, ex); } +void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result) +{ + Goal::waiteeDone(waitee, result); + + if (waitee->buildResult.success()) + if (auto bfd = std::get_if<DerivedPath::Built>(&waitee->buildResult.path)) + for (auto & [output, realisation] : waitee->buildResult.builtOutputs) + inputDrvOutputs.insert_or_assign( + { bfd->drvPath, output.outputName }, + realisation.outPath); +} + } diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh index f556b6f25..2d8bfd592 100644 --- a/src/libstore/build/derivation-goal.hh +++ b/src/libstore/build/derivation-goal.hh @@ -57,6 +57,11 @@ struct DerivationGoal : public Goal them. */ StringSet wantedOutputs; + /* Mapping from input derivations + output names to actual store + paths. This is filled in by waiteeDone() as each dependency + finishes, before inputsRealised() is reached, */ + std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs; + /* Whether additional wanted outputs have been added. */ bool needRestart = false; @@ -224,6 +229,8 @@ struct DerivationGoal : public Goal DrvOutputs builtOutputs = {}, std::optional<Error> ex = {}); + void waiteeDone(GoalPtr waitee, ExitCode result) override; + StorePathSet exportReferences(const StorePathSet & storePaths); }; diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index b176f318b..a6c07314f 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -395,7 +395,7 @@ void LocalDerivationGoal::startBuilder() else if (settings.sandboxMode == smDisabled) useChroot = false; else if (settings.sandboxMode == smRelaxed) - useChroot = !(derivationType.isImpure()) && !noChroot; + useChroot = !derivationType.needsNetworkAccess() && !noChroot; } auto & localStore = getLocalStore(); @@ -608,7 +608,7 @@ void LocalDerivationGoal::startBuilder() "nogroup:x:65534:\n", sandboxGid())); /* Create /etc/hosts with localhost entry. */ - if (!(derivationType.isImpure())) + if (!derivationType.needsNetworkAccess()) writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n"); /* Make the closure of the inputs available in the chroot, @@ -796,7 +796,7 @@ void LocalDerivationGoal::startBuilder() us. */ - if (!(derivationType.isImpure())) + if (!derivationType.needsNetworkAccess()) privateNetwork = true; userNamespaceSync.create(); @@ -1060,7 +1060,7 @@ void LocalDerivationGoal::initEnv() to the builder is generally impure, but the output of fixed-output derivations is by definition pure (since we already know the cryptographic hash of the output). */ - if (derivationType.isImpure()) { + if (derivationType.needsNetworkAccess()) { for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings())) env[i] = getEnv(i).value_or(""); } @@ -1674,7 +1674,7 @@ void LocalDerivationGoal::runChild() /* Fixed-output derivations typically need to access the network, so give them access to /etc/resolv.conf and so on. */ - if (derivationType.isImpure()) { + if (derivationType.needsNetworkAccess()) { // Only use nss functions to resolve hosts and // services. Don’t use it for anything else that may // be configured for this system. This limits the @@ -2399,6 +2399,13 @@ DrvOutputs LocalDerivationGoal::registerOutputs() assert(false); }, + [&](const DerivationOutput::Impure & doi) { + return newInfoFromCA(DerivationOutput::CAFloating { + .method = doi.method, + .hashType = doi.hashType, + }); + }, + }, output.raw()); /* FIXME: set proper permissions in restorePath() so @@ -2609,7 +2616,9 @@ DrvOutputs LocalDerivationGoal::registerOutputs() }, .outPath = newInfo.path }; - if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { + if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) + && drv->type().isPure()) + { signRealisation(thisRealisation); worker.store.registerDrvOutput(thisRealisation); } diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 85d75523f..75e0178bb 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -25,26 +25,42 @@ std::optional<StorePath> DerivationOutput::path(const Store & store, std::string [](const DerivationOutput::Deferred &) -> std::optional<StorePath> { return std::nullopt; }, + [](const DerivationOutput::Impure &) -> std::optional<StorePath> { + return std::nullopt; + }, }, raw()); } -StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const { +StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const +{ return store.makeFixedOutputPath( hash.method, hash.hash, outputPathName(drvName, outputName)); } -bool DerivationType::isCA() const { +bool DerivationType::isCA() const +{ /* Normally we do the full `std::visit` to make sure we have exhaustively handled all variants, but so long as there is a variant called `ContentAddressed`, it must be the only one for which `isCA` is true for this to make sense!. */ - return std::holds_alternative<ContentAddressed>(raw()); + return std::visit(overloaded { + [](const InputAddressed & ia) { + return false; + }, + [](const ContentAddressed & ca) { + return true; + }, + [](const Impure &) { + return true; + }, + }, raw()); } -bool DerivationType::isFixed() const { +bool DerivationType::isFixed() const +{ return std::visit(overloaded { [](const InputAddressed & ia) { return false; @@ -52,10 +68,14 @@ bool DerivationType::isFixed() const { [](const ContentAddressed & ca) { return ca.fixed; }, + [](const Impure &) { + return false; + }, }, raw()); } -bool DerivationType::hasKnownOutputPaths() const { +bool DerivationType::hasKnownOutputPaths() const +{ return std::visit(overloaded { [](const InputAddressed & ia) { return !ia.deferred; @@ -63,11 +83,15 @@ bool DerivationType::hasKnownOutputPaths() const { [](const ContentAddressed & ca) { return ca.fixed; }, + [](const Impure &) { + return false; + }, }, raw()); } -bool DerivationType::isImpure() const { +bool DerivationType::needsNetworkAccess() const +{ return std::visit(overloaded { [](const InputAddressed & ia) { return false; @@ -75,6 +99,25 @@ bool DerivationType::isImpure() const { [](const ContentAddressed & ca) { return !ca.pure; }, + [](const Impure &) { + return true; + }, + }, raw()); +} + + +bool DerivationType::isPure() const +{ + return std::visit(overloaded { + [](const InputAddressed & ia) { + return true; + }, + [](const ContentAddressed & ca) { + return true; + }, + [](const Impure &) { + return false; + }, }, raw()); } @@ -176,7 +219,16 @@ static DerivationOutput parseDerivationOutput(const Store & store, hashAlgo = hashAlgo.substr(2); } const auto hashType = parseHashType(hashAlgo); - if (hash != "") { + if (hash == "impure") { + settings.requireExperimentalFeature(Xp::ImpureDerivations); + assert(pathS == ""); + return DerivationOutput { + .output = DerivationOutputImpure { + .method = std::move(method), + .hashType = std::move(hashType), + }, + }; + } else if (hash != "") { validatePath(pathS); return DerivationOutput::CAFixed { .hash = FixedOutputHash { @@ -345,6 +397,12 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs, s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, ""); + }, + [&](const DerivationOutputImpure & doi) { + // FIXME + s += ','; printUnquotedString(s, ""); + s += ','; printUnquotedString(s, makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType)); + s += ','; printUnquotedString(s, "impure"); } }, i.second.raw()); s += ')'; @@ -410,8 +468,14 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName DerivationType BasicDerivation::type() const { - std::set<std::string_view> inputAddressedOutputs, fixedCAOutputs, floatingCAOutputs, deferredIAOutputs; + std::set<std::string_view> + inputAddressedOutputs, + fixedCAOutputs, + floatingCAOutputs, + deferredIAOutputs, + impureOutputs; std::optional<HashType> floatingHashType; + for (auto & i : outputs) { std::visit(overloaded { [&](const DerivationOutput::InputAddressed &) { @@ -426,43 +490,78 @@ DerivationType BasicDerivation::type() const floatingHashType = dof.hashType; } else { if (*floatingHashType != dof.hashType) - throw Error("All floating outputs must use the same hash type"); + throw Error("all floating outputs must use the same hash type"); } }, [&](const DerivationOutput::Deferred &) { - deferredIAOutputs.insert(i.first); + deferredIAOutputs.insert(i.first); + }, + [&](const DerivationOutput::Impure &) { + impureOutputs.insert(i.first); }, }, i.second.raw()); } - if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { - throw Error("Must have at least one output"); - } else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { + if (inputAddressedOutputs.empty() + && fixedCAOutputs.empty() + && floatingCAOutputs.empty() + && deferredIAOutputs.empty() + && impureOutputs.empty()) + throw Error("must have at least one output"); + + if (!inputAddressedOutputs.empty() + && fixedCAOutputs.empty() + && floatingCAOutputs.empty() + && deferredIAOutputs.empty() + && impureOutputs.empty()) return DerivationType::InputAddressed { .deferred = false, }; - } else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { + + if (inputAddressedOutputs.empty() + && !fixedCAOutputs.empty() + && floatingCAOutputs.empty() + && deferredIAOutputs.empty() + && impureOutputs.empty()) + { if (fixedCAOutputs.size() > 1) // FIXME: Experimental feature? - throw Error("Only one fixed output is allowed for now"); + throw Error("only one fixed output is allowed for now"); if (*fixedCAOutputs.begin() != "out") - throw Error("Single fixed output must be named \"out\""); + throw Error("single fixed output must be named \"out\""); return DerivationType::ContentAddressed { .pure = false, .fixed = true, }; - } else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) { + } + + if (inputAddressedOutputs.empty() + && fixedCAOutputs.empty() + && !floatingCAOutputs.empty() + && deferredIAOutputs.empty() + && impureOutputs.empty()) return DerivationType::ContentAddressed { .pure = true, .fixed = false, }; - } else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) { + + if (inputAddressedOutputs.empty() + && fixedCAOutputs.empty() + && floatingCAOutputs.empty() + && !deferredIAOutputs.empty() + && impureOutputs.empty()) return DerivationType::InputAddressed { .deferred = true, }; - } else { - throw Error("Can't mix derivation output types"); - } + + if (inputAddressedOutputs.empty() + && fixedCAOutputs.empty() + && floatingCAOutputs.empty() + && deferredIAOutputs.empty() + && !impureOutputs.empty()) + return DerivationType::Impure { }; + + throw Error("can't mix derivation output types"); } @@ -524,12 +623,22 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut + store.printStorePath(dof.path(store, drv.name, i.first))); outputHashes.insert_or_assign(i.first, std::move(hash)); } - return DrvHash{ + return DrvHash { .hashes = outputHashes, .kind = DrvHash::Kind::Regular, }; } + if (!type.isPure()) { + std::map<std::string, Hash> outputHashes; + for (const auto & [outputName, _] : drv.outputs) + outputHashes.insert_or_assign(outputName, impureOutputHash); + return DrvHash { + .hashes = outputHashes, + .kind = DrvHash::Kind::Deferred, + }; + } + auto kind = std::visit(overloaded { [](const DerivationType::InputAddressed & ia) { /* This might be a "pesimistically" deferred output, so we don't @@ -541,6 +650,9 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut ? DrvHash::Kind::Regular : DrvHash::Kind::Deferred; }, + [](const DerivationType::Impure &) -> DrvHash::Kind { + assert(false); + } }, drv.type().raw()); std::map<std::string, StringSet> inputs2; @@ -599,7 +711,8 @@ StringSet BasicDerivation::outputNames() const return names; } -DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & store) const { +DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & store) const +{ DerivationOutputsAndOptPaths outsAndOptPaths; for (auto output : outputs) outsAndOptPaths.insert(std::make_pair( @@ -610,7 +723,8 @@ DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & s return outsAndOptPaths; } -std::string_view BasicDerivation::nameFromPath(const StorePath & drvPath) { +std::string_view BasicDerivation::nameFromPath(const StorePath & drvPath) +{ auto nameWithSuffix = drvPath.name(); constexpr std::string_view extension = ".drv"; assert(hasSuffix(nameWithSuffix, extension)); @@ -672,6 +786,11 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr << "" << ""; }, + [&](const DerivationOutput::Impure & doi) { + out << "" + << (makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType)) + << "impure"; + }, }, i.second.raw()); } worker_proto::write(store, out, drv.inputSrcs); @@ -697,21 +816,19 @@ std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath } -static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites) { - - debug("Rewriting the derivation"); - - for (auto &rewrite: rewrites) { +static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites) +{ + for (auto & rewrite : rewrites) { debug("rewriting %s as %s", rewrite.first, rewrite.second); } drv.builder = rewriteStrings(drv.builder, rewrites); - for (auto & arg: drv.args) { + for (auto & arg : drv.args) { arg = rewriteStrings(arg, rewrites); } StringPairs newEnv; - for (auto & envVar: drv.env) { + for (auto & envVar : drv.env) { auto envName = rewriteStrings(envVar.first, rewrites); auto envValue = rewriteStrings(envVar.second, rewrites); newEnv.emplace(envName, envValue); @@ -732,48 +849,48 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String } -static bool tryResolveInput( - Store & store, StorePathSet & inputSrcs, StringMap & inputRewrites, - const StorePath & inputDrv, const StringSet & inputOutputs) +std::optional<BasicDerivation> Derivation::tryResolve(Store & store) const { - auto inputDrvOutputs = store.queryPartialDerivationOutputMap(inputDrv); + std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs; - auto getOutput = [&](const std::string & outputName) { - auto & actualPathOpt = inputDrvOutputs.at(outputName); - if (!actualPathOpt) - warn("output %s of input %s missing, aborting the resolving", - outputName, - store.printStorePath(inputDrv) - ); - return actualPathOpt; - }; + for (auto & input : inputDrvs) + for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(input.first)) + if (outputPath) + inputDrvOutputs.insert_or_assign({input.first, outputName}, *outputPath); - for (auto & outputName : inputOutputs) { - auto actualPathOpt = getOutput(outputName); - if (!actualPathOpt) return false; - auto actualPath = *actualPathOpt; - inputRewrites.emplace( - downstreamPlaceholder(store, inputDrv, outputName), - store.printStorePath(actualPath)); - inputSrcs.insert(std::move(actualPath)); - } - - return true; + return tryResolve(store, inputDrvOutputs); } -std::optional<BasicDerivation> Derivation::tryResolve(Store & store) { +std::optional<BasicDerivation> Derivation::tryResolve( + Store & store, + const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const +{ BasicDerivation resolved { *this }; // Input paths that we'll want to rewrite in the derivation StringMap inputRewrites; - for (auto & [inputDrv, inputOutputs] : inputDrvs) - if (!tryResolveInput(store, resolved.inputSrcs, inputRewrites, inputDrv, inputOutputs)) - return std::nullopt; + for (auto & [inputDrv, inputOutputs] : inputDrvs) { + for (auto & outputName : inputOutputs) { + if (auto actualPath = get(inputDrvOutputs, { inputDrv, outputName })) { + inputRewrites.emplace( + downstreamPlaceholder(store, inputDrv, outputName), + store.printStorePath(*actualPath)); + resolved.inputSrcs.insert(*actualPath); + } else { + warn("output '%s' of input '%s' missing, aborting the resolving", + outputName, + store.printStorePath(inputDrv)); + return {}; + } + } + } rewriteDerivation(store, resolved, inputRewrites); return resolved; } +const Hash impureOutputHash = hashString(htSHA256, "impure"); + } diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index 63ea5ef76..b62e40786 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -41,15 +41,26 @@ struct DerivationOutputCAFloating }; /* Input-addressed output which depends on a (CA) derivation whose hash isn't - * known atm + * known yet. */ struct DerivationOutputDeferred {}; +/* Impure output which is moved to a content-addressed location (like + CAFloating) but isn't registered as a realization. + */ +struct DerivationOutputImpure +{ + /* information used for expected hash computation */ + FileIngestionMethod method; + HashType hashType; +}; + typedef std::variant< DerivationOutputInputAddressed, DerivationOutputCAFixed, DerivationOutputCAFloating, - DerivationOutputDeferred + DerivationOutputDeferred, + DerivationOutputImpure > _DerivationOutputRaw; struct DerivationOutput : _DerivationOutputRaw @@ -61,6 +72,7 @@ struct DerivationOutput : _DerivationOutputRaw using CAFixed = DerivationOutputCAFixed; using CAFloating = DerivationOutputCAFloating; using Deferred = DerivationOutputDeferred; + using Impure = DerivationOutputImpure; /* Note, when you use this function you should make sure that you're passing the right derivation name. When in doubt, you should use the safer @@ -94,9 +106,13 @@ struct DerivationType_ContentAddressed { bool fixed; }; +struct DerivationType_Impure { +}; + typedef std::variant< DerivationType_InputAddressed, - DerivationType_ContentAddressed + DerivationType_ContentAddressed, + DerivationType_Impure > _DerivationTypeRaw; struct DerivationType : _DerivationTypeRaw { @@ -104,7 +120,7 @@ struct DerivationType : _DerivationTypeRaw { using Raw::Raw; using InputAddressed = DerivationType_InputAddressed; using ContentAddressed = DerivationType_ContentAddressed; - + using Impure = DerivationType_Impure; /* Do the outputs of the derivation have paths calculated from their content, or from the derivation itself? */ @@ -114,10 +130,13 @@ struct DerivationType : _DerivationTypeRaw { non-CA derivations. */ bool isFixed() const; - /* Is the derivation impure and needs to access non-deterministic resources, or - pure and can be sandboxed? Note that whether or not we actually sandbox the - derivation is controlled separately. Never true for non-CA derivations. */ - bool isImpure() const; + /* Whether the derivation needs to access the network. Note that + whether or not we actually sandbox the derivation is controlled + separately. Never true for non-CA derivations. */ + bool needsNetworkAccess() const; + + /* FIXME */ + bool isPure() const; /* Does the derivation knows its own output paths? Only true when there's no floating-ca derivation involved in the @@ -173,7 +192,14 @@ struct Derivation : BasicDerivation added directly to input sources. 2. Input placeholders are replaced with realized input store paths. */ - std::optional<BasicDerivation> tryResolve(Store & store); + std::optional<BasicDerivation> tryResolve(Store & store) const; + + /* Like the above, but instead of querying the Nix database for + realisations, uses a given mapping from input derivation paths + + output names to actual output store paths. */ + std::optional<BasicDerivation> tryResolve( + Store & store, + const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const; Derivation() = default; Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { } @@ -211,7 +237,7 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName struct DrvHash { std::map<std::string, Hash> hashes; - enum struct Kind: bool { + enum struct Kind : bool { // Statically determined derivations. // This hash will be directly used to compute the output paths Regular, @@ -252,8 +278,10 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut /* Return a map associating each output to a hash that uniquely identifies its derivation (modulo the self-references). + + FIXME: what is the Hash in this map? */ -std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv); +std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation & drv); /* Memoisation of hashDerivationModulo(). */ typedef std::map<StorePath, DrvHash> DrvHashes; @@ -286,4 +314,6 @@ std::string hashPlaceholder(const std::string_view outputName); dependency which is a CA derivation. */ std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath, std::string_view outputName); +extern const Hash impureOutputHash; + } diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 60fe53af1..d77fff963 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -719,6 +719,9 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat [&](const DerivationOutput::Deferred &) { /* Nothing to check */ }, + [&](const DerivationOutput::Impure &) { + /* Nothing to check */ + }, }, i.second.raw()); } } diff --git a/src/libstore/path.cc b/src/libstore/path.cc index e642abcd5..392db225e 100644 --- a/src/libstore/path.cc +++ b/src/libstore/path.cc @@ -1,5 +1,7 @@ #include "store-api.hh" +#include <sodium.h> + namespace nix { static void checkName(std::string_view path, std::string_view name) @@ -41,6 +43,13 @@ bool StorePath::isDerivation() const StorePath StorePath::dummy("ffffffffffffffffffffffffffffffff-x"); +StorePath StorePath::random(std::string_view name) +{ + Hash hash(htSHA1); + randombytes_buf(hash.hash, hash.hashSize); + return StorePath(hash, name); +} + StorePath Store::parseStorePath(std::string_view path) const { auto p = canonPath(std::string(path)); diff --git a/src/libstore/path.hh b/src/libstore/path.hh index e65fee622..77fd0f8dc 100644 --- a/src/libstore/path.hh +++ b/src/libstore/path.hh @@ -58,6 +58,8 @@ public: } static StorePath dummy; + + static StorePath random(std::string_view name); }; typedef std::set<StorePath> StorePathSet; diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index 01f318fa3..e033a4116 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -7,6 +7,7 @@ namespace nix { std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = { { Xp::CaDerivations, "ca-derivations" }, + { Xp::ImpureDerivations, "impure-derivations" }, { Xp::Flakes, "flakes" }, { Xp::NixCommand, "nix-command" }, { Xp::RecursiveNix, "recursive-nix" }, diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index b5140dcfe..3a254b423 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -16,6 +16,7 @@ namespace nix { enum struct ExperimentalFeature { CaDerivations, + ImpureDerivations, Flakes, NixCommand, RecursiveNix, diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc index 0d9655732..fb46b4dbf 100644 --- a/src/nix/show-derivation.cc +++ b/src/nix/show-derivation.cc @@ -77,6 +77,10 @@ struct CmdShowDerivation : InstallablesCommand outputObj.attr("hashAlgo", makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType)); }, [&](const DerivationOutput::Deferred &) {}, + [&](const DerivationOutput::Impure & doi) { + outputObj.attr("hashAlgo", makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType)); + outputObj.attr("impure", true); + }, }, output.raw()); } } diff --git a/tests/impure-derivations.nix b/tests/impure-derivations.nix new file mode 100644 index 000000000..ba7d53146 --- /dev/null +++ b/tests/impure-derivations.nix @@ -0,0 +1,46 @@ +with import ./config.nix; + +rec { + + impure = mkDerivation { + name = "impure"; + outputs = [ "out" "stuff" ]; + buildCommand = + '' + x=$(< $TEST_ROOT/counter) + mkdir $out $stuff + echo $x > $out/n + ln -s $out/n $stuff/bla + printf $((x + 1)) > $TEST_ROOT/counter + ''; + __impure = true; + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + impureEnvVars = [ "TEST_ROOT" ]; + }; + + impureOnImpure = mkDerivation { + name = "impure-on-impure"; + buildCommand = + '' + x=$(< ${impure}/n) + mkdir $out + printf X$x > $out/n + ln -s ${impure.stuff} $out/symlink + ln -s $out $out/self + ''; + __impure = true; + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + }; + + # This is not allowed. + inputAddressed = mkDerivation { + name = "input-addressed"; + buildCommand = + '' + cat ${impure} > $out + ''; + }; + +} diff --git a/tests/impure-derivations.sh b/tests/impure-derivations.sh new file mode 100644 index 000000000..cb1eaf84a --- /dev/null +++ b/tests/impure-derivations.sh @@ -0,0 +1,39 @@ +source common.sh + +requireDaemonNewerThan "2.8pre20220311" + +enableFeatures "ca-derivations ca-references impure-derivations" + +clearStore + +# Basic test of impure derivations: building one a second time should not use the previous result. +printf 0 > $TEST_ROOT/counter + +json=$(nix build -L --no-link --json --file ./impure-derivations.nix impure) +path1=$(echo $json | jq -r .[].outputs.out) +path1_stuff=$(echo $json | jq -r .[].outputs.stuff) +[[ $(< $path1/n) = 0 ]] +[[ $(< $path1_stuff/bla) = 0 ]] + +[[ $(nix path-info --json $path1 | jq .[].ca) =~ fixed:r:sha256: ]] + +path2=$(nix build -L --no-link --json --file ./impure-derivations.nix impure | jq -r .[].outputs.out) +[[ $(< $path2/n) = 1 ]] + +# Test impure derivations that depend on impure derivations. +path3=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnImpure -vvvvv | jq -r .[].outputs.out) +[[ $(< $path3/n) = X2 ]] + +path4=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnImpure -vvvvv | jq -r .[].outputs.out) +[[ $(< $path4/n) = X3 ]] + +# Test that (self-)references work. +[[ $(< $path4/symlink/bla) = 3 ]] +[[ $(< $path4/self/n) = X3 ]] + +# Input-addressed derivations cannot depend on impure derivations directly. +nix build -L --no-link --json --file ./impure-derivations.nix inputAddressed 2>&1 | grep 'depends on impure derivation' + +drvPath=$(nix eval --json --file ./impure-derivations.nix impure.drvPath | jq -r .) +[[ $(nix show-derivation $drvPath | jq ".[\"$drvPath\"].outputs.out.impure") = true ]] +[[ $(nix show-derivation $drvPath | jq ".[\"$drvPath\"].outputs.stuff.impure") = true ]] diff --git a/tests/local.mk b/tests/local.mk index 97971dd76..668b34500 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -97,7 +97,8 @@ nix_tests = \ nix-profile.sh \ suggestions.sh \ store-ping.sh \ - fetchClosure.sh + fetchClosure.sh \ + impure-derivations.sh ifeq ($(HAVE_LIBCPUID), 1) nix_tests += compute-levels.sh From 18935e8b9f152f18705e738d4b8cc6fce97c8b02 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Fri, 11 Mar 2022 13:23:23 +0100 Subject: [PATCH 071/171] Support fixed-output derivations depending on impure derivations --- src/libstore/build/derivation-goal.cc | 9 +++++---- src/libstore/misc.cc | 7 ++++--- tests/impure-derivations.nix | 22 ++++++++++++++++++++++ tests/impure-derivations.sh | 19 +++++++++++++++++-- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 2f3490829..542a6f6ea 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -342,9 +342,9 @@ void DerivationGoal::gaveUpOnSubstitution() inputDrvOutputs.clear(); if (useDerivation) for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) { - /* Ensure that pure derivations don't depend on impure - derivations. */ - if (drv->type().isPure()) { + /* Ensure that pure, non-fixed-output derivations don't + depend on impure derivations. */ + if (drv->type().isPure() && !drv->type().isFixed()) { auto inputDrv = worker.evalStore.readDerivation(i.first); if (!inputDrv.type().isPure()) throw Error("pure derivation '%s' depends on impure derivation '%s'", @@ -993,7 +993,8 @@ void DerivationGoal::resolvedFinished() auto newRealisation = realisation; newRealisation.id = DrvOutput { initialOutputs.at(wantedOutput).outputHash, wantedOutput }; newRealisation.signatures.clear(); - newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath); + if (!drv->type().isFixed()) + newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath); signRealisation(newRealisation); worker.store.registerDrvOutput(newRealisation); } diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc index 1f0bae7fe..2bbd7aa70 100644 --- a/src/libstore/misc.cc +++ b/src/libstore/misc.cc @@ -277,15 +277,15 @@ std::map<DrvOutput, StorePath> drvOutputReferences( { std::set<Realisation> inputRealisations; - for (const auto& [inputDrv, outputNames] : drv.inputDrvs) { + for (const auto & [inputDrv, outputNames] : drv.inputDrvs) { auto outputHashes = staticOutputHashes(store, store.readDerivation(inputDrv)); - for (const auto& outputName : outputNames) { + for (const auto & outputName : outputNames) { auto thisRealisation = store.queryRealisation( DrvOutput{outputHashes.at(outputName), outputName}); if (!thisRealisation) throw Error( - "output '%s' of derivation '%s' isn’t built", outputName, + "output '%s' of derivation '%s' isn't built", outputName, store.printStorePath(inputDrv)); inputRealisations.insert(*thisRealisation); } @@ -295,4 +295,5 @@ std::map<DrvOutput, StorePath> drvOutputReferences( return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references); } + } diff --git a/tests/impure-derivations.nix b/tests/impure-derivations.nix index ba7d53146..2fed56fe7 100644 --- a/tests/impure-derivations.nix +++ b/tests/impure-derivations.nix @@ -7,6 +7,7 @@ rec { outputs = [ "out" "stuff" ]; buildCommand = '' + echo impure x=$(< $TEST_ROOT/counter) mkdir $out $stuff echo $x > $out/n @@ -23,6 +24,7 @@ rec { name = "impure-on-impure"; buildCommand = '' + echo impure-on-impure x=$(< ${impure}/n) mkdir $out printf X$x > $out/n @@ -43,4 +45,24 @@ rec { ''; }; + contentAddressed = mkDerivation { + name = "content-addressed"; + buildCommand = + '' + echo content-addressed + x=$(< ${impureOnImpure}/n) + printf ''${x:0:1} > $out + ''; + outputHashAlgo = "sha256"; + outputHashMode = "recursive"; + outputHash = "sha256-eBYxcgkuWuiqs4cKNgKwkb3vY/HR0vVsJnqe8itJGcQ="; + }; + + inputAddressedAfterCA = mkDerivation { + name = "input-addressed-after-ca"; + buildCommand = + '' + cat ${contentAddressed} > $out + ''; + }; } diff --git a/tests/impure-derivations.sh b/tests/impure-derivations.sh index cb1eaf84a..85e3e09cc 100644 --- a/tests/impure-derivations.sh +++ b/tests/impure-derivations.sh @@ -21,10 +21,10 @@ path2=$(nix build -L --no-link --json --file ./impure-derivations.nix impure | j [[ $(< $path2/n) = 1 ]] # Test impure derivations that depend on impure derivations. -path3=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnImpure -vvvvv | jq -r .[].outputs.out) +path3=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnImpure | jq -r .[].outputs.out) [[ $(< $path3/n) = X2 ]] -path4=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnImpure -vvvvv | jq -r .[].outputs.out) +path4=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnImpure | jq -r .[].outputs.out) [[ $(< $path4/n) = X3 ]] # Test that (self-)references work. @@ -37,3 +37,18 @@ nix build -L --no-link --json --file ./impure-derivations.nix inputAddressed 2>& drvPath=$(nix eval --json --file ./impure-derivations.nix impure.drvPath | jq -r .) [[ $(nix show-derivation $drvPath | jq ".[\"$drvPath\"].outputs.out.impure") = true ]] [[ $(nix show-derivation $drvPath | jq ".[\"$drvPath\"].outputs.stuff.impure") = true ]] + +# Fixed-output derivations *can* depend on impure derivations. +path5=$(nix build -L --no-link --json --file ./impure-derivations.nix contentAddressed | jq -r .[].outputs.out) +[[ $(< $path5) = X ]] +[[ $(< $TEST_ROOT/counter) = 5 ]] + +# And they should not be rebuilt. +path5=$(nix build -L --no-link --json --file ./impure-derivations.nix contentAddressed | jq -r .[].outputs.out) +[[ $(< $path5) = X ]] +[[ $(< $TEST_ROOT/counter) = 5 ]] + +# Input-addressed derivations can depend on fixed-output derivations that depend on impure derivations. +path6=$(nix build -L --no-link --json --file ./impure-derivations.nix inputAddressedAfterCA | jq -r .[].outputs.out) +[[ $(< $path6) = X ]] +[[ $(< $TEST_ROOT/counter) = 5 ]] From b2ae922747adfe187d02c1bfca8231c2d8bceb75 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Fri, 11 Mar 2022 15:56:22 +0100 Subject: [PATCH 072/171] tests/impure-derivations.sh: Restart daemon --- tests/impure-derivations.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/impure-derivations.sh b/tests/impure-derivations.sh index 85e3e09cc..1b33a785c 100644 --- a/tests/impure-derivations.sh +++ b/tests/impure-derivations.sh @@ -3,6 +3,7 @@ source common.sh requireDaemonNewerThan "2.8pre20220311" enableFeatures "ca-derivations ca-references impure-derivations" +restartDaemon clearStore From 162beb25955adcedeed76e97510feb577d4f86db Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Wed, 30 Mar 2022 17:01:32 +0200 Subject: [PATCH 073/171] Fix test --- tests/impure-derivations.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/impure-derivations.sh b/tests/impure-derivations.sh index 1b33a785c..aab5cb61a 100644 --- a/tests/impure-derivations.sh +++ b/tests/impure-derivations.sh @@ -10,7 +10,7 @@ clearStore # Basic test of impure derivations: building one a second time should not use the previous result. printf 0 > $TEST_ROOT/counter -json=$(nix build -L --no-link --json --file ./impure-derivations.nix impure) +json=$(nix build -L --no-link --json --file ./impure-derivations.nix impure.all) path1=$(echo $json | jq -r .[].outputs.out) path1_stuff=$(echo $json | jq -r .[].outputs.stuff) [[ $(< $path1/n) = 0 ]] From d7fc33c8426768040b322a060b5a50433b3a78e6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 31 Mar 2022 15:59:14 +0200 Subject: [PATCH 074/171] Fix macOS build --- src/libstore/build/local-derivation-goal.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index a6c07314f..108c661c0 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -1918,7 +1918,7 @@ void LocalDerivationGoal::runChild() sandboxProfile += "(import \"sandbox-defaults.sb\")\n"; - if (derivationType.isImpure()) + if (derivationType.needsNetworkAccess()) sandboxProfile += "(import \"sandbox-network.sb\")\n"; /* Add the output paths we'll use at build-time to the chroot */ From 4e043c2f32af3d3d49c53a2d06746e2fd6967836 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 31 Mar 2022 16:01:50 +0200 Subject: [PATCH 075/171] Document isPure() --- src/libstore/derivations.hh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index b62e40786..98e59b64e 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -135,7 +135,10 @@ struct DerivationType : _DerivationTypeRaw { separately. Never true for non-CA derivations. */ bool needsNetworkAccess() const; - /* FIXME */ + /* Whether the derivation is expected to produce the same result + every time, and therefore it only needs to be built once. This + is only false for derivations that have the attribute '__impure + = true'. */ bool isPure() const; /* Does the derivation knows its own output paths? From e279fbb16a99896101006ec00277a5d9d50f5040 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 31 Mar 2022 16:06:40 +0200 Subject: [PATCH 076/171] needsNetworkAccess() -> isSandboxed() --- src/libstore/build/derivation-goal.cc | 2 +- src/libstore/build/local-derivation-goal.cc | 12 ++++++------ src/libstore/derivations.cc | 8 ++++---- src/libstore/derivations.hh | 10 ++++++---- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 542a6f6ea..6582497bd 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -955,7 +955,7 @@ void DerivationGoal::buildDone() st = dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic : statusOk(status) ? BuildResult::OutputRejected : - derivationType.needsNetworkAccess() || diskFull ? BuildResult::TransientFailure : + !derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure : BuildResult::PermanentFailure; } diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 108c661c0..40ef706a6 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -395,7 +395,7 @@ void LocalDerivationGoal::startBuilder() else if (settings.sandboxMode == smDisabled) useChroot = false; else if (settings.sandboxMode == smRelaxed) - useChroot = !derivationType.needsNetworkAccess() && !noChroot; + useChroot = derivationType.isSandboxed() && !noChroot; } auto & localStore = getLocalStore(); @@ -608,7 +608,7 @@ void LocalDerivationGoal::startBuilder() "nogroup:x:65534:\n", sandboxGid())); /* Create /etc/hosts with localhost entry. */ - if (!derivationType.needsNetworkAccess()) + if (derivationType.isSandboxed()) writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n"); /* Make the closure of the inputs available in the chroot, @@ -796,7 +796,7 @@ void LocalDerivationGoal::startBuilder() us. */ - if (!derivationType.needsNetworkAccess()) + if (derivationType.isSandboxed()) privateNetwork = true; userNamespaceSync.create(); @@ -1060,7 +1060,7 @@ void LocalDerivationGoal::initEnv() to the builder is generally impure, but the output of fixed-output derivations is by definition pure (since we already know the cryptographic hash of the output). */ - if (derivationType.needsNetworkAccess()) { + if (!derivationType.isSandboxed()) { for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings())) env[i] = getEnv(i).value_or(""); } @@ -1674,7 +1674,7 @@ void LocalDerivationGoal::runChild() /* Fixed-output derivations typically need to access the network, so give them access to /etc/resolv.conf and so on. */ - if (derivationType.needsNetworkAccess()) { + if (!derivationType.isSandboxed()) { // Only use nss functions to resolve hosts and // services. Don’t use it for anything else that may // be configured for this system. This limits the @@ -1918,7 +1918,7 @@ void LocalDerivationGoal::runChild() sandboxProfile += "(import \"sandbox-defaults.sb\")\n"; - if (derivationType.needsNetworkAccess()) + if (!derivationType.isSandboxed()) sandboxProfile += "(import \"sandbox-network.sb\")\n"; /* Add the output paths we'll use at build-time to the chroot */ diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 75e0178bb..b4fb77f9f 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -90,17 +90,17 @@ bool DerivationType::hasKnownOutputPaths() const } -bool DerivationType::needsNetworkAccess() const +bool DerivationType::isSandboxed() const { return std::visit(overloaded { [](const InputAddressed & ia) { - return false; + return true; }, [](const ContentAddressed & ca) { - return !ca.pure; + return ca.pure; }, [](const Impure &) { - return true; + return false; }, }, raw()); } diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index 98e59b64e..489948c30 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -130,10 +130,12 @@ struct DerivationType : _DerivationTypeRaw { non-CA derivations. */ bool isFixed() const; - /* Whether the derivation needs to access the network. Note that - whether or not we actually sandbox the derivation is controlled - separately. Never true for non-CA derivations. */ - bool needsNetworkAccess() const; + /* Whether the derivation is fully sandboxed. If false, the + sandbox is opened up, e.g. the derivation has access to the + network. Note that whether or not we actually sandbox the + derivation is controlled separately. Always true for non-CA + derivations. */ + bool isSandboxed() const; /* Whether the derivation is expected to produce the same result every time, and therefore it only needs to be built once. This From 6051cc954b990fa57a6d3b75bd4b0aaaceb0ca82 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 31 Mar 2022 16:12:25 +0200 Subject: [PATCH 077/171] Rename 'pure' -> 'sandboxed' for consistency --- src/libstore/derivations.cc | 6 +++--- src/libstore/derivations.hh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index b4fb77f9f..cc04119c3 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -97,7 +97,7 @@ bool DerivationType::isSandboxed() const return true; }, [](const ContentAddressed & ca) { - return ca.pure; + return ca.sandboxed; }, [](const Impure &) { return false; @@ -530,7 +530,7 @@ DerivationType BasicDerivation::type() const if (*fixedCAOutputs.begin() != "out") throw Error("single fixed output must be named \"out\""); return DerivationType::ContentAddressed { - .pure = false, + .sandboxed = false, .fixed = true, }; } @@ -541,7 +541,7 @@ DerivationType BasicDerivation::type() const && deferredIAOutputs.empty() && impureOutputs.empty()) return DerivationType::ContentAddressed { - .pure = true, + .sandboxed = true, .fixed = false, }; diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index 489948c30..af198a767 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -102,7 +102,7 @@ struct DerivationType_InputAddressed { }; struct DerivationType_ContentAddressed { - bool pure; + bool sandboxed; bool fixed; }; From a99af85a770df462985b621c4c3dd710b8487f44 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 31 Mar 2022 16:39:18 +0200 Subject: [PATCH 078/171] Fix macOS build --- src/libstore/derivations.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index cc04119c3..1c695de82 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -222,11 +222,9 @@ static DerivationOutput parseDerivationOutput(const Store & store, if (hash == "impure") { settings.requireExperimentalFeature(Xp::ImpureDerivations); assert(pathS == ""); - return DerivationOutput { - .output = DerivationOutputImpure { - .method = std::move(method), - .hashType = std::move(hashType), - }, + return DerivationOutput::Impure { + .method = std::move(method), + .hashType = std::move(hashType), }; } else if (hash != "") { validatePath(pathS); From 75370972847a1b992055085f39b38f1f659e5275 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 31 Mar 2022 16:56:44 +0200 Subject: [PATCH 079/171] Provide default values for outputHashAlgo and outputHashMode --- src/libexpr/primops.cc | 19 +++++++++++-------- tests/impure-derivations.nix | 5 ----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index eaf04320e..969391725 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -991,8 +991,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * bool contentAddressed = false; bool isImpure = false; std::optional<std::string> outputHash; - std::string outputHashAlgo; - auto ingestionMethod = FileIngestionMethod::Flat; + std::optional<std::string> outputHashAlgo; + std::optional<FileIngestionMethod> ingestionMethod; StringSet outputs; outputs.insert("out"); @@ -1190,15 +1190,16 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * .errPos = posDrvName }); - std::optional<HashType> ht = parseHashTypeOpt(outputHashAlgo); + std::optional<HashType> ht = parseHashTypeOpt(outputHashAlgo.value_or("sha256")); Hash h = newHashAllowEmpty(*outputHash, ht); - auto outPath = state.store->makeFixedOutputPath(ingestionMethod, h, drvName); + auto method = ingestionMethod.value_or(FileIngestionMethod::Flat); + auto outPath = state.store->makeFixedOutputPath(method, h, drvName); drv.env["out"] = state.store->printStorePath(outPath); drv.outputs.insert_or_assign("out", DerivationOutput::CAFixed { .hash = FixedOutputHash { - .method = ingestionMethod, + .method = method, .hash = std::move(h), }, }); @@ -1211,19 +1212,21 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * .errPos = posDrvName }); - HashType ht = parseHashType(outputHashAlgo); + auto ht = parseHashType(outputHashAlgo.value_or("sha256")); + auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive); + for (auto & i : outputs) { drv.env[i] = hashPlaceholder(i); if (isImpure) drv.outputs.insert_or_assign(i, DerivationOutput::Impure { - .method = ingestionMethod, + .method = method, .hashType = ht, }); else drv.outputs.insert_or_assign(i, DerivationOutput::CAFloating { - .method = ingestionMethod, + .method = method, .hashType = ht, }); } diff --git a/tests/impure-derivations.nix b/tests/impure-derivations.nix index 2fed56fe7..98547e6c1 100644 --- a/tests/impure-derivations.nix +++ b/tests/impure-derivations.nix @@ -15,8 +15,6 @@ rec { printf $((x + 1)) > $TEST_ROOT/counter ''; __impure = true; - outputHashAlgo = "sha256"; - outputHashMode = "recursive"; impureEnvVars = [ "TEST_ROOT" ]; }; @@ -32,8 +30,6 @@ rec { ln -s $out $out/self ''; __impure = true; - outputHashAlgo = "sha256"; - outputHashMode = "recursive"; }; # This is not allowed. @@ -53,7 +49,6 @@ rec { x=$(< ${impureOnImpure}/n) printf ''${x:0:1} > $out ''; - outputHashAlgo = "sha256"; outputHashMode = "recursive"; outputHash = "sha256-eBYxcgkuWuiqs4cKNgKwkb3vY/HR0vVsJnqe8itJGcQ="; }; From d63a5f5dd3b47e629295eb68264b4a6aadc65aa7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 31 Mar 2022 17:33:06 +0200 Subject: [PATCH 080/171] Update release notes --- doc/manual/src/release-notes/rl-next.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 2ec864ee4..4f3c9ce41 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -14,3 +14,21 @@ This function is only available if you enable the experimental feature `fetch-closure`. + +* New experimental feature: *impure derivations*. These are + derivations that can produce a different result every time they're + built. Here is an example: + + ```nix + stdenv.mkDerivation { + name = "impure"; + __impure = true; # marks this derivation as impure + buildCommand = "date > $out"; + } + ``` + + Running `nix build` twice on this expression will build the + derivation twice, producing two different content-addressed store + paths. Like fixed-output derivations, impure derivations have access + to the network. Only fixed-output derivations and impure derivations + can depend on an impure derivation. From 6377442c983f4399d0a997238b898298e349fc1b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 31 Mar 2022 17:38:15 +0200 Subject: [PATCH 081/171] tests/impure-derivations.sh: Ensure that inputAddressed build fails --- tests/impure-derivations.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/impure-derivations.sh b/tests/impure-derivations.sh index aab5cb61a..35ae3f5d3 100644 --- a/tests/impure-derivations.sh +++ b/tests/impure-derivations.sh @@ -5,6 +5,8 @@ requireDaemonNewerThan "2.8pre20220311" enableFeatures "ca-derivations ca-references impure-derivations" restartDaemon +set -o pipefail + clearStore # Basic test of impure derivations: building one a second time should not use the previous result. @@ -33,7 +35,7 @@ path4=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnIm [[ $(< $path4/self/n) = X3 ]] # Input-addressed derivations cannot depend on impure derivations directly. -nix build -L --no-link --json --file ./impure-derivations.nix inputAddressed 2>&1 | grep 'depends on impure derivation' +(! nix build -L --no-link --json --file ./impure-derivations.nix inputAddressed 2>&1) | grep 'depends on impure derivation' drvPath=$(nix eval --json --file ./impure-derivations.nix impure.drvPath | jq -r .) [[ $(nix show-derivation $drvPath | jq ".[\"$drvPath\"].outputs.out.impure") = true ]] From 7492030ed7d1d570b71e832b610150054a9f095b Mon Sep 17 00:00:00 2001 From: Artturin <Artturin@artturin.com> Date: Thu, 31 Mar 2022 22:14:53 +0300 Subject: [PATCH 082/171] scripts/install-systemd-multi-user.sh: fix another typo --- scripts/install-systemd-multi-user.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install-systemd-multi-user.sh b/scripts/install-systemd-multi-user.sh index 1d92c5388..62397127a 100755 --- a/scripts/install-systemd-multi-user.sh +++ b/scripts/install-systemd-multi-user.sh @@ -90,7 +90,7 @@ poly_configure_nix_daemon_service() { ln -sfn /nix/var/nix/profiles/default/$TMPFILES_SRC $TMPFILES_DEST _sudo "to run systemd-tmpfiles once to pick that path up" \ - systemd-tmpfiles create --prefix=/nix/var/nix + systemd-tmpfiles --create --prefix=/nix/var/nix _sudo "to set up the nix-daemon service" \ systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC" From fdfe737867920ed0ec29ba8ffb7d19854d8658bb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Fri, 1 Apr 2022 12:40:49 +0200 Subject: [PATCH 083/171] Fix handling of outputHash when outputHashAlgo is not specified https://hydra.nixos.org/build/171351131 --- src/libexpr/primops.cc | 7 +++---- tests/ca/content-addressed.nix | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 969391725..73817dbdd 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -991,7 +991,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * bool contentAddressed = false; bool isImpure = false; std::optional<std::string> outputHash; - std::optional<std::string> outputHashAlgo; + std::string outputHashAlgo; std::optional<FileIngestionMethod> ingestionMethod; StringSet outputs; @@ -1190,8 +1190,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * .errPos = posDrvName }); - std::optional<HashType> ht = parseHashTypeOpt(outputHashAlgo.value_or("sha256")); - Hash h = newHashAllowEmpty(*outputHash, ht); + auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo)); auto method = ingestionMethod.value_or(FileIngestionMethod::Flat); auto outPath = state.store->makeFixedOutputPath(method, h, drvName); @@ -1212,7 +1211,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * .errPos = posDrvName }); - auto ht = parseHashType(outputHashAlgo.value_or("sha256")); + auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256); auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive); for (auto & i : outputs) { diff --git a/tests/ca/content-addressed.nix b/tests/ca/content-addressed.nix index d328fc92c..1be3eeb6e 100644 --- a/tests/ca/content-addressed.nix +++ b/tests/ca/content-addressed.nix @@ -64,8 +64,7 @@ rec { dependentFixedOutput = mkDerivation { name = "dependent-fixed-output"; outputHashMode = "recursive"; - outputHashAlgo = "sha256"; - outputHash = "sha256-QvtAMbUl/uvi+LCObmqOhvNOapHdA2raiI4xG5zI5pA="; + outputHash = "sha512-7aJcmSuEuYP5tGKcmGY8bRr/lrCjJlOxP2mIUjO/vMQeg6gx/65IbzRWES8EKiPDOs9z+wF30lEfcwxM/cT4pw=="; buildCommand = '' cat ${dependentCA}/dep echo foo > $out From 435848cef12d065b209c82c96ce3a8bfa5e6a051 Mon Sep 17 00:00:00 2001 From: aszlig <aszlig@nix.build> Date: Fri, 1 Apr 2022 09:23:43 -0700 Subject: [PATCH 084/171] libutil: Fix restoring mount namespace I regularly pass around simple scripts by using nix-shell as the script interpreter, eg. like this: #!/usr/bin/env nix-shell #!nix-shell -p dd_rescue coreutils bash -i bash While this works most of the time, I recently had one occasion where it would not and the above would result in the following: $ sudo ./myscript.sh bash: ./myscript.sh: No such file or directory Note the "sudo" here, because this error only occurs if we're root. The reason for the latter is because running Nix as root means that we can directly access the store, which makes sure we use a filesystem namespace to make the store writable. XXX - REWORD! So when stracing the process, I stumbled on the following sequence: openat(AT_FDCWD, "/proc/self/ns/mnt", O_RDONLY) = 3 unshare(CLONE_NEWNS) = 0 ... later ... getcwd("/the/real/cwd", 4096) = 14 setns(3, CLONE_NEWNS) = 0 getcwd("/", 4096) = 2 In the whole strace output there are no calls to chdir() whatsoever, so I decided to look into the kernel source to see what else could change directories and found this[1]: /* Update the pwd and root */ set_fs_pwd(fs, &root); set_fs_root(fs, &root); The set_fs_pwd() call is roughly equivalent to a chdir() syscall and this is called when the setns() syscall is invoked[2]. [1]: https://github.com/torvalds/linux/blob/b14ffae378aa1db993e62b01392e70d1e585fb23/fs/namespace.c#L4659 [2]: https://github.com/torvalds/linux/blob/b14ffae378aa1db993e62b01392e70d1e585fb23/kernel/nsproxy.c#L346 --- src/libutil/util.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 59e3aad6d..e62672717 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -10,6 +10,7 @@ #include <cstdio> #include <cstdlib> #include <cstring> +#include <filesystem> #include <future> #include <iostream> #include <mutex> @@ -1688,13 +1689,17 @@ void setStackSize(size_t stackSize) #endif } +#if __linux__ static AutoCloseFD fdSavedMountNamespace; +std::optional<std::filesystem::path> savedCwd; +#endif void saveMountNamespace() { #if __linux__ static std::once_flag done; std::call_once(done, []() { + savedCwd.emplace(std::filesystem::current_path()); AutoCloseFD fd = open("/proc/self/ns/mnt", O_RDONLY); if (!fd) throw SysError("saving parent mount namespace"); @@ -1712,6 +1717,12 @@ void restoreMountNamespace() } catch (Error & e) { debug(e.msg()); } + try { + if (savedCwd) + std::filesystem::current_path(*savedCwd); + } catch (std::filesystem::filesystem_error const &e) { + debug(e.what()); + } #endif } From 7f5caaa7c0f151520d05d4662415ac09d4cf34b0 Mon Sep 17 00:00:00 2001 From: Cole Helbling <cole.e.helbling@outlook.com> Date: Fri, 1 Apr 2022 10:24:31 -0700 Subject: [PATCH 085/171] libutil: Don't use std::filesystem Just in case making libutil depend on std::filesystem is unacceptable, here is the non-filesystem approach. --- src/libutil/util.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index e62672717..0b18f1027 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -10,7 +10,6 @@ #include <cstdio> #include <cstdlib> #include <cstring> -#include <filesystem> #include <future> #include <iostream> #include <mutex> @@ -1691,7 +1690,7 @@ void setStackSize(size_t stackSize) #if __linux__ static AutoCloseFD fdSavedMountNamespace; -std::optional<std::filesystem::path> savedCwd; +std::optional<Path> savedCwd; #endif void saveMountNamespace() @@ -1699,7 +1698,11 @@ void saveMountNamespace() #if __linux__ static std::once_flag done; std::call_once(done, []() { - savedCwd.emplace(std::filesystem::current_path()); + char* cwd = getcwd(NULL, 0); + if (cwd == NULL) throw SysError("getting cwd"); + savedCwd.emplace(cwd); + free(cwd); + AutoCloseFD fd = open("/proc/self/ns/mnt", O_RDONLY); if (!fd) throw SysError("saving parent mount namespace"); @@ -1717,11 +1720,8 @@ void restoreMountNamespace() } catch (Error & e) { debug(e.msg()); } - try { - if (savedCwd) - std::filesystem::current_path(*savedCwd); - } catch (std::filesystem::filesystem_error const &e) { - debug(e.what()); + if (savedCwd && chdir(savedCwd->c_str()) == -1) { + throw SysError("restoring cwd"); } #endif } From 2a45cf54e4201a894254604676dcb51f4c1a471c Mon Sep 17 00:00:00 2001 From: Cole Helbling <cole.e.helbling@outlook.com> Date: Fri, 1 Apr 2022 12:20:34 -0700 Subject: [PATCH 086/171] libutil: Properly guard self-allocating getcwd on GNU It's a GNU extension, as pointed out by pennae. --- src/libutil/util.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 0b18f1027..28ab77adc 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1698,10 +1698,19 @@ void saveMountNamespace() #if __linux__ static std::once_flag done; std::call_once(done, []() { - char* cwd = getcwd(NULL, 0); - if (cwd == NULL) throw SysError("getting cwd"); +#ifdef __GNU__ + // getcwd allocating its return value is a GNU extension. + char *cwd = getcwd(NULL, 0); + if (cwd == NULL) +#else + char cwd[PATH_MAX]; + if (!getcwd(cwd, sizeof(cwd))) +#endif + throw SysError("getting cwd"); savedCwd.emplace(cwd); +#ifdef __GNU__ free(cwd); +#endif AutoCloseFD fd = open("/proc/self/ns/mnt", O_RDONLY); if (!fd) From c1e2ce4515c12ff0b4b1c3ab6d1e057507104979 Mon Sep 17 00:00:00 2001 From: Tom Bereknyei <tomberek@gmail.com> Date: Thu, 31 Mar 2022 20:30:53 -0400 Subject: [PATCH 087/171] fix(run): set applyNixConfig lockFlag --- src/nix/run.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nix/run.cc b/src/nix/run.cc index 23e893fbf..25a8fa8d3 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -182,6 +182,7 @@ struct CmdRun : InstallableCommand { auto state = getEvalState(); + lockFlags.applyNixConfig = true; auto app = installable->toApp(*state).resolve(getEvalStore(), store); Strings allArgs{app.program}; From a4a1de69dcc3c6e0c40a093d67b5f20568a5f31e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Mon, 4 Apr 2022 16:49:39 +0200 Subject: [PATCH 088/171] Add missing #include --- src/libstore/build-result.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libstore/build-result.hh b/src/libstore/build-result.hh index cb6d19b8e..7f9bcce6d 100644 --- a/src/libstore/build-result.hh +++ b/src/libstore/build-result.hh @@ -1,6 +1,7 @@ #pragma once #include "realisation.hh" +#include "derived-path.hh" #include <string> #include <chrono> From e5b70d47aa656833e4609106f9f1ef48b67664cc Mon Sep 17 00:00:00 2001 From: Cole Helbling <cole.e.helbling@outlook.com> Date: Mon, 4 Apr 2022 08:19:49 -0700 Subject: [PATCH 089/171] libutil: cleanup savedCwd logic Co-authored-by: Eelco Dolstra <edolstra@gmail.com> --- src/libutil/util.cc | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 28ab77adc..9b34d5d72 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1698,20 +1698,7 @@ void saveMountNamespace() #if __linux__ static std::once_flag done; std::call_once(done, []() { -#ifdef __GNU__ - // getcwd allocating its return value is a GNU extension. - char *cwd = getcwd(NULL, 0); - if (cwd == NULL) -#else - char cwd[PATH_MAX]; - if (!getcwd(cwd, sizeof(cwd))) -#endif - throw SysError("getting cwd"); - savedCwd.emplace(cwd); -#ifdef __GNU__ - free(cwd); -#endif - + savedCwd = absPath("."); AutoCloseFD fd = open("/proc/self/ns/mnt", O_RDONLY); if (!fd) throw SysError("saving parent mount namespace"); From e135d223f66f77f2b03a11b979d714e582364013 Mon Sep 17 00:00:00 2001 From: Cole Helbling <cole.e.helbling@outlook.com> Date: Mon, 4 Apr 2022 08:32:45 -0700 Subject: [PATCH 090/171] libutil: save fd to cwd instead of cwd itself --- src/libutil/util.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 9b34d5d72..a00a03978 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1690,7 +1690,7 @@ void setStackSize(size_t stackSize) #if __linux__ static AutoCloseFD fdSavedMountNamespace; -std::optional<Path> savedCwd; +static AutoCloseFD fdSavedCwd; #endif void saveMountNamespace() @@ -1698,11 +1698,15 @@ void saveMountNamespace() #if __linux__ static std::once_flag done; std::call_once(done, []() { - savedCwd = absPath("."); AutoCloseFD fd = open("/proc/self/ns/mnt", O_RDONLY); if (!fd) throw SysError("saving parent mount namespace"); fdSavedMountNamespace = std::move(fd); + + fd = open("/proc/self/cwd", O_RDONLY); + if (!fd) + throw SysError("saving cwd"); + fdSavedCwd = std::move(fd); }); #endif } @@ -1716,7 +1720,7 @@ void restoreMountNamespace() } catch (Error & e) { debug(e.msg()); } - if (savedCwd && chdir(savedCwd->c_str()) == -1) { + if (fdSavedCwd && fchdir(fdSavedCwd.get()) == -1) { throw SysError("restoring cwd"); } #endif From f89b0f7846f2177b65eebdbff7f24a255d66819e Mon Sep 17 00:00:00 2001 From: Cole Helbling <cole.e.helbling@outlook.com> Date: Mon, 4 Apr 2022 08:33:59 -0700 Subject: [PATCH 091/171] libutil: `try` restoring the cwd from fdSavedCwd --- src/libutil/util.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index a00a03978..1f800f3f4 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1717,12 +1717,12 @@ void restoreMountNamespace() try { if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1) throw SysError("restoring parent mount namespace"); + if (fdSavedCwd && fchdir(fdSavedCwd.get()) == -1) { + throw SysError("restoring cwd"); + } } catch (Error & e) { debug(e.msg()); } - if (fdSavedCwd && fchdir(fdSavedCwd.get()) == -1) { - throw SysError("restoring cwd"); - } #endif } From 10b9c1b2b269b96dcb8b3d298491fa143d1663e8 Mon Sep 17 00:00:00 2001 From: Cole Helbling <cole.e.helbling@outlook.com> Date: Mon, 4 Apr 2022 10:16:30 -0700 Subject: [PATCH 092/171] libutil: save cwd fd in restoreMountNamespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This doesn't work very well (maybe I'm misunderstanding the desired implementation): : ~/w/vc/nix ; doas outputs/out/bin/nix --experimental-features 'nix-command flakes' develop -c pwd pwd: couldn't find directory entry in ‘../../../..’ with matching i-node --- src/libutil/util.cc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 1f800f3f4..701545589 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1690,7 +1690,6 @@ void setStackSize(size_t stackSize) #if __linux__ static AutoCloseFD fdSavedMountNamespace; -static AutoCloseFD fdSavedCwd; #endif void saveMountNamespace() @@ -1702,11 +1701,6 @@ void saveMountNamespace() if (!fd) throw SysError("saving parent mount namespace"); fdSavedMountNamespace = std::move(fd); - - fd = open("/proc/self/cwd", O_RDONLY); - if (!fd) - throw SysError("saving cwd"); - fdSavedCwd = std::move(fd); }); #endif } @@ -1715,6 +1709,10 @@ void restoreMountNamespace() { #if __linux__ try { + AutoCloseFD fdSavedCwd = open("/proc/self/cwd", O_RDONLY); + if (!fdSavedCwd) { + throw SysError("saving cwd"); + } if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1) throw SysError("restoring parent mount namespace"); if (fdSavedCwd && fchdir(fdSavedCwd.get()) == -1) { From 56009b2639dc878be11f94d096fae56ac14dcd1d Mon Sep 17 00:00:00 2001 From: Cole Helbling <cole.e.helbling@outlook.com> Date: Mon, 4 Apr 2022 10:21:56 -0700 Subject: [PATCH 093/171] libutil: don't save cwd fd, use path instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Saving the cwd fd didn't actually work well -- prior to this commit, the following would happen: : ~/w/vc/nix ; doas outputs/out/bin/nix --experimental-features 'nix-command flakes' run nixpkgs#coreutils -- --coreutils-prog=pwd pwd: couldn't find directory entry in ‘../../../..’ with matching i-node : ~/w/vc/nix ; doas outputs/out/bin/nix --experimental-features 'nix-command flakes' develop -c pwd pwd: couldn't find directory entry in ‘../../../..’ with matching i-node --- src/libutil/util.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 701545589..3132e7161 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1709,13 +1709,11 @@ void restoreMountNamespace() { #if __linux__ try { - AutoCloseFD fdSavedCwd = open("/proc/self/cwd", O_RDONLY); - if (!fdSavedCwd) { - throw SysError("saving cwd"); - } + auto savedCwd = absPath("."); + if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1) throw SysError("restoring parent mount namespace"); - if (fdSavedCwd && fchdir(fdSavedCwd.get()) == -1) { + if (chdir(savedCwd.c_str()) == -1) { throw SysError("restoring cwd"); } } catch (Error & e) { From 5abe3f4aa61f33ac2124a624763e067257541871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <theophane@hufschmitt.net> Date: Tue, 5 Apr 2022 11:03:43 +0200 Subject: [PATCH 094/171] Allow `welcomeText` when checking a flake template Fix https://github.com/NixOS/nix/issues/6321 --- src/nix/flake.cc | 2 +- tests/flakes.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 47a380238..ce7dc101a 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -463,7 +463,7 @@ struct CmdFlakeCheck : FlakeCommand for (auto & attr : *v.attrs) { std::string name(attr.name); - if (name != "path" && name != "description") + if (name != "path" && name != "description" && name != "welcomeText") throw Error("template '%s' has unsupported attribute '%s'", attrPath, name); } } catch (Error & e) { diff --git a/tests/flakes.sh b/tests/flakes.sh index ea629ae70..46e6a7982 100644 --- a/tests/flakes.sh +++ b/tests/flakes.sh @@ -376,6 +376,9 @@ cat > $templatesDir/flake.nix <<EOF trivial = { path = ./trivial; description = "A trivial flake"; + welcomeText = '' + Welcome to my trivial flake + ''; }; default = trivial; }; From 660c19eb492d834c238bcdde02ba0a7e3c4f0c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <theophane@hufschmitt.net> Date: Tue, 5 Apr 2022 13:34:25 +0200 Subject: [PATCH 095/171] manual: Add some anchor targets for the nix.conf options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For each `nix.conf` option, add an empty html node with a unique `id` that can be used as an anchor target. Also make the name of the option be a link to that target so that it’s easily discoverable. We can’t rewrite the whole list as an html definition list like it’s done for the builtins because these options also appear in a man page, and the manpage renderer (lowdown) can’t render arbitrary html. But the hack here allows to keep the manpage and have the links in the html version. Fix https://github.com/NixOS/nix/issues/5745 --- doc/manual/generate-options.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/manual/generate-options.nix b/doc/manual/generate-options.nix index 84d90beb6..2d586fa1b 100644 --- a/doc/manual/generate-options.nix +++ b/doc/manual/generate-options.nix @@ -6,7 +6,8 @@ options: concatStrings (map (name: let option = options.${name}; in - " - `${name}` \n\n" + " - [`${name}`](#conf-${name})" + + "<p id=\"conf-${name}\"></p>\n\n" + concatStrings (map (s: " ${s}\n") (splitLines option.description)) + "\n\n" + (if option.documentDefault then " **Default:** " + ( From 9a640afc1e63bed28926cb44fe85137fb7d2d2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <theophane@hufschmitt.net> Date: Tue, 5 Apr 2022 11:33:46 +0200 Subject: [PATCH 096/171] doctor: Always show the output Fix https://github.com/NixOS/nix/issues/6342 --- src/nix/doctor.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nix/doctor.cc b/src/nix/doctor.cc index 4f3003448..ea87e3d87 100644 --- a/src/nix/doctor.cc +++ b/src/nix/doctor.cc @@ -24,12 +24,12 @@ std::string formatProtocol(unsigned int proto) } bool checkPass(const std::string & msg) { - logger->log(ANSI_GREEN "[PASS] " ANSI_NORMAL + msg); + notice(ANSI_GREEN "[PASS] " ANSI_NORMAL + msg); return true; } bool checkFail(const std::string & msg) { - logger->log(ANSI_RED "[FAIL] " ANSI_NORMAL + msg); + notice(ANSI_RED "[FAIL] " ANSI_NORMAL + msg); return false; } From f98d76ff1aeaaf18ea68a86f9eb91dd73b2d6ce4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 5 Apr 2022 14:13:26 +0200 Subject: [PATCH 097/171] rl-2.7.md: Fix title --- doc/manual/src/release-notes/rl-2.7.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/release-notes/rl-2.7.md b/doc/manual/src/release-notes/rl-2.7.md index dc92a9cde..2f3879422 100644 --- a/doc/manual/src/release-notes/rl-2.7.md +++ b/doc/manual/src/release-notes/rl-2.7.md @@ -1,4 +1,4 @@ -# Release X.Y (2022-03-07) +# Release 2.7 (2022-03-07) * Nix will now make some helpful suggestions when you mistype something on the command line. For instance, if you type `nix build From 8d6c937d6a233106dcf9c3e0f0e1c60148b7a71e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 5 Apr 2022 16:41:40 +0200 Subject: [PATCH 098/171] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/82891b5e2c2359d7e58d08849e4c89511ab94234' (2021-09-28) → 'github:NixOS/nixpkgs/530a53dcbc9437363471167a5e4762c5fcfa34a1' (2022-02-19) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 61eccb73c..cd79fa85e 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1632864508, - "narHash": "sha256-d127FIvGR41XbVRDPVvozUPQ/uRHbHwvfyKHwEt5xFM=", + "lastModified": 1645296114, + "narHash": "sha256-y53N7TyIkXsjMpOG7RhvqJFGDacLs9HlyHeSTBioqYU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "82891b5e2c2359d7e58d08849e4c89511ab94234", + "rev": "530a53dcbc9437363471167a5e4762c5fcfa34a1", "type": "github" }, "original": { From 1fa03934791189f02208e1807d822128b216f087 Mon Sep 17 00:00:00 2001 From: Daniel Pauls <daniel1708.de+git@gmail.com> Date: Tue, 5 Apr 2022 21:16:11 +0200 Subject: [PATCH 099/171] libutil: Reserve memory when en/decoding base64 The size of the output when encoding to and decoding from base64 is (roughly) known so we can allocate it in advance to prevent reallocation. --- src/libutil/util.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 59e3aad6d..cca66d5bf 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1471,6 +1471,7 @@ constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv std::string base64Encode(std::string_view s) { std::string res; + res.reserve((s.size() + 2) / 3 * 4); int data = 0, nbits = 0; for (char c : s) { @@ -1502,6 +1503,9 @@ std::string base64Decode(std::string_view s) }(); std::string res; + // Some sequences are missing the padding consisting of up to two '='. + // vvv + res.reserve((s.size() + 2) / 4 * 3); unsigned int d = 0, bits = 0; for (char c : s) { From 513652d5947c1923a004318ea453cab786cabda1 Mon Sep 17 00:00:00 2001 From: Daniel Pauls <daniel1708.de+git@gmail.com> Date: Tue, 5 Apr 2022 22:13:45 +0200 Subject: [PATCH 100/171] tokenizeString: Fix semantic mistake `string_view::find_first_not_of(...)` and `string_view::find_first_of(...)` return `string_view::npos` on error not `string::npos`. --- src/libutil/util.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 59e3aad6d..8ab385837 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1259,9 +1259,9 @@ template<class C> C tokenizeString(std::string_view s, std::string_view separato { C result; auto pos = s.find_first_not_of(separators, 0); - while (pos != std::string::npos) { + while (pos != std::string_view::npos) { auto end = s.find_first_of(separators, pos + 1); - if (end == std::string::npos) end = s.size(); + if (end == std::string_view::npos) end = s.size(); result.insert(result.end(), std::string(s, pos, end - pos)); pos = s.find_first_not_of(separators, end); } From 589f6f267b009bc2856597995db360f910e69a6f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Wed, 6 Apr 2022 11:52:51 +0200 Subject: [PATCH 101/171] fetchClosure: Don't allow URL query parameters Allowing this is a potential security hole, since it allows the user to specify parameters like 'local-nar-cache'. --- src/libexpr/primops/fetchClosure.cc | 9 ++++++++- tests/fetchClosure.sh | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc index efeb93daf..821eba698 100644 --- a/src/libexpr/primops/fetchClosure.cc +++ b/src/libexpr/primops/fetchClosure.cc @@ -61,6 +61,12 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args .errPos = pos }); + if (!parsedURL.query.empty()) + throw Error({ + .msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl), + .errPos = pos + }); + auto fromStore = openStore(parsedURL.to_string()); if (toCA) { @@ -87,7 +93,8 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args }); } } else { - copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath }); + if (!state.store->isValidPath(*fromPath)) + copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath }); toPath = fromPath; } diff --git a/tests/fetchClosure.sh b/tests/fetchClosure.sh index 0c905ac43..96e4bb741 100644 --- a/tests/fetchClosure.sh +++ b/tests/fetchClosure.sh @@ -56,3 +56,15 @@ nix copy --to file://$cacheDir $caPath fromPath = $caPath; } ") = $caPath ]] + +# Check that URL query parameters aren't allowed. +clearStore +narCache=$TEST_ROOT/nar-cache +rm -rf $narCache +(! nix eval -v --raw --expr " + builtins.fetchClosure { + fromStore = \"file://$cacheDir?local-nar-cache=$narCache\"; + fromPath = $caPath; + } +") +(! [ -e $narCache ]) From 318936366d5198123ae9ba0292538529f706dce8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Wed, 6 Apr 2022 12:41:18 +0200 Subject: [PATCH 102/171] Fix empty 'nix copy' error message This was caused by SubstitutionGoal not setting the errorMsg field in its BuildResult. We now get a more descriptive message than in 2.7.0, e.g. error: path '/nix/store/13mh...' is required, but there is no substituter that can build it instead of the misleading (since there was no build) error: build of '/nix/store/13mh...' failed Fixes #6295. --- .../build/drv-output-substitution-goal.cc | 2 +- src/libstore/build/substitution-goal.cc | 19 ++++++++++++++----- src/libstore/build/substitution-goal.hh | 5 ++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/libstore/build/drv-output-substitution-goal.cc b/src/libstore/build/drv-output-substitution-goal.cc index e50292c1e..b7f7b5ab1 100644 --- a/src/libstore/build/drv-output-substitution-goal.cc +++ b/src/libstore/build/drv-output-substitution-goal.cc @@ -41,7 +41,7 @@ void DrvOutputSubstitutionGoal::tryNext() if (subs.size() == 0) { /* None left. Terminate this goal and let someone else deal with it. */ - debug("drv output '%s' is required, but there is no substituter that can provide it", id.to_string()); + debug("derivation output '%s' is required, but there is no substituter that can provide it", id.to_string()); /* Hack: don't indicate failure if there were no substituters. In that case the calling derivation should just do a diff --git a/src/libstore/build/substitution-goal.cc b/src/libstore/build/substitution-goal.cc index 31e6dbc9f..ca5218627 100644 --- a/src/libstore/build/substitution-goal.cc +++ b/src/libstore/build/substitution-goal.cc @@ -24,9 +24,16 @@ PathSubstitutionGoal::~PathSubstitutionGoal() } -void PathSubstitutionGoal::done(ExitCode result, BuildResult::Status status) +void PathSubstitutionGoal::done( + ExitCode result, + BuildResult::Status status, + std::optional<std::string> errorMsg) { buildResult.status = status; + if (errorMsg) { + debug(*errorMsg); + buildResult.errorMsg = *errorMsg; + } amDone(result); } @@ -67,12 +74,14 @@ void PathSubstitutionGoal::tryNext() if (subs.size() == 0) { /* None left. Terminate this goal and let someone else deal with it. */ - debug("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath)); /* Hack: don't indicate failure if there were no substituters. In that case the calling derivation should just do a build. */ - done(substituterFailed ? ecFailed : ecNoSubstituters, BuildResult::NoSubstituters); + done( + substituterFailed ? ecFailed : ecNoSubstituters, + BuildResult::NoSubstituters, + fmt("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath))); if (substituterFailed) { worker.failedSubstitutions++; @@ -169,10 +178,10 @@ void PathSubstitutionGoal::referencesValid() trace("all references realised"); if (nrFailed > 0) { - debug("some references of path '%s' could not be realised", worker.store.printStorePath(storePath)); done( nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed, - BuildResult::DependencyFailed); + BuildResult::DependencyFailed, + fmt("some references of path '%s' could not be realised", worker.store.printStorePath(storePath))); return; } diff --git a/src/libstore/build/substitution-goal.hh b/src/libstore/build/substitution-goal.hh index 946f13841..a73f8e666 100644 --- a/src/libstore/build/substitution-goal.hh +++ b/src/libstore/build/substitution-goal.hh @@ -53,7 +53,10 @@ struct PathSubstitutionGoal : public Goal /* Content address for recomputing store path */ std::optional<ContentAddress> ca; - void done(ExitCode result, BuildResult::Status status); + void done( + ExitCode result, + BuildResult::Status status, + std::optional<std::string> errorMsg = {}); public: PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt); From a7b12c6bd90cd44432bdaf45cb32b0af916d499c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Wed, 6 Apr 2022 13:34:25 +0200 Subject: [PATCH 103/171] curl: Use --fail to catch errors --- scripts/check-hydra-status.sh | 2 +- scripts/install.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check-hydra-status.sh b/scripts/check-hydra-status.sh index 5e2f03429..e62705e94 100644 --- a/scripts/check-hydra-status.sh +++ b/scripts/check-hydra-status.sh @@ -14,7 +14,7 @@ curl -sS -H 'Accept: application/json' https://hydra.nixos.org/jobset/nix/master someBuildFailed=0 for buildId in $BUILDS_FOR_LATEST_EVAL; do - buildInfo=$(curl -sS -H 'Accept: application/json' "https://hydra.nixos.org/build/$buildId") + buildInfo=$(curl --fail -sS -H 'Accept: application/json' "https://hydra.nixos.org/build/$buildId") finished=$(echo "$buildInfo" | jq -r '.finished') diff --git a/scripts/install.in b/scripts/install.in index 38d1fb36f..af5f71080 100755 --- a/scripts/install.in +++ b/scripts/install.in @@ -82,7 +82,7 @@ if [ "$(uname -s)" != "Darwin" ]; then fi if command -v curl > /dev/null 2>&1; then - fetch() { curl -L "$1" -o "$2"; } + fetch() { curl --fail -L "$1" -o "$2"; } elif command -v wget > /dev/null 2>&1; then fetch() { wget "$1" -O "$2"; } else From 1e1cd6e7a926097683da366983cc362c2430867d Mon Sep 17 00:00:00 2001 From: Daniel Pauls <daniel1708.de+git@gmail.com> Date: Wed, 6 Apr 2022 17:33:23 +0200 Subject: [PATCH 104/171] libfetchers: Fix assertion The filter expects all paths to have a prefix of the raw `actualUrl`, but `Store::addToStore(...)` provides absolute canonicalized paths. To fix this create an absolute and canonicalized path from the `actualUrl` and use it instead. Fixes #6195. --- src/libfetchers/git.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index d75c5d3ae..f8433bc28 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -285,9 +285,11 @@ struct GitInputScheme : InputScheme auto files = tokenizeString<std::set<std::string>>( runProgram("git", true, gitOpts), "\0"s); + Path actualPath(absPath(actualUrl)); + PathFilter filter = [&](const Path & p) -> bool { - assert(hasPrefix(p, actualUrl)); - std::string file(p, actualUrl.size() + 1); + assert(hasPrefix(p, actualPath)); + std::string file(p, actualPath.size() + 1); auto st = lstat(p); @@ -300,13 +302,13 @@ struct GitInputScheme : InputScheme return files.count(file); }; - auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter); + 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", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); + hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); return {std::move(storePath), input}; } From b9c969a86667a7b16a7efa1df0e3d090ef2ead72 Mon Sep 17 00:00:00 2001 From: Rehno Lindeque <errantkid@gmail.com> Date: Wed, 6 Apr 2022 12:20:39 -0400 Subject: [PATCH 105/171] nix flake check: Warn about deprecated nixosModule output --- src/nix/flake.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nix/flake.cc b/src/nix/flake.cc index ce7dc101a..a876bb3af 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -508,6 +508,7 @@ struct CmdFlakeCheck : FlakeCommand name == "defaultBundler" ? "bundlers.<system>.default" : name == "overlay" ? "overlays.default" : name == "devShell" ? "devShells.<system>.default" : + name == "nixosModule" ? "nixosModules.default" : ""; if (replacement != "") warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement); From 5ff4c42608bdf54d6f889f196f59a6c20c1631d6 Mon Sep 17 00:00:00 2001 From: Rehno Lindeque <errantkid@gmail.com> Date: Wed, 6 Apr 2022 12:24:35 -0400 Subject: [PATCH 106/171] Update release notes --- doc/manual/src/release-notes/rl-next.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 4f3c9ce41..8c8c0fd41 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -32,3 +32,11 @@ paths. Like fixed-output derivations, impure derivations have access to the network. Only fixed-output derivations and impure derivations can depend on an impure derivation. + +* The `nixosModule` flake output attribute has been renamed consistent + with the `.default` renames in nix 2.7. + + * `nixosModule` → `nixosModules.default` + + As before, the old output will continue to work, but `nix flake check` will + issue a warning about it. From 305d3a0ec3c7d53a5ceffee239c6cd4949f99423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <theophane@hufschmitt.net> Date: Thu, 7 Apr 2022 17:31:12 +0200 Subject: [PATCH 107/171] Test fetchgit with path containing a `.` segment --- tests/fetchGit.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh index ac23be5c0..9179e2071 100644 --- a/tests/fetchGit.sh +++ b/tests/fetchGit.sh @@ -7,7 +7,9 @@ fi clearStore -repo=$TEST_ROOT/git +# Intentionally not in a canonical form +# See https://github.com/NixOS/nix/issues/6195 +repo=$TEST_ROOT/./git export _NIX_FORCE_HTTP=1 From 2c2fd4946f96e6839ecbfb4cf61318d8910e7e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Ga=C5=82kowski?= <kpg@posteo.net> Date: Wed, 6 Apr 2022 19:28:12 +0200 Subject: [PATCH 108/171] don't assume that rev is a SHA1 hash This was a problem when writing a fetcher that uses e.g. sha256 hashes for revisions. This doesn't actually do anything new, but allows for creating such fetchers in the future (perhaps when support for Git's SHA256 object format gains more popularity). --- src/libfetchers/fetchers.cc | 15 ++++++++++++--- src/libutil/hash.cc | 2 +- src/libutil/hash.hh | 2 -- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index 976f40d3b..6957d2da4 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -238,9 +238,18 @@ std::optional<std::string> Input::getRef() const std::optional<Hash> Input::getRev() const { - if (auto s = maybeGetStrAttr(attrs, "rev")) - return Hash::parseAny(*s, htSHA1); - return {}; + std::optional<Hash> hash = {}; + + if (auto s = maybeGetStrAttr(attrs, "rev")) { + try { + hash = Hash::parseAnyPrefixed(*s); + } catch (BadHash &e) { + // Default to sha1 for backwards compatibility with existing flakes + hash = Hash::parseAny(*s, htSHA1); + } + } + + return hash; } std::optional<uint64_t> Input::getRevCount() const diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index a4d632161..d2fd0c15a 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -155,7 +155,7 @@ static std::pair<std::optional<HashType>, bool> getParsedTypeAndSRI(std::string_ { bool isSRI = false; - // Parse the has type before the separater, if there was one. + // Parse the hash type before the separator, if there was one. std::optional<HashType> optParsedType; { auto hashRaw = splitPrefixTo(rest, ':'); diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index 56b5938b3..00f70a572 100644 --- a/src/libutil/hash.hh +++ b/src/libutil/hash.hh @@ -93,13 +93,11 @@ public: std::string gitRev() const { - assert(type == htSHA1); return to_string(Base16, false); } std::string gitShortRev() const { - assert(type == htSHA1); return std::string(to_string(Base16, false), 0, 7); } From 4f29cf1a1d906f35054f8b318df7c0d3a9117bb3 Mon Sep 17 00:00:00 2001 From: Martin Schwaighofer <mschwaig@users.noreply.github.com> Date: Thu, 7 Apr 2022 11:28:20 +0200 Subject: [PATCH 109/171] installer: ask for confirmation on multi-user install without systemd On Linux a user can go through all the way through the multi-user install and find out at the end that they now have to manually configure their init system to launch the nix daemon. I suspect that for a significant number of users this is not what they wanted. They might prefer a single-user install. Now they have to manually uninstall nix before they can go through the single-user install. This introduces a confirmation dialog before the install in that specific situation to make sure that they want to proceed. See also: https://github.com/NixOS/nix/issues/4999#issuecomment-1064188080 This closes #4999 but rejecting it and closing that issue anyways would also be valid. --- scripts/install-multi-user.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh index 69b6676ea..b79a9c23a 100644 --- a/scripts/install-multi-user.sh +++ b/scripts/install-multi-user.sh @@ -423,6 +423,18 @@ EOF fi done + if [ "$(uname -s)" = "Linux" ] && [ ! -e /run/systemd/system ]; then + warning <<EOF +We did not detect systemd on your system. With a multi-user install +without systemd you will have to manually configure your init system to +launch the Nix daemon after installation. +EOF + if ! ui_confirm "Do you want to proceed with a multi-user installation?"; then + failure <<EOF +You have aborted the installation. +EOF + fi + fi } setup_report() { From 168ef9f3ab73ac8ea2d8263304fd6df29a76e0e0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Wed, 6 Apr 2022 13:02:26 +0200 Subject: [PATCH 110/171] Remove unused Error.name field --- src/libstore/sqlite.cc | 1 - src/libutil/error.cc | 3 --- src/libutil/error.hh | 1 - src/libutil/serialise.cc | 5 ++--- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index e6ecadd7f..1d82b4ab1 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -215,7 +215,6 @@ void handleSQLiteBusy(const SQLiteBusy & e) if (now > lastWarned + 10) { lastWarned = now; logWarning({ - .name = "Sqlite busy", .msg = hintfmt(e.what()) }); } diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 02bc5caa5..9172f67a6 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -21,12 +21,9 @@ const std::string & BaseError::calcWhat() const if (what_.has_value()) return *what_; else { - err.name = sname(); - std::ostringstream oss; showErrorInfo(oss, err, loggerSettings.showTrace); what_ = oss.str(); - return *what_; } } diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 6a757f9ad..005c2c225 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -109,7 +109,6 @@ struct Trace { struct ErrorInfo { Verbosity level; - std::string name; // FIXME: rename hintformat msg; std::optional<ErrPos> errPos; std::list<Trace> traces; diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc index 6445b3f1b..8ff904583 100644 --- a/src/libutil/serialise.cc +++ b/src/libutil/serialise.cc @@ -357,7 +357,7 @@ Sink & operator << (Sink & sink, const Error & ex) sink << "Error" << info.level - << info.name + << "Error" // removed << info.msg.str() << 0 // FIXME: info.errPos << info.traces.size(); @@ -426,11 +426,10 @@ Error readError(Source & source) auto type = readString(source); assert(type == "Error"); auto level = (Verbosity) readInt(source); - auto name = readString(source); + auto name = readString(source); // removed auto msg = readString(source); ErrorInfo info { .level = level, - .name = name, .msg = hintformat(std::move(format("%s") % msg)), }; auto havePos = readNum<size_t>(source); From 8bd9ebf52c9f7c5381d071250660a48d133b5e87 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Wed, 6 Apr 2022 15:05:08 +0200 Subject: [PATCH 111/171] Error: Remove unused sname() method --- src/libstore/filetransfer.hh | 2 -- src/libutil/error.hh | 5 ----- src/libutil/experimental-features.hh | 4 ---- 3 files changed, 11 deletions(-) diff --git a/src/libstore/filetransfer.hh b/src/libstore/filetransfer.hh index ca61e3937..1ad96bc10 100644 --- a/src/libstore/filetransfer.hh +++ b/src/libstore/filetransfer.hh @@ -123,8 +123,6 @@ public: template<typename... Args> FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args); - - virtual const char* sname() const override { return "FileTransferError"; } }; bool isUri(std::string_view s); diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 005c2c225..348018f57 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -161,8 +161,6 @@ public: : err(e) { } - virtual const char* sname() const { return "BaseError"; } - #ifdef EXCEPTION_NEEDS_THROW_SPEC ~BaseError() throw () { }; const char * what() const throw () { return calcWhat().c_str(); } @@ -189,7 +187,6 @@ public: { \ public: \ using superClass::superClass; \ - virtual const char* sname() const override { return #newClass; } \ } MakeError(Error, BaseError); @@ -209,8 +206,6 @@ public: auto hf = hintfmt(args...); err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo)); } - - virtual const char* sname() const override { return "SysError"; } }; } diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index 3a254b423..266e41a22 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -49,10 +49,6 @@ public: ExperimentalFeature missingFeature; MissingExperimentalFeature(ExperimentalFeature); - virtual const char * sname() const override - { - return "MissingExperimentalFeature"; - } }; } From c68963eaea7005b5bfb954e50fefb36c36084414 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Fri, 8 Apr 2022 11:48:30 +0200 Subject: [PATCH 112/171] Remove duplicate "error:" --- src/libstore/build-result.hh | 2 ++ src/libstore/build/derivation-goal.cc | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libstore/build-result.hh b/src/libstore/build-result.hh index 7f9bcce6d..24fb1f763 100644 --- a/src/libstore/build-result.hh +++ b/src/libstore/build-result.hh @@ -31,6 +31,8 @@ struct BuildResult ResolvesToAlreadyValid, NoSubstituters, } status = MiscFailure; + + // FIXME: include entire ErrorInfo object. std::string errorMsg; std::string toString() const { diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 6582497bd..53f212c1d 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -1371,8 +1371,7 @@ void DerivationGoal::done( { buildResult.status = status; if (ex) - // FIXME: strip: "error: " - buildResult.errorMsg = ex->what(); + buildResult.errorMsg = fmt("%s", normaltxt(ex->info().msg)); if (buildResult.status == BuildResult::TimedOut) worker.timedOut = true; if (buildResult.status == BuildResult::PermanentFailure) From f3d3587ab39130e8d87d290b8cedabb3e6e9d715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <theophane@hufschmitt.net> Date: Thu, 7 Apr 2022 21:09:39 +0200 Subject: [PATCH 113/171] Allow empty path segments in urls Valid per https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 (and also somewhat frequently happening for local paths) --- src/libutil/tests/url.cc | 4 ++-- src/libutil/url-parts.hh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libutil/tests/url.cc b/src/libutil/tests/url.cc index f20e2dc41..c3b233797 100644 --- a/src/libutil/tests/url.cc +++ b/src/libutil/tests/url.cc @@ -178,7 +178,7 @@ namespace nix { } TEST(parseURL, parseFileURLWithQueryAndFragment) { - auto s = "file:///none/of/your/business"; + auto s = "file:///none/of//your/business"; auto parsed = parseURL(s); ParsedURL expected { @@ -186,7 +186,7 @@ namespace nix { .base = "", .scheme = "file", .authority = "", - .path = "/none/of/your/business", + .path = "/none/of//your/business", .query = (StringMap) { }, .fragment = "", }; diff --git a/src/libutil/url-parts.hh b/src/libutil/url-parts.hh index da10a6bbc..d5e6a2736 100644 --- a/src/libutil/url-parts.hh +++ b/src/libutil/url-parts.hh @@ -18,7 +18,7 @@ const static std::string userRegex = "(?:(?:" + unreservedRegex + "|" + pctEncod const static std::string authorityRegex = "(?:" + userRegex + "@)?" + hostRegex + "(?::[0-9]+)?"; const static std::string pcharRegex = "(?:" + unreservedRegex + "|" + pctEncoded + "|" + subdelimsRegex + "|[:@])"; const static std::string queryRegex = "(?:" + pcharRegex + "|[/? \"])*"; -const static std::string segmentRegex = "(?:" + pcharRegex + "+)"; +const static std::string segmentRegex = "(?:" + pcharRegex + "*)"; const static std::string absPathRegex = "(?:(?:/" + segmentRegex + ")*/?)"; const static std::string pathRegex = "(?:" + segmentRegex + "(?:/" + segmentRegex + ")*/?)"; From 770f7371f33a56f7214001981a698113477ccec4 Mon Sep 17 00:00:00 2001 From: Daniel Pauls <daniel1708.de+git@gmail.com> Date: Sat, 9 Apr 2022 17:00:14 +0200 Subject: [PATCH 114/171] libfetchers: Replace regex to clarify intent --- src/libfetchers/git.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index f8433bc28..91dd332a2 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -28,9 +28,7 @@ static std::string readHead(const Path & path) static bool isNotDotGitDirectory(const Path & path) { - static const std::regex gitDirRegex("^(?:.*/)?\\.git$"); - - return not std::regex_match(path, gitDirRegex); + return baseNameOf(path) != ".git"; } struct GitInputScheme : InputScheme From d6b75295797af332f2cba635531b2019571319e2 Mon Sep 17 00:00:00 2001 From: Daniel Pauls <daniel1708.de+git@gmail.com> Date: Sat, 9 Apr 2022 19:10:23 +0200 Subject: [PATCH 115/171] libfetchers: Fix assertion (Mercurial) See commit 1e1cd6e7a for more information. --- src/libfetchers/mercurial.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 8b82e9daa..51cf35bf4 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -178,9 +178,11 @@ struct MercurialInputScheme : InputScheme auto files = tokenizeString<std::set<std::string>>( runHg({ "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s); + Path actualPath(absPath(actualUrl)); + PathFilter filter = [&](const Path & p) -> bool { - assert(hasPrefix(p, actualUrl)); - std::string file(p, actualUrl.size() + 1); + assert(hasPrefix(p, actualPath)); + std::string file(p, actualPath.size() + 1); auto st = lstat(p); @@ -193,7 +195,7 @@ struct MercurialInputScheme : InputScheme return files.count(file); }; - auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter); + auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter); return {std::move(storePath), input}; } From 38125a47ab512446dd78d3d0f1ed2d52e1d9cbd2 Mon Sep 17 00:00:00 2001 From: Daniel Pauls <daniel1708.de+git@gmail.com> Date: Sat, 9 Apr 2022 23:39:00 +0200 Subject: [PATCH 116/171] Test fetchMercurial with path containing a `.` segment --- tests/fetchMercurial.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/fetchMercurial.sh b/tests/fetchMercurial.sh index 726840664..5c64ffd26 100644 --- a/tests/fetchMercurial.sh +++ b/tests/fetchMercurial.sh @@ -7,7 +7,9 @@ fi clearStore -repo=$TEST_ROOT/hg +# Intentionally not in a canonical form +# See https://github.com/NixOS/nix/issues/6195 +repo=$TEST_ROOT/./hg rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix @@ -28,6 +30,12 @@ echo world > $repo/hello hg commit --cwd $repo -m 'Bla2' rev2=$(hg log --cwd $repo -r tip --template '{node}') +# Fetch an unclean branch. +echo unclean > $repo/hello +path=$(nix eval --impure --raw --expr "(builtins.fetchMercurial file://$repo).outPath") +[[ $(cat $path/hello) = unclean ]] +hg revert --cwd $repo --all + # Fetch the default branch. path=$(nix eval --impure --raw --expr "(builtins.fetchMercurial file://$repo).outPath") [[ $(cat $path/hello) = world ]] From 63d9a818194f940fcd2da8fb68bef303c984f300 Mon Sep 17 00:00:00 2001 From: Sebastian Blunt <nix@sebastianblunt.com> Date: Sun, 10 Apr 2022 21:09:04 -0700 Subject: [PATCH 117/171] Log builder args and environment variables Previously it only logged the builder's path, this changes it to log the arguments at the same log level, and the environment variables at the vomit level. This helped me debug https://github.com/svanderburg/node2nix/issues/75 --- src/libstore/build/local-derivation-goal.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 40ef706a6..4c91fa4fb 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -704,6 +704,9 @@ void LocalDerivationGoal::startBuilder() /* Run the builder. */ printMsg(lvlChatty, "executing builder '%1%'", drv->builder); + printMsg(lvlChatty, "using builder args '%1%'", concatStringsSep(" ", drv->args)); + for (auto & i : drv->env) + printMsg(lvlVomit, "setting builder env variable '%1%'='%2%'", i.first, i.second); /* Create the log file. */ Path logFile = openLogFile(); From 5fc73c276b369aee13185ae6c9c8faed35490460 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 22:01:20 +0000 Subject: [PATCH 118/171] build(deps): bump cachix/install-nix-action from 16 to 17 Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 16 to 17. - [Release notes](https://github.com/cachix/install-nix-action/releases) - [Commits](https://github.com/cachix/install-nix-action/compare/v16...v17) --- updated-dependencies: - dependency-name: cachix/install-nix-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09436b7e3..4ecb050dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v2.4.0 with: fetch-depth: 0 - - uses: cachix/install-nix-action@v16 + - uses: cachix/install-nix-action@v17 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - uses: cachix/cachix-action@v10 if: needs.check_cachix.outputs.secret == 'true' @@ -50,7 +50,7 @@ jobs: with: fetch-depth: 0 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - - uses: cachix/install-nix-action@v16 + - uses: cachix/install-nix-action@v17 - uses: cachix/cachix-action@v10 with: name: '${{ env.CACHIX_NAME }}' @@ -69,7 +69,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - - uses: cachix/install-nix-action@v16 + - uses: cachix/install-nix-action@v17 with: install_url: '${{needs.installer.outputs.installerURL}}' install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve" @@ -86,7 +86,7 @@ jobs: - uses: actions/checkout@v2.4.0 with: fetch-depth: 0 - - uses: cachix/install-nix-action@v16 + - uses: cachix/install-nix-action@v17 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - run: echo NIX_VERSION="$(nix-instantiate --eval -E '(import ./default.nix).defaultPackage.${builtins.currentSystem}.version' | tr -d \")" >> $GITHUB_ENV - uses: cachix/cachix-action@v10 From 2769e43f61d827e1b8fe46257450986d76319243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Ga=C5=82kowski?= <kpg@posteo.net> Date: Fri, 8 Apr 2022 19:38:43 +0200 Subject: [PATCH 119/171] assert hash types for Git and Mercurial --- src/libfetchers/git.cc | 8 ++++++++ src/libfetchers/mercurial.cc | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index d75c5d3ae..220442479 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -189,8 +189,16 @@ struct GitInputScheme : InputScheme if (submodules) cacheType += "-submodules"; if (allRefs) cacheType += "-all-refs"; + auto checkHashType = [&](const std::optional<Hash> & hash) + { + if (hash.has_value() && !(hash->type == htSHA1 || hash->type == htSHA256)) + throw Error("Hash '%s' is not supported by Git. Supported types are sha1 and sha256.", hash->to_string(Base16, true)); + }; + auto getLockedAttrs = [&]() { + checkHashType(input.getRev()); + return Attrs({ {"type", cacheType}, {"name", name}, diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 8b82e9daa..19ff98030 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -201,8 +201,17 @@ struct MercurialInputScheme : InputScheme if (!input.getRef()) input.attrs.insert_or_assign("ref", "default"); + auto checkHashType = [&](const std::optional<Hash> & hash) + { + if (hash.has_value() && hash->type != htSHA1) + throw Error("Hash '%s' is not supported by Mercurial. Only sha1 is supported.", hash->to_string(Base16, true)); + }; + + auto getLockedAttrs = [&]() { + checkHashType(input.getRev()); + return Attrs({ {"type", "hg"}, {"name", name}, From dc9510c8d73e5180172359a577fb96d01bbada1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:10:29 +0000 Subject: [PATCH 120/171] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/backport.yml | 2 +- .github/workflows/ci.yml | 8 ++++---- .github/workflows/hydra_status.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index ec7ab4516..dd481160f 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -8,7 +8,7 @@ jobs: if: github.repository_owner == 'NixOS' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name)) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} # required to find all branches diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ecb050dd..d01ef4768 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: cachix/install-nix-action@v17 @@ -46,7 +46,7 @@ jobs: outputs: installerURL: ${{ steps.prepare-installer.outputs.installerURL }} steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 with: fetch-depth: 0 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV @@ -67,7 +67,7 @@ jobs: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - uses: cachix/install-nix-action@v17 with: @@ -83,7 +83,7 @@ jobs: needs.check_cachix.outputs.secret == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: cachix/install-nix-action@v17 diff --git a/.github/workflows/hydra_status.yml b/.github/workflows/hydra_status.yml index b97076bd7..53e69cb2d 100644 --- a/.github/workflows/hydra_status.yml +++ b/.github/workflows/hydra_status.yml @@ -9,7 +9,7 @@ jobs: if: github.repository_owner == 'NixOS' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 with: fetch-depth: 0 - run: bash scripts/check-hydra-status.sh From d89840b103e57e81e5245c3fe9edfbf7c3477ad5 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 14 Apr 2022 14:04:19 +0200 Subject: [PATCH 121/171] Make InstallableFlake::toValue() and toDerivation() behave consistently In particular, this means that 'nix eval` (which uses toValue()) no longer auto-calls functions or functors (because AttrCursor::findAlongAttrPath() doesn't). Fixes #6152. Also use ref<> in a few places, and don't return attrpaths from getCursor() because cursors already have a getAttrPath() method. --- src/libcmd/installables.cc | 117 +++++++++++++++++-------------------- src/libcmd/installables.hh | 12 +++- src/libexpr/eval-cache.cc | 4 +- src/libexpr/eval-cache.hh | 4 +- src/nix/app.cc | 4 +- src/nix/flake.cc | 2 +- src/nix/search.cc | 4 +- 7 files changed, 72 insertions(+), 75 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 955bbe6fb..1bb5d6300 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -334,16 +334,16 @@ DerivedPath Installable::toDerivedPath() return std::move(buildables[0]); } -std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> +std::vector<ref<eval_cache::AttrCursor>> Installable::getCursors(EvalState & state) { auto evalCache = std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state, [&]() { return toValue(state).first; }); - return {{evalCache->getRoot(), ""}}; + return {evalCache->getRoot()}; } -std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string> +ref<eval_cache::AttrCursor> Installable::getCursor(EvalState & state) { auto cursors = getCursors(state); @@ -566,43 +566,21 @@ InstallableFlake::InstallableFlake( std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation() { - auto lockedFlake = getLockedFlake(); + auto attr = getCursor(*state); - auto cache = openEvalCache(*state, lockedFlake); - auto root = cache->getRoot(); + auto attrPath = attr->getAttrPathStr(); - Suggestions suggestions; + if (!attr->isDerivation()) + throw Error("flake output attribute '%s' is not a derivation", attrPath); - for (auto & attrPath : getActualAttrPaths()) { - debug("trying flake output attribute '%s'", attrPath); + auto drvPath = attr->forceDerivation(); - auto attrOrSuggestions = root->findAlongAttrPath( - parseAttrPath(*state, attrPath), - true - ); + auto drvInfo = DerivationInfo { + std::move(drvPath), + attr->getAttr(state->sOutputName)->getString() + }; - if (!attrOrSuggestions) { - suggestions += attrOrSuggestions.getSuggestions(); - continue; - } - - auto attr = *attrOrSuggestions; - - if (!attr->isDerivation()) - throw Error("flake output attribute '%s' is not a derivation", attrPath); - - auto drvPath = attr->forceDerivation(); - - auto drvInfo = DerivationInfo { - std::move(drvPath), - attr->getAttr(state->sOutputName)->getString() - }; - - return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)}; - } - - throw Error(suggestions, "flake '%s' does not provide attribute %s", - flakeRef, showAttrPaths(getActualAttrPaths())); + return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)}; } std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations() @@ -614,33 +592,10 @@ std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations() std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state) { - auto lockedFlake = getLockedFlake(); - - auto vOutputs = getFlakeOutputs(state, *lockedFlake); - - auto emptyArgs = state.allocBindings(0); - - Suggestions suggestions; - - for (auto & attrPath : getActualAttrPaths()) { - try { - auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs); - state.forceValue(*v, pos); - return {v, pos}; - } catch (AttrPathNotFound & e) { - suggestions += e.info().suggestions; - } - } - - throw Error( - suggestions, - "flake '%s' does not provide attribute %s", - flakeRef, - showAttrPaths(getActualAttrPaths()) - ); + return {&getCursor(state)->forceValue(), noPos}; } -std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> +std::vector<ref<eval_cache::AttrCursor>> InstallableFlake::getCursors(EvalState & state) { auto evalCache = openEvalCache(state, @@ -648,21 +603,55 @@ InstallableFlake::getCursors(EvalState & state) auto root = evalCache->getRoot(); - std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> res; + std::vector<ref<eval_cache::AttrCursor>> res; for (auto & attrPath : getActualAttrPaths()) { auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath)); - if (attr) res.push_back({*attr, attrPath}); + if (attr) res.push_back(ref(*attr)); } return res; } +ref<eval_cache::AttrCursor> InstallableFlake::getCursor(EvalState & state) +{ + auto lockedFlake = getLockedFlake(); + + auto cache = openEvalCache(state, lockedFlake); + auto root = cache->getRoot(); + + Suggestions suggestions; + + auto attrPaths = getActualAttrPaths(); + + for (auto & attrPath : attrPaths) { + debug("trying flake output attribute '%s'", attrPath); + + auto attrOrSuggestions = root->findAlongAttrPath( + parseAttrPath(state, attrPath), + true + ); + + if (!attrOrSuggestions) { + suggestions += attrOrSuggestions.getSuggestions(); + continue; + } + + return *attrOrSuggestions; + } + + throw Error( + suggestions, + "flake '%s' does not provide attribute %s", + flakeRef, + showAttrPaths(attrPaths)); +} + std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const { - flake::LockFlags lockFlagsApplyConfig = lockFlags; - lockFlagsApplyConfig.applyNixConfig = true; if (!_lockedFlake) { + flake::LockFlags lockFlagsApplyConfig = lockFlags; + lockFlagsApplyConfig.applyNixConfig = true; _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig)); } return _lockedFlake; diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index f4bf0d406..b847f8939 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -80,10 +80,10 @@ struct Installable return {}; } - virtual std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> + virtual std::vector<ref<eval_cache::AttrCursor>> getCursors(EvalState & state); - std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string> + virtual ref<eval_cache::AttrCursor> getCursor(EvalState & state); virtual FlakeRef nixpkgsFlakeRef() const @@ -180,9 +180,15 @@ struct InstallableFlake : InstallableValue std::pair<Value *, Pos> toValue(EvalState & state) override; - std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> + /* Get a cursor to every attrpath in getActualAttrPaths() that + exists. */ + std::vector<ref<eval_cache::AttrCursor>> getCursors(EvalState & state) override; + /* Get a cursor to the first attrpath in getActualAttrPaths() that + exists, or throw an exception with suggestions if none exists. */ + ref<eval_cache::AttrCursor> getCursor(EvalState & state) override; + std::shared_ptr<flake::LockedFlake> getLockedFlake() const; FlakeRef nixpkgsFlakeRef() const override; diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 54fa9b741..7d3fd01a4 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -306,9 +306,9 @@ Value * EvalCache::getRootValue() return *value; } -std::shared_ptr<AttrCursor> EvalCache::getRoot() +ref<AttrCursor> EvalCache::getRoot() { - return std::make_shared<AttrCursor>(ref(shared_from_this()), std::nullopt); + return make_ref<AttrCursor>(ref(shared_from_this()), std::nullopt); } AttrCursor::AttrCursor( diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh index c9a9bf471..b0709ebc2 100644 --- a/src/libexpr/eval-cache.hh +++ b/src/libexpr/eval-cache.hh @@ -33,7 +33,7 @@ public: EvalState & state, RootLoader rootLoader); - std::shared_ptr<AttrCursor> getRoot(); + ref<AttrCursor> getRoot(); }; enum AttrType { @@ -104,6 +104,8 @@ public: ref<AttrCursor> getAttr(std::string_view name); + /* Get an attribute along a chain of attrsets. Note that this does + not auto-call functors or functions. */ OrSuggestions<ref<AttrCursor>> findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force = false); std::string getString(); diff --git a/src/nix/app.cc b/src/nix/app.cc index 803d028f0..55efccdee 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -61,7 +61,7 @@ std::string resolveString(Store & store, const std::string & toResolve, const Bu UnresolvedApp Installable::toApp(EvalState & state) { - auto [cursor, attrPath] = getCursor(state); + auto cursor = getCursor(state); auto type = cursor->getAttr("type")->getString(); @@ -101,7 +101,7 @@ UnresolvedApp Installable::toApp(EvalState & state) } else - throw Error("attribute '%s' has unsupported type '%s'", attrPath, type); + throw Error("attribute '%s' has unsupported type '%s'", cursor->getAttrPathStr(), type); } // FIXME: move to libcmd diff --git a/src/nix/flake.cc b/src/nix/flake.cc index a876bb3af..66c315e5a 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -705,7 +705,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand defaultTemplateAttrPathsPrefixes, lockFlags); - auto [cursor, attrPath] = installable.getCursor(*evalState); + auto cursor = installable.getCursor(*evalState); auto templateDirAttr = cursor->getAttr("path"); auto templateDir = templateDirAttr->getString(); diff --git a/src/nix/search.cc b/src/nix/search.cc index e9307342c..e96a85ea2 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -165,8 +165,8 @@ struct CmdSearch : InstallableCommand, MixJSON } }; - for (auto & [cursor, prefix] : installable->getCursors(*state)) - visit(*cursor, parseAttrPath(*state, prefix), true); + for (auto & cursor : installable->getCursors(*state)) + visit(*cursor, cursor->getAttrPath(), true); if (!json && !results) throw Error("no results for the given search term(s)!"); From aa3927f0f19a654a80166aa007b7f830fc3536b8 Mon Sep 17 00:00:00 2001 From: Jairo Llopis <Yajo@users.noreply.github.com> Date: Thu, 14 Apr 2022 13:49:47 +0100 Subject: [PATCH 122/171] 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 9b41239d8fdcc3fe50febe718c15833ebc224354 Mon Sep 17 00:00:00 2001 From: Tom Bereknyei <tomberek@gmail.com> Date: Fri, 25 Mar 2022 13:36:41 -0400 Subject: [PATCH 123/171] fix: ensure apps are apps and packages are packages --- doc/manual/src/release-notes/rl-next.md | 4 ++++ src/nix/app.cc | 9 ++++++++ tests/flakes-run.sh | 29 +++++++++++++++++++++++++ tests/local.mk | 1 + 4 files changed, 43 insertions(+) create mode 100644 tests/flakes-run.sh diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 8c8c0fd41..6ebbe5eb4 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -40,3 +40,7 @@ As before, the old output will continue to work, but `nix flake check` will issue a warning about it. + +* `nix run` is now stricter wrt what it accepts: + * Members of `apps` are now required to be apps (as defined in [the manual](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-run.html#apps)) + * Member of `packages` or `legacyPackages` cannot be of type "app" when used by `nix run`. diff --git a/src/nix/app.cc b/src/nix/app.cc index 803d028f0..a43566496 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -65,6 +65,15 @@ UnresolvedApp Installable::toApp(EvalState & state) auto type = cursor->getAttr("type")->getString(); + std::string expected; + if (hasPrefix(attrPath,"apps.")) { + expected = "app"; + } else { + expected = "derivation"; + } + if (type != expected) { + throw Error("Attribute '%s' should have type '%s'.", attrPath, expected); + } if (type == "app") { auto [program, context] = cursor->getAttr("program")->getStringWithContext(); diff --git a/tests/flakes-run.sh b/tests/flakes-run.sh new file mode 100644 index 000000000..c8035431c --- /dev/null +++ b/tests/flakes-run.sh @@ -0,0 +1,29 @@ +source common.sh + +clearStore +rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local +cp ./shell-hello.nix ./config.nix $TEST_HOME +cd $TEST_HOME + +cat <<EOF > flake.nix +{ + outputs = {self}: { + packages.$system.PkgAsPkg = (import ./shell-hello.nix).hello; + packages.$system.AppAsApp = self.packages.$system.AppAsApp; + + apps.$system.PkgAsApp = self.packages.$system.PkgAsPkg; + apps.$system.AppAsApp = { + type = "app"; + program = "\${(import ./shell-hello.nix).hello}/bin/hello"; + }; + }; +} +EOF +nix run --no-write-lock-file .#AppAsApp +nix run --no-write-lock-file .#PkgAsPkg + +! nix run --no-write-lock-file .#PkgAsApp || fail "'nix run' shouldn’t accept an 'app' defined under 'packages'" +! nix run --no-write-lock-file .#AppAsPkg || fail "elements of 'apps' should be of type 'app'" + +clearStore + diff --git a/tests/local.mk b/tests/local.mk index 668b34500..51536188c 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -1,5 +1,6 @@ nix_tests = \ flakes.sh \ + flakes-run.sh \ ca/gc.sh \ gc.sh \ remote-store.sh \ From 25c85f5a0e838bf34a20804e06ce7e6574bdfd74 Mon Sep 17 00:00:00 2001 From: Alex Ameen <alex.ameen.tx@gmail.com> Date: Sun, 17 Apr 2022 17:14:38 -0500 Subject: [PATCH 124/171] doc: document nix.conf connect-timeout default --- src/libstore/filetransfer.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/filetransfer.hh b/src/libstore/filetransfer.hh index 1ad96bc10..fcdf1c9d1 100644 --- a/src/libstore/filetransfer.hh +++ b/src/libstore/filetransfer.hh @@ -31,7 +31,7 @@ struct FileTransferSettings : Config R"( The timeout (in seconds) for establishing connections in the binary cache substituter. It corresponds to `curl`’s - `--connect-timeout` option. + `--connect-timeout` option. The default 0 means no limit. )"}; Setting<unsigned long> stalledDownloadTimeout{ From e5c934cd48c25eff28c956e414af9b5ff1c6523a Mon Sep 17 00:00:00 2001 From: Alex Ameen <alex.ameen.tx@gmail.com> Date: Sun, 17 Apr 2022 18:17:37 -0500 Subject: [PATCH 125/171] doc: rephrase connect-timeout help message Co-authored-by: Cole Helbling <cole.e.helbling@outlook.com> --- src/libstore/filetransfer.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/filetransfer.hh b/src/libstore/filetransfer.hh index fcdf1c9d1..40e7cf52c 100644 --- a/src/libstore/filetransfer.hh +++ b/src/libstore/filetransfer.hh @@ -31,7 +31,7 @@ struct FileTransferSettings : Config R"( The timeout (in seconds) for establishing connections in the binary cache substituter. It corresponds to `curl`’s - `--connect-timeout` option. The default 0 means no limit. + `--connect-timeout` option. A value of 0 means no limit. )"}; Setting<unsigned long> stalledDownloadTimeout{ From 8b659eacce046326fa510f83b505b0ad326e60ef Mon Sep 17 00:00:00 2001 From: Robert Helgesson <robert@rycee.net> Date: Mon, 18 Apr 2022 17:14:15 +0200 Subject: [PATCH 126/171] Add .tgz as tarball extension in documentation Support for the `tgz` shorthand was added in 52f5fa948a4784b6a9b707770f4beee6a8674dee. --- src/nix/flake.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nix/flake.md b/src/nix/flake.md index d59915eeb..7d179a6c4 100644 --- a/src/nix/flake.md +++ b/src/nix/flake.md @@ -177,8 +177,8 @@ Currently the `type` attribute can be one of the following: attribute `url`. In URL form, the schema must be `http://`, `https://` or `file://` - URLs and the extension must be `.zip`, `.tar`, `.tar.gz`, `.tar.xz`, - `.tar.bz2` or `.tar.zst`. + URLs and the extension must be `.zip`, `.tar`, `.tgz`, `.tar.gz`, + `.tar.xz`, `.tar.bz2` or `.tar.zst`. * `github`: A more efficient way to fetch repositories from GitHub. The following attributes are required: From 75b62e52600a44b42693944b50638bf580a2c86e Mon Sep 17 00:00:00 2001 From: John Ericson <John.Ericson@Obsidian.Systems> Date: Thu, 18 Jun 2020 17:54:16 +0000 Subject: [PATCH 127/171] 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<std::string> 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> 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<typename... Args> +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<typename... Args> +[[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<void>([&]() { 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<typename... Args> + [[noreturn]] static void throw_(sqlite3 * db, const std::string & fs, const Args & ... args); + +protected: + template<typename... Args> + 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 2016b7142ad1282981ad1505085fff0ac9c7d66c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 19 Apr 2022 12:09:12 +0200 Subject: [PATCH 128/171] Fix compilation, style fixes --- src/nix/app.cc | 15 +++++---------- tests/flakes-run.sh | 16 ++++++++-------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/nix/app.cc b/src/nix/app.cc index bd6988066..6b6b31a12 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -62,22 +62,17 @@ std::string resolveString(Store & store, const std::string & toResolve, const Bu UnresolvedApp Installable::toApp(EvalState & state) { auto cursor = getCursor(state); + auto attrPath = cursor->getAttrPath(); auto type = cursor->getAttr("type")->getString(); - std::string expected; - if (hasPrefix(attrPath,"apps.")) { - expected = "app"; - } else { - expected = "derivation"; - } - if (type != expected) { - throw Error("Attribute '%s' should have type '%s'.", attrPath, expected); - } + std::string expected = !attrPath.empty() && attrPath[0] == "apps" ? "app" : "derivation"; + if (type != expected) + throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected); + if (type == "app") { auto [program, context] = cursor->getAttr("program")->getStringWithContext(); - std::vector<StorePathWithOutputs> context2; for (auto & [path, name] : context) context2.push_back({path, {name}}); diff --git a/tests/flakes-run.sh b/tests/flakes-run.sh index c8035431c..88fc3e628 100644 --- a/tests/flakes-run.sh +++ b/tests/flakes-run.sh @@ -8,22 +8,22 @@ cd $TEST_HOME cat <<EOF > flake.nix { outputs = {self}: { - packages.$system.PkgAsPkg = (import ./shell-hello.nix).hello; - packages.$system.AppAsApp = self.packages.$system.AppAsApp; + packages.$system.pkgAsPkg = (import ./shell-hello.nix).hello; + packages.$system.appAsApp = self.packages.$system.appAsApp; - apps.$system.PkgAsApp = self.packages.$system.PkgAsPkg; - apps.$system.AppAsApp = { + apps.$system.pkgAsApp = self.packages.$system.pkgAsPkg; + apps.$system.appAsApp = { type = "app"; program = "\${(import ./shell-hello.nix).hello}/bin/hello"; }; }; } EOF -nix run --no-write-lock-file .#AppAsApp -nix run --no-write-lock-file .#PkgAsPkg +nix run --no-write-lock-file .#appAsApp +nix run --no-write-lock-file .#pkgAsPkg -! nix run --no-write-lock-file .#PkgAsApp || fail "'nix run' shouldn’t accept an 'app' defined under 'packages'" -! nix run --no-write-lock-file .#AppAsPkg || fail "elements of 'apps' should be of type 'app'" +! nix run --no-write-lock-file .#pkgAsApp || fail "'nix run' shouldn’t accept an 'app' defined under 'packages'" +! nix run --no-write-lock-file .#appAsPkg || fail "elements of 'apps' should be of type 'app'" clearStore From c9e58aa5ff75351a5bb5af1258e019beef13721a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 19 Apr 2022 20:48:13 +0200 Subject: [PATCH 129/171] Require formatters to be packages Because of 9b41239d8fdcc3fe50febe718c15833ebc224354, a formatter can no longer be a package *or* an app. So let's require it to be a package for now. --- src/nix/app.cc | 4 ++-- src/nix/flake.cc | 3 +-- tests/fmt.sh | 10 ++++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/nix/app.cc b/src/nix/app.cc index 6b6b31a12..df7303e15 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -35,7 +35,7 @@ struct InstallableDerivedPath : Installable /** * Return the rewrites that are needed to resolve a string whose context is - * included in `dependencies` + * included in `dependencies`. */ StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies) { @@ -51,7 +51,7 @@ StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies) } /** - * Resolve the given string assuming the given context + * Resolve the given string assuming the given context. */ std::string resolveString(Store & store, const std::string & toResolve, const BuiltPaths dependencies) { diff --git a/src/nix/flake.cc b/src/nix/flake.cc index dbd157248..04b23ed0f 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -1038,7 +1038,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON } else if ( - (attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell")) + (attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell" || attrPath[0] == "formatter")) || (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages" || attrPath[0] == "devShells")) ) { @@ -1071,7 +1071,6 @@ struct CmdFlakeShow : FlakeCommand, MixJSON else if ( (attrPath.size() == 2 && attrPath[0] == "defaultApp") || - (attrPath.size() == 2 && attrPath[0] == "formatter") || (attrPath.size() == 3 && attrPath[0] == "apps")) { auto aType = visitor.maybeGetAttr("type"); diff --git a/tests/fmt.sh b/tests/fmt.sh index 7df1c82d3..2b482f14a 100644 --- a/tests/fmt.sh +++ b/tests/fmt.sh @@ -14,10 +14,12 @@ nix fmt --help | grep "Format" cat << EOF > flake.nix { outputs = _: { - formatter.$system = { - type = "app"; - program = ./fmt.simple.sh; - }; + formatter.$system = + with import ./config.nix; + mkDerivation { + name = "formatter"; + buildCommand = "mkdir -p \$out/bin; cp \${./fmt.simple.sh} \$out/bin/formatter"; + }; }; } EOF From 1cdad1074c42bb0b086f09b083db2f3c0f930b0e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 19 Apr 2022 21:12:33 +0200 Subject: [PATCH 130/171] Move rl-next.md to rl-2.8.md --- doc/manual/src/SUMMARY.md.in | 1 + doc/manual/src/release-notes/rl-2.8.md | 53 +++++++++++++++++++++++++ doc/manual/src/release-notes/rl-next.md | 45 --------------------- 3 files changed, 54 insertions(+), 45 deletions(-) create mode 100644 doc/manual/src/release-notes/rl-2.8.md diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in index f0f9457d2..860222337 100644 --- a/doc/manual/src/SUMMARY.md.in +++ b/doc/manual/src/SUMMARY.md.in @@ -72,6 +72,7 @@ - [CLI guideline](contributing/cli-guideline.md) - [Release Notes](release-notes/release-notes.md) - [Release X.Y (202?-??-??)](release-notes/rl-next.md) + - [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md) - [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md) - [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md) - [Release 2.5 (2021-12-13)](release-notes/rl-2.5.md) diff --git a/doc/manual/src/release-notes/rl-2.8.md b/doc/manual/src/release-notes/rl-2.8.md new file mode 100644 index 000000000..9778e8c3a --- /dev/null +++ b/doc/manual/src/release-notes/rl-2.8.md @@ -0,0 +1,53 @@ +# Release 2.8 (2022-04-19) + +* New experimental command: `nix fmt`, which applies a formatter + defined by the `formatter.<system>` flake output to the Nix + expressions in a flake. + +* Various Nix commands can now read expressions from standard input + using `--file -`. + +* New experimental builtin function `builtins.fetchClosure` that + copies a closure from a binary cache at evaluation time and rewrites + it to content-addressed form (if it isn't already). Like + `builtins.storePath`, this allows importing pre-built store paths; + the difference is that it doesn't require the user to configure + binary caches and trusted public keys. + + This function is only available if you enable the experimental + feature `fetch-closure`. + +* New experimental feature: *impure derivations*. These are + derivations that can produce a different result every time they're + built. Here is an example: + + ```nix + stdenv.mkDerivation { + name = "impure"; + __impure = true; # marks this derivation as impure + buildCommand = "date > $out"; + } + ``` + + Running `nix build` twice on this expression will build the + derivation twice, producing two different content-addressed store + paths. Like fixed-output derivations, impure derivations have access + to the network. Only fixed-output derivations and impure derivations + can depend on an impure derivation. + +* `nix store make-content-addressable` has been renamed to `nix store + make-content-addressed`. + +* The `nixosModule` flake output attribute has been renamed consistent + with the `.default` renames in Nix 2.7. + + * `nixosModule` → `nixosModules.default` + + As before, the old output will continue to work, but `nix flake check` will + issue a warning about it. + +* `nix run` is now stricter in what it accepts: members of the `apps` + flake output are now required to be apps (as defined in [the + manual](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-run.html#apps)), + and members of `packages` or `legacyPackages` must be derivations + (not apps). diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 6ebbe5eb4..c869b5e2f 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1,46 +1 @@ # Release X.Y (202?-??-??) - -* Various nix commands can now read expressions from stdin with `--file -`. - -* `nix store make-content-addressable` has been renamed to `nix store - make-content-addressed`. - -* New experimental builtin function `builtins.fetchClosure` that - copies a closure from a binary cache at evaluation time and rewrites - it to content-addressed form (if it isn't already). Like - `builtins.storePath`, this allows importing pre-built store paths; - the difference is that it doesn't require the user to configure - binary caches and trusted public keys. - - This function is only available if you enable the experimental - feature `fetch-closure`. - -* New experimental feature: *impure derivations*. These are - derivations that can produce a different result every time they're - built. Here is an example: - - ```nix - stdenv.mkDerivation { - name = "impure"; - __impure = true; # marks this derivation as impure - buildCommand = "date > $out"; - } - ``` - - Running `nix build` twice on this expression will build the - derivation twice, producing two different content-addressed store - paths. Like fixed-output derivations, impure derivations have access - to the network. Only fixed-output derivations and impure derivations - can depend on an impure derivation. - -* The `nixosModule` flake output attribute has been renamed consistent - with the `.default` renames in nix 2.7. - - * `nixosModule` → `nixosModules.default` - - As before, the old output will continue to work, but `nix flake check` will - issue a warning about it. - -* `nix run` is now stricter wrt what it accepts: - * Members of `apps` are now required to be apps (as defined in [the manual](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-run.html#apps)) - * Member of `packages` or `legacyPackages` cannot be of type "app" when used by `nix run`. From a3c843e6357be9d1cf84c1e030b8e5169815d827 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 19 Apr 2022 21:47:13 +0200 Subject: [PATCH 131/171] Fix 'nix fmt' test --- tests/fmt.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fmt.sh b/tests/fmt.sh index 2b482f14a..bc05118ff 100644 --- a/tests/fmt.sh +++ b/tests/fmt.sh @@ -25,6 +25,6 @@ cat << EOF > flake.nix EOF nix fmt ./file ./folder | grep 'Formatting: ./file ./folder' nix flake check -nix flake show | grep -P 'x86_64-linux|x86_64-darwin' +nix flake show | grep -P "package 'formatter'" clearStore From ee57f91413c9d01f1027eccbe01f7706c94919ac Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Tue, 19 Apr 2022 21:48:17 +0200 Subject: [PATCH 132/171] Bump version --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 6533b6687..f3ac133c5 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.8.0 \ No newline at end of file +2.9.0 \ No newline at end of file From 0e2b01b14e77dfb9a6f1748dec353273f91d1609 Mon Sep 17 00:00:00 2001 From: ckie <git-525ff67@ckie.dev> Date: Mon, 18 Apr 2022 20:21:47 +0300 Subject: [PATCH 133/171] 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 <nixpkgs> 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) << " <expr> Evaluate and print expression\n" << " <x> = <expr> Bind expression to variable\n" << " :a <expr> Add attributes from resulting set to scope\n" - << " :b <expr> Build derivation\n" + << " :b <expr> Build a derivation\n" + << " :bl <expr> Build a derivation, creating GC roots in the working directory\n" << " :e <expr> Open package or function in $EDITOR\n" << " :i <expr> Build derivation, then install result into current profile\n" << " :l <path> Load Nix expression and add it to scope\n" @@ -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<LocalFSStore>(); + if (localStore && command == ":bl") { + std::string symlink = "repl-result-" + outputName; + localStore->addPermRoot(outputPath, absPath(symlink)); + logger->cout(" ./%s -> %s", symlink, state->store->printStorePath(outputPath)); + } else { + logger->cout(" %s -> %s", outputName, state->store->printStorePath(outputPath)); + } + } } else if (command == ":i") { runNix("nix-env", {"-i", drvPathRaw}); } else if (command == ":log") { 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 <yorick@yorickvanpelt.nl> Date: Wed, 20 Apr 2022 15:41:01 +0200 Subject: [PATCH 134/171] 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 <Artturin@artturin.com> Date: Sat, 19 Mar 2022 20:16:05 +0200 Subject: [PATCH 135/171] 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 <nlohmann/json.hpp> @@ -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 <John.Ericson@Obsidian.Systems> Date: Wed, 20 Apr 2022 16:57:06 +0000 Subject: [PATCH 136/171] 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 <iostream> #include <algorithm> 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 <sqlite3.h> + +#include <atomic> + +namespace nix { + +template<typename... Args> +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<typename... Args> +[[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<typename... Args> -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<typename... Args> -[[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 <John.Ericson@Obsidian.Systems> Date: Wed, 20 Apr 2022 17:37:59 +0000 Subject: [PATCH 137/171] 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 <iostream> #include <algorithm> 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 <sqlite3.h> - -#include <atomic> - -namespace nix { - -template<typename... Args> -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<typename... Args> -[[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<typename... Args> - [[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<typename... Args> - 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?= <theophane.hufschmitt@tweag.io> Date: Thu, 21 Apr 2022 09:26:45 +0200 Subject: [PATCH 138/171] 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<std::pair<std::shared_ptr<Installable>, 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?= <theophane.hufschmitt@tweag.io> Date: Thu, 21 Apr 2022 09:40:55 +0200 Subject: [PATCH 139/171] 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<typename... Args> 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 <slyich@gmail.com> Date: Mon, 4 Oct 2021 09:00:01 +0100 Subject: [PATCH 140/171] 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 <rg@regnat.ovh> Date: Wed, 3 Nov 2021 16:30:22 +0100 Subject: [PATCH 141/171] 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 <slyich@gmail.com> Date: Sat, 26 Mar 2022 16:35:06 +0000 Subject: [PATCH 142/171] 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 <rg@regnat.ovh> Date: Wed, 3 Nov 2021 17:52:49 +0100 Subject: [PATCH 143/171] 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 <slyich@gmail.com> Date: Sat, 26 Mar 2022 16:18:51 +0000 Subject: [PATCH 144/171] 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 <rg@regnat.ovh> Date: Fri, 5 Nov 2021 11:17:22 +0100 Subject: [PATCH 145/171] 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 <edolstra@gmail.com> Date: Thu, 21 Apr 2022 11:58:40 +0200 Subject: [PATCH 146/171] Move hiliteMatches into a separate header This is mostly so that we don't #include <regex> 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 <boost/format.hpp> #include <string> -#include <regex> #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<std::smatch> 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 <regex> +#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 <regex> +#include <vector> +#include <string> + +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<std::smatch> 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 <regex> #include <fstream> From f1eee873ea064e8f94369bdfe2557c18ba35ccc9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 21 Apr 2022 13:00:24 +0200 Subject: [PATCH 147/171] 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 <gtest/gtest.h> -#include <regex> - namespace nix { /* ----------- tests for fmt.hh -------------------------------------------------*/ From 3b9d31b88c95e591c28f3a7423f83c40b9788781 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra <edolstra@gmail.com> Date: Thu, 21 Apr 2022 13:00:50 +0200 Subject: [PATCH 148/171] 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 <github@quasiparticle.net> Date: Sat, 5 Mar 2022 19:26:36 +0100 Subject: [PATCH 149/171] 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<std::string> args; const char * doc = nullptr; }; @@ -305,7 +305,7 @@ public: struct Doc { Pos pos; - std::optional<Symbol> name; + std::optional<std::string> name; size_t arity; std::vector<std::string> args; const char * doc; @@ -391,7 +391,7 @@ private: bool countCalls; - typedef std::map<Symbol, size_t> PrimOpCalls; + typedef std::map<std::string, size_t> PrimOpCalls; PrimOpCalls primOpCalls; typedef std::map<ExprLambda *, size_t> 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 <github@quasiparticle.net> Date: Sat, 5 Mar 2022 14:20:25 +0100 Subject: [PATCH 150/171] 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 <github@quasiparticle.net> Date: Fri, 4 Mar 2022 19:47:32 +0100 Subject: [PATCH 151/171] 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 <github@quasiparticle.net> Date: Fri, 4 Mar 2022 20:54:50 +0100 Subject: [PATCH 152/171] 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> store, std::vector<StorePath> && storePath run(store, *storePaths.begin()); } -Strings editorFor(const Pos & pos) +Strings editorFor(const Path & file, uint32_t line) { auto editor = getEnv("EDITOR").value_or("cat"); auto args = tokenizeString<Strings>(editor); - if (pos.line > 0 && ( + if (line > 0 && ( editor.find("emacs") != std::string::npos || editor.find("nano") != std::string::npos || editor.find("vim") != std::string::npos || editor.find("kak") != std::string::npos)) - args.push_back(fmt("+%d", pos.line)); - args.push_back(pos.file); + args.push_back(fmt("+%d", line)); + args.push_back(file); return args; } 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<std::string> && name) /* Helper function to generate args that invoke $EDITOR on filename:lineno. */ -Strings editorFor(const Pos & pos); +Strings editorFor(const Path & file, uint32_t line); struct MixProfile : virtual StoreCommand { 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<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string & } -Pos findPackageFilename(EvalState & state, Value & v, std::string what) +std::pair<std::string, uint32_t> 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<Value *, Pos> 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<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what); std::vector<Symbol> 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<std::string, uint32_t> { + 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 <github@quasiparticle.net> Date: Mon, 7 Mar 2022 21:02:17 +0100 Subject: [PATCH 153/171] 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::Doc> 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<std::string> 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 <github@quasiparticle.net> Date: Fri, 4 Mar 2022 19:31:59 +0100 Subject: [PATCH 154/171] 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<Pos> 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<Value *, Pos> toValue(EvalState & state) override + std::pair<Value *, PosIdx> toValue(EvalState & state) override { auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v); state.forceValue(*vRes, pos); @@ -613,7 +613,7 @@ std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations() return res; } -std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state) +std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state) { return {&getCursor(state)->forceValue(), noPos}; } 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<Value *, Pos> toValue(EvalState & state) + virtual std::pair<Value *, PosIdx> toValue(EvalState & state) { throw Error("argument '%s' cannot be evaluated", what()); } @@ -178,7 +178,7 @@ struct InstallableFlake : InstallableValue std::vector<DerivationInfo> toDerivations() override; - std::pair<Value *, Pos> toValue(EvalState & state) override; + std::pair<Value *, PosIdx> toValue(EvalState & state) override; /* Get a cursor to every attrpath in getActualAttrPaths() that exists. */ diff --git a/src/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<Symbol> parseAttrPath(EvalState & state, std::string_view s) } -std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string & attrPath, +std::pair<Value *, PosIdx> 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<Value *, Pos> 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<Value *, Pos> findAlongAttrPath( +std::pair<Value *, PosIdx> 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> 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> pos) } -Value & BindingsBuilder::alloc(std::string_view name, ptr<Pos> 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> pos; - Attr(Symbol name, Value * value, ptr<Pos> 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> 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> 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> pos = ptr(&noPos)); + Value & alloc(const Symbol & name, PosIdx pos = noPos); - Value & alloc(std::string_view name, ptr<Pos> 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::Doc> 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::Doc> 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> 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<Pos> 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<FunctionCallTrace>(pos) : nullptr; + auto trace = evalSettings.traceFunctionCalls + ? std::make_unique<FunctionCallTrace>(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<std::string> EvalState::tryAttrsToString(const Pos & pos, Value & v, +std::optional<std::string> 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<std::string> 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<bool, std::string> 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 <typename Callable> 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 <typename Callable> 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<std::string> tryAttrsToString(const Pos & pos, Value & v, + std::optional<std::string> 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> 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<Pos, size_t> AttrSelects; + typedef std::map<PosIdx, size_t> 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<fetchers::Tree, FlakeRef, FlakeRef> 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<FlakeId, FlakeInput> parseFlakeInputs( - EvalState & state, Value * value, const Pos & pos, + EvalState & state, Value * value, const PosIdx pos, const std::optional<Path> & 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<Path> & 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<FlakeId, FlakeInput> parseFlakeInputs( - EvalState & state, Value * value, const Pos & pos, + EvalState & state, Value * value, const PosIdx pos, const std::optional<Path> & baseDir, InputPath lockRootPath) { std::map<FlakeId, FlakeInput> inputs; @@ -180,7 +180,7 @@ static std::map<FlakeId, FlakeInput> 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<bool> { state.forceBool(*setting.value, *setting.pos) }}); + flake.config.settings.insert({setting.name, Explicit<bool> { state.forceBool(*setting.value, setting.pos) }}); else if (setting.value->type() == nList) { std::vector<std::string> 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<StorePath> 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; } <INPATH_SLASH><<EOF>> { 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 <map> +#include <vector> + #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<uint32_t>::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<Origin> origins; + ChunkedVector<Offset, 8192> 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<DynamicAttrDef> 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<Expr *> args; - Pos pos; - ExprCall(const Pos & pos, Expr * fun, std::vector<Expr *> && args) + PosIdx pos; + ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && 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<std::pair<Pos, Expr *> > * es; - ExprConcatStrings(const Pos & pos, bool forceString, std::vector<std::pair<Pos, Expr *> > * es) + std::vector<std::pair<PosIdx, Expr *> > * es; + ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *> > * 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<ErrorInfo> 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<ExprAttrs *>(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<std::pair<Symbol, Pos>> duplicate; + std::optional<std::pair<Symbol, PosIdx>> 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<std::pair<Pos, std::variant<Expr *, StringToken> > > & es) +static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols, + std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken> > > & 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<std::pair<Pos, Expr *> > * es2 = new std::vector<std::pair<Pos, Expr *> >; + auto * es2 = new std::vector<std::pair<PosIdx, Expr *> >; 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<nix::AttrName> * attrNames; - std::vector<std::pair<nix::Pos, nix::Expr *> > * string_parts; - std::vector<std::pair<nix::Pos, std::variant<nix::Expr *, StringToken> > > * ind_string_parts; + std::vector<std::pair<nix::PosIdx, nix::Expr *> > * string_parts; + std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, StringToken> > > * ind_string_parts; } %type <e> 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<std::pair<Pos, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } + { $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<PosIdx, Expr *> >({{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<std::pair<Pos, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); } + | DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); } | STR DOLLAR_CURLY expr '}' { - $$ = new std::vector<std::pair<Pos, Expr *> >; + $$ = new std::vector<std::pair<PosIdx, Expr *> >; $$->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<std::pair<Pos, std::variant<Expr *, StringToken> > >; } + | { $$ = new std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken> > >; } ; 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 <x> 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<HashType> 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<NixInt>::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<HashType> 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<RegexCache> makeRegexCache() return std::make_shared<RegexCache>(); } -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<std::string> 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<Hash> 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<std::string> 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<std::string> 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 <list> #include <set> #include <string> +#include <limits> #include <map> #include <variant> #include <vector> @@ -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<typename T, size_t ChunkSize> +class ChunkedVector { +private: + uint32_t size_ = 0; + std::vector<std::vector<T>> chunks; + + /* keep this out of the ::add hot path */ + [[gnu::noinline]] + auto & addChunk() + { + if (size_ >= std::numeric_limits<uint32_t>::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<T &, uint32_t> 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<void(Value & v, const Pos & pos, const Path & path)> recurse; + std::function<void(Value & v, const PosIdx pos, const Path & path)> 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<void(const std::string & name, Value & vProvide, const Pos & pos)> callback) + std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> 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<StorePath> { + 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<StorePath> { 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<DerivedPath> 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<void(const std::string & attrPath, Value & v, const Pos & pos)> checkHydraJobs; + std::function<void(const std::string & attrPath, Value & v, const PosIdx pos)> 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 <nixpkgs> {}).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 <github@quasiparticle.net> Date: Sat, 5 Mar 2022 17:31:50 +0100 Subject: [PATCH 155/171] 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 <github@quasiparticle.net> Date: Sat, 5 Mar 2022 14:40:24 +0100 Subject: [PATCH 156/171] 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<std::string, FlakeRef, InstallableValue::DerivationInfo> 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<Symbol> parseAttrPath(EvalState & state, std::string_view s) { std::vector<Symbol> 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<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin if (a == v->attrs->end()) { std::set<std::string> 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<const Attr *> lexicographicOrder() const + std::vector<const Attr *> lexicographicOrder(const SymbolTable & symbols) const { std::vector<const Attr *> 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<Symbol> 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> AttrCursor::maybeGetAttr(Symbol name, bool forceErrors) +std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name, bool forceErrors) { if (root->db) { if (!cachedValue) @@ -461,10 +461,10 @@ std::shared_ptr<AttrCursor> 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> AttrCursor::maybeGetAttr(Symbol name, bool forceErro root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2)); } -std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name) -{ - return maybeGetAttr(root->state.symbols.create(name)); -} - -ref<AttrCursor> AttrCursor::getAttr(Symbol name, bool forceErrors) +ref<AttrCursor> AttrCursor::getAttr(std::string_view name, bool forceErrors) { auto p = maybeGetAttr(name, forceErrors); if (!p) @@ -499,11 +494,6 @@ ref<AttrCursor> AttrCursor::getAttr(Symbol name, bool forceErrors) return ref(p); } -ref<AttrCursor> AttrCursor::getAttr(std::string_view name) -{ - return getAttr(root->state.symbols.create(name)); -} - OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force) { auto res = shared_from_this(); @@ -616,7 +606,7 @@ std::vector<Symbol> AttrCursor::getAttrs() std::vector<Symbol> 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<AttrCursor> maybeGetAttr(Symbol name, bool forceErrors = false); + std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name, bool forceErrors = false); - std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name); - - ref<AttrCursor> getAttr(Symbol name, bool forceErrors = false); - - ref<AttrCursor> getAttr(std::string_view name); + ref<AttrCursor> 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<const void *> * seen) const +void Value::print(const SymbolTable & symbols, std::ostream & str, + std::set<const void *> * seen) const { checkInterrupt(); @@ -129,9 +130,9 @@ void Value::print(std::ostream & str, std::set<const void *> * 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<const void *> * 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<const void *> * 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<const void *> 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<std::string> 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<std::string> 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<Path, StorePath> 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<std::string, std::string> 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<bool> { attr.value->boolean }); + attrs.emplace(state.symbols[attr.name], Explicit<bool> { 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<FlakeId, FlakeInput> 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<bool> { state.forceBool(*setting.value, setting.pos) }}); + flake.config.settings.emplace( + state.symbols[setting.name], + Explicit<bool> { state.forceBool(*setting.value, setting.pos) }); else if (setting.value->type() == nList) { std::vector<std::string> 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 <cstdlib> @@ -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<Attr> 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<AttrName> 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<Symbol, AttrDef> AttrDefs; + typedef std::map<SymbolIdx, AttrDef> 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<Formal> lexicographicOrder() const + std::vector<Formal> lexicographicOrder(const SymbolTable & symbols) const { std::vector<Formal> 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<std::pair<Symbol, Displacement>> Vars; + typedef std::vector<std::pair<SymbolIdx, Displacement>> 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<ExprAttrs *>(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<std::pair<Symbol, PosIdx>> duplicate; + std::optional<std::pair<SymbolIdx, PosIdx>> 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<Value *> 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<Symbol> seen; + std::set<SymbolIdx> 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<Symbol, std::pair<size_t, Value * *>> attrsSeen; + std::map<SymbolIdx, std::pair<size_t, Value * *>> 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<StorePath> 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<bool>{attr.value->boolean}); + attrs.emplace(state.symbols[attr.name], Explicit<bool>{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<std::string_view, Symbol> symbols; - std::list<std::string> store; + std::unordered_map<std::string_view, std::pair<const Symbol *, uint32_t>> symbols; + ChunkedVector<Symbol, 8192> 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<typename T> - 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<const void *> * seen) const; + void print(const SymbolTable & symbols, std::ostream & str, std::set<const void *> * 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<Value *, traceable_allocator<Value *> > ValueVector; -typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *> > > ValueMap; -typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector> > > ValueVectorMap; +typedef std::map<SymbolIdx, Value *, std::less<SymbolIdx>, traceable_allocator<std::pair<const SymbolIdx, Value *> > > ValueMap; +typedef std::map<SymbolIdx, ValueVector, std::less<SymbolIdx>, traceable_allocator<std::pair<const SymbolIdx, ValueVector> > > ValueVectorMap; #else typedef std::vector<Value *> ValueVector; -typedef std::map<Symbol, Value *> ValueMap; -typedef std::map<Symbol, ValueVector> ValueVectorMap; +typedef std::map<SymbolIdx, Value *> ValueMap; +typedef std::map<SymbolIdx, ValueVector> 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<typename Fn> + 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<std::string> 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<std::string, uint32_t> { 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<std::string, Value *> 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 <github@quasiparticle.net> Date: Tue, 8 Mar 2022 15:26:17 +0100 Subject: [PATCH 157/171] 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 <nixpkgs/nixos> {}; 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 <nixpkgs/nixos> {}; 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 <github@quasiparticle.net> Date: Tue, 5 Apr 2022 18:37:38 +0200 Subject: [PATCH 158/171] remove pos<T> 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<T>(p); } - -/* A non-nullable pointer. - This is similar to a C++ "& reference", but mutable. - This is similar to ref<T> but backed by a regular pointer instead of a smart pointer. - */ -template<typename T> -class ptr { -private: - T * p; - -public: - ptr<T>(const ptr<T> & r) - : p(r.p) - { } - - explicit ptr<T>(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<T> & other) const - { - return p == other.p; - } - - bool operator != (const ptr<T> & other) const - { - return p != other.p; - } -}; - } From f25112d3832b93a2bc8abe7936e6355dae9a25d5 Mon Sep 17 00:00:00 2001 From: Tom Bereknyei <tomberek@gmail.com> Date: Thu, 21 Apr 2022 16:41:37 -0400 Subject: [PATCH 159/171] 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?= <theophane.hufschmitt@tweag.io> Date: Fri, 22 Apr 2022 10:01:02 +0200 Subject: [PATCH 160/171] 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 <unordered_map> #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 <bits/stdint-uintn.h> +#include <cstdlib> +#include <vector> +#include <limits> + +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<typename T, size_t ChunkSize> +class ChunkedVector { +private: + uint32_t size_ = 0; + std::vector<std::vector<T>> chunks; + + /* keep this out of the ::add hot path */ + [[gnu::noinline]] + auto & addChunk() + { + if (size_ >= std::numeric_limits<uint32_t>::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<T &, uint32_t> 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<typename Fn> + 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<typename T, size_t ChunkSize> -class ChunkedVector { -private: - uint32_t size_ = 0; - std::vector<std::vector<T>> chunks; - - /* keep this out of the ::add hot path */ - [[gnu::noinline]] - auto & addChunk() - { - if (size_ >= std::numeric_limits<uint32_t>::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<T &, uint32_t> 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<typename Fn> - 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?= <theophane.hufschmitt@tweag.io> Date: Fri, 22 Apr 2022 10:01:10 +0200 Subject: [PATCH 161/171] 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 <gtest/gtest.h> + +namespace nix { + TEST(ChunkedVector, InitEmpty) { + auto v = ChunkedVector<int, 2>(100); + ASSERT_EQ(v.size(), 0); + } + + TEST(ChunkedVector, GrowsCorrectly) { + auto v = ChunkedVector<int, 2>(100); + for (auto i = 1; i < 20; i++) { + v.add(i); + ASSERT_EQ(v.size(), i); + } + } + + TEST(ChunkedVector, AddAndGet) { + auto v = ChunkedVector<int, 2>(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<int, 2>(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<int, 2>(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?= <theophane.hufschmitt@tweag.io> Date: Fri, 22 Apr 2022 10:56:01 +0200 Subject: [PATCH 162/171] 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 <bits/stdint-uintn.h> +#include <cstdint> #include <cstdlib> #include <vector> #include <limits> From 0256e5578e97a11db66207e1f8e231db115c91f8 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch <maximilian@mbosch.me> Date: Sat, 23 Apr 2022 23:04:35 +0200 Subject: [PATCH 163/171] 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<bool, std::string> 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 <maximilian@mbosch.me> Date: Sun, 24 Apr 2022 18:06:36 +0200 Subject: [PATCH 164/171] 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<bool, std::string> getActualUrl(const Input & input) const @@ -175,6 +185,7 @@ struct GitInputScheme : InputScheme std::pair<StorePath, Input> fetch(ref<Store> 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 <git@midchildan.org> Date: Mon, 25 Apr 2022 01:23:49 +0900 Subject: [PATCH 165/171] 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 <git@midchildan.org> Date: Mon, 25 Apr 2022 02:34:59 +0900 Subject: [PATCH 166/171] 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 <github@quasiparticle.net> Date: Fri, 22 Apr 2022 21:45:39 +0200 Subject: [PATCH 167/171] 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<Symbol> parseAttrPath(EvalState & state, std::string_view s) { std::vector<Symbol> 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<Symbol> & 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<Symbol> 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<Symbol> 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<std::string> 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> AttrCursor::maybeGetAttr(std::string_view name, bool forceErrors) @@ -428,11 +429,11 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name, bool if (cachedValue) { if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) { for (auto & attr : *attrs) - if (attr == name) - return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), name)); + if (root->state.symbols[attr] == name) + return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), attr)); return nullptr; } else if (std::get_if<placeholder_t>(&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<missing_t>(&attr->second)) return nullptr; @@ -440,10 +441,10 @@ std::shared_ptr<AttrCursor> 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<AttrCursor>(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> 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> 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<AttrCursor>( - 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> 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<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const std::vector<S { auto res = shared_from_this(); for (auto & attr : attrPath) { - auto child = res->maybeGetAttr(attr, force); + auto child = res->maybeGetAttr(root->state.symbols[attr], force); if (!child) { auto suggestions = res->getSuggestionsForAttr(attr); return OrSuggestions<ref<AttrCursor>>::failed(suggestions); @@ -606,13 +607,14 @@ std::vector<Symbol> AttrCursor::getAttrs() std::vector<Symbol> 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<AttrId, Symbol> AttrKey; +typedef std::pair<AttrId, std::string> AttrKey; typedef std::pair<std::string, NixStringContext> 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<SymbolIdx, AttrDef> AttrDefs; + typedef std::map<Symbol, AttrDef> 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<std::pair<SymbolIdx, Displacement>> Vars; + typedef std::vector<std::pair<Symbol, Displacement>> 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<std::pair<SymbolIdx, PosIdx>> duplicate; + std::optional<std::pair<Symbol, PosIdx>> 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<Value *> 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<SymbolIdx> seen; + std::set<Symbol> 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<SymbolIdx, std::pair<size_t, Value * *>> attrsSeen; + std::map<Symbol, std::pair<size_t, Value * *>> 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<std::string_view, std::pair<const Symbol *, uint32_t>> symbols; - ChunkedVector<Symbol, 8192> store{16}; + std::unordered_map<std::string_view, std::pair<const std::string *, uint32_t>> symbols; + ChunkedVector<std::string, 8192> 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<SymbolStr> resolve(const std::vector<Symbol> & symbols) const + { + std::vector<SymbolStr> 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<Value *, traceable_allocator<Value *> > ValueVector; -typedef std::map<SymbolIdx, Value *, std::less<SymbolIdx>, traceable_allocator<std::pair<const SymbolIdx, Value *> > > ValueMap; -typedef std::map<SymbolIdx, ValueVector, std::less<SymbolIdx>, traceable_allocator<std::pair<const SymbolIdx, ValueVector> > > ValueVectorMap; +typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *> > > ValueMap; +typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector> > > ValueVectorMap; #else typedef std::vector<Value *> ValueVector; -typedef std::map<SymbolIdx, Value *> ValueMap; -typedef std::map<SymbolIdx, ValueVector> ValueVectorMap; +typedef std::map<Symbol, Value *> ValueMap; +typedef std::map<Symbol, ValueVector> 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<Symbol> & 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<std::smatch> attrPathMatches; std::vector<std::smatch> 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 <edolstra@gmail.com> Date: Tue, 26 Apr 2022 13:23:32 +0200 Subject: [PATCH 168/171] 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 <edolstra@gmail.com> Date: Tue, 26 Apr 2022 14:01:21 +0200 Subject: [PATCH 169/171] 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<std::string, FlakeRef, InstallableValue::DerivationInfo> 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> AttrCursor::maybeGetAttr(std::string_view name, bool forceErrors) +std::shared_ptr<AttrCursor> 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> AttrCursor::maybeGetAttr(std::string_view name, bool if (cachedValue) { if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) { for (auto & attr : *attrs) - if (root->state.symbols[attr] == name) + if (attr == name) return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), attr)); return nullptr; } else if (std::get_if<placeholder_t>(&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<missing_t>(&attr->second)) return nullptr; @@ -441,10 +443,10 @@ std::shared_ptr<AttrCursor> 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<AttrCursor>(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> 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> 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<AttrCursor>( - 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> AttrCursor::getAttr(std::string_view name, bool forceErrors) +std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name) +{ + return maybeGetAttr(root->state.symbols.create(name)); +} + +ref<AttrCursor> 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> AttrCursor::getAttr(std::string_view name) +{ + return getAttr(root->state.symbols.create(name)); +} + OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & 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<ref<AttrCursor>>::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<AttrCursor> maybeGetAttr(std::string_view name, bool forceErrors = false); + std::shared_ptr<AttrCursor> maybeGetAttr(Symbol name, bool forceErrors = false); - ref<AttrCursor> getAttr(std::string_view name, bool forceErrors = false); + std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name); + + ref<AttrCursor> getAttr(Symbol name, bool forceErrors = false); + + ref<AttrCursor> 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<std::string> 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 <edolstra@gmail.com> Date: Tue, 26 Apr 2022 14:16:20 +0200 Subject: [PATCH 170/171] 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<Sync<State>> _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<Sync<State>>()) + , symbols(symbols) { auto state(_state->lock()); @@ -92,7 +98,6 @@ struct AttrDb AttrId setAttrs( AttrKey key, - const SymbolTable & symbols, const std::vector<Symbol> & 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<std::pair<AttrId, AttrValue>> getAttr( - AttrKey key, - SymbolTable & symbols) + std::optional<std::pair<AttrId, AttrValue>> 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<AttrDb> makeAttrDb(const Store & cfg, const Hash & fingerprint) +static std::shared_ptr<AttrDb> makeAttrDb( + const Store & cfg, + const Hash & fingerprint, + SymbolTable & symbols) { try { - return std::make_shared<AttrDb>(cfg, fingerprint); + return std::make_shared<AttrDb>(cfg, fingerprint, symbols); } catch (SQLiteError &) { ignoreException(); return nullptr; @@ -292,7 +298,7 @@ EvalCache::EvalCache( std::optional<std::reference_wrapper<const Hash>> 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> 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<std::vector<Symbol>>(&cachedValue->second)) { @@ -435,7 +438,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), attr)); return nullptr; } else if (std::get_if<placeholder_t>(&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<missing_t>(&attr->second)) return nullptr; @@ -464,7 +467,7 @@ std::shared_ptr<AttrCursor> 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> 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> 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<AttrCursor>( @@ -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<placeholder_t>(&cachedValue->second)) { if (auto s = std::get_if<string_t>(&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<placeholder_t>(&cachedValue->second)) { if (auto s = std::get_if<string_t>(&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<placeholder_t>(&cachedValue->second)) { if (auto b = std::get_if<bool>(&cachedValue->second)) { debug("using cached Boolean attribute '%s'", getAttrPathStr()); @@ -602,7 +605,7 @@ std::vector<Symbol> 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<placeholder_t>(&cachedValue->second)) { if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) { debug("using cached attrset attribute '%s'", getAttrPathStr()); @@ -626,7 +629,7 @@ std::vector<Symbol> 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<AttrId, std::string> AttrKey; +typedef std::pair<AttrId, Symbol> AttrKey; typedef std::pair<std::string, NixStringContext> 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<std::string, 8192> 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 68d51ae01278f314283523120713e745ef184011 Mon Sep 17 00:00:00 2001 From: midchildan <git@midchildan.org> Date: Wed, 27 Apr 2022 01:56:10 +0900 Subject: [PATCH 171/171] 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 $?