python: drop dependency on file for script check (#29513)

`file` was used to detect Python scripts with shebangs, so that the interpreter could be changed from <python prefix> to <view path>. With this change, we detect shebangs using Python instead, so that `file` is no longer required.
This commit is contained in:
Harmen Stoppels 2022-03-23 10:31:23 +01:00 committed by GitHub
parent 487b1c3690
commit 773da7ceba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 4 deletions

View File

@ -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"""

View File

@ -11,7 +11,7 @@
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 @@ def add_files_to_view(self, view, merge_map):
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(

View File

@ -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')

View File

@ -13,7 +13,11 @@
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 @@ def add_files_to_view(self, view, merge_map):
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(