Repository: spack/spack

Issue:
problem with shebang of Python scripts being too long
Some Python packages install python scripts to be executed via bash.
If we use the Python installed with `spack`, the path of the python is something like:
`#!/<SOME_DIR>/spack_git_repo/opt/spack/chaos_5_x86_64_ib/gcc-4.4.7/python-2.7.10-              yqkr6m74axh3nafjscgtjb3zj3htx3fe/bin/python`

Somehow the bash interpreter will cut it off when the shebang is longer than a certain number of characters as documented [here](http://www.in-ulm.de/~mascheck/various/shebang/#issues)

For me it cut off so the she-bang looked like:
``#!/<SOME_DIR>/spack_git_repo/opt/spack/chaos_5_x86_64_ib/gcc-4.4.7/python-2.7.10-              yqkr6m74axh3nafjscgtjb3zj3htx3fe/bin/p`

A simple fix is to change the line of shebang to:
`#!/bin/env python` 

Is there a better solution than manually changing all the shebangs for the python scripts?
In any case, I thought it may be nice to document this behavior somewhere in the `spack` documentation for Python use.
Thanks.



Discussion:
Yes, there is a character limit, defined [here](http://lxr.free-electrons.com/source/include/uapi/linux/binfmts.h?v=3.18#L18). It also only supports one argument (see [this LWN article](https://lwn.net/Articles/630727/)).

As for a solution, it might be best if the scripts just used the environment to get the right Python from spack (requiring that spack be loaded to run the script).

Duplicates #54 


Resolving patch:
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py
--- a/lib/spack/llnl/util/filesystem.py
+++ b/lib/spack/llnl/util/filesystem.py
@@ -25,7 +25,8 @@
 __all__ = ['set_install_permissions', 'install', 'install_tree', 'traverse_tree',
            'expand_user', 'working_dir', 'touch', 'touchp', 'mkdirp',
            'force_remove', 'join_path', 'ancestor', 'can_access', 'filter_file',
-           'FileFilter', 'change_sed_delimiter', 'is_exe', 'force_symlink']
+           'FileFilter', 'change_sed_delimiter', 'is_exe', 'force_symlink',
+           'copy_mode', 'unset_executable_mode']
 
 import os
 import sys
@@ -158,6 +159,14 @@ def copy_mode(src, dest):
     os.chmod(dest, dest_mode)
 
 
+def unset_executable_mode(path):
+    mode = os.stat(path).st_mode
+    mode &= ~stat.S_IXUSR
+    mode &= ~stat.S_IXGRP
+    mode &= ~stat.S_IXOTH
+    os.chmod(path, mode)
+
+
 def install(src, dest):
     """Manually install a file to a particular location."""
     tty.debug("Installing %s to %s" % (src, dest))
diff --git a/lib/spack/spack/hooks/sbang.py b/lib/spack/spack/hooks/sbang.py
new file mode 100644
--- /dev/null
+++ b/lib/spack/spack/hooks/sbang.py
@@ -0,0 +1,77 @@
+##############################################################################
+# Copyright (c) 2013-2015, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import os
+
+from llnl.util.filesystem import *
+import llnl.util.tty as tty
+
+import spack
+import spack.modules
+
+# Character limit for shebang line.  Using Linux's 127 characters
+# here, as it is the shortest I could find on a modern OS.
+shebang_limit = 127
+
+def shebang_too_long(path):
+    """Detects whether an file has a shebang line that is too long."""
+    with open(path, 'r') as script:
+        bytes = script.read(2)
+        if bytes != '#!':
+            return False
+
+        line = bytes + script.readline()
+        return len(line) > shebang_limit
+
+
+def filter_shebang(path):
+    """Adds a second shebang line, using sbang, at the beginning of a file."""
+    backup = path + ".shebang.bak"
+    os.rename(path, backup)
+
+    with open(backup, 'r') as bak_file:
+        original = bak_file.read()
+
+    with open(path, 'w') as new_file:
+        new_file.write('#!/bin/bash %s/bin/sbang\n' % spack.spack_root)
+        new_file.write(original)
+
+    copy_mode(backup, path)
+    unset_executable_mode(backup)
+
+    tty.warn("Patched overly long shebang in %s" % path)
+
+
+def post_install(pkg):
+    """This hook edits scripts so that they call /bin/bash
+       $spack_prefix/bin/sbang instead of something longer than the
+       shebang limit."""
+    if not os.path.isdir(pkg.prefix.bin):
+        return
+
+    for file in os.listdir(pkg.prefix.bin):
+        path = os.path.join(pkg.prefix.bin, file)
+        if shebang_too_long(path):
+            filter_shebang(path)
+


Test patch: