Merge remote-tracking branch 'origin/master' into coerce-string

This commit is contained in:
Guillaume Maudoux 2022-04-29 00:12:25 +02:00
commit e93b59fbc5
170 changed files with 4010 additions and 2203 deletions

View file

@ -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

View file

@ -14,10 +14,10 @@ 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@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'
@ -46,11 +46,11 @@ 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
- uses: cachix/install-nix-action@v16
- uses: cachix/install-nix-action@v17
- uses: cachix/cachix-action@v10
with:
name: '${{ env.CACHIX_NAME }}'
@ -67,9 +67,9 @@ 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@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"
@ -83,10 +83,10 @@ 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@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

View file

@ -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

2
.gitignore vendored
View file

@ -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
@ -90,6 +91,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

View file

@ -1 +1 @@
2.8.0
2.9.0

View file

@ -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:** " + (

View file

@ -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)

View file

@ -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,95 @@ 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.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
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.
> **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 -->

View file

@ -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

View file

@ -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).

View file

@ -1,3 +1,16 @@
# Release X.Y (202?-??-??)
* Various nix commands can now read expressions from stdin with `--file -`.
* 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.
* 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.
* `nix build` has a new `--print-out-paths` flag to print the resulting output paths.
This matches the default behaviour of `nix-build`.

View file

@ -22,6 +22,7 @@ let
findutils
iana-etc
git
openssh
];
users = {

View file

@ -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": {

View file

@ -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"
}

View file

@ -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

View file

@ -0,0 +1 @@
d @localstatedir@/nix/daemon-socket 0755 root root - -

View file

@ -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]

View file

@ -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))

42
misc/zsh/run-help-nix Normal file
View file

@ -0,0 +1,42 @@
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
while [[ "$#" != 0 && "$1" == -* ]]; do
shift
done
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 $?

View file

@ -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')

View file

@ -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() {
@ -739,7 +751,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/"

View file

@ -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" \
systemd-tmpfiles --create --prefix=/nix/var/nix
_sudo "to set up the nix-daemon service" \
systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC"

View file

@ -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

View file

@ -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 };

View file

@ -197,16 +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))
args.push_back(fmt("+%d", pos.line));
args.push_back(pos.file);
editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", line));
args.push_back(file);
return args;
}

View file

@ -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,13 +129,13 @@ struct InstallableCommand : virtual Args, SourceExprCommand
{
std::shared_ptr<Installable> installable;
InstallableCommand();
InstallableCommand(bool supportReadOnlyMode = false);
void prepare() override;
std::optional<FlakeRef> getFlakeRefForCompletion() override
{
return parseFlakeRef(_installable, absPath("."));
return parseFlakeRefWithFragment(_installable, absPath(".")).first;
}
private:
@ -218,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
{

View file

@ -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);
}}
});

View file

@ -1,4 +1,6 @@
#include "globals.hh"
#include "installables.hh"
#include "util.hh"
#include "command.hh"
#include "attr-path.hh"
#include "common-eval-args.hh"
@ -99,6 +101,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);
}
}}
});
@ -129,7 +139,7 @@ MixFlakeOptions::MixFlakeOptions()
});
}
SourceExprCommand::SourceExprCommand()
SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
{
addFlag({
.longName = "file",
@ -157,6 +167,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()
@ -182,6 +203,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(
@ -210,13 +233,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;
std::string name = state->symbols[i.name];
if (name.find(searchWord) == 0) {
completions->add(i.name);
if (prefix_ == "")
completions->add(name);
else
completions->add(prefix_ + "." + name);
}
}
}
@ -244,10 +268,11 @@ 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.
auto flakeRef = parseFlakeRef(flakeRefS, absPath("."));
auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath("."));
auto evalCache = openEvalCache(*evalState,
std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
@ -259,8 +284,6 @@ void completeFlakeRefWithFragment(
flake. */
attrPathPrefixes.push_back("");
completionType = ctAttrs;
for (auto & attrPathPrefixS : attrPathPrefixes) {
auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
auto attrPathS = attrPathPrefixS + std::string(fragment);
@ -268,7 +291,7 @@ void completeFlakeRefWithFragment(
std::string lastAttr;
if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) {
lastAttr = attrPath.back();
lastAttr = evalState->symbols[attrPath.back()];
attrPath.pop_back();
}
@ -276,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)));
}
}
}
@ -334,16 +357,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);
@ -450,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);
@ -566,27 +589,9 @@ 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();
Suggestions suggestions;
for (auto & attrPath : getActualAttrPaths()) {
debug("trying flake output attribute '%s'", attrPath);
auto attrOrSuggestions = root->findAlongAttrPath(
parseAttrPath(*state, attrPath),
true
);
if (!attrOrSuggestions) {
suggestions += attrOrSuggestions.getSuggestions();
continue;
}
auto attr = *attrOrSuggestions;
auto attrPath = attr->getAttrPathStr();
if (!attr->isDerivation())
throw Error("flake output attribute '%s' is not a derivation", attrPath);
@ -598,11 +603,7 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
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()
@ -612,35 +613,12 @@ std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
return res;
}
std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
std::pair<Value *, PosIdx> 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 +626,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
{
if (!_lockedFlake) {
flake::LockFlags lockFlagsApplyConfig = lockFlags;
lockFlagsApplyConfig.applyNixConfig = true;
if (!_lockedFlake) {
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
}
return _lockedFlake;
@ -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");
@ -756,15 +772,50 @@ 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});
},
for (auto & [_, builtPath] : build2(evalStore, store, mode, installables, bMode))
res.push_back(builtPath);
return res;
}
std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::build2(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
BuildMode bMode)
{
if (mode == Realise::Nothing)
settings.readOnlyMode = true;
std::vector<DerivedPath> pathsToBuild;
std::map<DerivedPath, std::vector<std::shared_ptr<Installable>>> backmap;
for (auto & i : installables) {
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);
for (auto & path : pathsToBuild) {
for (auto & installable : backmap[path]) {
std::visit(overloaded {
[&](const DerivedPath::Built & bfd) {
OutputPathMap outputs;
auto drv = evalStore->readDerivation(bfd.drvPath);
@ -776,17 +827,14 @@ BuiltPaths getBuiltPaths(ref<Store> evalStore, ref<Store> store, const DerivedPa
"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);
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'",
"cannot operate on an output of the "
"unbuilt derivation '%s'",
outputId.to_string());
outputs.insert_or_assign(
output, realisation->outPath);
outputs.insert_or_assign(output, realisation->outPath);
} else {
// If ca-derivations isn't enabled, assume that
// the output path is statically known.
@ -796,58 +844,45 @@ BuiltPaths getBuiltPaths(ref<Store> evalStore, ref<Store> store, const DerivedPa
output, *drvOutputs.at(output).second);
}
}
res.push_back(BuiltPath::Built{bfd.drvPath, outputs});
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
},
[&](const DerivedPath::Opaque & bo) {
res.push_back({installable, BuiltPath::Opaque { bo.path }});
},
b.raw());
return res;
}
BuiltPaths Installable::build(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
BuildMode bMode)
{
if (mode == Realise::Nothing)
settings.readOnlyMode = true;
std::vector<DerivedPath> pathsToBuild;
for (auto & i : installables) {
auto b = i->toDerivedPaths();
pathsToBuild.insert(pathsToBuild.end(), b.begin(), b.end());
}, path.raw());
}
}
switch (mode) {
case Realise::Nothing:
case Realise::Derivation:
printMissing(store, pathsToBuild, lvlError);
return getBuiltPaths(evalStore, store, pathsToBuild);
break;
case Realise::Outputs: {
BuiltPaths res;
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
if (!buildResult.success())
buildResult.rethrow();
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(BuiltPath::Built { bfd.drvPath, outputs });
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
},
[&](const DerivedPath::Opaque & bo) {
res.push_back(BuiltPath::Opaque { bo.path });
res.push_back({installable, BuiltPath::Opaque { bo.path }});
},
}, buildResult.path.raw());
}
return res;
}
break;
}
default:
assert(false);
}
return res;
}
BuiltPaths Installable::toBuiltPaths(
@ -935,7 +970,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);
@ -945,13 +980,14 @@ 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()
InstallableCommand::InstallableCommand(bool supportReadOnlyMode)
: SourceExprCommand(supportReadOnlyMode)
{
expectArgs({
.label = "installable",

View file

@ -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());
}
@ -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
@ -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,
@ -171,11 +178,17 @@ struct InstallableFlake : InstallableValue
std::vector<DerivationInfo> toDerivations() override;
std::pair<Value *, Pos> toValue(EvalState & state) override;
std::pair<Value *, PosIdx> 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;
@ -185,9 +198,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);
}

View file

@ -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) {
@ -77,13 +77,13 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string &
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);
}
v = &*a->value;
pos = *a->pos;
pos = a->pos;
}
else {
@ -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 };
}

View file

@ -10,14 +10,14 @@ 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,
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);

View file

@ -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, ptr<Pos> pos)
Value & BindingsBuilder::alloc(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);
}

View file

