Merge remote-tracking branch 'upstream/master' into indexed-store-path-outputs

This commit is contained in:
John Ericson 2022-05-12 19:13:33 +00:00
commit b18720ee17
137 changed files with 4190 additions and 1583 deletions

View file

@ -100,7 +100,7 @@ jobs:
- run: docker tag nix:$NIX_VERSION nixos/nix:$NIX_VERSION - run: docker tag nix:$NIX_VERSION nixos/nix:$NIX_VERSION
- run: docker tag nix:$NIX_VERSION nixos/nix:master - run: docker tag nix:$NIX_VERSION nixos/nix:master
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}

2
.gitignore vendored
View file

@ -35,6 +35,7 @@ perl/Makefile.config
/src/libexpr/parser-tab.hh /src/libexpr/parser-tab.hh
/src/libexpr/parser-tab.output /src/libexpr/parser-tab.output
/src/libexpr/nix.tbl /src/libexpr/nix.tbl
/src/libexpr/tests/libexpr-tests
# /src/libstore/ # /src/libstore/
*.gen.* *.gen.*
@ -79,6 +80,7 @@ perl/Makefile.config
/tests/shell.drv /tests/shell.drv
/tests/config.nix /tests/config.nix
/tests/ca/config.nix /tests/ca/config.nix
/tests/repl-result-out
# /tests/lang/ # /tests/lang/
/tests/lang/*.out /tests/lang/*.out

View file

@ -8,6 +8,7 @@ makefiles = \
src/libfetchers/local.mk \ src/libfetchers/local.mk \
src/libmain/local.mk \ src/libmain/local.mk \
src/libexpr/local.mk \ src/libexpr/local.mk \
src/libexpr/tests/local.mk \
src/libcmd/local.mk \ src/libcmd/local.mk \
src/nix/local.mk \ src/nix/local.mk \
src/resolve-system-dependencies/local.mk \ src/resolve-system-dependencies/local.mk \

View file

@ -1,4 +1,29 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
* Nix now provides better integration with zsh's run-help feature. It is now
included in the Nix installation in the form of an autoloadable shell
function, run-help-nix. It picks up Nix subcommands from the currently typed
in command and directs the user to the associated man pages.
* `nix repl` has a new build-'n-link (`:bl`) command that builds a derivation
while creating GC root symlinks.
* 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`.
* You can now specify which outputs of a derivation `nix` should
operate on using the syntax `installable^outputs`,
e.g. `nixpkgs#glibc^dev,static` or `nixpkgs#glibc^*`. By default,
`nix` will use the outputs specified by the derivation's
`meta.outputsToInstall` attribute if it exists, or all outputs
otherwise.
Selecting derivation outputs using the attribute selection syntax
(e.g. `nixpkgs#glibc.dev`) no longer works.
* Add experimental *indexed store derivations* installable syntax, part of the * Add experimental *indexed store derivations* installable syntax, part of the
the `computed-derivations` experimental feature. the `computed-derivations` experimental feature.

View file

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

View file

@ -23,7 +23,7 @@
crossSystems = [ "armv6l-linux" "armv7l-linux" ]; crossSystems = [ "armv6l-linux" "armv7l-linux" ];
stdenvs = [ "gccStdenv" "clangStdenv" "clang11Stdenv" "stdenv" ]; stdenvs = [ "gccStdenv" "clangStdenv" "clang11Stdenv" "stdenv" "libcxxStdenv" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
forAllSystemsAndStdenvs = f: forAllSystems (system: forAllSystemsAndStdenvs = f: forAllSystems (system:

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

@ -197,17 +197,17 @@ void StorePathCommand::run(ref<Store> store, std::vector<StorePath> && storePath
run(store, *storePaths.begin()); 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 editor = getEnv("EDITOR").value_or("cat");
auto args = tokenizeString<Strings>(editor); auto args = tokenizeString<Strings>(editor);
if (pos.line > 0 && ( if (line > 0 && (
editor.find("emacs") != std::string::npos || editor.find("emacs") != std::string::npos ||
editor.find("nano") != std::string::npos || editor.find("nano") != std::string::npos ||
editor.find("vim") != std::string::npos || editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos)) editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", pos.line)); args.push_back(fmt("+%d", line));
args.push_back(pos.file); args.push_back(file);
return args; return args;
} }

View file

@ -219,7 +219,7 @@ static RegisterCommand registerCommand2(std::vector<std::string> && name)
/* Helper function to generate args that invoke $EDITOR on /* Helper function to generate args that invoke $EDITOR on
filename:lineno. */ filename:lineno. */
Strings editorFor(const Pos & pos); Strings editorFor(const Path & file, uint32_t line);
struct MixProfile : virtual StoreCommand struct MixProfile : virtual StoreCommand
{ {

View file

@ -235,7 +235,7 @@ void SourceExprCommand::completeInstallable(std::string_view prefix)
if (v2.type() == nAttrs) { if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) { for (auto & i : *v2.attrs) {
std::string name = i.name; std::string name = state->symbols[i.name];
if (name.find(searchWord) == 0) { if (name.find(searchWord) == 0) {
if (prefix_ == "") if (prefix_ == "")
completions->add(name); completions->add(name);
@ -291,7 +291,7 @@ void completeFlakeRefWithFragment(
std::string lastAttr; std::string lastAttr;
if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) { if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) {
lastAttr = attrPath.back(); lastAttr = evalState->symbols[attrPath.back()];
attrPath.pop_back(); attrPath.pop_back();
} }
@ -299,11 +299,11 @@ void completeFlakeRefWithFragment(
if (!attr) continue; if (!attr) continue;
for (auto & attr2 : (*attr)->getAttrs()) { for (auto & attr2 : (*attr)->getAttrs()) {
if (hasPrefix(attr2, lastAttr)) { if (hasPrefix(evalState->symbols[attr2], lastAttr)) {
auto attrPath2 = (*attr)->getAttrPath(attr2); auto attrPath2 = (*attr)->getAttrPath(attr2);
/* Strip the attrpath prefix. */ /* Strip the attrpath prefix. */
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size()); attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
completions->add(flakeRefS + "#" + concatStringsSep(".", attrPath2)); completions->add(flakeRefS + "#" + concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
} }
} }
} }
@ -460,9 +460,7 @@ DerivedPaths InstallableValue::toDerivedPaths()
// Group by derivation, helps with .all in particular // Group by derivation, helps with .all in particular
for (auto & drv : toDerivations()) { for (auto & drv : toDerivations()) {
auto outputName = drv.outputName; for (auto & outputName : drv.outputsToInstall)
if (outputName == "")
throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath));
drvsToOutputs[drv.drvPath].insert(outputName); drvsToOutputs[drv.drvPath].insert(outputName);
drvsToCopy.insert(drv.drvPath); drvsToCopy.insert(drv.drvPath);
} }
@ -486,14 +484,24 @@ struct InstallableAttrPath : InstallableValue
SourceExprCommand & cmd; SourceExprCommand & cmd;
RootValue v; RootValue v;
std::string attrPath; std::string attrPath;
OutputsSpec outputsSpec;
InstallableAttrPath(ref<EvalState> state, SourceExprCommand & cmd, Value * v, const std::string & attrPath) InstallableAttrPath(
: InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath) ref<EvalState> state,
SourceExprCommand & cmd,
Value * v,
const std::string & attrPath,
OutputsSpec outputsSpec)
: InstallableValue(state)
, cmd(cmd)
, v(allocRootValue(v))
, attrPath(attrPath)
, outputsSpec(std::move(outputsSpec))
{ } { }
std::string what() const override { return attrPath; } std::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); auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
state.forceValue(*vRes, pos); state.forceValue(*vRes, pos);
@ -517,7 +525,19 @@ std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations
auto drvPath = drvInfo.queryDrvPath(); auto drvPath = drvInfo.queryDrvPath();
if (!drvPath) if (!drvPath)
throw Error("'%s' is not a derivation", what()); throw Error("'%s' is not a derivation", what());
res.push_back({ *drvPath, drvInfo.queryOutputName() });
std::set<std::string> outputsToInstall;
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
outputsToInstall = *outputNames;
else
for (auto & output : drvInfo.queryOutputs(false, std::get_if<DefaultOutputs>(&outputsSpec)))
outputsToInstall.insert(output.first);
res.push_back(DerivationInfo {
.drvPath = *drvPath,
.outputsToInstall = std::move(outputsToInstall)
});
} }
return res; return res;
@ -594,6 +614,7 @@ InstallableFlake::InstallableFlake(
ref<EvalState> state, ref<EvalState> state,
FlakeRef && flakeRef, FlakeRef && flakeRef,
std::string_view fragment, std::string_view fragment,
OutputsSpec outputsSpec,
Strings attrPaths, Strings attrPaths,
Strings prefixes, Strings prefixes,
const flake::LockFlags & lockFlags) const flake::LockFlags & lockFlags)
@ -601,6 +622,7 @@ InstallableFlake::InstallableFlake(
flakeRef(flakeRef), flakeRef(flakeRef),
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}), attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
prefixes(fragment == "" ? Strings{} : prefixes), prefixes(fragment == "" ? Strings{} : prefixes),
outputsSpec(std::move(outputsSpec)),
lockFlags(lockFlags) lockFlags(lockFlags)
{ {
if (cmd && cmd->getAutoArgs(*state)->size()) if (cmd && cmd->getAutoArgs(*state)->size())
@ -618,9 +640,29 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
auto drvPath = attr->forceDerivation(); auto drvPath = attr->forceDerivation();
std::set<std::string> outputsToInstall;
if (auto aMeta = attr->maybeGetAttr(state->sMeta))
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
for (auto & s : aOutputsToInstall->getListOfStrings())
outputsToInstall.insert(s);
if (outputsToInstall.empty() || std::get_if<AllOutputs>(&outputsSpec)) {
outputsToInstall.clear();
if (auto aOutputs = attr->maybeGetAttr(state->sOutputs))
for (auto & s : aOutputs->getListOfStrings())
outputsToInstall.insert(s);
}
if (outputsToInstall.empty())
outputsToInstall.insert("out");
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
outputsToInstall = *outputNames;
auto drvInfo = DerivationInfo { auto drvInfo = DerivationInfo {
std::move(drvPath), .drvPath = std::move(drvPath),
attr->getAttr(state->sOutputName)->getString() .outputsToInstall = std::move(outputsToInstall),
}; };
return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)}; return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};
@ -633,7 +675,7 @@ std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
return res; return res;
} }
std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state) std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
{ {
return {&getCursor(state)->forceValue(), noPos}; return {&getCursor(state)->forceValue(), noPos};
} }
@ -743,8 +785,14 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
state->eval(e, *vFile); state->eval(e, *vFile);
} }
for (auto & s : ss) for (auto & s : ss) {
result.push_back(std::make_shared<InstallableAttrPath>(state, *this, vFile, s == "." ? "" : s)); auto [prefix, outputsSpec] = parseOutputsSpec(s);
result.push_back(
std::make_shared<InstallableAttrPath>(
state, *this, vFile,
prefix == "." ? "" : prefix,
outputsSpec));
}
} else { } else {
@ -777,12 +825,13 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
} }
try { try {
auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath(".")); auto [flakeRef, fragment, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(s, absPath("."));
result.push_back(std::make_shared<InstallableFlake>( result.push_back(std::make_shared<InstallableFlake>(
this, this,
getEvalState(), getEvalState(),
std::move(flakeRef), std::move(flakeRef),
fragment, fragment,
outputsSpec,
getDefaultFlakeAttrPaths(), getDefaultFlakeAttrPaths(),
getDefaultFlakeAttrPathPrefixes(), getDefaultFlakeAttrPathPrefixes(),
lockFlags)); lockFlags));
@ -856,26 +905,28 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
auto drvOutputs = drv.outputsAndOptPaths(*store); auto drvOutputs = drv.outputsAndOptPaths(*store);
for (auto & output : bfd.outputs) { for (auto & output : bfd.outputs) {
if (!outputHashes.count(output)) auto outputHash = get(outputHashes, output);
if (!outputHash)
throw Error( throw Error(
"the derivation '%s' doesn't have an output named '%s'", "the derivation '%s' doesn't have an output named '%s'",
store->printStorePath(bfd.drvPath), output); store->printStorePath(bfd.drvPath), output);
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
DrvOutput outputId { outputHashes.at(output), output }; DrvOutput outputId { *outputHash, output };
auto realisation = store->queryRealisation(outputId); auto realisation = store->queryRealisation(outputId);
if (!realisation) if (!realisation)
throw Error( throw Error(
"cannot operate on an output of unbuilt " "cannot operate on an output of the "
"content-addressed derivation '%s'", "unbuilt derivation '%s'",
outputId.to_string()); outputId.to_string());
outputs.insert_or_assign(output, realisation->outPath); outputs.insert_or_assign(output, realisation->outPath);
} else { } else {
// If ca-derivations isn't enabled, assume that // If ca-derivations isn't enabled, assume that
// the output path is statically known. // the output path is statically known.
assert(drvOutputs.count(output)); auto drvOutput = get(drvOutputs, output);
assert(drvOutputs.at(output).second); assert(drvOutput);
assert(drvOutput->second);
outputs.insert_or_assign( outputs.insert_or_assign(
output, *drvOutputs.at(output).second); output, *drvOutput->second);
} }
} }
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }}); res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});

View file

@ -68,7 +68,7 @@ struct Installable
UnresolvedApp toApp(EvalState & state); 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()); throw Error("argument '%s' cannot be evaluated", what());
} }
@ -141,7 +141,7 @@ struct InstallableValue : Installable
struct DerivationInfo struct DerivationInfo
{ {
StorePath drvPath; StorePath drvPath;
std::string outputName; std::set<std::string> outputsToInstall;
}; };
virtual std::vector<DerivationInfo> toDerivations() = 0; virtual std::vector<DerivationInfo> toDerivations() = 0;
@ -156,6 +156,7 @@ struct InstallableFlake : InstallableValue
FlakeRef flakeRef; FlakeRef flakeRef;
Strings attrPaths; Strings attrPaths;
Strings prefixes; Strings prefixes;
OutputsSpec outputsSpec;
const flake::LockFlags & lockFlags; const flake::LockFlags & lockFlags;
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake; mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
@ -164,6 +165,7 @@ struct InstallableFlake : InstallableValue
ref<EvalState> state, ref<EvalState> state,
FlakeRef && flakeRef, FlakeRef && flakeRef,
std::string_view fragment, std::string_view fragment,
OutputsSpec outputsSpec,
Strings attrPaths, Strings attrPaths,
Strings prefixes, Strings prefixes,
const flake::LockFlags & lockFlags); const flake::LockFlags & lockFlags);
@ -178,7 +180,7 @@ struct InstallableFlake : InstallableValue
std::vector<DerivationInfo> toDerivations() override; std::vector<DerivationInfo> toDerivations() override;
std::pair<Value *, Pos> toValue(EvalState & state) override; std::pair<Value *, PosIdx> toValue(EvalState & state) override;
/* Get a cursor to every attrpath in getActualAttrPaths() that /* Get a cursor to every attrpath in getActualAttrPaths() that
exists. */ exists. */

View file

