sbang: convert sbang script to POSIX shell
`sbang` was previously a bash script but did not need to be. This converts it to a plain old POSIX shell script and adds some options. This also allows us to simplify sbang shebangs to `#!/bin/sh /path/to/sbang` instead of `#!/bin/bash /path/to/sbang`. The new script passes shellcheck (with a few exceptions noted in the file) - [x] `SBANG_DEBUG` env var enables printing what *would* be executed - [x] `sbang` checks whether it has been passed an option and fails gracefully - [x] `sbang` will now fail if it can't find a second shebang line, or if the second line happens to be sbang (avoid infinite loops) - [x] add more rigorous tests for `sbang` behavior using `SBANG_DEBUG`
This commit is contained in:
@@ -51,7 +51,7 @@ def filter_shebang(path):
|
||||
original = original.decode('UTF-8')
|
||||
|
||||
# This line will be prepended to file
|
||||
new_sbang_line = '#!/bin/bash %s\n' % sbang_install_path()
|
||||
new_sbang_line = '#!/bin/sh %s\n' % sbang_install_path()
|
||||
|
||||
# Skip files that are already using sbang.
|
||||
if original.startswith(new_sbang_line):
|
||||
|
@@ -23,18 +23,21 @@
|
||||
|
||||
short_line = "#!/this/is/short/bin/bash\n"
|
||||
long_line = "#!/this/" + ('x' * 200) + "/is/long\n"
|
||||
|
||||
lua_line = "#!/this/" + ('x' * 200) + "/is/lua\n"
|
||||
lua_in_text = ("line\n") * 100 + "lua\n" + ("line\n" * 100)
|
||||
lua_line_patched = "--!/this/" + ('x' * 200) + "/is/lua\n"
|
||||
|
||||
node_line = "#!/this/" + ('x' * 200) + "/is/node\n"
|
||||
node_in_text = ("line\n") * 100 + "lua\n" + ("line\n" * 100)
|
||||
node_line_patched = "//!/this/" + ('x' * 200) + "/is/node\n"
|
||||
sbang_line = '#!/bin/bash %s/bin/sbang\n' % spack.store.layout.root
|
||||
|
||||
php_line = "#!/this/" + ('x' * 200) + "/is/php\n"
|
||||
php_in_text = ("line\n") * 100 + "php\n" + ("line\n" * 100)
|
||||
php_line_patched = "<?php #!/this/" + ('x' * 200) + "/is/php\n"
|
||||
php_line_patched2 = "?>\n"
|
||||
sbang_line = '#!/bin/bash %s/bin/sbang\n' % spack.store.layout.root
|
||||
|
||||
sbang_line = '#!/bin/sh %s/bin/sbang\n' % spack.store.layout.root
|
||||
last_line = "last!\n"
|
||||
|
||||
|
||||
@@ -178,7 +181,7 @@ def test_shebang_handles_non_writable_files(script_dir):
|
||||
assert oct(not_writable_mode) == oct(st.st_mode)
|
||||
|
||||
|
||||
def check_sbang():
|
||||
def check_sbang_installation():
|
||||
sbang_path = sbang.sbang_install_path()
|
||||
sbang_bin_dir = os.path.dirname(sbang_path)
|
||||
assert sbang_path.startswith(spack.store.layout.root)
|
||||
@@ -201,7 +204,7 @@ def test_install_sbang(install_mockery):
|
||||
assert not os.path.exists(sbang_bin_dir)
|
||||
|
||||
sbang.install_sbang()
|
||||
check_sbang()
|
||||
check_sbang_installation()
|
||||
|
||||
# put an invalid file in for sbang
|
||||
fs.mkdirp(sbang_bin_dir)
|
||||
@@ -209,8 +212,73 @@ def test_install_sbang(install_mockery):
|
||||
f.write("foo")
|
||||
|
||||
sbang.install_sbang()
|
||||
check_sbang()
|
||||
check_sbang_installation()
|
||||
|
||||
# install again and make sure sbang is still fine
|
||||
sbang.install_sbang()
|
||||
check_sbang()
|
||||
check_sbang_installation()
|
||||
|
||||
|
||||
def test_sbang_fails_without_argument():
|
||||
sbang = which(spack.paths.sbang_script)
|
||||
sbang(fail_on_error=False)
|
||||
assert sbang.returncode == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("shebang,returncode,expected", [
|
||||
# perl, with and without /usr/bin/env
|
||||
("#!/path/to/perl", 0, "/path/to/perl -x"),
|
||||
("#!/usr/bin/env perl", 0, "/usr/bin/env perl -x"),
|
||||
|
||||
# perl -w, with and without /usr/bin/env
|
||||
("#!/path/to/perl -w", 0, "/path/to/perl -w -x"),
|
||||
("#!/usr/bin/env perl -w", 0, "/usr/bin/env perl -w -x"),
|
||||
|
||||
# ruby, with and without /usr/bin/env
|
||||
("#!/path/to/ruby", 0, "/path/to/ruby -x"),
|
||||
("#!/usr/bin/env ruby", 0, "/usr/bin/env ruby -x"),
|
||||
|
||||
# python, with and without /usr/bin/env
|
||||
("#!/path/to/python", 0, "/path/to/python"),
|
||||
("#!/usr/bin/env python", 0, "/usr/bin/env python"),
|
||||
|
||||
# php with one-line php comment
|
||||
("<?php #!/usr/bin/php ?>", 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 <shebang> 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()
|
||||
|
Reference in New Issue
Block a user