@ -15,18 +15,27 @@ 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. */
Symbol name;
PosIdx pos;
Value * value;
ptr<Pos> pos;
Attr(Symbol name, Value * value, ptr<Pos> pos = ptr(&noPos))
: name(name), value(value), pos(pos) { };
Attr() : pos(&noPos) { };
Attr(Symbol name, Value * value, PosIdx pos = noPos)
: name(name), pos(pos), value(value) { };
Attr() { };
bool operator < (const Attr & a) const
{
return name < a.name;
}
};
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
@ -35,13 +44,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:
@ -57,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);
@ -65,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);
@ -73,18 +82,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_]; }
@ -98,14 +95,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;
}
@ -130,7 +128,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));
}
@ -145,9 +143,9 @@ public:
bindings->push_back(attr);
}
Value & alloc(const Symbol & name, ptr<Pos> pos = ptr(&noPos));
Value & alloc(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()
{

View file

@ -21,6 +21,8 @@ struct AttrDb
{
std::atomic_bool failed{false};
const Store & cfg;
struct State
{
SQLite db;
@ -33,8 +35,15 @@ struct AttrDb
std::unique_ptr<Sync<State>> _state;
AttrDb(const Hash & fingerprint)
: _state(std::make_unique<Sync<State>>())
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());
@ -97,7 +106,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(key.second)
(symbols[key.second])
(AttrType::FullAttrs)
(0, false).exec();
@ -107,7 +116,7 @@ struct AttrDb
for (auto & attr : attrs)
state->insertAttribute.use()
(rowId)
(attr)
(symbols[attr])
(AttrType::Placeholder)
(0, false).exec();
@ -132,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();
}
@ -158,7 +167,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(key.second)
(symbols[key.second])
(AttrType::Bool)
(b ? 1 : 0).exec();
@ -174,7 +183,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(key.second)
(symbols[key.second])
(AttrType::Placeholder)
(0, false).exec();
@ -190,7 +199,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(key.second)
(symbols[key.second])
(AttrType::Missing)
(0, false).exec();
@ -206,7 +215,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(key.second)
(symbols[key.second])
(AttrType::Misc)
(0, false).exec();
@ -222,7 +231,7 @@ struct AttrDb
state->insertAttribute.use()
(key.first)
(key.second)
(symbols[key.second])
(AttrType::Failed)
(0, false).exec();
@ -230,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);
@ -250,14 +257,14 @@ 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(symbols.create(queryAttributes.getStr(0)));
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));
context.push_back(decodeContext(cfg, s));
return {{rowId, string_t{queryAttribute.getStr(2), context}}};
}
case AttrType::Bool:
@ -274,10 +281,13 @@ struct AttrDb
}
};
static std::shared_ptr<AttrDb> makeAttrDb(const Hash & fingerprint)
static std::shared_ptr<AttrDb> makeAttrDb(
const Store & cfg,
const Hash & fingerprint,
SymbolTable & symbols)
{
try {
return std::make_shared<AttrDb>(fingerprint);
return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
} catch (SQLiteError &) {
ignoreException();
return nullptr;
@ -288,7 +298,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, state.symbols) : nullptr)
, state(state)
, rootLoader(rootLoader)
{
@ -303,9 +313,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(
@ -324,8 +334,7 @@ AttrKey AttrCursor::getKey()
if (!parent)
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, parent->second};
@ -366,12 +375,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()
@ -411,25 +420,25 @@ 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(Symbol name, bool forceErrors)
{
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)) {
for (auto & attr : *attrs)
if (attr == name)
return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), 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, name});
if (attr) {
if (std::get_if<missing_t>(&attr->second))
return nullptr;
@ -519,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());
@ -541,12 +550,12 @@ 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;
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 +572,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
@ -574,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());
@ -596,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());
@ -614,8 +623,9 @@ std::vector<Symbol> AttrCursor::getAttrs()
std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs)
attrs.push_back(attr.name);
std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) {
return (const std::string &) a < (const std::string &) b;
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)

View file

@ -33,7 +33,7 @@ public:
EvalState & state,
RootLoader rootLoader);
std::shared_ptr<AttrCursor> getRoot();
ref<AttrCursor> getRoot();
};
enum AttrType {
@ -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>,
@ -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();

View file

@ -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]]
@ -99,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; });
}
@ -128,7 +109,7 @@ void EvalState::forceValue(Value & v, Callable getPos)
[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const Pos & pos, std::string_view errorCtx)
inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
{
forceAttrs(v, [&]() { return pos; }, errorCtx);
}
@ -144,15 +125,15 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e
throwTypeError(noPos, "value is %1% while a set was expected", v);
}
} catch (Error & e) {
Pos pos = getPos();
e.addTrace(pos, errorCtx);
PosIdx pos = getPos();
e.addTrace(positions[pos], errorCtx);
throw;
}
}
[[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const Pos & pos, std::string_view errorCtx)
inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
forceValue(v, noPos);
@ -160,7 +141,7 @@ inline void EvalState::forceList(Value & v, const Pos & pos, std::string_view er
throwTypeError(noPos, "value is %1% while a list was expected", v);
}
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
}

View file