@ -9,10 +9,12 @@ namespace nix {
std::string renderMarkdownToTerminal(std::string_view markdown) std::string renderMarkdownToTerminal(std::string_view markdown)
{ {
int windowWidth = getWindowSize().second;
struct lowdown_opts opts { struct lowdown_opts opts {
.type = LOWDOWN_TERM, .type = LOWDOWN_TERM,
.maxdepth = 20, .maxdepth = 20,
.cols = std::max(getWindowSize().second, (unsigned short) 80), .cols = (size_t) std::max(windowWidth - 5, 60),
.hmargin = 0, .hmargin = 0,
.vmargin = 0, .vmargin = 0,
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES, .feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,

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) Bindings & autoArgs, Value & vIn)
{ {
Strings tokens = parseAttrPath(attrPath); Strings tokens = parseAttrPath(attrPath);
Value * v = &vIn; Value * v = &vIn;
Pos pos = noPos; PosIdx pos = noPos;
for (auto & attr : tokens) { for (auto & attr : tokens) {
@ -77,13 +77,13 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string &
if (a == v->attrs->end()) { if (a == v->attrs->end()) {
std::set<std::string> attrNames; std::set<std::string> attrNames;
for (auto & attr : *v->attrs) for (auto & attr : *v->attrs)
attrNames.insert(attr.name); attrNames.insert(state.symbols[attr.name]);
auto suggestions = Suggestions::bestMatches(attrNames, attr); auto suggestions = Suggestions::bestMatches(attrNames, attr);
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath); throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
} }
v = &*a->value; v = &*a->value;
pos = *a->pos; pos = a->pos;
} }
else { 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; Value * v2;
try { try {
@ -132,9 +132,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what)
throw ParseError("cannot parse line number '%s'", pos); throw ParseError("cannot parse line number '%s'", pos);
} }
Symbol file = state.symbols.create(filename); return { std::move(filename), lineno };
return { foFile, file, lineno, 0 };
} }

View file

@ -10,14 +10,14 @@ namespace nix {
MakeError(AttrPathNotFound, Error); MakeError(AttrPathNotFound, Error);
MakeError(NoPositionInfo, Error); MakeError(NoPositionInfo, Error);
std::pair<Value *, Pos> findAlongAttrPath( std::pair<Value *, PosIdx> findAlongAttrPath(
EvalState & state, EvalState & state,
const std::string & attrPath, const std::string & attrPath,
Bindings & autoArgs, Bindings & autoArgs,
Value & vIn); Value & vIn);
/* Heuristic to find the filename and lineno or a nix value. */ /* 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); 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 /* Create a new attribute named 'name' on an existing attribute set stored
in 'vAttrs' and return the newly allocated Value which is associated with in 'vAttrs' and return the newly allocated Value which is associated with
this attribute. */ this attribute. */
Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name) Value * EvalState::allocAttr(Value & vAttrs, Symbol name)
{ {
Value * v = allocValue(); Value * v = allocValue();
vAttrs.attrs->push_back(Attr(name, v)); 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(); auto value = state.allocValue();
bindings->push_back(Attr(name, value, pos)); 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); return alloc(state.symbols.create(name), pos);
} }

View file

@ -15,18 +15,27 @@ struct Value;
/* Map one attribute name to its value. */ /* Map one attribute name to its value. */
struct Attr 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; Symbol name;
PosIdx pos;
Value * value; Value * value;
ptr<Pos> pos; Attr(Symbol name, Value * value, PosIdx pos = noPos)
Attr(Symbol name, Value * value, ptr<Pos> pos = ptr(&noPos)) : name(name), pos(pos), value(value) { };
: name(name), value(value), pos(pos) { }; Attr() { };
Attr() : pos(&noPos) { };
bool operator < (const Attr & a) const bool operator < (const Attr & a) const
{ {
return name < a.name; 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 /* 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 by its size and its capacity, the capacity being the number of Attr
elements allocated after this structure, while the size corresponds to elements allocated after this structure, while the size corresponds to
@ -35,13 +44,13 @@ class Bindings
{ {
public: public:
typedef uint32_t size_t; typedef uint32_t size_t;
ptr<Pos> pos; PosIdx pos;
private: private:
size_t size_, capacity_; size_t size_, capacity_;
Attr attrs[0]; 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; Bindings(const Bindings & bindings) = delete;
public: public:
@ -57,7 +66,7 @@ public:
attrs[size_++] = attr; attrs[size_++] = attr;
} }
iterator find(const Symbol & name) iterator find(Symbol name)
{ {
Attr key(name, 0); Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key); iterator i = std::lower_bound(begin(), end(), key);
@ -65,7 +74,7 @@ public:
return end(); return end();
} }
Attr * get(const Symbol & name) Attr * get(Symbol name)
{ {
Attr key(name, 0); Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key); iterator i = std::lower_bound(begin(), end(), key);
@ -73,18 +82,6 @@ public:
return nullptr; 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 begin() { return &attrs[0]; }
iterator end() { return &attrs[size_]; } iterator end() { return &attrs[size_]; }
@ -98,14 +95,15 @@ public:
size_t capacity() { return capacity_; } size_t capacity() { return capacity_; }
/* Returns the attributes in lexicographically sorted order. */ /* 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; std::vector<const Attr *> res;
res.reserve(size_); res.reserve(size_);
for (size_t n = 0; n < size_; n++) for (size_t n = 0; n < size_; n++)
res.emplace_back(&attrs[n]); res.emplace_back(&attrs[n]);
std::sort(res.begin(), res.end(), [](const Attr * a, const Attr * b) { std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) {
return (const std::string &) a->name < (const std::string &) b->name; std::string_view sa = symbols[a->name], sb = symbols[b->name];
return sa < sb;
}); });
return res; return res;
} }
@ -130,7 +128,7 @@ public:
: bindings(bindings), state(state) : 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)); insert(Attr(name, value, pos));
} }
@ -145,9 +143,9 @@ public:
bindings->push_back(attr); 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() Bindings * finish()
{ {

View file

@ -35,13 +35,19 @@ struct AttrDb
std::unique_ptr<Sync<State>> _state; std::unique_ptr<Sync<State>> _state;
AttrDb(const Store & cfg, const Hash & fingerprint) SymbolTable & symbols;
AttrDb(
const Store & cfg,
const Hash & fingerprint,
SymbolTable & symbols)
: cfg(cfg) : cfg(cfg)
, _state(std::make_unique<Sync<State>>()) , _state(std::make_unique<Sync<State>>())
, symbols(symbols)
{ {
auto state(_state->lock()); auto state(_state->lock());
Path cacheDir = getCacheDir() + "/nix/eval-cache-v2"; Path cacheDir = getCacheDir() + "/nix/eval-cache-v3";
createDirs(cacheDir); createDirs(cacheDir);
Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite"; Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite";
@ -100,7 +106,7 @@ struct AttrDb
state->insertAttribute.use() state->insertAttribute.use()
(key.first) (key.first)
(key.second) (symbols[key.second])
(AttrType::FullAttrs) (AttrType::FullAttrs)
(0, false).exec(); (0, false).exec();
@ -110,7 +116,7 @@ struct AttrDb
for (auto & attr : attrs) for (auto & attr : attrs)
state->insertAttribute.use() state->insertAttribute.use()
(rowId) (rowId)
(attr) (symbols[attr])
(AttrType::Placeholder) (AttrType::Placeholder)
(0, false).exec(); (0, false).exec();
@ -135,14 +141,14 @@ struct AttrDb
} }
state->insertAttributeWithContext.use() state->insertAttributeWithContext.use()
(key.first) (key.first)
(key.second) (symbols[key.second])
(AttrType::String) (AttrType::String)
(s) (s)
(ctx).exec(); (ctx).exec();
} else { } else {
state->insertAttribute.use() state->insertAttribute.use()
(key.first) (key.first)
(key.second) (symbols[key.second])
(AttrType::String) (AttrType::String)
(s).exec(); (s).exec();
} }
@ -161,7 +167,7 @@ struct AttrDb
state->insertAttribute.use() state->insertAttribute.use()
(key.first) (key.first)
(key.second) (symbols[key.second])
(AttrType::Bool) (AttrType::Bool)
(b ? 1 : 0).exec(); (b ? 1 : 0).exec();
@ -169,6 +175,24 @@ struct AttrDb
}); });
} }
AttrId setListOfStrings(
AttrKey key,
const std::vector<std::string> & l)
{
return doSQLite([&]()
{
auto state(_state->lock());
state->insertAttribute.use()
(key.first)
(symbols[key.second])
(AttrType::ListOfStrings)
(concatStringsSep("\t", l)).exec();
return state->db.getLastInsertedRowId();
});
}
AttrId setPlaceholder(AttrKey key) AttrId setPlaceholder(AttrKey key)
{ {
return doSQLite([&]() return doSQLite([&]()
@ -177,7 +201,7 @@ struct AttrDb
state->insertAttribute.use() state->insertAttribute.use()
(key.first) (key.first)
(key.second) (symbols[key.second])
(AttrType::Placeholder) (AttrType::Placeholder)
(0, false).exec(); (0, false).exec();
@ -193,7 +217,7 @@ struct AttrDb
state->insertAttribute.use() state->insertAttribute.use()
(key.first) (key.first)
(key.second) (symbols[key.second])
(AttrType::Missing) (AttrType::Missing)
(0, false).exec(); (0, false).exec();
@ -209,7 +233,7 @@ struct AttrDb
state->insertAttribute.use() state->insertAttribute.use()
(key.first) (key.first)
(key.second) (symbols[key.second])
(AttrType::Misc) (AttrType::Misc)
(0, false).exec(); (0, false).exec();
@ -225,7 +249,7 @@ struct AttrDb
state->insertAttribute.use() state->insertAttribute.use()
(key.first) (key.first)
(key.second) (symbols[key.second])
(AttrType::Failed) (AttrType::Failed)
(0, false).exec(); (0, false).exec();
@ -233,13 +257,11 @@ struct AttrDb
}); });
} }
std::optional<std::pair<AttrId, AttrValue>> getAttr( std::optional<std::pair<AttrId, AttrValue>> getAttr(AttrKey key)
AttrKey key,
SymbolTable & symbols)
{ {
auto state(_state->lock()); 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 {}; if (!queryAttribute.next()) return {};
auto rowId = (AttrType) queryAttribute.getInt(0); auto rowId = (AttrType) queryAttribute.getInt(0);
@ -253,7 +275,7 @@ struct AttrDb
std::vector<Symbol> attrs; std::vector<Symbol> attrs;
auto queryAttributes(state->queryAttributes.use()(rowId)); auto queryAttributes(state->queryAttributes.use()(rowId));
while (queryAttributes.next()) while (queryAttributes.next())
attrs.push_back(symbols.create(queryAttributes.getStr(0))); attrs.emplace_back(symbols.create(queryAttributes.getStr(0)));
return {{rowId, attrs}}; return {{rowId, attrs}};
} }
case AttrType::String: { case AttrType::String: {
@ -265,6 +287,8 @@ struct AttrDb
} }
case AttrType::Bool: case AttrType::Bool:
return {{rowId, queryAttribute.getInt(2) != 0}}; return {{rowId, queryAttribute.getInt(2) != 0}};
case AttrType::ListOfStrings:
return {{rowId, tokenizeString<std::vector<std::string>>(queryAttribute.getStr(2), "\t")}};
case AttrType::Missing: case AttrType::Missing:
return {{rowId, missing_t()}}; return {{rowId, missing_t()}};
case AttrType::Misc: case AttrType::Misc:
@ -277,10 +301,13 @@ struct AttrDb
} }
}; };
static std::shared_ptr<AttrDb> makeAttrDb(const Store & cfg, const Hash & fingerprint) static std::shared_ptr<AttrDb> makeAttrDb(
const Store & cfg,
const Hash & fingerprint,
SymbolTable & symbols)
{ {
try { try {
return std::make_shared<AttrDb>(cfg, fingerprint); return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
} catch (SQLiteError &) { } catch (SQLiteError &) {
ignoreException(); ignoreException();
return nullptr; return nullptr;
@ -291,7 +318,7 @@ EvalCache::EvalCache(
std::optional<std::reference_wrapper<const Hash>> useCache, std::optional<std::reference_wrapper<const Hash>> useCache,
EvalState & state, EvalState & state,
RootLoader rootLoader) RootLoader rootLoader)
: db(useCache ? makeAttrDb(*state.store, *useCache) : nullptr) : db(useCache ? makeAttrDb(*state.store, *useCache, state.symbols) : nullptr)
, state(state) , state(state)
, rootLoader(rootLoader) , rootLoader(rootLoader)
{ {
@ -327,8 +354,7 @@ AttrKey AttrCursor::getKey()
if (!parent) if (!parent)
return {0, root->state.sEpsilon}; return {0, root->state.sEpsilon};
if (!parent->first->cachedValue) { if (!parent->first->cachedValue) {
parent->first->cachedValue = root->db->getAttr( parent->first->cachedValue = root->db->getAttr(parent->first->getKey());
parent->first->getKey(), root->state.symbols);
assert(parent->first->cachedValue); assert(parent->first->cachedValue);
} }
return {parent->first->cachedValue->first, parent->second}; return {parent->first->cachedValue->first, parent->second};
@ -369,17 +395,17 @@ std::vector<Symbol> AttrCursor::getAttrPath(Symbol name) const
std::string AttrCursor::getAttrPathStr() const std::string AttrCursor::getAttrPathStr() const
{ {
return concatStringsSep(".", getAttrPath()); return concatStringsSep(".", root->state.symbols.resolve(getAttrPath()));
} }
std::string AttrCursor::getAttrPathStr(Symbol name) const std::string AttrCursor::getAttrPathStr(Symbol name) const
{ {
return concatStringsSep(".", getAttrPath(name)); return concatStringsSep(".", root->state.symbols.resolve(getAttrPath(name)));
} }
Value & AttrCursor::forceValue() Value & AttrCursor::forceValue()
{ {
debug("evaluating uncached attribute %s", getAttrPathStr()); debug("evaluating uncached attribute '%s'", getAttrPathStr());
auto & v = getValue(); auto & v = getValue();
@ -414,25 +440,25 @@ Suggestions AttrCursor::getSuggestionsForAttr(Symbol name)
auto attrNames = getAttrs(); auto attrNames = getAttrs();
std::set<std::string> strAttrNames; std::set<std::string> strAttrNames;
for (auto & name : attrNames) 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) std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErrors)
{ {
if (root->db) { if (root->db) {
if (!cachedValue) if (!cachedValue)
cachedValue = root->db->getAttr(getKey(), root->state.symbols); cachedValue = root->db->getAttr(getKey());
if (cachedValue) { if (cachedValue) {
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) { if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
for (auto & attr : *attrs) for (auto & attr : *attrs)
if (attr == name) 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; return nullptr;
} else if (std::get_if<placeholder_t>(&cachedValue->second)) { } 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 (attr) {
if (std::get_if<missing_t>(&attr->second)) if (std::get_if<missing_t>(&attr->second))
return nullptr; return nullptr;
@ -522,7 +548,7 @@ std::string AttrCursor::getString()
{ {
if (root->db) { if (root->db) {
if (!cachedValue) 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 (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto s = std::get_if<string_t>(&cachedValue->second)) { if (auto s = std::get_if<string_t>(&cachedValue->second)) {
debug("using cached string attribute '%s'", getAttrPathStr()); debug("using cached string attribute '%s'", getAttrPathStr());
@ -544,7 +570,7 @@ string_t AttrCursor::getStringWithContext()
{ {
if (root->db) { if (root->db) {
if (!cachedValue) 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 (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto s = std::get_if<string_t>(&cachedValue->second)) { if (auto s = std::get_if<string_t>(&cachedValue->second)) {
bool valid = true; bool valid = true;
@ -577,7 +603,7 @@ bool AttrCursor::getBool()
{ {
if (root->db) { if (root->db) {
if (!cachedValue) 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 (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto b = std::get_if<bool>(&cachedValue->second)) { if (auto b = std::get_if<bool>(&cachedValue->second)) {
debug("using cached Boolean attribute '%s'", getAttrPathStr()); debug("using cached Boolean attribute '%s'", getAttrPathStr());
@ -595,11 +621,44 @@ bool AttrCursor::getBool()
return v.boolean; return v.boolean;
} }
std::vector<std::string> AttrCursor::getListOfStrings()
{
if (root->db) {
if (!cachedValue)
cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto l = std::get_if<std::vector<std::string>>(&cachedValue->second)) {
debug("using cached list of strings attribute '%s'", getAttrPathStr());
return *l;
} else
throw TypeError("'%s' is not a list of strings", getAttrPathStr());
}
}
debug("evaluating uncached attribute '%s'", getAttrPathStr());
auto & v = getValue();
root->state.forceValue(v, noPos);
if (v.type() != nList)
throw TypeError("'%s' is not a list", getAttrPathStr());
std::vector<std::string> res;
for (auto & elem : v.listItems())
res.push_back(std::string(root->state.forceStringNoCtx(*elem)));
if (root->db)
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
return res;
}
std::vector<Symbol> AttrCursor::getAttrs() std::vector<Symbol> AttrCursor::getAttrs()
{ {
if (root->db) { if (root->db) {
if (!cachedValue) 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 (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) { if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
debug("using cached attrset attribute '%s'", getAttrPathStr()); debug("using cached attrset attribute '%s'", getAttrPathStr());
@ -617,8 +676,9 @@ std::vector<Symbol> AttrCursor::getAttrs()
std::vector<Symbol> attrs; std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs) for (auto & attr : *getValue().attrs)
attrs.push_back(attr.name); attrs.push_back(attr.name);
std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) { std::sort(attrs.begin(), attrs.end(), [&](Symbol a, Symbol b) {
return (const std::string &) a < (const std::string &) b; std::string_view sa = root->state.symbols[a], sb = root->state.symbols[b];
return sa < sb;
}); });
if (root->db) if (root->db)

View file

@ -44,6 +44,7 @@ enum AttrType {
Misc = 4, Misc = 4,
Failed = 5, Failed = 5,
Bool = 6, Bool = 6,
ListOfStrings = 7,
}; };
struct placeholder_t {}; struct placeholder_t {};
@ -61,7 +62,8 @@ typedef std::variant<
missing_t, missing_t,
misc_t, misc_t,
failed_t, failed_t,
bool bool,
std::vector<std::string>
> AttrValue; > AttrValue;
class AttrCursor : public std::enable_shared_from_this<AttrCursor> class AttrCursor : public std::enable_shared_from_this<AttrCursor>
@ -114,6 +116,8 @@ public:
bool getBool(); bool getBool();
std::vector<std::string> getListOfStrings();
std::vector<Symbol> getAttrs(); std::vector<Symbol> getAttrs();
bool isDerivation(); bool isDerivation();

View file

@ -2,27 +2,8 @@
#include "eval.hh" #include "eval.hh"
#define LocalNoInline(f) static f __attribute__((noinline)); f
#define LocalNoInlineNoReturn(f) static f __attribute__((noinline, noreturn)); f
namespace nix { 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. */ /* Note: Various places expect the allocated memory to be zeroed. */
[[gnu::always_inline]] [[gnu::always_inline]]
@ -99,7 +80,7 @@ Env & EvalState::allocEnv(size_t size)
[[gnu::always_inline]] [[gnu::always_inline]]
void EvalState::forceValue(Value & v, const Pos & pos) void EvalState::forceValue(Value & v, const PosIdx pos)
{ {
forceValue(v, [&]() { return pos; }); forceValue(v, [&]() { return pos; });
} }
@ -128,7 +109,7 @@ void EvalState::forceValue(Value & v, Callable getPos)
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const Pos & pos) inline void EvalState::forceAttrs(Value & v, const PosIdx pos)
{ {
forceAttrs(v, [&]() { return pos; }); forceAttrs(v, [&]() { return pos; });
} }
@ -145,7 +126,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos)
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const Pos & pos) inline void EvalState::forceList(Value & v, const PosIdx pos)
{ {
forceValue(v, pos); forceValue(v, pos);
if (!v.isList()) if (!v.isList())

View file

@ -96,7 +96,8 @@ RootValue allocRootValue(Value * v)
} }
void Value::print(std::ostream & str, std::set<const void *> * seen) const void Value::print(const SymbolTable & symbols, std::ostream & str,
std::set<const void *> * seen) const
{ {
checkInterrupt(); checkInterrupt();
@ -129,9 +130,9 @@ void Value::print(std::ostream & str, std::set<const void *> * seen) const
str << "«repeated»"; str << "«repeated»";
else { else {
str << "{ "; str << "{ ";
for (auto & i : attrs->lexicographicOrder()) { for (auto & i : attrs->lexicographicOrder(symbols)) {
str << i->name << " = "; str << symbols[i->name] << " = ";
i->value->print(str, seen); i->value->print(symbols, str, seen);
str << "; "; str << "; ";
} }
str << "}"; str << "}";
@ -146,7 +147,10 @@ void Value::print(std::ostream & str, std::set<const void *> * seen) const
else { else {
str << "[ "; str << "[ ";
for (auto v2 : listItems()) { for (auto v2 : listItems()) {
v2->print(str, seen); if (v2)
v2->print(symbols, str, seen);
else
str << "(nullptr)";
str << " "; str << " ";
} }
str << "]"; str << "]";
@ -177,17 +181,23 @@ void Value::print(std::ostream & str, std::set<const void *> * seen) const
} }
void Value::print(std::ostream & str, bool showRepeated) const void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepeated) const
{ {
std::set<const void *> seen; std::set<const void *> seen;
print(str, showRepeated ? nullptr : &seen); print(symbols, str, showRepeated ? nullptr : &seen);
} }
// Pretty print types for assertion errors
std::ostream & operator << (std::ostream & os, const ValueType t) {
os << showType(t);
return os;
}
std::ostream & operator << (std::ostream & str, const Value & v) std::string printValue(const EvalState & state, const Value & v)
{ {
v.print(str, false); std::ostringstream out;
return str; v.print(state.symbols, out);
return out.str();
} }
@ -236,10 +246,10 @@ std::string showType(const Value & v)
} }
} }
Pos Value::determinePos(const Pos & pos) const PosIdx Value::determinePos(const PosIdx pos) const
{ {
switch (internalType) { switch (internalType) {
case tAttrs: return *attrs->pos; case tAttrs: return attrs->pos;
case tLambda: return lambda.fun->pos; case tLambda: return lambda.fun->pos;
case tApp: return app.left->determinePos(pos); case tApp: return app.left->determinePos(pos);
default: return pos; default: return pos;
@ -308,7 +318,7 @@ static BoehmGCStackAllocator boehmGCStackAllocator;
static Symbol getName(const AttrName & name, EvalState & state, Env & env) static Symbol getName(const AttrName & name, EvalState & state, Env & env)
{ {
if (name.symbol.set()) { if (name.symbol) {
return name.symbol; return name.symbol;
} else { } else {
Value nameValue; Value nameValue;
@ -639,20 +649,20 @@ Value * EvalState::addPrimOp(const std::string & name,
size_t arity, PrimOpFun primOp) size_t arity, PrimOpFun primOp)
{ {
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name; auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
Symbol sym = symbols.create(name2); auto sym = symbols.create(name2);
/* Hack to make constants lazy: turn them into a application of /* Hack to make constants lazy: turn them into a application of
the primop to a dummy value. */ the primop to a dummy value. */
if (arity == 0) { if (arity == 0) {
auto vPrimOp = allocValue(); auto vPrimOp = allocValue();
vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = sym }); vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = name2 });
Value v; Value v;
v.mkApp(vPrimOp, vPrimOp); v.mkApp(vPrimOp, vPrimOp);
return addConstant(name, v); return addConstant(name, v);
} }
Value * v = allocValue(); Value * v = allocValue();
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym }); v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 });
staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl); staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v; baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(sym, v)); baseEnv.values[0]->attrs->push_back(Attr(sym, v));
@ -667,21 +677,21 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
if (primOp.arity == 0) { if (primOp.arity == 0) {
primOp.arity = 1; primOp.arity = 1;
auto vPrimOp = allocValue(); auto vPrimOp = allocValue();
vPrimOp->mkPrimOp(new PrimOp(std::move(primOp))); vPrimOp->mkPrimOp(new PrimOp(primOp));
Value v; Value v;
v.mkApp(vPrimOp, vPrimOp); v.mkApp(vPrimOp, vPrimOp);
return addConstant(primOp.name, v); return addConstant(primOp.name, v);
} }
Symbol envName = primOp.name; auto envName = symbols.create(primOp.name);
if (hasPrefix(primOp.name, "__")) if (hasPrefix(primOp.name, "__"))
primOp.name = symbols.create(std::string(primOp.name, 2)); primOp.name = primOp.name.substr(2);
Value * v = allocValue(); Value * v = allocValue();
v->mkPrimOp(new PrimOp(std::move(primOp))); v->mkPrimOp(new PrimOp(primOp));
staticBaseEnv.vars.emplace_back(envName, baseEnvDispl); staticBaseEnv.vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v; 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; return v;
} }
@ -698,7 +708,7 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
auto v2 = &v; auto v2 = &v;
if (v2->primOp->doc) if (v2->primOp->doc)
return Doc { return Doc {
.pos = noPos, .pos = {},
.name = v2->primOp->name, .name = v2->primOp->name,
.arity = v2->primOp->arity, .arity = v2->primOp->arity,
.args = v2->primOp->args, .args = v2->primOp->args,
@ -714,113 +724,133 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
evaluator. So here are some helper functions for throwing evaluator. So here are some helper functions for throwing
exceptions. */ exceptions. */
LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2)) void EvalState::throwEvalError(const PosIdx pos, const char * s) const
{
throw EvalError({
.msg = hintfmt(s),
.errPos = positions[pos]
});
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v) const
{
throw TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
});
}
void EvalState::throwEvalError(const char * s, const std::string & s2) const
{ {
throw EvalError(s, s2); throw EvalError(s, s2);
} }
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 & s2) const
{ {
throw EvalError(ErrorInfo { throw EvalError(ErrorInfo {
.msg = hintfmt(s, s2), .msg = hintfmt(s, s2),
.errPos = pos, .errPos = positions[pos],
.suggestions = suggestions, .suggestions = suggestions,
}); });
} }
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2)) void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const
{ {
throw EvalError(ErrorInfo { throw EvalError(ErrorInfo {
.msg = hintfmt(s, s2), .msg = hintfmt(s, s2),
.errPos = pos .errPos = positions[pos]
}); });
} }
LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2, const std::string & s3)) void EvalState::throwEvalError(const char * s, const std::string & s2, const std::string & s3) const
{ {
throw EvalError(s, s2, s3); throw EvalError(s, s2, s3);
} }
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3)) void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
const std::string & s3) const
{ {
throw EvalError({ throw EvalError({
.msg = hintfmt(s, s2, s3), .msg = hintfmt(s, s2, s3),
.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 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. // p1 is where the error occurred; p2 is a position mentioned in the message.
throw EvalError({ throw EvalError({
.msg = hintfmt(s, sym, p2), .msg = hintfmt(s, symbols[sym], positions[p2]),
.errPos = p1 .errPos = positions[p1]
}); });
} }
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s)) void EvalState::throwTypeError(const PosIdx pos, const char * s) const
{ {
throw TypeError({ throw TypeError({
.msg = hintfmt(s), .msg = hintfmt(s),
.errPos = pos .errPos = positions[pos]
}); });
} }
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2)) void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun,
const Symbol s2) const
{ {
throw TypeError({ throw TypeError({
.msg = hintfmt(s, fun.showNamePos(), s2), .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = pos .errPos = positions[pos]
}); });
} }
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2)) void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const ExprLambda & fun, const Symbol s2) const
{ {
throw TypeError(ErrorInfo { throw TypeError(ErrorInfo {
.msg = hintfmt(s, fun.showNamePos(), s2), .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = pos, .errPos = positions[pos],
.suggestions = suggestions, .suggestions = suggestions,
}); });
} }
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) void EvalState::throwTypeError(const char * s, const Value & v) const
{ {
throw TypeError(s, showType(v)); throw TypeError(s, showType(v));
} }
LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const std::string & s1)) void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const
{ {
throw AssertionError({ throw AssertionError({
.msg = hintfmt(s, s1), .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({ throw UndefinedVarError({
.msg = hintfmt(s, s1), .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({ throw MissingArgumentError({
.msg = hintfmt(s, s1), .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); 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);
} }
@ -877,11 +907,11 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
} }
Bindings::iterator j = env->values[0]->attrs->find(var.name); Bindings::iterator j = env->values[0]->attrs->find(var.name);
if (j != env->values[0]->attrs->end()) { if (j != env->values[0]->attrs->end()) {
if (countCalls) attrSelects[*j->pos]++; if (countCalls) attrSelects[j->pos]++;
return j->value; return j->value;
} }
if (!env->prevWith) 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) ; for (size_t l = env->prevWith; l; --l, env = env->up) ;
} }
} }
@ -911,13 +941,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); auto attrs = buildBindings(3);
attrs.alloc(sFile).mkString(pos->file); attrs.alloc(sFile).mkString(pos.file);
attrs.alloc(sLine).mkInt(pos->line); attrs.alloc(sLine).mkInt(pos.line);
attrs.alloc(sColumn).mkInt(pos->column); attrs.alloc(sColumn).mkInt(pos.column);
v.mkAttrs(attrs); v.mkAttrs(attrs);
} else } else
v.mkNull(); v.mkNull();
@ -1050,7 +1081,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e)
} }
inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos) inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos)
{ {
Value v; Value v;
e->eval(*this, env, v); e->eval(*this, env, v);
@ -1124,7 +1155,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
} else } else
vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2); vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
env2.values[displ++] = vAttr; 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 /* If the rec contains an attribute called `__overrides', then
@ -1156,7 +1187,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
else else
for (auto & i : attrs) 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. */ /* Dynamic attrs apply *after* rec and __overrides. */
for (auto & i : dynamicAttrs) { for (auto & i : dynamicAttrs) {
@ -1166,18 +1197,18 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
if (nameVal.type() == nNull) if (nameVal.type() == nNull)
continue; continue;
state.forceStringNoCtx(nameVal); state.forceStringNoCtx(nameVal);
Symbol nameSym = state.symbols.create(nameVal.string.s); auto nameSym = state.symbols.create(nameVal.string.s);
Bindings::iterator j = v.attrs->find(nameSym); Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end()) 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); i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */ /* 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->sort(); // FIXME: inefficient
} }
v.attrs->pos = ptr(&pos); v.attrs->pos = pos;
} }
@ -1222,10 +1253,12 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a
for (auto & i : attrPath) { for (auto & i : attrPath) {
if (!first) out << '.'; else first = false; if (!first) out << '.'; else first = false;
try { try {
out << getName(i, state, env); out << state.symbols[getName(i, state, env)];
} catch (Error & e) { } catch (Error & e) {
assert(!i.symbol.set()); assert(!i.symbol);
out << "\"${" << *i.expr << "}\""; out << "\"${";
i.expr->show(state.symbols, out);
out << "}\"";
} }
} }
return out.str(); return out.str();
@ -1235,7 +1268,7 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a
void ExprSelect::eval(EvalState & state, Env & env, Value & v) void ExprSelect::eval(EvalState & state, Env & env, Value & v)
{ {
Value vTmp; Value vTmp;
ptr<Pos> pos2(&noPos); PosIdx pos2;
Value * vAttrs = &vTmp; Value * vAttrs = &vTmp;
e->eval(state, env, vTmp); e->eval(state, env, vTmp);
@ -1245,7 +1278,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
for (auto & i : attrPath) { for (auto & i : attrPath) {
state.nrLookups++; state.nrLookups++;
Bindings::iterator j; Bindings::iterator j;
Symbol name = getName(i, state, env); auto name = getName(i, state, env);
if (def) { if (def) {
state.forceValue(*vAttrs, pos); state.forceValue(*vAttrs, pos);
if (vAttrs->type() != nAttrs || if (vAttrs->type() != nAttrs ||
@ -1259,23 +1292,24 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
std::set<std::string> allAttrNames; std::set<std::string> allAttrNames;
for (auto & attr : *vAttrs->attrs) for (auto & attr : *vAttrs->attrs)
allAttrNames.insert(attr.name); allAttrNames.insert(state.symbols[attr.name]);
throwEvalError( state.throwEvalError(
pos, pos,
Suggestions::bestMatches(allAttrNames, name), Suggestions::bestMatches(allAttrNames, state.symbols[name]),
"attribute '%1%' missing", name); "attribute '%1%' missing", state.symbols[name]);
} }
} }
vAttrs = j->value; vAttrs = j->value;
pos2 = j->pos; 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) { } catch (Error & e) {
if (*pos2 != noPos && pos2->file != state.sDerivationNix) auto pos2r = state.positions[pos2];
addErrorTrace(e, *pos2, "while evaluating the attribute '%1%'", if (pos2 && pos2r.file != state.derivationNixPath)
state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
showAttrPath(state, env, attrPath)); showAttrPath(state, env, attrPath));
throw; throw;
} }
@ -1294,7 +1328,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
for (auto & i : attrPath) { for (auto & i : attrPath) {
state.forceValue(*vAttrs, noPos); state.forceValue(*vAttrs, noPos);
Bindings::iterator j; Bindings::iterator j;
Symbol name = getName(i, state, env); auto name = getName(i, state, env);
if (vAttrs->type() != nAttrs || if (vAttrs->type() != nAttrs ||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
{ {
@ -1315,9 +1349,11 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
} }
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos) void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
{ {
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr; auto trace = evalSettings.traceFunctionCalls
? std::make_unique<FunctionCallTrace>(positions[pos])
: nullptr;
forceValue(fun, pos); forceValue(fun, pos);
@ -1342,7 +1378,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
ExprLambda & lambda(*vCur.lambda.fun); ExprLambda & lambda(*vCur.lambda.fun);
auto size = auto size =
(lambda.arg.empty() ? 0 : 1) + (!lambda.arg ? 0 : 1) +
(lambda.hasFormals() ? lambda.formals->formals.size() : 0); (lambda.hasFormals() ? lambda.formals->formals.size() : 0);
Env & env2(allocEnv(size)); Env & env2(allocEnv(size));
env2.up = vCur.lambda.env; env2.up = vCur.lambda.env;
@ -1355,7 +1391,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
else { else {
forceAttrs(*args[0], pos); forceAttrs(*args[0], pos);
if (!lambda.arg.empty()) if (lambda.arg)
env2.values[displ++] = args[0]; env2.values[displ++] = args[0];
/* For each formal argument, get the actual argument. If /* For each formal argument, get the actual argument. If
@ -1383,10 +1419,10 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (!lambda.formals->has(i.name)) { if (!lambda.formals->has(i.name)) {
std::set<std::string> formalNames; std::set<std::string> formalNames;
for (auto & formal : lambda.formals->formals) for (auto & formal : lambda.formals->formals)
formalNames.insert(formal.name); formalNames.insert(symbols[formal.name]);
throwTypeError( throwTypeError(
pos, pos,
Suggestions::bestMatches(formalNames, i.name), Suggestions::bestMatches(formalNames, symbols[i.name]),
"%1% called with unexpected argument '%2%'", "%1% called with unexpected argument '%2%'",
lambda, lambda,
i.name); i.name);
@ -1404,8 +1440,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
} catch (Error & e) { } catch (Error & e) {
if (loggerSettings.showTrace.get()) { if (loggerSettings.showTrace.get()) {
addErrorTrace(e, lambda.pos, "while evaluating %s", addErrorTrace(e, lambda.pos, "while evaluating %s",
(lambda.name.set() (lambda.name
? "'" + (const std::string &) lambda.name + "'" ? concatStrings("'", symbols[lambda.name], "'")
: "anonymous lambda")); : "anonymous lambda"));
addErrorTrace(e, pos, "from call site%s", ""); addErrorTrace(e, pos, "from call site%s", "");
} }
@ -1554,7 +1590,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
Nix attempted to evaluate a function as a top level expression; in Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See 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/expressions/language-constructs.html#functions.)", symbols[i.name]);
} }
} }
@ -1586,8 +1622,8 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{ {
if (!state.evalBool(env, cond, pos)) { if (!state.evalBool(env, cond, pos)) {
std::ostringstream out; std::ostringstream out;
cond->show(out); cond->show(state.symbols, out);
throwAssertionError(pos, "assertion '%1%' failed", out.str()); state.throwAssertionError(pos, "assertion '%1%' failed", out.str());
} }
body->eval(state, env, v); body->eval(state, env, v);
} }
@ -1680,7 +1716,7 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
} }
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos) void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos)
{ {
nrListConcats++; nrListConcats++;
@ -1764,14 +1800,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n; nf = n;
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp)); state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp));
} else if (firstType == nFloat) { } else if (firstType == nFloat) {
if (vTmp.type() == nInt) { if (vTmp.type() == nInt) {
nf += vTmp.integer; nf += vTmp.integer;
} else if (vTmp.type() == nFloat) { } else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp)); state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp));
} else { } else {
if (s.empty()) s.reserve(es->size()); if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not /* skip canonization of first path, which would only be not
@ -1791,7 +1827,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
v.mkFloat(nf); v.mkFloat(nf);
else if (firstType == nPath) { else if (firstType == nPath) {
if (!context.empty()) 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())); v.mkPath(canonPath(str()));
} else } else
v.mkStringMove(c_str(), context); v.mkStringMove(c_str(), context);
@ -1800,7 +1836,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
void ExprPos::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);
} }
@ -1820,7 +1856,7 @@ void EvalState::forceValueDeep(Value & v)
try { try {
recurse(*i.value); recurse(*i.value);
} catch (Error & e) { } 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; throw;
} }
} }
@ -1835,7 +1871,7 @@ void EvalState::forceValueDeep(Value & v)
} }
NixInt EvalState::forceInt(Value & v, const Pos & pos) NixInt EvalState::forceInt(Value & v, const PosIdx pos)
{ {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nInt) if (v.type() != nInt)
@ -1844,7 +1880,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos)
} }
NixFloat EvalState::forceFloat(Value & v, const Pos & pos) NixFloat EvalState::forceFloat(Value & v, const PosIdx pos)
{ {
forceValue(v, pos); forceValue(v, pos);
if (v.type() == nInt) if (v.type() == nInt)
@ -1855,7 +1891,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
} }
bool EvalState::forceBool(Value & v, const Pos & pos) bool EvalState::forceBool(Value & v, const PosIdx pos)
{ {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nBool) if (v.type() != nBool)
@ -1870,7 +1906,7 @@ bool EvalState::isFunctor(Value & fun)
} }
void EvalState::forceFunction(Value & v, const Pos & pos) void EvalState::forceFunction(Value & v, const PosIdx pos)
{ {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v)) if (v.type() != nFunction && !isFunctor(v))
@ -1878,7 +1914,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos)
} }
std::string_view EvalState::forceString(Value & v, const Pos & pos) std::string_view EvalState::forceString(Value & v, const PosIdx pos)
{ {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nString) { if (v.type() != nString) {
@ -1931,7 +1967,7 @@ NixStringContext Value::getContext(const Store & store)
} }
std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos) std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos)
{ {
auto s = forceString(v, pos); auto s = forceString(v, pos);
copyContext(v, context); copyContext(v, context);
@ -1939,7 +1975,7 @@ std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos
} }
std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos) std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos)
{ {
auto s = forceString(v, pos); auto s = forceString(v, pos);
if (v.string.context) { if (v.string.context) {
@ -1959,13 +1995,13 @@ bool EvalState::isDerivation(Value & v)
if (v.type() != nAttrs) return false; if (v.type() != nAttrs) return false;
Bindings::iterator i = v.attrs->find(sType); Bindings::iterator i = v.attrs->find(sType);
if (i == v.attrs->end()) return false; if (i == v.attrs->end()) return false;
forceValue(*i->value, *i->pos); forceValue(*i->value, i->pos);
if (i->value->type() != nString) return false; if (i->value->type() != nString) return false;
return strcmp(i->value->string.s, "derivation") == 0; 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) PathSet & context, bool coerceMore, bool copyToStore)
{ {
auto i = v.attrs->find(sToString); auto i = v.attrs->find(sToString);
@ -1978,7 +2014,7 @@ std::optional<std::string> EvalState::tryAttrsToString(const Pos & pos, Value &
return {}; 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) bool coerceMore, bool copyToStore, bool canonicalizePath)
{ {
forceValue(v, pos); forceValue(v, pos);
@ -2007,7 +2043,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
} }
if (v.type() == nExternal) if (v.type() == nExternal)
return v.external->coerceToString(pos, context, coerceMore, copyToStore); return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
if (coerceMore) { if (coerceMore) {
@ -2060,7 +2096,7 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
} }
Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context)
{ {
auto path = coerceToString(pos, v, context, false, false).toOwned(); auto path = coerceToString(pos, v, context, false, false).toOwned();
if (path == "" || path[0] != '/') if (path == "" || path[0] != '/')
@ -2069,14 +2105,14 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
} }
StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context) StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context)
{ {
auto path = coerceToString(pos, v, context, false, false).toOwned(); auto path = coerceToString(pos, v, context, false, false).toOwned();
if (auto storePath = store->maybeParseStorePath(path)) if (auto storePath = store->maybeParseStorePath(path))
return *storePath; return *storePath;
throw EvalError({ throw EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path), .msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = pos .errPos = positions[pos]
}); });
} }
@ -2243,14 +2279,14 @@ void EvalState::printStats()
auto list = topObj.list("functions"); auto list = topObj.list("functions");
for (auto & i : functionCalls) { for (auto & i : functionCalls) {
auto obj = list.object(); auto obj = list.object();
if (i.first->name.set()) if (i.first->name)
obj.attr("name", (const std::string &) i.first->name); obj.attr("name", (const std::string &) i.first->name);
else else
obj.attr("name", nullptr); obj.attr("name", nullptr);
if (i.first->pos) { if (auto pos = positions[i.first->pos]) {
obj.attr("file", (const std::string &) i.first->pos.file); obj.attr("file", (const std::string &) pos.file);
obj.attr("line", i.first->pos.line); obj.attr("line", pos.line);
obj.attr("column", i.first->pos.column); obj.attr("column", pos.column);
} }
obj.attr("count", i.second); obj.attr("count", i.second);
} }
@ -2259,10 +2295,10 @@ void EvalState::printStats()
auto list = topObj.list("attributes"); auto list = topObj.list("attributes");
for (auto & i : attrSelects) { for (auto & i : attrSelects) {
auto obj = list.object(); auto obj = list.object();
if (i.first) { if (auto pos = positions[i.first]) {
obj.attr("file", (const std::string &) i.first.file); obj.attr("file", (const std::string &) pos.file);
obj.attr("line", i.first.line); obj.attr("line", pos.line);
obj.attr("column", i.first.column); obj.attr("column", pos.column);
} }
obj.attr("count", i.second); obj.attr("count", i.second);
} }

View file

@ -23,14 +23,14 @@ class StorePath;
enum RepairFlag : bool; 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 struct PrimOp
{ {
PrimOpFun fun; PrimOpFun fun;
size_t arity; size_t arity;
Symbol name; std::string name;
std::vector<std::string> args; std::vector<std::string> args;
const char * doc = nullptr; const char * doc = nullptr;
}; };
@ -53,7 +53,9 @@ void copyContext(const Value & v, PathSet & context);
typedef std::map<Path, StorePath> SrcToStore; 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);
std::ostream & operator << (std::ostream & os, const ValueType t);
typedef std::pair<std::string, std::string> SearchPathElem; typedef std::pair<std::string, std::string> SearchPathElem;
@ -73,6 +75,9 @@ class EvalState
{ {
public: public:
SymbolTable symbols; SymbolTable symbols;
PosTable positions;
static inline std::string derivationNixPath = "//builtin/derivation.nix";
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
@ -205,7 +210,7 @@ public:
/* Look up a file in the search path. */ /* Look up a file in the search path. */
Path findFile(const std::string_view 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. */ /* If the specified search path element is a URI, download it. */
std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem); std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem);
@ -217,14 +222,14 @@ public:
/* Evaluation the expression, then verify that it has the expected /* Evaluation the expression, then verify that it has the expected
type. */ type. */
inline bool evalBool(Env & env, Expr * e); inline bool evalBool(Env & env, Expr * e);
inline bool evalBool(Env & env, Expr * e, const Pos & pos); inline bool evalBool(Env & env, Expr * e, const PosIdx pos);
inline void evalAttrs(Env & env, Expr * e, Value & v); inline void evalAttrs(Env & env, Expr * e, Value & v);
/* If `v' is a thunk, enter it and overwrite `v' with the result /* 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 of the evaluation of the thunk. If `v' is a delayed function
application, call the function and overwrite `v' with the application, call the function and overwrite `v' with the
result. Otherwise, this is a no-op. */ 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> template <typename Callable>
inline void forceValue(Value & v, Callable getPos); inline void forceValue(Value & v, Callable getPos);
@ -234,33 +239,72 @@ public:
void forceValueDeep(Value & v); void forceValueDeep(Value & v);
/* Force `v', and then verify that it has the expected type. */ /* Force `v', and then verify that it has the expected type. */
NixInt forceInt(Value & v, const Pos & pos); NixInt forceInt(Value & v, const PosIdx pos);
NixFloat forceFloat(Value & v, const Pos & pos); NixFloat forceFloat(Value & v, const PosIdx pos);
bool forceBool(Value & v, const Pos & pos); bool forceBool(Value & v, const PosIdx pos);
void forceAttrs(Value & v, const Pos & pos); void forceAttrs(Value & v, const PosIdx pos);
template <typename Callable> template <typename Callable>
inline void forceAttrs(Value & v, Callable getPos); inline void forceAttrs(Value & v, Callable getPos);
inline void forceList(Value & v, const Pos & pos); inline void forceList(Value & v, const PosIdx pos);
void forceFunction(Value & v, const Pos & pos); // either lambda or primop void forceFunction(Value & v, const PosIdx pos); // either lambda or primop
std::string_view forceString(Value & v, const Pos & pos = noPos); std::string_view forceString(Value & v, const PosIdx pos = noPos);
std::string_view forceString(Value & v, PathSet & context, const Pos & pos = noPos); std::string_view forceString(Value & v, PathSet & context, const PosIdx pos = noPos);
std::string_view forceStringNoCtx(Value & v, const Pos & pos = noPos); std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(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 throwEvalError(const char * s, const std::string & s2) const;
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const std::string & s2) const;
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const;
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2, const std::string & s3) const;
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const 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 throwTypeError(const PosIdx pos, const char * s) const;
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2) const;
[[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 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 /* Return true iff the value `v' denotes a derivation (i.e. a
set with attribute `type = "derivation"'). */ set with attribute `type = "derivation"'). */
bool isDerivation(Value & v); 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); PathSet & context, bool coerceMore = false, bool copyToStore = true);
/* String coercion. Converts strings, paths and derivations to a /* String coercion. Converts strings, paths and derivations to a
string. If `coerceMore' is set, also converts nulls, integers, string. If `coerceMore' is set, also converts nulls, integers,
booleans and lists to a string. If `copyToStore' is set, booleans and lists to a string. If `copyToStore' is set,
referenced paths are copied to the Nix store as a side effect. */ 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 coerceMore = false, bool copyToStore = true,
bool canonicalizePath = true); bool canonicalizePath = true);
@ -269,10 +313,10 @@ public:
/* Path coercion. Converts strings, paths and derivations to a /* Path coercion. Converts strings, paths and derivations to a
path. The result is guaranteed to be a canonicalised, absolute path. The result is guaranteed to be a canonicalised, absolute
path. Nothing is copied to the store. */ path. Nothing is copied to the store. */
Path coerceToPath(const Pos & pos, Value & v, PathSet & context); Path coerceToPath(const PosIdx pos, Value & v, PathSet & context);
/* Like coerceToPath, but the result must be a store path. */ /* Like coerceToPath, but the result must be a store path. */
StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context); StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context);
public: public:
@ -305,7 +349,7 @@ public:
struct Doc struct Doc
{ {
Pos pos; Pos pos;
std::optional<Symbol> name; std::optional<std::string> name;
size_t arity; size_t arity;
std::vector<std::string> args; std::vector<std::string> args;
const char * doc; const char * doc;
@ -333,9 +377,9 @@ public:
bool isFunctor(Value & fun); bool isFunctor(Value & fun);
// FIXME: use std::span // 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}; Value * args[] = {&arg};
callFunction(fun, 1, args, vRes, pos); callFunction(fun, 1, args, vRes, pos);
@ -349,7 +393,7 @@ public:
inline Value * allocValue(); inline Value * allocValue();
inline Env & allocEnv(size_t size); 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); Value * allocAttr(Value & vAttrs, std::string_view name);
Bindings * allocBindings(size_t capacity); Bindings * allocBindings(size_t capacity);
@ -361,9 +405,9 @@ public:
void mkList(Value & v, size_t length); void mkList(Value & v, size_t length);
void mkThunk_(Value & v, Expr * expr); void mkThunk_(Value & v, Expr * expr);
void mkPos(Value & v, ptr<Pos> pos); void mkPos(Value & v, PosIdx pos);
void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos); void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos);
/* Print statistics. */ /* Print statistics. */
void printStats(); void printStats();
@ -391,7 +435,7 @@ private:
bool countCalls; bool countCalls;
typedef std::map<Symbol, size_t> PrimOpCalls; typedef std::map<std::string, size_t> PrimOpCalls;
PrimOpCalls primOpCalls; PrimOpCalls primOpCalls;
typedef std::map<ExprLambda *, size_t> FunctionCalls; typedef std::map<ExprLambda *, size_t> FunctionCalls;
@ -399,7 +443,7 @@ private:
void incrFunctionCall(ExprLambda * fun); void incrFunctionCall(ExprLambda * fun);
typedef std::map<Pos, size_t> AttrSelects; typedef std::map<PosIdx, size_t> AttrSelects;
AttrSelects attrSelects; AttrSelects attrSelects;
friend struct ExprOpUpdate; friend struct ExprOpUpdate;
@ -410,9 +454,9 @@ private:
friend struct ExprFloat; friend struct ExprFloat;
friend struct ExprPath; friend struct ExprPath;
friend struct ExprSelect; friend struct ExprSelect;
friend void prim_getAttr(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 Pos & 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 Pos & pos, Value * * args, Value & v); friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v);
friend struct Value; friend struct Value;
}; };

