Merge remote-tracking branch 'origin/master' into script-to-make-docker-release

This commit is contained in:
Rok Garbas 2022-02-18 00:15:23 +01:00
commit bf435664d7
No known key found for this signature in database
GPG key ID: A0E01EF44C27BF00
92 changed files with 904 additions and 26074 deletions

View file

@ -25,7 +25,7 @@ jobs:
name: '${{ env.CACHIX_NAME }}' name: '${{ env.CACHIX_NAME }}'
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix-build -A checks.$(nix-instantiate --eval -E '(builtins.currentSystem)') - run: nix --experimental-features 'nix-command flakes' flake check -L
check_cachix: check_cachix:
name: Cachix secret present for installer tests name: Cachix secret present for installer tests
@ -95,7 +95,7 @@ jobs:
name: '${{ env.CACHIX_NAME }}' name: '${{ env.CACHIX_NAME }}'
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix-build -A checks.$(nix-instantiate --eval -E 'builtins.currentSystem' --json).dockerImage - run: nix --experimental-features 'nix-command flakes' build .#dockerImage -L
- run: docker load -i ./result/image.tar.gz - run: docker load -i ./result/image.tar.gz
- 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

View file

@ -10,7 +10,6 @@ makefiles = \
src/libexpr/local.mk \ src/libexpr/local.mk \
src/libcmd/local.mk \ src/libcmd/local.mk \
src/nix/local.mk \ src/nix/local.mk \
src/nlohmann/local.mk \
src/resolve-system-dependencies/local.mk \ src/resolve-system-dependencies/local.mk \
scripts/local.mk \ scripts/local.mk \
misc/bash/local.mk \ misc/bash/local.mk \

View file

@ -16,6 +16,7 @@ LDFLAGS = @LDFLAGS@
LIBARCHIVE_LIBS = @LIBARCHIVE_LIBS@ LIBARCHIVE_LIBS = @LIBARCHIVE_LIBS@
LIBBROTLI_LIBS = @LIBBROTLI_LIBS@ LIBBROTLI_LIBS = @LIBBROTLI_LIBS@
LIBCURL_LIBS = @LIBCURL_LIBS@ LIBCURL_LIBS = @LIBCURL_LIBS@
LOWDOWN_LIBS = @LOWDOWN_LIBS@
OPENSSL_LIBS = @OPENSSL_LIBS@ OPENSSL_LIBS = @OPENSSL_LIBS@
LIBSECCOMP_LIBS = @LIBSECCOMP_LIBS@ LIBSECCOMP_LIBS = @LIBSECCOMP_LIBS@
PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_NAME = @PACKAGE_NAME@

View file

@ -262,13 +262,17 @@ fi
PKG_CHECK_MODULES([GTEST], [gtest_main]) PKG_CHECK_MODULES([GTEST], [gtest_main])
# Look for nlohmann/json.
PKG_CHECK_MODULES([NLOHMANN_JSON], [nlohmann_json >= 3.9])
# documentation generation switch # documentation generation switch
AC_ARG_ENABLE(doc-gen, AS_HELP_STRING([--disable-doc-gen],[disable documentation generation]), AC_ARG_ENABLE(doc-gen, AS_HELP_STRING([--disable-doc-gen],[disable documentation generation]),
doc_generate=$enableval, doc_generate=yes) doc_generate=$enableval, doc_generate=yes)
AC_SUBST(doc_generate) AC_SUBST(doc_generate)
# Look for lowdown library. # Look for lowdown library.
PKG_CHECK_MODULES([LOWDOWN], [lowdown >= 0.8.0], [CXXFLAGS="$LOWDOWN_CFLAGS $CXXFLAGS"]) PKG_CHECK_MODULES([LOWDOWN], [lowdown >= 0.9.0], [CXXFLAGS="$LOWDOWN_CFLAGS $CXXFLAGS"])
# Setuid installations. # Setuid installations.
AC_CHECK_FUNCS([setresuid setreuid lchown]) AC_CHECK_FUNCS([setresuid setreuid lchown])

View file

@ -20,7 +20,7 @@ concatStrings (map
# JSON, but that converts to "{ }" here. # JSON, but that converts to "{ }" here.
(if isAttrs option.value then "`\"\"`" (if isAttrs option.value then "`\"\"`"
else "`" + toString option.value + "`")) + "\n\n" else "`" + toString option.value + "`")) + "\n\n"
else " **Default:** *machine-specific*") else " **Default:** *machine-specific*\n")
+ (if option.aliases != [] + (if option.aliases != []
then " **Deprecated alias:** " + (concatStringsSep ", " (map (s: "`${s}`") option.aliases)) + "\n\n" then " **Deprecated alias:** " + (concatStringsSep ", " (map (s: "`${s}`") option.aliases)) + "\n\n"
else "") else "")

View file

@ -1,2 +1,9 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
* `nix bundle` breaking API change now supports bundlers of the form
`bundler.<system>.<name>= derivation: another-derivation;`. This supports
additional functionality to inspect evaluation information during bundling. A
new [repository](https://github.com/NixOS/bundlers) has various bundlers
implemented.
* `nix store ping` now reports the version of the remote Nix daemon.

View file

@ -133,6 +133,7 @@
./boehmgc-coroutine-sp-fallback.diff ./boehmgc-coroutine-sp-fallback.diff
]; ];
})) }))
nlohmann_json
]; ];
perlDeps = perlDeps =
@ -446,19 +447,7 @@
installerScriptForGHA = installScriptFor [ "x86_64-linux" "x86_64-darwin" "armv6l-linux" "armv7l-linux"]; installerScriptForGHA = installScriptFor [ "x86_64-linux" "x86_64-darwin" "armv6l-linux" "armv7l-linux"];
# docker image with Nix inside # docker image with Nix inside
dockerImage = nixpkgs.lib.genAttrs linux64BitSystems (system: dockerImage = nixpkgs.lib.genAttrs linux64BitSystems (system: self.packages.${system}.dockerImage);
let
pkgs = nixpkgsFor.${system};
image = import ./docker.nix { inherit pkgs; tag = version; };
in pkgs.runCommand "docker-image-tarball-${version}"
{ meta.description = "Docker image with Nix for ${system}";
}
''
mkdir -p $out/nix-support
image=$out/image.tar.gz
ln -s ${image} $image
echo "file binary-dist $image" >> $out/nix-support/hydra-build-products
'');
# Line coverage analysis. # Line coverage analysis.
coverage = coverage =
@ -605,6 +594,20 @@
hardeningDisable = [ "pie" ]; hardeningDisable = [ "pie" ];
}; };
dockerImage =
let
pkgs = nixpkgsFor.${system};
image = import ./docker.nix { inherit pkgs; tag = version; };
in
pkgs.runCommand
"docker-image-tarball-${version}"
{ meta.description = "Docker image with Nix for ${system}"; }
''
mkdir -p $out/nix-support
image=$out/image.tar.gz
ln -s ${image} $image
echo "file binary-dist $image" >> $out/nix-support/hydra-build-products
'';
} // builtins.listToAttrs (map (crossSystem: { } // builtins.listToAttrs (map (crossSystem: {
name = "nix-${crossSystem}"; name = "nix-${crossSystem}";
value = let value = let
@ -648,8 +651,7 @@
nixpkgsFor.${system}.lib.nameValuePair nixpkgsFor.${system}.lib.nameValuePair
"nix-${stdenvName}" "nix-${stdenvName}"
nixpkgsFor.${system}."${stdenvName}Packages".nix nixpkgsFor.${system}."${stdenvName}Packages".nix
) stdenvs)) ) stdenvs)));
);
defaultPackage = forAllSystems (system: self.packages.${system}.nix); defaultPackage = forAllSystems (system: self.packages.${system}.nix);

View file

@ -15,7 +15,7 @@ function _complete_nix {
else else
COMPREPLY+=("$completion") COMPREPLY+=("$completion")
fi fi
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}") done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}" 2>/dev/null)
__ltrim_colon_completions "$cur" __ltrim_colon_completions "$cur"
} }

View file

@ -4,7 +4,7 @@ function _nix() {
local ifs_bk="$IFS" local ifs_bk="$IFS"
local input=("${(Q)words[@]}") local input=("${(Q)words[@]}")
IFS=$'\n' IFS=$'\n'
local res=($(NIX_GET_COMPLETIONS=$((CURRENT - 1)) "$input[@]")) local res=($(NIX_GET_COMPLETIONS=$((CURRENT - 1)) "$input[@]" 2>/dev/null))
IFS="$ifs_bk" IFS="$ifs_bk"
local tpe="${${res[1]}%%> *}" local tpe="${${res[1]}%%> *}"
local -a suggestions local -a suggestions

View file

@ -16,12 +16,17 @@ someBuildFailed=0
for buildId in $BUILDS_FOR_LATEST_EVAL; do for buildId in $BUILDS_FOR_LATEST_EVAL; do
buildInfo=$(curl -sS -H 'Accept: application/json' "https://hydra.nixos.org/build/$buildId") buildInfo=$(curl -sS -H 'Accept: application/json' "https://hydra.nixos.org/build/$buildId")
buildStatus=$(echo "$buildInfo" | \ finished=$(echo "$buildInfo" | jq -r '.finished')
jq -r '.buildstatus')
if [[ "$buildStatus" -ne 0 ]]; then if [[ $finished = 0 ]]; then
continue
fi
buildStatus=$(echo "$buildInfo" | jq -r '.buildstatus')
if [[ $buildStatus != 0 ]]; then
someBuildFailed=1 someBuildFailed=1
echo "Job “$(echo "$buildInfo" | jq -r '.job')” failed on hydra" echo "Job “$(echo "$buildInfo" | jq -r '.job')” failed on hydra: $buildInfo"
fi fi
done done

View file

@ -576,21 +576,40 @@ create_directories() {
# since this bit is cross-platform: # since this bit is cross-platform:
# - first try with `command -vp` to try and find # - first try with `command -vp` to try and find
# chown in the usual places # chown in the usual places
# * to work around some sort of deficiency in
# `command -p` in macOS bash 3.2, we also add
# PATH="$(getconf PATH 2>/dev/null)". As long as
# getconf is found, this should set a sane PATH
# which `command -p` in bash 3.2 appears to use.
# A bash with a properly-working `command -p`
# should ignore this hard-set PATH in favor of
# whatever it obtains internally. See
# github.com/NixOS/nix/issues/5768
# - fall back on `command -v` which would find # - fall back on `command -v` which would find
# any chown on path # any chown on path
# if we don't find one, the command is already # if we don't find one, the command is already
# hiding behind || true, and the general state # hiding behind || true, and the general state
# should be one the user can repair once they # should be one the user can repair once they
# figure out where chown is... # figure out where chown is...
local get_chr_own="$(command -vp chown)" local get_chr_own="$(PATH="$(getconf PATH 2>/dev/null)" command -vp chown)"
if [[ -z "$get_chr_own" ]]; then if [[ -z "$get_chr_own" ]]; then
get_chr_own="$(command -v chown)" get_chr_own="$(command -v chown)"
fi fi
if [[ -z "$get_chr_own" ]]; then
reminder <<EOF
I wanted to take root ownership of existing Nix store files,
but I couldn't locate 'chown'. (You may need to fix your PATH.)
To manually change file ownership, you can run:
sudo chown -R 'root:$NIX_BUILD_GROUP_NAME' '$NIX_ROOT'
EOF
else
_sudo "to take root ownership of existing Nix store files" \ _sudo "to take root ownership of existing Nix store files" \
"$get_chr_own" -R "root:$NIX_BUILD_GROUP_NAME" "$NIX_ROOT" || true "$get_chr_own" -R "root:$NIX_BUILD_GROUP_NAME" "$NIX_ROOT" || true
fi fi
fi
_sudo "to make the basic directory structure of Nix (part 1)" \ _sudo "to make the basic directory structure of Nix (part 1)" \
install -dv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool} /nix/var/nix/{gcroots,profiles}/per-user install -dv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool,/daemon-socket} /nix/var/nix/{gcroots,profiles}/per-user
_sudo "to make the basic directory structure of Nix (part 2)" \ _sudo "to make the basic directory structure of Nix (part 2)" \
install -dv -g "$NIX_BUILD_GROUP_NAME" -m 1775 /nix/store install -dv -g "$NIX_BUILD_GROUP_NAME" -m 1775 /nix/store

View file

@ -97,7 +97,7 @@ MixFlakeOptions::MixFlakeOptions()
lockFlags.writeLockFile = false; lockFlags.writeLockFile = false;
lockFlags.inputOverrides.insert_or_assign( lockFlags.inputOverrides.insert_or_assign(
flake::parseInputPath(inputPath), flake::parseInputPath(inputPath),
parseFlakeRef(flakeRef, absPath("."))); parseFlakeRef(flakeRef, absPath("."), true));
}} }}
}); });
@ -198,8 +198,9 @@ void SourceExprCommand::completeInstallable(std::string_view prefix)
prefix_ = ""; prefix_ = "";
} }
Value &v1(*findAlongAttrPath(*state, prefix_, *autoArgs, root).first); auto [v, pos] = findAlongAttrPath(*state, prefix_, *autoArgs, root);
state->forceValue(v1); Value &v1(*v);
state->forceValue(v1, pos);
Value v2; Value v2;
state->autoCallFunction(*autoArgs, v1, v2); state->autoCallFunction(*autoArgs, v1, v2);
@ -446,7 +447,7 @@ struct InstallableAttrPath : InstallableValue
std::pair<Value *, Pos> toValue(EvalState & state) override std::pair<Value *, Pos> 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); state.forceValue(*vRes, pos);
return {vRes, pos}; return {vRes, pos};
} }
@ -496,7 +497,7 @@ Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::Locked
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
assert(aOutputs); assert(aOutputs);
state.forceValue(*aOutputs->value); state.forceValue(*aOutputs->value, [&]() { return aOutputs->value->determinePos(noPos); });
return aOutputs->value; return aOutputs->value;
} }
@ -521,7 +522,7 @@ ref<eval_cache::EvalCache> openEvalCache(
auto vFlake = state.allocValue(); auto vFlake = state.allocValue();
flake::callFlake(state, *lockedFlake, *vFlake); flake::callFlake(state, *lockedFlake, *vFlake);
state.forceAttrs(*vFlake); state.forceAttrs(*vFlake, noPos);
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
assert(aOutputs); assert(aOutputs);
@ -544,13 +545,14 @@ InstallableFlake::InstallableFlake(
SourceExprCommand * cmd, SourceExprCommand * cmd,
ref<EvalState> state, ref<EvalState> state,
FlakeRef && flakeRef, FlakeRef && flakeRef,
Strings && attrPaths, std::string_view fragment,
Strings && prefixes, Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags) const flake::LockFlags & lockFlags)
: InstallableValue(state), : InstallableValue(state),
flakeRef(flakeRef), flakeRef(flakeRef),
attrPaths(attrPaths), attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
prefixes(prefixes), prefixes(fragment == "" ? Strings{} : prefixes),
lockFlags(lockFlags) lockFlags(lockFlags)
{ {
if (cmd && cmd->getAutoArgs(*state)->size()) if (cmd && cmd->getAutoArgs(*state)->size())
@ -565,6 +567,8 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
auto root = cache->getRoot(); auto root = cache->getRoot();
for (auto & attrPath : getActualAttrPaths()) { for (auto & attrPath : getActualAttrPaths()) {
debug("trying flake output attribute '%s'", attrPath);
auto attr = root->findAlongAttrPath( auto attr = root->findAlongAttrPath(
parseAttrPath(*state, attrPath), parseAttrPath(*state, attrPath),
true true
@ -608,7 +612,7 @@ std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
for (auto & attrPath : getActualAttrPaths()) { for (auto & attrPath : getActualAttrPaths()) {
try { try {
auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs); auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
state.forceValue(*v); state.forceValue(*v, pos);
return {v, pos}; return {v, pos};
} catch (AttrPathNotFound & e) { } catch (AttrPathNotFound & e) {
} }
@ -707,7 +711,8 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
this, this,
getEvalState(), getEvalState(),
std::move(flakeRef), std::move(flakeRef),
fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment}, fragment,
getDefaultFlakeAttrPaths(),
getDefaultFlakeAttrPathPrefixes(), getDefaultFlakeAttrPathPrefixes(),
lockFlags)); lockFlags));
continue; continue;

View file

@ -102,8 +102,9 @@ struct InstallableFlake : InstallableValue
SourceExprCommand * cmd, SourceExprCommand * cmd,
ref<EvalState> state, ref<EvalState> state,
FlakeRef && flakeRef, FlakeRef && flakeRef,
Strings && attrPaths, std::string_view fragment,
Strings && prefixes, Strings attrPaths,
Strings prefixes,
const flake::LockFlags & lockFlags); const flake::LockFlags & lockFlags);
std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); } std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }

View file

@ -8,7 +8,7 @@ libcmd_SOURCES := $(wildcard $(d)/*.cc)
libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers
libcmd_LDFLAGS += -llowdown -pthread libcmd_LDFLAGS += $(LOWDOWN_LIBS) -pthread
libcmd_LIBS = libstore libutil libexpr libmain libfetchers libcmd_LIBS = libstore libutil libexpr libmain libfetchers

View file

@ -58,7 +58,7 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
Value * vNew = state.allocValue(); Value * vNew = state.allocValue();
state.autoCallFunction(autoArgs, *v, *vNew); state.autoCallFunction(autoArgs, *v, *vNew);
v = vNew; v = vNew;
state.forceValue(*v); state.forceValue(*v, noPos);
/* It should evaluate to either a set or an expression, /* It should evaluate to either a set or an expression,
according to what is specified in the attrPath. */ according to what is specified in the attrPath. */
@ -121,7 +121,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what)
std::string filename(pos, 0, colon); std::string filename(pos, 0, colon);
unsigned int lineno; unsigned int lineno;
try { try {
lineno = std::stoi(std::string(pos, colon + 1)); lineno = std::stoi(std::string(pos, colon + 1, string::npos));
} catch (std::invalid_argument & e) { } catch (std::invalid_argument & e) {
throw ParseError("cannot parse line number '%s'", pos); throw ParseError("cannot parse line number '%s'", pos);
} }

View file

