mirror of
https://github.com/NixOS/nix.git
synced 2024-11-22 06:42:28 +00:00
Merge pull request #11058 from hercules-ci/more-nix-shell
Make `#!nix-shell` arguments and options relative to script
This commit is contained in:
commit
1e1a8e8ad1
62
doc/manual/rl-next/shebang-relative.md
Normal file
62
doc/manual/rl-next/shebang-relative.md
Normal file
@ -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
|
||||||
|
```
|
@ -214,7 +214,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
|
|||||||
auto v = state.allocValue();
|
auto v = state.allocValue();
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const AutoArgExpr & arg) {
|
[&](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) {
|
[&](const AutoArgString & arg) {
|
||||||
v->mkString(arg.s);
|
v->mkString(arg.s);
|
||||||
|
@ -7,12 +7,29 @@ struct CompatibilitySettings : public Config
|
|||||||
|
|
||||||
CompatibilitySettings() = default;
|
CompatibilitySettings() = default;
|
||||||
|
|
||||||
|
// Added in Nix 2.24, July 2024.
|
||||||
Setting<bool> nixShellAlwaysLooksForShellNix{this, true, "nix-shell-always-looks-for-shell-nix", R"(
|
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.
|
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.
|
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.
|
||||||
)"};
|
)"};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ struct Completions final : AddCompletions
|
|||||||
*/
|
*/
|
||||||
class RootArgs : virtual public Args
|
class RootArgs : virtual public Args
|
||||||
{
|
{
|
||||||
|
protected:
|
||||||
/**
|
/**
|
||||||
* @brief The command's "working directory", but only set when top level.
|
* @brief The command's "working directory", but only set when top level.
|
||||||
*
|
*
|
||||||
|
@ -183,6 +183,9 @@ static void main_nix_build(int argc, char * * argv)
|
|||||||
struct MyArgs : LegacyArgs, MixEvalArgs
|
struct MyArgs : LegacyArgs, MixEvalArgs
|
||||||
{
|
{
|
||||||
using LegacyArgs::LegacyArgs;
|
using LegacyArgs::LegacyArgs;
|
||||||
|
void setBaseDir(Path baseDir) {
|
||||||
|
commandBaseDir = baseDir;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MyArgs myArgs(myName, [&](Strings::iterator & arg, const Strings::iterator & end) {
|
MyArgs myArgs(myName, [&](Strings::iterator & arg, const Strings::iterator & end) {
|
||||||
@ -290,6 +293,9 @@ static void main_nix_build(int argc, char * * argv)
|
|||||||
state->repair = myArgs.repair;
|
state->repair = myArgs.repair;
|
||||||
if (myArgs.repair) buildMode = bmRepair;
|
if (myArgs.repair) buildMode = bmRepair;
|
||||||
|
|
||||||
|
if (inShebang && compatibilitySettings.nixShellShebangArgumentsRelativeToScript) {
|
||||||
|
myArgs.setBaseDir(absPath(dirOf(script)));
|
||||||
|
}
|
||||||
auto autoArgs = myArgs.getAutoArgs(*state);
|
auto autoArgs = myArgs.getAutoArgs(*state);
|
||||||
|
|
||||||
auto autoArgsWithInNixShell = autoArgs;
|
auto autoArgsWithInNixShell = autoArgs;
|
||||||
@ -334,8 +340,13 @@ static void main_nix_build(int argc, char * * argv)
|
|||||||
exprs = {state->parseStdin()};
|
exprs = {state->parseStdin()};
|
||||||
else
|
else
|
||||||
for (auto i : remainingArgs) {
|
for (auto i : remainingArgs) {
|
||||||
|
auto baseDir = inShebang && !packages ? absPath(dirOf(script)) : i;
|
||||||
|
|
||||||
if (fromArgs)
|
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 {
|
else {
|
||||||
auto absolute = i;
|
auto absolute = i;
|
||||||
try {
|
try {
|
||||||
|
@ -65,6 +65,25 @@ chmod a+rx $TEST_ROOT/shell.shebang.sh
|
|||||||
output=$($TEST_ROOT/shell.shebang.sh abc def)
|
output=$($TEST_ROOT/shell.shebang.sh abc def)
|
||||||
[ "$output" = "foo bar 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.
|
# 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.
|
# 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
|
sed -e "s|@ENV_PROG@|$(type -P env)|" shell.shebang.sh > $TEST_ROOT/spaced\ \\\'\"shell.shebang.sh
|
||||||
|
@ -46,6 +46,7 @@ let pkgs = rec {
|
|||||||
ASCII_PERCENT = "%";
|
ASCII_PERCENT = "%";
|
||||||
ASCII_AT = "@";
|
ASCII_AT = "@";
|
||||||
TEST_inNixShell = if inNixShell then "true" else "false";
|
TEST_inNixShell = if inNixShell then "true" else "false";
|
||||||
|
FOO = fooContents;
|
||||||
inherit stdenv;
|
inherit stdenv;
|
||||||
outputs = ["dev" "out"];
|
outputs = ["dev" "out"];
|
||||||
} // {
|
} // {
|
||||||
|
9
tests/functional/shell.shebang.expr
Executable file
9
tests/functional/shell.shebang.expr
Executable file
@ -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
tests/functional/shell.shebang.legacy.expr
Executable file
10
tests/functional/shell.shebang.legacy.expr
Executable file
@ -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…
Reference in New Issue
Block a user