View file

@ -50,13 +50,11 @@ void ConfigFile::apply()
else else
assert(false); assert(false);
if (!whitelist.count(baseName)) { if (!whitelist.count(baseName) && !nix::fetchSettings.acceptFlakeConfig) {
auto trustedList = readTrustedList();
bool trusted = false; bool trusted = false;
if (nix::fetchSettings.acceptFlakeConfig){ auto trustedList = readTrustedList();
trusted = true; auto tlname = get(trustedList, name);
} else if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) { if (auto saved = tlname ? get(*tlname, valueS) : nullptr) {
trusted = *saved; trusted = *saved;
warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS); warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS);
} else { } else {
@ -69,7 +67,6 @@ void ConfigFile::apply()
writeTrustedList(trustedList); writeTrustedList(trustedList);
} }
} }
if (!trusted) { if (!trusted) {
warn("ignoring untrusted flake configuration setting '%s'", name); warn("ignoring untrusted flake configuration setting '%s'", name);
continue; continue;

View file

@ -72,7 +72,7 @@ static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
return {std::move(tree), resolvedRef, lockedRef}; 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()) if (value.isThunk() && value.isTrivial())
state.forceValue(value, pos); 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, static void expectType(EvalState & state, ValueType type,
Value & value, const Pos & pos) Value & value, const PosIdx pos)
{ {
forceTrivialValue(state, value, pos); forceTrivialValue(state, value, pos);
if (value.type() != type) if (value.type() != type)
throw Error("expected %s but got %s at %s", 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( 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); const std::optional<Path> & baseDir, InputPath lockRootPath);
static FlakeInput parseFlakeInput(EvalState & state, 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) const std::optional<Path> & baseDir, InputPath lockRootPath)
{ {
expectType(state, nAttrs, *value, pos); expectType(state, nAttrs, *value, pos);
@ -111,37 +111,39 @@ static FlakeInput parseFlakeInput(EvalState & state,
for (nix::Attr attr : *(value->attrs)) { for (nix::Attr attr : *(value->attrs)) {
try { try {
if (attr.name == sUrl) { if (attr.name == sUrl) {
expectType(state, nString, *attr.value, *attr.pos); expectType(state, nString, *attr.value, attr.pos);
url = attr.value->string.s; url = attr.value->string.s;
attrs.emplace("url", *url); attrs.emplace("url", *url);
} else if (attr.name == sFlake) { } else if (attr.name == sFlake) {
expectType(state, nBool, *attr.value, *attr.pos); expectType(state, nBool, *attr.value, attr.pos);
input.isFlake = attr.value->boolean; input.isFlake = attr.value->boolean;
} else if (attr.name == sInputs) { } 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) { } 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)); auto follows(parseInputPath(attr.value->string.s));
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end()); follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
input.follows = follows; input.follows = follows;
} else { } else {
switch (attr.value->type()) { switch (attr.value->type()) {
case nString: case nString:
attrs.emplace(attr.name, attr.value->string.s); attrs.emplace(state.symbols[attr.name], attr.value->string.s);
break; break;
case nBool: case nBool:
attrs.emplace(attr.name, Explicit<bool> { attr.value->boolean }); attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean });
break; break;
case nInt: 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; break;
default: default:
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", 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) { } 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; throw;
} }
} }
@ -150,13 +152,13 @@ static FlakeInput parseFlakeInput(EvalState & state,
try { try {
input.ref = FlakeRef::fromAttrs(attrs); input.ref = FlakeRef::fromAttrs(attrs);
} catch (Error & e) { } catch (Error & e) {
e.addTrace(pos, hintfmt("in flake input")); e.addTrace(state.positions[pos], hintfmt("in flake input"));
throw; throw;
} }
else { else {
attrs.erase("url"); attrs.erase("url");
if (!attrs.empty()) 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) if (url)
input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake); input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake);
} }
@ -168,7 +170,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
} }
static std::map<FlakeId, FlakeInput> parseFlakeInputs( 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) const std::optional<Path> & baseDir, InputPath lockRootPath)
{ {
std::map<FlakeId, FlakeInput> inputs; std::map<FlakeId, FlakeInput> inputs;
@ -176,11 +178,11 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
expectType(state, nAttrs, *value, pos); expectType(state, nAttrs, *value, pos);
for (nix::Attr & inputAttr : *(*value).attrs) { for (nix::Attr & inputAttr : *(*value).attrs) {
inputs.emplace(inputAttr.name, inputs.emplace(state.symbols[inputAttr.name],
parseFlakeInput(state, parseFlakeInput(state,
inputAttr.name, state.symbols[inputAttr.name],
inputAttr.value, inputAttr.value,
*inputAttr.pos, inputAttr.pos,
baseDir, baseDir,
lockRootPath)); lockRootPath));
} }
@ -218,28 +220,28 @@ static Flake getFlake(
Value vInfo; Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack 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)) { 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; flake.description = description->value->string.s;
} }
auto sInputs = state.symbols.create("inputs"); auto sInputs = state.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs)) 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"); auto sOutputs = state.symbols.create("outputs");
if (auto outputs = vInfo.attrs->get(sOutputs)) { 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()) { if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) { for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.sSelf) if (formal.name != state.sSelf)
flake.inputs.emplace(formal.name, FlakeInput { flake.inputs.emplace(state.symbols[formal.name], FlakeInput {
.ref = parseFlakeRef(formal.name) .ref = parseFlakeRef(state.symbols[formal.name])
}); });
} }
} }
@ -250,35 +252,41 @@ static Flake getFlake(
auto sNixConfig = state.symbols.create("nixConfig"); auto sNixConfig = state.symbols.create("nixConfig");
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) { 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) { for (auto & setting : *nixConfig->value->attrs) {
forceTrivialValue(state, *setting.value, *setting.pos); forceTrivialValue(state, *setting.value, setting.pos);
if (setting.value->type() == nString) 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) { else if (setting.value->type() == nPath) {
PathSet emptyContext = {}; PathSet emptyContext = {};
flake.config.settings.emplace( flake.config.settings.emplace(
setting.name, state.symbols[setting.name],
state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true) .toOwned()); state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true) .toOwned());
} }
else if (setting.value->type() == nInt) 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) 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) { else if (setting.value->type() == nList) {
std::vector<std::string> ss; std::vector<std::string> ss;
for (auto elem : setting.value->listItems()) { for (auto elem : setting.value->listItems()) {
if (elem->type() != nString) if (elem->type() != nString)
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected", throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
setting.name, showType(*setting.value)); state.symbols[setting.name], showType(*setting.value));
ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos)); 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 else
throw TypeError("flake configuration setting '%s' is %s", 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 != sOutputs &&
attr.name != sNixConfig) attr.name != sNixConfig)
throw Error("flake '%s' has an unsupported attribute '%s', at %s", 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; return flake;
@ -704,12 +712,12 @@ void callFlake(EvalState & state,
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos); state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
} }
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos)); std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true); auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked()) 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, callFlake(state,
lockFlake(state, flakeRef, lockFlake(state, flakeRef,

View file

@ -176,7 +176,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
parsedURL.query.insert_or_assign("shallow", "1"); parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair( return std::make_pair(
FlakeRef(Input::fromURL(parsedURL), get(parsedURL.query, "dir").value_or("")), FlakeRef(Input::fromURL(parsedURL), getOr(parsedURL.query, "dir", "")),
fragment); fragment);
} }
@ -189,7 +189,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
if (!hasPrefix(path, "/")) if (!hasPrefix(path, "/"))
throw BadURL("flake reference '%s' is not an absolute path", url); throw BadURL("flake reference '%s' is not an absolute path", url);
auto query = decodeQuery(match[2]); auto query = decodeQuery(match[2]);
path = canonPath(path + "/" + get(query, "dir").value_or("")); path = canonPath(path + "/" + getOr(query, "dir", ""));
} }
fetchers::Attrs attrs; fetchers::Attrs attrs;
@ -208,7 +208,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
input.parent = baseDir; input.parent = baseDir;
return std::make_pair( return std::make_pair(
FlakeRef(std::move(input), get(parsedURL.query, "dir").value_or("")), FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")),
fragment); fragment);
} }
} }
@ -238,4 +238,15 @@ std::pair<fetchers::Tree, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
return {std::move(tree), FlakeRef(std::move(lockedInput), subdir)}; return {std::move(tree), FlakeRef(std::move(lockedInput), subdir)};
} }
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
const std::string & url,
const std::optional<Path> & baseDir,
bool allowMissing,
bool isFlake)
{
auto [prefix, outputsSpec] = parseOutputsSpec(url);
auto [flakeRef, fragment] = parseFlakeRefWithFragment(prefix, baseDir, allowMissing, isFlake);
return {std::move(flakeRef), fragment, outputsSpec};
}
} }

View file

@ -3,6 +3,7 @@
#include "types.hh" #include "types.hh"
#include "hash.hh" #include "hash.hh"
#include "fetchers.hh" #include "fetchers.hh"
#include "path-with-outputs.hh"
#include <variant> #include <variant>
@ -79,4 +80,11 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment( std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
const std::string & url, const std::optional<Path> & baseDir = {}); const std::string & url, const std::optional<Path> & baseDir = {});
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
const std::string & url,
const std::optional<Path> & baseDir = {},
bool allowMissing = false,
bool isFlake = true);
} }

View file

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

View file

@ -34,7 +34,7 @@ DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPat
outputName = outputName =
selectedOutputs.empty() selectedOutputs.empty()
? get(drv.env, "outputName").value_or("out") ? getOr(drv.env, "outputName", "out")
: *selectedOutputs.begin(); : *selectedOutputs.begin();
auto i = drv.outputs.find(outputName); auto i = drv.outputs.find(outputName);
@ -61,7 +61,7 @@ std::string DrvInfo::querySystem() const
{ {
if (system == "" && attrs) { if (system == "" && attrs) {
auto i = attrs->find(state->sSystem); auto i = attrs->find(state->sSystem);
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos); system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos);
} }
return system; return system;
} }
@ -75,7 +75,7 @@ std::optional<StorePath> DrvInfo::queryDrvPath() const
if (i == attrs->end()) if (i == attrs->end())
drvPath = {std::nullopt}; drvPath = {std::nullopt};
else else
drvPath = {state->coerceToStorePath(*i->pos, *i->value, context)}; drvPath = {state->coerceToStorePath(i->pos, *i->value, context)};
} }
return drvPath.value_or(std::nullopt); return drvPath.value_or(std::nullopt);
} }
@ -95,7 +95,7 @@ StorePath DrvInfo::queryOutPath() const
Bindings::iterator i = attrs->find(state->sOutPath); Bindings::iterator i = attrs->find(state->sOutPath);
PathSet context; PathSet context;
if (i != attrs->end()) if (i != attrs->end())
outPath = state->coerceToStorePath(*i->pos, *i->value, context); outPath = state->coerceToStorePath(i->pos, *i->value, context);
} }
if (!outPath) if (!outPath)
throw UnimplementedError("CA derivations are not yet supported"); throw UnimplementedError("CA derivations are not yet supported");
@ -109,23 +109,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
/* Get the outputs list. */ /* Get the outputs list. */
Bindings::iterator i; Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
state->forceList(*i->value, *i->pos); state->forceList(*i->value, i->pos);
/* For each output... */ /* For each output... */
for (auto elem : i->value->listItems()) { for (auto elem : i->value->listItems()) {
std::string output(state->forceStringNoCtx(*elem, *i->pos)); std::string output(state->forceStringNoCtx(*elem, i->pos));
if (withPaths) { if (withPaths) {
/* Evaluate the corresponding set. */ /* Evaluate the corresponding set. */
Bindings::iterator out = attrs->find(state->symbols.create(output)); Bindings::iterator out = attrs->find(state->symbols.create(output));
if (out == attrs->end()) continue; // FIXME: throw error? if (out == attrs->end()) continue; // FIXME: throw error?
state->forceAttrs(*out->value, *i->pos); state->forceAttrs(*out->value, i->pos);
/* And evaluate its outPath attribute. */ /* And evaluate its outPath attribute. */
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
PathSet context; PathSet context;
outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context)); outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context));
} else } else
outputs.emplace(output, std::nullopt); outputs.emplace(output, std::nullopt);
} }
@ -168,7 +168,7 @@ Bindings * DrvInfo::getMeta()
if (!attrs) return 0; if (!attrs) return 0;
Bindings::iterator a = attrs->find(state->sMeta); Bindings::iterator a = attrs->find(state->sMeta);
if (a == attrs->end()) return 0; if (a == attrs->end()) return 0;
state->forceAttrs(*a->value, *a->pos); state->forceAttrs(*a->value, a->pos);
meta = a->value->attrs; meta = a->value->attrs;
return meta; return meta;
} }
@ -179,7 +179,7 @@ StringSet DrvInfo::queryMetaNames()
StringSet res; StringSet res;
if (!getMeta()) return res; if (!getMeta()) return res;
for (auto & i : *meta) for (auto & i : *meta)
res.insert(i.name); res.emplace(state->symbols[i.name]);
return res; return res;
} }
@ -269,7 +269,7 @@ void DrvInfo::setMeta(const std::string & name, Value * v)
{ {
getMeta(); getMeta();
auto attrs = state->buildBindings(1 + (meta ? meta->size() : 0)); auto attrs = state->buildBindings(1 + (meta ? meta->size() : 0));
Symbol sym = state->symbols.create(name); auto sym = state->symbols.create(name);
if (meta) if (meta)
for (auto i : *meta) for (auto i : *meta)
if (i.name != sym) if (i.name != sym)
@ -356,11 +356,11 @@ static void getDerivations(EvalState & state, Value & vIn,
there are names clashes between derivations, the derivation there are names clashes between derivations, the derivation
bound to the attribute with the "lower" name should take bound to the attribute with the "lower" name should take
precedence). */ precedence). */
for (auto & i : v.attrs->lexicographicOrder()) { for (auto & i : v.attrs->lexicographicOrder(state.symbols)) {
debug("evaluating attribute '%1%'", i->name); debug("evaluating attribute '%1%'", state.symbols[i->name]);
if (!std::regex_match(std::string(i->name), attrRegex)) if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
continue; continue;
std::string pathPrefix2 = addToPath(pathPrefix, i->name); std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[i->name]);
if (combineChannels) if (combineChannels)
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) { else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
@ -369,7 +369,7 @@ static void getDerivations(EvalState & state, Value & vIn,
`recurseForDerivations = true' attribute. */ `recurseForDerivations = true' attribute. */
if (i->value->type() == nAttrs) { if (i->value->type() == nAttrs) {
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos)) if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos))
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
} }
} }

View file

@ -28,9 +28,9 @@ using namespace nix;
namespace nix { namespace nix {
static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data) static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data)
{ {
return Pos(data->origin, data->file, loc.first_line, loc.first_column); return data->state.positions.add(data->origin, loc.first_line, loc.first_column);
} }
#define CUR_POS makeCurPos(*yylloc, data) #define CUR_POS makeCurPos(*yylloc, data)
@ -155,7 +155,7 @@ or { return OR_KW; }
} catch (const boost::bad_lexical_cast &) { } catch (const boost::bad_lexical_cast &) {
throw ParseError({ throw ParseError({
.msg = hintfmt("invalid integer '%1%'", yytext), .msg = hintfmt("invalid integer '%1%'", yytext),
.errPos = CUR_POS, .errPos = data->state.positions[CUR_POS],
}); });
} }
return INT; return INT;
@ -165,7 +165,7 @@ or { return OR_KW; }
if (errno != 0) if (errno != 0)
throw ParseError({ throw ParseError({
.msg = hintfmt("invalid float '%1%'", yytext), .msg = hintfmt("invalid float '%1%'", yytext),
.errPos = CUR_POS, .errPos = data->state.positions[CUR_POS],
}); });
return FLOAT; return FLOAT;
} }
@ -294,7 +294,7 @@ or { return OR_KW; }
<INPATH_SLASH><<EOF>> { <INPATH_SLASH><<EOF>> {
throw ParseError({ throw ParseError({
.msg = hintfmt("path has a trailing slash"), .msg = hintfmt("path has a trailing slash"),
.errPos = CUR_POS, .errPos = data->state.positions[CUR_POS],
}); });
} }

View file

@ -1,5 +1,7 @@
#include "nixexpr.hh" #include "nixexpr.hh"
#include "derivations.hh" #include "derivations.hh"
#include "eval.hh"
#include "symbol-table.hh"
#include "util.hh" #include "util.hh"
#include <cstdlib> #include <cstdlib>
@ -10,12 +12,6 @@ namespace nix {
/* Displaying abstract syntax trees. */ /* 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) static void showString(std::ostream & str, std::string_view s)
{ {
str << '"'; str << '"';
@ -28,8 +24,10 @@ static void showString(std::ostream & str, std::string_view s)
str << '"'; 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()) if (s.empty())
str << "\"\""; str << "\"\"";
else if (s == "if") // FIXME: handle other keywords 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]; char c = s[0];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) { if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) {
showString(str, s); showString(str, s);
return; return str;
} }
for (auto c : s) for (auto c : s)
if (!((c >= 'a' && c <= 'z') || if (!((c >= 'a' && c <= 'z') ||
@ -46,89 +44,104 @@ static void showId(std::ostream & str, std::string_view s)
(c >= '0' && c <= '9') || (c >= '0' && c <= '9') ||
c == '_' || c == '\'' || c == '-')) { c == '_' || c == '\'' || c == '-')) {
showString(str, s); showString(str, s);
return; return str;
} }
str << s; str << s;
} }
}
std::ostream & operator << (std::ostream & str, const Symbol & sym)
{
showId(str, *sym.s);
return str; return str;
} }
void Expr::show(std::ostream & str) const void Expr::show(const SymbolTable & symbols, std::ostream & str) const
{ {
abort(); abort();
} }
void ExprInt::show(std::ostream & str) const void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const
{ {
str << n; str << n;
} }
void ExprFloat::show(std::ostream & str) const void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
{ {
str << nf; str << nf;
} }
void ExprString::show(std::ostream & str) const void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
{ {
showString(str, s); showString(str, s);
} }
void ExprPath::show(std::ostream & str) const void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
{ {
str << s; 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); str << "(";
if (def) str << " or (" << *def << ")"; 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 "; if (recursive) str << "rec ";
str << "{ "; str << "{ ";
typedef const decltype(attrs)::value_type * Attr; typedef const decltype(attrs)::value_type * Attr;
std::vector<Attr> sorted; std::vector<Attr> sorted;
for (auto & i : attrs) sorted.push_back(&i); for (auto & i : attrs) sorted.push_back(&i);
std::sort(sorted.begin(), sorted.end(), [](Attr a, Attr b) { std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) {
return (const std::string &) a->first < (const std::string &) b->first; std::string_view sa = symbols[a->first], sb = symbols[b->first];
return sa < sb;
}); });
for (auto & i : sorted) { for (auto & i : sorted) {
if (i->second.inherited) if (i->second.inherited)
str << "inherit " << i->first << " " << "; "; str << "inherit " << symbols[i->first] << " " << "; ";
else else {
str << i->first << " = " << *i->second.e << "; "; 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 << "}"; str << "}";
} }
void ExprList::show(std::ostream & str) const void ExprList::show(const SymbolTable & symbols, std::ostream & str) const
{ {
str << "[ "; str << "[ ";
for (auto & i : elems) for (auto & i : elems) {
str << "(" << *i << ") "; str << "(";
i->show(symbols, str);
str << ") ";
}
str << "]"; str << "]";
} }
void ExprLambda::show(std::ostream & str) const void ExprLambda::show(const SymbolTable & symbols, std::ostream & str) const
{ {
str << "("; str << "(";
if (hasFormals()) { if (hasFormals()) {
@ -136,74 +149,100 @@ void ExprLambda::show(std::ostream & str) const
bool first = true; bool first = true;
for (auto & i : formals->formals) { for (auto & i : formals->formals) {
if (first) first = false; else str << ", "; if (first) first = false; else str << ", ";
str << i.name; str << symbols[i.name];
if (i.def) str << " ? " << *i.def; if (i.def) {
str << " ? ";
i.def->show(symbols, str);
}
} }
if (formals->ellipsis) { if (formals->ellipsis) {
if (!first) str << ", "; if (!first) str << ", ";
str << "..."; str << "...";
} }
str << " }"; str << " }";
if (!arg.empty()) str << " @ "; if (arg) str << " @ ";
} }
if (!arg.empty()) str << arg; if (arg) str << symbols[arg];
str << ": " << *body << ")"; 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) { for (auto e : args) {
str << ' '; str << ' ';
str << *e; e->show(symbols, str);
} }
str << ')'; str << ')';
} }
void ExprLet::show(std::ostream & str) const void ExprLet::show(const SymbolTable & symbols, std::ostream & str) const
{ {
str << "(let "; str << "(let ";
for (auto & i : attrs->attrs) for (auto & i : attrs->attrs)
if (i.second.inherited) { if (i.second.inherited) {
str << "inherit " << i.first << "; "; str << "inherit " << symbols[i.first] << "; ";
} }
else else {
str << i.first << " = " << *i.second.e << "; "; str << symbols[i.first] << " = ";
str << "in " << *body << ")"; 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; bool first = true;
str << "("; str << "(";
for (auto & i : *es) { for (auto & i : *es) {
if (first) first = false; else str << " + "; if (first) first = false; else str << " + ";
str << *i.second; i.second->show(symbols, str);
} }
str << ")"; str << ")";
} }
void ExprPos::show(std::ostream & str) const void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const
{ {
str << "__curPos"; 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; std::ostringstream out;
bool first = true; bool first = true;
for (auto & i : attrPath) { for (auto & i : attrPath) {
if (!first) out << '.'; else first = false; if (!first) out << '.'; else first = false;
if (i.symbol.set()) if (i.symbol)
out << i.symbol; out << symbols[i.symbol];
else else {
out << "\"${" << *i.expr << "}\""; out << "\"${";
i.expr->show(symbols, out);
out << "}\"";
}
} }
return out.str(); return out.str();
} }
Pos noPos;
/* Computing levels/displacements for variables. */ /* Computing levels/displacements for variables. */
void Expr::bindVars(const StaticEnv & env) void Expr::bindVars(const EvalState & es, const StaticEnv & env)
{ {
abort(); 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, /* Check whether the variable appears in the environment. If so,
set its level and displacement. */ set its level and displacement. */
@ -301,31 +341,31 @@ void ExprVar::bindVars(const StaticEnv & env)
"undefined variable" error now. */ "undefined variable" error now. */
if (withLevel == -1) if (withLevel == -1)
throw UndefinedVarError({ throw UndefinedVarError({
.msg = hintfmt("undefined variable '%1%'", name), .msg = hintfmt("undefined variable '%1%'", es.symbols[name]),
.errPos = pos .errPos = es.positions[pos]
}); });
fromWith = true; fromWith = true;
this->level = withLevel; this->level = withLevel;
} }
void ExprSelect::bindVars(const StaticEnv & env) void ExprSelect::bindVars(const EvalState & es, const StaticEnv & env)
{ {
e->bindVars(env); e->bindVars(es, env);
if (def) def->bindVars(env); if (def) def->bindVars(es, env);
for (auto & i : attrPath) for (auto & i : attrPath)
if (!i.symbol.set()) if (!i.symbol)
i.expr->bindVars(env); 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) for (auto & i : attrPath)
if (!i.symbol.set()) if (!i.symbol)
i.expr->bindVars(env); i.expr->bindVars(es, env);
} }
void ExprAttrs::bindVars(const StaticEnv & env) void ExprAttrs::bindVars(const EvalState & es, const StaticEnv & env)
{ {
const StaticEnv * dynamicEnv = &env; const StaticEnv * dynamicEnv = &env;
StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0); 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. // No need to sort newEnv since attrs is in sorted order.
for (auto & i : attrs) for (auto & i : attrs)
i.second.e->bindVars(i.second.inherited ? env : newEnv); i.second.e->bindVars(es, i.second.inherited ? env : newEnv);
} }
else else
for (auto & i : attrs) for (auto & i : attrs)
i.second.e->bindVars(env); i.second.e->bindVars(es, env);
for (auto & i : dynamicAttrs) { for (auto & i : dynamicAttrs) {
i.nameExpr->bindVars(*dynamicEnv); i.nameExpr->bindVars(es, *dynamicEnv);
i.valueExpr->bindVars(*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) 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( StaticEnv newEnv(
false, &env, false, &env,
(hasFormals() ? formals->formals.size() : 0) + (hasFormals() ? formals->formals.size() : 0) +
(arg.empty() ? 0 : 1)); (!arg ? 0 : 1));
Displacement displ = 0; Displacement displ = 0;
if (!arg.empty()) newEnv.vars.emplace_back(arg, displ++); if (arg) newEnv.vars.emplace_back(arg, displ++);
if (hasFormals()) { if (hasFormals()) {
for (auto & i : formals->formals) for (auto & i : formals->formals)
@ -377,20 +417,20 @@ void ExprLambda::bindVars(const StaticEnv & env)
newEnv.sort(); newEnv.sort();
for (auto & i : formals->formals) 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) 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()); 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. // No need to sort newEnv since attrs->attrs is in sorted order.
for (auto & i : attrs->attrs) 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 /* Does this `with' have an enclosing `with'? If so, record its
level so that `lookupVar' can look up variables in the previous level so that `lookupVar' can look up variables in the previous
@ -420,57 +460,60 @@ void ExprWith::bindVars(const StaticEnv & env)
break; break;
} }
attrs->bindVars(env); attrs->bindVars(es, env);
StaticEnv newEnv(true, &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); cond->bindVars(es, env);
then->bindVars(env); then->bindVars(es, env);
else_->bindVars(env); else_->bindVars(es, env);
} }
void ExprAssert::bindVars(const StaticEnv & env) void ExprAssert::bindVars(const EvalState & es, const StaticEnv & env)
{ {
cond->bindVars(env); cond->bindVars(es, env);
body->bindVars(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) for (auto & i : *this->es)
i.second->bindVars(env); i.second->bindVars(es, env);
} }
void ExprPos::bindVars(const StaticEnv & env) void ExprPos::bindVars(const EvalState & es, const StaticEnv & env)
{ {
} }
/* Storing function names. */ /* 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; this->name = name;
body->setName(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 SymbolTable::totalSize() const
{ {
size_t n = 0; size_t n = 0;
for (auto & i : store) dump([&] (const std::string & s) { n += s.size(); });
n += i.size();
return n; return n;
} }

View file

@ -1,8 +1,12 @@
#pragma once #pragma once
#include <map>
#include <vector>
#include "value.hh" #include "value.hh"
#include "symbol-table.hh" #include "symbol-table.hh"
#include "error.hh" #include "error.hh"
#include "chunked-vector.hh"
namespace nix { namespace nix {
@ -23,32 +27,92 @@ MakeError(RestrictedPathError, Error);
struct Pos struct Pos
{ {
Symbol file; std::string file;
FileOrigin origin;
uint32_t line; uint32_t line;
FileOrigin origin:2; uint32_t column;
uint32_t column:30;
Pos() : line(0), origin(foString), column(0) { }; explicit operator bool() const { return line > 0; }
Pos(FileOrigin origin, const Symbol & file, uint32_t line, uint32_t column) };
: file(file), line(line), origin(origin), column(column) { };
operator bool() const 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
{ {
return line != 0; 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)
{
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; const auto idx = offsets.add({line, column}).second;
if (!p2.line) return false; if (origins.empty() || origins.back().idx != origin.idx) {
int d = ((const std::string &) file).compare((const std::string &) p2.file); origin.idx = idx;
if (d < 0) return true; origins.push_back(origin);
if (d > 0) return false; }
if (line < p2.line) return true; return PosIdx(idx + 1);
if (line > p2.line) return false; }
return column < p2.column;
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); std::ostream & operator << (std::ostream & str, const Pos & pos);
@ -64,13 +128,13 @@ struct AttrName
{ {
Symbol symbol; Symbol symbol;
Expr * expr; Expr * expr;
AttrName(const Symbol & s) : symbol(s) {}; AttrName(Symbol s) : symbol(s) {};
AttrName(Expr * e) : expr(e) {}; AttrName(Expr * e) : expr(e) {};
}; };
typedef std::vector<AttrName> AttrPath; 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. */ /* Abstract syntax of Nix expressions. */
@ -78,19 +142,17 @@ std::string showAttrPath(const AttrPath & attrPath);
struct Expr struct Expr
{ {
virtual ~Expr() { }; virtual ~Expr() { };
virtual void show(std::ostream & str) const; virtual void show(const SymbolTable & symbols, std::ostream & str) const;
virtual void bindVars(const StaticEnv & env); virtual void bindVars(const EvalState & es, const StaticEnv & env);
virtual void eval(EvalState & state, Env & env, Value & v); virtual void eval(EvalState & state, Env & env, Value & v);
virtual Value * maybeThunk(EvalState & state, Env & env); 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 \ #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 eval(EvalState & state, Env & env, Value & v); \
void bindVars(const StaticEnv & env); void bindVars(const EvalState & es, const StaticEnv & env);
struct ExprInt : Expr struct ExprInt : Expr
{ {
@ -133,7 +195,7 @@ typedef uint32_t Displacement;
struct ExprVar : Expr struct ExprVar : Expr
{ {
Pos pos; PosIdx pos;
Symbol name; Symbol name;
/* Whether the variable comes from an environment (e.g. a rec, let /* Whether the variable comes from an environment (e.g. a rec, let
@ -149,19 +211,19 @@ struct ExprVar : Expr
Level level; Level level;
Displacement displ; Displacement displ;
ExprVar(const Symbol & name) : name(name) { }; ExprVar(Symbol name) : name(name) { };
ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { }; ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { };
COMMON_METHODS COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env); Value * maybeThunk(EvalState & state, Env & env);
}; };
struct ExprSelect : Expr struct ExprSelect : Expr
{ {
Pos pos; PosIdx pos;
Expr * e, * def; Expr * e, * def;
AttrPath attrPath; AttrPath attrPath;
ExprSelect(const Pos & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; ExprSelect(const PosIdx & 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, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
COMMON_METHODS COMMON_METHODS
}; };
@ -176,13 +238,13 @@ struct ExprOpHasAttr : Expr
struct ExprAttrs : Expr struct ExprAttrs : Expr
{ {
bool recursive; bool recursive;
Pos pos; PosIdx pos;
struct AttrDef { struct AttrDef {
bool inherited; bool inherited;
Expr * e; Expr * e;
Pos pos; PosIdx pos;
Displacement displ; // displacement 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) { }; : inherited(inherited), e(e), pos(pos) { };
AttrDef() { }; AttrDef() { };
}; };
@ -190,14 +252,14 @@ struct ExprAttrs : Expr
AttrDefs attrs; AttrDefs attrs;
struct DynamicAttrDef { struct DynamicAttrDef {
Expr * nameExpr, * valueExpr; Expr * nameExpr, * valueExpr;
Pos pos; PosIdx pos;
DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos) DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const PosIdx & pos)
: nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { }; : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { };
}; };
typedef std::vector<DynamicAttrDef> DynamicAttrDefs; typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
DynamicAttrDefs dynamicAttrs; DynamicAttrDefs dynamicAttrs;
ExprAttrs(const Pos &pos) : recursive(false), pos(pos) { }; ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { };
ExprAttrs() : recursive(false), pos(noPos) { }; ExprAttrs() : recursive(false) { };
COMMON_METHODS COMMON_METHODS
}; };
@ -210,10 +272,9 @@ struct ExprList : Expr
struct Formal struct Formal
{ {
Pos pos; PosIdx pos;
Symbol name; Symbol name;
Expr * def; Expr * def;
Formal(const Pos & pos, const Symbol & name, Expr * def) : pos(pos), name(name), def(def) { };
}; };
struct Formals struct Formals
@ -222,18 +283,20 @@ struct Formals
Formals_ formals; Formals_ formals;
bool ellipsis; bool ellipsis;
bool has(Symbol arg) const { bool has(Symbol arg) const
{
auto it = std::lower_bound(formals.begin(), formals.end(), arg, auto it = std::lower_bound(formals.begin(), formals.end(), arg,
[] (const Formal & f, const Symbol & sym) { return f.name < sym; }); [] (const Formal & f, const Symbol & sym) { return f.name < sym; });
return it != formals.end() && it->name == arg; 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::vector<Formal> result(formals.begin(), formals.end());
std::sort(result.begin(), result.end(), std::sort(result.begin(), result.end(),
[] (const Formal & a, const Formal & b) { [&] (const Formal & a, const Formal & b) {
return std::string_view(a.name) < std::string_view(b.name); std::string_view sa = symbols[a.name], sb = symbols[b.name];
return sa < sb;
}); });
return result; return result;
} }
@ -241,17 +304,21 @@ struct Formals
struct ExprLambda : Expr struct ExprLambda : Expr
{ {
Pos pos; PosIdx pos;
Symbol name; Symbol name;
Symbol arg; Symbol arg;
Formals * formals; Formals * formals;
Expr * body; 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) : pos(pos), arg(arg), formals(formals), body(body)
{ {
}; };
void setName(Symbol & name); ExprLambda(PosIdx pos, Formals * formals, Expr * body)
std::string showNamePos() const; : pos(pos), formals(formals), body(body)
{
}
void setName(Symbol name);
std::string showNamePos(const EvalState & state) const;
inline bool hasFormals() const { return formals != nullptr; } inline bool hasFormals() const { return formals != nullptr; }
COMMON_METHODS COMMON_METHODS
}; };
@ -260,8 +327,8 @@ struct ExprCall : Expr
{ {
Expr * fun; Expr * fun;
std::vector<Expr *> args; std::vector<Expr *> args;
Pos pos; PosIdx pos;
ExprCall(const Pos & pos, Expr * fun, std::vector<Expr *> && args) ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
: fun(fun), args(args), pos(pos) : fun(fun), args(args), pos(pos)
{ } { }
COMMON_METHODS COMMON_METHODS
@ -277,26 +344,26 @@ struct ExprLet : Expr
struct ExprWith : Expr struct ExprWith : Expr
{ {
Pos pos; PosIdx pos;
Expr * attrs, * body; Expr * attrs, * body;
size_t prevWith; 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 COMMON_METHODS
}; };
struct ExprIf : Expr struct ExprIf : Expr
{ {
Pos pos; PosIdx pos;
Expr * cond, * then, * else_; 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 COMMON_METHODS
}; };
struct ExprAssert : Expr struct ExprAssert : Expr
{ {
Pos pos; PosIdx pos;
Expr * cond, * body; 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 COMMON_METHODS
}; };
@ -310,17 +377,17 @@ struct ExprOpNot : Expr
#define MakeBinOp(name, s) \ #define MakeBinOp(name, s) \
struct name : Expr \ struct name : Expr \
{ \ { \
Pos pos; \ PosIdx pos; \
Expr * e1, * e2; \ Expr * e1, * e2; \
name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \ name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \
name(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \ name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \
void show(std::ostream & str) const \ 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); \ void eval(EvalState & state, Env & env, Value & v); \
}; };
@ -335,18 +402,18 @@ MakeBinOp(ExprOpConcatLists, "++")
struct ExprConcatStrings : Expr struct ExprConcatStrings : Expr
{ {
Pos pos; PosIdx pos;
bool forceString; bool forceString;
std::vector<std::pair<Pos, Expr *> > * es; std::vector<std::pair<PosIdx, Expr *> > * es;
ExprConcatStrings(const Pos & pos, bool forceString, std::vector<std::pair<Pos, Expr *> > * es) ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *> > * es)
: pos(pos), forceString(forceString), es(es) { }; : pos(pos), forceString(forceString), es(es) { };
COMMON_METHODS COMMON_METHODS
}; };
struct ExprPos : Expr struct ExprPos : Expr
{ {
Pos pos; PosIdx pos;
ExprPos(const Pos & pos) : pos(pos) { }; ExprPos(const PosIdx & pos) : pos(pos) { };
COMMON_METHODS COMMON_METHODS
}; };
@ -384,7 +451,7 @@ struct StaticEnv
vars.erase(it, end); 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); Vars::value_type key(name, 0);
auto i = std::lower_bound(vars.begin(), vars.end(), key); auto i = std::lower_bound(vars.begin(), vars.end(), key);

View file

@ -32,12 +32,12 @@ namespace nix {
SymbolTable & symbols; SymbolTable & symbols;
Expr * result; Expr * result;
Path basePath; Path basePath;
Symbol file; PosTable::Origin origin;
FileOrigin origin;
std::optional<ErrorInfo> error; std::optional<ErrorInfo> error;
ParseData(EvalState & state) ParseData(EvalState & state, PosTable::Origin origin)
: state(state) : state(state)
, symbols(state.symbols) , symbols(state.symbols)
, origin(std::move(origin))
{ }; { };
}; };
@ -77,26 +77,26 @@ using namespace nix;
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({ throw ParseError({
.msg = hintfmt("attribute '%1%' already defined at %2%", .msg = hintfmt("attribute '%1%' already defined at %2%",
showAttrPath(attrPath), prevPos), showAttrPath(state.symbols, attrPath), state.positions[prevPos]),
.errPos = pos .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({ throw ParseError({
.msg = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos), .msg = hintfmt("attribute '%1%' already defined at %2%", state.symbols[attr], state.positions[prevPos]),
.errPos = pos .errPos = state.positions[pos]
}); });
} }
static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
Expr * e, const Pos & pos) Expr * e, const PosIdx pos, const nix::EvalState & state)
{ {
AttrPath::iterator i; AttrPath::iterator i;
// All attrpaths have at least one attr // All attrpaths have at least one attr
@ -104,15 +104,15 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
// Checking attrPath validity. // Checking attrPath validity.
// =========================== // ===========================
for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) { 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); ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) { if (j != attrs->attrs.end()) {
if (!j->second.inherited) { if (!j->second.inherited) {
ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e); 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; attrs = attrs2;
} else } else
dupAttr(attrPath, pos, j->second.pos); dupAttr(state, attrPath, pos, j->second.pos);
} else { } else {
ExprAttrs * nested = new ExprAttrs; ExprAttrs * nested = new ExprAttrs;
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos); attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos);
@ -126,7 +126,7 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
} }
// Expr insertion. // Expr insertion.
// ========================== // ==========================
if (i->symbol.set()) { if (i->symbol) {
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) { if (j != attrs->attrs.end()) {
// This attr path is already defined. However, if both // 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) { for (auto & ad : ae->attrs) {
auto j2 = jAttrs->attrs.find(ad.first); auto j2 = jAttrs->attrs.find(ad.first);
if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error. 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); jAttrs->attrs.emplace(ad.first, ad.second);
} }
} else { } else {
dupAttr(attrPath, pos, j->second.pos); dupAttr(state, attrPath, pos, j->second.pos);
} }
} else { } else {
// This attr path is not defined. Let's create it. // 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, 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(), std::sort(formals->formals.begin(), formals->formals.end(),
[] (const auto & a, const auto & b) { [] (const auto & a, const auto & b) {
return std::tie(a.name, a.pos) < std::tie(b.name, b.pos); 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++) { for (size_t i = 0; i + 1 < formals->formals.size(); i++) {
if (formals->formals[i].name != formals->formals[i + 1].name) if (formals->formals[i].name != formals->formals[i + 1].name)
continue; continue;
@ -173,18 +173,18 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
} }
if (duplicate) if (duplicate)
throw ParseError({ throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", duplicate->first), .msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[duplicate->first]),
.errPos = duplicate->second .errPos = data.state.positions[duplicate->second]
}); });
Formals result; Formals result;
result.ellipsis = formals->ellipsis; result.ellipsis = formals->ellipsis;
result.formals = std::move(formals->formals); result.formals = std::move(formals->formals);
if (arg.set() && result.has(arg)) if (arg && result.has(arg))
throw ParseError({ throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", arg), .msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[arg]),
.errPos = pos .errPos = data.state.positions[pos]
}); });
delete formals; delete formals;
@ -192,8 +192,8 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
} }
static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols,
std::vector<std::pair<Pos, std::variant<Expr *, StringToken> > > & es) std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken> > > & es)
{ {
if (es.empty()) return new ExprString(""); if (es.empty()) return new ExprString("");
@ -233,7 +233,7 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
} }
/* Strip spaces from each line. */ /* 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; atStartOfLine = true;
size_t curDropped = 0; size_t curDropped = 0;
size_t n = es.size(); 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) #define CUR_POS makeCurPos(*yylocp, data)
@ -299,7 +299,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
{ {
data->error = { data->error = {
.msg = hintfmt(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 uri;
StringToken str; StringToken str;
std::vector<nix::AttrName> * attrNames; std::vector<nix::AttrName> * attrNames;
std::vector<std::pair<nix::Pos, nix::Expr *> > * string_parts; std::vector<std::pair<nix::PosIdx, nix::Expr *> > * string_parts;
std::vector<std::pair<nix::Pos, std::variant<nix::Expr *, StringToken> > > * ind_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 %type <e> start expr expr_function expr_if expr_op
@ -369,15 +369,15 @@ expr_function
: ID ':' expr_function : ID ':' expr_function
{ $$ = new ExprLambda(CUR_POS, data->symbols.create($1), 0, $3); } { $$ = new ExprLambda(CUR_POS, data->symbols.create($1), 0, $3); }
| '{' formals '}' ':' expr_function | '{' 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 | '{' 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); $$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $2, CUR_POS, arg), $7);
} }
| ID '@' '{' formals '}' ':' expr_function | 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); $$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $4, CUR_POS, arg), $7);
} }
| ASSERT expr ';' expr_function | ASSERT expr ';' expr_function
@ -388,7 +388,7 @@ expr_function
{ if (!$2->dynamicAttrs.empty()) { if (!$2->dynamicAttrs.empty())
throw ParseError({ throw ParseError({
.msg = hintfmt("dynamic attributes not allowed in let"), .msg = hintfmt("dynamic attributes not allowed in let"),
.errPos = CUR_POS .errPos = data->state.positions[CUR_POS]
}); });
$$ = new ExprLet($2, $4); $$ = new ExprLet($2, $4);
} }
@ -415,7 +415,7 @@ expr_op
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); } | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); }
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
| expr_op '+' expr_op | expr_op '+' expr_op
{ $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<Pos, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } { $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<PosIdx, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
| expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); } | expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
| expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); } | expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
| expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); } | expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); }
@ -477,7 +477,7 @@ expr_simple
if (noURLLiterals) if (noURLLiterals)
throw ParseError({ throw ParseError({
.msg = hintfmt("URL literals are disabled"), .msg = hintfmt("URL literals are disabled"),
.errPos = CUR_POS .errPos = data->state.positions[CUR_POS]
}); });
$$ = new ExprString(std::string($1)); $$ = new ExprString(std::string($1));
} }
@ -503,9 +503,9 @@ string_parts_interpolated
: string_parts_interpolated STR : string_parts_interpolated STR
{ $$ = $1; $1->emplace_back(makeCurPos(@2, data), new ExprString(std::string($2))); } { $$ = $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); } | 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 '}' { | 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(@1, data), new ExprString(std::string($1)));
$$->emplace_back(makeCurPos(@2, data), $3); $$->emplace_back(makeCurPos(@2, data), $3);
} }
@ -528,17 +528,17 @@ path_start
ind_string_parts ind_string_parts
: ind_string_parts IND_STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $2); } : 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); } | 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
: 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 ';' | binds INHERIT attrs ';'
{ $$ = $1; { $$ = $1;
for (auto & i : *$3) { for (auto & i : *$3) {
if ($$->attrs.find(i.symbol) != $$->attrs.end()) if ($$->attrs.find(i.symbol) != $$->attrs.end())
dupAttr(i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos); dupAttr(data->state, i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos);
Pos pos = makeCurPos(@3, data); auto pos = makeCurPos(@3, data);
$$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true)); $$->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. */ /* !!! Should ensure sharing of the expression in $4. */
for (auto & i : *$6) { for (auto & i : *$6) {
if ($$->attrs.find(i.symbol) != $$->attrs.end()) 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))); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data)));
} }
} }
@ -565,7 +565,7 @@ attrs
} else } else
throw ParseError({ throw ParseError({
.msg = hintfmt("dynamic attributes not allowed in inherit"), .msg = hintfmt("dynamic attributes not allowed in inherit"),
.errPos = makeCurPos(@2, data) .errPos = data->state.positions[makeCurPos(@2, data)]
}); });
} }
| { $$ = new AttrPath; } | { $$ = new AttrPath; }
@ -621,8 +621,8 @@ formals
; ;
formal formal
: ID { $$ = new Formal(CUR_POS, data->symbols.create($1), 0); } : ID { $$ = new Formal{CUR_POS, data->symbols.create($1), 0}; }
| ID '?' expr { $$ = new Formal(CUR_POS, data->symbols.create($1), $3); } | 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) const PathView path, const PathView basePath, StaticEnv & staticEnv)
{ {
yyscan_t scanner; yyscan_t scanner;
ParseData data(*this); std::string file;
data.origin = origin;
switch (origin) { switch (origin) {
case foFile: case foFile:
data.file = data.symbols.create(path); file = path;
break; break;
case foStdin: case foStdin:
case foString: case foString:
data.file = data.symbols.create(text); file = text;
break; break;
default: default:
assert(false); assert(false);
} }
ParseData data(*this, {file, origin});
data.basePath = basePath; data.basePath = basePath;
yylex_init(&scanner); yylex_init(&scanner);
@ -668,7 +668,7 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
if (res) throw ParseError(data.error.value()); if (res) throw ParseError(data.error.value());
data.result->bindVars(staticEnv); data.result->bindVars(*this, staticEnv);
return data.result; 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) { for (auto & i : searchPath) {
std::string suffix; 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)" ? "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)", : "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
path), path),
.errPos = pos .errPos = positions[pos]
}); });
} }

File diff suppressed because it is too large Load diff

View file

@ -38,9 +38,9 @@ struct RegisterPrimOp
them. */ them. */
/* Load a ValueInitializer from a DSO and return whatever it initializes */ /* 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 */ /* 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

@ -5,7 +5,7 @@
namespace nix { 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; PathSet context;
auto s = state.coerceToString(pos, *args[0], context); auto s = state.coerceToString(pos, *args[0], context);
@ -15,7 +15,7 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos,
static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext); static 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; PathSet context;
state.forceString(*args[0], context, pos); state.forceString(*args[0], context, pos);
@ -31,7 +31,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
source-only deployment). This primop marks the string context so source-only deployment). This primop marks the string context so
that builtins.derivation adds the path to drv.inputSrcs rather than that builtins.derivation adds the path to drv.inputSrcs rather than
drv.inputDrvs. */ 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; PathSet context;
auto s = state.coerceToString(pos, *args[0], context); auto s = state.coerceToString(pos, *args[0], context);
@ -65,7 +65,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutpu
Note that for a given path any combination of the above attributes Note that for a given path any combination of the above attributes
may be present. 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 { struct ContextInfo {
bool path = false; bool path = false;
@ -134,7 +134,7 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
See the commentary above unsafeGetContext for details of the See the commentary above unsafeGetContext for details of the
context representation. 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; PathSet context;
auto orig = state.forceString(*args[0], context, pos); auto orig = state.forceString(*args[0], context, pos);
@ -144,45 +144,46 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
auto sPath = state.symbols.create("path"); auto sPath = state.symbols.create("path");
auto sAllOutputs = state.symbols.create("allOutputs"); auto sAllOutputs = state.symbols.create("allOutputs");
for (auto & i : *args[1]->attrs) { 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({ throw EvalError({
.msg = hintfmt("Context key '%s' is not a store path", i.name), .msg = hintfmt("Context key '%s' is not a store path", name),
.errPos = *i.pos .errPos = state.positions[i.pos]
}); });
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(state.store->parseStorePath(i.name)); state.store->ensurePath(state.store->parseStorePath(name));
state.forceAttrs(*i.value, *i.pos); state.forceAttrs(*i.value, i.pos);
auto iter = i.value->attrs->find(sPath); auto iter = i.value->attrs->find(sPath);
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, *iter->pos)) if (state.forceBool(*iter->value, iter->pos))
context.insert(i.name); context.emplace(name);
} }
iter = i.value->attrs->find(sAllOutputs); iter = i.value->attrs->find(sAllOutputs);
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, *iter->pos)) { if (state.forceBool(*iter->value, iter->pos)) {
if (!isDerivation(i.name)) { if (!isDerivation(name)) {
throw EvalError({ throw EvalError({
.msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name), .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", name),
.errPos = *i.pos .errPos = state.positions[i.pos]
}); });
} }
context.insert("=" + std::string(i.name)); context.insert(concatStrings("=", name));
} }
} }
iter = i.value->attrs->find(state.sOutputs); iter = i.value->attrs->find(state.sOutputs);
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, *iter->pos); state.forceList(*iter->value, iter->pos);
if (iter->value->listSize() && !isDerivation(i.name)) { if (iter->value->listSize() && !isDerivation(name)) {
throw EvalError({ throw EvalError({
.msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name), .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", name),
.errPos = *i.pos .errPos = state.positions[i.pos]
}); });
} }
for (auto elem : iter->value->listItems()) { for (auto elem : iter->value->listItems()) {
auto name = state.forceStringNoCtx(*elem, *iter->pos); auto outputName = state.forceStringNoCtx(*elem, iter->pos);
context.insert(concatStrings("!", name, "!", i.name)); context.insert(concatStrings("!", outputName, "!", name));
} }
} }
} }

View file

@ -5,7 +5,7 @@
namespace nix { namespace nix {
static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
state.forceAttrs(*args[0], pos); state.forceAttrs(*args[0], pos);
@ -15,40 +15,42 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
std::optional<StorePath> toPath; std::optional<StorePath> toPath;
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs) {
if (attr.name == "fromPath") { const auto & attrName = state.symbols[attr.name];
if (attrName == "fromPath") {
PathSet context; PathSet context;
fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context); fromPath = state.coerceToStorePath(attr.pos, *attr.value, context);
} }
else if (attr.name == "toPath") { else if (attrName == "toPath") {
state.forceValue(*attr.value, *attr.pos); state.forceValue(*attr.value, attr.pos);
toCA = true; toCA = true;
if (attr.value->type() != nString || attr.value->string.s != std::string("")) { if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
PathSet context; PathSet context;
toPath = state.coerceToStorePath(*attr.pos, *attr.value, context); toPath = state.coerceToStorePath(attr.pos, *attr.value, context);
} }
} }
else if (attr.name == "fromStore") else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos); fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos);
else else
throw Error({ throw Error({
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name), .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
.errPos = pos .errPos = state.positions[pos]
}); });
} }
if (!fromPath) if (!fromPath)
throw Error({ throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"), .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.errPos = pos .errPos = state.positions[pos]
}); });
if (!fromStoreUrl) if (!fromStoreUrl)
throw Error({ throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"), .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.errPos = pos .errPos = state.positions[pos]
}); });
auto parsedURL = parseURL(*fromStoreUrl); auto parsedURL = parseURL(*fromStoreUrl);
@ -58,13 +60,13 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file")) !(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
throw Error({ throw Error({
.msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"), .msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
.errPos = pos .errPos = state.positions[pos]
}); });
if (!parsedURL.query.empty()) if (!parsedURL.query.empty())
throw Error({ throw Error({
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl), .msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
.errPos = pos .errPos = state.positions[pos]
}); });
auto fromStore = openStore(parsedURL.to_string()); auto fromStore = openStore(parsedURL.to_string());
@ -80,7 +82,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
state.store->printStorePath(*fromPath), state.store->printStorePath(*fromPath),
state.store->printStorePath(i->second), state.store->printStorePath(i->second),
state.store->printStorePath(*toPath)), state.store->printStorePath(*toPath)),
.errPos = pos .errPos = state.positions[pos]
}); });
if (!toPath) if (!toPath)
throw Error({ throw Error({
@ -89,7 +91,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
"please set this in the 'toPath' attribute passed to 'fetchClosure'", "please set this in the 'toPath' attribute passed to 'fetchClosure'",
state.store->printStorePath(*fromPath), state.store->printStorePath(*fromPath),
state.store->printStorePath(i->second)), state.store->printStorePath(i->second)),
.errPos = pos .errPos = state.positions[pos]
}); });
} }
} else { } else {
@ -105,7 +107,7 @@ static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args
throw Error({ throw Error({
.msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't", .msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't",
state.store->printStorePath(*toPath)), state.store->printStorePath(*toPath)),
.errPos = pos .errPos = state.positions[pos]
}); });
} }

View file

@ -7,7 +7,7 @@
namespace nix { 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::string url;
std::optional<Hash> rev; std::optional<Hash> rev;
@ -22,31 +22,31 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
state.forceAttrs(*args[0], pos); state.forceAttrs(*args[0], pos);
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs) {
std::string_view n(attr.name); std::string_view n(state.symbols[attr.name]);
if (n == "url") if (n == "url")
url = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned(); url = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned();
else if (n == "rev") { else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can // Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name. // be both a revision or a branch/tag name.
auto value = state.forceStringNoCtx(*attr.value, *attr.pos); auto value = state.forceStringNoCtx(*attr.value, attr.pos);
if (std::regex_match(value.begin(), value.end(), revRegex)) if (std::regex_match(value.begin(), value.end(), revRegex))
rev = Hash::parseAny(value, htSHA1); rev = Hash::parseAny(value, htSHA1);
else else
ref = value; ref = value;
} }
else if (n == "name") else if (n == "name")
name = state.forceStringNoCtx(*attr.value, *attr.pos); name = state.forceStringNoCtx(*attr.value, attr.pos);
else else
throw EvalError({ throw EvalError({
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name), .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
.errPos = *attr.pos .errPos = state.positions[attr.pos]
}); });
} }
if (url.empty()) if (url.empty())
throw EvalError({ throw EvalError({
.msg = hintfmt("'url' argument required"), .msg = hintfmt("'url' argument required"),
.errPos = pos .errPos = state.positions[pos]
}); });
} else } else

View file

@ -90,7 +90,7 @@ struct FetchTreeParams {
static void fetchTree( static void fetchTree(
EvalState & state, EvalState & state,
const Pos & pos, const PosIdx pos,
Value * * args, Value * * args,
Value & v, Value & v,
std::optional<std::string> type, std::optional<std::string> type,
@ -110,43 +110,43 @@ static void fetchTree(
if (type) if (type)
throw Error({ throw Error({
.msg = hintfmt("unexpected attribute 'type'"), .msg = hintfmt("unexpected attribute 'type'"),
.errPos = pos .errPos = state.positions[pos]
}); });
type = state.forceStringNoCtx(*aType->value, *aType->pos); type = state.forceStringNoCtx(*aType->value, aType->pos);
} else if (!type) } else if (!type)
throw Error({ throw Error({
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
.errPos = pos .errPos = state.positions[pos]
}); });
attrs.emplace("type", type.value()); attrs.emplace("type", type.value());
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs) {
if (attr.name == state.sType) continue; 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) { if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned(); auto s = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned();
attrs.emplace(attr.name, attrs.emplace(state.symbols[attr.name],
attr.name == "url" state.symbols[attr.name] == "url"
? type == "git" ? type == "git"
? fixURIForGit(s, state) ? fixURIForGit(s, state)
: fixURI(s, state) : fixURI(s, state)
: s); : s);
} }
else if (attr.value->type() == nBool) 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) 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 else
throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", 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 (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
throw Error({ throw Error({
.msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"), .msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"),
.errPos = pos .errPos = state.positions[pos]
}); });
input = fetchers::Input::fromAttrs(std::move(attrs)); input = fetchers::Input::fromAttrs(std::move(attrs));
@ -167,7 +167,7 @@ static void fetchTree(
input = lookupInRegistries(state.store, input).first; input = lookupInRegistries(state.store, input).first;
if (evalSettings.pureEval && !input.isLocked()) 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); auto [tree, input2] = input.fetch(state.store);
@ -176,7 +176,7 @@ static void fetchTree(
emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false); 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); settings.requireExperimentalFeature(Xp::Flakes);
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false }); 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 // FIXME: document
static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree); 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) const std::string & who, bool unpack, std::string name)
{ {
std::optional<std::string> url; std::optional<std::string> url;
@ -198,24 +198,24 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
state.forceAttrs(*args[0], pos); state.forceAttrs(*args[0], pos);
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs) {
std::string n(attr.name); std::string_view n(state.symbols[attr.name]);
if (n == "url") if (n == "url")
url = state.forceStringNoCtx(*attr.value, *attr.pos); url = state.forceStringNoCtx(*attr.value, attr.pos);
else if (n == "sha256") else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256); expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256);
else if (n == "name") else if (n == "name")
name = state.forceStringNoCtx(*attr.value, *attr.pos); name = state.forceStringNoCtx(*attr.value, attr.pos);
else else
throw EvalError({ throw EvalError({
.msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who), .msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
.errPos = *attr.pos .errPos = state.positions[attr.pos]
}); });
} }
if (!url) if (!url)
throw EvalError({ throw EvalError({
.msg = hintfmt("'url' argument required"), .msg = hintfmt("'url' argument required"),
.errPos = pos .errPos = state.positions[pos]
}); });
} else } else
url = state.forceStringNoCtx(*args[0], pos); url = state.forceStringNoCtx(*args[0], pos);
@ -262,7 +262,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
state.allowAndSetStorePathString(storePath, v); 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, ""); fetch(state, pos, args, v, "fetchurl", false, "");
} }
@ -278,7 +278,7 @@ static RegisterPrimOp primop_fetchurl({
.fun = prim_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"); fetch(state, pos, args, v, "fetchTarball", true, "source");
} }
@ -329,7 +329,7 @@ static RegisterPrimOp primop_fetchTarball({
.fun = prim_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 }); fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
} }

View file

@ -5,7 +5,7 @@
namespace nix { 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); auto toml = state.forceStringNoCtx(*args[0], pos);
@ -73,7 +73,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
} catch (std::exception & e) { // TODO: toml::syntax_error } catch (std::exception & e) { // TODO: toml::syntax_error
throw EvalError({ throw EvalError({
.msg = hintfmt("while parsing a TOML string: %s", e.what()), .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 <unordered_map>
#include "types.hh" #include "types.hh"
#include "chunked-vector.hh"
namespace nix { namespace nix {
/* Symbol table used by the parser and evaluator to represent and look /* Symbol table used by the parser and evaluator to represent and look
up identifiers and attributes efficiently. SymbolTable::create() up identifiers and attributes efficiently. SymbolTable::create()
converts a string into a symbol. Symbols have the property that 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. */ 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; friend class SymbolTable;
private:
const std::string * s;
explicit SymbolStr(const std::string & symbol): s(&symbol) {}
public: public:
Symbol() : s(0) { };
bool operator == (const Symbol & s2) const
{
return s == s2.s;
}
// FIXME: remove
bool operator == (std::string_view s2) const bool operator == (std::string_view s2) const
{ {
return s->compare(s2) == 0; return *s == s2;
}
bool operator != (const Symbol & s2) const
{
return s != s2.s;
}
bool operator < (const Symbol & s2) const
{
return s < s2.s;
} }
operator const std::string & () const operator const std::string & () const
@ -55,51 +43,78 @@ public:
return *s; return *s;
} }
bool set() const friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol);
{ };
return s;
}
bool empty() const class Symbol
{ {
return s->empty(); 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 class SymbolTable
{ {
private: private:
std::unordered_map<std::string_view, Symbol> symbols; std::unordered_map<std::string_view, std::pair<const std::string *, uint32_t>> symbols;
std::list<std::string> store; ChunkedVector<std::string, 8192> store{16};
public: public:
Symbol create(std::string_view s) Symbol create(std::string_view s)
{ {
// Most symbols are looked up more than once, so we trade off insertion performance // Most symbols are looked up more than once, so we trade off insertion performance
// for lookup performance. // for lookup performance.
// TODO: could probably be done more efficiently with transparent Hash and Equals // TODO: could probably be done more efficiently with transparent Hash and Equals
// on the original implementation using unordered_set // on the original implementation using unordered_set
// FIXME: make this thread-safe.
auto it = symbols.find(s); 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); const auto & [rawSym, idx] = store.add(std::string(s));
return symbols.emplace(rawSym, Symbol(&rawSym)).first->second; 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 size_t size() const
{ {
return symbols.size(); return store.size();
} }
size_t totalSize() const; size_t totalSize() const;
template<typename T> template<typename T>
void dump(T callback) void dump(T callback) const
{ {
for (auto & s : store) store.forEach(callback);
callback(s);
} }
}; };

68
src/libexpr/tests/json.cc Normal file
View file

@ -0,0 +1,68 @@
#include "libexprtests.hh"
#include "value-to-json.hh"
namespace nix {
// Testing the conversion to JSON
class JSONValueTest : public LibExprTest {
protected:
std::string getJSONValue(Value& value) {
std::stringstream ss;
PathSet ps;
printValueAsJSON(state, true, value, noPos, ss, ps);
return ss.str();
}
};
TEST_F(JSONValueTest, null) {
Value v;
v.mkNull();
ASSERT_EQ(getJSONValue(v), "null");
}
TEST_F(JSONValueTest, BoolFalse) {
Value v;
v.mkBool(false);
ASSERT_EQ(getJSONValue(v),"false");
}
TEST_F(JSONValueTest, BoolTrue) {
Value v;
v.mkBool(true);
ASSERT_EQ(getJSONValue(v), "true");
}
TEST_F(JSONValueTest, IntPositive) {
Value v;
v.mkInt(100);
ASSERT_EQ(getJSONValue(v), "100");
}
TEST_F(JSONValueTest, IntNegative) {
Value v;
v.mkInt(-100);
ASSERT_EQ(getJSONValue(v), "-100");
}
TEST_F(JSONValueTest, String) {
Value v;
v.mkString("test");
ASSERT_EQ(getJSONValue(v), "\"test\"");
}
TEST_F(JSONValueTest, StringQuotes) {
Value v;
v.mkString("test\"");
ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
}
// The dummy store doesn't support writing files. Fails with this exception message:
// C++ exception with description "error: operation 'addToStoreFromDump' is
// not supported by store 'dummy'" thrown in the test body.
TEST_F(JSONValueTest, DISABLED_Path) {
Value v;
v.mkPath("test");
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
}
} /* namespace nix */

View file

@ -0,0 +1,136 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "value.hh"
#include "nixexpr.hh"
#include "eval.hh"
#include "eval-inline.hh"
#include "store-api.hh"
namespace nix {
class LibExprTest : public ::testing::Test {
public:
static void SetUpTestSuite() {
initGC();
}
protected:
LibExprTest()
: store(openStore("dummy://"))
, state({}, store)
{
}
Value eval(std::string input, bool forceValue = true) {
Value v;
Expr * e = state.parseExprFromString(input, "");
assert(e);
state.eval(e, v);
if (forceValue)
state.forceValue(v, noPos);
return v;
}
Symbol createSymbol(const char * value) {
return state.symbols.create(value);
}
ref<Store> store;
EvalState state;
};
MATCHER(IsListType, "") {
return arg != nList;
}
MATCHER(IsList, "") {
return arg.type() == nList;
}
MATCHER(IsString, "") {
return arg.type() == nString;
}
MATCHER(IsNull, "") {
return arg.type() == nNull;
}
MATCHER(IsThunk, "") {
return arg.type() == nThunk;
}
MATCHER(IsAttrs, "") {
return arg.type() == nAttrs;
}
MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s)) {
if (arg.type() != nString) {
return false;
}
return std::string_view(arg.string.s) == s;
}
MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v)) {
if (arg.type() != nInt) {
return false;
}
return arg.integer == v;
}
MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v)) {
if (arg.type() != nFloat) {
return false;
}
return arg.fpoint == v;
}
MATCHER(IsTrue, "") {
if (arg.type() != nBool) {
return false;
}
return arg.boolean == true;
}
MATCHER(IsFalse, "") {
if (arg.type() != nBool) {
return false;
}
return arg.boolean == false;
}
MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) {
if (arg.type() != nPath) {
*result_listener << "Expected a path got " << arg.type();
return false;
} else if (std::string_view(arg.string.s) != p) {
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << arg.string.s;
return false;
}
return true;
}
MATCHER_P(IsListOfSize, n, fmt("Is a list of size [%1%]", n)) {
if (arg.type() != nList) {
*result_listener << "Expected list got " << arg.type();
return false;
} else if (arg.listSize() != (size_t)n) {
*result_listener << "Expected as list of size " << n << " got " << arg.listSize();
return false;
}
return true;
}
MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) {
if (arg.type() != nAttrs) {
*result_listener << "Expexted set got " << arg.type();
return false;
} else if (arg.attrs->size() != (size_t)n) {
*result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size();
return false;
}
return true;
}
} /* namespace nix */