@ -336,7 +336,7 @@ Value & AttrCursor::getValue()
if (!_value) { if (!_value) {
if (parent) { if (parent) {
auto & vParent = parent->first->getValue(); auto & vParent = parent->first->getValue();
root->state.forceAttrs(vParent); root->state.forceAttrs(vParent, noPos);
auto attr = vParent.attrs->get(parent->second); auto attr = vParent.attrs->get(parent->second);
if (!attr) if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr()); throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
@ -381,7 +381,7 @@ Value & AttrCursor::forceValue()
auto & v = getValue(); auto & v = getValue();
try { try {
root->state.forceValue(v); root->state.forceValue(v, noPos);
} catch (EvalError &) { } catch (EvalError &) {
debug("setting '%s' to failed", getAttrPathStr()); debug("setting '%s' to failed", getAttrPathStr());
if (root->db) if (root->db)

View file

@ -15,12 +15,6 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s))
}); });
} }
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
{
throw TypeError(s, showType(v));
}
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v)) LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v))
{ {
throw TypeError({ throw TypeError({
@ -31,6 +25,13 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
void EvalState::forceValue(Value & v, const Pos & pos) void EvalState::forceValue(Value & v, const Pos & pos)
{
forceValue(v, [&]() { return pos; });
}
template<typename Callable>
void EvalState::forceValue(Value & v, Callable getPos)
{ {
if (v.isThunk()) { if (v.isThunk()) {
Env * env = v.thunk.env; Env * env = v.thunk.env;
@ -47,31 +48,22 @@ void EvalState::forceValue(Value & v, const Pos & pos)
else if (v.isApp()) else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, noPos); callFunction(*v.app.left, *v.app.right, v, noPos);
else if (v.isBlackhole()) else if (v.isBlackhole())
throwEvalError(pos, "infinite recursion encountered"); throwEvalError(getPos(), "infinite recursion encountered");
}
inline void EvalState::forceAttrs(Value & v)
{
forceValue(v);
if (v.type() != nAttrs)
throwTypeError("value is %1% while a set was expected", v);
} }
inline void EvalState::forceAttrs(Value & v, const Pos & pos) inline void EvalState::forceAttrs(Value & v, const Pos & pos)
{ {
forceValue(v, pos); forceAttrs(v, [&]() { return pos; });
if (v.type() != nAttrs)
throwTypeError(pos, "value is %1% while a set was expected", v);
} }
inline void EvalState::forceList(Value & v) template <typename Callable>
inline void EvalState::forceAttrs(Value & v, Callable getPos)
{ {
forceValue(v); forceValue(v, getPos);
if (!v.isList()) if (v.type() != nAttrs)
throwTypeError("value is %1% while a list was expected", v); throwTypeError(getPos(), "value is %1% while a set was expected", v);
} }

View file

@ -1,5 +1,6 @@
#include "eval.hh" #include "eval.hh"
#include "hash.hh" #include "hash.hh"
#include "types.hh"
#include "util.hh" #include "util.hh"
#include "store-api.hh" #include "store-api.hh"
#include "derivations.hh" #include "derivations.hh"
@ -753,6 +754,11 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
}); });
} }
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
{
throw TypeError(s, showType(v));
}
LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1)) LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1))
{ {
throw AssertionError({ throw AssertionError({
@ -1137,7 +1143,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
Hence we need __overrides.) */ Hence we need __overrides.) */
if (hasOverrides) { if (hasOverrides) {
Value * vOverrides = (*v.attrs)[overrides->second.displ].value; Value * vOverrides = (*v.attrs)[overrides->second.displ].value;
state.forceAttrs(*vOverrides); state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); });
Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size()); Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size());
for (auto & i : *v.attrs) for (auto & i : *v.attrs)
newBnds->push_back(i); newBnds->push_back(i);
@ -1285,7 +1291,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
e->eval(state, env, vTmp); e->eval(state, env, vTmp);
for (auto & i : attrPath) { for (auto & i : attrPath) {
state.forceValue(*vAttrs); state.forceValue(*vAttrs, noPos);
Bindings::iterator j; Bindings::iterator j;
Symbol name = getName(i, state, env); Symbol name = getName(i, state, env);
if (vAttrs->type() != nAttrs || if (vAttrs->type() != nAttrs ||
@ -1373,7 +1379,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
/* Nope, so show the first unexpected argument to the /* Nope, so show the first unexpected argument to the
user. */ user. */
for (auto & i : *args[0]->attrs) for (auto & i : *args[0]->attrs)
if (!lambda.formals->argNames.count(i.name)) if (!lambda.formals->has(i.name))
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name); throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
abort(); // can't happen abort(); // can't happen
} }
@ -1499,14 +1505,16 @@ void EvalState::incrFunctionCall(ExprLambda * fun)
void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
{ {
forceValue(fun); auto pos = fun.determinePos(noPos);
forceValue(fun, pos);
if (fun.type() == nAttrs) { if (fun.type() == nAttrs) {
auto found = fun.attrs->find(sFunctor); auto found = fun.attrs->find(sFunctor);
if (found != fun.attrs->end()) { if (found != fun.attrs->end()) {
Value * v = allocValue(); Value * v = allocValue();
callFunction(*found->value, fun, *v, noPos); callFunction(*found->value, fun, *v, pos);
forceValue(*v); forceValue(*v, pos);
return autoCallFunction(args, *v, res); return autoCallFunction(args, *v, res);
} }
} }
@ -1694,7 +1702,7 @@ void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Po
void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
{ {
PathSet context; PathSet context;
std::vector<std::string> s; std::vector<BackedStringView> s;
size_t sSize = 0; size_t sSize = 0;
NixInt n = 0; NixInt n = 0;
NixFloat nf = 0; NixFloat nf = 0;
@ -1705,7 +1713,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
const auto str = [&] { const auto str = [&] {
std::string result; std::string result;
result.reserve(sSize); result.reserve(sSize);
for (const auto & part : s) result += part; for (const auto & part : s) result += *part;
return result; return result;
}; };
/* c_str() is not str().c_str() because we want to create a string /* c_str() is not str().c_str() because we want to create a string
@ -1715,15 +1723,18 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
char * result = allocString(sSize + 1); char * result = allocString(sSize + 1);
char * tmp = result; char * tmp = result;
for (const auto & part : s) { for (const auto & part : s) {
memcpy(tmp, part.c_str(), part.size()); memcpy(tmp, part->data(), part->size());
tmp += part.size(); tmp += part->size();
} }
*tmp = 0; *tmp = 0;
return result; return result;
}; };
Value values[es->size()];
Value * vTmpP = values;
for (auto & [i_pos, i] : *es) { for (auto & [i_pos, i] : *es) {
Value vTmp; Value & vTmp = *vTmpP++;
i->eval(state, env, vTmp); i->eval(state, env, vTmp);
/* If the first element is a path, then the result will also /* If the first element is a path, then the result will also
@ -1756,9 +1767,9 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
/* skip canonization of first path, which would only be not /* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type canonized in the first place if it's coming from a ./${foo} type
path */ path */
s.emplace_back( auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first);
state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first)); sSize += part->size();
sSize += s.back().size(); s.emplace_back(std::move(part));
} }
first = false; first = false;
@ -1792,7 +1803,7 @@ void EvalState::forceValueDeep(Value & v)
recurse = [&](Value & v) { recurse = [&](Value & v) {
if (!seen.insert(&v).second) return; if (!seen.insert(&v).second) return;
forceValue(v); forceValue(v, [&]() { return v.determinePos(noPos); });
if (v.type() == nAttrs) { if (v.type() == nAttrs) {
for (auto & i : *v.attrs) for (auto & i : *v.attrs)
@ -1857,7 +1868,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos)
} }
string EvalState::forceString(Value & v, const Pos & pos) std::string_view EvalState::forceString(Value & v, const Pos & pos)
{ {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nString) { if (v.type() != nString) {
@ -1866,7 +1877,7 @@ string EvalState::forceString(Value & v, const Pos & pos)
else else
throwTypeError("value is %1% while a string was expected", v); throwTypeError("value is %1% while a string was expected", v);
} }
return string(v.string.s); return v.string.s;
} }
@ -1901,17 +1912,17 @@ std::vector<std::pair<Path, std::string>> Value::getContext()
} }
string EvalState::forceString(Value & v, PathSet & context, const Pos & pos) std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos)
{ {
string s = forceString(v, pos); auto s = forceString(v, pos);
copyContext(v, context); copyContext(v, context);
return s; return s;
} }
string EvalState::forceStringNoCtx(Value & v, const Pos & pos) std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos)
{ {
string s = forceString(v, pos); auto s = forceString(v, pos);
if (v.string.context) { if (v.string.context) {
if (pos) if (pos)
throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')", throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')",
@ -1929,7 +1940,7 @@ 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); 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;
} }
@ -1942,34 +1953,35 @@ std::optional<string> EvalState::tryAttrsToString(const Pos & pos, Value & v,
if (i != v.attrs->end()) { if (i != v.attrs->end()) {
Value v1; Value v1;
callFunction(*i->value, v, v1, pos); callFunction(*i->value, v, v1, pos);
return coerceToString(pos, v1, context, coerceMore, copyToStore); return coerceToString(pos, v1, context, coerceMore, copyToStore).toOwned();
} }
return {}; return {};
} }
string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context, BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
bool coerceMore, bool copyToStore, bool canonicalizePath) bool coerceMore, bool copyToStore, bool canonicalizePath)
{ {
forceValue(v, pos); forceValue(v, pos);
string s;
if (v.type() == nString) { if (v.type() == nString) {
copyContext(v, context); copyContext(v, context);
return v.string.s; return std::string_view(v.string.s);
} }
if (v.type() == nPath) { if (v.type() == nPath) {
Path path(canonicalizePath ? canonPath(v.path) : v.path); BackedStringView path(PathView(v.path));
return copyToStore ? copyPathToStore(context, path) : path; if (canonicalizePath)
path = canonPath(*path);
if (copyToStore)
path = copyPathToStore(context, std::move(path).toOwned());
return path;
} }
if (v.type() == nAttrs) { if (v.type() == nAttrs) {
auto maybeString = tryAttrsToString(pos, v, context, coerceMore, copyToStore); auto maybeString = tryAttrsToString(pos, v, context, coerceMore, copyToStore);
if (maybeString) { if (maybeString)
return *maybeString; return std::move(*maybeString);
}
auto i = v.attrs->find(sOutPath); auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end()) throwTypeError(pos, "cannot coerce a set to a string"); if (i == v.attrs->end()) throwTypeError(pos, "cannot coerce a set to a string");
return coerceToString(pos, *i->value, context, coerceMore, copyToStore); return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
@ -1991,14 +2003,13 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
if (v.isList()) { if (v.isList()) {
string result; string result;
for (auto [n, v2] : enumerate(v.listItems())) { for (auto [n, v2] : enumerate(v.listItems())) {
result += coerceToString(pos, *v2, result += *coerceToString(pos, *v2, context, coerceMore, copyToStore);
context, coerceMore, copyToStore);
if (n < v.listSize() - 1 if (n < v.listSize() - 1
/* !!! not quite correct */ /* !!! not quite correct */
&& (!v2->isList() || v2->listSize() != 0)) && (!v2->isList() || v2->listSize() != 0))
result += " "; result += " ";
} }
return result; return std::move(result);
} }
} }
@ -2032,7 +2043,7 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context) Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
{ {
string path = coerceToString(pos, v, context, false, false); string path = coerceToString(pos, v, context, false, false).toOwned();
if (path == "" || path[0] != '/') if (path == "" || path[0] != '/')
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path); throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path);
return path; return path;
@ -2041,8 +2052,8 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
bool EvalState::eqValues(Value & v1, Value & v2) bool EvalState::eqValues(Value & v1, Value & v2)
{ {
forceValue(v1); forceValue(v1, noPos);
forceValue(v2); forceValue(v2, noPos);
/* !!! Hack to support some old broken code that relies on pointer /* !!! Hack to support some old broken code that relies on pointer
equality tests between sets. (Specifically, builderDefs calls equality tests between sets. (Specifically, builderDefs calls

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "attr-set.hh" #include "attr-set.hh"
#include "types.hh"
#include "value.hh" #include "value.hh"
#include "nixexpr.hh" #include "nixexpr.hh"
#include "symbol-table.hh" #include "symbol-table.hh"
@ -201,8 +202,8 @@ public:
void resetFileCache(); void resetFileCache();
/* Look up a file in the search path. */ /* Look up a file in the search path. */
Path findFile(const string & path); Path findFile(const std::string_view path);
Path findFile(SearchPath & searchPath, const string & path, const Pos & pos = noPos); Path findFile(SearchPath & searchPath, const std::string_view path, const Pos & 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);
@ -221,7 +222,10 @@ public:
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 = noPos); inline void forceValue(Value & v, const Pos & pos);
template <typename Callable>
inline void forceValue(Value & v, Callable getPos);
/* Force a value, then recursively force list elements and /* Force a value, then recursively force list elements and
attributes. */ attributes. */
@ -231,14 +235,17 @@ public:
NixInt forceInt(Value & v, const Pos & pos); NixInt forceInt(Value & v, const Pos & pos);
NixFloat forceFloat(Value & v, const Pos & pos); NixFloat forceFloat(Value & v, const Pos & pos);
bool forceBool(Value & v, const Pos & pos); bool forceBool(Value & v, const Pos & pos);
inline void forceAttrs(Value & v);
inline void forceAttrs(Value & v, const Pos & pos); void forceAttrs(Value & v, const Pos & pos);
inline void forceList(Value & v);
template <typename Callable>
inline void forceAttrs(Value & v, Callable getPos);
inline void forceList(Value & v, const Pos & pos); inline void forceList(Value & v, const Pos & pos);
void forceFunction(Value & v, const Pos & pos); // either lambda or primop void forceFunction(Value & v, const Pos & pos); // either lambda or primop
string forceString(Value & v, const Pos & pos = noPos); std::string_view forceString(Value & v, const Pos & pos = noPos);
string forceString(Value & v, PathSet & context, const Pos & pos = noPos); std::string_view forceString(Value & v, PathSet & context, const Pos & pos = noPos);
string forceStringNoCtx(Value & v, const Pos & pos = noPos); std::string_view forceStringNoCtx(Value & v, const Pos & pos = noPos);
/* 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"'). */
@ -251,7 +258,7 @@ public:
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. */
string coerceToString(const Pos & pos, Value & v, PathSet & context, BackedStringView coerceToString(const Pos & pos, Value & v, PathSet & context,
bool coerceMore = false, bool copyToStore = true, bool coerceMore = false, bool copyToStore = true,
bool canonicalizePath = true); bool canonicalizePath = true);
@ -309,8 +316,8 @@ private:
friend struct ExprAttrs; friend struct ExprAttrs;
friend struct ExprLet; friend struct ExprLet;
Expr * parse(char * text, size_t length, FileOrigin origin, const Path & path, Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path,
const Path & basePath, StaticEnv & staticEnv); const PathView basePath, StaticEnv & staticEnv);
public: public:

View file

@ -89,11 +89,11 @@ static void expectType(EvalState & state, ValueType type,
static std::map<FlakeId, FlakeInput> parseFlakeInputs( static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const Pos & pos, EvalState & state, Value * value, const Pos & pos,
const std::optional<Path> & baseDir); 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 Pos & pos,
const std::optional<Path> & baseDir) const std::optional<Path> & baseDir, InputPath lockRootPath)
{ {
expectType(state, nAttrs, *value, pos); expectType(state, nAttrs, *value, pos);
@ -117,10 +117,12 @@ static FlakeInput parseFlakeInput(EvalState & state,
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); 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);
input.follows = parseInputPath(attr.value->string.s); auto follows(parseInputPath(attr.value->string.s));
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
input.follows = follows;
} else { } else {
switch (attr.value->type()) { switch (attr.value->type()) {
case nString: case nString:
@ -166,7 +168,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 Pos & pos,
const std::optional<Path> & baseDir) const std::optional<Path> & baseDir, InputPath lockRootPath)
{ {
std::map<FlakeId, FlakeInput> inputs; std::map<FlakeId, FlakeInput> inputs;
@ -178,7 +180,8 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
inputAttr.name, inputAttr.name,
inputAttr.value, inputAttr.value,
*inputAttr.pos, *inputAttr.pos,
baseDir)); baseDir,
lockRootPath));
} }
return inputs; return inputs;
@ -188,7 +191,8 @@ static Flake getFlake(
EvalState & state, EvalState & state,
const FlakeRef & originalRef, const FlakeRef & originalRef,
bool allowLookup, bool allowLookup,
FlakeCache & flakeCache) FlakeCache & flakeCache,
InputPath lockRootPath)
{ {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree( auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, originalRef, allowLookup, flakeCache); state, originalRef, allowLookup, flakeCache);
@ -223,7 +227,7 @@ static Flake getFlake(
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); flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos, flakeDir, lockRootPath);
auto sOutputs = state.symbols.create("outputs"); auto sOutputs = state.symbols.create("outputs");
@ -250,10 +254,12 @@ static Flake getFlake(
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, state.forceStringNoCtx(*setting.value, *setting.pos)}); flake.config.settings.insert({setting.name, 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.insert({setting.name, state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true)}); flake.config.settings.emplace(
setting.name,
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.insert({setting.name, state.forceInt(*setting.value, *setting.pos)});
@ -265,7 +271,7 @@ static Flake getFlake(
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)); setting.name, showType(*setting.value));
ss.push_back(state.forceStringNoCtx(*elem, *setting.pos)); ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos));
} }
flake.config.settings.insert({setting.name, ss}); flake.config.settings.insert({setting.name, ss});
} }
@ -287,6 +293,11 @@ static Flake getFlake(
return flake; return flake;
} }
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup, FlakeCache & flakeCache)
{
return getFlake(state, originalRef, allowLookup, flakeCache, {});
}
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup) Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup)
{ {
FlakeCache flakeCache; FlakeCache flakeCache;
@ -332,22 +343,12 @@ LockedFlake lockFlake(
std::vector<FlakeRef> parents; std::vector<FlakeRef> parents;
struct LockParent {
/* The path to this parent. */
InputPath path;
/* Whether we are currently inside a top-level lockfile
(inputs absolute) or subordinate lockfile (inputs
relative). */
bool absolute;
};
std::function<void( std::function<void(
const FlakeInputs & flakeInputs, const FlakeInputs & flakeInputs,
std::shared_ptr<Node> node, std::shared_ptr<Node> node,
const InputPath & inputPathPrefix, const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode, std::shared_ptr<const Node> oldNode,
const LockParent & parent, const InputPath & lockRootPath,
const Path & parentPath, const Path & parentPath,
bool trustLock)> bool trustLock)>
computeLocks; computeLocks;
@ -357,7 +358,7 @@ LockedFlake lockFlake(
std::shared_ptr<Node> node, std::shared_ptr<Node> node,
const InputPath & inputPathPrefix, const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode, std::shared_ptr<const Node> oldNode,
const LockParent & parent, const InputPath & lockRootPath,
const Path & parentPath, const Path & parentPath,
bool trustLock) bool trustLock)
{ {
@ -402,17 +403,7 @@ LockedFlake lockFlake(
if (input.follows) { if (input.follows) {
InputPath target; InputPath target;
if (parent.absolute && !hasOverride) { target.insert(target.end(), input.follows->begin(), input.follows->end());
target = *input.follows;
} else {
if (hasOverride) {
target = inputPathPrefix;
target.pop_back();
} else
target = parent.path;
for (auto & i : *input.follows) target.push_back(i);
}
debug("input '%s' follows '%s'", inputPathS, printInputPath(target)); debug("input '%s' follows '%s'", inputPathS, printInputPath(target));
node->inputs.insert_or_assign(id, target); node->inputs.insert_or_assign(id, target);
@ -485,23 +476,25 @@ LockedFlake lockFlake(
break; break;
} }
} }
auto absoluteFollows(lockRootPath);
absoluteFollows.insert(absoluteFollows.end(), follows->begin(), follows->end());
fakeInputs.emplace(i.first, FlakeInput { fakeInputs.emplace(i.first, FlakeInput {
.follows = *follows, .follows = absoluteFollows,
}); });
} }
} }
} }
LockParent newParent { auto localPath(parentPath);
.path = inputPath, // If this input is a path, recurse it down.
.absolute = true // This allows us to resolve path inputs relative to the current flake.
}; if ((*input.ref).input.getType() == "path")
localPath = absPath(*input.ref->input.getSourcePath(), parentPath);
computeLocks( computeLocks(
mustRefetch mustRefetch
? getFlake(state, oldLock->lockedRef, false, flakeCache).inputs ? getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath).inputs
: fakeInputs, : fakeInputs,
childNode, inputPath, oldLock, newParent, parentPath, !mustRefetch); childNode, inputPath, oldLock, lockRootPath, parentPath, !mustRefetch);
} else { } else {
/* We need to create a new lock file entry. So fetch /* We need to create a new lock file entry. So fetch
@ -520,7 +513,7 @@ LockedFlake lockFlake(
if (localRef.input.getType() == "path") if (localRef.input.getType() == "path")
localPath = absPath(*input.ref->input.getSourcePath(), parentPath); localPath = absPath(*input.ref->input.getSourcePath(), parentPath);
auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache); auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath);
/* Note: in case of an --override-input, we use /* Note: in case of an --override-input, we use
the *original* ref (input2.ref) for the the *original* ref (input2.ref) for the
@ -541,13 +534,6 @@ LockedFlake lockFlake(
parents.push_back(*input.ref); parents.push_back(*input.ref);
Finally cleanup([&]() { parents.pop_back(); }); Finally cleanup([&]() { parents.pop_back(); });
// Follows paths from existing inputs in the top-level lockfile are absolute,
// whereas paths in subordinate lockfiles are relative to those lockfiles.
LockParent newParent {
.path = inputPath,
.absolute = oldLock ? true : false
};
/* Recursively process the inputs of this /* Recursively process the inputs of this
flake. Also, unless we already have this flake flake. Also, unless we already have this flake
in the top-level lock file, use this flake's in the top-level lock file, use this flake's
@ -558,7 +544,7 @@ LockedFlake lockFlake(
? std::dynamic_pointer_cast<const Node>(oldLock) ? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read( : LockFile::read(
inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root, inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root,
newParent, localPath, false); oldLock ? lockRootPath : inputPath, localPath, false);
} }
else { else {
@ -576,17 +562,12 @@ LockedFlake lockFlake(
} }
}; };
LockParent parent {
.path = {},
.absolute = true
};
// Bring in the current ref for relative path resolution if we have it // Bring in the current ref for relative path resolution if we have it
auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true); auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true);
computeLocks( computeLocks(
flake.inputs, newLockFile.root, {}, flake.inputs, newLockFile.root, {},
lockFlags.recreateLockFile ? nullptr : oldLockFile.root, parent, parentPath, false); lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, parentPath, false);
for (auto & i : lockFlags.inputOverrides) for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first)) if (!overridesUsed.count(i.first))
@ -726,7 +707,7 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va
{ {
state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos); state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos);
auto flakeRefS = state.forceStringNoCtx(*args[0], pos); string flakeRefS(state.forceStringNoCtx(*args[0], pos));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true); auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isImmutable()) if (evalSettings.pureEval && !flakeRef.input.isImmutable())
throw Error("cannot call 'getFlake' on mutable flake reference '%s', at %s (use --impure to override)", flakeRefS, pos); throw Error("cannot call 'getFlake' on mutable flake reference '%s', at %s (use --impure to override)", flakeRefS, pos);

View file

@ -104,10 +104,10 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
/* For each output... */ /* For each output... */
for (auto elem : i->value->listItems()) { for (auto elem : i->value->listItems()) {
/* Evaluate the corresponding set. */ /* Evaluate the corresponding set. */
string name = state->forceStringNoCtx(*elem, *i->pos); string name(state->forceStringNoCtx(*elem, *i->pos));
Bindings::iterator out = attrs->find(state->symbols.create(name)); Bindings::iterator out = attrs->find(state->symbols.create(name));
if (out == attrs->end()) continue; // FIXME: throw error? if (out == attrs->end()) continue; // FIXME: throw error?
state->forceAttrs(*out->value); 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);
@ -172,7 +172,7 @@ StringSet DrvInfo::queryMetaNames()
bool DrvInfo::checkMeta(Value & v) bool DrvInfo::checkMeta(Value & v)
{ {
state->forceValue(v); state->forceValue(v, [&]() { return v.determinePos(noPos); });
if (v.type() == nList) { if (v.type() == nList) {
for (auto elem : v.listItems()) for (auto elem : v.listItems())
if (!checkMeta(*elem)) return false; if (!checkMeta(*elem)) return false;
@ -278,7 +278,7 @@ static bool getDerivation(EvalState & state, Value & v,
bool ignoreAssertionFailures) bool ignoreAssertionFailures)
{ {
try { try {
state.forceValue(v); state.forceValue(v, [&]() { return v.determinePos(noPos); });
if (!state.isDerivation(v)) return true; if (!state.isDerivation(v)) return true;
/* Remove spurious duplicates (e.g., a set like `rec { x = /* Remove spurious duplicates (e.g., a set like `rec { x =

View file

@ -163,7 +163,7 @@ public:
} }
}; };
void parseJSON(EvalState & state, const string & s_, Value & v) void parseJSON(EvalState & state, const std::string_view & s_, Value & v)
{ {
JSONSax parser(state, v); JSONSax parser(state, v);
bool res = json::sax_parse(s_, &parser); bool res = json::sax_parse(s_, &parser);

View file

@ -8,6 +8,6 @@ namespace nix {
MakeError(JSONParseError, EvalError); MakeError(JSONParseError, EvalError);
void parseJSON(EvalState & state, const string & s, Value & v); void parseJSON(EvalState & state, const std::string_view & s, Value & v);
} }

View file

@ -66,7 +66,7 @@ static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
// we make use of the fact that the parser receives a private copy of the input // we make use of the fact that the parser receives a private copy of the input
// string and can munge around in it. // string and can munge around in it.
static Expr * unescapeStr(SymbolTable & symbols, char * s, size_t length) static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length)
{ {
char * result = s; char * result = s;
char * t = s; char * t = s;
@ -89,7 +89,7 @@ static Expr * unescapeStr(SymbolTable & symbols, char * s, size_t length)
else *t = c; else *t = c;
t++; t++;
} }
return new ExprString(symbols.create({result, size_t(t - result)})); return {result, size_t(t - result)};
} }
@ -176,7 +176,7 @@ or { return OR_KW; }
/* It is impossible to match strings ending with '$' with one /* It is impossible to match strings ending with '$' with one
regex because trailing contexts are only valid at the end regex because trailing contexts are only valid at the end
of a rule. (A sane but undocumented limitation.) */ of a rule. (A sane but undocumented limitation.) */
yylval->e = unescapeStr(data->symbols, yytext, yyleng); yylval->str = unescapeStr(data->symbols, yytext, yyleng);
return STR; return STR;
} }
<STRING>\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; } <STRING>\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
@ -191,26 +191,26 @@ or { return OR_KW; }
\'\'(\ *\n)? { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; } \'\'(\ *\n)? { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; }
<IND_STRING>([^\$\']|\$[^\{\']|\'[^\'\$])+ { <IND_STRING>([^\$\']|\$[^\{\']|\'[^\'\$])+ {
yylval->e = new ExprIndStr(yytext); yylval->str = {yytext, (size_t) yyleng, true};
return IND_STR; return IND_STR;
} }
<IND_STRING>\'\'\$ | <IND_STRING>\'\'\$ |
<IND_STRING>\$ { <IND_STRING>\$ {
yylval->e = new ExprIndStr("$"); yylval->str = {"$", 1};
return IND_STR; return IND_STR;
} }
<IND_STRING>\'\'\' { <IND_STRING>\'\'\' {
yylval->e = new ExprIndStr("''"); yylval->str = {"''", 2};
return IND_STR; return IND_STR;
} }
<IND_STRING>\'\'\\{ANY} { <IND_STRING>\'\'\\{ANY} {
yylval->e = unescapeStr(data->symbols, yytext + 2, yyleng - 2); yylval->str = unescapeStr(data->symbols, yytext + 2, yyleng - 2);
return IND_STR; return IND_STR;
} }
<IND_STRING>\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; } <IND_STRING>\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }
<IND_STRING>\'\' { POP_STATE(); return IND_STRING_CLOSE; } <IND_STRING>\'\' { POP_STATE(); return IND_STRING_CLOSE; }
<IND_STRING>\' { <IND_STRING>\' {
yylval->e = new ExprIndStr("'"); yylval->str = {"'", 1};
return IND_STR; return IND_STR;
} }
@ -264,7 +264,7 @@ or { return OR_KW; }
PUSH_STATE(INPATH_SLASH); PUSH_STATE(INPATH_SLASH);
else else
PUSH_STATE(INPATH); PUSH_STATE(INPATH);
yylval->e = new ExprString(data->symbols.create(string(yytext))); yylval->str = {yytext, (size_t) yyleng};
return STR; return STR;
} }
<INPATH>{ANY} | <INPATH>{ANY} |

View file

@ -110,20 +110,13 @@ struct ExprFloat : Expr
struct ExprString : Expr struct ExprString : Expr
{ {
Symbol s; string s;
Value v; Value v;
ExprString(const Symbol & s) : s(s) { v.mkString(s); }; ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); };
COMMON_METHODS COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env); Value * maybeThunk(EvalState & state, Env & env);
}; };
/* Temporary class used during parsing of indented strings. */
struct ExprIndStr : Expr
{
string s;
ExprIndStr(const string & s) : s(s) { };
};
struct ExprPath : Expr struct ExprPath : Expr
{ {
string s; string s;
@ -223,10 +216,25 @@ struct Formal
struct Formals struct Formals
{ {
typedef std::list<Formal> Formals_; typedef std::vector<Formal> Formals_;
Formals_ formals; Formals_ formals;
std::set<Symbol> argNames; // used during parsing
bool ellipsis; bool ellipsis;
bool has(Symbol arg) const {
auto it = std::lower_bound(formals.begin(), formals.end(), arg,
[] (const Formal & f, const Symbol & sym) { return f.name < sym; });
return it != formals.end() && it->name == arg;
}
std::vector<Formal> lexicographicOrder() const
{
std::vector<Formal> result(formals.begin(), formals.end());
std::sort(result.begin(), result.end(),
[] (const Formal & a, const Formal & b) {
return std::string_view(a.name) < std::string_view(b.name);
});
return result;
}
}; };
struct ExprLambda : Expr struct ExprLambda : Expr
@ -239,11 +247,6 @@ struct ExprLambda : Expr
ExprLambda(const Pos & pos, const Symbol & arg, Formals * formals, Expr * body) ExprLambda(const Pos & pos, const Symbol & arg, Formals * formals, Expr * body)
: pos(pos), arg(arg), formals(formals), body(body) : pos(pos), arg(arg), formals(formals), body(body)
{ {
if (!arg.empty() && formals && formals->argNames.find(arg) != formals->argNames.end())
throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", arg),
.errPos = pos
});
}; };
void setName(Symbol & name); void setName(Symbol & name);
string showNamePos() const; string showNamePos() const;
@ -364,15 +367,19 @@ struct StaticEnv
void sort() void sort()
{ {
std::sort(vars.begin(), vars.end(), std::stable_sort(vars.begin(), vars.end(),
[](const Vars::value_type & a, const Vars::value_type & b) { return a.first < b.first; }); [](const Vars::value_type & a, const Vars::value_type & b) { return a.first < b.first; });
} }
void deduplicate() void deduplicate()
{ {
const auto last = std::unique(vars.begin(), vars.end(), auto it = vars.begin(), jt = it, end = vars.end();
[] (const Vars::value_type & a, const Vars::value_type & b) { return a.first == b.first; }); while (jt != end) {
vars.erase(last, vars.end()); *it = *jt++;
while (jt != end && it->first == jt->first) *it = *jt++;
it++;
}
vars.erase(it, end);
} }
Vars::const_iterator find(const Symbol & name) const Vars::const_iterator find(const Symbol & name) const

View file