@ -96,20 +96,21 @@ RootValue allocRootValue(Value * v)
}
void printValue(std::ostream & str, std::set<const void *> & seen, const Value & v)
void Value::print(const SymbolTable & symbols, 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 +120,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)
str << "<REPEAT>";
if (seen && !attrs->empty() && !seen->insert(attrs).second)
str << "«repeated»";
else {
str << "{ ";
for (auto & i : v.attrs->lexicographicOrder()) {
str << i->name << " = ";
printValue(str, seen, *i->value);
for (auto & i : attrs->lexicographicOrder(symbols)) {
str << symbols[i->name] << " = ";
i->value->print(symbols, str, seen);
str << "; ";
}
str << "}";
@ -141,12 +142,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)
str << "<REPEAT>";
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(symbols, str, seen);
str << " ";
}
str << "]";
@ -166,10 +167,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,11 +178,18 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
}
std::ostream & operator << (std::ostream & str, const Value & v)
void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepeated) const
{
std::set<const void *> seen;
printValue(str, seen, v);
return str;
print(symbols, str, showRepeated ? nullptr : &seen);
}
std::string printValue(const EvalState & state, const Value & v)
{
std::ostringstream out;
v.print(state.symbols, out);
return out.str();
}
@ -230,10 +238,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;
@ -302,7 +310,7 @@ static BoehmGCStackAllocator boehmGCStackAllocator;
static Symbol getName(const AttrName & name, EvalState & state, Env & env)
{
if (name.symbol.set()) {
if (name.symbol) {
return name.symbol;
} else {
Value nameValue;
@ -430,6 +438,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"))
@ -501,23 +510,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)
@ -648,7 +640,7 @@ void EvalState::addConstant(const std::string & name, Value * v)
Value * EvalState::addPrimOp(const std::string & name,
size_t arity, PrimOpFun primOp)
{
return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = symbols.create(name) });
return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = name });
}
@ -659,21 +651,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;
auto 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;
}
@ -690,7 +682,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,
@ -706,140 +698,164 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
evaluator. So here are some helper functions for throwing
exceptions. */
LocalNoInlineNoReturn(void throwTypeErrorWithTrace(
const Pos & pos,
void EvalState::throwTypeErrorWithTrace(
const PosIdx pos,
const char * s,
const std::string_view & s2,
const std::string_view s2,
const Symbol & sym,
const Pos & p2,
const std::string_view & s3))
const PosIdx p2,
const std::string_view s3) const
{
auto e = TypeError(ErrorInfo {
.msg = hintfmt(s, s2, sym),
.errPos = pos,
.msg = hintfmt(s, s2, symbols[sym]),
.errPos = positions[pos],
});
e.addTrace(p2, s3);
e.addTrace(positions[p2], s3);
throw e;
}
LocalNoInlineNoReturn(void throwTypeErrorWithTrace(
const Pos & pos,
void EvalState::throwTypeErrorWithTrace(
const PosIdx pos,
const Suggestions & suggestions,
const char * s,
const std::string_view & s2,
const std::string_view s2,
const Symbol & sym,
const Pos & p2,
const std::string_view & s3))
const PosIdx p2,
const std::string_view s3) const
{
auto e = TypeError(ErrorInfo {
.msg = hintfmt(s, s2, sym),
.errPos = pos,
.msg = hintfmt(s, s2, symbols[sym]),
.errPos = positions[pos],
.suggestions = suggestions
});
e.addTrace(p2, s3);
e.addTrace(positions[p2], s3);
throw e;
}
LocalNoInlineNoReturn(void throwTypeErrorWithTrace(const char * s, const std::string & s2, const Pos & p2, const std::string_view s3))
void EvalState::throwTypeErrorWithTrace(const char * s, const std::string_view s2, const PosIdx p2, const std::string_view s3) const
{
auto e = TypeError(ErrorInfo {
.msg = hintfmt(s, s2),
});
e.addTrace(p2, s3);
e.addTrace(positions[p2], s3);
throw e;
}
LocalNoInlineNoReturn(void throwEvalErrorWithTrace(const char * s, const std::string & s2, const Pos & p2, const std::string_view s3))
void EvalState::throwEvalErrorWithTrace(const char * s, const std::string_view s2, const PosIdx p2, const std::string_view s3) const
{
auto e = EvalError(ErrorInfo {
.msg = hintfmt(s, s2),
});
e.addTrace(p2, s3);
e.addTrace(positions[p2], s3);
throw e;
}
LocalNoInlineNoReturn(void throwEvalErrorWithTrace(const char * s, const std::string_view & s2, const std::string_view & s3, const Pos & p2, const std::string_view s4))
void EvalState::throwEvalErrorWithTrace(const char * s, const std::string_view s2, const std::string_view s3, const PosIdx p2, const std::string_view s4) const
{
auto e = EvalError(ErrorInfo {
.msg = hintfmt(s, s2, s3),
});
e.addTrace(p2, s4);
e.addTrace(positions[p2], s4);
throw e;
}
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2))
void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string_view s2) const
{
throw EvalError(ErrorInfo {
.msg = hintfmt(s, s2),
.errPos = pos,
.errPos = positions[pos],
.suggestions = suggestions,
});
}
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const Value & v))
void EvalState::throwEvalError(const PosIdx pos, const char * s) const
{
throw EvalError(ErrorInfo {
.msg = hintfmt(s),
.errPos = positions[pos]
});
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const Value & v) const
{
throw EvalError(ErrorInfo {
.msg = hintfmt(s, showType(v)),
.errPos = pos
.errPos = positions[pos]
});
}
LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2))
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string_view s2) const
{
throw EvalError(ErrorInfo {
.msg = hintfmt(s, s2),
.errPos = positions[pos]
});
}
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(ErrorInfo {
.msg = hintfmt(s, sym, p2),
.errPos = p1
.msg = hintfmt(s, symbols[sym], positions[p2]),
.errPos = positions[p1]
});
}
LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string_view & s1))
void EvalState::throwEvalError(const char * s, const std::string_view s1) const
{
throw EvalError(s, s1);
}
LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string_view & s1, const std::string_view & s2))
void EvalState::throwEvalError(const char * s, const std::string_view s1, const std::string_view s2) const
{
throw EvalError(s, s1, s2);
}
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::throwTypeError(const PosIdx pos, const char * s, const Value & v) const
{
throw TypeError(ErrorInfo {
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
});
}
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]
});
}
LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1))
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]
});
}
LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1))
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]
});
}
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 PosIdx pos, const char * s, const std::string & s2) const
{
e.addTrace(pos, s, s2);
e.addTrace(positions[pos], s, s2);
}
@ -896,11 +912,11 @@ 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)
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) ;
}
}
@ -930,13 +946,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.empty()) {
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();
@ -1057,7 +1074,7 @@ void EvalState::eval(Expr * e, Value & v)
}
inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, std::string_view errorCtx)
inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx)
{
try {
Value v;
@ -1066,20 +1083,20 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos, std::strin
throwTypeError("value is %1% while a Boolean was expected", v);
return v.boolean;
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
}
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx)
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
e->eval(*this, env, v);
if (v.type() != nAttrs)
throwTypeError("value is %1% while a set was expected", v);
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
}
@ -1141,7 +1158,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
@ -1173,7 +1190,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) {
@ -1183,18 +1200,18 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
if (nameVal.type() == nNull)
continue;
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
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())
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;
}
@ -1239,10 +1256,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();
@ -1252,7 +1271,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);
@ -1262,7 +1281,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 ||
@ -1276,23 +1295,24 @@ 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);
throwEvalError(
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;
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)
addErrorTrace(e, *pos2, "while evaluating the attribute '%1%'",
auto pos2r = state.positions[pos2];
if (pos2 && pos2r.file != state.derivationNixPath)
state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
showAttrPath(state, env, attrPath));
throw;
}
@ -1311,7 +1331,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())
{
@ -1331,14 +1351,11 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
v.mkLambda(&env, this);
}
const std::string prettyLambdaName(const ExprLambda & e)
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
{
return e.name.set() ? std::string(e.name): "anonymous lambda";
}
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & 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);
@ -1363,7 +1380,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 ? 0 : 1) +
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
Env & env2(allocEnv(size));
env2.up = vCur.lambda.env;
@ -1377,11 +1394,11 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
try {
forceAttrs(*args[0], lambda.pos, "while evaluating the value passed for the lambda argument");
} catch (Error & e) {
e.addTrace(pos, "from call site");
e.addTrace(positions[pos], "from call site");
throw;
}
if (!lambda.arg.empty())
if (lambda.arg)
env2.values[displ++] = args[0];
/* For each formal argument, get the actual argument. If
@ -1393,8 +1410,9 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (!j) {
if (!i.def) {
throwTypeErrorWithTrace(lambda.pos,
"function '%1%' called without required argument '%2%'", prettyLambdaName(lambda), i.name,
pos, "from call site");
"function '%1%' called without required argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
i.name, pos, "from call site");
}
env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else {
@ -1412,11 +1430,12 @@ 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]);
throwTypeErrorWithTrace(lambda.pos,
Suggestions::bestMatches(formalNames, i.name),
"function '%1%' called with unexpected argument '%2%'", prettyLambdaName(lambda), i.name,
pos, "from call site");
Suggestions::bestMatches(formalNames, symbols[i.name]),
"function '%1%' called with unexpected argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
i.name, pos, "from call site");
}
abort(); // can't happen
}
@ -1430,8 +1449,9 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
lambda.body->eval(*this, env2, vCur);
} catch (Error & e) {
if (loggerSettings.showTrace.get()) {
addErrorTrace(e, lambda.pos, "while evaluating the '%s' function", prettyLambdaName(lambda));
if (pos) e.addTrace(pos, "from call site");
addErrorTrace(e, lambda.pos, "while evaluating the '%s' function",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"));
addErrorTrace(e, pos, "from call site%s", "");
}
throw;
}
@ -1450,7 +1470,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
return;
} else {
/* We have all the arguments, so call the primop. */
Symbol name = vCur.primOp->name;
auto name = vCur.primOp->name;
nrPrimOpCalls++;
if (countCalls) primOpCalls[name]++;
@ -1495,7 +1515,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (size_t i = 0; i < argsLeft; ++i)
vArgs[argsDone + i] = args[i];
Symbol name = primOp->primOp->name;
auto name = primOp->primOp->name;
nrPrimOpCalls++;
if (countCalls) primOpCalls[name]++;
@ -1518,9 +1538,9 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
Value * args2[] = {allocValue(), args[0]};
*args2[0] = vCur;
try {
callFunction(*functor->value, 2, args2, vCur, *functor->pos);
callFunction(*functor->value, 2, args2, vCur, functor->pos);
} catch (Error & e) {
e.addTrace(pos, "while calling a functor (an attribute set with a '__functor' attribute)");
e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
throw;
}
nrArgs--;
@ -1597,7 +1617,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]);
}
}
@ -1630,8 +1650,8 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
std::ostringstream out;
cond->show(out);
throwAssertionError(pos, "assertion '%1%' failed", out.str());
cond->show(state.symbols, out);
state.throwAssertionError(pos, "assertion '%1%' failed", out.str());
}
body->eval(state, env, v);
}
@ -1724,7 +1744,7 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
}
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos, std::string_view errorCtx)
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx)
{
nrListConcats++;
@ -1808,14 +1828,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", 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", 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
@ -1835,7 +1855,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);
@ -1844,7 +1864,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);
}
@ -1864,7 +1884,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;
}
}
@ -1879,7 +1899,7 @@ void EvalState::forceValueDeep(Value & v)
}
NixInt EvalState::forceInt(Value & v, const Pos & pos, std::string_view errorCtx)
NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
forceValue(v, pos);
@ -1887,13 +1907,13 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos, std::string_view errorCtx
throwTypeError("value is %1% while an integer was expected", v);
return v.integer;
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
}
NixFloat EvalState::forceFloat(Value & v, const Pos & pos, std::string_view errorCtx)
NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
forceValue(v, pos);
@ -1903,13 +1923,13 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos, std::string_view erro
throwTypeError("value is %1% while a float was expected", v);
return v.fpoint;
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
}
bool EvalState::forceBool(Value & v, const Pos & pos, std::string_view errorCtx)
bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
forceValue(v, pos);
@ -1917,7 +1937,7 @@ bool EvalState::forceBool(Value & v, const Pos & pos, std::string_view errorCtx)
throwTypeError("value is %1% while a Boolean was expected", v);
return v.boolean;
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
}
@ -1929,20 +1949,20 @@ bool EvalState::isFunctor(Value & fun)
}
void EvalState::forceFunction(Value & v, const Pos & pos, std::string_view errorCtx)
void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v))
throwTypeError("value is %1% while a function was expected", v);
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
}
std::string_view EvalState::forceString(Value & v, const Pos & pos, std::string_view errorCtx)
std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
forceValue(v, pos);
@ -1950,7 +1970,7 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos, std::string_
throwTypeError("value is %1% while a string was expected", v);
return v.string.s;
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
}
@ -1958,13 +1978,22 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos, std::string_
/* Decode a context string !<name>!<path> into a pair <path,
name>. */
std::pair<std::string, std::string> 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)),
"",
};
}
@ -1976,18 +2005,18 @@ void copyContext(const Value & v, PathSet & context)
}
std::vector<std::pair<Path, std::string>> Value::getContext()
NixStringContext Value::getContext(const Store & store)
{
std::vector<std::pair<Path, std::string>> res;
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;
}
std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos, std::string_view errorCtx)
std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx)
{
auto s = forceString(v, pos, errorCtx);
copyContext(v, context);
@ -1995,7 +2024,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 errorCtx)
std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
auto s = forceString(v, pos, errorCtx);
@ -2007,7 +2036,7 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos, std::st
}
return s;
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
}
@ -2018,13 +2047,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);
@ -2038,7 +2067,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, std::string_view errorCtx)
{
forceValue(v, pos);
@ -2068,7 +2097,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
}
if (v.type() == nExternal)
return v.external->coerceToString(pos, context, coerceMore, copyToStore, errorCtx);
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
if (coerceMore) {
@ -2087,7 +2116,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, canonicalizePath,
"while evaluating one element of the list");
} catch (Error & e) {
e.addTrace(pos, errorCtx);
e.addTrace(positions[pos], errorCtx);
throw;
}
if (n < v.listSize() - 1
@ -2127,7 +2156,7 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
}
Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, std::string_view errorCtx)
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{
auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
if (path == "" || path[0] != '/')
@ -2136,7 +2165,7 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context, std:
}
StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context, std::string_view errorCtx)
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{
auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
if (auto storePath = store->maybeParseStorePath(path))
@ -2145,7 +2174,7 @@ StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & con
}
bool EvalState::eqValues(Value & v1, Value & v2, const Pos & pos, std::string_view errorCtx)
bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
{
forceValue(v1, noPos);
forceValue(v2, noPos);
@ -2307,14 +2336,14 @@ 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);
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);
}
@ -2323,10 +2352,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);
}
@ -2343,7 +2372,11 @@ void EvalState::printStats()
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const
{
throwTypeErrorWithTrace("cannot coerce %1% to a string", showType(), pos, errorCtx);
auto e = TypeError(ErrorInfo {
.msg = hintfmt("cannot coerce %1% to a string", showType())
});
e.addTrace(pos, errorCtx);
throw e;
}