View file

@ -0,0 +1,15 @@
check: libexpr-tests_RUN
programs += libexpr-tests
libexpr-tests_DIR := $(d)
libexpr-tests_INSTALL_DIR :=
libexpr-tests_SOURCES := $(wildcard $(d)/*.cc)
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests
libexpr-tests_LIBS = libexpr libutil libstore libfetchers
libexpr-tests_LDFLAGS := $(GTEST_LIBS) -lgmock

View file

@ -0,0 +1,839 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "libexprtests.hh"
namespace nix {
class CaptureLogger : public Logger
{
std::ostringstream oss;
public:
CaptureLogger() {}
std::string get() const {
return oss.str();
}
void log(Verbosity lvl, const FormatOrString & fs) override {
oss << fs.s << std::endl;
}
void logEI(const ErrorInfo & ei) override {
showErrorInfo(oss, ei, loggerSettings.showTrace.get());
}
};
class CaptureLogging {
Logger * oldLogger;
std::unique_ptr<CaptureLogger> tempLogger;
public:
CaptureLogging() : tempLogger(std::make_unique<CaptureLogger>()) {
oldLogger = logger;
logger = tempLogger.get();
}
~CaptureLogging() {
logger = oldLogger;
}
std::string get() const {
return tempLogger->get();
}
};
// Testing eval of PrimOp's
class PrimOpTest : public LibExprTest {};
TEST_F(PrimOpTest, throw) {
ASSERT_THROW(eval("throw \"foo\""), ThrownError);
}
TEST_F(PrimOpTest, abort) {
ASSERT_THROW(eval("abort \"abort\""), Abort);
}
TEST_F(PrimOpTest, ceil) {
auto v = eval("builtins.ceil 1.9");
ASSERT_THAT(v, IsIntEq(2));
}
TEST_F(PrimOpTest, floor) {
auto v = eval("builtins.floor 1.9");
ASSERT_THAT(v, IsIntEq(1));
}
TEST_F(PrimOpTest, tryEvalFailure) {
auto v = eval("builtins.tryEval (throw \"\")");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto s = createSymbol("success");
auto p = v.attrs->get(s);
ASSERT_NE(p, nullptr);
ASSERT_THAT(*p->value, IsFalse());
}
TEST_F(PrimOpTest, tryEvalSuccess) {
auto v = eval("builtins.tryEval 123");
ASSERT_THAT(v, IsAttrs());
auto s = createSymbol("success");
auto p = v.attrs->get(s);
ASSERT_NE(p, nullptr);
ASSERT_THAT(*p->value, IsTrue());
s = createSymbol("value");
p = v.attrs->get(s);
ASSERT_NE(p, nullptr);
ASSERT_THAT(*p->value, IsIntEq(123));
}
TEST_F(PrimOpTest, getEnv) {
setenv("_NIX_UNIT_TEST_ENV_VALUE", "test value", 1);
auto v = eval("builtins.getEnv \"_NIX_UNIT_TEST_ENV_VALUE\"");
ASSERT_THAT(v, IsStringEq("test value"));
}
TEST_F(PrimOpTest, seq) {
ASSERT_THROW(eval("let x = throw \"test\"; in builtins.seq x { }"), ThrownError);
}
TEST_F(PrimOpTest, seqNotDeep) {
auto v = eval("let x = { z = throw \"test\"; }; in builtins.seq x { }");
ASSERT_THAT(v, IsAttrs());
}
TEST_F(PrimOpTest, deepSeq) {
ASSERT_THROW(eval("let x = { z = throw \"test\"; }; in builtins.deepSeq x { }"), ThrownError);
}
TEST_F(PrimOpTest, trace) {
CaptureLogging l;
auto v = eval("builtins.trace \"test string 123\" 123");
ASSERT_THAT(v, IsIntEq(123));
auto text = l.get();
ASSERT_NE(text.find("test string 123"), std::string::npos);
}
TEST_F(PrimOpTest, placeholder) {
auto v = eval("builtins.placeholder \"out\"");
ASSERT_THAT(v, IsStringEq("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"));
}
TEST_F(PrimOpTest, baseNameOf) {
auto v = eval("builtins.baseNameOf /some/path");
ASSERT_THAT(v, IsStringEq("path"));
}
TEST_F(PrimOpTest, dirOf) {
auto v = eval("builtins.dirOf /some/path");
ASSERT_THAT(v, IsPathEq("/some"));
}
TEST_F(PrimOpTest, attrValues) {
auto v = eval("builtins.attrValues { x = \"foo\"; a = 1; }");
ASSERT_THAT(v, IsListOfSize(2));
ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
ASSERT_THAT(*v.listElems()[1], IsStringEq("foo"));
}
TEST_F(PrimOpTest, getAttr) {
auto v = eval("builtins.getAttr \"x\" { x = \"foo\"; }");
ASSERT_THAT(v, IsStringEq("foo"));
}
TEST_F(PrimOpTest, getAttrNotFound) {
// FIXME: TypeError is really bad here, also the error wording is worse
// than on Nix <=2.3
ASSERT_THROW(eval("builtins.getAttr \"y\" { }"), TypeError);
}
TEST_F(PrimOpTest, unsafeGetAttrPos) {
// The `y` attribute is at position
const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }";
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(3));
auto file = v.attrs->find(createSymbol("file"));
ASSERT_NE(file, nullptr);
// FIXME: The file when running these tests is the input string?!?
ASSERT_THAT(*file->value, IsStringEq(expr));
auto line = v.attrs->find(createSymbol("line"));
ASSERT_NE(line, nullptr);
ASSERT_THAT(*line->value, IsIntEq(1));
auto column = v.attrs->find(createSymbol("column"));
ASSERT_NE(column, nullptr);
ASSERT_THAT(*column->value, IsIntEq(33));
}
TEST_F(PrimOpTest, hasAttr) {
auto v = eval("builtins.hasAttr \"x\" { x = 1; }");
ASSERT_THAT(v, IsTrue());
}
TEST_F(PrimOpTest, hasAttrNotFound) {
auto v = eval("builtins.hasAttr \"x\" { }");
ASSERT_THAT(v, IsFalse());
}
TEST_F(PrimOpTest, isAttrs) {
auto v = eval("builtins.isAttrs {}");
ASSERT_THAT(v, IsTrue());
}
TEST_F(PrimOpTest, isAttrsFalse) {
auto v = eval("builtins.isAttrs null");
ASSERT_THAT(v, IsFalse());
}
TEST_F(PrimOpTest, removeAttrs) {
auto v = eval("builtins.removeAttrs { x = 1; } [\"x\"]");
ASSERT_THAT(v, IsAttrsOfSize(0));
}
TEST_F(PrimOpTest, removeAttrsRetains) {
auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]");
ASSERT_THAT(v, IsAttrsOfSize(1));
ASSERT_NE(v.attrs->find(createSymbol("y")), nullptr);
}
TEST_F(PrimOpTest, listToAttrsEmptyList) {
auto v = eval("builtins.listToAttrs []");
ASSERT_THAT(v, IsAttrsOfSize(0));
ASSERT_EQ(v.type(), nAttrs);
ASSERT_EQ(v.attrs->size(), 0);
}
TEST_F(PrimOpTest, listToAttrsNotFieldName) {
ASSERT_THROW(eval("builtins.listToAttrs [{}]"), Error);
}
TEST_F(PrimOpTest, listToAttrs) {
auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto key = v.attrs->find(createSymbol("key"));
ASSERT_NE(key, nullptr);
ASSERT_THAT(*key->value, IsIntEq(123));
}
TEST_F(PrimOpTest, intersectAttrs) {
auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs->find(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(3));
}
TEST_F(PrimOpTest, catAttrs) {
auto v = eval("builtins.catAttrs \"a\" [{a = 1;} {b = 0;} {a = 2;}]");
ASSERT_THAT(v, IsListOfSize(2));
ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
ASSERT_THAT(*v.listElems()[1], IsIntEq(2));
}
TEST_F(PrimOpTest, functionArgs) {
auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto x = v.attrs->find(createSymbol("x"));
ASSERT_NE(x, nullptr);
ASSERT_THAT(*x->value, IsFalse());
auto y = v.attrs->find(createSymbol("y"));
ASSERT_NE(y, nullptr);
ASSERT_THAT(*y->value, IsTrue());
}
TEST_F(PrimOpTest, mapAttrs) {
auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto a = v.attrs->find(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsThunk());
state.forceValue(*a->value, noPos);
ASSERT_THAT(*a->value, IsIntEq(10));
auto b = v.attrs->find(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsThunk());
state.forceValue(*b->value, noPos);
ASSERT_THAT(*b->value, IsIntEq(20));
}
TEST_F(PrimOpTest, isList) {
auto v = eval("builtins.isList []");
ASSERT_THAT(v, IsTrue());
}
TEST_F(PrimOpTest, isListFalse) {
auto v = eval("builtins.isList null");
ASSERT_THAT(v, IsFalse());
}
TEST_F(PrimOpTest, elemtAt) {
auto v = eval("builtins.elemAt [0 1 2 3] 3");
ASSERT_THAT(v, IsIntEq(3));
}
TEST_F(PrimOpTest, elemtAtOutOfBounds) {
ASSERT_THROW(eval("builtins.elemAt [0 1 2 3] 5"), Error);
}
TEST_F(PrimOpTest, head) {
auto v = eval("builtins.head [ 3 2 1 0 ]");
ASSERT_THAT(v, IsIntEq(3));
}
TEST_F(PrimOpTest, headEmpty) {
ASSERT_THROW(eval("builtins.head [ ]"), Error);
}
TEST_F(PrimOpTest, headWrongType) {
ASSERT_THROW(eval("builtins.head { }"), Error);
}
TEST_F(PrimOpTest, tail) {
auto v = eval("builtins.tail [ 3 2 1 0 ]");
ASSERT_THAT(v, IsListOfSize(3));
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(2 - static_cast<int>(n)));
}
TEST_F(PrimOpTest, tailEmpty) {
ASSERT_THROW(eval("builtins.tail []"), Error);
}
TEST_F(PrimOpTest, map) {
auto v = eval("map (x: \"foo\" + x) [ \"bar\" \"bla\" \"abc\" ]");
ASSERT_THAT(v, IsListOfSize(3));
auto elem = v.listElems()[0];
ASSERT_THAT(*elem, IsThunk());
state.forceValue(*elem, noPos);
ASSERT_THAT(*elem, IsStringEq("foobar"));
elem = v.listElems()[1];
ASSERT_THAT(*elem, IsThunk());
state.forceValue(*elem, noPos);
ASSERT_THAT(*elem, IsStringEq("foobla"));
elem = v.listElems()[2];
ASSERT_THAT(*elem, IsThunk());
state.forceValue(*elem, noPos);
ASSERT_THAT(*elem, IsStringEq("fooabc"));
}
TEST_F(PrimOpTest, filter) {
auto v = eval("builtins.filter (x: x == 2) [ 3 2 3 2 3 2 ]");
ASSERT_THAT(v, IsListOfSize(3));
for (const auto elem : v.listItems())
ASSERT_THAT(*elem, IsIntEq(2));
}
TEST_F(PrimOpTest, elemTrue) {
auto v = eval("builtins.elem 3 [ 1 2 3 4 5 ]");
ASSERT_THAT(v, IsTrue());
}
TEST_F(PrimOpTest, elemFalse) {
auto v = eval("builtins.elem 6 [ 1 2 3 4 5 ]");
ASSERT_THAT(v, IsFalse());
}
TEST_F(PrimOpTest, concatLists) {
auto v = eval("builtins.concatLists [[1 2] [3 4]]");
ASSERT_THAT(v, IsListOfSize(4));
for (const auto [i, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i)+1));
}
TEST_F(PrimOpTest, length) {
auto v = eval("builtins.length [ 1 2 3 ]");
ASSERT_THAT(v, IsIntEq(3));
}
TEST_F(PrimOpTest, foldStrict) {
auto v = eval("builtins.foldl' (a: b: a + b) 0 [1 2 3]");
ASSERT_THAT(v, IsIntEq(6));
}
TEST_F(PrimOpTest, anyTrue) {
auto v = eval("builtins.any (x: x == 2) [ 1 2 3 ]");
ASSERT_THAT(v, IsTrue());
}
TEST_F(PrimOpTest, anyFalse) {
auto v = eval("builtins.any (x: x == 5) [ 1 2 3 ]");
ASSERT_THAT(v, IsFalse());
}
TEST_F(PrimOpTest, allTrue) {
auto v = eval("builtins.all (x: x > 0) [ 1 2 3 ]");
ASSERT_THAT(v, IsTrue());
}
TEST_F(PrimOpTest, allFalse) {
auto v = eval("builtins.all (x: x <= 0) [ 1 2 3 ]");
ASSERT_THAT(v, IsFalse());
}
TEST_F(PrimOpTest, genList) {
auto v = eval("builtins.genList (x: x + 1) 3");
ASSERT_EQ(v.type(), nList);
ASSERT_EQ(v.listSize(), 3);
for (const auto [i, elem] : enumerate(v.listItems())) {
ASSERT_THAT(*elem, IsThunk());
state.forceValue(*elem, noPos);
ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i)+1));
}
}
TEST_F(PrimOpTest, sortLessThan) {
auto v = eval("builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]");
ASSERT_EQ(v.type(), nList);
ASSERT_EQ(v.listSize(), 6);
const std::vector<int> numbers = { 42, 77, 147, 249, 483, 526 };
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(numbers[n]));
}
TEST_F(PrimOpTest, partition) {
auto v = eval("builtins.partition (x: x > 10) [1 23 9 3 42]");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto right = v.attrs->get(createSymbol("right"));
ASSERT_NE(right, nullptr);
ASSERT_THAT(*right->value, IsListOfSize(2));
ASSERT_THAT(*right->value->listElems()[0], IsIntEq(23));
ASSERT_THAT(*right->value->listElems()[1], IsIntEq(42));
auto wrong = v.attrs->get(createSymbol("wrong"));
ASSERT_NE(wrong, nullptr);
ASSERT_EQ(wrong->value->type(), nList);
ASSERT_EQ(wrong->value->listSize(), 3);
ASSERT_THAT(*wrong->value, IsListOfSize(3));
ASSERT_THAT(*wrong->value->listElems()[0], IsIntEq(1));
ASSERT_THAT(*wrong->value->listElems()[1], IsIntEq(9));
ASSERT_THAT(*wrong->value->listElems()[2], IsIntEq(3));
}
TEST_F(PrimOpTest, concatMap) {
auto v = eval("builtins.concatMap (x: x ++ [0]) [ [1 2] [3 4] ]");
ASSERT_EQ(v.type(), nList);
ASSERT_EQ(v.listSize(), 6);
const std::vector<int> numbers = { 1, 2, 0, 3, 4, 0 };
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(numbers[n]));
}
TEST_F(PrimOpTest, addInt) {
auto v = eval("builtins.add 3 5");
ASSERT_THAT(v, IsIntEq(8));
}
TEST_F(PrimOpTest, addFloat) {
auto v = eval("builtins.add 3.0 5.0");
ASSERT_THAT(v, IsFloatEq(8.0));
}
TEST_F(PrimOpTest, addFloatToInt) {
auto v = eval("builtins.add 3.0 5");
ASSERT_THAT(v, IsFloatEq(8.0));
v = eval("builtins.add 3 5.0");
ASSERT_THAT(v, IsFloatEq(8.0));
}
TEST_F(PrimOpTest, subInt) {
auto v = eval("builtins.sub 5 2");
ASSERT_THAT(v, IsIntEq(3));
}
TEST_F(PrimOpTest, subFloat) {
auto v = eval("builtins.sub 5.0 2.0");
ASSERT_THAT(v, IsFloatEq(3.0));
}
TEST_F(PrimOpTest, subFloatFromInt) {
auto v = eval("builtins.sub 5.0 2");
ASSERT_THAT(v, IsFloatEq(3.0));
v = eval("builtins.sub 4 2.0");
ASSERT_THAT(v, IsFloatEq(2.0));
}
TEST_F(PrimOpTest, mulInt) {
auto v = eval("builtins.mul 3 5");
ASSERT_THAT(v, IsIntEq(15));
}
TEST_F(PrimOpTest, mulFloat) {
auto v = eval("builtins.mul 3.0 5.0");
ASSERT_THAT(v, IsFloatEq(15.0));
}
TEST_F(PrimOpTest, mulFloatMixed) {
auto v = eval("builtins.mul 3 5.0");
ASSERT_THAT(v, IsFloatEq(15.0));
v = eval("builtins.mul 2.0 5");
ASSERT_THAT(v, IsFloatEq(10.0));
}
TEST_F(PrimOpTest, divInt) {
auto v = eval("builtins.div 5 (-1)");
ASSERT_THAT(v, IsIntEq(-5));
}
TEST_F(PrimOpTest, divIntZero) {
ASSERT_THROW(eval("builtins.div 5 0"), EvalError);
}
TEST_F(PrimOpTest, divFloat) {
auto v = eval("builtins.div 5.0 (-1)");
ASSERT_THAT(v, IsFloatEq(-5.0));
}
TEST_F(PrimOpTest, divFloatZero) {
ASSERT_THROW(eval("builtins.div 5.0 0.0"), EvalError);
}
TEST_F(PrimOpTest, bitOr) {
auto v = eval("builtins.bitOr 1 2");
ASSERT_THAT(v, IsIntEq(3));
}
TEST_F(PrimOpTest, bitXor) {
auto v = eval("builtins.bitXor 3 2");
ASSERT_THAT(v, IsIntEq(1));
}
TEST_F(PrimOpTest, lessThanFalse) {
auto v = eval("builtins.lessThan 3 1");
ASSERT_THAT(v, IsFalse());
}
TEST_F(PrimOpTest, lessThanTrue) {
auto v = eval("builtins.lessThan 1 3");
ASSERT_THAT(v, IsTrue());
}
TEST_F(PrimOpTest, toStringAttrsThrows) {
ASSERT_THROW(eval("builtins.toString {}"), EvalError);
}
TEST_F(PrimOpTest, toStringLambdaThrows) {
ASSERT_THROW(eval("builtins.toString (x: x)"), EvalError);
}
class ToStringPrimOpTest :
public PrimOpTest,
public testing::WithParamInterface<std::tuple<std::string, std::string_view>>
{};
TEST_P(ToStringPrimOpTest, toString) {
const auto [input, output] = GetParam();
auto v = eval(input);
ASSERT_THAT(v, IsStringEq(output));
}
#define CASE(input, output) (std::make_tuple(std::string_view("builtins.toString " #input), std::string_view(output)))
INSTANTIATE_TEST_SUITE_P(
toString,
ToStringPrimOpTest,
testing::Values(
CASE("foo", "foo"),
CASE(1, "1"),
CASE([1 2 3], "1 2 3"),
CASE(.123, "0.123000"),
CASE(true, "1"),
CASE(false, ""),
CASE(null, ""),
CASE({ v = "bar"; __toString = self: self.v; }, "bar"),
CASE({ v = "bar"; __toString = self: self.v; outPath = "foo"; }, "bar"),
CASE({ outPath = "foo"; }, "foo"),
CASE(./test, "/test")
)
);
#undef CASE
TEST_F(PrimOpTest, substring){
auto v = eval("builtins.substring 0 3 \"nixos\"");
ASSERT_THAT(v, IsStringEq("nix"));
}
TEST_F(PrimOpTest, substringSmallerString){
auto v = eval("builtins.substring 0 3 \"n\"");
ASSERT_THAT(v, IsStringEq("n"));
}
TEST_F(PrimOpTest, substringEmptyString){
auto v = eval("builtins.substring 1 3 \"\"");
ASSERT_THAT(v, IsStringEq(""));
}
TEST_F(PrimOpTest, stringLength) {
auto v = eval("builtins.stringLength \"123\"");
ASSERT_THAT(v, IsIntEq(3));
}
TEST_F(PrimOpTest, hashStringMd5) {
auto v = eval("builtins.hashString \"md5\" \"asdf\"");
ASSERT_THAT(v, IsStringEq("912ec803b2ce49e4a541068d495ab570"));
}
TEST_F(PrimOpTest, hashStringSha1) {
auto v = eval("builtins.hashString \"sha1\" \"asdf\"");
ASSERT_THAT(v, IsStringEq("3da541559918a808c2402bba5012f6c60b27661c"));
}
TEST_F(PrimOpTest, hashStringSha256) {
auto v = eval("builtins.hashString \"sha256\" \"asdf\"");
ASSERT_THAT(v, IsStringEq("f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"));
}
TEST_F(PrimOpTest, hashStringSha512) {
auto v = eval("builtins.hashString \"sha512\" \"asdf\"");
ASSERT_THAT(v, IsStringEq("401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"));
}
TEST_F(PrimOpTest, hashStringInvalidHashType) {
ASSERT_THROW(eval("builtins.hashString \"foobar\" \"asdf\""), Error);
}
TEST_F(PrimOpTest, nixPath) {
auto v = eval("builtins.nixPath");
ASSERT_EQ(v.type(), nList);
// We can't test much more as currently the EvalSettings are a global
// that we can't easily swap / replace
}
TEST_F(PrimOpTest, langVersion) {
auto v = eval("builtins.langVersion");
ASSERT_EQ(v.type(), nInt);
}
TEST_F(PrimOpTest, storeDir) {
auto v = eval("builtins.storeDir");
ASSERT_THAT(v, IsStringEq("/nix/store"));
}
TEST_F(PrimOpTest, nixVersion) {
auto v = eval("builtins.nixVersion");
ASSERT_THAT(v, IsStringEq(nixVersion));
}
TEST_F(PrimOpTest, currentSystem) {
auto v = eval("builtins.currentSystem");
ASSERT_THAT(v, IsStringEq(settings.thisSystem.get()));
}
TEST_F(PrimOpTest, derivation) {
auto v = eval("derivation");
ASSERT_EQ(v.type(), nFunction);
ASSERT_TRUE(v.isLambda());
ASSERT_NE(v.lambda.fun, nullptr);
ASSERT_TRUE(v.lambda.fun->hasFormals());
}
TEST_F(PrimOpTest, currentTime) {
auto v = eval("builtins.currentTime");
ASSERT_EQ(v.type(), nInt);
ASSERT_TRUE(v.integer > 0);
}
TEST_F(PrimOpTest, splitVersion) {
auto v = eval("builtins.splitVersion \"1.2.3git\"");
ASSERT_THAT(v, IsListOfSize(4));
const std::vector<std::string_view> strings = { "1", "2", "3", "git" };
for (const auto [n, p] : enumerate(v.listItems()))
ASSERT_THAT(*p, IsStringEq(strings[n]));
}
class CompareVersionsPrimOpTest :
public PrimOpTest,
public testing::WithParamInterface<std::tuple<std::string, const int>>
{};
TEST_P(CompareVersionsPrimOpTest, compareVersions) {
auto [expression, expectation] = GetParam();
auto v = eval(expression);
ASSERT_THAT(v, IsIntEq(expectation));
}
#define CASE(a, b, expected) (std::make_tuple("builtins.compareVersions \"" #a "\" \"" #b "\"", expected))
INSTANTIATE_TEST_SUITE_P(
compareVersions,
CompareVersionsPrimOpTest,
testing::Values(
// The first two are weird cases. Intuition tells they should
// be the same but they aren't.
CASE(1.0, 1.0.0, -1),
CASE(1.0.0, 1.0, 1),
// the following are from the nix-env manual:
CASE(1.0, 2.3, -1),
CASE(2.1, 2.3, -1),
CASE(2.3, 2.3, 0),
CASE(2.5, 2.3, 1),
CASE(3.1, 2.3, 1),
CASE(2.3.1, 2.3, 1),
CASE(2.3.1, 2.3a, 1),
CASE(2.3pre1, 2.3, -1),
CASE(2.3pre3, 2.3pre12, -1),
CASE(2.3a, 2.3c, -1),
CASE(2.3pre1, 2.3c, -1),
CASE(2.3pre1, 2.3q, -1)
)
);
#undef CASE
class ParseDrvNamePrimOpTest :
public PrimOpTest,
public testing::WithParamInterface<std::tuple<std::string, std::string_view, std::string_view>>
{};
TEST_P(ParseDrvNamePrimOpTest, parseDrvName) {
auto [input, expectedName, expectedVersion] = GetParam();
const auto expr = fmt("builtins.parseDrvName \"%1%\"", input);
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(2));
auto name = v.attrs->find(createSymbol("name"));
ASSERT_TRUE(name);
ASSERT_THAT(*name->value, IsStringEq(expectedName));
auto version = v.attrs->find(createSymbol("version"));
ASSERT_TRUE(version);
ASSERT_THAT(*version->value, IsStringEq(expectedVersion));
}
INSTANTIATE_TEST_SUITE_P(
parseDrvName,
ParseDrvNamePrimOpTest,
testing::Values(
std::make_tuple("nix-0.12pre12876", "nix", "0.12pre12876"),
std::make_tuple("a-b-c-1234pre5+git", "a-b-c", "1234pre5+git")
)
);
TEST_F(PrimOpTest, replaceStrings) {
// FIXME: add a test that verifies the string context is as expected
auto v = eval("builtins.replaceStrings [\"oo\" \"a\"] [\"a\" \"i\"] \"foobar\"");
ASSERT_EQ(v.type(), nString);
ASSERT_EQ(v.string.s, std::string_view("fabir"));
}
TEST_F(PrimOpTest, concatStringsSep) {
// FIXME: add a test that verifies the string context is as expected
auto v = eval("builtins.concatStringsSep \"%\" [\"foo\" \"bar\" \"baz\"]");
ASSERT_EQ(v.type(), nString);
ASSERT_EQ(std::string_view(v.string.s), "foo%bar%baz");
}
TEST_F(PrimOpTest, split1) {
// v = [ "" [ "a" ] "c" ]
auto v = eval("builtins.split \"(a)b\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(3));
ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
ASSERT_THAT(*v.listElems()[1], IsListOfSize(1));
ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
ASSERT_THAT(*v.listElems()[2], IsStringEq("c"));
}
TEST_F(PrimOpTest, split2) {
// v is expected to be a list [ "" [ "a" ] "b" [ "c"] "" ]
auto v = eval("builtins.split \"([ac])\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(5));
ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
ASSERT_THAT(*v.listElems()[1], IsListOfSize(1));
ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
ASSERT_THAT(*v.listElems()[2], IsStringEq("b"));
ASSERT_THAT(*v.listElems()[3], IsListOfSize(1));
ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsStringEq("c"));
ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
}
TEST_F(PrimOpTest, split3) {
auto v = eval("builtins.split \"(a)|(c)\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(5));
// First list element
ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
// 2nd list element is a list [ "" null ]
ASSERT_THAT(*v.listElems()[1], IsListOfSize(2));
ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
ASSERT_THAT(*v.listElems()[1]->listElems()[1], IsNull());
// 3rd element
ASSERT_THAT(*v.listElems()[2], IsStringEq("b"));
// 4th element is a list: [ null "c" ]
ASSERT_THAT(*v.listElems()[3], IsListOfSize(2));
ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsNull());
ASSERT_THAT(*v.listElems()[3]->listElems()[1], IsStringEq("c"));
// 5th element is the empty string
ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
}
TEST_F(PrimOpTest, split4) {
auto v = eval("builtins.split \"([[:upper:]]+)\" \" FOO \"");
ASSERT_THAT(v, IsListOfSize(3));
auto first = v.listElems()[0];
auto second = v.listElems()[1];
auto third = v.listElems()[2];
ASSERT_THAT(*first, IsStringEq(" "));
ASSERT_THAT(*second, IsListOfSize(1));
ASSERT_THAT(*second->listElems()[0], IsStringEq("FOO"));
ASSERT_THAT(*third, IsStringEq(" "));
}
TEST_F(PrimOpTest, match1) {
auto v = eval("builtins.match \"ab\" \"abc\"");
ASSERT_THAT(v, IsNull());
}
TEST_F(PrimOpTest, match2) {
auto v = eval("builtins.match \"abc\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(0));
}
TEST_F(PrimOpTest, match3) {
auto v = eval("builtins.match \"a(b)(c)\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(2));
ASSERT_THAT(*v.listElems()[0], IsStringEq("b"));
ASSERT_THAT(*v.listElems()[1], IsStringEq("c"));
}
TEST_F(PrimOpTest, match4) {
auto v = eval("builtins.match \"[[:space:]]+([[:upper:]]+)[[:space:]]+\" \" FOO \"");
ASSERT_THAT(v, IsListOfSize(1));
ASSERT_THAT(*v.listElems()[0], IsStringEq("FOO"));
}
TEST_F(PrimOpTest, attrNames) {
auto v = eval("builtins.attrNames { x = 1; y = 2; z = 3; a = 2; }");
ASSERT_THAT(v, IsListOfSize(4));
// ensure that the list is sorted
const std::vector<std::string_view> expected { "a", "x", "y", "z" };
for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsStringEq(expected[n]));
}
} /* namespace nix */

View file

@ -0,0 +1,196 @@
#include "libexprtests.hh"
namespace nix {
// Testing of trivial expressions
class TrivialExpressionTest : public LibExprTest {};
TEST_F(TrivialExpressionTest, true) {
auto v = eval("true");
ASSERT_THAT(v, IsTrue());
}
TEST_F(TrivialExpressionTest, false) {
auto v = eval("false");
ASSERT_THAT(v, IsFalse());
}
TEST_F(TrivialExpressionTest, null) {
auto v = eval("null");
ASSERT_THAT(v, IsNull());
}
TEST_F(TrivialExpressionTest, 1) {
auto v = eval("1");
ASSERT_THAT(v, IsIntEq(1));
}
TEST_F(TrivialExpressionTest, 1plus1) {
auto v = eval("1+1");
ASSERT_THAT(v, IsIntEq(2));
}
TEST_F(TrivialExpressionTest, minus1) {
auto v = eval("-1");
ASSERT_THAT(v, IsIntEq(-1));
}
TEST_F(TrivialExpressionTest, 1minus1) {
auto v = eval("1-1");
ASSERT_THAT(v, IsIntEq(0));
}
TEST_F(TrivialExpressionTest, lambdaAdd) {
auto v = eval("let add = a: b: a + b; in add 1 2");
ASSERT_THAT(v, IsIntEq(3));
}
TEST_F(TrivialExpressionTest, list) {
auto v = eval("[]");
ASSERT_THAT(v, IsListOfSize(0));
}
TEST_F(TrivialExpressionTest, attrs) {
auto v = eval("{}");
ASSERT_THAT(v, IsAttrsOfSize(0));
}
TEST_F(TrivialExpressionTest, float) {
auto v = eval("1.234");
ASSERT_THAT(v, IsFloatEq(1.234));
}
TEST_F(TrivialExpressionTest, updateAttrs) {
auto v = eval("{ a = 1; } // { b = 2; a = 3; }");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto a = v.attrs->find(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsIntEq(3));
auto b = v.attrs->find(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(2));
}
TEST_F(TrivialExpressionTest, hasAttrOpFalse) {
auto v = eval("{} ? a");
ASSERT_THAT(v, IsFalse());
}
TEST_F(TrivialExpressionTest, hasAttrOpTrue) {
auto v = eval("{ a = 123; } ? a");
ASSERT_THAT(v, IsTrue());
}
TEST_F(TrivialExpressionTest, withFound) {
auto v = eval("with { a = 23; }; a");
ASSERT_THAT(v, IsIntEq(23));
}
TEST_F(TrivialExpressionTest, withNotFound) {
ASSERT_THROW(eval("with {}; a"), Error);
}
TEST_F(TrivialExpressionTest, withOverride) {
auto v = eval("with { a = 23; }; with { a = 42; }; a");
ASSERT_THAT(v, IsIntEq(42));
}
TEST_F(TrivialExpressionTest, letOverWith) {
auto v = eval("let a = 23; in with { a = 1; }; a");
ASSERT_THAT(v, IsIntEq(23));
}
TEST_F(TrivialExpressionTest, multipleLet) {
auto v = eval("let a = 23; in let a = 42; in a");
ASSERT_THAT(v, IsIntEq(42));
}
TEST_F(TrivialExpressionTest, defaultFunctionArgs) {
auto v = eval("({ a ? 123 }: a) {}");
ASSERT_THAT(v, IsIntEq(123));
}
TEST_F(TrivialExpressionTest, defaultFunctionArgsOverride) {
auto v = eval("({ a ? 123 }: a) { a = 5; }");
ASSERT_THAT(v, IsIntEq(5));
}
TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureBack) {
auto v = eval("({ a ? 123 }@args: args) {}");
ASSERT_THAT(v, IsAttrsOfSize(0));
}
TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureFront) {
auto v = eval("(args@{ a ? 123 }: args) {}");
ASSERT_THAT(v, IsAttrsOfSize(0));
}
TEST_F(TrivialExpressionTest, assertThrows) {
ASSERT_THROW(eval("let x = arg: assert arg == 1; 123; in x 2"), Error);
}
TEST_F(TrivialExpressionTest, assertPassed) {
auto v = eval("let x = arg: assert arg == 1; 123; in x 1");
ASSERT_THAT(v, IsIntEq(123));
}
class AttrSetMergeTrvialExpressionTest :
public TrivialExpressionTest,
public testing::WithParamInterface<const char*>
{};
TEST_P(AttrSetMergeTrvialExpressionTest, attrsetMergeLazy) {
// Usually Nix rejects duplicate keys in an attrset but it does allow
// so if it is an attribute set that contains disjoint sets of keys.
// The below is equivalent to `{a.b = 1; a.c = 2; }`.
// The attribute set `a` will be a Thunk at first as the attribuets
// have to be merged (or otherwise computed) and that is done in a lazy
// manner.
auto expr = GetParam();
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(1));
auto a = v.attrs->find(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsThunk());
state.forceValue(*a->value, noPos);
ASSERT_THAT(*a->value, IsAttrsOfSize(2));
auto b = a->value->attrs->find(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(1));
auto c = a->value->attrs->find(createSymbol("c"));
ASSERT_NE(c, nullptr);
ASSERT_THAT(*c->value, IsIntEq(2));
}
INSTANTIATE_TEST_SUITE_P(
attrsetMergeLazy,
AttrSetMergeTrvialExpressionTest,
testing::Values(
"{ a.b = 1; a.c = 2; }",
"{ a = { b = 1; }; a = { c = 2; }; }"
)
);
TEST_F(TrivialExpressionTest, functor) {
auto v = eval("{ __functor = self: arg: self.v + arg; v = 10; } 5");
ASSERT_THAT(v, IsIntEq(15));
}
TEST_F(TrivialExpressionTest, bindOr) {
auto v = eval("{ or = 1; }");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs->find(createSymbol("or"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(1));
}
TEST_F(TrivialExpressionTest, orCantBeUsed) {
ASSERT_THROW(eval("let or = 1; in or"), Error);
}
} /* namespace nix */