@ -16,6 +16,8 @@
#ifndef BISON_HEADER #ifndef BISON_HEADER
#define BISON_HEADER #define BISON_HEADER
#include <variant>
#include "util.hh" #include "util.hh"
#include "nixexpr.hh" #include "nixexpr.hh"
@ -39,8 +41,22 @@ namespace nix {
{ }; { };
}; };
struct ParserFormals {
std::vector<Formal> formals;
bool ellipsis = false;
};
} }
// using C a struct allows us to avoid having to define the special
// members that using string_view here would implicitly delete.
struct StringToken {
const char * p;
size_t l;
bool hasIndentation;
operator std::string_view() const { return {p, l}; }
};
#define YY_DECL int yylex \ #define YY_DECL int yylex \
(YYSTYPE * yylval_param, YYLTYPE * yylloc_param, yyscan_t yyscanner, nix::ParseData * data) (YYSTYPE * yylval_param, YYLTYPE * yylloc_param, yyscan_t yyscanner, nix::ParseData * data)
@ -140,21 +156,46 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
} }
static void addFormal(const Pos & pos, Formals * formals, const Formal & formal) static Formals * toFormals(ParseData & data, ParserFormals * formals,
Pos pos = noPos, Symbol arg = {})
{ {
if (!formals->argNames.insert(formal.name).second) std::sort(formals->formals.begin(), formals->formals.end(),
[] (const auto & a, const auto & b) {
return std::tie(a.name, a.pos) < std::tie(b.name, b.pos);
});
std::optional<std::pair<Symbol, Pos>> duplicate;
for (size_t i = 0; i + 1 < formals->formals.size(); i++) {
if (formals->formals[i].name != formals->formals[i + 1].name)
continue;
std::pair thisDup{formals->formals[i].name, formals->formals[i + 1].pos};
duplicate = std::min(thisDup, duplicate.value_or(thisDup));
}
if (duplicate)
throw ParseError({ throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", .msg = hintfmt("duplicate formal function argument '%1%'", duplicate->first),
formal.name), .errPos = duplicate->second
});
Formals result;
result.ellipsis = formals->ellipsis;
result.formals = std::move(formals->formals);
if (arg.set() && result.has(arg))
throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", arg),
.errPos = pos .errPos = pos
}); });
formals->formals.push_front(formal);
delete formals;
return new Formals(std::move(result));
} }
static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, vector<std::pair<Pos, Expr *> > & es) static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
vector<std::pair<Pos, std::variant<Expr *, StringToken> > > & es)
{ {
if (es.empty()) return new ExprString(symbols.create("")); if (es.empty()) return new ExprString("");
/* Figure out the minimum indentation. Note that by design /* Figure out the minimum indentation. Note that by design
whitespace-only final lines are not taken into account. (So whitespace-only final lines are not taken into account. (So
@ -163,20 +204,20 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, vector<st
size_t minIndent = 1000000; size_t minIndent = 1000000;
size_t curIndent = 0; size_t curIndent = 0;
for (auto & [i_pos, i] : es) { for (auto & [i_pos, i] : es) {
ExprIndStr * e = dynamic_cast<ExprIndStr *>(i); auto * str = std::get_if<StringToken>(&i);
if (!e) { if (!str || !str->hasIndentation) {
/* Anti-quotations end the current start-of-line whitespace. */ /* Anti-quotations and escaped characters end the current start-of-line whitespace. */
if (atStartOfLine) { if (atStartOfLine) {
atStartOfLine = false; atStartOfLine = false;
if (curIndent < minIndent) minIndent = curIndent; if (curIndent < minIndent) minIndent = curIndent;
} }
continue; continue;
} }
for (size_t j = 0; j < e->s.size(); ++j) { for (size_t j = 0; j < str->l; ++j) {
if (atStartOfLine) { if (atStartOfLine) {
if (e->s[j] == ' ') if (str->p[j] == ' ')
curIndent++; curIndent++;
else if (e->s[j] == '\n') { else if (str->p[j] == '\n') {
/* Empty line, doesn't influence minimum /* Empty line, doesn't influence minimum
indentation. */ indentation. */
curIndent = 0; curIndent = 0;
@ -184,7 +225,7 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, vector<st
atStartOfLine = false; atStartOfLine = false;
if (curIndent < minIndent) minIndent = curIndent; if (curIndent < minIndent) minIndent = curIndent;
} }
} else if (e->s[j] == '\n') { } else if (str->p[j] == '\n') {
atStartOfLine = true; atStartOfLine = true;
curIndent = 0; curIndent = 0;
} }
@ -196,33 +237,31 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, vector<st
atStartOfLine = true; atStartOfLine = true;
size_t curDropped = 0; size_t curDropped = 0;
size_t n = es.size(); size_t n = es.size();
for (vector<std::pair<Pos, Expr *> >::iterator i = es.begin(); i != es.end(); ++i, --n) { auto i = es.begin();
ExprIndStr * e = dynamic_cast<ExprIndStr *>(i->second); const auto trimExpr = [&] (Expr * e) {
if (!e) {
atStartOfLine = false; atStartOfLine = false;
curDropped = 0; curDropped = 0;
es2->push_back(*i); es2->emplace_back(i->first, e);
continue; };
} const auto trimString = [&] (const StringToken & t) {
string s2; string s2;
for (size_t j = 0; j < e->s.size(); ++j) { for (size_t j = 0; j < t.l; ++j) {
if (atStartOfLine) { if (atStartOfLine) {
if (e->s[j] == ' ') { if (t.p[j] == ' ') {
if (curDropped++ >= minIndent) if (curDropped++ >= minIndent)
s2 += e->s[j]; s2 += t.p[j];
} }
else if (e->s[j] == '\n') { else if (t.p[j] == '\n') {
curDropped = 0; curDropped = 0;
s2 += e->s[j]; s2 += t.p[j];
} else { } else {
atStartOfLine = false; atStartOfLine = false;
curDropped = 0; curDropped = 0;
s2 += e->s[j]; s2 += t.p[j];
} }
} else { } else {
s2 += e->s[j]; s2 += t.p[j];
if (e->s[j] == '\n') atStartOfLine = true; if (t.p[j] == '\n') atStartOfLine = true;
} }
} }
@ -234,7 +273,10 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols, vector<st
s2 = string(s2, 0, p + 1); s2 = string(s2, 0, p + 1);
} }
es2->emplace_back(i->first, new ExprString(symbols.create(s2))); es2->emplace_back(i->first, new ExprString(s2));
};
for (; i != es.end(); ++i, --n) {
std::visit(overloaded { trimExpr, trimString }, i->second);
} }
/* If this is a single string, then don't do a concatenation. */ /* If this is a single string, then don't do a concatenation. */
@ -269,22 +311,17 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
nix::Expr * e; nix::Expr * e;
nix::ExprList * list; nix::ExprList * list;
nix::ExprAttrs * attrs; nix::ExprAttrs * attrs;
nix::Formals * formals; nix::ParserFormals * formals;
nix::Formal * formal; nix::Formal * formal;
nix::NixInt n; nix::NixInt n;
nix::NixFloat nf; nix::NixFloat nf;
// using C a struct allows us to avoid having to define the special
// members that using string_view here would implicitly delete.
struct StringToken {
const char * p;
size_t l;
operator std::string_view() const { return {p, l}; }
};
StringToken id; // !!! -> Symbol StringToken id; // !!! -> Symbol
StringToken path; StringToken path;
StringToken uri; StringToken uri;
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::Pos, nix::Expr *> > * string_parts;
std::vector<std::pair<nix::Pos, 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
@ -294,11 +331,12 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
%type <formals> formals %type <formals> formals
%type <formal> formal %type <formal> formal
%type <attrNames> attrs attrpath %type <attrNames> attrs attrpath
%type <string_parts> string_parts_interpolated ind_string_parts %type <string_parts> string_parts_interpolated
%type <ind_string_parts> ind_string_parts
%type <e> path_start string_parts string_attr %type <e> path_start string_parts string_attr
%type <id> attr %type <id> attr
%token <id> ID ATTRPATH %token <id> ID ATTRPATH
%token <e> STR IND_STR %token <str> STR IND_STR
%token <n> INT %token <n> INT
%token <nf> FLOAT %token <nf> FLOAT
%token <path> PATH HPATH SPATH PATH_END %token <path> PATH HPATH SPATH PATH_END
@ -331,11 +369,17 @@ 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(""), $2, $5); } { $$ = new ExprLambda(CUR_POS, data->symbols.create(""), toFormals(*data, $2), $5); }
| '{' formals '}' '@' ID ':' expr_function | '{' formals '}' '@' ID ':' expr_function
{ $$ = new ExprLambda(CUR_POS, data->symbols.create($5), $2, $7); } {
Symbol arg = data->symbols.create($5);
$$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $2, CUR_POS, arg), $7);
}
| ID '@' '{' formals '}' ':' expr_function | ID '@' '{' formals '}' ':' expr_function
{ $$ = new ExprLambda(CUR_POS, data->symbols.create($1), $4, $7); } {
Symbol arg = data->symbols.create($1);
$$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $4, CUR_POS, arg), $7);
}
| ASSERT expr ';' expr_function | ASSERT expr ';' expr_function
{ $$ = new ExprAssert(CUR_POS, $2, $4); } { $$ = new ExprAssert(CUR_POS, $2, $4); }
| WITH expr ';' expr_function | WITH expr ';' expr_function
@ -426,7 +470,7 @@ expr_simple
$$ = new ExprCall(CUR_POS, $$ = new ExprCall(CUR_POS,
new ExprVar(data->symbols.create("__findFile")), new ExprVar(data->symbols.create("__findFile")),
{new ExprVar(data->symbols.create("__nixPath")), {new ExprVar(data->symbols.create("__nixPath")),
new ExprString(data->symbols.create(path))}); new ExprString(path)});
} }
| URI { | URI {
static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals); static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals);
@ -435,7 +479,7 @@ expr_simple
.msg = hintfmt("URL literals are disabled"), .msg = hintfmt("URL literals are disabled"),
.errPos = CUR_POS .errPos = CUR_POS
}); });
$$ = new ExprString(data->symbols.create($1)); $$ = new ExprString(string($1));
} }
| '(' expr ')' { $$ = $2; } | '(' expr ')' { $$ = $2; }
/* Let expressions `let {..., body = ...}' are just desugared /* Let expressions `let {..., body = ...}' are just desugared
@ -450,18 +494,19 @@ expr_simple
; ;
string_parts string_parts
: STR : STR { $$ = new ExprString(string($1)); }
| string_parts_interpolated { $$ = new ExprConcatStrings(CUR_POS, true, $1); } | string_parts_interpolated { $$ = new ExprConcatStrings(CUR_POS, true, $1); }
| { $$ = new ExprString(data->symbols.create("")); } | { $$ = new ExprString(""); }
; ;
string_parts_interpolated string_parts_interpolated
: string_parts_interpolated STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $2); } : string_parts_interpolated STR
{ $$ = $1; $1->emplace_back(makeCurPos(@2, data), new ExprString(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 vector<std::pair<Pos, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); } | DOLLAR_CURLY expr '}' { $$ = new vector<std::pair<Pos, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); }
| STR DOLLAR_CURLY expr '}' { | STR DOLLAR_CURLY expr '}' {
$$ = new vector<std::pair<Pos, Expr *> >; $$ = new vector<std::pair<Pos, Expr *> >;
$$->emplace_back(makeCurPos(@1, data), $1); $$->emplace_back(makeCurPos(@1, data), new ExprString(string($1)));
$$->emplace_back(makeCurPos(@2, data), $3); $$->emplace_back(makeCurPos(@2, data), $3);
} }
; ;
@ -483,7 +528,7 @@ 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 vector<std::pair<Pos, Expr *> >; } | { $$ = new vector<std::pair<Pos, std::variant<Expr *, StringToken> > >; }
; ;
binds binds
@ -515,7 +560,7 @@ attrs
{ $$ = $1; { $$ = $1;
ExprString * str = dynamic_cast<ExprString *>($2); ExprString * str = dynamic_cast<ExprString *>($2);
if (str) { if (str) {
$$->push_back(AttrName(str->s)); $$->push_back(AttrName(data->symbols.create(str->s)));
delete str; delete str;
} else } else
throw ParseError({ throw ParseError({
@ -532,7 +577,7 @@ attrpath
{ $$ = $1; { $$ = $1;
ExprString * str = dynamic_cast<ExprString *>($3); ExprString * str = dynamic_cast<ExprString *>($3);
if (str) { if (str) {
$$->push_back(AttrName(str->s)); $$->push_back(AttrName(data->symbols.create(str->s)));
delete str; delete str;
} else } else
$$->push_back(AttrName($3)); $$->push_back(AttrName($3));
@ -542,7 +587,7 @@ attrpath
{ $$ = new vector<AttrName>; { $$ = new vector<AttrName>;
ExprString *str = dynamic_cast<ExprString *>($1); ExprString *str = dynamic_cast<ExprString *>($1);
if (str) { if (str) {
$$->push_back(AttrName(str->s)); $$->push_back(AttrName(data->symbols.create(str->s)));
delete str; delete str;
} else } else
$$->push_back(AttrName($1)); $$->push_back(AttrName($1));
@ -566,13 +611,13 @@ expr_list
formals formals
: formal ',' formals : formal ',' formals
{ $$ = $3; addFormal(CUR_POS, $$, *$1); } { $$ = $3; $$->formals.push_back(*$1); }
| formal | formal
{ $$ = new Formals; addFormal(CUR_POS, $$, *$1); $$->ellipsis = false; } { $$ = new ParserFormals; $$->formals.push_back(*$1); $$->ellipsis = false; }
| |
{ $$ = new Formals; $$->ellipsis = false; } { $$ = new ParserFormals; $$->ellipsis = false; }
| ELLIPSIS | ELLIPSIS
{ $$ = new Formals; $$->ellipsis = true; } { $$ = new ParserFormals; $$->ellipsis = true; }
; ;
formal formal
@ -598,7 +643,7 @@ namespace nix {
Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
const Path & path, const Path & basePath, StaticEnv & staticEnv) const PathView path, const PathView basePath, StaticEnv & staticEnv)
{ {
yyscan_t scanner; yyscan_t scanner;
ParseData data(*this); ParseData data(*this);
@ -709,24 +754,24 @@ void EvalState::addToSearchPath(const string & s)
} }
Path EvalState::findFile(const string & path) Path EvalState::findFile(const std::string_view path)
{ {
return findFile(searchPath, path); return findFile(searchPath, path);
} }
Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos & pos) Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const Pos & pos)
{ {
for (auto & i : searchPath) { for (auto & i : searchPath) {
std::string suffix; std::string suffix;
if (i.first.empty()) if (i.first.empty())
suffix = "/" + path; suffix = concatStrings("/", path);
else { else {
auto s = i.first.size(); auto s = i.first.size();
if (path.compare(0, s, i.first) != 0 || if (path.compare(0, s, i.first) != 0 ||
(path.size() > s && path[s] != '/')) (path.size() > s && path[s] != '/'))
continue; continue;
suffix = path.size() == s ? "" : "/" + string(path, s); suffix = path.size() == s ? "" : concatStrings("/", path.substr(s));
} }
auto r = resolveSearchPathElem(i); auto r = resolveSearchPathElem(i);
if (!r.first) continue; if (!r.first) continue;
@ -735,7 +780,7 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos
} }
if (hasPrefix(path, "nix/")) if (hasPrefix(path, "nix/"))
return corepkgsPrefix + path.substr(4); return concatStrings(corepkgsPrefix, path.substr(4));
throw ThrownError({ throw ThrownError({
.msg = hintfmt(evalSettings.pureEval .msg = hintfmt(evalSettings.pureEval

View file

@ -92,8 +92,6 @@ StringMap EvalState::realiseContext(const PathSet & context)
} }
struct RealisePathFlags { struct RealisePathFlags {
// Whether to check whether the path is a valid absolute path
bool requireAbsolutePath = true;
// Whether to check that the path is allowed in pure eval mode // Whether to check that the path is allowed in pure eval mode
bool checkForPureEval = true; bool checkForPureEval = true;
}; };
@ -105,9 +103,7 @@ static Path realisePath(EvalState & state, const Pos & pos, Value & v, const Rea
auto path = [&]() auto path = [&]()
{ {
try { try {
return flags.requireAbsolutePath return state.coerceToPath(pos, v, context);
? state.coerceToPath(pos, v, context)
: state.coerceToString(pos, v, context, false, false);
} catch (Error & e) { } catch (Error & e) {
e.addTrace(pos, "while realising the context of a path"); e.addTrace(pos, "while realising the context of a path");
throw; throw;
@ -214,7 +210,7 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
if (!vScope) if (!vScope)
state.evalFile(path, v); state.evalFile(path, v);
else { else {
state.forceAttrs(*vScope); state.forceAttrs(*vScope, pos);
Env * env = &state.allocEnv(vScope->attrs->size()); Env * env = &state.allocEnv(vScope->attrs->size());
env->up = &state.baseEnv; env->up = &state.baseEnv;
@ -318,7 +314,7 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
{ {
auto path = realisePath(state, pos, *args[0]); auto path = realisePath(state, pos, *args[0]);
string sym = state.forceStringNoCtx(*args[1], pos); string sym(state.forceStringNoCtx(*args[1], pos));
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle) if (!handle)
@ -354,10 +350,11 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
}); });
} }
PathSet context; PathSet context;
auto program = state.coerceToString(pos, *elems[0], context, false, false); auto program = state.coerceToString(pos, *elems[0], context, false, false).toOwned();
Strings commandArgs; Strings commandArgs;
for (unsigned int i = 1; i < args[0]->listSize(); ++i) for (unsigned int i = 1; i < args[0]->listSize(); ++i) {
commandArgs.emplace_back(state.coerceToString(pos, *elems[i], context, false, false)); commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false).toOwned());
}
try { try {
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
} catch (InvalidPathError & e) { } catch (InvalidPathError & e) {
@ -710,7 +707,7 @@ static RegisterPrimOp primop_abort({
.fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
string s = state.coerceToString(pos, *args[0], context); string s = state.coerceToString(pos, *args[0], context).toOwned();
throw Abort("evaluation aborted with the following error message: '%1%'", s); throw Abort("evaluation aborted with the following error message: '%1%'", s);
} }
}); });
@ -728,7 +725,7 @@ static RegisterPrimOp primop_throw({
.fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v) .fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
string s = state.coerceToString(pos, *args[0], context); string s = state.coerceToString(pos, *args[0], context).toOwned();
throw ThrownError(s); throw ThrownError(s);
} }
}); });
@ -740,7 +737,7 @@ static void prim_addErrorContext(EvalState & state, const Pos & pos, Value * * a
v = *args[1]; v = *args[1];
} catch (Error & e) { } catch (Error & e) {
PathSet context; PathSet context;
e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context)); e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context).toOwned());
throw; throw;
} }
} }
@ -829,7 +826,7 @@ static RegisterPrimOp primop_tryEval({
/* Return an environment variable. Use with care. */ /* Return an environment variable. Use with care. */
static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string name = state.forceStringNoCtx(*args[0], pos); string name(state.forceStringNoCtx(*args[0], pos));
v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or("")); v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or(""));
} }
@ -979,7 +976,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
const string & key = i->name; const string & key = i->name;
vomit("processing attribute '%1%'", key); vomit("processing attribute '%1%'", key);
auto handleHashMode = [&](const std::string & s) { auto handleHashMode = [&](const std::string_view s) {
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive; if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat; else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
else else
@ -1034,7 +1031,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
else if (i->name == state.sArgs) { else if (i->name == state.sArgs) {
state.forceList(*i->value, pos); state.forceList(*i->value, pos);
for (auto elem : i->value->listItems()) { for (auto elem : i->value->listItems()) {
string s = state.coerceToString(posDrvName, *elem, context, true); string s = state.coerceToString(posDrvName, *elem, context, true).toOwned();
drv.args.push_back(s); drv.args.push_back(s);
} }
} }
@ -1070,7 +1067,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
} }
} else { } else {
auto s = state.coerceToString(*i->pos, *i->value, context, true); auto s = state.coerceToString(*i->pos, *i->value, context, true).toOwned();
drv.env.emplace(key, s); drv.env.emplace(key, s);
if (i->name == state.sBuilder) drv.builder = std::move(s); if (i->name == state.sBuilder) drv.builder = std::move(s);
else if (i->name == state.sSystem) drv.platform = std::move(s); else if (i->name == state.sSystem) drv.platform = std::move(s);
@ -1183,7 +1180,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
drv.outputs.insert_or_assign(i, DerivationOutput { drv.outputs.insert_or_assign(i, DerivationOutput {
.output = DerivationOutputCAFloating { .output = DerivationOutputCAFloating {
.method = ingestionMethod, .method = ingestionMethod,
.hashType = std::move(ht), .hashType = ht,
}, },
}); });
} }
@ -1273,7 +1270,7 @@ static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info {
substituted by the corresponding output path at build time. For substituted by the corresponding output path at build time. For
example, 'placeholder "out"' returns the string example, 'placeholder "out"' returns the string
/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9. At build /1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9. At build
time, any occurence of this string in an derivation attribute will time, any occurrence of this string in an derivation attribute will
be replaced with the concrete path in the Nix store of the output be replaced with the concrete path in the Nix store of the output
out. */ out. */
static void prim_placeholder(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_placeholder(EvalState & state, const Pos & pos, Value * * args, Value & v)
@ -1403,7 +1400,7 @@ static RegisterPrimOp primop_pathExists({
static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
v.mkString(baseNameOf(state.coerceToString(pos, *args[0], context, false, false)), context); v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false)), context);
} }
static RegisterPrimOp primop_baseNameOf({ static RegisterPrimOp primop_baseNameOf({
@ -1423,7 +1420,8 @@ static RegisterPrimOp primop_baseNameOf({
static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
Path dir = dirOf(state.coerceToString(pos, *args[0], context, false, false)); auto path = state.coerceToString(pos, *args[0], context, false, false);
auto dir = dirOf(*path);
if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context); if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context);
} }
@ -1489,12 +1487,24 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
pos pos
); );
auto path = realisePath(state, pos, *i->value, { .requireAbsolutePath = false }); PathSet context;
string path = state.coerceToString(pos, *i->value, context, false, false).toOwned();
try {
auto rewrites = state.realiseContext(context);
path = rewriteStrings(path, rewrites);
} catch (InvalidPathError & e) {
throw EvalError({
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
.errPos = pos
});
}
searchPath.emplace_back(prefix, path); searchPath.emplace_back(prefix, path);
} }
string path = state.forceStringNoCtx(*args[1], pos); auto path = state.forceStringNoCtx(*args[1], pos);
v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos))); v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos)));
} }
@ -1508,7 +1518,7 @@ static RegisterPrimOp primop_findFile(RegisterPrimOp::Info {
/* Return the cryptographic hash of a file in base-16. */ /* Return the cryptographic hash of a file in base-16. */
static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string type = state.forceStringNoCtx(*args[0], pos); auto type = state.forceStringNoCtx(*args[0], pos);
std::optional<HashType> ht = parseHashType(type); std::optional<HashType> ht = parseHashType(type);
if (!ht) if (!ht)
throw Error({ throw Error({
@ -1715,7 +1725,7 @@ static RegisterPrimOp primop_toJSON({
/* Parse a JSON string to a value. */ /* Parse a JSON string to a value. */
static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string s = state.forceStringNoCtx(*args[0], pos); auto s = state.forceStringNoCtx(*args[0], pos);
try { try {
parseJSON(state, s, v); parseJSON(state, s, v);
} catch (JSONParseError &e) { } catch (JSONParseError &e) {
@ -1744,8 +1754,8 @@ static RegisterPrimOp primop_fromJSON({
static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
string name = state.forceStringNoCtx(*args[0], pos); string name(state.forceStringNoCtx(*args[0], pos));
string contents = state.forceString(*args[1], context, pos); string contents(state.forceString(*args[1], context, pos));
StorePathSet refs; StorePathSet refs;
@ -2145,7 +2155,7 @@ static RegisterPrimOp primop_attrValues({
/* Dynamic version of the `.' operator. */ /* Dynamic version of the `.' operator. */
void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string attr = state.forceStringNoCtx(*args[0], pos); auto attr = state.forceStringNoCtx(*args[0], pos);
state.forceAttrs(*args[1], pos); state.forceAttrs(*args[1], pos);
Bindings::iterator i = getAttr( Bindings::iterator i = getAttr(
state, state,
@ -2175,7 +2185,7 @@ static RegisterPrimOp primop_getAttr({
/* Return position information of the specified attribute. */ /* Return position information of the specified attribute. */
static void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_unsafeGetAttrPos(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string attr = state.forceStringNoCtx(*args[0], pos); auto attr = state.forceStringNoCtx(*args[0], pos);
state.forceAttrs(*args[1], pos); state.forceAttrs(*args[1], pos);
Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr)); Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr));
if (i == args[1]->attrs->end()) if (i == args[1]->attrs->end())
@ -2193,7 +2203,7 @@ static RegisterPrimOp primop_unsafeGetAttrPos(RegisterPrimOp::Info {
/* Dynamic version of the `?' operator. */ /* Dynamic version of the `?' operator. */
static void prim_hasAttr(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_hasAttr(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string attr = state.forceStringNoCtx(*args[0], pos); auto attr = state.forceStringNoCtx(*args[0], pos);
state.forceAttrs(*args[1], pos); state.forceAttrs(*args[1], pos);
v.mkBool(args[1]->attrs->find(state.symbols.create(attr)) != args[1]->attrs->end()); v.mkBool(args[1]->attrs->find(state.symbols.create(attr)) != args[1]->attrs->end());
} }
@ -2271,7 +2281,7 @@ static RegisterPrimOp primop_removeAttrs({
/* Builds a set from a list specifying (name, value) pairs. To be /* Builds a set from a list specifying (name, value) pairs. To be
precise, a list [{name = "name1"; value = value1;} ... {name = precise, a list [{name = "name1"; value = value1;} ... {name =
"nameN"; value = valueN;}] is transformed to {name1 = value1; "nameN"; value = valueN;}] is transformed to {name1 = value1;
... nameN = valueN;}. In case of duplicate occurences of the same ... nameN = valueN;}. In case of duplicate occurrences of the same
name, the first takes precedence. */ name, the first takes precedence. */
static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
@ -2292,7 +2302,7 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args,
pos pos
); );
string name = state.forceStringNoCtx(*j->value, *j->pos); auto name = state.forceStringNoCtx(*j->value, *j->pos);
Symbol sym = state.symbols.create(name); Symbol sym = state.symbols.create(name);
if (seen.insert(sym).second) { if (seen.insert(sym).second) {
@ -2492,7 +2502,7 @@ static void prim_zipAttrsWith(EvalState & state, const Pos & pos, Value * * args
for (unsigned int n = 0; n < listSize; ++n) { for (unsigned int n = 0; n < listSize; ++n) {
Value * vElem = listElems[n]; Value * vElem = listElems[n];
try { try {
state.forceAttrs(*vElem); state.forceAttrs(*vElem, noPos);
for (auto & attr : *vElem->attrs) for (auto & attr : *vElem->attrs)
attrsSeen[attr.name].first++; attrsSeen[attr.name].first++;
} catch (TypeError & e) { } catch (TypeError & e) {
@ -3024,7 +3034,7 @@ static void prim_groupBy(EvalState & state, const Pos & pos, Value * * args, Val
for (auto vElem : args[1]->listItems()) { for (auto vElem : args[1]->listItems()) {
Value res; Value res;
state.callFunction(*args[0], *vElem, res, pos); state.callFunction(*args[0], *vElem, res, pos);
string name = state.forceStringNoCtx(res, pos); auto name = state.forceStringNoCtx(res, pos);
Symbol sym = state.symbols.create(name); Symbol sym = state.symbols.create(name);
auto vector = attrs.try_emplace(sym, ValueVector()).first; auto vector = attrs.try_emplace(sym, ValueVector()).first;
vector->second.push_back(vElem); vector->second.push_back(vElem);
@ -3280,8 +3290,8 @@ static RegisterPrimOp primop_lessThan({
static void prim_toString(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_toString(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
string s = state.coerceToString(pos, *args[0], context, true, false); auto s = state.coerceToString(pos, *args[0], context, true, false);
v.mkString(s, context); v.mkString(*s, context);
} }
static RegisterPrimOp primop_toString({ static RegisterPrimOp primop_toString({
@ -3317,7 +3327,7 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V
int start = state.forceInt(*args[0], pos); int start = state.forceInt(*args[0], pos);
int len = state.forceInt(*args[1], pos); int len = state.forceInt(*args[1], pos);
PathSet context; PathSet context;
string s = state.coerceToString(pos, *args[2], context); auto s = state.coerceToString(pos, *args[2], context);
if (start < 0) if (start < 0)
throw EvalError({ throw EvalError({
@ -3325,7 +3335,7 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V
.errPos = pos .errPos = pos
}); });
v.mkString((unsigned int) start >= s.size() ? "" : string(s, start, len), context); v.mkString((unsigned int) start >= s->size() ? "" : s->substr(start, len), context);
} }
static RegisterPrimOp primop_substring({ static RegisterPrimOp primop_substring({
@ -3351,8 +3361,8 @@ static RegisterPrimOp primop_substring({
static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
string s = state.coerceToString(pos, *args[0], context); auto s = state.coerceToString(pos, *args[0], context);
v.mkInt(s.size()); v.mkInt(s->size());
} }
static RegisterPrimOp primop_stringLength({ static RegisterPrimOp primop_stringLength({
@ -3368,7 +3378,7 @@ static RegisterPrimOp primop_stringLength({
/* Return the cryptographic hash of a string in base-16. */ /* Return the cryptographic hash of a string in base-16. */
static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string type = state.forceStringNoCtx(*args[0], pos); auto type = state.forceStringNoCtx(*args[0], pos);
std::optional<HashType> ht = parseHashType(type); std::optional<HashType> ht = parseHashType(type);
if (!ht) if (!ht)
throw Error({ throw Error({
@ -3377,7 +3387,7 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args,
}); });
PathSet context; // discarded PathSet context; // discarded
string s = state.forceString(*args[1], context, pos); auto s = state.forceString(*args[1], context, pos);
v.mkString(hashString(*ht, s).to_string(Base16, false)); v.mkString(hashString(*ht, s).to_string(Base16, false));
} }
@ -3395,7 +3405,18 @@ static RegisterPrimOp primop_hashString({
struct RegexCache struct RegexCache
{ {
std::unordered_map<std::string, std::regex> cache; // TODO use C++20 transparent comparison when available
std::unordered_map<std::string_view, std::regex> cache;
std::list<std::string> keys;
std::regex get(std::string_view re)
{
auto it = cache.find(re);
if (it != cache.end())
return it->second;
keys.emplace_back(re);
return cache.emplace(keys.back(), std::regex(keys.back(), std::regex::extended)).first->second;
}
}; };
std::shared_ptr<RegexCache> makeRegexCache() std::shared_ptr<RegexCache> makeRegexCache()
@ -3409,15 +3430,13 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v)
try { try {
auto regex = state.regexCache->cache.find(re); auto regex = state.regexCache->get(re);
if (regex == state.regexCache->cache.end())
regex = state.regexCache->cache.emplace(re, std::regex(re, std::regex::extended)).first;
PathSet context; PathSet context;
const std::string str = state.forceString(*args[1], context, pos); const auto str = state.forceString(*args[1], context, pos);
std::smatch match; std::cmatch match;
if (!std::regex_match(str, match, regex->second)) { if (!std::regex_match(str.begin(), str.end(), match, regex)) {
v.mkNull(); v.mkNull();
return; return;
} }
@ -3492,15 +3511,13 @@ void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v)
try { try {
auto regex = state.regexCache->cache.find(re); auto regex = state.regexCache->get(re);
if (regex == state.regexCache->cache.end())
regex = state.regexCache->cache.emplace(re, std::regex(re, std::regex::extended)).first;
PathSet context; PathSet context;
const std::string str = state.forceString(*args[1], context, pos); const auto str = state.forceString(*args[1], context, pos);
auto begin = std::sregex_iterator(str.begin(), str.end(), regex->second); auto begin = std::cregex_iterator(str.begin(), str.end(), regex);
auto end = std::sregex_iterator(); auto end = std::cregex_iterator();
// Any matches results are surrounded by non-matching results. // Any matches results are surrounded by non-matching results.
const size_t len = std::distance(begin, end); const size_t len = std::distance(begin, end);
@ -3512,9 +3529,9 @@ void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v)
return; return;
} }
for (std::sregex_iterator i = begin; i != end; ++i) { for (auto i = begin; i != end; ++i) {
assert(idx <= 2 * len + 1 - 3); assert(idx <= 2 * len + 1 - 3);
std::smatch match = *i; auto match = *i;
// Add a string for non-matched characters. // Add a string for non-matched characters.
(v.listElems()[idx++] = state.allocValue())->mkString(match.prefix().str()); (v.listElems()[idx++] = state.allocValue())->mkString(match.prefix().str());
@ -3605,7 +3622,7 @@ static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * *
for (auto elem : args[1]->listItems()) { for (auto elem : args[1]->listItems()) {
if (first) first = false; else res += sep; if (first) first = false; else res += sep;
res += state.coerceToString(pos, *elem, context); res += *state.coerceToString(pos, *elem, context);
} }
v.mkString(res, context); v.mkString(res, context);
@ -3635,14 +3652,14 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar
vector<string> from; vector<string> from;
from.reserve(args[0]->listSize()); from.reserve(args[0]->listSize());
for (auto elem : args[0]->listItems()) for (auto elem : args[0]->listItems())
from.push_back(state.forceString(*elem, pos)); from.emplace_back(state.forceString(*elem, pos));
vector<std::pair<string, PathSet>> to; vector<std::pair<string, PathSet>> to;
to.reserve(args[1]->listSize()); to.reserve(args[1]->listSize());
for (auto elem : args[1]->listItems()) { for (auto elem : args[1]->listItems()) {
PathSet ctx; PathSet ctx;
auto s = state.forceString(*elem, ctx, pos); auto s = state.forceString(*elem, ctx, pos);
to.push_back(std::make_pair(std::move(s), std::move(ctx))); to.emplace_back(s, std::move(ctx));
} }
PathSet context; PathSet context;
@ -3704,7 +3721,7 @@ static RegisterPrimOp primop_replaceStrings({
static void prim_parseDrvName(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_parseDrvName(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string name = state.forceStringNoCtx(*args[0], pos); auto name = state.forceStringNoCtx(*args[0], pos);
DrvName parsed(name); DrvName parsed(name);
auto attrs = state.buildBindings(2); auto attrs = state.buildBindings(2);
attrs.alloc(state.sName).mkString(parsed.name); attrs.alloc(state.sName).mkString(parsed.name);
@ -3728,8 +3745,8 @@ static RegisterPrimOp primop_parseDrvName({
static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string version1 = state.forceStringNoCtx(*args[0], pos); auto version1 = state.forceStringNoCtx(*args[0], pos);
string version2 = state.forceStringNoCtx(*args[1], pos); auto version2 = state.forceStringNoCtx(*args[1], pos);
v.mkInt(compareVersions(version1, version2)); v.mkInt(compareVersions(version1, version2));
} }
@ -3748,14 +3765,14 @@ static RegisterPrimOp primop_compareVersions({
static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
string version = state.forceStringNoCtx(*args[0], pos); auto version = state.forceStringNoCtx(*args[0], pos);
auto iter = version.cbegin(); auto iter = version.cbegin();
Strings components; Strings components;
while (iter != version.cend()) { while (iter != version.cend()) {
auto component = nextComponent(iter, version.cend()); auto component = nextComponent(iter, version.cend());
if (component.empty()) if (component.empty())
break; break;
components.emplace_back(std::move(component)); components.emplace_back(component);
} }
state.mkList(v, components.size()); state.mkList(v, components.size());
for (const auto & [n, component] : enumerate(components)) for (const auto & [n, component] : enumerate(components))

View file

@ -7,7 +7,8 @@ namespace nix {
static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
v.mkString(state.coerceToString(pos, *args[0], context)); auto s = state.coerceToString(pos, *args[0], context);
v.mkString(*s);
} }
static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext); static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
@ -32,13 +33,13 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
string s = state.coerceToString(pos, *args[0], context); auto s = state.coerceToString(pos, *args[0], context);
PathSet context2; PathSet context2;
for (auto & p : context) for (auto & p : context)
context2.insert(p.at(0) == '=' ? string(p, 1) : p); context2.insert(p.at(0) == '=' ? string(p, 1) : p);
v.mkString(s, context2); v.mkString(*s, context2);
} }
static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency); static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency);
@ -180,7 +181,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
} }
for (auto elem : iter->value->listItems()) { for (auto elem : iter->value->listItems()) {
auto name = state.forceStringNoCtx(*elem, *iter->pos); auto name = state.forceStringNoCtx(*elem, *iter->pos);
context.insert("!" + name + "!" + string(i.name)); context.insert(concatStrings("!", name, "!", i.name));
} }
} }
} }

View file

@ -12,7 +12,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
std::string url; std::string url;
std::optional<Hash> rev; std::optional<Hash> rev;
std::optional<std::string> ref; std::optional<std::string> ref;
std::string name = "source"; std::string_view name = "source";
PathSet context; PathSet context;
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
@ -22,14 +22,14 @@ 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) {
string n(attr.name); std::string_view n(attr.name);
if (n == "url") if (n == "url")
url = state.coerceToString(*attr.pos, *attr.value, context, false, false); 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, 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;
@ -50,7 +50,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
}); });
} else } else
url = state.coerceToString(pos, *args[0], context, false, false); url = state.coerceToString(pos, *args[0], context, false, false).toOwned();
// FIXME: git externals probably can be used to bypass the URI // FIXME: git externals probably can be used to bypass the URI
// whitelist. Ah well. // whitelist. Ah well.
@ -62,7 +62,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
fetchers::Attrs attrs; fetchers::Attrs attrs;
attrs.insert_or_assign("type", "hg"); attrs.insert_or_assign("type", "hg");
attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url); attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url);
attrs.insert_or_assign("name", name); attrs.insert_or_assign("name", string(name));
if (ref) attrs.insert_or_assign("ref", *ref); if (ref) attrs.insert_or_assign("ref", *ref);
if (rev) attrs.insert_or_assign("rev", rev->gitRev()); if (rev) attrs.insert_or_assign("rev", rev->gitRev());
auto input = fetchers::Input::fromAttrs(std::move(attrs)); auto input = fetchers::Input::fromAttrs(std::move(attrs));

View file

@ -125,7 +125,7 @@ static void fetchTree(
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); auto s = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned();
attrs.emplace(attr.name, attrs.emplace(attr.name,
attr.name == "url" attr.name == "url"
? type == "git" ? type == "git"
@ -151,7 +151,7 @@ static void fetchTree(
input = fetchers::Input::fromAttrs(std::move(attrs)); input = fetchers::Input::fromAttrs(std::move(attrs));
} else { } else {
auto url = state.coerceToString(pos, *args[0], context, false, false); auto url = state.coerceToString(pos, *args[0], context, false, false).toOwned();
if (type == "git") { if (type == "git") {
fetchers::Attrs attrs; fetchers::Attrs attrs;

View file

@ -9,7 +9,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
{ {
auto toml = state.forceStringNoCtx(*args[0], pos); auto toml = state.forceStringNoCtx(*args[0], pos);
std::istringstream tomlStream(toml); std::istringstream tomlStream(string{toml});
std::function<void(Value &, toml::value)> visit; std::function<void(Value &, toml::value)> visit;

View file

@ -142,7 +142,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
if (!v.lambda.fun->arg.empty()) attrs["name"] = v.lambda.fun->arg; if (!v.lambda.fun->arg.empty()) attrs["name"] = 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->formals) for (auto & i : v.lambda.fun->formals->lexicographicOrder())
doc.writeEmptyElement("attr", singletonAttrs("name", i.name)); doc.writeEmptyElement("attr", singletonAttrs("name", i.name));
} else } else
doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg)); doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg));

View file

@ -124,15 +124,13 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
debug("using substituted/cached input '%s' in '%s'", debug("using substituted/cached input '%s' in '%s'",
to_string(), store->printStorePath(storePath)); to_string(), store->printStorePath(storePath));
auto actualPath = store->toRealPath(storePath); return {Tree { .actualPath = store->toRealPath(storePath), .storePath = std::move(storePath) }, *this};
return {fetchers::Tree(std::move(actualPath), std::move(storePath)), *this};
} catch (Error & e) { } catch (Error & e) {
debug("substitution of input '%s' failed: %s", to_string(), e.what()); debug("substitution of input '%s' failed: %s", to_string(), e.what());
} }
} }
auto [tree, input] = [&]() -> std::pair<Tree, Input> { auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
try { try {
return scheme->fetch(store, *this); return scheme->fetch(store, *this);
} catch (Error & e) { } catch (Error & e) {
@ -141,8 +139,10 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
} }
}(); }();
if (tree.actualPath == "") Tree tree {
tree.actualPath = store->toRealPath(tree.storePath); .actualPath = store->toRealPath(storePath),
.storePath = storePath,
};
auto narHash = store->queryPathInfo(tree.storePath)->narHash; auto narHash = store->queryPathInfo(tree.storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(SRI, true)); input.attrs.insert_or_assign("narHash", narHash.to_string(SRI, true));

View file

@ -16,7 +16,6 @@ struct Tree
{ {
Path actualPath; Path actualPath;
StorePath storePath; StorePath storePath;
Tree(Path && actualPath, StorePath && storePath) : actualPath(actualPath), storePath(std::move(storePath)) {}
}; };
struct InputScheme; struct InputScheme;
@ -131,7 +130,7 @@ struct InputScheme
virtual void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg); virtual void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg);
virtual std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) = 0; virtual std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) = 0;
}; };
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher); void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);

View file

@ -172,7 +172,7 @@ struct GitInputScheme : InputScheme
return {isLocal, isLocal ? url.path : url.base}; return {isLocal, isLocal ? url.path : url.base};
} }
std::pair<Tree, 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);
@ -197,17 +197,14 @@ struct GitInputScheme : InputScheme
}; };
auto makeResult = [&](const Attrs & infoAttrs, StorePath && storePath) auto makeResult = [&](const Attrs & infoAttrs, StorePath && storePath)
-> std::pair<Tree, Input> -> std::pair<StorePath, Input>
{ {
assert(input.getRev()); assert(input.getRev());
assert(!_input.getRev() || _input.getRev() == input.getRev()); assert(!_input.getRev() || _input.getRev() == input.getRev());
if (!shallow) if (!shallow)
input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount")); input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount"));
input.attrs.insert_or_assign("lastModified", getIntAttr(infoAttrs, "lastModified")); input.attrs.insert_or_assign("lastModified", getIntAttr(infoAttrs, "lastModified"));
return { return {std::move(storePath), input};
Tree(store->toRealPath(storePath), std::move(storePath)),
input
};
}; };
if (input.getRev()) { if (input.getRev()) {
@ -285,10 +282,7 @@ struct GitInputScheme : InputScheme
"lastModified", "lastModified",
haveCommits ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); haveCommits ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
return { return {std::move(storePath), input};
Tree(store->toRealPath(storePath), std::move(storePath)),
input
};
} }
} }

View file

@ -180,7 +180,7 @@ struct GitArchiveInputScheme : InputScheme
virtual DownloadUrl getDownloadUrl(const Input & input) const = 0; virtual DownloadUrl getDownloadUrl(const Input & input) const = 0;
std::pair<Tree, 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);
@ -199,10 +199,7 @@ struct GitArchiveInputScheme : InputScheme
if (auto res = getCache()->lookup(store, immutableAttrs)) { if (auto res = getCache()->lookup(store, immutableAttrs)) {
input.attrs.insert_or_assign("lastModified", getIntAttr(res->first, "lastModified")); input.attrs.insert_or_assign("lastModified", getIntAttr(res->first, "lastModified"));
return { return {std::move(res->second), input};
Tree(store->toRealPath(res->second), std::move(res->second)),
input
};
} }
auto url = getDownloadUrl(input); auto url = getDownloadUrl(input);
@ -221,7 +218,7 @@ struct GitArchiveInputScheme : InputScheme
tree.storePath, tree.storePath,
true); true);
return {std::move(tree), input}; return {std::move(tree.storePath), input};
} }
}; };

