From b865625a8eab8382e1643605594e9db23271c2e5 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 6 Jul 2024 21:31:24 +0200 Subject: [PATCH] nix-shell: Look for shell.nix when directory is specified --- src/libexpr/eval.cc | 4 ++-- src/libexpr/eval.hh | 4 +++- src/nix-build/nix-build.cc | 34 ++++++++++++++++++++++----- tests/functional/nix-shell.sh | 44 +++++++++++++++++++++++++++++++++++ tests/functional/shell.nix | 11 +++++++++ 5 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 48ed66883..2a0862123 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -2650,7 +2650,7 @@ void EvalState::printStatistics() } -SourcePath resolveExprPath(SourcePath path) +SourcePath resolveExprPath(SourcePath path, bool addDefaultNix) { unsigned int followCount = 0, maxFollow = 1024; @@ -2666,7 +2666,7 @@ SourcePath resolveExprPath(SourcePath path) } /* If `path' refers to a directory, append `/default.nix'. */ - if (path.resolveSymlinks().lstat().type == SourceAccessor::tDirectory) + if (addDefaultNix && path.resolveSymlinks().lstat().type == SourceAccessor::tDirectory) return path / "default.nix"; return path; diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index b84bc9907..e45358055 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -850,8 +850,10 @@ std::string showType(const Value & v); /** * If `path` refers to a directory, then append "/default.nix". + * + * @param addDefaultNix Whether to append "/default.nix" after resolving symlinks. */ -SourcePath resolveExprPath(SourcePath path); +SourcePath resolveExprPath(SourcePath path, bool addDefaultNix = true); /** * Whether a URI is allowed, assuming restrictEval is enabled diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc index e873f712b..30cc86456 100644 --- a/src/nix-build/nix-build.cc +++ b/src/nix-build/nix-build.cc @@ -90,6 +90,26 @@ static std::vector shellwords(const std::string & s) return res; } +/** + * Like `resolveExprPath`, but prefers `shell.nix` instead of `default.nix`, + * and if `path` was a directory, it checks eagerly whether `shell.nix` or + * `default.nix` exist, throwing an error if they don't. + */ +static SourcePath resolveShellExprPath(SourcePath path) +{ + auto resolvedOrDir = resolveExprPath(path, false); + if (resolvedOrDir.resolveSymlinks().lstat().type == SourceAccessor::tDirectory) { + if ((resolvedOrDir / "shell.nix").pathExists()) { + return resolvedOrDir / "shell.nix"; + } + if ((resolvedOrDir / "default.nix").pathExists()) { + return resolvedOrDir / "default.nix"; + } + throw Error("neither '%s' nor '%s' found in '%s'", "shell.nix", "default.nix", resolvedOrDir); + } + return resolvedOrDir; +} + static void main_nix_build(int argc, char * * argv) { auto dryRun = false; @@ -281,11 +301,12 @@ static void main_nix_build(int argc, char * * argv) joined << "]; } \"\""; fromArgs = true; remainingArgs = {joined.str()}; - } else if (!fromArgs) { - if (remainingArgs.empty() && isNixShell && pathExists("shell.nix")) - remainingArgs = {"shell.nix"}; - if (remainingArgs.empty()) - remainingArgs = {"default.nix"}; + } else if (!fromArgs && remainingArgs.empty()) { + remainingArgs = {"."}; + + // Instead of letting it throw later, we throw here to give a more relevant error message + if (isNixShell && !std::filesystem::exists("shell.nix") && !std::filesystem::exists("default.nix")) + throw Error("no argument specified and no '%s' or '%s' file found in the working directory", "shell.nix", "default.nix"); } if (isNixShell) @@ -317,7 +338,8 @@ static void main_nix_build(int argc, char * * argv) auto sourcePath = lookupFileArg(*state, baseDir); - auto resolvedPath = resolveExprPath(sourcePath); + auto resolvedPath = + isNixShell ? resolveShellExprPath(sourcePath) : resolveExprPath(sourcePath); exprs.push_back(state->parseExprFromFile(resolvedPath)); } diff --git a/tests/functional/nix-shell.sh b/tests/functional/nix-shell.sh index 2c94705de..b7a7db27c 100755 --- a/tests/functional/nix-shell.sh +++ b/tests/functional/nix-shell.sh @@ -91,6 +91,50 @@ sed -e "s|@ENV_PROG@|$(type -P env)|" shell.shebang.nix > $TEST_ROOT/shell.sheba chmod a+rx $TEST_ROOT/shell.shebang.nix $TEST_ROOT/shell.shebang.nix +mkdir $TEST_ROOT/lookup-test $TEST_ROOT/empty + +cp $shellDotNix $TEST_ROOT/lookup-test/shell.nix +cp config.nix $TEST_ROOT/lookup-test/ +echo 'abort "do not load default.nix!"' > $TEST_ROOT/lookup-test/default.nix + +nix-shell $TEST_ROOT/lookup-test -A shellDrv --run 'echo "it works"' | grepQuiet "it works" +# https://github.com/NixOS/nix/issues/4529 +nix-shell -I "testRoot=$TEST_ROOT" '' -A shellDrv --run 'echo "it works"' | grepQuiet "it works" + +( + cd $TEST_ROOT/empty; + expectStderr 1 nix-shell | \ + grepQuiet "error.*no argument specified and no .*shell\.nix.* or .*default\.nix.* file found in the working directory" +) + +expectStderr 1 nix-shell -I "testRoot=$TEST_ROOT" '' | + grepQuiet "error.*neither .*shell\.nix.* nor .*default\.nix.* found in .*/empty" + +cat >$TEST_ROOT/lookup-test/shebangscript <<"EOF" +#!/usr/bin/env nix-shell +#!nix-shell -A shellDrv -i bash +[[ $VAR_FROM_NIX == bar ]] +echo "script works" +EOF +chmod +x $TEST_ROOT/lookup-test/shebangscript + +$TEST_ROOT/lookup-test/shebangscript | grepQuiet "script works" + +# https://github.com/NixOS/nix/issues/5431 +mkdir $TEST_ROOT/marco{,/polo} +echo 'abort "marco/shell.nix must not be used, but its mere existence used to cause #5431"' > $TEST_ROOT/marco/shell.nix +cat >$TEST_ROOT/marco/polo/default.nix <