-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Shebang support with flakes #5189
Conversation
e8231c5
to
8b6fcce
Compare
I really like this. I didn't think we could use My only objection as far as the design is concerned, is that this isn't about shells at all, but on closer reading, this isn't specific to the dubious |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of comments, but I really like the overall idea.
automatically inject "shell" argument?
I have a mixed feeling on that one: On the one hand, the only other command that (I think) could make sense in a shebang is nix develop
, and that’s really an edge-case. On the other hand, having a too different behaviour when Nix is invoked as a shebang would probably be quite confusing (“why is ./myScript
working but nix myScript
returning some nonsense?”)
whitelist/blacklist certain options
I don’t think that would really be useful. At least from a security pov, you’re running an arbitrary script anyways, so restricting the interpreter wouldn’t change a lot.
support a bootstrap mechanism detecting if nix is installed and fetching otherwise (eg nix —store or nix-portable)?
How could that work? If you don’t have Nix installed, you can’t run the shebang at all afaik
src/libutil/args.cc
Outdated
if (runEnv && cmdline.size() > 0) { | ||
auto script = *cmdline.begin(); | ||
try { | ||
auto lines = tokenizeString<Strings>(readFile(script), "\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t know how much of an issue that is in practice, but this code reads the whole file, even when it bails right after because it doesn’t start by a shebang. Maybe you could use an FdSource
or similar instead to only read as much as is needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this? tomberek@cf02a4f
Could not see how to use FdSource for this use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that approach somewhat sane in a C++ world?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks alright.
The #!
check is a bit complicated, but at least it won't suffer when the first "line" is very long (e.g. not a text file). When it looks like a shebang, you'll still want to ignore the whole line though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like tomberek@cf02a4f.
It would also be nice to have a way to have an explicit final nix shebang lines, so that we can stop scanning. This would allow for huge files to start with a nix shebang, which is useful for installers. Perhaps a line with #! nix-end-of-arguments
or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another issue I’ve sometime hit is the desire to have a bit of shell parsing of the command line, or access to a SOURCE_SCRIPT var. (it’s an XY problem situation, I’m still trying to support perlPackages, etc)
To support |
Another issue that has come up as I'm writing the docs (stealing most of it from nix-shell), is that we don't run the setup or shell hooks to modify PYTHONPATH or PERL5LIB and so forth. Not sure if we want to adopt exactly the legacy nix-shell behavior which led us down an convoluted path. Is there an easier way? |
8b6fcce
to
31e9285
Compare
This is why we need to rename the current I would like Nix to defer to packages, allowing it to take "installing to environment variables" seriously. Maybe it's overkill because for regular commands Previously my (and apparently Eelco's) assumption was that we don't need the stdenv/setup-based ad-hoc shells like you are asking for. What's your use case? For packaging we've assumed writing an expression is best. Anyway that's all fairly orthogonal to the shebang implementation, as long as we don't
I hope I was able to provide some context. |
I find utterly confusing references to derivations are written as I think we should also support a varadic derivation references until the end of the line (e.g. |
The Note that Example:
And in case we're invoking an interpreter that can and must ignore
|
I agree that `#!nix` would be much more idiomatic. I think making both `#!` and `#! nix` valid will just create FUD for no reason.
Do you have examples of shebang-aware interpreters that'd play nice with nix shebangs as described in this document?
I've never seen them in the wild (doesn't mean they don't exist), but the idea of supporting bot cases does seem a bit feature-creep to me.
On 2 September 2021 12:45:22 CEST, Robert Hensing ***@***.***> wrote:
The `#! nix` has the same role as `#!nix-shell` which seemed to have served a technical purpose for potentially mixing shebang aware interpreters. I wonder to what extend this is required. Perhaps it could be optional? For example, we could strip either `#!` or `#!nix`, whichever is a longer prefix. That way, we only need to confuse the reader when it's necessary for technical reasons.
Note that `#! nix` with the space would then be parsed as a `#!` prefix and a `nix` argument instead of `#! nix` as a prefix. I think this is an improvement, because `#!nix` reads a bit more like a single keyword.
--
You are receiving this because you commented.
Reply to this email directly or view it on GitHub:
#5189 (comment)
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.
|
The proposed approach is variadic and can be put all on one line. Sometimes these lines end up very long and having them split up can be helpful. As to the prefix, this was meant to copy the way nix-shell does it( most of the code was simply copied or adopted over). There are several shebang-aware interpreters, eg perl. I’m sure there are more. The biggest issue is being unable to load the stdenv in the “right way”. I suspect that is actually what people will want. The examples of legacy nix-shell show bringing in python and Perl libraries and that will only work if we run shellHooks or at least do more than just modify PATH. |
Forgive me if I am wrong, but is it really necessary considering
That way your example becomes:
Aside from multi-line support, I don't see any problem here. |
@ymeister that's a good suggestion, but it is not universally supported on the other unixes. It also doesn't allow spaces in arguments for use with |
The original My greater concern is that examples such as (https://github.com/NixOS/nix/blob/7ab3bc32b139e251ff8d9abf25d6cadf3c0c6945/doc/manual/src/command-ref/nix-shell.md#use-as-a--interpreter) won't work with
To make (1)(3) possible Another approach
This would create three distinct tools:
|
@roberth that's a good notice. Albeit the wide adoption of
( Above code also showcases an example of a workaround of the issue discussed by @tomberek. Although I agree that it is just a workaround and is nowhere near a proper solution like one proposed by @tomberek. |
I may have been wrong about |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/nix-shell-with-shebang/17088/2 |
Please make sure these 2 keep working, Thanks. #!/usr/bin/env nix-shell
//! ```cargo
//! [dependencies]
//! time = "0.1.25"
//! ```
/*
#!nix-shell -i rust-script -p rustc -p rust-script -p cargo
*/
fn main() {
for argument in std::env::args().skip(1) {
println!("{}", argument);
};
println!("{}", std::env::var("HOME").expect(""));
println!("{}", time::now().rfc822z());
}
// vim: ft=rust #!/usr/bin/env nix-shell
#![allow()] /*
#!nix-shell -i bash -p rustc
rsfile="$(readlink -f $0)"
binfile="/tmp/$(basename "$rsfile").bin"
rustc "$rsfile" -o "$binfile" --edition=2021 && exec "$binfile" $@ || exit $?
*/
fn main() {
for argument in std::env::args().skip(1) {
println!("{}", argument);
};
println!("{}", std::env::var("HOME").expect(""));
}
// vim: ft=rust |
For anyone stumbling onto this in the future, you can avoid having to set NIX_PATH by using the following (as roberth hinted):
|
31e9285
to
86140ca
Compare
src/libutil/args.cc
Outdated
auto isNixCommand = std::regex_search(programName, std::regex("nix$")); | ||
if (isNixCommand && cmdline.size() > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a nice declaration would be
void Args::parseCmdline(const Strings & _cmdline, bool allowShebang = false);
This solves the architectural concern and a default is nicer than an overload I think.
@@ -69,6 +76,36 @@ void Args::parseCmdline(const Strings & _cmdline) | |||
} | |||
|
|||
bool argsSeen = false; | |||
|
|||
// Heuristic to see if we're invoked as a shebang script, namely, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This requires quite a lot of refactoring in nix-build.cc
, which seems disproportionate. I'm not convinced that the resulting abstraction would be useful, and coupling to the very stable nix-build.cc
may well be counterproductive.
That said, factoring out some static functions / private methods will probably make the code easier to follow.
std::smatch match; | ||
if (std::regex_match(line, match, std::regex("^#!\\s*nix\\s(.*)$"))) | ||
for (const auto & word : shellwords(match[1].str())) | ||
cmdline.push_back(word); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation seems to strike a good balance. We may learn more in the experimental phase.
Resolving this thread.
case '"': | ||
cur.append(begin, it); | ||
begin = it + 1; | ||
st = st == sBegin ? sQuote : sBegin; | ||
break; | ||
case '\\': | ||
/* perl shellwords mostly just treats the next char as part of the string with no special processing */ | ||
cur.append(begin, it); | ||
begin = ++it; | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to comment on moved code, but I think shellwords
isn't quite appropriate for this use case. I think we should fork it to be more predictable and future proof.
By restricting the syntax, we can be more compatible with the shell (in the shebang -> shell direction only of course; wouldn't want to reimplement a shell - in the limit). Enforcing this is perhaps convenient but most of all helps with understanding. Furthermore each syntax we forbid is trivially forward compatible.
No need to go overboard with this, so what I've tried to narrow it down to are the following changes based on shellwords
:
- In the unquoted state, reject single quotes. We don't need them and putting them in the parsed string would confuse readers.
- Similarly, we should reject/reserve at least these chars in the unquoted state:
\
,$
, `,<
,>
; perhaps also*
. - Fail for lines that have an unterminated quoted string.
Use single quotes, because we won't be doing variable substitution (certainly for now)reject unescaped$
in double-quoted strings?
The backtick, <
, and unterminated quoted string cases are particularly interesting, because they could serve as extension points for multiline strings, which I think would be very useful, but whose implementation could be scoped out of this PR.
OT?: I think double backticks would make a nice analog for nixlang's double single quote strings, except they'd be entirely verbatim.
These changes aren't compatible with nix-shell
, so this would be new code and the move can be reverted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roberth This is still pending.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think trying to conform with existing syntaxes and conventions is a far harder problem than coming up with a useful syntax that works well for our use case.
New proposal:
- Use double backtick quotes as the only support type of quotation
- Disallow any shell-like syntax outside the quoted strings, and be eager to reject syntax in general
This way we keep the code fairly simple, leaving reserved syntax for possible later extension, leaving our options open, while solving "our own use case" well: postponing good inline expression support seems like a mistake the more I think about it.
I'll have a go at implementing this.
```bash | ||
#! /usr/bin/env nix | ||
#! nix shell --impure --expr | ||
#! nix "with (import (builtins.getFlake ''nixpkgs'') {}); terraform.withPlugins (plugins: [ plugins.openstack ])" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#! nix "with (import (builtins.getFlake ''nixpkgs'') {}); terraform.withPlugins (plugins: [ plugins.openstack ])" | |
#! nix 'with (import (builtins.getFlake "nixpkgs") {}); terraform.withPlugins (plugins: [ plugins.openstack ])' |
This will then change a bit.
Reviewed in Nix team meeting 2023-05-01:
|
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/2023-05-01-nix-team-meeting-minutes-51/27803/1 |
Enables shebang usage of nix shell. All arguments with `#! nix` get added to the nix invocation. This implementation does NOT set any additional arguments other than placing the script path itself as the first argument such that the interpreter can utilize it. Example below: ``` #!/usr/bin/env nix #! nix shell --quiet #! nix nixpkgs#bash #! nix nixpkgs#shellcheck #! nix nixpkgs#hello #! nix --ignore-environment --command bash # shellcheck shell=bash set -eu shellcheck "$0" || exit 1 function main { hello echo 0:"$0" 1:"$1" 2:"$2" } "$@" ``` fix: include programName usage
fix: release notes
f50bdfe
to
f8b54bf
Compare
Oops, I didn't reset the |
I've changed the quoting and interpretation of relative paths in #8327. wdyt? |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/flox-nix-community-update-2023-04/28233/1 |
Looks reasonable, but more contentious. I'm okay with either approach. |
Would it be desirable to have support for brace expansion? So you can do something like this: #!/usr/bin/env nix
#!nix shell --command bash
#!nix nixpkgs#{bash,jq,fd} |
v2 was merged, please try it out and provide feedback |
Enables shebang usage of nix shell. All arguments with
#! nix
getadded to the nix invocation. This implementation does NOT set any
additional arguments other than placing the script path itself as the
first argument such that the interpreter can utilize it.
Example below:
Overall it should make sense to any users of
nix-shell
.nix run
would work if it added PATHs)re-use the "-i interpreter" concept from nix-shell?nix-build
Checklist for maintainers
Maintainers: tick if completed or explain if not relevant
tests/**.sh
src/*/tests