View file

@ -94,7 +94,7 @@ struct IndirectInputScheme : InputScheme
return input; return input;
} }
std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
{ {
throw Error("indirect input '%s' cannot be fetched directly", input.to_string()); throw Error("indirect input '%s' cannot be fetched directly", input.to_string());
} }

View file

@ -143,7 +143,7 @@ struct MercurialInputScheme : InputScheme
return {isLocal, isLocal ? url.path : url.base}; return {isLocal, isLocal ? url.path : url.base};
} }
std::pair<Tree, 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);
@ -193,10 +193,7 @@ struct MercurialInputScheme : InputScheme
auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter); auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
return { return {std::move(storePath), input};
Tree(store->toRealPath(storePath), std::move(storePath)),
input
};
} }
} }
@ -212,15 +209,12 @@ struct MercurialInputScheme : InputScheme
}; };
auto makeResult = [&](const Attrs & infoAttrs, StorePath && storePath) auto makeResult = [&](const Attrs & infoAttrs, StorePath && storePath)
-> std::pair<Tree, Input> -> std::pair<StorePath, Input>
{ {
assert(input.getRev()); assert(input.getRev());
assert(!_input.getRev() || _input.getRev() == input.getRev()); assert(!_input.getRev() || _input.getRev() == input.getRev());
input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount")); input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount"));
return { return {std::move(storePath), input};
Tree(store->toRealPath(storePath), std::move(storePath)),
input
};
}; };
if (input.getRev()) { if (input.getRev()) {

View file

@ -80,7 +80,7 @@ struct PathInputScheme : InputScheme
// nothing to do // nothing to do
} }
std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
{ {
std::string absPath; std::string absPath;
auto path = getStrAttr(input.attrs, "path"); auto path = getStrAttr(input.attrs, "path");
@ -115,10 +115,7 @@ struct PathInputScheme : InputScheme
// FIXME: try to substitute storePath. // FIXME: try to substitute storePath.
storePath = store->addToStore("source", absPath); storePath = store->addToStore("source", absPath);
return { return {std::move(*storePath), input};
Tree(store->toRealPath(*storePath), std::move(*storePath)),
input
};
} }
}; };

