Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make #!nix-shell arguments and options relative to script #11058

Merged
merged 29 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0402336
Evaluate nix-shell -i args relative to script
matthewbauer Aug 3, 2021
9a46411
tests: ensure nix-shell uses relative paths for expressions
tomberek Nov 26, 2023
f66f498
notes: document change in nix-shell behavior
tomberek Nov 26, 2023
1318135
Refactor: rename runEnv -> isNixShell
roberth Jul 6, 2024
5c367ec
Refactor: rename left -> remainingArgs
roberth Jul 6, 2024
e9479b2
nix-build.cc: Refactor: extract baseDir variable
roberth Jul 6, 2024
76245ff
nix-build.cc: Refactor: extract sourcePath, resolvedPath variables
roberth Jul 6, 2024
32fb127
Add legacy setting: nix-shell-always-looks-for-shell-nix
roberth Jul 6, 2024
a22f8b5
rl-next: Add note about shell.nix lookups
roberth Jul 6, 2024
b865625
nix-shell: Look for shell.nix when directory is specified
roberth Jul 6, 2024
afbe7c3
rl-next: Enter PR
roberth Jul 6, 2024
8838f5c
Merge remote-tracking branch 'matthewbauer/nix-shell-relative-shebang…
roberth Jul 6, 2024
d5854f3
rl-next: Typo
roberth Jul 6, 2024
f5b59fb
Fix and extend nix-shell baseDir test
roberth Jul 6, 2024
6c6d526
Add legacy setting: nix-shell-always-looks-for-shell-nix
roberth Jul 6, 2024
6959ac1
rl-next: Add note about shell.nix lookups
roberth Jul 6, 2024
4c59d6e
Merge branch 'nix-shell-lookup-shell-nix' into more-nix-shell
roberth Jul 6, 2024
63262e7
Add opt-out: nix-shell-shebang-arguments-relative-to-script
roberth Jul 6, 2024
73602a7
nix-shell: Look for shell.nix when directory is specified
roberth Jul 6, 2024
2f1fada
Add legacy setting: nix-shell-always-looks-for-shell-nix
roberth Jul 6, 2024
c4a20a4
rl-next: Add note about shell.nix lookups
roberth Jul 6, 2024
0f8a655
tests/functional/shell.nix: Implement runHook for dummy stdenv
roberth Jul 7, 2024
e1106b4
tests/functional/nix-shell.sh: Fix Polo test for VM test
roberth Jul 7, 2024
3e424b1
Merge branch 'nix-shell-lookup-shell-nix' into more-nix-shell
roberth Jul 7, 2024
193dd5d
Fixup: add missing test file
roberth Jul 7, 2024
d942d50
Merge remote-tracking branch 'upstream/master' into more-nix-shell
roberth Jul 10, 2024
c4e3e2d
Soft-deprecate the compatibility settings
roberth Jul 10, 2024
6f5f741
doc/rl-next/shebang-relative: Update with example
roberth Jul 11, 2024
bb312a7
Edit CompatibilitySettings
roberth Jul 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions doc/manual/rl-next/shebang-relative.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
synopsis: "`nix-shell` shebang uses relative path"
prs:
- 5088
- 11058
issues:
- 4232
---

<!-- unfortunately no link target for the specific syntax -->
Relative [path](@docroot@/language/values.md#type-path) literals in `nix-shell` shebang scripts' options are now resolved relative to the [script's location](@docroot@/glossary?highlight=base%20directory#gloss-base-directory).
Previously they were resolved relative to the current working directory.

For example, consider the following script in `~/myproject/say-hi`:

```shell
#!/usr/bin/env nix-shell
#!nix-shell --expr 'import ./shell.nix'
#!nix-shell --arg toolset './greeting-tools.nix'
#!nix-shell -i bash
hello
```

Older versions of `nix-shell` would resolve `shell.nix` relative to the current working directory; home in this example:

```console
[hostname:~]$ ./myproject/say-hi
error:
… while calling the 'import' builtin
at «string»:1:2:
1| (import ./shell.nix)
| ^

error: path '/home/user/shell.nix' does not exist
```

Since this release, `nix-shell` resolves `shell.nix` relative to the script's location, and `~/myproject/shell.nix` is used.

```console
$ ./myproject/say-hi
Hello, world!
```

**Opt-out**

This is technically a breaking change, so we have added an option so you can adapt independently of your Nix update.
The old behavior can be opted into by setting the option [`nix-shell-shebang-arguments-relative-to-script`](@docroot@/command-ref/conf-file.md#conf-nix-shell-shebang-arguments-relative-to-script) to `false`.
This option will be removed in a future release.

**`nix` command shebang**

The experimental [`nix` command shebang](@docroot@/command-ref/new-cli/nix.md?highlight=shebang#shebang-interpreter) already behaves in this script-relative manner.

Example:

```shell
#!/usr/bin/env nix
#!nix develop
#!nix --expr ``import ./shell.nix``
#!nix -c bash
hello
```
2 changes: 1 addition & 1 deletion src/libcmd/common-eval-args.cc
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
auto v = state.allocValue();
std::visit(overloaded {
[&](const AutoArgExpr & arg) {
state.mkThunk_(*v, state.parseExprFromString(arg.expr, state.rootPath(".")));
state.mkThunk_(*v, state.parseExprFromString(arg.expr, compatibilitySettings.nixShellShebangArgumentsRelativeToScript ? state.rootPath(absPath(getCommandBaseDir())) : state.rootPath(".")));
},
[&](const AutoArgString & arg) {
v->mkString(arg.s);
Expand Down
19 changes: 18 additions & 1 deletion src/libcmd/compatibility-settings.hh
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,29 @@ struct CompatibilitySettings : public Config

CompatibilitySettings() = default;

// Added in Nix 2.24, July 2024.
Setting<bool> nixShellAlwaysLooksForShellNix{this, true, "nix-shell-always-looks-for-shell-nix", R"(
Before Nix 2.24, [`nix-shell`](@docroot@/command-ref/nix-shell.md) would only look at `shell.nix` if it was in the working directory - when no file was specified.

Since Nix 2.24, `nix-shell` always looks for a `shell.nix`, whether that's in the working directory, or in a directory that was passed as an argument.

You may set this to `false` to revert to the Nix 2.3 behavior.
You may set this to `false` to temporarily revert to the behavior of Nix 2.23 and older.

Using this setting is not recommended.
It will be deprecated and removed.
)"};

// Added in Nix 2.24, July 2024.
Setting<bool> nixShellShebangArgumentsRelativeToScript{
this, true, "nix-shell-shebang-arguments-relative-to-script", R"(
Before Nix 2.24, relative file path expressions in arguments in a `nix-shell` shebang were resolved relative to the working directory.

Since Nix 2.24, `nix-shell` resolves these paths in a manner that is relative to the [base directory](@docroot@/glossary.md#gloss-base-directory), defined as the script's directory.

You may set this to `false` to temporarily revert to the behavior of Nix 2.23 and older.

Using this setting is not recommended.
It will be deprecated and removed.
)"};
};

Expand Down
1 change: 1 addition & 0 deletions src/libutil/args/root.hh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct Completions final : AddCompletions
*/
class RootArgs : virtual public Args
{
protected:
/**
* @brief The command's "working directory", but only set when top level.
*
Expand Down
13 changes: 12 additions & 1 deletion src/nix-build/nix-build.cc
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ static void main_nix_build(int argc, char * * argv)
struct MyArgs : LegacyArgs, MixEvalArgs
{
using LegacyArgs::LegacyArgs;
void setBaseDir(Path baseDir) {
commandBaseDir = baseDir;
}
};

MyArgs myArgs(myName, [&](Strings::iterator & arg, const Strings::iterator & end) {
Expand Down Expand Up @@ -290,6 +293,9 @@ static void main_nix_build(int argc, char * * argv)
state->repair = myArgs.repair;
if (myArgs.repair) buildMode = bmRepair;

if (inShebang && compatibilitySettings.nixShellShebangArgumentsRelativeToScript) {
myArgs.setBaseDir(absPath(dirOf(script)));
}
auto autoArgs = myArgs.getAutoArgs(*state);

auto autoArgsWithInNixShell = autoArgs;
Expand Down Expand Up @@ -334,8 +340,13 @@ static void main_nix_build(int argc, char * * argv)
exprs = {state->parseStdin()};
else
for (auto i : remainingArgs) {
auto baseDir = inShebang && !packages ? absPath(dirOf(script)) : i;

if (fromArgs)
exprs.push_back(state->parseExprFromString(std::move(i), state->rootPath(".")));
exprs.push_back(state->parseExprFromString(
std::move(i),
(inShebang && compatibilitySettings.nixShellShebangArgumentsRelativeToScript) ? lookupFileArg(*state, baseDir) : state->rootPath(".")
));
else {
auto absolute = i;
try {
Expand Down
19 changes: 19 additions & 0 deletions tests/functional/nix-shell.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,25 @@ chmod a+rx $TEST_ROOT/shell.shebang.sh
output=$($TEST_ROOT/shell.shebang.sh abc def)
[ "$output" = "foo bar abc def" ]

# Test nix-shell shebang mode with an alternate working directory
sed -e "s|@ENV_PROG@|$(type -P env)|" shell.shebang.expr > $TEST_ROOT/shell.shebang.expr
chmod a+rx $TEST_ROOT/shell.shebang.expr
# Should fail due to expressions using relative path
! $TEST_ROOT/shell.shebang.expr bar
cp shell.nix config.nix $TEST_ROOT
# Should succeed
echo "cwd: $PWD"
output=$($TEST_ROOT/shell.shebang.expr bar)
[ "$output" = foo ]

# Test nix-shell shebang mode with an alternate working directory
sed -e "s|@ENV_PROG@|$(type -P env)|" shell.shebang.legacy.expr > $TEST_ROOT/shell.shebang.legacy.expr
chmod a+rx $TEST_ROOT/shell.shebang.legacy.expr
# Should fail due to expressions using relative path
mkdir -p "$TEST_ROOT/somewhere-unrelated"
output="$(cd "$TEST_ROOT/somewhere-unrelated"; $TEST_ROOT/shell.shebang.legacy.expr bar;)"
[[ $(realpath "$output") = $(realpath "$TEST_ROOT/somewhere-unrelated") ]]

# Test nix-shell shebang mode again with metacharacters in the filename.
# First word of filename is chosen to not match any file in the test root.
sed -e "s|@ENV_PROG@|$(type -P env)|" shell.shebang.sh > $TEST_ROOT/spaced\ \\\'\"shell.shebang.sh
Expand Down
1 change: 1 addition & 0 deletions tests/functional/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ let pkgs = rec {
ASCII_PERCENT = "%";
ASCII_AT = "@";
TEST_inNixShell = if inNixShell then "true" else "false";
FOO = fooContents;
inherit stdenv;
outputs = ["dev" "out"];
} // {
Expand Down
9 changes: 9 additions & 0 deletions tests/functional/shell.shebang.expr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#! @ENV_PROG@ nix-shell
#! nix-shell "{ script, path, ... }: assert path == ./shell.nix; script { }"
#! nix-shell --no-substitute
#! nix-shell --expr
#! nix-shell --arg script "import ./shell.nix"
#! nix-shell --arg path "./shell.nix"
#! nix-shell -A shellDrv
#! nix-shell -i bash
echo "$FOO"
10 changes: 10 additions & 0 deletions tests/functional/shell.shebang.legacy.expr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#! @ENV_PROG@ nix-shell
#! nix-shell "{ script, path, ... }: assert path == ./shell.nix; script { fooContents = toString ./.; }"
#! nix-shell --no-substitute
#! nix-shell --expr
#! nix-shell --arg script "import ((builtins.getEnv ''TEST_ROOT'')+''/shell.nix'')"
#! nix-shell --arg path "./shell.nix"
#! nix-shell -A shellDrv
#! nix-shell -i bash
#! nix-shell --option nix-shell-shebang-arguments-relative-to-script false
echo "$FOO"
Loading