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

[CS2] Add #! support for executable scripts on Linux. #3946

Merged
merged 51 commits into from
Jul 19, 2017

Conversation

cosmicexplorer
Copy link
Contributor

Pass arguments to executable script unchanged if using "#!/usr/bin/env
coffee". (Previously, "./test.coffee -abck" would be turned into "-a -b -c -k",
for example.)

Fixes #1752.

Pass arguments to executable script unchanged if using "#!/usr/bin/env
coffee". (Previously, "./test.coffee -abck" would be turned into "-a -b -c -k",
for example.)

Fixes jashkenas#1752.
@dbushong
Copy link

👍 to this. The current behavior also has it so that you can't pass -- through to your coffee command-line program:

% echo 'console.log process.argv' > foo.coffee
% coffee -c foo.coffee
% coffee foo.coffee -a -- -b
[ 'coffee', '/Users/dbushong/foo.coffee', '-a', '-b' ]
% node foo.js -a -- -b
['/path/to/node', '/Users/dbushong/foo.js', '-a', '--', '-b']

While it works correctly if you say coffee foo.coffee -- -a -- -b, that's not on option on the linux shebang line.

# systems, which do not parse the '--' argument in the first line correctly.
if (args.indexOf '--' is -1) and
(args.length > 0) and
(isCoffee args[0])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rewrite this is as

if args.length isnt 0 and '--' in args and isCoffee(args[0])

@GeoffreyBooth
Copy link
Collaborator

Is there any chance that this could be a breaking change? If so please target the 2 branch.

@cosmicexplorer
Copy link
Contributor Author

I think so, for linux hosts; the situation I described at the top won't matter if the script follows standard command line conventions (where -abck is equivalent to -a -b -c -k), but it'll mysteriously break for users who weren't aware of this. I can fix this to work off the 2 branch sometime this week.

@GeoffreyBooth
Copy link
Collaborator

How would it break? Would -a -b -c -k no longer be possible?

@cosmicexplorer
Copy link
Contributor Author

Sorry, the other way around; -abck is always split to -a -b -c -k (not just these letters, others too) unless you use a workaround. There is no way to make a standalone script executable on Linux using an env shebang line so that it can accept multiple arguments; some discussion is here.

Invoking a script either like ./script.coffee -- -abck or coffee script.coffee -- -abck both have an argv of [ 'coffee', '/path/to/script.coffee', '-abck'], so it is possible, but requires a wrapper script. This is a problem because these arguments are consumed by the script, but still processed by the coffeescript executable's own argument parsing. On non-linux boxes you can have the line #!/usr/bin/env coffee --, but in linux that argv is parsed by the kernel as a two-element array ['/usr/bin/env', 'coffee --'], which obviously fails unless you add a symlink at "/usr/bin/coffee --" or something else ridiculous.

@GeoffreyBooth
Copy link
Collaborator

How will this affect traditional users running coffee from the command line?

@cosmicexplorer
Copy link
Contributor Author

isCoffee checks if the script to be evaluated was invoked with a shebang (as the first argument) or as an argument to the coffee executable. Anyone using a command line like coffee [args...] will be unaffected. Users who may be affected currently invoke a script as argv[0], e.g. ./test.coffee [args...], where test.coffee has a shebang line and is executable. If test.coffee has:

#!<SHEBANG LINE>

console.log process.argv

then there are about two and a half cases it differs:

  1. Scripts with the shebang #!/usr/bin/env coffee -- (which does not work on Linux) and are invoked as ./test.coffee -abck will see a -- after the script path: [ 'coffee', '/path/to/test.coffee', '--', '-abck' ] (where before there was no --). Users who invoke the script with shebang #!/usr/bin/env coffee -- (not on Linux) as ./test.coffee -- -abck intentionally to pass -- as an argument to their own script will see the -- argument disappear: [ 'coffee', '/home/cosmicexplorer/test.coffee', '-abck' ].

  2. Users on any platform (probably on Linux because it was the only way it would work, although not necessarily) who had used the shebang #!/usr/bin/env coffee or #!/path/to/some/executable/coffee and invoked it as ./test.coffee -- -abck to avoid the argument splitting into -a -b -c -k will see an extra --, just as above: [ 'coffee', '/path/to/test.coffee', '--', '-abck' ], where before there was none.