View file

@ -126,7 +126,7 @@ std::pair<Tree, time_t> downloadTarball(
if (cached && !cached->expired) if (cached && !cached->expired)
return { return {
Tree(store->toRealPath(cached->storePath), std::move(cached->storePath)), Tree { .actualPath = store->toRealPath(cached->storePath), .storePath = std::move(cached->storePath) },
getIntAttr(cached->infoAttrs, "lastModified") getIntAttr(cached->infoAttrs, "lastModified")
}; };
@ -163,7 +163,7 @@ std::pair<Tree, time_t> downloadTarball(
immutable); immutable);
return { return {
Tree(store->toRealPath(*unpackedStorePath), std::move(*unpackedStorePath)), Tree { .actualPath = store->toRealPath(*unpackedStorePath), .storePath = std::move(*unpackedStorePath) },
lastModified, lastModified,
}; };
} }
@ -225,10 +225,10 @@ struct TarballInputScheme : InputScheme
return true; return true;
} }
std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
{ {
auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first; auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first;
return {std::move(tree), input}; return {std::move(tree.storePath), input};
} }
}; };

View file

@ -307,7 +307,7 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
}}); }});
} }
StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, const string & name, StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references)
{ {
if (method != FileIngestionMethod::Recursive || hashAlgo != htSHA256) if (method != FileIngestionMethod::Recursive || hashAlgo != htSHA256)

View file

@ -98,7 +98,7 @@ public:
void addToStore(const ValidPathInfo & info, Source & narSource, void addToStore(const ValidPathInfo & info, Source & narSource,
RepairFlag repair, CheckSigsFlag checkSigs) override; RepairFlag repair, CheckSigsFlag checkSigs) override;
StorePath addToStoreFromDump(Source & dump, const string & name, StorePath addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) override; FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) override;
StorePath addToStore(const string & name, const Path & srcPath, StorePath addToStore(const string & name, const Path & srcPath,

View file

@ -912,9 +912,12 @@ void LocalDerivationGoal::startBuilder()
sandboxMountNamespace = open(fmt("/proc/%d/ns/mnt", (pid_t) pid).c_str(), O_RDONLY); sandboxMountNamespace = open(fmt("/proc/%d/ns/mnt", (pid_t) pid).c_str(), O_RDONLY);
if (sandboxMountNamespace.get() == -1) if (sandboxMountNamespace.get() == -1)
throw SysError("getting sandbox mount namespace"); throw SysError("getting sandbox mount namespace");
if (usingUserNamespace) {
sandboxUserNamespace = open(fmt("/proc/%d/ns/user", (pid_t) pid).c_str(), O_RDONLY); sandboxUserNamespace = open(fmt("/proc/%d/ns/user", (pid_t) pid).c_str(), O_RDONLY);
if (sandboxUserNamespace.get() == -1) if (sandboxUserNamespace.get() == -1)
throw SysError("getting sandbox user namespace"); throw SysError("getting sandbox user namespace");
}
/* Signal the builder that we've updated its user namespace. */ /* Signal the builder that we've updated its user namespace. */
writeFull(userNamespaceSync.writeSide.get(), "1"); writeFull(userNamespaceSync.writeSide.get(), "1");
@ -1205,7 +1208,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
return path; return path;
} }
StorePath addToStoreFromDump(Source & dump, const string & name, StorePath addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair, FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair,
const StorePathSet & references = StorePathSet()) override const StorePathSet & references = StorePathSet()) override
{ {

View file

@ -981,8 +981,12 @@ void processConnection(
readInt(from); readInt(from);
} }
if (GET_PROTOCOL_MINOR(clientVersion) >= 11)
readInt(from); // obsolete reserveSpace readInt(from); // obsolete reserveSpace
if (GET_PROTOCOL_MINOR(clientVersion) >= 33)
to << nixVersion;
/* Send startup error messages to the client. */ /* Send startup error messages to the client. */
tunnelLogger->startWork(); tunnelLogger->startWork();

View file

@ -699,10 +699,10 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
} }
std::string hashPlaceholder(const std::string & outputName) std::string hashPlaceholder(const std::string_view outputName)
{ {
// FIXME: memoize? // FIXME: memoize?
return "/" + hashString(htSHA256, "nix-output:" + outputName).to_string(Base32, false); return "/" + hashString(htSHA256, concatStrings("nix-output:", outputName)).to_string(Base32, false);
} }
std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath, std::string_view outputName) std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath, std::string_view outputName)

View file

@ -236,7 +236,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
It is used as a placeholder to allow derivations to refer to their It is used as a placeholder to allow derivations to refer to their
own outputs without needing to use the hash of a derivation in own outputs without needing to use the hash of a derivation in
itself, making the hash near-impossible to calculate. */ itself, making the hash near-impossible to calculate. */
std::string hashPlaceholder(const std::string & outputName); std::string hashPlaceholder(const std::string_view outputName);
/* This creates an opaque and almost certainly unique string /* This creates an opaque and almost certainly unique string
deterministically from a derivation path and output name. deterministically from a derivation path and output name.

View file

@ -128,7 +128,7 @@ struct curlFileTransfer : public FileTransfer
if (requestHeaders) curl_slist_free_all(requestHeaders); if (requestHeaders) curl_slist_free_all(requestHeaders);
try { try {
if (!done) if (!done)
fail(FileTransferError(Interrupted, nullptr, "download of '%s' was interrupted", request.uri)); fail(FileTransferError(Interrupted, {}, "download of '%s' was interrupted", request.uri));
} catch (...) { } catch (...) {
ignoreException(); ignoreException();
} }
@ -704,7 +704,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, nullptr, "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

@ -970,7 +970,7 @@ public:
Setting<std::string> commitLockFileSummary{ Setting<std::string> commitLockFileSummary{
this, "", "commit-lockfile-summary", this, "", "commit-lockfile-summary",
R"( R"(
The commit summary to use when commiting changed flake lock files. If The commit summary to use when committing changed flake lock files. If
empty, the summary is generated based on the action performed. empty, the summary is generated based on the action performed.
)"}; )"};
}; };

View file

@ -1318,7 +1318,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
} }
StorePath LocalStore::addToStoreFromDump(Source & source0, const string & name, StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name,
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references)
{ {
/* For computing the store path. */ /* For computing the store path. */
@ -1919,4 +1919,10 @@ void LocalStore::addBuildLog(const StorePath & drvPath, std::string_view log)
throw SysError("renaming '%1%' to '%2%'", tmpFile, logPath); throw SysError("renaming '%1%' to '%2%'", tmpFile, logPath);
} }
std::optional<std::string> LocalStore::getVersion()
{
return nixVersion;
}
} // namespace nix } // namespace nix

View file