View file

@ -23,14 +23,14 @@ 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
{
PrimOpFun fun;
size_t arity;
Symbol name;
std::string name;
std::vector<std::string> args;
const char * doc = nullptr;
};
@ -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;
@ -73,12 +74,15 @@ class EvalState
{
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,
sRight, sWrong, sStructuredAttrs, sBuilder, sArgs,
sContentAddressed,
sContentAddressed, sImpure,
sOutputHash, sOutputHashAlgo, sOutputHashMode,
sRecurseForDerivations,
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
@ -149,12 +153,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; }
@ -211,7 +209,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);
@ -222,14 +220,15 @@ public:
/* Evaluation the expression, then verify that it has the expected
type. */
inline bool evalBool(Env & env, Expr * e, const Pos & pos, std::string_view errorCtx);
inline void evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx);
inline bool evalBool(Env & env, Expr * e);
inline bool evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx);
inline void evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx);
/* 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);
@ -239,33 +238,61 @@ public:
void forceValueDeep(Value & v);
/* Force `v', and then verify that it has the expected type. */
NixInt forceInt(Value & v, const Pos & pos, std::string_view errorCtx);
NixFloat forceFloat(Value & v, const Pos & pos, std::string_view errorCtx);
bool forceBool(Value & v, const Pos & pos, std::string_view errorCtx);
NixInt forceInt(Value & v, const PosIdx pos, std::string_view errorCtx);
NixFloat forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx);
bool forceBool(Value & v, const PosIdx pos, std::string_view errorCtx);
void forceAttrs(Value & v, const Pos & pos, std::string_view errorCtx);
void forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx);
template <typename Callable>
inline void forceAttrs(Value & v, Callable getPos, std::string_view errorCtx);
inline void forceList(Value & v, const Pos & pos, std::string_view errorCtx);
void forceFunction(Value & v, const Pos & pos, std::string_view errorCtx); // either lambda or primop
std::string_view forceString(Value & v, const Pos & pos, std::string_view errorCtx);
std::string_view forceString(Value & v, PathSet & context, const Pos & pos, std::string_view errorCtx);
std::string_view forceStringNoCtx(Value & v, const Pos & pos, std::string_view errorCtx);
inline void forceList(Value & v, const PosIdx pos, std::string_view errorCtx);
void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx); // either lambda or primop
std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx);
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx);
std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const char * s) const;
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const char * s, const Value & v) const;
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const char * s, const std::string_view s2) const;
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string_view s2) const;
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const char * s, const std::string_view s2) const;
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const char * s, const std::string_view s2, const std::string_view s3) const;
[[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;
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const char * s, const Value & v) 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 Value & v) const;
[[gnu::noinline, gnu::noreturn]] 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 Symbol s2) const;
[[gnu::noinline, gnu::noreturn]] void throwTypeError(const char * s, const Value & v) const;
[[gnu::noinline, gnu::noreturn]] void throwTypeErrorWithTrace(const PosIdx, const char*, std::string_view, const nix::Symbol&, const PosIdx, std::string_view) const;
[[gnu::noinline, gnu::noreturn]] void throwTypeErrorWithTrace(const PosIdx, const nix::Suggestions&, const char*, std::string_view, const nix::Symbol&, const PosIdx, std::string_view) const;
[[gnu::noinline, gnu::noreturn]] void throwTypeErrorWithTrace(const char*, std::string_view, const Pos &, std::string_view) const;
[[gnu::noinline, gnu::noreturn]] void throwTypeErrorWithTrace(const char*, std::string_view, const PosIdx, std::string_view) const;
[[gnu::noinline, gnu::noreturn]] void throwEvalErrorWithTrace(const char*, std::string_view, const PosIdx, std::string_view) const;
[[gnu::noinline, gnu::noreturn]] void throwEvalErrorWithTrace(const char*, std::string_view, std::string_view, nix::PosIdx, std::string_view) const;
[[gnu::noinline, gnu::noreturn]] void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const;
[[gnu::noinline, gnu::noreturn]] void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const;
[[gnu::noinline, gnu::noreturn]] 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 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,
std::string_view errorCtx = "");
@ -275,10 +302,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, std::string_view errorCtx);
Path coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
/* Like coerceToPath, but the result must be a store path. */
StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context, std::string_view errorCtx);
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
public:
@ -311,7 +338,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;
@ -334,14 +361,14 @@ public:
/* Do a deep equality test between two values. That is, list
elements and attributes are compared recursively. */
bool eqValues(Value & v1, Value & v2, const Pos & pos, std::string_view errorCtx);
bool eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx);
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);
@ -355,7 +382,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);
@ -367,9 +394,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, std::string_view errorCtx);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
/* Print statistics. */
void printStats();
@ -397,7 +424,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;
@ -405,7 +432,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;
@ -416,9 +443,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;
};
@ -430,7 +457,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(const Store & store, std::string_view s);
/* If `path' refers to a directory, then append "/default.nix". */
Path resolveExprPath(Path path);

View file

@ -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,37 +111,39 @@ 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;
} 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(*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;
}
}
@ -150,13 +152,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 +170,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;
@ -176,11 +178,11 @@ 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,
inputAttr.pos,
baseDir,
lockRootPath));
}
@ -218,28 +220,28 @@ 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({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) {
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])
});
}
}
@ -250,35 +252,41 @@ 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.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.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true, "") .toOwned());
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));
ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos, ""));
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, *attr.pos);
lockedRef, state.symbols[attr.name], state.positions[attr.pos]);
}
return flake;
@ -704,14 +712,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)
{
state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos);
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
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,
@ -723,7 +729,30 @@ 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"},
.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,
});
}

View file

