diff --git a/COPYRIGHT b/COPYRIGHT index ae21fe54bd..7bc67442d7 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -28,9 +28,11 @@ text in the license header: External Packages ------------------- -Spack bundles its external dependencies in lib/spack/external. These -packages are covered by various permissive licenses. A summary listing -follows. See the license included with each package for full details. + +Spack bundles most external dependencies in lib/spack/external. It also +includes the sbang tool directly in bin/sbang. These packages are covered +by various permissive licenses. A summary listing follows. See the +license included with each package for full details. PackageName: argparse PackageHomePage: https://pypi.python.org/pypi/argparse @@ -76,6 +78,10 @@ PackageName: ruamel.yaml PackageHomePage: https://yaml.readthedocs.io/ PackageLicenseDeclared: MIT +PackageName: sbang +PackageHomePage: https://github.com/spack/sbang +PackageLicenseDeclared: Apache-2.0 OR MIT + PackageName: six PackageHomePage: https://pypi.python.org/pypi/six PackageLicenseDeclared: MIT diff --git a/bin/sbang b/bin/sbang index 983f778651..c8f1c7c39c 100755 --- a/bin/sbang +++ b/bin/sbang @@ -1,7 +1,7 @@ #!/bin/sh # # Copyright 2013-2020 Lawrence Livermore National Security, LLC and other -# Spack Project Developers. See the top-level COPYRIGHT file for details. +# sbang project developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) @@ -12,85 +12,16 @@ # arguments in shebang lines, making it hard to use interpreters that are # deep in the directory hierarchy or require special arguments. # -# `sbang` can run such scripts, either as a shebang interpreter, or -# directly on the command line. +# To use, put the long shebang on the second line of your script, and +# make sbang the interpreter, like this: # -# Usage -# ----- -# Suppose you have a script, long-shebang.sh, like this: +# #!/bin/sh /path/to/sbang +# #!/long/path/to/real/interpreter with arguments # -# 1 #!/very/long/path/to/some/interp -# 2 -# 3 echo "success!" +# `sbang` will run the real interpreter with the script as its argument. # -# Invoking this script will result in an error on some OS's. On -# Linux, you get this: +# See https://github.com/spack/sbang for more details. # -# $ ./longshebang.sh -# -bash: ./longshebang.sh: /very/long/path/to/some/interp: bad interpreter: -# No such file or directory -# -# On macOS, the system simply assumes the interpreter is the shell and -# tries to run with it, which is not likely what you want. -# -# -# `sbang` on the command line -# --------------------------- -# You can use `sbang` in two ways. The first is to use it directly, -# from the command line, like this: -# -# $ sbang ./long-shebang.sh -# success! -# -# -# `sbang` as the interpreter -# -------------------------- -# You can also use `sbang` *as* the interpreter for your script. Put -# `#!/bin/sh /path/to/sbang` on line 1, and move the original -# shebang to line 2 of the script: -# -# 1 #!/bin/sh /path/to/sbang -# 2 #!/long/path/to/real/interpreter with arguments -# 3 -# 4 echo "success!" -# -# $ ./long-shebang.sh -# success! -# -# On Linux, you could shorten line 1 to `#!/path/to/sbang`, but other -# operating systems like Mac OS X require the interpreter to be a binary, -# so it's best to use `sbang` as an argument to `/bin/sh`. Obviously, for -# this to work, `sbang` needs to have a short enough path that *it* will -# run without hitting OS limits. -# -# For Lua, node, and php scripts, the second line can't start with #!, as -# # is not the comment character in these languages (though they all -# ignore #! on the *first* line of a script). So, instrument such scripts -# like this, using --, //, or instead of # on the second -# line, e.g.: -# -# 1 #!/bin/sh /path/to/sbang -# 2 --!/long/path/to/lua with arguments -# 3 print "success!" -# -# 1 #!/bin/sh /path/to/sbang -# 2 //!/long/path/to/node with arguments -# 3 print "success!" -# -# 1 #!/bin/sh /path/to/sbang -# 2 -# 3 -# -# How it works -# ------------ -# `sbang` is a very simple posix shell script. It looks at the first two -# lines of a script argument and runs the last line starting with `#!`, -# with the script as an argument. It also forwards arguments. -# - -# We disable two shellcheck errors below: -# SC2124: when saving arguments, we intentionally assign as an array -# SC2086: when splitting $shebang_line and exec args, we want to expand args # Generic error handling die() { @@ -130,27 +61,16 @@ while read -r line && [ $lines -ne 2 ]; do lines=$((lines+1)) done < "$script" -# shellcheck disable=SC2124 -# this saves arguments for later and intentionally assigns as an array -args="$@" - -# handle scripts with sbang parameters, e.g.: -# -# #!//perl -w -# -# put the shebang line with all the parameters in the $@ array and get -# the first element. -# shellcheck disable=SC2086 -set $shebang_line -set -- "$@" -interpreter="$1" -arg1="$2" - # error if we did not find any interpreter -if [ -z "$interpreter" ]; then +if [ -z "$shebang_line" ]; then die "error: sbang found no interpreter in $script" fi +# parse out the interpreter and first argument +IFS=' ' read -r interpreter arg1 rest <", 0, "/usr/bin/php"), - - # simple shell scripts - ("#!/bin/sh", 0, "/bin/sh"), - ("#!/bin/bash", 0, "/bin/bash"), - - # error case: sbang as infinite loop - ("#!/path/to/sbang", 1, None), - ("#!/usr/bin/env sbang", 1, None), - - # lua - ("--!/path/to/lua", 0, "/path/to/lua"), - - # node - ("//!/path/to/node", 0, "/path/to/node"), -]) -def test_sbang_with_specific_shebang( - tmpdir, shebang, returncode, expected): - - script = str(tmpdir.join("script")) - - # write a script out with on second line - with open(script, "w") as f: - f.write("#!/bin/sh {sbang}\n{shebang}\n".format( - sbang=spack.paths.sbang_script, - shebang=shebang - )) - fs.set_executable(script) - - # test running the script in debug, which prints what would be executed - exe = which(script) - out = exe(output=str, fail_on_error=False, env={"SBANG_DEBUG": "1"}) - - # check error status and output vs. expected - assert exe.returncode == returncode - - if expected is not None: - expected += " " + script - assert expected == out.strip()