Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@
from easybuild.tools.config import install_path, log_path, package_path, source_paths
from easybuild.tools.environment import restore_env, sanitize_env
from easybuild.tools.filetools import CHECKSUM_TYPE_MD5, CHECKSUM_TYPE_SHA256
from easybuild.tools.filetools import adjust_permissions, apply_patch, back_up_file, change_dir, convert_name
from easybuild.tools.filetools import compute_checksum, copy_file, derive_alt_pypi_url, diff_files
from easybuild.tools.filetools import download_file, encode_class_name, extract_file, find_backup_name_candidate
from easybuild.tools.filetools import get_source_tarball_from_git, is_alt_pypi_url, is_sha256_checksum, mkdir
from easybuild.tools.filetools import move_file, move_logs, read_file, remove_file, rmtree2, verify_checksum, weld_paths
from easybuild.tools.filetools import write_file
from easybuild.tools.filetools import adjust_permissions, apply_patch, apply_regex_substitutions, back_up_file
from easybuild.tools.filetools import change_dir, convert_name, compute_checksum, copy_file, derive_alt_pypi_url
from easybuild.tools.filetools import diff_files, download_file, encode_class_name, extract_file
from easybuild.tools.filetools import find_backup_name_candidate, get_source_tarball_from_git, is_alt_pypi_url
from easybuild.tools.filetools import is_sha256_checksum, mkdir, move_file, move_logs, read_file, remove_file, rmtree2
from easybuild.tools.filetools import verify_checksum, weld_paths, write_file
from easybuild.tools.hooks import BUILD_STEP, CLEANUP_STEP, CONFIGURE_STEP, EXTENSIONS_STEP, FETCH_STEP, INSTALL_STEP
from easybuild.tools.hooks import MODULE_STEP, PACKAGE_STEP, PATCH_STEP, PERMISSIONS_STEP, POSTITER_STEP, POSTPROC_STEP
from easybuild.tools.hooks import PREPARE_STEP, READY_STEP, SANITYCHECK_STEP, SOURCE_STEP, TEST_STEP, TESTCASES_STEP
Expand Down Expand Up @@ -2152,6 +2152,23 @@ def package_step(self):
else:
self.log.info("Skipping package step (not enabled)")

def fix_shebang(self):
"""Fix shebang lines for specified files."""
for lang in ['perl', 'python']:
fix_shebang_for = self.cfg['fix_%s_shebang_for' % lang]
if fix_shebang_for:
if isinstance(fix_shebang_for, basestring):
fix_shebang_for = [fix_shebang_for]

shebang = '#!/usr/bin/env %s' % lang
for glob_pattern in fix_shebang_for:
paths = glob.glob(os.path.join(self.installdir, glob_pattern))
self.log.info("Fixing '%s' shebang to '%s' for files that match '%s': %s",
lang, shebang, glob_pattern, paths)
regex = r'^#!.*/%s[0-9.]*$' % lang
for path in paths:
apply_regex_substitutions(path, [(regex, shebang)], backup=False)

def post_install_step(self):
"""
Do some postprocessing
Expand All @@ -2167,6 +2184,8 @@ def post_install_step(self):
raise EasyBuildError("Invalid element in 'postinstallcmds', not a string: %s", cmd)
run_cmd(cmd, simple=True, log_ok=True, log_all=True)

self.fix_shebang()

def sanity_check_step(self, *args, **kwargs):
"""
Do a sanity check on the installation
Expand Down
4 changes: 4 additions & 0 deletions easybuild/framework/easyconfig/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
'easyblock': [None, "EasyBlock to use for building; if set to None, an easyblock is selected "
"based on the software name", BUILD],
'easybuild_version': [None, "EasyBuild-version this spec-file was written for", BUILD],
'fix_perl_shebang_for': [None, "List of files for which Perl shebang should be fixed "
"to '#!/usr/bin/env perl' (glob patterns supported)", BUILD],
'fix_python_shebang_for': [None, "List of files for which Python shebang should be fixed "
"to '#!/usr/bin/env python' (glob patterns supported)", BUILD],
'github_account': ['%(namelower)s', "GitHub account name to be used to resolve template values in source URLs",
BUILD],
'hidden': [False, "Install module file as 'hidden' by prefixing its version with '.'", BUILD],
Expand Down
41 changes: 41 additions & 0 deletions test/framework/toy_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -2199,6 +2199,47 @@ def check_toy_load(depends_on=False):

check_toy_load(depends_on=True)

def test_fix_shebang(self):
"""Test use of fix_python_shebang_for & co."""
test_ecs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs')
toy_ec_txt = read_file(os.path.join(test_ecs_dir, 't', 'toy', 'toy-0.0.eb'))

test_ec = os.path.join(self.test_prefix, 'test.eb')

test_ec_txt = '\n'.join([
toy_ec_txt,
"postinstallcmds = ["
" 'echo \"#!/usr/bin/python\\n# test\" > %(installdir)s/bin/t1.py',",
" 'echo \"#!/software/Python/3.6.6-foss-2018b/bin/python3.6\\n# test\" > %(installdir)s/bin/t2.py',",
" 'echo \"#!/usr/bin/env python\\n# test\" > %(installdir)s/bin/t3.py',",
" 'echo \"#!/usr/bin/perl\\n# test\" > %(installdir)s/bin/t1.pl',",
" 'echo \"#!/software/Perl/5.28.1-GCCcore-7.3.0/bin/perl5\\n# test\" > %(installdir)s/bin/t2.pl',",
" 'echo \"#!/usr/bin/env perl\\n# test\" > %(installdir)s/bin/t3.pl',",
"]",
"fix_python_shebang_for = ['bin/t1.py', 'bin/*.py', 'nosuchdir/*.py']",
"fix_perl_shebang_for = 'bin/*.pl'",
])
write_file(test_ec, test_ec_txt)
self.test_toy_build(ec_file=test_ec, raise_error=True)

toy_bindir = os.path.join(self.test_installpath, 'software', 'toy', '0.0', 'bin')

# no re.M, this should match at start of file!
py_shebang_regex = re.compile(r'^#!/usr/bin/env python\n# test$')
for pybin in ['t1.py', 't2.py', 't3.py']:
pybin_path = os.path.join(toy_bindir, pybin)
pybin_txt = read_file(pybin_path)
self.assertTrue(py_shebang_regex.match(pybin_txt),
"Pattern '%s' found in %s: %s" % (py_shebang_regex.pattern, pybin_path, pybin_txt))

# no re.M, this should match at start of file!
perl_shebang_regex = re.compile(r'^#!/usr/bin/env perl\n# test$')
for perlbin in ['t1.pl', 't2.pl', 't3.pl']:
perlbin_path = os.path.join(toy_bindir, perlbin)
perlbin_txt = read_file(perlbin_path)
self.assertTrue(perl_shebang_regex.match(perlbin_txt),
"Pattern '%s' found in %s: %s" % (perl_shebang_regex.pattern, perlbin_path, perlbin_txt))


def suite():
""" return all the tests in this file """
Expand Down