@ -8,7 +8,7 @@ namespace nix {
struct FunctionCallTrace
{
const Pos & pos;
const Pos pos;
FunctionCallTrace(const Pos & pos);
~FunctionCallTrace();
};

View file

@ -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"
@ -60,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, "while evaluating the 'system' attribute of a derivation");
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
}
return system;
}
@ -74,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, "while evaluating the 'drvPath' attribute of a derivation")};
drvPath = {state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
}
return drvPath.value_or(std::nullopt);
}
@ -94,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, "while evaluating the output path of a derivation");
outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
}
if (!outPath)
throw UnimplementedError("CA derivations are not yet supported");
@ -108,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, "while evaluating the 'outputs' attribute of a derivation");
state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation");
/* For each output... */
for (auto elem : i->value->listItems()) {
std::string output(state->forceStringNoCtx(*elem, *i->pos, "while evaluating the name of an output of a derivation"));
std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation"));
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, "while evaluating an output of a derivation");
state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
/* 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, "while evaluating an output path of a derivation"));
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
} else
outputs.emplace(output, std::nullopt);
}
@ -167,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, "while evaluating the 'meta' attribute of a derivation");
state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value->attrs;
return meta;
}
@ -178,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;
}
@ -268,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)
@ -355,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)) {
@ -368,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, "while evaluating the attribute `recurseForDerivations`"))
if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
}
}

View file

@ -28,6 +28,13 @@ using namespace nix;
namespace nix {
static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data)
{
return data->state.positions.add(data->origin, 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 = data->state.positions[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 = data->state.positions[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 = data->state.positions[CUR_POS],
});
}
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }

View file

@ -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 << '"';
@ -28,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
@ -38,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') ||
@ -46,89 +44,104 @@ 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;
}
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 +149,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.empty()) str << " @ ";
if (arg) str << " @ ";
}
if (!arg.empty()) 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,48 +273,49 @@ 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();
}
Pos noPos;
/* Computing levels/displacements for variables. */
void Expr::bindVars(const StaticEnv & env)
void Expr::bindVars(const EvalState & es, const StaticEnv & env)
{
abort();
}
void ExprInt::bindVars(const StaticEnv & env)
void ExprInt::bindVars(const EvalState & es, const StaticEnv & env)
{
}
void ExprFloat::bindVars(const StaticEnv & env)
void ExprFloat::bindVars(const EvalState & es, const StaticEnv & env)
{
}
void ExprString::bindVars(const StaticEnv & env)
void ExprString::bindVars(const EvalState & es, const StaticEnv & env)
{
}
void ExprPath::bindVars(const StaticEnv & env)
void ExprPath::bindVars(const EvalState & es, const StaticEnv & env)
{
}
void ExprVar::bindVars(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. */
@ -301,31 +341,31 @@ void ExprVar::bindVars(const StaticEnv & env)
"undefined variable" error now. */
if (withLevel == -1)
throw UndefinedVarError({
.msg = hintfmt("undefined variable '%1%'", name),
.errPos = pos
.msg = hintfmt("undefined variable '%1%'", es.symbols[name]),
.errPos = es.positions[pos]
});
fromWith = true;
this->level = withLevel;
}
void ExprSelect::bindVars(const StaticEnv & env)
void ExprSelect::bindVars(const EvalState & es, const StaticEnv & env)
{
e->bindVars(env);
if (def) def->bindVars(env);
e->bindVars(es, env);
if (def) def->bindVars(es, env);
for (auto & i : attrPath)
if (!i.symbol.set())
i.expr->bindVars(env);
if (!i.symbol)
i.expr->bindVars(es, env);
}
void ExprOpHasAttr::bindVars(const StaticEnv & env)
void ExprOpHasAttr::bindVars(const EvalState & es, const StaticEnv & env)
{
e->bindVars(env);
e->bindVars(es, env);
for (auto & i : attrPath)
if (!i.symbol.set())
i.expr->bindVars(env);
if (!i.symbol)
i.expr->bindVars(es, env);
}
void ExprAttrs::bindVars(const StaticEnv & env)
void ExprAttrs::bindVars(const EvalState & es, const StaticEnv & env)
{
const StaticEnv * dynamicEnv = &env;
StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0);
@ -340,35 +380,35 @@ 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(es, i.second.inherited ? env : newEnv);
}
else
for (auto & i : attrs)
i.second.e->bindVars(env);
i.second.e->bindVars(es, env);
for (auto & i : dynamicAttrs) {
i.nameExpr->bindVars(*dynamicEnv);
i.valueExpr->bindVars(*dynamicEnv);
i.nameExpr->bindVars(es, *dynamicEnv);
i.valueExpr->bindVars(es, *dynamicEnv);
}
}
void ExprList::bindVars(const StaticEnv & env)
void ExprList::bindVars(const EvalState & es, const StaticEnv & env)
{
for (auto & i : elems)
i->bindVars(env);
i->bindVars(es, env);
}
void ExprLambda::bindVars(const StaticEnv & env)
void ExprLambda::bindVars(const EvalState & es, const StaticEnv & env)
{
StaticEnv newEnv(
false, &env,
(hasFormals() ? formals->formals.size() : 0) +
(arg.empty() ? 0 : 1));
(!arg ? 0 : 1));
Displacement displ = 0;
if (!arg.empty()) newEnv.vars.emplace_back(arg, displ++);
if (arg) newEnv.vars.emplace_back(arg, displ++);
if (hasFormals()) {
for (auto & i : formals->formals)
@ -377,20 +417,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(es, newEnv);
}
body->bindVars(newEnv);
body->bindVars(es, newEnv);
}
void ExprCall::bindVars(const StaticEnv & env)
void ExprCall::bindVars(const EvalState & es, const StaticEnv & env)
{
fun->bindVars(env);
fun->bindVars(es, env);
for (auto e : args)
e->bindVars(env);
e->bindVars(es, env);
}
void ExprLet::bindVars(const StaticEnv & env)
void ExprLet::bindVars(const EvalState & es, const StaticEnv & env)
{
StaticEnv newEnv(false, &env, attrs->attrs.size());
@ -401,12 +441,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(es, i.second.inherited ? env : newEnv);
body->bindVars(newEnv);
body->bindVars(es, newEnv);
}
void ExprWith::bindVars(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
@ -420,57 +460,60 @@ void ExprWith::bindVars(const StaticEnv & env)
break;
}
attrs->bindVars(env);
attrs->bindVars(es, env);
StaticEnv newEnv(true, &env);
body->bindVars(newEnv);
body->bindVars(es, newEnv);
}
void ExprIf::bindVars(const StaticEnv & env)
void ExprIf::bindVars(const EvalState & es, const StaticEnv & env)
{
cond->bindVars(env);
then->bindVars(env);
else_->bindVars(env);
cond->bindVars(es, env);
then->bindVars(es, env);
else_->bindVars(es, env);
}
void ExprAssert::bindVars(const StaticEnv & env)
void ExprAssert::bindVars(const EvalState & es, const StaticEnv & env)
{
cond->bindVars(env);
body->bindVars(env);
cond->bindVars(es, env);
body->bindVars(es, env);
}
void ExprOpNot::bindVars(const StaticEnv & env)
void ExprOpNot::bindVars(const EvalState & es, const StaticEnv & env)
{
e->bindVars(env);
e->bindVars(es, env);
}
void ExprConcatStrings::bindVars(const StaticEnv & env)
void ExprConcatStrings::bindVars(const EvalState & es, const StaticEnv & env)
{
for (auto & i : *es)
i.second->bindVars(env);
for (auto & i : *this->es)
i.second->bindVars(es, env);
}
void ExprPos::bindVars(const StaticEnv & env)
void ExprPos::bindVars(const EvalState & es, const StaticEnv & env)
{
}
/* Storing function names. */
void Expr::setName(Symbol & name)
void Expr::setName(Symbol name)
{
}
void ExprLambda::setName(Symbol & name)
void ExprLambda::setName(Symbol name)
{
this->name = name;
body->setName(name);
}
std::string ExprLambda::showNamePos() const
std::string ExprLambda::showNamePos(const EvalState & state) const
{
return fmt("%1% at %2%", name.set() ? "'" + (std::string) name + "'" : "anonymous function", pos);
std::string id(name
? concatStrings("'", state.symbols[name], "'")
: "anonymous function");
return fmt("%1% at %2%", id, state.positions[pos]);
}
@ -480,8 +523,7 @@ std::string ExprLambda::showNamePos() const
size_t SymbolTable::totalSize() const
{
size_t n = 0;
for (auto & i : store)
n += i.size();
dump([&] (const std::string & s) { n += s.size(); });
return n;
}

View file

@ -1,8 +1,12 @@
#pragma once
#include <map>
#include <vector>
#include "value.hh"
#include "symbol-table.hh"
#include "error.hh"
#include "chunked-vector.hh"
namespace nix {
@ -22,32 +26,92 @@ MakeError(RestrictedPathError, Error);
struct Pos
{
Symbol file;
std::string 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 std::string file;
const FileOrigin origin;
Origin(std::string file, FileOrigin origin): file(std::move(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);
@ -63,13 +127,13 @@ struct AttrName
{
Symbol symbol;
Expr * expr;
AttrName(const Symbol & s) : symbol(s) {};
AttrName(Symbol 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. */
@ -77,19 +141,17 @@ std::string showAttrPath(const AttrPath & attrPath);
struct Expr
{
virtual ~Expr() { };
virtual void show(std::ostream & str) const;
virtual void bindVars(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(Symbol 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 StaticEnv & env);
void bindVars(const EvalState & es, const StaticEnv & env);
struct ExprInt : Expr
{
@ -132,7 +194,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
@ -148,19 +210,19 @@ struct ExprVar : Expr
Level level;
Displacement displ;
ExprVar(const Symbol & name) : name(name) { };
ExprVar(const Pos & 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);
};
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, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
COMMON_METHODS
};
@ -175,13 +237,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() { };
};
@ -189,14 +251,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
};
@ -209,10 +271,9 @@ 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) { };
};
struct Formals
@ -221,18 +282,20 @@ 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;
}
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;
}
@ -240,17 +303,21 @@ 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(PosIdx pos, Symbol arg, Formals * formals, Expr * body)
: pos(pos), arg(arg), formals(formals), body(body)
{
};
void setName(Symbol & name);
std::string showNamePos() const;
ExprLambda(PosIdx pos, Formals * formals, Expr * body)
: pos(pos), formals(formals), body(body)
{
}
void setName(Symbol name);
std::string showNamePos(const EvalState & state) const;
inline bool hasFormals() const { return formals != nullptr; }
COMMON_METHODS
};
@ -259,8 +326,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
@ -276,26 +343,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
};
@ -309,17 +376,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) { }; \
void show(std::ostream & str) const \
name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \
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 StaticEnv & env) \
void bindVars(const EvalState & es, const StaticEnv & env) \
{ \
e1->bindVars(env); e2->bindVars(env); \
e1->bindVars(es, env); e2->bindVars(es, env); \
} \
void eval(EvalState & state, Env & env, Value & v); \
};
@ -334,18 +401,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
};
@ -383,7 +450,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);

View file

@ -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))
{ };
};
@ -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, Symbol 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 Pos & pos)
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, pos, j->second.pos);
if (!attrs2) dupAttr(state, attrPath, pos, j->second.pos);
attrs = attrs2;
} else
dupAttr(attrPath, pos, 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, j2->second.pos, ad.second.pos);
dupAttr(state, ad.first, j2->second.pos, ad.second.pos);
jAttrs->attrs.emplace(ad.first, ad.second);
}
} else {
dupAttr(attrPath, pos, 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,
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;
@ -173,18 +173,18 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
}
if (duplicate)
throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", duplicate->first),
.errPos = duplicate->second
.msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[duplicate->first]),
.errPos = data.state.positions[duplicate->second]
});
Formals result;
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),
.errPos = pos
.msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[arg]),
.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
@ -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, 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);
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
@ -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(makeCurPos(@2, data), $1, $3); }
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
| expr_op '+' expr_op
{ $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector<std::pair<Pos, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
{ $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector<std::pair<PosIdx, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
| expr_op '-' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
| expr_op '*' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
| expr_op '/' expr_op { $$ = new ExprCall(makeCurPos(@2, data), 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,17 @@ 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); }
| 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(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));
}
}
@ -547,7 +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, makeCurPos(@6, data), $$->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)));
}
}
@ -565,7 +565,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; }
@ -621,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}; }
;
%%
@ -646,19 +646,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;
std::string file;
switch (origin) {
case foFile:
data.file = data.symbols.create(path);
file = path;
break;
case foStdin:
case foString:
data.file = data.symbols.create(text);
file = text;
break;
default:
assert(false);
}
ParseData data(*this, {file, origin});
data.basePath = basePath;
yylex_init(&scanner);
@ -668,7 +668,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(*this, staticEnv);
return data.result;
}
@ -760,7 +760,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 +787,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]
});
}

File diff suppressed because it is too large Load diff

View file

@ -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,10 +36,11 @@ 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);
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);
}

View file

@ -1,10 +1,11 @@
#include "primops.hh"
#include "eval-inline.hh"
#include "derivations.hh"
#include "store-api.hh"
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, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
@ -14,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, "while evaluating the argument passed to builtins.hasContext");
@ -30,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, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
@ -64,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;
@ -82,8 +83,8 @@ 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);
drv = ctx.first;
NixStringContextElem ctx = decodeContext(*state.store, p);
drv = state.store->printStorePath(ctx.first);
output = ctx.second;
path = &drv;
}
@ -133,55 +134,56 @@ 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, "while evaluating the first argument passed to builtins.appendContext");
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
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),
.errPos = *i.pos
.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.forceAttrs(*i.value, *i.pos, "while evaluating the value of a string context");
state.store->ensurePath(state.store->parseStorePath(name));
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
auto iter = i.value->attrs->find(sPath);
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, *iter->pos, "while evaluating the `path` attribute of a string context"))
context.insert(i.name);
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
context.emplace(name);
}
iter = i.value->attrs->find(sAllOutputs);
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, *iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(i.name)) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
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),
.errPos = *i.pos
.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, "while evaluating the `outputs` attribute of a string context");
if (iter->value->listSize() && !isDerivation(i.name)) {
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
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),
.errPos = *i.pos
.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, "while evaluating an output name within a string context");
context.insert(concatStrings("!", name, "!", i.name));
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
context.insert(concatStrings("!", outputName, "!", name));
}
}
}

View file