The solution above won't fix this, but we can detect the case where the user invokes the script with -- in the shebang line and not in the script invocation. I'll push my (small) change in a second which does this.

@GeoffreyBooth
Copy link
Collaborator

We should add some tests around this. I think it’s testable within our current framework, via stuff like execSync('./imports/cli_test.coffee -a -b -c') or similar. There should also be tests for more traditional commands, like execSync('coffee -v'), so that the current behavior is covered against regressions. The tests should make sure to use the coffee binary in the repo, not whatever global coffee might be in the user’s $PATH.

@cosmicexplorer cosmicexplorer force-pushed the master branch 3 times, most recently from 74f9a2e to 65f1e73 Compare April 20, 2017 01:23
clean up parsing code and in the process fix oustanding bug where coffeescript
modified arguments meant for an executable script
@cosmicexplorer
Copy link
Contributor Author

I think the previous option parsing code was convoluted and made it too easy to make mistakes like the -- processing (which this commit also fixes), especially for platform-specific stuff. With respect to the (possible) breaking changes I mentioned in my previous post, invoking the coffee interpreter is still the exact same. When invoking a shebang script ./test.coffee [args...]:

  1. If the script begins with a shebang line of #!/usr/bin/env coffee -- or #!/usr/bin/env coffee (or just a full path to the executable), it will not modify any arguments passed on the command line to the script. This part breaks nothing and makes scripts work on Linux.

  2. There may be users (most probably on Linux, if they exist) who used a shebang script of #!/usr/bin/env coffee and passed -- as an argument to their script, like ./test.coffee -- [intended-args...] to work around the previous behavior (which would have only passed intended-args... to their script). These users will see an "extra" --, which is a breaking change, but I think easily spotted.

I can absolutely add some tests. I do not have time today, but will try to do so tomorrow. Let me know if there are any style changes I can make, I tried to follow the rest of the code as much as possible.

state =
argsLeft: args[..]
options: {}
while (arg = state.argsLeft.shift())?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve been trying to avoid assignments inside conditionals. Can we just use for arg in args?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, I think we can just do a single pass over the arguments beforehand to 1. group arguments with values (-o [dir]) 2. explode combined options (e.g. -abck) 3. identify the first non-optional argument, then use a for. I'll do that.

normalized = "-#{multiArg}" for multiArg in multiMatch[1].split ''
state.argsLeft.unshift(normalized...)
else
# the CS option parser is a little odd; options after the first
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comments become the annotated source, so please use sentence case (capitalize first word, use periods).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, will fix. The first two lines are actually from the original source, so I wasn't sure whether I should change them.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Never too late to fix things 😄

if arg in [rule.shortFlag, rule.longFlag]
if rule.hasArgument
value = state.argsLeft.shift()
if not value?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, will change.

else
value = true

