diff options
-rw-r--r-- | lib/spack/llnl/util/filesystem.py | 23 | ||||
-rw-r--r-- | lib/spack/spack/build_systems/python.py | 4 | ||||
-rw-r--r-- | lib/spack/spack/test/llnl/util/filesystem.py | 24 | ||||
-rw-r--r-- | var/spack/repos/builtin/packages/python/package.py | 8 |
4 files changed, 55 insertions, 4 deletions
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py index 22ca97c347..179e1703b9 100644 --- a/lib/spack/llnl/util/filesystem.py +++ b/lib/spack/llnl/util/filesystem.py @@ -617,6 +617,29 @@ def get_filetype(path_name): return output.strip() +@system_path_filter +def is_nonsymlink_exe_with_shebang(path): + """ + Returns whether the path is an executable script with a shebang. + Return False when the path is a *symlink* to an executable script. + """ + try: + st = os.lstat(path) + # Should not be a symlink + if stat.S_ISLNK(st.st_mode): + return False + + # Should be executable + if not st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH): + return False + + # Should start with a shebang + with open(path, 'rb') as f: + return f.read(2) == b'#!' + except (IOError, OSError): + return False + + @system_path_filter(arg_slice=slice(1)) def chgrp_if_not_world_writable(path, group): """chgrp path to group if path is not world writable""" diff --git a/lib/spack/spack/build_systems/python.py b/lib/spack/spack/build_systems/python.py index b3552f0337..6aa225ba6c 100644 --- a/lib/spack/spack/build_systems/python.py +++ b/lib/spack/spack/build_systems/python.py @@ -11,7 +11,7 @@ import llnl.util.tty as tty from llnl.util.filesystem import ( filter_file, find, - get_filetype, + is_nonsymlink_exe_with_shebang, path_contains_subdirectory, same_path, working_dir, @@ -230,7 +230,7 @@ class PythonPackage(PackageBase): view.link(src, dst) elif not os.path.islink(src): shutil.copy2(src, dst) - is_script = 'script' in get_filetype(src) + is_script = is_nonsymlink_exe_with_shebang(src) if is_script and not python_is_external: filter_file( python_prefix, os.path.abspath( diff --git a/lib/spack/spack/test/llnl/util/filesystem.py b/lib/spack/spack/test/llnl/util/filesystem.py index 2bd6994754..b5a4594efb 100644 --- a/lib/spack/spack/test/llnl/util/filesystem.py +++ b/lib/spack/spack/test/llnl/util/filesystem.py @@ -674,3 +674,27 @@ def test_temporary_dir_context_manager(): with fs.temporary_dir() as tmp_dir: assert previous_dir != os.path.realpath(os.getcwd()) assert os.path.realpath(str(tmp_dir)) == os.path.realpath(os.getcwd()) + + +@pytest.mark.skipif(sys.platform == 'win32', reason="No shebang on Windows") +def test_is_nonsymlink_exe_with_shebang(tmpdir): + with tmpdir.as_cwd(): + # Create an executable with shebang. + with open('executable_script', 'wb') as f: + f.write(b'#!/interpreter') + os.chmod('executable_script', 0o100775) + + with open('executable_but_not_script', 'wb') as f: + f.write(b'#/not-a-shebang') + os.chmod('executable_but_not_script', 0o100775) + + with open('not_executable_with_shebang', 'wb') as f: + f.write(b'#!/interpreter') + os.chmod('not_executable_with_shebang', 0o100664) + + os.symlink('executable_script', 'symlink_to_executable_script') + + assert fs.is_nonsymlink_exe_with_shebang('executable_script') + assert not fs.is_nonsymlink_exe_with_shebang('executable_but_not_script') + assert not fs.is_nonsymlink_exe_with_shebang('not_executable_with_shebang') + assert not fs.is_nonsymlink_exe_with_shebang('symlink_to_executable_script') diff --git a/var/spack/repos/builtin/packages/python/package.py b/var/spack/repos/builtin/packages/python/package.py index be21af5e16..c97407d9fc 100644 --- a/var/spack/repos/builtin/packages/python/package.py +++ b/var/spack/repos/builtin/packages/python/package.py @@ -13,7 +13,11 @@ import sys from shutil import copy import llnl.util.tty as tty -from llnl.util.filesystem import copy_tree, get_filetype, path_contains_subdirectory +from llnl.util.filesystem import ( + copy_tree, + is_nonsymlink_exe_with_shebang, + path_contains_subdirectory, +) from llnl.util.lang import match_predicate from spack import * @@ -1369,7 +1373,7 @@ config.update(get_paths()) view.link(src, dst, spec=self.spec) elif not os.path.islink(src): copy(src, dst) - if 'script' in get_filetype(src): + if is_nonsymlink_exe_with_shebang(src): filter_file( self.spec.prefix, os.path.abspath( |