View file

@ -10,7 +10,7 @@
namespace nix { namespace nix {
void printValueAsJSON(EvalState & state, bool strict, 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(); checkInterrupt();
@ -50,14 +50,14 @@ void printValueAsJSON(EvalState & state, bool strict,
auto obj(out.object()); auto obj(out.object());
StringSet names; StringSet names;
for (auto & j : *v.attrs) for (auto & j : *v.attrs)
names.insert(j.name); names.emplace(state.symbols[j.name]);
for (auto & j : names) { for (auto & j : names) {
Attr & a(*v.attrs->find(state.symbols.create(j))); Attr & a(*v.attrs->find(state.symbols.create(j)));
auto placeholder(obj.placeholder(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 } else
printValueAsJSON(state, strict, *i->value, *i->pos, out, context); printValueAsJSON(state, strict, *i->value, i->pos, out, context);
break; break;
} }
@ -82,15 +82,15 @@ void printValueAsJSON(EvalState & state, bool strict,
case nFunction: case nFunction:
auto e = TypeError({ auto e = TypeError({
.msg = hintfmt("cannot convert %1% to JSON", showType(v)), .msg = hintfmt("cannot convert %1% to JSON", showType(v)),
.errPos = v.determinePos(pos) .errPos = state.positions[v.determinePos(pos)]
}); });
e.addTrace(pos, hintfmt("message for the trace")); e.addTrace(state.positions[pos], hintfmt("message for the trace"));
throw e; throw e;
} }
} }
void printValueAsJSON(EvalState & state, bool strict, 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); JSONPlaceholder out(str);
printValueAsJSON(state, strict, v, pos, out, context); printValueAsJSON(state, strict, v, pos, out, context);

View file

@ -11,9 +11,9 @@ namespace nix {
class JSONPlaceholder; class JSONPlaceholder;
void printValueAsJSON(EvalState & state, bool strict, 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, 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, static void printValueAsXML(EvalState & state, bool strict, bool location,
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, 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["path"] = pos.file;
xmlAttrs["line"] = (format("%1%") % pos.line).str(); xmlAttrs["line"] = (format("%1%") % pos.line).str();
@ -36,25 +36,25 @@ static void showAttrs(EvalState & state, bool strict, bool location,
StringSet names; StringSet names;
for (auto & i : attrs) for (auto & i : attrs)
names.insert(i.name); names.emplace(state.symbols[i.name]);
for (auto & i : names) { for (auto & i : names) {
Attr & a(*attrs.find(state.symbols.create(i))); Attr & a(*attrs.find(state.symbols.create(i)));
XMLAttrs xmlAttrs; XMLAttrs xmlAttrs;
xmlAttrs["name"] = i; 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); XMLOpenElement _(doc, "attr", xmlAttrs);
printValueAsXML(state, strict, location, 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, static void printValueAsXML(EvalState & state, bool strict, bool location,
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
const Pos & pos) const PosIdx pos)
{ {
checkInterrupt(); checkInterrupt();
@ -93,14 +93,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
Path drvPath; Path drvPath;
a = v.attrs->find(state.sDrvPath); a = v.attrs->find(state.sDrvPath);
if (a != v.attrs->end()) { 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) if (a->value->type() == nString)
xmlAttrs["drvPath"] = drvPath = a->value->string.s; xmlAttrs["drvPath"] = drvPath = a->value->string.s;
} }
a = v.attrs->find(state.sOutPath); a = v.attrs->find(state.sOutPath);
if (a != v.attrs->end()) { 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) if (a->value->type() == nString)
xmlAttrs["outPath"] = a->value->string.s; xmlAttrs["outPath"] = a->value->string.s;
} }
@ -134,18 +134,18 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break; break;
} }
XMLAttrs xmlAttrs; 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); XMLOpenElement _(doc, "function", xmlAttrs);
if (v.lambda.fun->hasFormals()) { if (v.lambda.fun->hasFormals()) {
XMLAttrs attrs; 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"; if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
XMLOpenElement _(doc, "attrspat", attrs); XMLOpenElement _(doc, "attrspat", attrs);
for (auto & i : v.lambda.fun->formals->lexicographicOrder()) for (auto & i : v.lambda.fun->formals->lexicographicOrder(state.symbols))
doc.writeEmptyElement("attr", singletonAttrs("name", i.name)); doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name]));
} else } else
doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg)); doc.writeEmptyElement("varpat", singletonAttrs("name", state.symbols[v.lambda.fun->arg]));
break; break;
} }
@ -166,14 +166,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
void ExternalValueBase::printValueAsXML(EvalState & state, bool strict, void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen, bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
const Pos & pos) const const PosIdx pos) const
{ {
doc.writeEmptyElement("unevaluated"); doc.writeEmptyElement("unevaluated");
} }
void printValueAsXML(EvalState & state, bool strict, bool location, 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); XMLWriter doc(true, out);
XMLOpenElement root(doc, "expr"); XMLOpenElement root(doc, "expr");

View file

@ -9,6 +9,6 @@
namespace nix { namespace nix {
void printValueAsXML(EvalState & state, bool strict, bool location, 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,6 +56,7 @@ struct Expr;
struct ExprLambda; struct ExprLambda;
struct PrimOp; struct PrimOp;
class Symbol; class Symbol;
class PosIdx;
struct Pos; struct Pos;
class StorePath; class StorePath;
class Store; class Store;
@ -103,7 +104,7 @@ class ExternalValueBase
/* Print the value as XML. Defaults to unevaluated */ /* Print the value as XML. Defaults to unevaluated */
virtual void printValueAsXML(EvalState & state, bool strict, bool location, virtual void printValueAsXML(EvalState & state, bool strict, bool location,
XMLWriter & doc, PathSet & context, PathSet & drvsSeen, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
const Pos & pos) const; const PosIdx pos) const;
virtual ~ExternalValueBase() virtual ~ExternalValueBase()
{ {
@ -120,11 +121,11 @@ private:
friend std::string showType(const Value & v); friend std::string showType(const Value & v);
void print(std::ostream & str, std::set<const void *> * seen) const; void print(const SymbolTable & symbols, std::ostream & str, std::set<const void *> * seen) const;
public: public:
void print(std::ostream & str, bool showRepeated = false) const; void print(const SymbolTable & symbols, std::ostream & str, bool showRepeated = false) const;
// Functions needed to distinguish the type // Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's // These should be removed eventually, by putting the functionality that's
@ -250,11 +251,6 @@ public:
void mkStringMove(const char * s, const PathSet & context); 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) inline void mkPath(const char * s)
{ {
clearValue(); clearValue();
@ -368,7 +364,7 @@ public:
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size; 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 /* Check whether forcing this value requires a trivial amount of
computation. In particular, function applications are computation. In particular, function applications are

View file

@ -5,15 +5,20 @@
#include "store-api.hh" #include "store-api.hh"
#include "url-parts.hh" #include "url-parts.hh"
#include "pathlocks.hh" #include "pathlocks.hh"
#include "util.hh"
#include "git.hh"
#include "fetch-settings.hh" #include "fetch-settings.hh"
#include <regex>
#include <string.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/wait.h> #include <sys/wait.h>
using namespace std::string_literals; using namespace std::string_literals;
namespace nix::fetchers { namespace nix::fetchers {
namespace {
// Explicit initial branch of our bare repo to suppress warnings from new version of git. // Explicit initial branch of our bare repo to suppress warnings from new version of git.
// The value itself does not matter, since we always fetch a specific revision or branch. // The value itself does not matter, since we always fetch a specific revision or branch.
@ -21,16 +26,231 @@ namespace nix::fetchers {
// old version of git, which will ignore unrecognized `-c` options. // old version of git, which will ignore unrecognized `-c` options.
const std::string gitInitialBranch = "__nix_dummy_branch"; const std::string gitInitialBranch = "__nix_dummy_branch";
static std::string readHead(const Path & path) std::string getGitDir()
{ {
return chomp(runProgram("git", true, { "-C", path, "rev-parse", "--abbrev-ref", "HEAD" })); return getEnv("GIT_DIR").value_or(".git");
} }
static bool isNotDotGitDirectory(const Path & path) bool isCacheFileWithinTtl(const time_t now, const struct stat & st)
{
return st.st_mtime + settings.tarballTtl > now;
}
bool touchCacheFile(const Path& path, const time_t& touch_time)
{
struct timeval times[2];
times[0].tv_sec = touch_time;
times[0].tv_usec = 0;
times[1].tv_sec = touch_time;
times[1].tv_usec = 0;
return lutimes(path.c_str(), times) == 0;
}
Path getCachePath(std::string key)
{
return getCacheDir() + "/nix/gitv3/" +
hashString(htSHA256, key).to_string(Base32, false);
}
// Returns the name of the HEAD branch.
//
// Returns the head branch name as reported by git ls-remote --symref, e.g., if
// ls-remote returns the output below, "main" is returned based on the ref line.
//
// ref: refs/heads/main HEAD
// ...
std::optional<std::string> readHead(const Path & path)
{
auto [exit_code, output] = runProgram(RunOptions {
.program = "git",
.args = {"ls-remote", "--symref", path},
});
if (exit_code != 0) {
return std::nullopt;
}
std::string_view line = output;
line = line.substr(0, line.find("\n"));
if (const auto parseResult = git::parseLsRemoteLine(line)) {
switch (parseResult->kind) {
case git::LsRemoteRefLine::Kind::Symbolic:
debug("resolved HEAD ref '%s' for repo '%s'", parseResult->target, path);
break;
case git::LsRemoteRefLine::Kind::Object:
debug("resolved HEAD rev '%s' for repo '%s'", parseResult->target, path);
break;
}
return parseResult->target;
}
return std::nullopt;
}
// Persist the HEAD ref from the remote repo in the local cached repo.
bool storeCachedHead(const std::string& actualUrl, const std::string& headRef)
{
Path cacheDir = getCachePath(actualUrl);
try {
runProgram("git", true, { "-C", cacheDir, "symbolic-ref", "--", "HEAD", headRef });
} catch (ExecError &e) {
if (!WIFEXITED(e.status)) throw;
return false;
}
/* No need to touch refs/HEAD, because `git symbolic-ref` updates the mtime. */
return true;
}
std::optional<std::string> readHeadCached(const std::string& actualUrl)
{
// Create a cache path to store the branch of the HEAD ref. Append something
// in front of the URL to prevent collision with the repository itself.
Path cacheDir = getCachePath(actualUrl);
Path headRefFile = cacheDir + "/HEAD";
time_t now = time(0);
struct stat st;
std::optional<std::string> cachedRef;
if (stat(headRefFile.c_str(), &st) == 0) {
cachedRef = readHead(cacheDir);
if (cachedRef != std::nullopt &&
*cachedRef != gitInitialBranch &&
isCacheFileWithinTtl(now, st)) {
debug("using cached HEAD ref '%s' for repo '%s'", *cachedRef, actualUrl);
return cachedRef;
}
}
auto ref = readHead(actualUrl);
if (ref) {
return ref;
}
if (cachedRef) {
// If the cached git ref is expired in fetch() below, and the 'git fetch'
// fails, it falls back to continuing with the most recent version.
// This function must behave the same way, so we return the expired
// cached ref here.
warn("could not get HEAD ref for repository '%s'; using expired cached ref '%s'", actualUrl, *cachedRef);
return *cachedRef;
}
return std::nullopt;
}
bool isNotDotGitDirectory(const Path & path)
{ {
return baseNameOf(path) != ".git"; return baseNameOf(path) != ".git";
} }
struct WorkdirInfo
{
bool clean = false;
bool hasHead = false;
};
// Returns whether a git workdir is clean and has commits.
WorkdirInfo getWorkdirInfo(const Input & input, const Path & workdir)
{
const bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false);
auto gitDir = getGitDir();
auto env = getEnv();
// Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong
// that way unknown errors can lead to a failure instead of continuing through the wrong code path
env["LC_ALL"] = "C";
/* Check whether HEAD points to something that looks like a commit,
since that is the refrence we want to use later on. */
auto result = runProgram(RunOptions {
.program = "git",
.args = { "-C", workdir, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
.environment = env,
.mergeStderrToStdout = true
});
auto exitCode = WEXITSTATUS(result.first);
auto errorMessage = result.second;
if (errorMessage.find("fatal: not a git repository") != std::string::npos) {
throw Error("'%s' is not a Git repository", workdir);
} else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) {
// indicates that the repo does not have any commits
// we want to proceed and will consider it dirty later
} else if (exitCode != 0) {
// any other errors should lead to a failure
throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", workdir, exitCode, errorMessage);
}
bool clean = false;
bool hasHead = exitCode == 0;
try {
if (hasHead) {
// Using git diff is preferrable over lower-level operations here,
// because its conceptually simpler and we only need the exit code anyways.
auto gitDiffOpts = Strings({ "-C", workdir, "diff", "HEAD", "--quiet"});
if (!submodules) {
// Changes in submodules should only make the tree dirty
// when those submodules will be copied as well.
gitDiffOpts.emplace_back("--ignore-submodules");
}
gitDiffOpts.emplace_back("--");
runProgram("git", true, gitDiffOpts);
clean = true;
}
} catch (ExecError & e) {
if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw;
}
return WorkdirInfo { .clean = clean, .hasHead = hasHead };
}
std::pair<StorePath, Input> fetchFromWorkdir(ref<Store> store, Input & input, const Path & workdir, const WorkdirInfo & workdirInfo)
{
const bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false);
if (!fetchSettings.allowDirty)
throw Error("Git tree '%s' is dirty", workdir);
if (fetchSettings.warnDirty)
warn("Git tree '%s' is dirty", workdir);
auto gitOpts = Strings({ "-C", workdir, "ls-files", "-z" });
if (submodules)
gitOpts.emplace_back("--recurse-submodules");
auto files = tokenizeString<std::set<std::string>>(
runProgram("git", true, gitOpts), "\0"s);
Path actualPath(absPath(workdir));
PathFilter filter = [&](const Path & p) -> bool {
assert(hasPrefix(p, actualPath));
std::string file(p, actualPath.size() + 1);
auto st = lstat(p);
if (S_ISDIR(st.st_mode)) {
auto prefix = file + "/";
auto i = files.lower_bound(prefix);
return i != files.end() && hasPrefix(*i, prefix);
}
return files.count(file);
};
auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter);
// FIXME: maybe we should use the timestamp of the last
// modified dirty file?
input.attrs.insert_or_assign(
"lastModified",
workdirInfo.hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
return {std::move(storePath), input};
}
} // end namespace
struct GitInputScheme : InputScheme struct GitInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) override std::optional<Input> inputFromURL(const ParsedURL & url) override
@ -150,13 +370,14 @@ struct GitInputScheme : InputScheme
{ {
auto sourcePath = getSourcePath(input); auto sourcePath = getSourcePath(input);
assert(sourcePath); assert(sourcePath);
auto gitDir = getGitDir();
runProgram("git", true, 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) if (commitMsg)
runProgram("git", true, 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 std::pair<bool, std::string> getActualUrl(const Input & input) const
@ -175,6 +396,7 @@ struct GitInputScheme : InputScheme
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
{ {
Input input(_input); Input input(_input);
auto gitDir = getGitDir();
std::string name = input.getName(); std::string name = input.getName();
@ -223,106 +445,16 @@ struct GitInputScheme : InputScheme
auto [isLocal, actualUrl_] = getActualUrl(input); auto [isLocal, actualUrl_] = getActualUrl(input);
auto actualUrl = actualUrl_; // work around clang bug auto actualUrl = actualUrl_; // work around clang bug
// If this is a local directory and no ref or revision is /* If this is a local directory and no ref or revision is given,
// given, then allow the use of an unclean working tree. allow fetching directly from a dirty workdir. */
if (!input.getRef() && !input.getRev() && isLocal) { if (!input.getRef() && !input.getRev() && isLocal) {
bool clean = false; auto workdirInfo = getWorkdirInfo(input, actualUrl);
if (!workdirInfo.clean) {
auto env = getEnv(); return fetchFromWorkdir(store, input, actualUrl, workdirInfo);
// Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong
// that way unknown errors can lead to a failure instead of continuing through the wrong code path
env["LC_ALL"] = "C";
/* Check whether HEAD points to something that looks like a commit,
since that is the refrence we want to use later on. */
auto result = runProgram(RunOptions {
.program = "git",
.args = { "-C", actualUrl, "--git-dir=.git", "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
.environment = env,
.mergeStderrToStdout = true
});
auto exitCode = WEXITSTATUS(result.first);
auto errorMessage = result.second;
if (errorMessage.find("fatal: not a git repository") != std::string::npos) {
throw Error("'%s' is not a Git repository", actualUrl);
} else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) {
// indicates that the repo does not have any commits
// we want to proceed and will consider it dirty later
} else if (exitCode != 0) {
// any other errors should lead to a failure
throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", actualUrl, exitCode, errorMessage);
}
bool hasHead = exitCode == 0;
try {
if (hasHead) {
// Using git diff is preferrable over lower-level operations here,
// because its conceptually simpler and we only need the exit code anyways.
auto gitDiffOpts = Strings({ "-C", actualUrl, "diff", "HEAD", "--quiet"});
if (!submodules) {
// Changes in submodules should only make the tree dirty
// when those submodules will be copied as well.
gitDiffOpts.emplace_back("--ignore-submodules");
}
gitDiffOpts.emplace_back("--");
runProgram("git", true, gitDiffOpts);
clean = true;
}
} catch (ExecError & e) {
if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw;
}
if (!clean) {
/* This is an unclean working tree. So copy all tracked files. */
if (!fetchSettings.allowDirty)
throw Error("Git tree '%s' is dirty", actualUrl);
if (fetchSettings.warnDirty)
warn("Git tree '%s' is dirty", actualUrl);
auto gitOpts = Strings({ "-C", actualUrl, "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, actualPath));
std::string file(p, actualPath.size() + 1);
auto st = lstat(p);
if (S_ISDIR(st.st_mode)) {
auto prefix = file + "/";
auto i = files.lower_bound(prefix);
return i != files.end() && hasPrefix(*i, prefix);
}
return files.count(file);
};
auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter);
// FIXME: maybe we should use the timestamp of the last
// modified dirty file?
input.attrs.insert_or_assign(
"lastModified",
hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
return {std::move(storePath), input};
} }
} }
if (!input.getRef()) input.attrs.insert_or_assign("ref", isLocal ? readHead(actualUrl) : "master"); const Attrs unlockedAttrs({
Attrs unlockedAttrs({
{"type", cacheType}, {"type", cacheType},
{"name", name}, {"name", name},
{"url", actualUrl}, {"url", actualUrl},
@ -332,14 +464,30 @@ struct GitInputScheme : InputScheme
Path repoDir; Path repoDir;
if (isLocal) { if (isLocal) {
if (!input.getRef()) {
auto head = readHead(actualUrl);
if (!head) {
warn("could not read HEAD ref from repo at '%s', using 'master'", actualUrl);
head = "master";
}
input.attrs.insert_or_assign("ref", *head);
}
if (!input.getRev()) if (!input.getRev())
input.attrs.insert_or_assign("rev", 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; repoDir = actualUrl;
} else { } else {
const bool useHeadRef = !input.getRef();
if (useHeadRef) {
auto head = readHeadCached(actualUrl);
if (!head) {
warn("could not read HEAD ref from repo at '%s', using 'master'", actualUrl);
head = "master";
}
input.attrs.insert_or_assign("ref", *head);
}
if (auto res = getCache()->lookup(store, unlockedAttrs)) { if (auto res = getCache()->lookup(store, unlockedAttrs)) {
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), htSHA1); auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), htSHA1);
@ -349,8 +497,9 @@ struct GitInputScheme : InputScheme
} }
} }
Path cacheDir = getCacheDir() + "/nix/gitv3/" + hashString(htSHA256, actualUrl).to_string(Base32, false); Path cacheDir = getCachePath(actualUrl);
repoDir = cacheDir; repoDir = cacheDir;
gitDir = ".";
createDirs(dirOf(cacheDir)); createDirs(dirOf(cacheDir));
PathLocks cacheDirLock({cacheDir + ".lock"}); PathLocks cacheDirLock({cacheDir + ".lock"});
@ -371,7 +520,7 @@ struct GitInputScheme : InputScheme
repo. */ repo. */
if (input.getRev()) { if (input.getRev()) {
try { try {
runProgram("git", true, { "-C", repoDir, "cat-file", "-e", input.getRev()->gitRev() }); runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "cat-file", "-e", input.getRev()->gitRev() });
doFetch = false; doFetch = false;
} catch (ExecError & e) { } catch (ExecError & e) {
if (WIFEXITED(e.status)) { if (WIFEXITED(e.status)) {
@ -388,7 +537,7 @@ struct GitInputScheme : InputScheme
git fetch to update the local ref to the remote ref. */ git fetch to update the local ref to the remote ref. */
struct stat st; struct stat st;
doFetch = stat(localRefFile.c_str(), &st) != 0 || doFetch = stat(localRefFile.c_str(), &st) != 0 ||
(uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now; !isCacheFileWithinTtl(now, st);
} }
} }
@ -406,19 +555,16 @@ struct GitInputScheme : InputScheme
: ref == "HEAD" : ref == "HEAD"
? *ref ? *ref
: "refs/heads/" + *ref; : "refs/heads/" + *ref;
runProgram("git", true, { "-C", repoDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) }); runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) });
} catch (Error & e) { } catch (Error & e) {
if (!pathExists(localRefFile)) throw; if (!pathExists(localRefFile)) throw;
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", actualUrl); warn("could not update local clone of Git repository '%s'; continuing with the most recent version", actualUrl);
} }
struct timeval times[2]; if (!touchCacheFile(localRefFile, now))
times[0].tv_sec = now; warn("could not update mtime for file '%s': %s", localRefFile, strerror(errno));
times[0].tv_usec = 0; if (useHeadRef && !storeCachedHead(actualUrl, *input.getRef()))
times[1].tv_sec = now; warn("could not update cached head '%s' for '%s'", *input.getRef(), actualUrl);
times[1].tv_usec = 0;
utimes(localRefFile.c_str(), times);
} }
if (!input.getRev()) if (!input.getRev())
@ -427,7 +573,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 // 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) if (isShallow && !shallow)
throw Error("'%s' is a shallow Git repository, but a non-shallow repository is needed", actualUrl); throw Error("'%s' is a shallow Git repository, but a non-shallow repository is needed", actualUrl);
@ -447,7 +593,7 @@ struct GitInputScheme : InputScheme
auto result = runProgram(RunOptions { auto result = runProgram(RunOptions {
.program = "git", .program = "git",
.args = { "-C", repoDir, "cat-file", "commit", input.getRev()->gitRev() }, .args = { "-C", repoDir, "--git-dir", gitDir, "cat-file", "commit", input.getRev()->gitRev() },
.mergeStderrToStdout = true .mergeStderrToStdout = true
}); });
if (WEXITSTATUS(result.first) == 128 if (WEXITSTATUS(result.first) == 128
@ -486,7 +632,7 @@ struct GitInputScheme : InputScheme
auto source = sinkToSource([&](Sink & sink) { auto source = sinkToSource([&](Sink & sink) {
runProgram2({ runProgram2({
.program = "git", .program = "git",
.args = { "-C", repoDir, "archive", input.getRev()->gitRev() }, .args = { "-C", repoDir, "--git-dir", gitDir, "archive", input.getRev()->gitRev() },
.standardOut = &sink .standardOut = &sink
}); });
}); });
@ -496,7 +642,7 @@ struct GitInputScheme : InputScheme
auto storePath = store->addToStore(name, tmpDir, FileIngestionMethod::Recursive, htSHA256, filter); auto storePath = store->addToStore(name, tmpDir, FileIngestionMethod::Recursive, htSHA256, filter);
auto lastModified = std::stoull(runProgram("git", true, { "-C", repoDir, "log", "-1", "--format=%ct", "--no-show-signature", input.getRev()->gitRev() })); auto lastModified = std::stoull(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", input.getRev()->gitRev() }));
Attrs infoAttrs({ Attrs infoAttrs({
{"rev", input.getRev()->gitRev()}, {"rev", input.getRev()->gitRev()},
@ -505,7 +651,7 @@ struct GitInputScheme : InputScheme
if (!shallow) if (!shallow)
infoAttrs.insert_or_assign("revCount", infoAttrs.insert_or_assign("revCount",
std::stoull(runProgram("git", true, { "-C", repoDir, "rev-list", "--count", input.getRev()->gitRev() }))); std::stoull(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-list", "--count", input.getRev()->gitRev() })));
if (!_input.getRev()) if (!_input.getRev())
getCache()->add( getCache()->add(

View file

@ -4,7 +4,7 @@
#include "store-api.hh" #include "store-api.hh"
#include "types.hh" #include "types.hh"
#include "url-parts.hh" #include "url-parts.hh"
#include "git.hh"
#include "fetchers.hh" #include "fetchers.hh"
#include "fetch-settings.hh" #include "fetch-settings.hh"
@ -243,7 +243,10 @@ struct GitHubInputScheme : GitArchiveInputScheme
Hash getRevFromRef(nix::ref<Store> store, const Input & input) const override Hash getRevFromRef(nix::ref<Store> store, const Input & input) const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com");
auto url = fmt("https://api.%s/repos/%s/%s/commits/%s", // FIXME: check auto url = fmt(
host == "github.com"
? "https://api.%s/repos/%s/%s/commits/%s"
: "https://%s/api/v3/repos/%s/%s/commits/%s",
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef()); host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef());
Headers headers = makeHeadersWithAuthTokens(host); Headers headers = makeHeadersWithAuthTokens(host);
@ -262,7 +265,10 @@ struct GitHubInputScheme : GitArchiveInputScheme
// FIXME: use regular /archive URLs instead? api.github.com // FIXME: use regular /archive URLs instead? api.github.com
// might have stricter rate limits. // might have stricter rate limits.
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com");
auto url = fmt("https://api.%s/repos/%s/%s/tarball/%s", // FIXME: check if this is correct for self hosted instances auto url = fmt(
host == "github.com"
? "https://api.%s/repos/%s/%s/tarball/%s"
: "https://%s/api/v3/repos/%s/%s/tarball/%s",
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
input.getRev()->to_string(Base16, false)); input.getRev()->to_string(Base16, false));
@ -383,35 +389,31 @@ struct SourceHutInputScheme : GitArchiveInputScheme
std::string line; std::string line;
getline(is, line); getline(is, line);
auto ref_index = line.find("ref: "); auto remoteLine = git::parseLsRemoteLine(line);
if (ref_index == std::string::npos) { if (!remoteLine) {
throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref); throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref);
} }
ref_uri = remoteLine->target;
ref_uri = line.substr(ref_index+5, line.length()-1); } else {
} else
ref_uri = fmt("refs/(heads|tags)/%s", ref); ref_uri = fmt("refs/(heads|tags)/%s", ref);
}
auto file = store->toRealPath( auto file = store->toRealPath(
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath); downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
std::ifstream is(file); std::ifstream is(file);
std::string line; std::string line;
std::string id; std::optional<std::string> id;
while(getline(is, line)) { while(!id && getline(is, line)) {
// Append $ to avoid partial name matches auto parsedLine = git::parseLsRemoteLine(line);
std::regex pattern(fmt("%s$", ref_uri)); if (parsedLine && parsedLine->reference == ref_uri)
id = parsedLine->target;
if (std::regex_search(line, pattern)) {
id = line.substr(0, line.find('\t'));
break;
}
} }
if(id.empty()) if(!id)
throw BadURL("in '%d', couldn't find ref '%d'", input.to_string(), ref); throw BadURL("in '%d', couldn't find ref '%d'", input.to_string(), ref);
auto rev = Hash::parseAny(id, htSHA1); auto rev = Hash::parseAny(*id, htSHA1);
debug("HEAD revision for '%s' is %s", fmt("%s/%s", base_url, ref), rev.gitRev()); debug("HEAD revision for '%s' is %s", fmt("%s/%s", base_url, ref), rev.gitRev());
return rev; return rev;
} }

View file

@ -36,7 +36,7 @@ static std::string runHg(const Strings & args, const std::optional<std::string>
auto res = runProgram(std::move(opts)); auto res = runProgram(std::move(opts));
if (!statusOk(res.first)) 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; return res.second;
} }
@ -273,7 +273,7 @@ struct MercurialInputScheme : InputScheme
runHg({ "recover", "-R", cacheDir }); runHg({ "recover", "-R", cacheDir });
runHg({ "pull", "-R", cacheDir, "--", actualUrl }); runHg({ "pull", "-R", cacheDir, "--", actualUrl });
} else { } else {
throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status))); throw ExecError(e.status, "'hg pull' %s", statusToString(e.status));
} }
} }
} else { } else {

View file

@ -60,37 +60,37 @@ void printMissing(ref<Store> store, const StorePathSet & willBuild,
{ {
if (!willBuild.empty()) { if (!willBuild.empty()) {
if (willBuild.size() == 1) if (willBuild.size() == 1)
printMsg(lvl, fmt("this derivation will be built:")); printMsg(lvl, "this derivation will be built:");
else 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); auto sorted = store->topoSortPaths(willBuild);
reverse(sorted.begin(), sorted.end()); reverse(sorted.begin(), sorted.end());
for (auto & i : sorted) for (auto & i : sorted)
printMsg(lvl, fmt(" %s", store->printStorePath(i))); printMsg(lvl, " %s", store->printStorePath(i));
} }
if (!willSubstitute.empty()) { if (!willSubstitute.empty()) {
const float downloadSizeMiB = downloadSize / (1024.f * 1024.f); const float downloadSizeMiB = downloadSize / (1024.f * 1024.f);
const float narSizeMiB = narSize / (1024.f * 1024.f); const float narSizeMiB = narSize / (1024.f * 1024.f);
if (willSubstitute.size() == 1) { 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, downloadSizeMiB,
narSizeMiB)); narSizeMiB);
} else { } 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(), willSubstitute.size(),
downloadSizeMiB, downloadSizeMiB,
narSizeMiB)); narSizeMiB);
} }
for (auto & i : willSubstitute) for (auto & i : willSubstitute)
printMsg(lvl, fmt(" %s", store->printStorePath(i))); printMsg(lvl, " %s", store->printStorePath(i));
} }
if (!unknown.empty()) { if (!unknown.empty()) {
printMsg(lvl, fmt("don't know how to build these paths%s:", printMsg(lvl, "don't know how to build these paths%s:",
(settings.readOnlyMode ? " (may be caused by read-only store access)" : ""))); (settings.readOnlyMode ? " (may be caused by read-only store access)" : ""));
for (auto & i : unknown) for (auto & i : unknown)
printMsg(lvl, fmt(" %s", store->printStorePath(i))); printMsg(lvl, " %s", store->printStorePath(i));
} }
} }

View file

@ -786,8 +786,7 @@ void runPostBuildHook(
Store & store, Store & store,
Logger & logger, Logger & logger,
const StorePath & drvPath, const StorePath & drvPath,
StorePathSet outputPaths const StorePathSet & outputPaths)
)
{ {
auto hook = settings.postBuildHook; auto hook = settings.postBuildHook;
if (hook == "") if (hook == "")
@ -906,7 +905,7 @@ void DerivationGoal::buildDone()
auto builtOutputs = registerOutputs(); auto builtOutputs = registerOutputs();
StorePathSet outputPaths; StorePathSet outputPaths;
for (auto & [_, output] : buildResult.builtOutputs) for (auto & [_, output] : builtOutputs)
outputPaths.insert(output.outPath); outputPaths.insert(output.outPath);
runPostBuildHook( runPostBuildHook(
worker.store, worker.store,
@ -985,21 +984,28 @@ void DerivationGoal::resolvedFinished()
realWantedOutputs = resolvedDrv.outputNames(); realWantedOutputs = resolvedDrv.outputNames();
for (auto & wantedOutput : realWantedOutputs) { for (auto & wantedOutput : realWantedOutputs) {
assert(initialOutputs.count(wantedOutput) != 0); auto initialOutput = get(initialOutputs, wantedOutput);
assert(resolvedHashes.count(wantedOutput) != 0); auto resolvedHash = get(resolvedHashes, wantedOutput);
auto realisation = resolvedResult.builtOutputs.at( if ((!initialOutput) || (!resolvedHash))
DrvOutput { resolvedHashes.at(wantedOutput), wantedOutput }); throw Error(
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,resolve)",
worker.store.printStorePath(drvPath), wantedOutput);
auto realisation = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput });
if (!realisation)
throw Error(
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,realisation)",
worker.store.printStorePath(resolvedDrvGoal->drvPath), wantedOutput);
if (drv->type().isPure()) { if (drv->type().isPure()) {
auto newRealisation = realisation; auto newRealisation = *realisation;
newRealisation.id = DrvOutput { initialOutputs.at(wantedOutput).outputHash, wantedOutput }; newRealisation.id = DrvOutput { initialOutput->outputHash, wantedOutput };
newRealisation.signatures.clear(); newRealisation.signatures.clear();
if (!drv->type().isFixed()) if (!drv->type().isFixed())
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath); newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath);
signRealisation(newRealisation); signRealisation(newRealisation);
worker.store.registerDrvOutput(newRealisation); worker.store.registerDrvOutput(newRealisation);
} }
outputPaths.insert(realisation.outPath); outputPaths.insert(realisation->outPath);
builtOutputs.emplace(realisation.id, realisation); builtOutputs.emplace(realisation->id, *realisation);
} }
runPostBuildHook( runPostBuildHook(
@ -1295,7 +1301,11 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
DrvOutputs validOutputs; DrvOutputs validOutputs;
for (auto & i : queryPartialDerivationOutputMap()) { for (auto & i : queryPartialDerivationOutputMap()) {
InitialOutput & info = initialOutputs.at(i.first); auto initialOutput = get(initialOutputs, i.first);
if (!initialOutput)
// this is an invalid output, gets catched with (!wantedOutputsLeft.empty())
continue;
auto & info = *initialOutput;
info.wanted = wantOutput(i.first, wantedOutputs); info.wanted = wantOutput(i.first, wantedOutputs);
if (info.wanted) if (info.wanted)
wantedOutputsLeft.erase(i.first); wantedOutputsLeft.erase(i.first);
@ -1310,7 +1320,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
: PathStatus::Corrupt, : PathStatus::Corrupt,
}; };
} }
auto drvOutput = DrvOutput{initialOutputs.at(i.first).outputHash, i.first}; auto drvOutput = DrvOutput{info.outputHash, i.first};
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
if (auto real = worker.store.queryRealisation(drvOutput)) { if (auto real = worker.store.queryRealisation(drvOutput)) {
info.known = { info.known = {

View file

@ -14,6 +14,7 @@
#include "worker-protocol.hh" #include "worker-protocol.hh"
#include "topo-sort.hh" #include "topo-sort.hh"
#include "callback.hh" #include "callback.hh"
#include "json-utils.hh"
#include <regex> #include <regex>
#include <queue> #include <queue>
@ -56,8 +57,6 @@
#include <pwd.h> #include <pwd.h>
#include <grp.h> #include <grp.h>
#include <nlohmann/json.hpp>
namespace nix { namespace nix {
void handleDiffHook( void handleDiffHook(
@ -482,7 +481,7 @@ void LocalDerivationGoal::startBuilder()
temporary build directory. The text files have the format used temporary build directory. The text files have the format used
by `nix-store --register-validity'. However, the deriver by `nix-store --register-validity'. However, the deriver
fields are left empty. */ fields are left empty. */
auto s = get(drv->env, "exportReferencesGraph").value_or(""); auto s = getOr(drv->env, "exportReferencesGraph", "");
Strings ss = tokenizeString<Strings>(s); Strings ss = tokenizeString<Strings>(s);
if (ss.size() % 2 != 0) if (ss.size() % 2 != 0)
throw BuildError("odd number of tokens in 'exportReferencesGraph': '%1%'", s); throw BuildError("odd number of tokens in 'exportReferencesGraph': '%1%'", s);
@ -989,7 +988,7 @@ void LocalDerivationGoal::initTmpDir() {
there is no size constraint). */ there is no size constraint). */
if (!parsedDrv->getStructuredAttrs()) { if (!parsedDrv->getStructuredAttrs()) {
StringSet passAsFile = tokenizeString<StringSet>(get(drv->env, "passAsFile").value_or("")); StringSet passAsFile = tokenizeString<StringSet>(getOr(drv->env, "passAsFile", ""));
for (auto & i : drv->env) { for (auto & i : drv->env) {
if (passAsFile.find(i.first) == passAsFile.end()) { if (passAsFile.find(i.first) == passAsFile.end()) {
env[i.first] = i.second; env[i.first] = i.second;
@ -2128,12 +2127,22 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
std::map<std::string, std::variant<AlreadyRegistered, PerhapsNeedToRegister>> outputReferencesIfUnregistered; std::map<std::string, std::variant<AlreadyRegistered, PerhapsNeedToRegister>> outputReferencesIfUnregistered;
std::map<std::string, struct stat> outputStats; std::map<std::string, struct stat> outputStats;
for (auto & [outputName, _] : drv->outputs) { for (auto & [outputName, _] : drv->outputs) {
auto actualPath = toRealPathChroot(worker.store.printStorePath(scratchOutputs.at(outputName))); auto scratchOutput = get(scratchOutputs, outputName);
if (!scratchOutput)
throw BuildError(
"builder for '%s' has no scratch output for '%s'",
worker.store.printStorePath(drvPath), outputName);
auto actualPath = toRealPathChroot(worker.store.printStorePath(*scratchOutput));
outputsToSort.insert(outputName); outputsToSort.insert(outputName);
/* Updated wanted info to remove the outputs we definitely don't need to register */ /* Updated wanted info to remove the outputs we definitely don't need to register */
auto & initialInfo = initialOutputs.at(outputName); auto initialOutput = get(initialOutputs, outputName);
if (!initialOutput)
throw BuildError(
"builder for '%s' has no initial output for '%s'",
worker.store.printStorePath(drvPath), outputName);
auto & initialInfo = *initialOutput;
/* Don't register if already valid, and not checking */ /* Don't register if already valid, and not checking */
initialInfo.wanted = buildMode == bmCheck initialInfo.wanted = buildMode == bmCheck
@ -2185,6 +2194,11 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
auto sortedOutputNames = topoSort(outputsToSort, auto sortedOutputNames = topoSort(outputsToSort,
{[&](const std::string & name) { {[&](const std::string & name) {
auto orifu = get(outputReferencesIfUnregistered, name);
if (!orifu)
throw BuildError(
"no output reference for '%s' in build of '%s'",
name, worker.store.printStorePath(drvPath));
return std::visit(overloaded { return std::visit(overloaded {
/* Since we'll use the already installed versions of these, we /* Since we'll use the already installed versions of these, we
can treat them as leaves and ignore any references they can treat them as leaves and ignore any references they
@ -2199,7 +2213,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
referencedOutputs.insert(o); referencedOutputs.insert(o);
return referencedOutputs; return referencedOutputs;
}, },
}, outputReferencesIfUnregistered.at(name)); }, *orifu);
}}, }},
{[&](const std::string & path, const std::string & parent) { {[&](const std::string & path, const std::string & parent) {
// TODO with more -vvvv also show the temporary paths for manual inspection. // TODO with more -vvvv also show the temporary paths for manual inspection.
@ -2213,9 +2227,10 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
OutputPathMap finalOutputs; OutputPathMap finalOutputs;
for (auto & outputName : sortedOutputNames) { for (auto & outputName : sortedOutputNames) {
auto output = drv->outputs.at(outputName); auto output = get(drv->outputs, outputName);
auto & scratchPath = scratchOutputs.at(outputName); auto scratchPath = get(scratchOutputs, outputName);
auto actualPath = toRealPathChroot(worker.store.printStorePath(scratchPath)); assert(output && scratchPath);
auto actualPath = toRealPathChroot(worker.store.printStorePath(*scratchPath));
auto finish = [&](StorePath finalStorePath) { auto finish = [&](StorePath finalStorePath) {
/* Store the final path */ /* Store the final path */
@ -2223,10 +2238,13 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
/* The rewrite rule will be used in downstream outputs that refer to /* The rewrite rule will be used in downstream outputs that refer to
use. This is why the topological sort is essential to do first use. This is why the topological sort is essential to do first
before this for loop. */ before this for loop. */
if (scratchPath != finalStorePath) if (*scratchPath != finalStorePath)
outputRewrites[std::string { scratchPath.hashPart() }] = std::string { finalStorePath.hashPart() }; outputRewrites[std::string { scratchPath->hashPart() }] = std::string { finalStorePath.hashPart() };
}; };
auto orifu = get(outputReferencesIfUnregistered, outputName);
assert(orifu);
std::optional<StorePathSet> referencesOpt = std::visit(overloaded { std::optional<StorePathSet> referencesOpt = std::visit(overloaded {
[&](const AlreadyRegistered & skippedFinalPath) -> std::optional<StorePathSet> { [&](const AlreadyRegistered & skippedFinalPath) -> std::optional<StorePathSet> {
finish(skippedFinalPath.path); finish(skippedFinalPath.path);
@ -2235,7 +2253,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
[&](const PerhapsNeedToRegister & r) -> std::optional<StorePathSet> { [&](const PerhapsNeedToRegister & r) -> std::optional<StorePathSet> {
return r.refs; return r.refs;
}, },
}, outputReferencesIfUnregistered.at(outputName)); }, *orifu);
if (!referencesOpt) if (!referencesOpt)
continue; continue;
@ -2268,25 +2286,29 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
for (auto & r : references) { for (auto & r : references) {
auto name = r.name(); auto name = r.name();
auto origHash = std::string { r.hashPart() }; auto origHash = std::string { r.hashPart() };
if (r == scratchPath) if (r == *scratchPath) {
res.first = true; res.first = true;
else if (outputRewrites.count(origHash) == 0) } else if (auto outputRewrite = get(outputRewrites, origHash)) {
res.second.insert(r); std::string newRef = *outputRewrite;
else {
std::string newRef = outputRewrites.at(origHash);
newRef += '-'; newRef += '-';
newRef += name; newRef += name;
res.second.insert(StorePath { newRef }); res.second.insert(StorePath { newRef });
} else {
res.second.insert(r);
} }
} }
return res; return res;
}; };
auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo { auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo {
auto & st = outputStats.at(outputName); auto st = get(outputStats, outputName);
if (!st)
throw BuildError(
"output path %1% without valid stats info",
actualPath);
if (outputHash.method == FileIngestionMethod::Flat) { if (outputHash.method == FileIngestionMethod::Flat) {
/* The output path should be a regular file without execute permission. */ /* The output path should be a regular file without execute permission. */
if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0) if (!S_ISREG(st->st_mode) || (st->st_mode & S_IXUSR) != 0)
throw BuildError( throw BuildError(
"output path '%1%' should be a non-executable regular file " "output path '%1%' should be a non-executable regular file "
"since recursive hashing is not enabled (outputHashMode=flat)", "since recursive hashing is not enabled (outputHashMode=flat)",
@ -2294,7 +2316,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
} }
rewriteOutput(); rewriteOutput();
/* FIXME optimize and deduplicate with addToStore */ /* FIXME optimize and deduplicate with addToStore */
std::string oldHashPart { scratchPath.hashPart() }; std::string oldHashPart { scratchPath->hashPart() };
HashModuloSink caSink { outputHash.hashType, oldHashPart }; HashModuloSink caSink { outputHash.hashType, oldHashPart };
switch (outputHash.method) { switch (outputHash.method) {
case FileIngestionMethod::Recursive: case FileIngestionMethod::Recursive:
@ -2313,7 +2335,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
outputPathName(drv->name, outputName), outputPathName(drv->name, outputName),
refs.second, refs.second,
refs.first); refs.first);
if (scratchPath != finalPath) { if (*scratchPath != finalPath) {
// Also rewrite the output path // Also rewrite the output path
auto source = sinkToSource([&](Sink & nextSink) { auto source = sinkToSource([&](Sink & nextSink) {
StringSink sink; StringSink sink;
@ -2354,9 +2376,9 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
auto requiredFinalPath = output.path; auto requiredFinalPath = output.path;
/* Preemptively add rewrite rule for final hash, as that is /* Preemptively add rewrite rule for final hash, as that is
what the NAR hash will use rather than normalized-self references */ what the NAR hash will use rather than normalized-self references */
if (scratchPath != requiredFinalPath) if (*scratchPath != requiredFinalPath)
outputRewrites.insert_or_assign( outputRewrites.insert_or_assign(
std::string { scratchPath.hashPart() }, std::string { scratchPath->hashPart() },
std::string { requiredFinalPath.hashPart() }); std::string { requiredFinalPath.hashPart() });
rewriteOutput(); rewriteOutput();
auto narHashAndSize = hashPath(htSHA256, actualPath); auto narHashAndSize = hashPath(htSHA256, actualPath);
@ -2409,7 +2431,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
}); });
}, },
}, output.raw()); }, output->raw());
/* FIXME: set proper permissions in restorePath() so /* FIXME: set proper permissions in restorePath() so
we don't have to do another traversal. */ we don't have to do another traversal. */
@ -2425,7 +2447,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
derivations. */ derivations. */
PathLocks dynamicOutputLock; PathLocks dynamicOutputLock;
dynamicOutputLock.setDeletion(true); dynamicOutputLock.setDeletion(true);
auto optFixedPath = output.path(worker.store, drv->name, outputName); auto optFixedPath = output->path(worker.store, drv->name, outputName);
if (!optFixedPath || if (!optFixedPath ||
worker.store.printStorePath(*optFixedPath) != finalDestPath) worker.store.printStorePath(*optFixedPath) != finalDestPath)
{ {
@ -2491,11 +2513,10 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
/* For debugging, print out the referenced and unreferenced paths. */ /* For debugging, print out the referenced and unreferenced paths. */
for (auto & i : inputPaths) { for (auto & i : inputPaths) {
auto j = references.find(i); if (references.count(i))
if (j == references.end())
debug("unreferenced input: '%1%'", worker.store.printStorePath(i));
else
debug("referenced input: '%1%'", worker.store.printStorePath(i)); debug("referenced input: '%1%'", worker.store.printStorePath(i));
else
debug("unreferenced input: '%1%'", worker.store.printStorePath(i));
} }
if (curRound == nrRounds) { if (curRound == nrRounds) {
@ -2612,9 +2633,11 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
DrvOutputs builtOutputs; DrvOutputs builtOutputs;
for (auto & [outputName, newInfo] : infos) { for (auto & [outputName, newInfo] : infos) {
auto oldinfo = get(initialOutputs, outputName);
assert(oldinfo);
auto thisRealisation = Realisation { auto thisRealisation = Realisation {
.id = DrvOutput { .id = DrvOutput {
initialOutputs.at(outputName).outputHash, oldinfo->outputHash,
outputName outputName
}, },
.outPath = newInfo.path .outPath = newInfo.path
@ -2710,9 +2733,10 @@ void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo
for (auto & i : *value) { for (auto & i : *value) {
if (worker.store.isStorePath(i)) if (worker.store.isStorePath(i))
spec.insert(worker.store.parseStorePath(i)); spec.insert(worker.store.parseStorePath(i));
else if (outputs.count(i)) else if (auto output = get(outputs, i))
spec.insert(outputs.at(i).path); spec.insert(output->path);
else throw BuildError("derivation contains an illegal reference specifier '%s'", i); else
throw BuildError("derivation contains an illegal reference specifier '%s'", i);
} }
auto used = recursive auto used = recursive
@ -2751,24 +2775,18 @@ void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo
}; };
if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) { if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
auto outputChecks = structuredAttrs->find("outputChecks"); if (auto outputChecks = get(*structuredAttrs, "outputChecks")) {
if (outputChecks != structuredAttrs->end()) { if (auto output = get(*outputChecks, outputName)) {
auto output = outputChecks->find(outputName);
if (output != outputChecks->end()) {
Checks checks; Checks checks;
auto maxSize = output->find("maxSize"); if (auto maxSize = get(*output, "maxSize"))
if (maxSize != output->end())
checks.maxSize = maxSize->get<uint64_t>(); checks.maxSize = maxSize->get<uint64_t>();
auto maxClosureSize = output->find("maxClosureSize"); if (auto maxClosureSize = get(*output, "maxClosureSize"))
if (maxClosureSize != output->end())
checks.maxClosureSize = maxClosureSize->get<uint64_t>(); checks.maxClosureSize = maxClosureSize->get<uint64_t>();
auto get = [&](const std::string & name) -> std::optional<Strings> { auto get_ = [&](const std::string & name) -> std::optional<Strings> {
auto i = output->find(name); if (auto i = get(*output, name)) {
if (i != output->end()) {
Strings res; Strings res;
for (auto j = i->begin(); j != i->end(); ++j) { for (auto j = i->begin(); j != i->end(); ++j) {
if (!j->is_string()) if (!j->is_string())
@ -2781,10 +2799,10 @@ void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo
return {}; return {};
}; };
checks.allowedReferences = get("allowedReferences"); checks.allowedReferences = get_("allowedReferences");
checks.allowedRequisites = get("allowedRequisites"); checks.allowedRequisites = get_("allowedRequisites");
checks.disallowedReferences = get("disallowedReferences"); checks.disallowedReferences = get_("disallowedReferences");
checks.disallowedRequisites = get("disallowedRequisites"); checks.disallowedRequisites = get_("disallowedRequisites");
applyChecks(checks); applyChecks(checks);
} }

View file

@ -350,7 +350,7 @@ void Worker::waitForInput()
become `available'. Note that `available' (i.e., non-blocking) become `available'. Note that `available' (i.e., non-blocking)
includes EOF. */ includes EOF. */
std::vector<struct pollfd> pollStatus; std::vector<struct pollfd> pollStatus;
std::map <int, int> fdToPollStatus; std::map<int, size_t> fdToPollStatus;
for (auto & i : children) { for (auto & i : children) {
for (auto & j : i.fds) { for (auto & j : i.fds) {
pollStatus.push_back((struct pollfd) { .fd = j, .events = POLLIN }); pollStatus.push_back((struct pollfd) { .fd = j, .events = POLLIN });
@ -380,7 +380,10 @@ void Worker::waitForInput()
std::set<int> fds2(j->fds); std::set<int> fds2(j->fds);
std::vector<unsigned char> buffer(4096); std::vector<unsigned char> buffer(4096);
for (auto & k : fds2) { for (auto & k : fds2) {
if (pollStatus.at(fdToPollStatus.at(k)).revents) { const auto fdPollStatusId = get(fdToPollStatus, k);
assert(fdPollStatusId);
assert(*fdPollStatusId < pollStatus.size());
if (pollStatus.at(*fdPollStatusId).revents) {
ssize_t rd = ::read(k, buffer.data(), buffer.size()); ssize_t rd = ::read(k, buffer.data(), buffer.size());
// FIXME: is there a cleaner way to handle pt close // FIXME: is there a cleaner way to handle pt close
// than EIO? Is this even standard? // than EIO? Is this even standard?

View file

@ -24,7 +24,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
Path storePath = getAttr("out"); Path storePath = getAttr("out");
auto mainUrl = getAttr("url"); auto mainUrl = getAttr("url");
bool unpack = get(drv.env, "unpack").value_or("") == "1"; bool unpack = getOr(drv.env, "unpack", "") == "1";
/* Note: have to use a fresh fileTransfer here because we're in /* Note: have to use a fresh fileTransfer here because we're in
a forked process. */ a forked process. */

View file

@ -13,12 +13,27 @@ create table if not exists Realisations (
create index if not exists IndexRealisations on Realisations(drvPath, outputName); 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 ( create table if not exists RealisationsRefs (
referrer integer not null, referrer integer not null,
realisationReference integer, realisationReference integer,
foreign key (referrer) references Realisations(id) on delete cascade, foreign key (referrer) references Realisations(id) on delete cascade,
foreign key (realisationReference) references Realisations(id) on delete restrict 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 -- used by QueryRealisationReferences
create index if not exists IndexRealisationsRefs on RealisationsRefs(referrer); create index if not exists IndexRealisationsRefs on RealisationsRefs(referrer);

View file

@ -661,8 +661,10 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut
if (res.kind == DrvHash::Kind::Deferred) if (res.kind == DrvHash::Kind::Deferred)
kind = DrvHash::Kind::Deferred; kind = DrvHash::Kind::Deferred;
for (auto & outputName : inputOutputs) { for (auto & outputName : inputOutputs) {
const auto h = res.hashes.at(outputName); const auto h = get(res.hashes, outputName);
inputs2[h.to_string(Base16, false)].insert(outputName); if (!h)
throw Error("no hash for output '%s' of derivation '%s'", outputName, drv.name);
inputs2[h->to_string(Base16, false)].insert(outputName);
} }
} }
@ -836,8 +838,11 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true); auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
for (auto & [outputName, output] : drv.outputs) { for (auto & [outputName, output] : drv.outputs) {
if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) { if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) {
auto & h = hashModulo.hashes.at(outputName); auto h = get(hashModulo.hashes, outputName);
auto outPath = store.makeOutputPath(outputName, h, drv.name); if (!h)
throw Error("derivation '%s' output '%s' has no hash (derivations.cc/rewriteDerivation)",
drv.name, outputName);
auto outPath = store.makeOutputPath(outputName, *h, drv.name);
drv.env[outputName] = store.printStorePath(outPath); drv.env[outputName] = store.printStorePath(outPath);
output = DerivationOutput::InputAddressed { output = DerivationOutput::InputAddressed {
.path = std::move(outPath), .path = std::move(outPath),

View file

@ -4,6 +4,8 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <optional>
namespace nix { namespace nix {
nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const { nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const {
@ -17,12 +19,12 @@ nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
res["drvPath"] = store->printStorePath(drvPath); res["drvPath"] = store->printStorePath(drvPath);
// Fallback for the input-addressed derivation case: We expect to always be // Fallback for the input-addressed derivation case: We expect to always be
// able to print the output paths, so lets do it // able to print the output paths, so lets do it
auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath); const auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath);
for (const auto& output : outputs) { for (const auto& output : outputs) {
if (knownOutputs.at(output)) auto knownOutput = get(knownOutputs, output);
res["outputs"][output] = store->printStorePath(knownOutputs.at(output).value()); res["outputs"][output] = (knownOutput && *knownOutput)
else ? store->printStorePath(**knownOutput)
res["outputs"][output] = nullptr; : nullptr;
} }
return res; return res;
} }
@ -123,10 +125,15 @@ RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
for (auto& [outputName, outputPath] : p.outputs) { for (auto& [outputName, outputPath] : p.outputs) {
if (settings.isExperimentalFeatureEnabled( if (settings.isExperimentalFeatureEnabled(
Xp::CaDerivations)) { Xp::CaDerivations)) {
auto drvOutput = get(drvHashes, outputName);
if (!drvOutput)
throw Error(
"the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
store.printStorePath(p.drvPath), outputName);
auto thisRealisation = store.queryRealisation( auto thisRealisation = store.queryRealisation(
DrvOutput{drvHashes.at(outputName), outputName}); DrvOutput{*drvOutput, outputName});
assert(thisRealisation); // Weve built it, so we must h assert(thisRealisation); // Weve built it, so we must
// ve the realisation // have the realisation
res.insert(*thisRealisation); res.insert(*thisRealisation);
} else { } else {
res.insert(outputPath); res.insert(outputPath);

View file

@ -443,14 +443,13 @@ struct curlFileTransfer : public FileTransfer
: httpStatus != 0 : httpStatus != 0
? FileTransferError(err, ? FileTransferError(err,
std::move(response), std::move(response),
fmt("unable to %s '%s': HTTP error %d ('%s')", "unable to %s '%s': HTTP error %d%s",
request.verb(), request.uri, httpStatus, statusMsg) request.verb(), request.uri, httpStatus,
+ (code == CURLE_OK ? "" : fmt(" (curl error: %s)", curl_easy_strerror(code))) code == CURLE_OK ? "" : fmt(" (curl error: %s)", curl_easy_strerror(code)))
)
: FileTransferError(err, : FileTransferError(err,
std::move(response), std::move(response),
fmt("unable to %s '%s': %s (%d)", "unable to %s '%s': %s (%d)",
request.verb(), request.uri, curl_easy_strerror(code), code)); request.verb(), request.uri, curl_easy_strerror(code), code);
/* If this is a transient error, then maybe retry the /* If this is a transient error, then maybe retry the
download after a while. If we're writing to a download after a while. If we're writing to a
@ -693,10 +692,10 @@ struct curlFileTransfer : public FileTransfer
#if ENABLE_S3 #if ENABLE_S3
auto [bucketName, key, params] = parseS3Uri(request.uri); auto [bucketName, key, params] = parseS3Uri(request.uri);
std::string profile = get(params, "profile").value_or(""); std::string profile = getOr(params, "profile", "");
std::string region = get(params, "region").value_or(Aws::Region::US_EAST_1); std::string region = getOr(params, "region", Aws::Region::US_EAST_1);
std::string scheme = get(params, "scheme").value_or(""); std::string scheme = getOr(params, "scheme", "");
std::string endpoint = get(params, "endpoint").value_or(""); std::string endpoint = getOr(params, "endpoint", "");
S3Helper s3Helper(profile, region, scheme, endpoint); S3Helper s3Helper(profile, region, scheme, endpoint);
@ -704,7 +703,7 @@ struct curlFileTransfer : public FileTransfer
auto s3Res = s3Helper.getObject(bucketName, key); auto s3Res = s3Helper.getObject(bucketName, key);
FileTransferResult res; FileTransferResult res;
if (!s3Res.data) 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); res.data = std::move(*s3Res.data);
callback(std::move(res)); callback(std::move(res));
#else #else

View file

@ -81,7 +81,7 @@ int getSchema(Path schemaPath)
void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
{ {
const int nixCASchemaVersion = 3; const int nixCASchemaVersion = 4;
int curCASchema = getSchema(schemaPath); int curCASchema = getSchema(schemaPath);
if (curCASchema != nixCASchemaVersion) { if (curCASchema != nixCASchemaVersion) {
if (curCASchema > nixCASchemaVersion) { if (curCASchema > nixCASchemaVersion) {
@ -143,6 +143,21 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
)"); )");
txn.commit(); 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)); writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
lockFile(lockFd.get(), ltRead, true); lockFile(lockFd.get(), ltRead, true);
} }
@ -482,18 +497,18 @@ void LocalStore::openDB(State & state, bool create)
SQLiteStmt stmt; SQLiteStmt stmt;
stmt.create(db, "pragma main.journal_mode;"); stmt.create(db, "pragma main.journal_mode;");
if (sqlite3_step(stmt) != SQLITE_ROW) 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)); prevMode = std::string((const char *) sqlite3_column_text(stmt, 0));
} }
if (prevMode != mode && if (prevMode != mode &&
sqlite3_exec(db, ("pragma main.journal_mode = " + mode + ";").c_str(), 0, 0, 0) != SQLITE_OK) 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 /* Increase the auto-checkpoint interval to 40000 pages. This
seems enough to ensure that instantiating the NixOS system seems enough to ensure that instantiating the NixOS system
derivation is done in a single fsync(). */ derivation is done in a single fsync(). */
if (mode == "wal" && sqlite3_exec(db, "pragma wal_autocheckpoint = 40000;", 0, 0, 0) != SQLITE_OK) 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. */ /* Initialise the database schema, if necessary. */
if (create) { if (create) {
@ -703,7 +718,11 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
// somewhat expensive so we do lazily // somewhat expensive so we do lazily
hashesModulo = hashDerivationModulo(*this, drv, true); hashesModulo = hashDerivationModulo(*this, drv, true);
} }
StorePath recomputed = makeOutputPath(i.first, hashesModulo->hashes.at(i.first), drvName); auto currentOutputHash = get(hashesModulo->hashes, i.first);
if (!currentOutputHash)
throw Error("derivation '%s' has unexpected output '%s' (local-store / hashesModulo) named '%s'",
printStorePath(drvPath), printStorePath(doia.path), i.first);
StorePath recomputed = makeOutputPath(i.first, *currentOutputHash, drvName);
if (doia.path != recomputed) if (doia.path != recomputed)
throw Error("derivation '%s' has incorrect output '%s', should be '%s'", throw Error("derivation '%s' has incorrect output '%s', should be '%s'",
printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed)); printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed));

View file

@ -278,11 +278,16 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
std::set<Realisation> inputRealisations; std::set<Realisation> inputRealisations;
for (const auto & [inputDrv, outputNames] : drv.inputDrvs) { for (const auto & [inputDrv, outputNames] : drv.inputDrvs) {
auto outputHashes = const auto outputHashes =
staticOutputHashes(store, store.readDerivation(inputDrv)); staticOutputHashes(store, store.readDerivation(inputDrv));
for (const auto & outputName : outputNames) { for (const auto & outputName : outputNames) {
auto outputHash = get(outputHashes, outputName);
if (!outputHash)
throw Error(
"output '%s' of derivation '%s' isn't realised", outputName,
store.printStorePath(inputDrv));
auto thisRealisation = store.queryRealisation( auto thisRealisation = store.queryRealisation(
DrvOutput{outputHashes.at(outputName), outputName}); DrvOutput{*outputHash, outputName});
if (!thisRealisation) if (!thisRealisation)
throw Error( throw Error(
"output '%s' of derivation '%s' isn't built", outputName, "output '%s' of derivation '%s' isn't built", outputName,

View file

@ -1,5 +1,8 @@
#include "path-with-outputs.hh" #include "path-with-outputs.hh"
#include "store-api.hh" #include "store-api.hh"
#include "nlohmann/json.hpp"
#include <regex>
namespace nix { namespace nix {
@ -68,4 +71,57 @@ StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std:
return StorePathWithOutputs { store.followLinksToStorePath(path), std::move(outputs) }; return StorePathWithOutputs { store.followLinksToStorePath(path), std::move(outputs) };
} }
std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s)
{
static std::regex regex(R"((.*)\^((\*)|([a-z]+(,[a-z]+)*)))");
std::smatch match;
if (!std::regex_match(s, match, regex))
return {s, DefaultOutputs()};
if (match[3].matched)
return {match[1], AllOutputs()};
return {match[1], tokenizeString<OutputNames>(match[4].str(), ",")};
}
std::string printOutputsSpec(const OutputsSpec & outputsSpec)
{
if (std::get_if<DefaultOutputs>(&outputsSpec))
return "";
if (std::get_if<AllOutputs>(&outputsSpec))
return "^*";
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
return "^" + concatStringsSep(",", *outputNames);
assert(false);
}
void to_json(nlohmann::json & json, const OutputsSpec & outputsSpec)
{
if (std::get_if<DefaultOutputs>(&outputsSpec))
json = nullptr;
else if (std::get_if<AllOutputs>(&outputsSpec))
json = std::vector<std::string>({"*"});
else if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
json = *outputNames;
}
void from_json(const nlohmann::json & json, OutputsSpec & outputsSpec)
{
if (json.is_null())
outputsSpec = DefaultOutputs();
else {
auto names = json.get<OutputNames>();
if (names == OutputNames({"*"}))
outputsSpec = AllOutputs();
else
outputsSpec = names;
}
}
} }

View file

@ -4,6 +4,7 @@
#include "path.hh" #include "path.hh"
#include "derived-path.hh" #include "derived-path.hh"
#include "nlohmann/json_fwd.hpp"
namespace nix { namespace nix {
@ -32,4 +33,25 @@ StorePathWithOutputs parsePathWithOutputs(const Store & store, std::string_view
StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std::string_view pathWithOutputs); StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std::string_view pathWithOutputs);
typedef std::set<std::string> OutputNames;
struct AllOutputs {
bool operator < (const AllOutputs & _) const { return false; }
};
struct DefaultOutputs {
bool operator < (const DefaultOutputs & _) const { return false; }
};
typedef std::variant<DefaultOutputs, AllOutputs, OutputNames> OutputsSpec;
/* Parse a string of the form 'prefix^output1,...outputN' or
'prefix^*', returning the prefix and the outputs spec. */
std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s);
std::string printOutputsSpec(const OutputsSpec & outputsSpec);
void to_json(nlohmann::json &, const OutputsSpec &);
void from_json(const nlohmann::json &, OutputsSpec &);
} }

View file

@ -853,15 +853,15 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
OutputPathMap outputs; OutputPathMap outputs;
auto drv = evalStore->readDerivation(bfd.drvPath); auto drv = evalStore->readDerivation(bfd.drvPath);
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive const auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
auto drvOutputs = drv.outputsAndOptPaths(*this); const auto drvOutputs = drv.outputsAndOptPaths(*this);
for (auto & output : bfd.outputs) { for (auto & output : bfd.outputs) {
if (!outputHashes.count(output)) auto outputHash = get(outputHashes, output);
if (!outputHash)
throw Error( throw Error(
"the derivation '%s' doesn't have an output named '%s'", "the derivation '%s' doesn't have an output named '%s'",
printStorePath(bfd.drvPath), output); printStorePath(bfd.drvPath), output);
auto outputId = auto outputId = DrvOutput{ *outputHash, output };
DrvOutput{outputHashes.at(output), output};
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
auto realisation = auto realisation =
queryRealisation(outputId); queryRealisation(outputId);
@ -874,13 +874,14 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
} else { } else {
// If ca-derivations isn't enabled, assume that // If ca-derivations isn't enabled, assume that
// the output path is statically known. // the output path is statically known.
assert(drvOutputs.count(output)); const auto drvOutput = get(drvOutputs, output);
assert(drvOutputs.at(output).second); assert(drvOutput);
assert(drvOutput->second);
res.builtOutputs.emplace( res.builtOutputs.emplace(
outputId, outputId,
Realisation { Realisation {
.id = outputId, .id = outputId,
.outPath = *drvOutputs.at(output).second .outPath = *drvOutput->second,
}); });
} }
} }

View file

@ -5,6 +5,7 @@
#include "ref.hh" #include "ref.hh"
#include <optional> #include <optional>
#include <string>
namespace Aws { namespace Client { class ClientConfiguration; } } namespace Aws { namespace Client { class ClientConfiguration; } }
namespace Aws { namespace S3 { class S3Client; } } namespace Aws { namespace S3 { class S3Client; } }

View file

@ -8,22 +8,32 @@
namespace nix { 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 err = sqlite3_errcode(db);
int exterr = sqlite3_extended_errcode(db); int exterr = sqlite3_extended_errcode(db);
auto path = sqlite3_db_filename(db, nullptr); auto path = sqlite3_db_filename(db, nullptr);
if (!path) path = "(in-memory)";
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { 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 err == SQLITE_PROTOCOL
? fmt("SQLite database '%s' is busy (SQLITE_PROTOCOL)", path) ? "SQLite database '%s' is busy (SQLITE_PROTOCOL)"
: fmt("SQLite database '%s' is busy", path)); : "SQLite database '%s' is busy",
} path ? path : "(in-memory)");
else throw exp;
throw SQLiteError("%s: %s (in '%s')", fs.s, sqlite3_errstr(exterr), path); } else
throw SQLiteError(path, err, exterr, std::move(hf));
} }
SQLite::SQLite(const Path & path, bool create) 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); throw Error("cannot open SQLite database '%s'", path);
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
throwSQLiteError(db, "setting timeout"); SQLiteError::throw_(db, "setting timeout");
exec("pragma foreign_keys = 1"); exec("pragma foreign_keys = 1");
} }
@ -46,7 +56,7 @@ SQLite::~SQLite()
{ {
try { try {
if (db && sqlite3_close(db) != SQLITE_OK) if (db && sqlite3_close(db) != SQLITE_OK)
throwSQLiteError(db, "closing database"); SQLiteError::throw_(db, "closing database");
} catch (...) { } catch (...) {
ignoreException(); ignoreException();
} }
@ -62,7 +72,7 @@ void SQLite::exec(const std::string & stmt)
{ {
retrySQLite<void>([&]() { retrySQLite<void>([&]() {
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK) 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(); checkInterrupt();
assert(!stmt); assert(!stmt);
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK) 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->db = db;
this->sql = sql; this->sql = sql;
} }
@ -85,7 +95,7 @@ SQLiteStmt::~SQLiteStmt()
{ {
try { try {
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK) if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
throwSQLiteError(db, fmt("finalizing statement '%s'", sql)); SQLiteError::throw_(db, "finalizing statement '%s'", sql);
} catch (...) { } catch (...) {
ignoreException(); ignoreException();
} }
@ -109,7 +119,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (std::string_view value, bool not
{ {
if (notNull) { if (notNull) {
if (sqlite3_bind_text(stmt, curArg++, value.data(), -1, SQLITE_TRANSIENT) != SQLITE_OK) 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 } else
bind(); bind();
return *this; return *this;
@ -119,7 +129,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (const unsigned char * data, size
{ {
if (notNull) { if (notNull) {
if (sqlite3_bind_blob(stmt, curArg++, data, len, SQLITE_TRANSIENT) != SQLITE_OK) if (sqlite3_bind_blob(stmt, curArg++, data, len, SQLITE_TRANSIENT) != SQLITE_OK)
throwSQLiteError(stmt.db, "binding argument"); SQLiteError::throw_(stmt.db, "binding argument");
} else } else
bind(); bind();
return *this; return *this;
@ -129,7 +139,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull)
{ {
if (notNull) { if (notNull) {
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK) if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
throwSQLiteError(stmt.db, "binding argument"); SQLiteError::throw_(stmt.db, "binding argument");
} else } else
bind(); bind();
return *this; return *this;
@ -138,7 +148,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull)
SQLiteStmt::Use & SQLiteStmt::Use::bind() SQLiteStmt::Use & SQLiteStmt::Use::bind()
{ {
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK) if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
throwSQLiteError(stmt.db, "binding argument"); SQLiteError::throw_(stmt.db, "binding argument");
return *this; return *this;
} }
@ -152,14 +162,14 @@ void SQLiteStmt::Use::exec()
int r = step(); int r = step();
assert(r != SQLITE_ROW); assert(r != SQLITE_ROW);
if (r != SQLITE_DONE) 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() bool SQLiteStmt::Use::next()
{ {
int r = step(); int r = step();
if (r != SQLITE_DONE && r != SQLITE_ROW) 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; return r == SQLITE_ROW;
} }
@ -185,14 +195,14 @@ SQLiteTxn::SQLiteTxn(sqlite3 * db)
{ {
this->db = db; this->db = db;
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK) if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, "starting transaction"); SQLiteError::throw_(db, "starting transaction");
active = true; active = true;
} }
void SQLiteTxn::commit() void SQLiteTxn::commit()
{ {
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK) if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, "committing transaction"); SQLiteError::throw_(db, "committing transaction");
active = false; active = false;
} }
@ -200,7 +210,7 @@ SQLiteTxn::~SQLiteTxn()
{ {
try { try {
if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK) if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, "aborting transaction"); SQLiteError::throw_(db, "aborting transaction");
} catch (...) { } catch (...) {
ignoreException(); ignoreException();
} }

View file

@ -96,10 +96,30 @@ struct SQLiteTxn
}; };
MakeError(SQLiteError, Error); struct SQLiteError : Error
MakeError(SQLiteBusy, SQLiteError); {
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); void handleSQLiteBusy(const SQLiteBusy & e);

View file

@ -1314,7 +1314,7 @@ static bool isNonUriPath(const std::string & spec) {
std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Params & params) std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Params & params)
{ {
if (uri == "" || uri == "auto") { if (uri == "" || uri == "auto") {
auto stateDir = get(params, "state").value_or(settings.nixStateDir); auto stateDir = getOr(params, "state", settings.nixStateDir);
if (access(stateDir.c_str(), R_OK | W_OK) == 0) if (access(stateDir.c_str(), R_OK | W_OK) == 0)
return std::make_shared<LocalStore>(params); return std::make_shared<LocalStore>(params);
else if (pathExists(settings.nixDaemonSocketFile)) else if (pathExists(settings.nixDaemonSocketFile))

View file

@ -0,0 +1,46 @@
#include "path-with-outputs.hh"
#include <gtest/gtest.h>
namespace nix {
TEST(parseOutputsSpec, basic)
{
{
auto [prefix, outputsSpec] = parseOutputsSpec("foo");
ASSERT_EQ(prefix, "foo");
ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
}
{
auto [prefix, outputsSpec] = parseOutputsSpec("foo^*");
ASSERT_EQ(prefix, "foo");
ASSERT_TRUE(std::get_if<AllOutputs>(&outputsSpec));
}
{
auto [prefix, outputsSpec] = parseOutputsSpec("foo^out");
ASSERT_EQ(prefix, "foo");
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out"}));
}
{
auto [prefix, outputsSpec] = parseOutputsSpec("foo^out,bin");
ASSERT_EQ(prefix, "foo");
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
}
{
auto [prefix, outputsSpec] = parseOutputsSpec("foo^bar^out,bin");
ASSERT_EQ(prefix, "foo^bar");
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
}
{
auto [prefix, outputsSpec] = parseOutputsSpec("foo^&*()");
ASSERT_EQ(prefix, "foo^&*()");
ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
}
}
}

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

@ -87,11 +87,7 @@ struct ErrPos {
origin = pos.origin; origin = pos.origin;
line = pos.line; line = pos.line;
column = pos.column; column = pos.column;
// is file symbol null?
if (pos.file.set())
file = pos.file; file = pos.file;
else
file = "";
return *this; return *this;
} }

View file

@ -36,7 +36,9 @@ const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::str
std::string_view showExperimentalFeature(const ExperimentalFeature feature) std::string_view showExperimentalFeature(const ExperimentalFeature feature)
{ {
return stringifiedXpFeatures.at(feature); const auto ret = get(stringifiedXpFeatures, feature);
assert(ret);
return *ret;
} }
std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> & rawFeatures) std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> & rawFeatures)
@ -59,4 +61,20 @@ std::ostream & operator <<(std::ostream & str, const ExperimentalFeature & featu
return str << showExperimentalFeature(feature); 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);
}
} }

View file

@ -52,4 +52,11 @@ public:
MissingExperimentalFeature(ExperimentalFeature); MissingExperimentalFeature(ExperimentalFeature);
}; };
/**
* Semi-magic conversion to and from json.
* See the nlohmann/json readme for more details.
*/
void to_json(nlohmann::json &, const ExperimentalFeature &);
void from_json(const nlohmann::json &, ExperimentalFeature &);
} }

View file

@ -2,7 +2,6 @@
#include <boost/format.hpp> #include <boost/format.hpp>
#include <string> #include <string>
#include <regex>
#include "ansicolor.hh" #include "ansicolor.hh"
@ -155,15 +154,4 @@ inline hintformat hintfmt(std::string plain_string)
return hintfmt("%s", normaltxt(plain_string)); return hintfmt("%s", normaltxt(plain_string));
} }
/* Highlight all the given matches in the given string `s` by wrapping
them between `prefix` and `postfix`.
If some matches overlap, then their union will be wrapped rather
than the individual matches. */
std::string hiliteMatches(
std::string_view s,
std::vector<std::smatch> matches,
std::string_view prefix,
std::string_view postfix);
} }

25
src/libutil/git.cc Normal file
View file

@ -0,0 +1,25 @@
#include "git.hh"
#include <regex>
namespace nix {
namespace git {
std::optional<LsRemoteRefLine> parseLsRemoteLine(std::string_view line)
{
const static std::regex line_regex("^(ref: *)?([^\\s]+)(?:\\t+(.*))?$");
std::match_results<std::string_view::const_iterator> match;
if (!std::regex_match(line.cbegin(), line.cend(), match, line_regex))
return std::nullopt;
return LsRemoteRefLine {
.kind = match[1].length() == 0
? LsRemoteRefLine::Kind::Object
: LsRemoteRefLine::Kind::Symbolic,
.target = match[2],
.reference = match[3].length() == 0 ? std::nullopt : std::optional<std::string>{ match[3] }
};
}
}
}

40
src/libutil/git.hh Normal file
View file

@ -0,0 +1,40 @@
#pragma once
#include <string>
#include <string_view>
#include <optional>
namespace nix {
namespace git {
// A line from the output of `git ls-remote --symref`.
//
// These can be of two kinds:
//
// - Symbolic references of the form
//
// ref: {target} {reference}
//
// where {target} is itself a reference and {reference} is optional
//
// - Object references of the form
//
// {target} {reference}
//
// where {target} is a commit id and {reference} is mandatory
struct LsRemoteRefLine {
enum struct Kind {
Symbolic,
Object
};
Kind kind;
std::string target;
std::optional<std::string> reference;
};
std::optional<LsRemoteRefLine> parseLsRemoteLine(std::string_view line);
}
}

View file

@ -1,6 +1,4 @@
#include "fmt.hh" #include "hilite.hh"
#include <regex>
namespace nix { namespace nix {

20
src/libutil/hilite.hh Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <regex>
#include <vector>
#include <string>
namespace nix {
/* Highlight all the given matches in the given string `s` by wrapping
them between `prefix` and `postfix`.
If some matches overlap, then their union will be wrapped rather
than the individual matches. */
std::string hiliteMatches(
std::string_view s,
std::vector<std::smatch> matches,
std::string_view prefix,
std::string_view postfix);
}

21
src/libutil/json-utils.hh Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <nlohmann/json.hpp>
namespace nix {
const nlohmann::json * get(const nlohmann::json & map, const std::string & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &*i;
}
nlohmann::json * get(nlohmann::json & map, const std::string & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &*i;
}
}

View file

@ -99,47 +99,4 @@ make_ref(Args&&... args)
return ref<T>(p); return ref<T>(p);
} }
/* A non-nullable pointer.
This is similar to a C++ "& reference", but mutable.
This is similar to ref<T> but backed by a regular pointer instead of a smart pointer.
*/
template<typename T>
class ptr {
private:
T * p;
public:
ptr<T>(const ptr<T> & r)
: p(r.p)
{ }
explicit ptr<T>(T * p)
: p(p)
{
if (!p)
throw std::invalid_argument("null pointer cast to ptr");
}
T* operator ->() const
{
return &*p;
}
T& operator *() const
{
return *p;
}
bool operator == (const ptr<T> & other) const
{
return p == other.p;
}
bool operator != (const ptr<T> & other) const
{
return p != other.p;
}
};
} }

View file

@ -0,0 +1,54 @@
#include "chunked-vector.hh"
#include <gtest/gtest.h>
namespace nix {
TEST(ChunkedVector, InitEmpty) {
auto v = ChunkedVector<int, 2>(100);
ASSERT_EQ(v.size(), 0);
}
TEST(ChunkedVector, GrowsCorrectly) {
auto v = ChunkedVector<int, 2>(100);
for (auto i = 1; i < 20; i++) {
v.add(i);
ASSERT_EQ(v.size(), i);
}
}
TEST(ChunkedVector, AddAndGet) {
auto v = ChunkedVector<int, 2>(100);
for (auto i = 1; i < 20; i++) {
auto [i2, idx] = v.add(i);
auto & i3 = v[idx];
ASSERT_EQ(i, i2);
ASSERT_EQ(&i2, &i3);
}
}
TEST(ChunkedVector, ForEach) {
auto v = ChunkedVector<int, 2>(100);
for (auto i = 1; i < 20; i++) {
v.add(i);
}
int count = 0;
v.forEach([&count](int elt) {
count++;
});
ASSERT_EQ(count, v.size());
}
TEST(ChunkedVector, OverflowOK) {
// Similar to the AddAndGet, but intentionnally use a small
// initial ChunkedVector to force it to overflow
auto v = ChunkedVector<int, 2>(2);
for (auto i = 1; i < 20; i++) {
auto [i2, idx] = v.add(i);
auto & i3 = v[idx];
ASSERT_EQ(i, i2);
ASSERT_EQ(&i2, &i3);
}
}
}

33
src/libutil/tests/git.cc Normal file
View file

@ -0,0 +1,33 @@
#include "git.hh"
#include <gtest/gtest.h>
namespace nix {
TEST(GitLsRemote, parseSymrefLineWithReference) {
auto line = "ref: refs/head/main HEAD";
auto res = git::parseLsRemoteLine(line);
ASSERT_TRUE(res.has_value());
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic);
ASSERT_EQ(res->target, "refs/head/main");
ASSERT_EQ(res->reference, "HEAD");
}
TEST(GitLsRemote, parseSymrefLineWithNoReference) {
auto line = "ref: refs/head/main";
auto res = git::parseLsRemoteLine(line);
ASSERT_TRUE(res.has_value());
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic);
ASSERT_EQ(res->target, "refs/head/main");
ASSERT_EQ(res->reference, std::nullopt);
}
TEST(GitLsRemote, parseObjectRefLine) {
auto line = "abc123 refs/head/main";
auto res = git::parseLsRemoteLine(line);
ASSERT_TRUE(res.has_value());
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Object);
ASSERT_EQ(res->target, "abc123");
ASSERT_EQ(res->reference, "refs/head/main");
}
}

View file

@ -1,9 +1,7 @@
#include "fmt.hh" #include "hilite.hh"
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <regex>
namespace nix { namespace nix {
/* ----------- tests for fmt.hh -------------------------------------------------*/ /* ----------- tests for fmt.hh -------------------------------------------------*/

View file

@ -548,7 +548,7 @@ namespace nix {
TEST(get, emptyContainer) { TEST(get, emptyContainer) {
StringMap s = { }; StringMap s = { };
auto expected = std::nullopt; auto expected = nullptr;
ASSERT_EQ(get(s, "one"), expected); ASSERT_EQ(get(s, "one"), expected);
} }
@ -559,7 +559,23 @@ namespace nix {
s["two"] = "er"; s["two"] = "er";
auto expected = "yi"; auto expected = "yi";
ASSERT_EQ(get(s, "one"), expected); ASSERT_EQ(*get(s, "one"), expected);
}
TEST(getOr, emptyContainer) {
StringMap s = { };
auto expected = "yi";
ASSERT_EQ(getOr(s, "one", "yi"), expected);
}
TEST(getOr, getFromContainer) {
StringMap s;
s["one"] = "yi";
s["two"] = "er";
auto expected = "yi";
ASSERT_EQ(getOr(s, "one", "nope"), expected);
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------

View file

@ -5,6 +5,7 @@
#include <list> #include <list>
#include <set> #include <set>
#include <string> #include <string>
#include <limits>
#include <map> #include <map>
#include <variant> #include <variant>
#include <vector> #include <vector>

View file

@ -1082,7 +1082,7 @@ std::string runProgram(Path program, bool searchPath, const Strings & args,
auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input}); auto res = runProgram(RunOptions {.program = program, .searchPath = searchPath, .args = args, .input = input});
if (!statusOk(res.first)) if (!statusOk(res.first))
throw ExecError(res.first, fmt("program '%1%' %2%", program, statusToString(res.first))); throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
return res.second; return res.second;
} }
@ -1210,7 +1210,7 @@ void runProgram2(const RunOptions & options)
if (source) promise.get_future().get(); if (source) promise.get_future().get();
if (status) if (status)
throw ExecError(status, fmt("program '%1%' %2%", options.program, statusToString(status))); throw ExecError(status, "program '%1%' %2%", options.program, statusToString(status));
} }
@ -1588,7 +1588,6 @@ std::string stripIndentation(std::string_view s)
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}}; static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};

View file

@ -543,13 +543,31 @@ std::string stripIndentation(std::string_view s);
/* Get a value for the specified key from an associate container. */ /* Get a value for the specified key from an associate container. */
template <class T> template <class T>
std::optional<typename T::mapped_type> get(const T & map, const typename T::key_type & key) const typename T::mapped_type * get(const T & map, const typename T::key_type & key)
{ {
auto i = map.find(key); auto i = map.find(key);
if (i == map.end()) return {}; if (i == map.end()) return nullptr;
return std::optional<typename T::mapped_type>(i->second); return &i->second;
} }
template <class T>
typename T::mapped_type * get(T & map, const typename T::key_type & key)
{
auto i = map.find(key);
if (i == map.end()) return nullptr;
return &i->second;
}
/* Get a value for the specified key from an associate container, or a default value if the key isn't present. */
template <class T>
const typename T::mapped_type & getOr(T & map,
const typename T::key_type & key,
const typename T::mapped_type & defaultValue)
{
auto i = map.find(key);
if (i == map.end()) return defaultValue;
return i->second;
}
/* Remove and return the first item from a container. */ /* Remove and return the first item from a container. */
template <class T> template <class T>

6
src/nix-build/nix-build.cc Executable file → Normal file
View file

@ -440,7 +440,7 @@ static void main_nix_build(int argc, char * * argv)
env["NIX_STORE"] = store->storeDir; env["NIX_STORE"] = store->storeDir;
env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores); env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores);
auto passAsFile = tokenizeString<StringSet>(get(drv.env, "passAsFile").value_or("")); auto passAsFile = tokenizeString<StringSet>(getOr(drv.env, "passAsFile", ""));
bool keepTmp = false; bool keepTmp = false;
int fileNr = 0; int fileNr = 0;
@ -543,8 +543,6 @@ static void main_nix_build(int argc, char * * argv)
restoreProcessContext(); restoreProcessContext();
logger->stop();
execvp(shell->c_str(), argPtrs.data()); execvp(shell->c_str(), argPtrs.data());
throw SysError("executing shell '%s'", *shell); throw SysError("executing shell '%s'", *shell);
@ -603,8 +601,6 @@ static void main_nix_build(int argc, char * * argv)
outPaths.push_back(outputPath); outPaths.push_back(outputPath);
} }
logger->stop();
for (auto & path : outPaths) for (auto & path : outPaths)
std::cout << store->printStorePath(path) << '\n'; std::cout << store->printStorePath(path) << '\n';
} }

View file

@ -1241,7 +1241,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
Attr & a(*attrs.find(i.name)); Attr & a(*attrs.find(i.name));
if(a.value->type() != nString) continue; if(a.value->type() != nString) continue;
XMLAttrs attrs3; XMLAttrs attrs3;
attrs3["type"] = i.name; attrs3["type"] = globals.state->symbols[i.name];
attrs3["value"] = a.value->string.s; attrs3["value"] = a.value->string.s;
xml.writeEmptyElement("string", attrs3); xml.writeEmptyElement("string", attrs3);
} }
@ -1489,8 +1489,6 @@ static int main_nix_env(int argc, char * * argv)
globals.state->printStats(); globals.state->printStats();
logger->stop();
return 0; return 0;
} }
} }

View file

@ -106,7 +106,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
the store; we need it for future modifications of the the store; we need it for future modifications of the
environment. */ environment. */
std::ostringstream str; std::ostringstream str;
manifest.print(str, true); manifest.print(state.symbols, str, true);
auto manifestFile = state.store->addTextToStore("env-manifest.nix", auto manifestFile = state.store->addTextToStore("env-manifest.nix",
str.str(), references); str.str(), references);
@ -134,9 +134,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); }); state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); });
PathSet context; PathSet context;
Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath)); Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath));
auto topLevelDrv = state.coerceToStorePath(*aDrvPath.pos, *aDrvPath.value, context); auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context);
Attr & aOutPath(*topLevel.attrs->find(state.sOutPath)); Attr & aOutPath(*topLevel.attrs->find(state.sOutPath));
auto topLevelOut = state.coerceToStorePath(*aOutPath.pos, *aOutPath.value, context); auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context);
/* Realise the resulting store expression. */ /* Realise the resulting store expression. */
debug("building user environment"); debug("building user environment");

View file

@ -31,7 +31,8 @@ void processExpr(EvalState & state, const Strings & attrPaths,
bool evalOnly, OutputKind output, bool location, Expr * e) bool evalOnly, OutputKind output, bool location, Expr * e)
{ {
if (parseOnly) { if (parseOnly) {
std::cout << format("%1%\n") % *e; e->show(state.symbols, std::cout);
std::cout << "\n";
return; return;
} }
@ -55,7 +56,8 @@ void processExpr(EvalState & state, const Strings & attrPaths,
printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context); printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context);
else { else {
if (strict) state.forceValueDeep(vRes); if (strict) state.forceValueDeep(vRes);
std::cout << vRes << std::endl; vRes.print(state.symbols, std::cout);
std::cout << std::endl;
} }
} else { } else {
DrvInfos drvs; DrvInfos drvs;

View file

@ -1095,8 +1095,6 @@ static int main_nix_store(int argc, char * * argv)
op(opFlags, opArgs); op(opFlags, opArgs);
logger->stop();
return 0; return 0;
} }
} }

View file

@ -66,7 +66,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
auto type = cursor->getAttr("type")->getString(); auto type = cursor->getAttr("type")->getString();
std::string expected = !attrPath.empty() && attrPath[0] == "apps" ? "app" : "derivation"; std::string expected = !attrPath.empty() && state.symbols[attrPath[0]] == "apps" ? "app" : "derivation";
if (type != expected) if (type != expected)
throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected); throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected);
@ -89,7 +89,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
auto outputName = cursor->getAttr(state.sOutputName)->getString(); auto outputName = cursor->getAttr(state.sOutputName)->getString();
auto name = cursor->getAttr(state.sName)->getString(); auto name = cursor->getAttr(state.sName)->getString();
auto aPname = cursor->maybeGetAttr("pname"); auto aPname = cursor->maybeGetAttr("pname");
auto aMeta = cursor->maybeGetAttr("meta"); auto aMeta = cursor->maybeGetAttr(state.sMeta);
auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr; auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr;
auto mainProgram = auto mainProgram =
aMainProgram aMainProgram

View file

@ -4,6 +4,7 @@
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "local-fs-store.hh" #include "local-fs-store.hh"
#include "progress-bar.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -12,6 +13,7 @@ using namespace nix;
struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
{ {
Path outLink = "result"; Path outLink = "result";
bool printOutputPaths = false;
BuildMode buildMode = bmNormal; BuildMode buildMode = bmNormal;
CmdBuild() CmdBuild()
@ -31,6 +33,12 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
.handler = {&outLink, Path("")}, .handler = {&outLink, Path("")},
}); });
addFlag({
.longName = "print-out-paths",
.description = "Print the resulting output paths",
.handler = {&printOutputPaths, true},
});
addFlag({ addFlag({
.longName = "rebuild", .longName = "rebuild",
.description = "Rebuild an already built package and compare the result to the existing store paths.", .description = "Rebuild an already built package and compare the result to the existing store paths.",
@ -93,6 +101,22 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
}, buildable.raw()); }, buildable.raw());
} }
if (printOutputPaths) {
stopProgressBar();
for (auto & buildable : buildables) {
std::visit(overloaded {
[&](const BuiltPath::Opaque & bo) {
std::cout << store->printStorePath(bo.path) << std::endl;
},
[&](const BuiltPath::Built & bfd) {
for (auto & output : bfd.outputs) {
std::cout << store->printStorePath(output.second) << std::endl;
}
},
}, buildable.raw());
}
}
updateProfile(buildables); updateProfile(buildables);
} }
}; };

View file

@ -25,6 +25,13 @@ R""(
lrwxrwxrwx 1 … result-1 -> /nix/store/rkfrm0z6x6jmi7d3gsmma4j53h15mg33-cowsay-3.03+dfsg2 lrwxrwxrwx 1 … result-1 -> /nix/store/rkfrm0z6x6jmi7d3gsmma4j53h15mg33-cowsay-3.03+dfsg2
``` ```
* Build GNU Hello and print the resulting store path.
```console
# nix build nixpkgs#hello --print-out-paths
/nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10
```
* Build a specific output: * Build a specific output:
```console ```console

View file

@ -75,10 +75,10 @@ struct CmdBundle : InstallableCommand
auto val = installable->toValue(*evalState).first; auto val = installable->toValue(*evalState).first;
auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath(".")); auto [bundlerFlakeRef, bundlerName, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(bundler, absPath("."));
const flake::LockFlags lockFlags{ .writeLockFile = false }; const flake::LockFlags lockFlags{ .writeLockFile = false };
InstallableFlake bundler{this, InstallableFlake bundler{this,
evalState, std::move(bundlerFlakeRef), bundlerName, evalState, std::move(bundlerFlakeRef), bundlerName, outputsSpec,
{"bundlers." + settings.thisSystem.get() + ".default", {"bundlers." + settings.thisSystem.get() + ".default",
"defaultBundler." + settings.thisSystem.get() "defaultBundler." + settings.thisSystem.get()
}, },
@ -97,21 +97,23 @@ struct CmdBundle : InstallableCommand
throw Error("the bundler '%s' does not produce a derivation", bundler.what()); throw Error("the bundler '%s' does not produce a derivation", bundler.what());
PathSet context2; PathSet context2;
auto drvPath = evalState->coerceToStorePath(*attr1->pos, *attr1->value, context2); auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2);
auto attr2 = vRes->attrs->get(evalState->sOutPath); auto attr2 = vRes->attrs->get(evalState->sOutPath);
if (!attr2) if (!attr2)
throw Error("the bundler '%s' does not produce a derivation", bundler.what()); throw Error("the bundler '%s' does not produce a derivation", bundler.what());
auto outPath = evalState->coerceToStorePath(*attr2->pos, *attr2->value, context2); auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2);
store->buildPaths({ DerivedPath::Built { drvPath } }); store->buildPaths({ DerivedPath::Built { drvPath } });
auto outPathS = store->printStorePath(outPath); auto outPathS = store->printStorePath(outPath);
if (!outLink) { if (!outLink) {
auto &attr = vRes->attrs->need(evalState->sName); auto * attr = vRes->attrs->get(evalState->sName);
outLink = evalState->forceStringNoCtx(*attr.value,*attr.pos); if (!attr)
throw Error("attribute 'name' missing");
outLink = evalState->forceStringNoCtx(*attr->value, attr->pos);
} }
// TODO: will crash if not a localFSStore? // TODO: will crash if not a localFSStore?

View file

@ -507,13 +507,25 @@ struct CmdDevelop : Common, MixEnvironment
state, state,
installable->nixpkgsFlakeRef(), installable->nixpkgsFlakeRef(),
"bashInteractive", "bashInteractive",
DefaultOutputs(),
Strings{}, Strings{},
Strings{"legacyPackages." + settings.thisSystem.get() + "."}, Strings{"legacyPackages." + settings.thisSystem.get() + "."},
nixpkgsLockFlags); nixpkgsLockFlags);
shell = store->printStorePath( bool found = false;
Installable::toStorePath(getEvalStore(), store, Realise::Outputs, OperateOn::Output, bashInstallable))
+ "/bin/bash"; for (auto & path : Installable::toStorePaths(getEvalStore(), store, Realise::Outputs, OperateOn::Output, {bashInstallable})) {
auto s = store->printStorePath(path) + "/bin/bash";
if (pathExists(s)) {
shell = s;
found = true;
break;
}
}
if (!found)
throw Error("package 'nixpkgs#bashInteractive' does not provide a 'bin/bash'");
} catch (Error &) { } catch (Error &) {
ignoreException(); ignoreException();
} }

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