addArgument(rule, state.options, value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid parentheses when possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, will fix.

value = true

addArgument(rule, state.options, value)
return
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to avoid the implicit return?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not, it would eventually leave the for loop and hit the throw statement. This was dealt with previously by keeping a boolean testing whether or not the rule was matched; I thought this was cleaner here since we don't then loop over the rest of the rules if we successfully match one.

@GeoffreyBooth
Copy link
Collaborator

Do you mind please submitting the “current state” tests of the CLI as it works now as a separate PR? So that we can see that they work before trying them against your refactor.

@cosmicexplorer cosmicexplorer force-pushed the master branch 2 times, most recently from baaf924 to 124d228 Compare April 20, 2017 10:25
@cosmicexplorer
Copy link
Contributor Author

Yes! I'll make a separate PR for those sometime tomorrow. Thanks for your responsiveness.

@cosmicexplorer cosmicexplorer force-pushed the master branch 6 times, most recently from e2d7fe7 to 5f3edbe Compare April 20, 2017 11:17
@GeoffreyBooth GeoffreyBooth changed the title Add #! support for executable scripts on Linux. [WIP] Add #! support for executable scripts on Linux. Apr 22, 2017
Copy link
Collaborator

@GeoffreyBooth GeoffreyBooth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to give such minor notes. I would make the changes myself but I lack access to your branch.

@@ -0,0 +1,3 @@
#!/usr/bin/env coffee --
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convention in this repo is to use underscores in filenames.

Copy link
Contributor Author

@cosmicexplorer cosmicexplorer Jun 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed all the files I have added to use underscores. The file test/argument-parsing.coffee was one I made in the previous PR to test v1's command-line argument parsing, and I have changed that one to use underscores too.

If called without options, `coffee` will run your script.


```
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the blank lines before and after the text?

Copy link
Contributor Author

@cosmicexplorer cosmicexplorer Jun 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually screwed up the help text (see the most recent commit -- needed to change the help method to use @rules.ruleList instead of just @rules). I made that fix, changed this text, and have also added tests in test/option_parser.coffee in the most recent commit to ensure the help text will always display the banner and the flags.

4
```

Due to a bug in the argument parsing of previous CoffeeScript versions, this used to fail when trying to pass arguments to the script. Some users on OSX worked around the problem by using `#!/usr/bin/env coffee --` at the top of the file instead. However, that won't work on Linux, which cannot parse shebang lines with more than a single argument. While these scripts will still run on OSX, CoffeeScript will now display a warning before compiling or evaluating files that begin with a too-long shebang line:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s “OS X”. Also the documentation uses curly quotes, e.g. “won’t”.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed both of those.

@GeoffreyBooth GeoffreyBooth mentioned this pull request Jun 30, 2017
also add tests for help text
Copy link
Collaborator

@GeoffreyBooth GeoffreyBooth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please merge in develop and resolve conflicts.

-l, --literate treat stdio as literate style coffeescript
-t, --tokens print out the tokens that the lexer/rewriter produce
-v, --version display the version number
-w, --watch watch scripts for changes and rerun commands
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already documented in http://coffeescript.org/v2/#usage. The Breaking Changes section should only discuss breaking changes from 1.x.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I have noted only the changed usage line now and left an ... to denote the rest of the help text.

@@ -45,7 +64,7 @@ console.log x
4
```

Due to a bug in the argument parsing of previous CoffeeScript versions, this used to fail when trying to pass arguments to the script. Some users on OSX worked around the problem by using `#!/usr/bin/env coffee --` at the top of the file instead. However, that won't work on Linux, which cannot parse shebang lines with more than a single argument. While these scripts will still run on OSX, CoffeeScript will now display a warning before compiling or evaluating files that begin with a too-long shebang line:
Due to a bug in the argument parsing of previous CoffeeScript versions, this used to fail when trying to pass arguments to the script. Some users on OS X worked around the problem by using `#!/usr/bin/env coffee --` at the top of the file instead. However, that wont work on Linux, which cannot parse shebang lines with more than a single argument. While these scripts will still run on OSX, CoffeeScript will now display a warning before compiling or evaluating files that begin with a too-long shebang line:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another “OS X”.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

ok bannerHelp.match bannerPattern
for flag in flags
linePat = flagPattern flag
ok bannerHelp.match linePat
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tests that the option parser correctly outputs a banner text string, but not necessarily that coffee -h actually outputs the correct help text. For that, you need to actually test opt.help() against the help text, which should be written here in the test. I know it doesn’t feel DRY to repeat a string that is elsewhere in the codebase, but if that string changes in the main code, we want this test to fail—either to inform us that we unexpectedly changed the help text, or to remind us to update the test, too, so that we preserve this check for the future. The same applies to the list of switches.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that makes sense. I'll make that change.

@cosmicexplorer
Copy link
Contributor Author

  1. I have responded to your comments and merged in the 2 branch (the conflict was with the file argument-parsing.md, which I renamed with an underscore instead).
  2. The tests for the help text now check the exact help text string. I have also added tests in option_parser.coffee which checks that the correct help text is output by the OptionParser class, not just the instance of it used in the coffee command.
  3. I have slightly edited breaking_changes_argument_parsing_and_shebang_lines.md to make it more clear.

Let me know if there are any issues with these changes.

Copy link
Collaborator

@GeoffreyBooth GeoffreyBooth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you’d like to give me access to your branch, I could fix a bunch of these tiny issues.

' -o, --optional desc optional'
' -l, --list desc list'
''
].join('\n')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could just be a ''' string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, except that ''' strings don't allow leading spaces unless one of the lines (e.g. the banner text) is not indented, so I don't think this would work as a ''' string.

test "outputs expected help text", ->
expectedBanner = '''