@ -0,0 +1,166 @@
#include "primops.hh"
#include "store-api.hh"
#include "make-content-addressed.hh"
#include "url.hh"
namespace nix {
static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure");
std::optional<std::string> fromStoreUrl;
std::optional<StorePath> fromPath;
bool toCA = false;
std::optional<StorePath> toPath;
for (auto & attr : *args[0]->attrs) {
const auto & attrName = state.symbols[attr.name];
if (attrName == "fromPath") {
PathSet context;
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context,
"while evaluating the 'fromPath' attribute passed to builtins.fetchClosure");
}
else if (attrName == "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,
"while evaluating the 'toPath' attribute passed to builtins.fetchClosure");
}
}
else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
"while evaluating the 'fromStore' attribute passed to builtins.fetchClosure");
else
throw Error({
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
.errPos = state.positions[pos]
});
}
if (!fromPath)
throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.errPos = state.positions[pos]
});
if (!fromStoreUrl)
throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.errPos = state.positions[pos]
});
auto parsedURL = parseURL(*fromStoreUrl);
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 = state.positions[pos]
});
if (!parsedURL.query.empty())
throw Error({
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
.errPos = state.positions[pos]
});
auto fromStore = openStore(parsedURL.to_string());
if (toCA) {
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 = state.positions[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 = state.positions[pos]
});
}
} else {
if (!state.store->isValidPath(*fromPath))
copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath });
toPath = fromPath;
}
/* In pure mode, require a CA path. */
if (evalSettings.pureEval) {
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(*toPath)),
.errPos = state.positions[pos]
});
}
auto toPathS = state.store->printStorePath(*toPath);
v.mkString(toPathS, {toPathS});
}
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 --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'
```
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.
This function is only available if you enable the experimental
feature `fetch-closure`.
)",
.fun = prim_fetchClosure,
.experimentalFeature = Xp::FetchClosure,
});
}

View file

@ -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;
@ -20,31 +20,31 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
if (args[0]->type() == nAttrs) {
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, "while evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned();
url = state.coerceToString(attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").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, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
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, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else
throw EvalError({
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name),
.errPos = *attr.pos
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
.errPos = state.positions[attr.pos]
});
}
if (url.empty())
throw EvalError({
.msg = hintfmt("'url' argument required"),
.errPos = pos
.errPos = state.positions[pos]
});
} else

View file

@ -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,43 +110,43 @@ 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, "while evaluating the `type` attribute passed to builtins.fetchTree");
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
} 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();
attrs.emplace(attr.name,
attr.name == "url"
auto s = state.coerceToString(attr.pos, *attr.value, context, false, false, "").toOwned();
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)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
throw Error({
.msg = hintfmt("attribute 'name' isnt supported in call to 'fetchTree'"),
.errPos = pos
.msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"),
.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;
@ -196,24 +196,24 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
if (args[0]->type() == nAttrs) {
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, "while evaluating the url we should fetch");
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the sha256 of the content we should fetch"), htSHA256);
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), htSHA256);
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, *attr.pos, "while evaluating the name of the content we should fetch");
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else
throw EvalError({
.msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who),
.errPos = *attr.pos
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
.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, "while evaluating the url we should fetch");
@ -260,7 +260,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, "");
}
@ -276,7 +276,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");
}
@ -327,7 +327,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 });
}

View file

@ -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, "while evaluating the argument passed to builtins.fromTOML");
@ -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]
});
}
}

View file

@ -5,44 +5,32 @@
#include <unordered_map>
#include "types.hh"
#include "chunked-vector.hh"
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
{
private:
const std::string * s; // pointer into SymbolTable
Symbol(const std::string * s) : s(s) { };
friend class SymbolTable;
private:
const std::string * s;
explicit SymbolStr(const std::string & symbol): s(&symbol) {}
public:
Symbol() : s(0) { };
bool operator == (const Symbol & s2) const
{
return s == s2.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
@ -55,51 +43,78 @@ public:
return *s;
}
bool set() const
{
return s;
}
friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol);
};
bool empty() const
{
return s->empty();
}
class Symbol
{
friend class SymbolTable;
friend std::ostream & operator << (std::ostream & str, const Symbol & sym);
private:
uint32_t id;
explicit Symbol(uint32_t id): id(id) {}
public:
Symbol() : id(0) {}
explicit operator bool() const { return id > 0; }
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, Symbol> symbols;
std::list<std::string> store;
std::unordered_map<std::string_view, std::pair<const std::string *, uint32_t>> symbols;
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 it->second;
if (it != symbols.end()) return Symbol(it->second.second + 1);
auto & rawSym = store.emplace_back(s);
return symbols.emplace(rawSym, Symbol(&rawSym)).first->second;
const auto & [rawSym, idx] = store.add(std::string(s));
symbols.emplace(rawSym, std::make_pair(&rawSym, idx));
return Symbol(idx + 1);
}
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 SymbolStr(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);
}
};

View file

@ -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();
@ -50,14 +50,14 @@ 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));
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,14 +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)]
});
throw 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);

View file

@ -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);
}

View file

@ -19,10 +19,10 @@ 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)
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
{
xmlAttrs["path"] = pos.file;
xmlAttrs["line"] = (format("%1%") % pos.line).str();
@ -36,25 +36,25 @@ 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 != ptr(&noPos)) posToXML(xmlAttrs, *a.pos);
if (location && a.pos) posToXML(state, 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,18 +134,18 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
}
XMLAttrs xmlAttrs;
if (location) posToXML(xmlAttrs, 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.empty()) 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;
}
@ -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");

View file

@ -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);
}

View file

@ -56,7 +56,10 @@ struct Expr;
struct ExprLambda;
struct PrimOp;
class Symbol;
class PosIdx;
struct Pos;
class StorePath;
class Store;
class EvalState;
class XMLWriter;
class JSONPlaceholder;
@ -64,6 +67,8 @@ class JSONPlaceholder;
typedef int64_t NixInt;
typedef double NixFloat;
typedef std::pair<StorePath, 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
@ -99,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()
{
@ -115,10 +120,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(const SymbolTable & symbols, std::ostream & str, std::set<const void *> * seen) const;
public:
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
// needed by callers into methods of this type
@ -243,11 +251,6 @@ public:
void mkStringMove(const char * s, const PathSet & context);
inline void mkString(const Symbol & s)
{
mkString(((const std::string &) s).c_str());
}
inline void mkPath(const char * s)
{
clearValue();
@ -361,14 +364,14 @@ 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
non-trivial. */
bool isTrivial() const;
std::vector<std::pair<Path, std::string>> getContext();
NixStringContext getContext(const Store &);
auto listItems()
{

View file

@ -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

View file

@ -21,16 +21,23 @@ 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, "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)
{
static const std::regex gitDirRegex("^(?:.*/)?\\.git$");
return not std::regex_match(path, gitDirRegex);
return baseNameOf(path) != ".git";
}
struct GitInputScheme : InputScheme
@ -152,13 +159,14 @@ struct GitInputScheme : InputScheme
{
auto sourcePath = getSourcePath(input);
assert(sourcePath);
auto gitDir = getGitDir();
runProgram("git", true,
{ "-C", *sourcePath, "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, "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
@ -177,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();
@ -189,8 +198,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},
@ -231,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
});
@ -253,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, "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.
@ -278,16 +295,18 @@ 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", gitDir, "ls-files", "-z" });
if (submodules)
gitOpts.emplace_back("--recurse-submodules");
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 +319,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, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
return {std::move(storePath), input};
}
@ -327,7 +346,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", gitDir, "rev-parse", *input.getRef() })), htSHA1).gitRev());
repoDir = actualUrl;
@ -343,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"});
@ -419,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);

View file

@ -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,11 @@ 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);
// 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;
}
}

View file

@ -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;
}
@ -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};
}
@ -201,8 +203,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},
@ -262,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 {

View file

@ -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));
}
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "realisation.hh"
#include "derived-path.hh"
#include <string>
#include <chrono>
@ -30,6 +31,8 @@ struct BuildResult
ResolvesToAlreadyValid,
NoSubstituters,
} status = MiscFailure;
// FIXME: include entire ErrorInfo object.
std::string errorMsg;
std::string toString() const {

View file

@ -204,10 +204,33 @@ void DerivationGoal::haveDerivation()
{
trace("have derivation");
if (drv->type() == DerivationType::CAFloating)
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
if (!drv->type().hasKnownOutputPaths())
settings.requireExperimentalFeature(Xp::CaDerivations);
retrySubstitution = false;
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)
@ -232,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. */
@ -268,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 ",
@ -311,18 +333,27 @@ 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. */
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, 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'",
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. */
@ -350,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
@ -426,7 +459,8 @@ void DerivationGoal::inputsRealised()
return;
}
if (retrySubstitution) {
if (retrySubstitution && !retriedSubstitution) {
retriedSubstitution = true;
haveDerivation();
return;
}
@ -440,19 +474,40 @@ 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);
},
[&](const DerivationType::Impure &) {
return true;
}
}, drvType.raw());
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,
@ -473,23 +528,15 @@ 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));
}
}
}
/* Second, the input sources. */
worker.store.computeFSClosure(drv->inputSrcs, inputPaths);
@ -501,7 +548,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 +955,7 @@ void DerivationGoal::buildDone()
st =
dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
statusOk(status) ? BuildResult::OutputRejected :
derivationIsImpure(derivationType) || diskFull ? BuildResult::TransientFailure :
!derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure :
BuildResult::PermanentFailure;
}
@ -919,9 +966,15 @@ void DerivationGoal::buildDone()
void DerivationGoal::resolvedFinished()
{
trace("resolved derivation finished");
assert(resolvedDrvGoal);
auto resolvedDrv = *resolvedDrvGoal->drv;
auto & resolvedResult = resolvedDrvGoal->buildResult;
DrvOutputs builtOutputs;
if (resolvedResult.success()) {
auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv);
StorePathSet outputPaths;
@ -931,30 +984,22 @@ void DerivationGoal::resolvedFinished()
if (realWantedOutputs.empty())
realWantedOutputs = resolvedDrv.outputNames();
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};
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);
if (!drv->type().isFixed())
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());
}
outputPaths.insert(realisation.outPath);
builtOutputs.emplace(realisation.id, realisation);
}
runPostBuildHook(
@ -963,16 +1008,11 @@ void DerivationGoal::resolvedFinished()
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));
}
@ -1221,7 +1261,8 @@ void DerivationGoal::flushLine()
std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap()
{
if (!useDerivation || drv->type() != DerivationType::CAFloating) {
assert(drv->type().isPure());
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 +1274,8 @@ std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDeri
OutputPathMap DerivationGoal::queryDerivationOutputMap()
{
if (!useDerivation || drv->type() != DerivationType::CAFloating) {
assert(drv->type().isPure());
if (!useDerivation || drv->type().hasKnownOutputPaths()) {
OutputPathMap res;
for (auto & [name, output] : drv->outputsAndOptPaths(worker.store))
res.insert_or_assign(name, *output.second);
@ -1246,6 +1288,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;
@ -1289,6 +1333,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.
@ -1326,9 +1371,7 @@ void DerivationGoal::done(
{
buildResult.status = status;
if (ex)
// FIXME: strip: "error: "
buildResult.errorMsg = ex->what();
amDone(buildResult.success() ? ecSuccess : ecFailed, ex);
buildResult.errorMsg = fmt("%s", normaltxt(ex->info().msg));
if (buildResult.status == BuildResult::TimedOut)
worker.timedOut = true;
if (buildResult.status == BuildResult::PermanentFailure)
@ -1355,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);
}
}

View file

@ -57,12 +57,21 @@ 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;
/* 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;
@ -220,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);
};

View file

@ -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

View file

@ -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()));

View file

@ -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()
{

View file

@ -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.isSandboxed() && !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.isSandboxed())
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
/* Make the closure of the inputs available in the chroot,
@ -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();
@ -796,7 +799,7 @@ void LocalDerivationGoal::startBuilder()
us.
*/
if (!(derivationIsImpure(derivationType)))
if (derivationType.isSandboxed())
privateNetwork = true;
userNamespaceSync.create();
@ -1049,7 +1052,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 +1063,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.isSandboxed()) {
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings()))
env[i] = getEnv(i).value_or("");
}
@ -1674,7 +1677,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.isSandboxed()) {
// Only use nss functions to resolve hosts and
// services. Dont use it for anything else that may
// be configured for this system. This limits the
@ -1918,7 +1921,7 @@ void LocalDerivationGoal::runChild()
sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
if (derivationIsImpure(derivationType))
if (!derivationType.isSandboxed())
sandboxProfile += "(import \"sandbox-network.sb\")\n";
/* Add the output paths we'll use at build-time to the chroot */
@ -2279,7 +2282,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 +2349,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 +2369,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 +2392,24 @@ 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);
[&](const DerivationOutput::Impure & doi) {
return newInfoFromCA(DerivationOutput::CAFloating {
.method = doi.method,
.hashType = doi.hashType,
});
},
}, output.raw());
/* FIXME: set proper permissions in restorePath() so
we don't have to do another traversal. */
@ -2609,10 +2619,13 @@ 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);
}
if (wantOutput(outputName, wantedOutputs))
builtOutputs.emplace(thisRealisation.id, thisRealisation);
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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)) {

View file

@ -13,12 +13,27 @@ 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
-- its 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 in (
select id from Realisations where outputPath = old.id
);
end;
create table if not exists RealisationsRefs (
referrer integer not null,
realisationReference integer,
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);

View file

@ -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;

View file

@ -11,72 +11,114 @@ 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);
[](const DerivationOutput::Impure &) -> std::optional<StorePath> {
return std::nullopt;
},
}, 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));
}
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::visit(overloaded {
[](const InputAddressed & ia) {
return false;
},
[](const ContentAddressed & ca) {
return true;
},
[](const Impure &) {
return true;
},
}, 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;
},
[](const Impure &) {
return false;
},
}, 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;
},
[](const Impure &) {
return false;
},
}, 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::isSandboxed() const
{
return std::visit(overloaded {
[](const InputAddressed & ia) {
return true;
},
[](const ContentAddressed & ca) {
return ca.sandboxed;
},
[](const Impure &) {
return false;
},
}, raw());
}
bool DerivationType::isPure() const
{
return std::visit(overloaded {
[](const InputAddressed & ia) {
return true;
},
[](const ContentAddressed & ca) {
return true;
},
[](const Impure &) {
return false;
},
}, raw());
}
@ -177,37 +219,36 @@ 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::Impure {
.method = std::move(method),
.hashType = std::move(hashType),
};
} else if (hash != "") {
validatePath(pathS);
return DerivationOutput {
.output = DerivationOutputCAFixed {
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 {
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 {
return DerivationOutput::InputAddressed {
.path = store.parseStorePath(pathS),
}
};
}
}
@ -335,27 +376,33 @@ 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, "");
},
[&](const DerivationOutputImpure & doi) {
// FIXME
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType));
s += ','; printUnquotedString(s, "impure");
}
}, i.second.output);
}, i.second.raw());
s += ')';
}
@ -419,49 +466,100 @@ 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 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;
} 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 DerivationOutputDeferred &) {
[&](const DerivationOutput::Deferred &) {
deferredIAOutputs.insert(i.first);
},
}, i.second.output);
[&](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()) {
return DerivationType::InputAddressed;
} 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,
};
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\"");
return DerivationType::CAFixed;
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
return DerivationType::CAFloating;
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) {
return DerivationType::DeferredInputAddressed;
} else {
throw Error("Can't mix derivation output types");
throw Error("single fixed output must be named \"out\"");
return DerivationType::ContentAddressed {
.sandboxed = false,
.fixed = true,
};
}
if (inputAddressedOutputs.empty()
&& fixedCAOutputs.empty()
&& !floatingCAOutputs.empty()
&& deferredIAOutputs.empty()
&& impureOutputs.empty())
return DerivationType::ContentAddressed {
.sandboxed = true,
.fixed = false,
};
if (inputAddressedOutputs.empty()
&& fixedCAOutputs.empty()
&& floatingCAOutputs.empty()
&& !deferredIAOutputs.empty()
&& impureOutputs.empty())
return DerivationType::InputAddressed {
.deferred = true,
};
if (inputAddressedOutputs.empty()
&& fixedCAOutputs.empty()
&& floatingCAOutputs.empty()
&& deferredIAOutputs.empty()
&& !impureOutputs.empty())
return DerivationType::Impure { };
throw Error("can't mix derivation output types");
}
@ -473,7 +571,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();
@ -508,91 +606,83 @@ 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 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<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) + ":"
+ store.printStorePath(dof.path(store, drv.name, i.first)));
outputHashes.insert_or_assign(i.first, std::move(hash));
}
return outputHashes;
}
case DerivationType::CAFloating:
kind = DrvHash::Kind::Deferred;
break;
case DerivationType::InputAddressed:
break;
case DerivationType::DeferredInputAddressed:
break;
return DrvHash {
.hashes = outputHashes,
.kind = DrvHash::Kind::Regular,
};
}
/* For other derivations, replace the inputs paths with recursive
calls to this function. */
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
"taint" the kind yet. */
return DrvHash::Kind::Regular;
},
[](const DerivationType::ContentAddressed & ca) {
return ca.fixed
? DrvHash::Kind::Regular
: DrvHash::Kind::Deferred;
},
[](const DerivationType::Impure &) -> DrvHash::Kind {
assert(false);
}
}, drv.type().raw());
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);
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);
}
},
}, res.raw());
}
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;
}
@ -619,7 +709,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(
@ -630,7 +721,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,27 +764,32 @@ 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);
[&](const DerivationOutput::Impure & doi) {
out << ""
<< (makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType))
<< "impure";
},
}, i.second.raw());
}
worker_proto::write(store, out, drv.inputSrcs);
out << drv.platform << drv.builder << drv.args;
@ -717,21 +814,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);
@ -740,69 +835,60 @@ 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)) {
auto & h = hashModulo.requireNoFixedNonDeferred();
if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) {
auto & h = hashModulo.hashes.at(outputName);
auto outPath = store.makeOutputPath(outputName, h, drv.name);
drv.env[outputName] = store.printStorePath(outPath);
output = DerivationOutput {
.output = DerivationOutputInputAddressed {
output = DerivationOutput::InputAddressed {
.path = std::move(outPath),
},
};
}
}
}
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)
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");
}

View file

@ -4,6 +4,7 @@
#include "types.hh"
#include "hash.hh"
#include "content-address.hh"
#include "repair-flag.hh"
#include "sync.hh"
#include <map>
@ -40,23 +41,47 @@ struct DerivationOutputCAFloating
};
/* Input-addressed output which depends on a (CA) derivation whose hash isn't
* known atm
* known yet.
*/
struct DerivationOutputDeferred {};
struct DerivationOutput
/* Impure output which is moved to a content-addressed location (like
CAFloating) but isn't registered as a realization.
*/
struct DerivationOutputImpure
{
std::variant<
/* information used for expected hash computation */
FileIngestionMethod method;
HashType hashType;
};
typedef std::variant<
DerivationOutputInputAddressed,
DerivationOutputCAFixed,
DerivationOutputCAFloating,
DerivationOutputDeferred
> output;
DerivationOutputDeferred,
DerivationOutputImpure
> _DerivationOutputRaw;
struct DerivationOutput : _DerivationOutputRaw
{
using Raw = _DerivationOutputRaw;
using Raw::Raw;
using InputAddressed = DerivationOutputInputAddressed;
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
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;
@ -72,30 +97,62 @@ 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,
struct DerivationType_ContentAddressed {
bool sandboxed;
bool fixed;
};
struct DerivationType_Impure {
};
typedef std::variant<
DerivationType_InputAddressed,
DerivationType_ContentAddressed,
DerivationType_Impure
> _DerivationTypeRaw;
struct DerivationType : _DerivationTypeRaw {
using Raw = _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? */
bool derivationIsCA(DerivationType);
bool isCA() const;
/* Is the content of the outputs fixed a-priori via a hash? Never true for
/* Is the content of the outputs fixed a-priori via a hash? Never true for
non-CA derivations. */
bool derivationIsFixed(DerivationType);
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 derivationIsImpure(DerivationType);
/* 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;
/* Does the derivation knows its own output paths?
* Only true when there's no floating-ca derivation involved in the closure.
/* 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?
Only true when there's no floating-ca derivation involved in the
closure, or if fixed output.
*/
bool derivationHasKnownOutputPaths(DerivationType);
bool hasKnownOutputPaths() const;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
};
struct BasicDerivation
{
@ -140,7 +197,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) { }
@ -150,8 +214,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,
@ -171,18 +233,20 @@ 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 {
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,
};
@ -191,28 +255,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.
@ -236,16 +278,18 @@ 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
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, DrvHashModulo> DrvHashes;
typedef std::map<StorePath, DrvHash> DrvHashes;
// FIXME: global, though at least thread-safe.
extern Sync<DrvHashes> drvHashes;
@ -275,4 +319,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;
}

View file

@ -1,4 +1,5 @@
#include "derived-path.hh"
#include "derivations.hh"
#include "store-api.hh"
#include <nlohmann/json.hpp>

View file

@ -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<

View file

@ -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

View file

@ -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. A value of 0 means no limit.
)"};
Setting<unsigned long> stalledDownloadTimeout{
@ -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);

View file

@ -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,21 @@ 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 in (
select id from Realisations where outputPath = old.id
);
end;
-- used by deletion trigger
create index if not exists IndexRealisationsRefsRealisationReference on RealisationsRefs(realisationReference);
)");
txn.commit();
}
writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
lockFile(lockFd.get(), ltRead, true);
}
@ -482,18 +497,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) {
@ -695,31 +710,34 @@ 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 DerivationOutputInputAddressed & doia) {
if (!h) {
[&](const DerivationOutput::InputAddressed & doia) {
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));
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 &) {
/* Nothing to check */
},
}, i.second.output);
[&](const DerivationOutput::Impure &) {
/* Nothing to check */
},
}, i.second.raw());
}
}

View file

@ -0,0 +1,80 @@
#include "make-content-addressed.hh"
#include "references.hh"
namespace nix {
std::map<StorePath, StorePath> makeContentAddressed(
Store & srcStore,
Store & dstStore,
const StorePathSet & storePaths)
{
StorePathSet closure;
srcStore.computeFSClosure(storePaths, closure);
auto paths = srcStore.topoSortPaths(closure);
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 narModuloHash = hashModuloSink.finish().first;
auto dstPath = dstStore.makeFixedOutputPath(
FileIngestionMethod::Recursive, narModuloHash, path.name(), references, hasSelfReference);
printInfo("rewriting '%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 = narModuloHash,
};
StringSource source(sink2.s);
dstStore.addToStore(info, source);
remappings.insert_or_assign(std::move(path), std::move(info.path));
}
return remappings;
}
}

View file

@ -0,0 +1,12 @@
#pragma once
#include "store-api.hh"
namespace nix {
std::map<StorePath, StorePath> makeContentAddressed(
Store & srcStore,
Store & dstStore,
const StorePathSet & storePaths);
}

View file

@ -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;
@ -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' isnt 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);
}
}

View file

@ -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;
}

View file

@ -1,5 +1,6 @@
#pragma once
#include "derivations.hh"
#include "store-api.hh"
#include <nlohmann/json_fwd.hpp>

View file

@ -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));

View file

@ -58,6 +58,8 @@ public:
}
static StorePath dummy;
static StorePath random(std::string_view name);
};
typedef std::set<StorePath> StorePathSet;

View file

@ -0,0 +1,7 @@
#pragma once
namespace nix {
enum RepairFlag : bool { NoRepair = false, Repair = true };
}

View file

@ -8,22 +8,32 @@
namespace nix {
[[noreturn]] void throwSQLiteError(sqlite3 * db, const FormatOrString & fs)
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 (!path) path = "(in-memory)";
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
throw SQLiteBusy(
auto exp = SQLiteBusy(path, err, exterr, std::move(hf));
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, std::move(hf));
}
SQLite::SQLite(const Path & path, bool create)
@ -37,7 +47,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 +56,7 @@ SQLite::~SQLite()
{
try {
if (db && sqlite3_close(db) != SQLITE_OK)
throwSQLiteError(db, "closing database");
SQLiteError::throw_(db, "closing database");
} catch (...) {
ignoreException();
}
@ -62,7 +72,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 +86,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 +95,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 +119,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 +129,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 +139,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 +148,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 +162,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 +195,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 +210,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();
}
@ -215,7 +225,6 @@ void handleSQLiteBusy(const SQLiteBusy & e)
if (now > lastWarned + 10) {
lastWarned = now;
logWarning({
.name = "Sqlite busy",
.msg = hintfmt(e.what())
});
}

View file

@ -96,10 +96,30 @@ 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) {
throw_(db, hintfmt(fs, args...));
}
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...))
{ }
[[noreturn]] static void throw_(sqlite3 * db, hintformat && hf);
};
MakeError(SQLiteBusy, SQLiteError);
void handleSQLiteBusy(const SQLiteBusy & e);

View file

@ -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"

View file

@ -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;

View file

@ -127,9 +127,9 @@ 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 (flag.completer)
flag.completer(n, *prefix);
}
args.push_back(*pos++);
@ -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 (auto prefix = needsCompletion(s)) {
ss.push_back(*prefix);
if (exp.completer)
if (auto prefix = needsCompletion(s))
exp.completer(n, *prefix);
} else
ss.push_back(s);
}
exp.handler.fun(ss);
expectedArgs.pop_front();
@ -279,21 +282,22 @@ 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 = 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]);
}
globfree(&globbuf);
}
globfree(&globbuf);
}
void completePath(size_t, std::string_view prefix)
@ -322,11 +326,6 @@ 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()) {
std::set<std::string> commandNames;
@ -337,6 +336,11 @@ MultiCommand::MultiCommand(const Commands & commands_)
}
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);
}}
});

View file

@ -0,0 +1,68 @@
#pragma once
#include <cstdint>
#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);
}
};
}

View file

@ -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.
@ -22,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_;
}
}

View file

@ -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 = "";
return *this;
}
@ -109,7 +105,6 @@ struct Trace {
struct ErrorInfo {
Verbosity level;
std::string name; // FIXME: rename
hintformat msg;
std::optional<ErrPos> errPos;
std::list<Trace> traces;
@ -164,8 +159,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(); }
@ -181,12 +174,12 @@ public:
}
template<typename... Args>
BaseError & addTrace(std::optional<ErrPos> e, const std::string_view & fs, const Args & ... args)
void addTrace(std::optional<ErrPos> e, std::string_view fs, const Args & ... args)
{
return addTrace(e, hintfmt(std::string(fs), args...));
addTrace(e, hintfmt(std::string(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(); }
};
@ -196,7 +189,6 @@ public:
{ \
public: \
using superClass::superClass; \
virtual const char* sname() const override { return #newClass; } \
}
MakeError(Error, BaseError);
@ -216,8 +208,6 @@ public:
auto hf = hintfmt(args...);
err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
}
virtual const char* sname() const override { return "SysError"; }
};
}

View file

@ -7,10 +7,12 @@ 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" },
{ Xp::NoUrlLiterals, "no-url-literals" },
{ Xp::FetchClosure, "fetch-closure" },
};
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
@ -56,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);
}
}

Some files were not shown because too many files have changed in this diff Show more