@ -144,7 +144,7 @@ public:
void addToStore(const ValidPathInfo & info, Source & source, void addToStore(const ValidPathInfo & info, Source & source,
RepairFlag repair, CheckSigsFlag checkSigs) override; RepairFlag repair, CheckSigsFlag checkSigs) override;
StorePath addToStoreFromDump(Source & dump, const string & name, StorePath addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) override; FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) override;
StorePath addTextToStore(const string & name, const string & s, StorePath addTextToStore(const string & name, const string & s,
@ -211,6 +211,8 @@ public:
void queryRealisationUncached(const DrvOutput&, void queryRealisationUncached(const DrvOutput&,
Callback<std::shared_ptr<const Realisation>> callback) noexcept override; Callback<std::shared_ptr<const Realisation>> callback) noexcept override;
std::optional<std::string> getVersion() override;
private: private:
int getSchema(); int getSchema();

View file

@ -56,8 +56,8 @@ bool DrvName::matches(const DrvName & n)
} }
string nextComponent(string::const_iterator & p, std::string_view nextComponent(std::string_view::const_iterator & p,
const string::const_iterator end) const std::string_view::const_iterator end)
{ {
/* Skip any dots and dashes (component separators). */ /* Skip any dots and dashes (component separators). */
while (p != end && (*p == '.' || *p == '-')) ++p; while (p != end && (*p == '.' || *p == '-')) ++p;
@ -67,18 +67,18 @@ string nextComponent(string::const_iterator & p,
/* If the first character is a digit, consume the longest sequence /* If the first character is a digit, consume the longest sequence
of digits. Otherwise, consume the longest sequence of of digits. Otherwise, consume the longest sequence of
non-digit, non-separator characters. */ non-digit, non-separator characters. */
string s; auto s = p;
if (isdigit(*p)) if (isdigit(*p))
while (p != end && isdigit(*p)) s += *p++; while (p != end && isdigit(*p)) p++;
else else
while (p != end && (!isdigit(*p) && *p != '.' && *p != '-')) while (p != end && (!isdigit(*p) && *p != '.' && *p != '-'))
s += *p++; p++;
return s; return {s, size_t(p - s)};
} }
static bool componentsLT(const string & c1, const string & c2) static bool componentsLT(const std::string_view c1, const std::string_view c2)
{ {
auto n1 = string2Int<int>(c1); auto n1 = string2Int<int>(c1);
auto n2 = string2Int<int>(c2); auto n2 = string2Int<int>(c2);
@ -94,14 +94,14 @@ static bool componentsLT(const string & c1, const string & c2)
} }
int compareVersions(const string & v1, const string & v2) int compareVersions(const std::string_view v1, const std::string_view v2)
{ {
string::const_iterator p1 = v1.begin(); auto p1 = v1.begin();
string::const_iterator p2 = v2.begin(); auto p2 = v2.begin();
while (p1 != v1.end() || p2 != v2.end()) { while (p1 != v1.end() || p2 != v2.end()) {
string c1 = nextComponent(p1, v1.end()); auto c1 = nextComponent(p1, v1.end());
string c2 = nextComponent(p2, v2.end()); auto c2 = nextComponent(p2, v2.end());
if (componentsLT(c1, c2)) return -1; if (componentsLT(c1, c2)) return -1;
else if (componentsLT(c2, c1)) return 1; else if (componentsLT(c2, c1)) return 1;
} }

View file

@ -27,9 +27,9 @@ private:
typedef list<DrvName> DrvNames; typedef list<DrvName> DrvNames;
string nextComponent(string::const_iterator & p, std::string_view nextComponent(std::string_view::const_iterator & p,
const string::const_iterator end); const std::string_view::const_iterator end);
int compareVersions(const string & v1, const string & v2); int compareVersions(const std::string_view v1, const std::string_view v2);
DrvNames drvNamesFromArgs(const Strings & opArgs); DrvNames drvNamesFromArgs(const Strings & opArgs);
} }

View file

@ -26,7 +26,7 @@ static void makeWritable(const Path & path)
struct MakeReadOnly struct MakeReadOnly
{ {
Path path; Path path;
MakeReadOnly(const Path & path) : path(path) { } MakeReadOnly(const PathView path) : path(path) { }
~MakeReadOnly() ~MakeReadOnly()
{ {
try { try {
@ -205,12 +205,13 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
/* Make the containing directory writable, but only if it's not /* Make the containing directory writable, but only if it's not
the store itself (we don't want or need to mess with its the store itself (we don't want or need to mess with its
permissions). */ permissions). */
bool mustToggle = dirOf(path) != realStoreDir.get(); const Path dirOfPath(dirOf(path));
if (mustToggle) makeWritable(dirOf(path)); bool mustToggle = dirOfPath != realStoreDir.get();
if (mustToggle) makeWritable(dirOfPath);
/* When we're done, make the directory read-only again and reset /* When we're done, make the directory read-only again and reset
its timestamp back to 0. */ its timestamp back to 0. */
MakeReadOnly makeReadOnly(mustToggle ? dirOf(path) : ""); MakeReadOnly makeReadOnly(mustToggle ? dirOfPath : "");
Path tempLink = (format("%1%/.tmp-link-%2%-%3%") Path tempLink = (format("%1%/.tmp-link-%2%-%3%")
% realStoreDir % getpid() % random()).str(); % realStoreDir % getpid() % random()).str();

View file

@ -170,7 +170,7 @@ std::string writeStructuredAttrsShell(const nlohmann::json & json)
auto handleSimpleType = [](const nlohmann::json & value) -> std::optional<std::string> { auto handleSimpleType = [](const nlohmann::json & value) -> std::optional<std::string> {
if (value.is_string()) if (value.is_string())
return shellEscape(value); return shellEscape(value.get<std::string_view>());
if (value.is_number()) { if (value.is_number()) {
auto f = value.get<float>(); auto f = value.get<float>();

View file

@ -188,7 +188,12 @@ void RemoteStore::initConnection(Connection & conn)
} }
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 11) if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 11)
conn.to << false; conn.to << false; // obsolete reserveSpace
if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 33) {
conn.to.flush();
conn.daemonNixVersion = readString(conn.from);
}
auto ex = conn.processStderr(); auto ex = conn.processStderr();
if (ex) std::rethrow_exception(ex); if (ex) std::rethrow_exception(ex);
@ -495,7 +500,7 @@ std::optional<StorePath> RemoteStore::queryPathFromHashPart(const std::string &
ref<const ValidPathInfo> RemoteStore::addCAToStore( ref<const ValidPathInfo> RemoteStore::addCAToStore(
Source & dump, Source & dump,
const string & name, std::string_view name,
ContentAddressMethod caMethod, ContentAddressMethod caMethod,
const StorePathSet & references, const StorePathSet & references,
RepairFlag repair) RepairFlag repair)
@ -577,7 +582,7 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
} }
StorePath RemoteStore::addToStoreFromDump(Source & dump, const string & name, StorePath RemoteStore::addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method, HashType hashType, RepairFlag repair, const StorePathSet & references) FileIngestionMethod method, HashType hashType, RepairFlag repair, const StorePathSet & references)
{ {
return addCAToStore(dump, name, FixedOutputHashMethod{ .fileIngestionMethod = method, .hashType = hashType }, references, repair)->path; return addCAToStore(dump, name, FixedOutputHashMethod{ .fileIngestionMethod = method, .hashType = hashType }, references, repair)->path;
@ -920,6 +925,13 @@ void RemoteStore::addBuildLog(const StorePath & drvPath, std::string_view log)
} }
std::optional<std::string> RemoteStore::getVersion()
{
auto conn(getConnection());
return conn->daemonNixVersion;
}
void RemoteStore::connect() void RemoteStore::connect()
{ {
auto conn(getConnection()); auto conn(getConnection());

View file

@ -66,13 +66,13 @@ public:
/* Add a content-addressable store path. `dump` will be drained. */ /* Add a content-addressable store path. `dump` will be drained. */
ref<const ValidPathInfo> addCAToStore( ref<const ValidPathInfo> addCAToStore(
Source & dump, Source & dump,
const string & name, std::string_view name,
ContentAddressMethod caMethod, ContentAddressMethod caMethod,
const StorePathSet & references, const StorePathSet & references,
RepairFlag repair); RepairFlag repair);
/* Add a content-addressable store path. Does not support references. `dump` will be drained. */ /* Add a content-addressable store path. Does not support references. `dump` will be drained. */
StorePath addToStoreFromDump(Source & dump, const string & name, StorePath addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair, const StorePathSet & references = StorePathSet()) override; FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair, const StorePathSet & references = StorePathSet()) override;
void addToStore(const ValidPathInfo & info, Source & nar, void addToStore(const ValidPathInfo & info, Source & nar,
@ -118,6 +118,8 @@ public:
void addBuildLog(const StorePath & drvPath, std::string_view log) override; void addBuildLog(const StorePath & drvPath, std::string_view log) override;
std::optional<std::string> getVersion() override;
void connect() override; void connect() override;
unsigned int getProtocol() override; unsigned int getProtocol() override;
@ -129,6 +131,7 @@ public:
FdSink to; FdSink to;
FdSource from; FdSource from;
unsigned int daemonVersion; unsigned int daemonVersion;
std::optional<std::string> daemonNixVersion;
std::chrono::time_point<std::chrono::steady_clock> startTime; std::chrono::time_point<std::chrono::steady_clock> startTime;
virtual ~Connection(); virtual ~Connection();

View file

@ -499,7 +499,7 @@ public:
false). false).
`dump` may be drained */ `dump` may be drained */
// FIXME: remove? // FIXME: remove?
virtual StorePath addToStoreFromDump(Source & dump, const string & name, virtual StorePath addToStoreFromDump(Source & dump, std::string_view name,
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair, FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair,
const StorePathSet & references = StorePathSet()) const StorePathSet & references = StorePathSet())
{ unsupported("addToStoreFromDump"); } { unsupported("addToStoreFromDump"); }
@ -765,6 +765,9 @@ public:
* (a no-op when theres no daemon) * (a no-op when theres no daemon)
*/ */
virtual void setOptions() { } virtual void setOptions() { }
virtual std::optional<std::string> getVersion() { return {}; }
protected: protected:
Stats stats; Stats stats;

View file

@ -9,7 +9,7 @@ namespace nix {
#define WORKER_MAGIC_1 0x6e697863 #define WORKER_MAGIC_1 0x6e697863
#define WORKER_MAGIC_2 0x6478696f #define WORKER_MAGIC_2 0x6478696f
#define PROTOCOL_VERSION (1 << 8 | 32) #define PROTOCOL_VERSION (1 << 8 | 33)
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00) #define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff) #define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)

View file

@ -259,7 +259,7 @@ Hash::Hash(std::string_view rest, HashType type, bool isSRI)
throw BadHash("hash '%s' has wrong length for hash type '%s'", rest, printHashType(this->type)); throw BadHash("hash '%s' has wrong length for hash type '%s'", rest, printHashType(this->type));
} }
Hash newHashAllowEmpty(std::string hashStr, std::optional<HashType> ht) Hash newHashAllowEmpty(std::string_view hashStr, std::optional<HashType> ht)
{ {
if (hashStr.empty()) { if (hashStr.empty()) {
if (!ht) if (!ht)

View file

@ -107,7 +107,7 @@ public:
}; };
/* Helper that defaults empty hashes to the 0 hash. */ /* Helper that defaults empty hashes to the 0 hash. */
Hash newHashAllowEmpty(std::string hashStr, std::optional<HashType> ht); Hash newHashAllowEmpty(std::string_view hashStr, std::optional<HashType> ht);
/* Print a hash in base-16 if it's MD5, or base-32 otherwise. */ /* Print a hash in base-16 if it's MD5, or base-32 otherwise. */
string printHash16or32(const Hash & hash); string printHash16or32(const Hash & hash);

View file

@ -6,6 +6,7 @@
#include <set> #include <set>
#include <string> #include <string>
#include <map> #include <map>
#include <variant>
#include <vector> #include <vector>
namespace nix { namespace nix {
@ -47,4 +48,63 @@ struct Explicit {
} }
}; };
/* This wants to be a little bit like rust's Cow type.
Some parts of the evaluator benefit greatly from being able to reuse
existing allocations for strings, but have to be able to also use
newly allocated storage for values.
We do not define implicit conversions, even with ref qualifiers,
since those can easily become ambiguous to the reader and can degrade
into copying behaviour we want to avoid. */
class BackedStringView {
private:
std::variant<std::string, std::string_view> data;
/* Needed to introduce a temporary since operator-> must return
a pointer. Without this we'd need to store the view object
even when we already own a string. */
class Ptr {
private:
std::string_view view;
public:
Ptr(std::string_view view): view(view) {}
const std::string_view * operator->() const { return &view; }
};
public:
BackedStringView(std::string && s): data(std::move(s)) {}
BackedStringView(std::string_view sv): data(sv) {}
template<size_t N>
BackedStringView(const char (& lit)[N]): data(std::string_view(lit)) {}
BackedStringView(const BackedStringView &) = delete;
BackedStringView & operator=(const BackedStringView &) = delete;
/* We only want move operations defined since the sole purpose of
this type is to avoid copies. */
BackedStringView(BackedStringView && other) = default;
BackedStringView & operator=(BackedStringView && other) = default;
bool isOwned() const
{
return std::holds_alternative<std::string>(data);
}
std::string toOwned() &&
{
return isOwned()
? std::move(std::get<std::string>(data))
: std::string(std::get<std::string_view>(data));
}
std::string_view operator*() const
{
return isOwned()
? std::get<std::string>(data)
: std::get<std::string_view>(data);
}
Ptr operator->() const { return Ptr(**this); }
};
} }

View file

@ -81,7 +81,7 @@ void replaceEnv(std::map<std::string, std::string> newEnv)
} }
Path absPath(Path path, std::optional<Path> dir, bool resolveSymlinks) Path absPath(Path path, std::optional<PathView> dir, bool resolveSymlinks)
{ {
if (path[0] != '/') { if (path[0] != '/') {
if (!dir) { if (!dir) {
@ -95,12 +95,12 @@ Path absPath(Path path, std::optional<Path> dir, bool resolveSymlinks)
if (!getcwd(buf, sizeof(buf))) if (!getcwd(buf, sizeof(buf)))
#endif #endif
throw SysError("cannot get cwd"); throw SysError("cannot get cwd");
dir = buf; path = concatStrings(buf, "/", path);
#ifdef __GNU__ #ifdef __GNU__
free(buf); free(buf);
#endif #endif
} } else
path = *dir + "/" + path; path = concatStrings(*dir, "/", path);
} }
return canonPath(path, resolveSymlinks); return canonPath(path, resolveSymlinks);
} }
@ -147,7 +147,7 @@ Path canonPath(PathView path, bool resolveSymlinks)
path = {}; path = {};
} else { } else {
s += path.substr(0, slash); s += path.substr(0, slash);
path = path.substr(slash + 1); path = path.substr(slash);
} }
/* If s points to a symlink, resolve it and continue from there */ /* If s points to a symlink, resolve it and continue from there */
@ -172,7 +172,7 @@ Path canonPath(PathView path, bool resolveSymlinks)
} }
Path dirOf(const Path & path) Path dirOf(const PathView path)
{ {
Path::size_type pos = path.rfind('/'); Path::size_type pos = path.rfind('/');
if (pos == string::npos) if (pos == string::npos)
@ -1344,9 +1344,11 @@ std::string toLower(const std::string & s)
} }
std::string shellEscape(const std::string & s) std::string shellEscape(const std::string_view s)
{ {
std::string r = "'"; std::string r;
r.reserve(s.size() + 2);
r += "'";
for (auto & i : s) for (auto & i : s)
if (i == '\'') r += "'\\''"; else r += i; if (i == '\'') r += "'\\''"; else r += i;
r += '\''; r += '\'';
@ -1356,11 +1358,15 @@ std::string shellEscape(const std::string & s)
void ignoreException() void ignoreException()
{ {
/* Make sure no exceptions leave this function.
printError() also throws when remote is closed. */
try {
try { try {
throw; throw;
} catch (std::exception & e) { } catch (std::exception & e) {
printError("error (ignored): %1%", e.what()); printError("error (ignored): %1%", e.what());
} }
} catch (...) { }
} }
bool shouldANSI() bool shouldANSI()
@ -1751,7 +1757,7 @@ void bind(int fd, const std::string & path)
if (path.size() + 1 >= sizeof(addr.sun_path)) { if (path.size() + 1 >= sizeof(addr.sun_path)) {
Pid pid = startProcess([&]() { Pid pid = startProcess([&]() {
auto dir = dirOf(path); Path dir = dirOf(path);
if (chdir(dir.c_str()) == -1) if (chdir(dir.c_str()) == -1)
throw SysError("chdir to '%s' failed", dir); throw SysError("chdir to '%s' failed", dir);
std::string base(baseNameOf(path)); std::string base(baseNameOf(path));
@ -1780,7 +1786,7 @@ void connect(int fd, const std::string & path)
if (path.size() + 1 >= sizeof(addr.sun_path)) { if (path.size() + 1 >= sizeof(addr.sun_path)) {
Pid pid = startProcess([&]() { Pid pid = startProcess([&]() {
auto dir = dirOf(path); Path dir = dirOf(path);
if (chdir(dir.c_str()) == -1) if (chdir(dir.c_str()) == -1)
throw SysError("chdir to '%s' failed", dir); throw SysError("chdir to '%s' failed", dir);
std::string base(baseNameOf(path)); std::string base(baseNameOf(path));

View file

@ -49,7 +49,7 @@ void clearEnv();
specified directory, or the current directory otherwise. The path specified directory, or the current directory otherwise. The path
is also canonicalised. */ is also canonicalised. */
Path absPath(Path path, Path absPath(Path path,
std::optional<Path> dir = {}, std::optional<PathView> dir = {},
bool resolveSymlinks = false); bool resolveSymlinks = false);
/* Canonicalise a path by removing all `.' or `..' components and /* Canonicalise a path by removing all `.' or `..' components and
@ -62,7 +62,7 @@ Path canonPath(PathView path, bool resolveSymlinks = false);
everything before the final `/'. If the path is the root or an everything before the final `/'. If the path is the root or an
immediate child thereof (e.g., `/foo'), this means `/' immediate child thereof (e.g., `/foo'), this means `/'
is returned.*/ is returned.*/
Path dirOf(const Path & path); Path dirOf(const PathView path);
/* Return the base name of the given canonical path, i.e., everything /* Return the base name of the given canonical path, i.e., everything
following the final `/' (trailing slashes are removed). */ following the final `/' (trailing slashes are removed). */
@ -148,6 +148,9 @@ Path getDataDir();
/* Create a directory and all its parents, if necessary. Returns the /* Create a directory and all its parents, if necessary. Returns the
list of created directories, in order of creation. */ list of created directories, in order of creation. */
Paths createDirs(const Path & path); Paths createDirs(const Path & path);
inline Paths createDirs(PathView path) {
return createDirs(Path(path));
}
/* Create a symlink. */ /* Create a symlink. */
void createSymlink(const Path & target, const Path & link, void createSymlink(const Path & target, const Path & link,
@ -187,6 +190,7 @@ public:
void cancel(); void cancel();
void reset(const Path & p, bool recursive = true); void reset(const Path & p, bool recursive = true);
operator Path() const { return path; } operator Path() const { return path; }
operator PathView() const { return path; }
}; };
@ -491,7 +495,7 @@ std::string toLower(const std::string & s);
/* Escape a string as a shell word. */ /* Escape a string as a shell word. */
std::string shellEscape(const std::string & s); std::string shellEscape(const std::string_view s);
/* Exception handling in destructors: print an error message, then /* Exception handling in destructors: print an error message, then

View file

@ -318,7 +318,7 @@ static void main_nix_build(int argc, char * * argv)
for (auto & i : attrPaths) { for (auto & i : attrPaths) {
Value & v(*findAlongAttrPath(*state, i, *autoArgs, vRoot).first); Value & v(*findAlongAttrPath(*state, i, *autoArgs, vRoot).first);
state->forceValue(v); state->forceValue(v, [&]() { return v.determinePos(noPos); });
getDerivations(*state, v, "", *autoArgs, drvs, false); getDerivations(*state, v, "", *autoArgs, drvs, false);
} }
} }
@ -500,6 +500,7 @@ static void main_nix_build(int argc, char * * argv)
"%3%" "%3%"
"PATH=%4%:\"$PATH\"; " "PATH=%4%:\"$PATH\"; "
"SHELL=%5%; " "SHELL=%5%; "
"BASH=%5%; "
"set +e; " "set +e; "
R"s([ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] '; )s" R"s([ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] '; )s"
"if [ \"$(type -t runHook)\" = function ]; then runHook shellHook; fi; " "if [ \"$(type -t runHook)\" = function ]; then runHook shellHook; fi; "

View file

@ -129,7 +129,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
/* Evaluate it. */ /* Evaluate it. */
debug("evaluating user environment builder"); debug("evaluating user environment builder");
state.forceValue(topLevel); 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.store->parseStorePath(state.coerceToPath(*aDrvPath.pos, *aDrvPath.value, context)); auto topLevelDrv = state.store->parseStorePath(state.coerceToPath(*aDrvPath.pos, *aDrvPath.value, context));

View file

@ -40,7 +40,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
for (auto & i : attrPaths) { for (auto & i : attrPaths) {
Value & v(*findAlongAttrPath(state, i, autoArgs, vRoot).first); Value & v(*findAlongAttrPath(state, i, autoArgs, vRoot).first);
state.forceValue(v); state.forceValue(v, [&]() { return v.determinePos(noPos); });
PathSet context; PathSet context;
if (evalOnly) { if (evalOnly) {

View file

@ -51,7 +51,9 @@ struct CmdBundle : InstallableCommand
Strings getDefaultFlakeAttrPaths() override Strings getDefaultFlakeAttrPaths() override
{ {
Strings res{"defaultApp." + settings.thisSystem.get()}; Strings res{
"defaultApp." + settings.thisSystem.get()
};
for (auto & s : SourceExprCommand::getDefaultFlakeAttrPaths()) for (auto & s : SourceExprCommand::getDefaultFlakeAttrPaths())
res.push_back(s); res.push_back(s);
return res; return res;
@ -59,7 +61,10 @@ struct CmdBundle : InstallableCommand
Strings getDefaultFlakeAttrPathPrefixes() override Strings getDefaultFlakeAttrPathPrefixes() override
{ {
Strings res{"apps." + settings.thisSystem.get() + "."}; Strings res{
"apps." + settings.thisSystem.get() + "."
};
for (auto & s : SourceExprCommand::getDefaultFlakeAttrPathPrefixes()) for (auto & s : SourceExprCommand::getDefaultFlakeAttrPathPrefixes())
res.push_back(s); res.push_back(s);
return res; return res;
@ -69,29 +74,19 @@ struct CmdBundle : InstallableCommand
{ {
auto evalState = getEvalState(); auto evalState = getEvalState();
auto app = installable->toApp(*evalState).resolve(getEvalStore(), store); auto val = installable->toValue(*evalState).first;
auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath(".")); auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath("."));
const flake::LockFlags lockFlags{ .writeLockFile = false }; const flake::LockFlags lockFlags{ .writeLockFile = false };
auto bundler = InstallableFlake(this, InstallableFlake bundler{this,
evalState, std::move(bundlerFlakeRef), evalState, std::move(bundlerFlakeRef), bundlerName,
Strings{bundlerName == "" ? "defaultBundler" : bundlerName}, {"defaultBundler." + settings.thisSystem.get()},
Strings({"bundlers."}), lockFlags); {"bundlers." + settings.thisSystem.get() + "."},
lockFlags
auto attrs = evalState->buildBindings(2); };
PathSet context;
for (auto & i : app.context)
context.insert("=" + store->printStorePath(i.path));
attrs.alloc("program").mkString(app.program, context);
attrs.alloc("system").mkString(settings.thisSystem.get());
auto vRes = evalState->allocValue(); auto vRes = evalState->allocValue();
evalState->callFunction( evalState->callFunction(*bundler.toValue(*evalState).first, *val, *vRes, noPos);
*bundler.toValue(*evalState).first,
evalState->allocValue()->mkAttrs(attrs),
*vRes, noPos);
if (!evalState->isDerivation(*vRes)) if (!evalState->isDerivation(*vRes))
throw Error("the bundler '%s' does not produce a derivation", bundler.what()); throw Error("the bundler '%s' does not produce a derivation", bundler.what());
@ -113,9 +108,12 @@ struct CmdBundle : InstallableCommand
auto outPathS = store->printStorePath(outPath); auto outPathS = store->printStorePath(outPath);
if (!outLink) if (!outLink) {
outLink = baseNameOf(app.program); auto &attr = vRes->attrs->need(evalState->sName);
outLink = evalState->forceStringNoCtx(*attr.value,*attr.pos);
}
// TODO: will crash if not a localFSStore?
store.dynamic_pointer_cast<LocalFSStore>()->addPermRoot(outPath, absPath(*outLink)); store.dynamic_pointer_cast<LocalFSStore>()->addPermRoot(outPath, absPath(*outLink));
} }
}; };

View file

@ -18,19 +18,51 @@ R""(
nix (Nix) 2.4pre20201215_e3ddffb nix (Nix) 2.4pre20201215_e3ddffb
``` ```
* Bundle a Hello using a specific bundler:
```console
# nix bundle --bundler github:NixOS/bundlers#toDockerImage nixpkgs#hello
# docker load < hello-2.10.tar.gz
# docker run hello-2.10:latest hello
Hello, world!
```
# Description # Description
`nix bundle` packs the closure of the [Nix app](./nix3-run.md) `nix bundle`, by default, packs the closure of the *installable* into a single
*installable* into a single self-extracting executable. See the self-extracting executable. See the [`bundlers`
[`nix-bundle` homepage](https://github.com/matthewbauer/nix-bundle) homepage](https://github.com/NixOS/bundlers) for more details.
for more details.
> **Note** > **Note**
> >
> This command only works on Linux. > This command only works on Linux.
# Bundler definitions # Flake output attributes
TODO If no flake output attribute is given, `nix bundle` tries the following
flake output attributes:
* `defaultBundler.<system>`
If an attribute *name* is given, `nix run` tries the following flake
output attributes:
* `bundler.<system>.<name>`
# Bundlers
A bundler is specified by a flake output attribute named
`bundlers.<system>.<name>` or `defaultBundler.<system>`. It looks like this:
```nix
bundlers.x86_64-linux.identity = drv: drv;
bundlers.x86_64-linux.blender_2_79 = drv: self.packages.x86_64-linux.blender_2_79;
defaultBundler.x86_64-linux = drv: drv;
```
A bundler must be a function that accepts an arbitrary value (typically a
derivation or app definition) and returns a derivation.
)"" )""

View file

@ -472,9 +472,11 @@ struct CmdDevelop : Common, MixEnvironment
else { else {
script = "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc;\n" + script; script = "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc;\n" + script;
if (developSettings.bashPrompt != "") if (developSettings.bashPrompt != "")
script += fmt("[ -n \"$PS1\" ] && PS1=%s;\n", shellEscape(developSettings.bashPrompt)); script += fmt("[ -n \"$PS1\" ] && PS1=%s;\n",
shellEscape(developSettings.bashPrompt.get()));
if (developSettings.bashPromptSuffix != "") if (developSettings.bashPromptSuffix != "")
script += fmt("[ -n \"$PS1\" ] && PS1+=%s;\n", shellEscape(developSettings.bashPromptSuffix)); script += fmt("[ -n \"$PS1\" ] && PS1+=%s;\n",
shellEscape(developSettings.bashPromptSuffix.get()));
} }
writeFull(rcFileFd.get(), script); writeFull(rcFileFd.get(), script);
@ -496,7 +498,8 @@ struct CmdDevelop : Common, MixEnvironment
this, this,
state, state,
installable->nixpkgsFlakeRef(), installable->nixpkgsFlakeRef(),
Strings{"bashInteractive"}, "bashInteractive",
Strings{},
Strings{"legacyPackages." + settings.thisSystem.get() + "."}, Strings{"legacyPackages." + settings.thisSystem.get() + "."},
nixpkgsLockFlags); nixpkgsLockFlags);

View file

@ -55,7 +55,7 @@ R""(
# nix develop /tmp/my-build-env # nix develop /tmp/my-build-env
``` ```
* Replace all occurences of the store path corresponding to * Replace all occurrences of the store path corresponding to
`glibc.dev` with a writable directory: `glibc.dev` with a writable directory:
```console ```console

View file

@ -81,7 +81,7 @@ struct CmdEval : MixJSON, InstallableCommand
recurse = [&](Value & v, const Pos & pos, const Path & path) recurse = [&](Value & v, const Pos & pos, const Path & path)
{ {
state->forceValue(v); state->forceValue(v, pos);
if (v.type() == nString) if (v.type() == nString)
// FIXME: disallow strings with contexts? // FIXME: disallow strings with contexts?
writeFile(path, v.string.s); writeFile(path, v.string.s);
@ -107,7 +107,7 @@ struct CmdEval : MixJSON, InstallableCommand
else if (raw) { else if (raw) {
stopProgressBar(); stopProgressBar();
std::cout << state->coerceToString(noPos, *v, context); std::cout << *state->coerceToString(noPos, *v, context);
} }
else if (json) { else if (json) {

View file

@ -124,12 +124,13 @@ struct CmdFlakeLock : FlakeCommand
static void enumerateOutputs(EvalState & state, Value & vFlake, static void enumerateOutputs(EvalState & state, Value & vFlake,
std::function<void(const std::string & name, Value & vProvide, const Pos & pos)> callback) std::function<void(const std::string & name, Value & vProvide, const Pos & pos)> callback)
{ {
state.forceAttrs(vFlake); auto pos = vFlake.determinePos(noPos);
state.forceAttrs(vFlake, pos);
auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs")); auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs"));
assert(aOutputs); assert(aOutputs);
state.forceAttrs(*aOutputs->value); state.forceAttrs(*aOutputs->value, pos);
auto sHydraJobs = state.symbols.create("hydraJobs"); auto sHydraJobs = state.symbols.create("hydraJobs");
@ -475,10 +476,7 @@ struct CmdFlakeCheck : FlakeCommand
state->forceValue(v, pos); state->forceValue(v, pos);
if (!v.isLambda()) if (!v.isLambda())
throw Error("bundler must be a function"); throw Error("bundler must be a function");
if (!v.lambda.fun->formals || // TODO: check types of inputs/outputs?
!v.lambda.fun->formals->argNames.count(state->symbols.create("program")) ||
!v.lambda.fun->formals->argNames.count(state->symbols.create("system")))
throw Error("bundler must take formal arguments 'program' and 'system'");
} catch (Error & e) { } catch (Error & e) {
e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath)); e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath));
reportError(e); reportError(e);
@ -609,14 +607,27 @@ struct CmdFlakeCheck : FlakeCommand
*attr.value, *attr.pos); *attr.value, *attr.pos);
} }
else if (name == "defaultBundler") else if (name == "defaultBundler") {
checkBundler(name, vOutput, pos); state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
checkSystemName(attr.name, *attr.pos);
checkBundler(
fmt("%s.%s", name, attr.name),
*attr.value, *attr.pos);
}
}
else if (name == "bundlers") { else if (name == "bundlers") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) for (auto & attr : *vOutput.attrs) {
checkBundler(fmt("%s.%s", name, attr.name), checkSystemName(attr.name, *attr.pos);
*attr.value, *attr.pos); state->forceAttrs(*attr.value, *attr.pos);
for (auto & attr2 : *attr.value->attrs) {
checkBundler(
fmt("%s.%s.%s", name, attr.name, attr2.name),
*attr2.value, *attr2.pos);
}
}
} }
else else
@ -638,12 +649,14 @@ struct CmdFlakeCheck : FlakeCommand
} }
}; };
static Strings defaultTemplateAttrPathsPrefixes{"templates."};
static Strings defaultTemplateAttrPaths = {"defaultTemplate"};
struct CmdFlakeInitCommon : virtual Args, EvalCommand struct CmdFlakeInitCommon : virtual Args, EvalCommand
{ {
std::string templateUrl = "templates"; std::string templateUrl = "templates";
Path destDir; Path destDir;
const Strings attrsPathPrefixes{"templates."};
const LockFlags lockFlags{ .writeLockFile = false }; const LockFlags lockFlags{ .writeLockFile = false };
CmdFlakeInitCommon() CmdFlakeInitCommon()
@ -658,8 +671,8 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
completeFlakeRefWithFragment( completeFlakeRefWithFragment(
getEvalState(), getEvalState(),
lockFlags, lockFlags,
attrsPathPrefixes, defaultTemplateAttrPathsPrefixes,
{"defaultTemplate"}, defaultTemplateAttrPaths,
prefix); prefix);
}} }}
}); });
@ -674,9 +687,10 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath(".")); auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath("."));
auto installable = InstallableFlake(nullptr, auto installable = InstallableFlake(nullptr,
evalState, std::move(templateFlakeRef), evalState, std::move(templateFlakeRef), templateName,
Strings{templateName == "" ? "defaultTemplate" : templateName}, defaultTemplateAttrPaths,
Strings(attrsPathPrefixes), lockFlags); defaultTemplateAttrPathsPrefixes,
lockFlags);
auto [cursor, attrPath] = installable.getCursor(*evalState); auto [cursor, attrPath] = installable.getCursor(*evalState);

View file

@ -292,6 +292,12 @@ The following attributes are supported in `flake.nix`:
value (e.g. `packages.x86_64-linux` must be an attribute set of value (e.g. `packages.x86_64-linux` must be an attribute set of
derivations built for the `x86_64-linux` platform). derivations built for the `x86_64-linux` platform).
* `nixConfig`: a set of `nix.conf` options to be set when evaluating any
part of a flake. In the interests of security, only a small set of
whitelisted options (currently `bash-prompt`, `bash-prompt-suffix`,
and `flake-registry`) are allowed to be set without confirmation so long as
`accept-flake-config` is not set in the global configuration.
## Flake inputs ## Flake inputs
The attribute `inputs` specifies the dependencies of a flake, as an The attribute `inputs` specifies the dependencies of a flake, as an

View file

@ -76,7 +76,7 @@ the Nix store. Here are the recognised types of installables:
Note that the search will only include files indexed by git. In particular, files Note that the search will only include files indexed by git. In particular, files
which are matched by `.gitignore` or have never been `git add`-ed will not be which are matched by `.gitignore` or have never been `git add`-ed will not be
available in the flake. If this is undesireable, specify `path:<directory>` explicitly; available in the flake. If this is undesirable, specify `path:<directory>` explicitly;
For example, if `/foo/bar` is a git repository with the following structure: For example, if `/foo/bar` is a git repository with the following structure:
``` ```

View file

@ -20,7 +20,10 @@ struct CmdPingStore : StoreCommand
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
notice("Store URL: %s", store->getUri());
store->connect(); store->connect();
if (auto version = store->getVersion())
notice("Version: %s", *version);
} }
}; };

View file

@ -28,17 +28,17 @@ string resolveMirrorUrl(EvalState & state, string url)
Value vMirrors; Value vMirrors;
// FIXME: use nixpkgs flake // FIXME: use nixpkgs flake
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors); state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
state.forceAttrs(vMirrors); state.forceAttrs(vMirrors, noPos);
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
if (mirrorList == vMirrors.attrs->end()) if (mirrorList == vMirrors.attrs->end())
throw Error("unknown mirror name '%s'", mirrorName); throw Error("unknown mirror name '%s'", mirrorName);
state.forceList(*mirrorList->value); state.forceList(*mirrorList->value, noPos);
if (mirrorList->value->listSize() < 1) if (mirrorList->value->listSize() < 1)
throw Error("mirror URL '%s' did not expand to anything", url); throw Error("mirror URL '%s' did not expand to anything", url);
auto mirror = state.forceString(*mirrorList->value->listElems()[0]); string mirror(state.forceString(*mirrorList->value->listElems()[0]));
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1); return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1);
} }
@ -196,11 +196,11 @@ static int main_nix_prefetch_url(int argc, char * * argv)
Value vRoot; Value vRoot;
state->evalFile(path, vRoot); state->evalFile(path, vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first); Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
state->forceAttrs(v); state->forceAttrs(v, noPos);
/* Extract the URL. */ /* Extract the URL. */
auto & attr = v.attrs->need(state->symbols.create("urls")); auto & attr = v.attrs->need(state->symbols.create("urls"));
state->forceList(*attr.value); state->forceList(*attr.value, noPos);
if (attr.value->listSize() < 1) if (attr.value->listSize() < 1)
throw Error("'urls' list is empty"); throw Error("'urls' list is empty");
url = state->forceString(*attr.value->listElems()[0]); url = state->forceString(*attr.value->listElems()[0]);

View file

@ -295,7 +295,11 @@ public:
expectArgs("elements", &_matchers); expectArgs("elements", &_matchers);
} }
typedef std::variant<size_t, Path, std::regex> Matcher; struct RegexPattern {
std::string pattern;
std::regex reg;
};
typedef std::variant<size_t, Path, RegexPattern> Matcher;
std::vector<Matcher> getMatchers(ref<Store> store) std::vector<Matcher> getMatchers(ref<Store> store)
{ {
@ -307,7 +311,7 @@ public:
else if (store->isStorePath(s)) else if (store->isStorePath(s))
res.push_back(s); res.push_back(s);
else else
res.push_back(std::regex(s, std::regex::extended | std::regex::icase)); res.push_back(RegexPattern{s,std::regex(s, std::regex::extended | std::regex::icase)});
} }
return res; return res;
@ -320,9 +324,9 @@ public:
if (*n == pos) return true; if (*n == pos) return true;
} else if (auto path = std::get_if<Path>(&matcher)) { } else if (auto path = std::get_if<Path>(&matcher)) {
if (element.storePaths.count(store.parseStorePath(*path))) return true; if (element.storePaths.count(store.parseStorePath(*path))) return true;
} else if (auto regex = std::get_if<std::regex>(&matcher)) { } else if (auto regex = std::get_if<RegexPattern>(&matcher)) {
if (element.source if (element.source
&& std::regex_match(element.source->attrPath, *regex)) && std::regex_match(element.source->attrPath, regex->reg))
return true; return true;
} }
} }
@ -355,16 +359,30 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem
for (size_t i = 0; i < oldManifest.elements.size(); ++i) { for (size_t i = 0; i < oldManifest.elements.size(); ++i) {
auto & element(oldManifest.elements[i]); auto & element(oldManifest.elements[i]);
if (!matches(*store, element, i, matchers)) if (!matches(*store, element, i, matchers)) {
newManifest.elements.push_back(std::move(element)); newManifest.elements.push_back(std::move(element));
} else {
notice("removing '%s'", element.describe());
}
} }
// FIXME: warn about unused matchers? auto removedCount = oldManifest.elements.size() - newManifest.elements.size();
printInfo("removed %d packages, kept %d packages", printInfo("removed %d packages, kept %d packages",
oldManifest.elements.size() - newManifest.elements.size(), removedCount,
newManifest.elements.size()); newManifest.elements.size());
if (removedCount == 0) {
for (auto matcher: matchers) {
if (const size_t* index = std::get_if<size_t>(&matcher)){
warn("'%d' is not a valid index in profile", *index);
} else if (const Path* path = std::get_if<Path>(&matcher)){
warn("'%s' does not match any paths in profile", *path);
} else if (const RegexPattern* regex = std::get_if<RegexPattern>(&matcher)){
warn("'%s' does not match any packages in profile", regex->pattern);
}
}
warn ("Try `nix profile list` to see the current profile.");
}
updateProfile(newManifest.build(store)); updateProfile(newManifest.build(store));
} }
}; };
@ -405,6 +423,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
this, this,
getEvalState(), getEvalState(),
FlakeRef(element.source->originalRef), FlakeRef(element.source->originalRef),
"",
{element.source->attrPath}, {element.source->attrPath},
{}, {},
lockFlags); lockFlags);

View file

@ -1,7 +1,7 @@
R"MdBoundary( R"MdBoundary(
# Description # Description
Display some informations about the given realisation Display some information about the given realisation
# Examples # Examples

View file

@ -342,7 +342,7 @@ StringSet NixRepl::completePrefix(string prefix)
Expr * e = parseString(expr); Expr * e = parseString(expr);
Value v; Value v;
e->eval(*state, *env, v); e->eval(*state, *env, v);
state->forceAttrs(v); state->forceAttrs(v, noPos);
for (auto & i : *v.attrs) { for (auto & i : *v.attrs) {
string name = i.name; string name = i.name;
@ -463,7 +463,7 @@ bool NixRepl::processLine(string line)
if (v.type() == nPath || v.type() == nString) { if (v.type() == nPath || v.type() == nString) {
PathSet context; PathSet context;
auto filename = state->coerceToString(noPos, v, context); auto filename = state->coerceToString(noPos, v, context);
pos.file = state->symbols.create(filename); pos.file = state->symbols.create(*filename);
} else if (v.isLambda()) { } else if (v.isLambda()) {
pos = v.lambda.fun->pos; pos = v.lambda.fun->pos;
} else { } else {
@ -623,6 +623,9 @@ void NixRepl::loadFile(const Path & path)
void NixRepl::loadFlake(const std::string & flakeRefS) void NixRepl::loadFlake(const std::string & flakeRefS)
{ {
if (flakeRefS.empty())
throw Error("cannot use ':load-flake' without a path specified. (Use '.' for the current working directory.)");
auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true); auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true);
if (evalSettings.pureEval && !flakeRef.input.isImmutable()) if (evalSettings.pureEval && !flakeRef.input.isImmutable())
throw Error("cannot use ':load-flake' on mutable flake reference '%s' (use --impure to override)", flakeRefS); throw Error("cannot use ':load-flake' on mutable flake reference '%s' (use --impure to override)", flakeRefS);
@ -673,7 +676,7 @@ void NixRepl::reloadFiles()
void NixRepl::addAttrsToScope(Value & attrs) void NixRepl::addAttrsToScope(Value & attrs)
{ {
state->forceAttrs(attrs); state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); });
if (displ + attrs.attrs->size() >= envSize) if (displ + attrs.attrs->size() >= envSize)
throw Error("environment full; cannot add more variables"); throw Error("environment full; cannot add more variables");
@ -712,7 +715,7 @@ void NixRepl::evalString(string s, Value & v)
{ {
Expr * e = parseString(s); Expr * e = parseString(s);
e->eval(*state, *env, v); e->eval(*state, *env, v);
state->forceValue(v); state->forceValue(v, [&]() { return v.determinePos(noPos); });
} }
@ -742,7 +745,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
str.flush(); str.flush();
checkInterrupt(); checkInterrupt();
state->forceValue(v); state->forceValue(v, [&]() { return v.determinePos(noPos); });
switch (v.type()) { switch (v.type()) {

File diff suppressed because it is too large Load diff

View file

@ -1,78 +0,0 @@
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
namespace nlohmann
{
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>>
class basic_json;
/*!
@brief JSON Pointer
A JSON pointer defines a string syntax for identifying a specific value
within a JSON document. It can be used with functions `at` and
`operator[]`. Furthermore, JSON pointers are the base for JSON patches.
@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
@since version 2.0.0
*/
template<typename BasicJsonType>
class json_pointer;
/*!
@brief default JSON class
This type is the default specialization of the @ref basic_json class which
uses the standard template types.
@since version 1.0.0
*/
using json = basic_json<>;
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/*!
@brief ordered JSON class
This type preserves the insertion order of object keys.
@since version 3.9.0
*/
using ordered_json = basic_json<nlohmann::ordered_map>;
} // namespace nlohmann
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

View file

@ -1,2 +0,0 @@
$(foreach i, $(wildcard src/nlohmann/*.hpp), \
$(eval $(call install-file-in, $(i), $(includedir)/nlohmann, 0644)))

View file

@ -107,7 +107,7 @@ Path resolveSymlink(const Path & path)
auto target = readLink(path); auto target = readLink(path);
return hasPrefix(target, "/") return hasPrefix(target, "/")
? target ? target
: dirOf(path) + "/" + target; : concatStrings(dirOf(path), "/", target);
} }
std::set<string> resolveTree(const Path & path, PathSet & deps) std::set<string> resolveTree(const Path & path, PathSet & deps)

View file

@ -50,6 +50,6 @@ with import ./config.nix;
fetchurl = import <nix/fetchurl.nix> { fetchurl = import <nix/fetchurl.nix> {
url = "file://" + toString ./lang/eval-okay-xml.exp.xml; url = "file://" + toString ./lang/eval-okay-xml.exp.xml;
sha256 = "0kg4sla7ihm8ijr8cb3117fhl99zrc2bwy1jrngsfmkh8bav4m0v"; sha256 = "sha256-behBlX+DQK/Pjvkuc8Tx68Jwi4E5v86wDq+ZLaHyhQE=";
}; };
} }

35
tests/flake-bundler.sh Normal file
View file

@ -0,0 +1,35 @@
source common.sh
clearStore
rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local
cp ./simple.nix ./simple.builder.sh ./config.nix $TEST_HOME
cd $TEST_HOME
cat <<EOF > flake.nix
{
outputs = {self}: {
bundlers.$system.simple = drv:
if drv?type && drv.type == "derivation"
then drv
else self.defaultPackage.$system;
defaultBundler.$system = self.bundlers.$system.simple;
defaultPackage.$system = import ./simple.nix;
defaultApp.$system = {
type = "app";
program = "\${import ./simple.nix}/hello";
};
};
}
EOF
nix build .#
nix bundle --bundler .# .#
nix bundle --bundler .#defaultBundler.$system .#defaultPackage.$system
nix bundle --bundler .#bundlers.$system.simple .#defaultPackage.$system
nix bundle --bundler .#defaultBundler.$system .#defaultApp.$system
nix bundle --bundler .#bundlers.$system.simple .#defaultApp.$system
clearStore

View file

@ -730,6 +730,7 @@ cat > $flakeFollowsB/flake.nix <<EOF
description = "Flake B"; description = "Flake B";
inputs = { inputs = {
foobar.url = "path:$flakeFollowsA/flakeE"; foobar.url = "path:$flakeFollowsA/flakeE";
goodoo.follows = "C/goodoo";
C = { C = {
url = "path:./flakeC"; url = "path:./flakeC";
inputs.foobar.follows = "foobar"; inputs.foobar.follows = "foobar";
@ -744,6 +745,7 @@ cat > $flakeFollowsC/flake.nix <<EOF
description = "Flake C"; description = "Flake C";
inputs = { inputs = {
foobar.url = "path:$flakeFollowsA/flakeE"; foobar.url = "path:$flakeFollowsA/flakeE";
goodoo.follows = "foobar";
}; };
outputs = { ... }: {}; outputs = { ... }: {};
} }
@ -759,7 +761,7 @@ EOF
cat > $flakeFollowsE/flake.nix <<EOF cat > $flakeFollowsE/flake.nix <<EOF
{ {
description = "Flake D"; description = "Flake E";
inputs = {}; inputs = {};
outputs = { ... }: {}; outputs = { ... }: {};
} }
@ -768,6 +770,8 @@ EOF
git -C $flakeFollowsA add flake.nix flakeB/flake.nix \ git -C $flakeFollowsA add flake.nix flakeB/flake.nix \
flakeB/flakeC/flake.nix flakeD/flake.nix flakeE/flake.nix flakeB/flakeC/flake.nix flakeD/flake.nix flakeE/flake.nix
nix flake metadata $flakeFollowsA
nix flake update $flakeFollowsA nix flake update $flakeFollowsA
oldLock="$(cat "$flakeFollowsA/flake.lock")" oldLock="$(cat "$flakeFollowsA/flake.lock")"

View file

@ -31,9 +31,9 @@
<attr name="f"> <attr name="f">
<function> <function>
<attrspat> <attrspat>
<attr name="z" />
<attr name="x" /> <attr name="x" />
<attr name="y" /> <attr name="y" />
<attr name="z" />
</attrspat> </attrspat>
</function> </function>
</attr> </attr>

View file

@ -48,6 +48,7 @@ nix_tests = \
flakes.sh \ flakes.sh \
flake-local-settings.sh \ flake-local-settings.sh \
flake-searching.sh \ flake-searching.sh \
flake-bundler.sh \
build.sh \ build.sh \
repl.sh ca/repl.sh \ repl.sh ca/repl.sh \
ca/build.sh \ ca/build.sh \
@ -62,6 +63,8 @@ nix_tests = \
ca/nix-copy.sh \ ca/nix-copy.sh \
eval-store.sh \ eval-store.sh \
readfile-context.sh \ readfile-context.sh \
store-ping.sh \
nix_path.sh \
why-depends.sh why-depends.sh
# parallel.sh # parallel.sh

11
tests/nix_path.sh Normal file
View file

@ -0,0 +1,11 @@
# Regression for https://github.com/NixOS/nix/issues/5998 and https://github.com/NixOS/nix/issues/5980
source common.sh
export NIX_PATH=non-existent=/non-existent/but-unused-anyways:by-absolute-path=$PWD:by-relative-path=.
nix-instantiate --eval -E '<by-absolute-path/simple.nix>' --restrict-eval
nix-instantiate --eval -E '<by-relative-path/simple.nix>' --restrict-eval
# Should ideally also test this, but theres no pure way to do it, so just trust me that it works
# nix-instantiate --eval -E '<nixpkgs>' -I nixpkgs=channel:nixos-unstable --restrict-eval

View file

@ -40,3 +40,26 @@ testRepl () {
testRepl testRepl
# Same thing (kind-of), but with a remote store. # Same thing (kind-of), but with a remote store.
testRepl --store "$TEST_ROOT/store?real=$NIX_STORE_DIR" testRepl --store "$TEST_ROOT/store?real=$NIX_STORE_DIR"
testReplResponse () {
local response="$(nix repl <<< "$1")"
echo "$response" | grep -qs "$2" \
|| fail "repl command set:
$1
does not respond with:
$2
but with:
$response"
}
# :a uses the newest version of a symbol
testReplResponse '
:a { a = "1"; }
:a { a = "2"; }
"result: ${a}"
' "result: 2"

13
tests/store-ping.sh Normal file
View file

@ -0,0 +1,13 @@
source common.sh
STORE_INFO=$(nix store ping 2>&1)
echo "$STORE_INFO" | grep "Store URL: ${NIX_REMOTE}"
if [[ -v NIX_DAEMON_PACKAGE ]] && isDaemonNewer "2.7.0pre20220126"; then
DAEMON_VERSION=$($NIX_DAEMON_PACKAGE/bin/nix-daemon --version | cut -d' ' -f3)
echo "$STORE_INFO" | grep "Version: $DAEMON_VERSION"
fi
expect 127 NIX_REMOTE=unix:$PWD/store nix store ping || \
fail "nix store ping on a non-existent store should fail"