banner text
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tests that the OptionParser class outputs the kind of help text that we expect, and not just the OptionParser instance that is used in command.coffee. It also tests the output without a banner.

Due to a bug in the argument parsing of previous CoffeeScript versions, this used to fail when trying to pass arguments to the script. Some users on OS X worked around the problem by using `#!/usr/bin/env coffee --` at the top of the file instead. However, that won’t work on Linux, which cannot parse shebang lines with more than a single argument. While these scripts will still run on OSX, CoffeeScript will now display a warning before compiling or evaluating files that begin with a too-long shebang line:
##### Incorrect Example

Due to a bug in the argument parsing of previous CoffeeScript versions, this used to fail when trying to pass arguments to the script. Some users on OSX worked around the problem by using `#!/usr/bin/env coffee --` at the top of the file instead. However, that won’t work on Linux, which cannot parse shebang lines with more than a single argument. While these scripts will still run on OSX, CoffeeScript will now display a warning before compiling or evaluating files that begin with a too-long shebang line:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“OS X”

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what the issue is -- should I add quotes around OSX? The space between OS and X was removed everywhere.

@cosmicexplorer
Copy link
Contributor Author

I have added you as a collaborator to my branch -- let me know if you have push access.

@GeoffreyBooth
Copy link
Collaborator

Okay, I think we’re good now. I shortened the docs considerably, can you let me know if I took out anything important? We try to keep the docs as concise as possible. The generated output is here:
http://rawgit.com/cosmicexplorer/coffeescript/master/docs/v2/#breaking-changes-argument-parsing-and-shebang-lines

Anyone else have any final notes before this gets merged in?

@@ -25,7 +25,7 @@ hidden = (file) -> /^\.|~$/.test file

# The help banner that is printed in conjunction with `-h`/`--help`.
BANNER = '''
Usage: coffee [options] [--] path/to/script.coffee [args]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like removing [--], the new version does not say anything about how -- is parsed as an argument. gawk --help uses [--] in a very similar way.

@@ -115,7 +115,7 @@ test "throw on invalid options", ->
test "has expected help text", ->
ok optionParser.help() is '''

Usage: coffee [options] [--] path/to/script.coffee [args]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in src/command.coffee.

@cosmicexplorer
Copy link
Contributor Author

I really like the changes. My only concern is in the usage text, as noted inline.

@GeoffreyBooth
Copy link
Collaborator

Why mention -- in the help text if we don't want it to be used? Does it serve some purpose?

@cosmicexplorer
Copy link
Contributor Author

After thinking about it a bit, I think the main reason I wanted the [--] to stay is because awk's usage text uses [--] and it seems more precise. I completely agree that since we don't want it to be used (even though the command can handle it), it shouldn't be mentioned.

@GeoffreyBooth
Copy link
Collaborator

I think it’s confusing to include the reference to it if we don’t define what it does (especially if the only thing it “does” is print a warning). If it did anything useful, then sure, let’s include it and describe its function like the other flags.

@lydell or anyone else, any final notes? This is an old PR, thank you @cosmicexplorer for sticking with us for so long!

@cosmicexplorer
Copy link
Contributor Author

I completely 100% agree with that. Let me know if anything weird happens with these changes -- I feel comfortable after all the test cases, but I'm still wary. Glad this change finally saw light, turned out to be much, much harder than I expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants