11# #
2- # Copyright 2009-2019 Ghent University
2+ # Copyright 2009-2020 Ghent University
33#
44# This file is part of EasyBuild,
55# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
4444import inspect
4545import os
4646import re
47- import shutil
4847import stat
4948import tempfile
5049import time
6261from easybuild .framework .easyconfig .style import MAX_LINE_LENGTH
6362from easybuild .framework .easyconfig .tools import get_paths_for
6463from easybuild .framework .easyconfig .templates import TEMPLATE_NAMES_EASYBLOCK_RUN_STEP , template_constant_dict
64+ from easybuild .framework .extension import resolve_exts_filter_template
6565from easybuild .tools import config , filetools
6666from easybuild .tools .build_details import get_build_stats
6767from easybuild .tools .build_log import EasyBuildError , dry_run_msg , dry_run_warning , dry_run_set_dirs
7171from easybuild .tools .config import install_path , log_path , package_path , source_paths
7272from easybuild .tools .environment import restore_env , sanitize_env
7373from easybuild .tools .filetools import CHECKSUM_TYPE_MD5 , CHECKSUM_TYPE_SHA256
74- from easybuild .tools .filetools import adjust_permissions , apply_patch , apply_regex_substitutions , back_up_file
74+ from easybuild .tools .filetools import adjust_permissions , apply_patch , back_up_file
7575from easybuild .tools .filetools import change_dir , convert_name , compute_checksum , copy_file , derive_alt_pypi_url
7676from easybuild .tools .filetools import diff_files , download_file , encode_class_name , extract_file
7777from easybuild .tools .filetools import find_backup_name_candidate , get_source_tarball_from_git , is_alt_pypi_url
7878from easybuild .tools .filetools import is_sha256_checksum , mkdir , move_file , move_logs , read_file , remove_dir
79- from easybuild .tools .filetools import remove_file , rmtree2 , verify_checksum , weld_paths , write_file
79+ from easybuild .tools .filetools import remove_file , rmtree2 , verify_checksum , weld_paths , write_file , dir_contains_files
8080from easybuild .tools .hooks import BUILD_STEP , CLEANUP_STEP , CONFIGURE_STEP , EXTENSIONS_STEP , FETCH_STEP , INSTALL_STEP
8181from easybuild .tools .hooks import MODULE_STEP , PACKAGE_STEP , PATCH_STEP , PERMISSIONS_STEP , POSTITER_STEP , POSTPROC_STEP
8282from easybuild .tools .hooks import PREPARE_STEP , READY_STEP , SANITYCHECK_STEP , SOURCE_STEP , TEST_STEP , TESTCASES_STEP
@@ -1290,6 +1290,9 @@ def make_module_req(self):
12901290 note += "for paths are skipped for the statements below due to dry run"
12911291 lines .append (self .module_generator .comment (note ))
12921292
1293+ # for these environment variables, the corresponding subdirectory must include at least one file
1294+ keys_requiring_files = ('CPATH' , 'LD_LIBRARY_PATH' , 'LIBRARY_PATH' , 'PATH' )
1295+
12931296 for key in sorted (requirements ):
12941297 if self .dry_run :
12951298 self .dry_run_msg (" $%s: %s" % (key , ', ' .join (requirements [key ])))
@@ -1302,6 +1305,16 @@ def make_module_req(self):
13021305 # only use glob if the string is non-empty
13031306 if path and not self .dry_run :
13041307 paths = sorted (glob .glob (path ))
1308+ if paths and key in keys_requiring_files :
1309+ # only retain paths that contain at least one file
1310+ retained_paths = [
1311+ path for path in paths
1312+ if os .path .isdir (os .path .join (self .installdir , path ))
1313+ and dir_contains_files (os .path .join (self .installdir , path ))
1314+ ]
1315+ self .log .info ("Only retaining paths for %s that contain at least one file: %s -> %s" ,
1316+ key , paths , retained_paths )
1317+ paths = retained_paths
13051318 else :
13061319 # empty string is a valid value here (i.e. to prepend the installation prefix, cfr $CUDA_HOME)
13071320 paths = [path ]
@@ -1317,15 +1330,18 @@ def make_module_req_guess(self):
13171330 """
13181331 A dictionary of possible directories to look for.
13191332 """
1333+ lib_paths = ['lib' , 'lib32' , 'lib64' ]
13201334 return {
13211335 'PATH' : ['bin' , 'sbin' ],
1322- 'LD_LIBRARY_PATH' : [ 'lib' , 'lib32' , 'lib64' ] ,
1323- 'LIBRARY_PATH' : [ 'lib' , 'lib32' , 'lib64' ] ,
1336+ 'LD_LIBRARY_PATH' : lib_paths ,
1337+ 'LIBRARY_PATH' : lib_paths ,
13241338 'CPATH' : ['include' ],
13251339 'MANPATH' : ['man' , os .path .join ('share' , 'man' )],
1326- 'PKG_CONFIG_PATH' : [os .path .join (x , 'pkgconfig' ) for x in [ 'lib' , 'lib32' , 'lib64' , 'share' ]],
1340+ 'PKG_CONFIG_PATH' : [os .path .join (x , 'pkgconfig' ) for x in lib_paths + [ 'share' ]],
13271341 'ACLOCAL_PATH' : [os .path .join ('share' , 'aclocal' )],
13281342 'CLASSPATH' : ['*.jar' ],
1343+ 'XDG_DATA_DIRS' : ['share' ],
1344+ 'GI_TYPELIB_PATH' : [os .path .join (x , 'girepository-*' ) for x in lib_paths ],
13291345 }
13301346
13311347 def load_module (self , mod_paths = None , purge = True , extra_modules = None ):
@@ -1431,43 +1447,18 @@ def skip_extensions(self):
14311447
14321448 if not exts_filter or len (exts_filter ) == 0 :
14331449 raise EasyBuildError ("Skipping of extensions, but no exts_filter set in easyconfig" )
1434- elif isinstance (exts_filter , string_type ) or len (exts_filter ) != 2 :
1435- raise EasyBuildError ('exts_filter should be a list or tuple of ("command","input")' )
1436- cmdtmpl = exts_filter [0 ]
1437- cmdinputtmpl = exts_filter [1 ]
14381450
14391451 res = []
14401452 for ext in self .exts :
1441- name = ext ['name' ]
1442- if 'options' in ext and 'modulename' in ext ['options' ]:
1443- modname = ext ['options' ]['modulename' ]
1444- else :
1445- modname = name
1446- tmpldict = {
1447- 'ext_name' : modname ,
1448- 'ext_version' : ext .get ('version' ),
1449- 'src' : ext .get ('source' ),
1450- }
1451-
1452- try :
1453- cmd = cmdtmpl % tmpldict
1454- except KeyError as err :
1455- msg = "KeyError occurred on completing extension filter template: %s; "
1456- msg += "'name'/'version' keys are no longer supported, should use 'ext_name'/'ext_version' instead"
1457- self .log .nosupport (msg % err , '2.0' )
1458-
1459- if cmdinputtmpl :
1460- stdin = cmdinputtmpl % tmpldict
1461- (cmdstdouterr , ec ) = run_cmd (cmd , log_all = False , log_ok = False , simple = False , inp = stdin , regexp = False )
1462- else :
1463- (cmdstdouterr , ec ) = run_cmd (cmd , log_all = False , log_ok = False , simple = False , regexp = False )
1453+ cmd , stdin = resolve_exts_filter_template (exts_filter , ext )
1454+ (cmdstdouterr , ec ) = run_cmd (cmd , log_all = False , log_ok = False , simple = False , inp = stdin , regexp = False )
14641455 self .log .info ("exts_filter result %s %s" , cmdstdouterr , ec )
14651456 if ec :
1466- self .log .info ("Not skipping %s" % name )
1457+ self .log .info ("Not skipping %s" % ext [ ' name' ] )
14671458 self .log .debug ("exit code: %s, stdout/err: %s" % (ec , cmdstdouterr ))
14681459 res .append (ext )
14691460 else :
1470- self .log .info ("Skipping %s" % name )
1461+ self .log .info ("Skipping %s" % ext [ ' name' ] )
14711462 self .exts = res
14721463
14731464 #
@@ -1585,8 +1576,9 @@ def post_iter_step(self):
15851576
15861577 def det_iter_cnt (self ):
15871578 """Determine iteration count based on configure/build/install options that may be lists."""
1588- iter_opt_counts = [len (self .cfg [opt ]) for opt in ITERATE_OPTIONS
1589- if opt not in ['builddependencies' ] and isinstance (self .cfg [opt ], (list , tuple ))]
1579+ # Using get_ref to avoid resolving templates as their required attributes may not be available yet
1580+ iter_opt_counts = [len (self .cfg .get_ref (opt )) for opt in ITERATE_OPTIONS
1581+ if opt not in ['builddependencies' ] and isinstance (self .cfg .get_ref (opt ), (list , tuple ))]
15901582
15911583 # we need to take into account that builddependencies is always a list
15921584 # we're only iterating over it if it's a list of lists
@@ -1814,7 +1806,7 @@ def checksum_step(self):
18141806
18151807 def check_checksums_for (self , ent , sub = '' , source_cnt = None ):
18161808 """
1817- Utility method: check whether checksums for all sources/patches are available, for given entity
1809+ Utility method: check whether SHA256 checksums for all sources/patches are available, for given entity
18181810 """
18191811 ec_fn = os .path .basename (self .cfg .path )
18201812 checksum_issues = []
@@ -1841,8 +1833,19 @@ def check_checksums_for(self, ent, sub='', source_cnt=None):
18411833 if isinstance (checksum , dict ):
18421834 checksum = checksum .get (fn )
18431835
1844- if not is_sha256_checksum (checksum ):
1845- msg = "Non-SHA256 checksum found for %s: %s" % (fn , checksum )
1836+ # take into account that we may encounter a tuple of valid SHA256 checksums
1837+ # (see https://github.com/easybuilders/easybuild-framework/pull/2958)
1838+ if isinstance (checksum , tuple ):
1839+ # 1st tuple item may indicate checksum type, must be SHA256 or else it's blatently ignored here
1840+ if len (checksum ) == 2 and checksum [0 ] == CHECKSUM_TYPE_SHA256 :
1841+ valid_checksums = (checksum [1 ],)
1842+ else :
1843+ valid_checksums = checksum
1844+ else :
1845+ valid_checksums = (checksum ,)
1846+
1847+ if not all (is_sha256_checksum (c ) for c in valid_checksums ):
1848+ msg = "Non-SHA256 checksum(s) found for %s: %s" % (fn , valid_checksums )
18461849 checksum_issues .append (msg )
18471850
18481851 return checksum_issues
@@ -2760,19 +2763,31 @@ def permissions_step(self):
27602763
27612764 # add read permissions for everybody on all files, taking into account group (if any)
27622765 perms = stat .S_IRUSR | stat .S_IRGRP
2766+ # directory permissions: readable (r) & searchable (x)
2767+ dir_perms = stat .S_IXUSR | stat .S_IXGRP
27632768 self .log .debug ("Ensuring read permissions for user/group on install dir (recursively)" )
2769+
27642770 if self .group is None :
27652771 perms |= stat .S_IROTH
2772+ dir_perms |= stat .S_IXOTH
27662773 self .log .debug ("Also ensuring read permissions for others on install dir (no group specified)" )
27672774
27682775 umask = build_option ('umask' )
27692776 if umask is not None :
27702777 # umask is specified as a string, so interpret it first as integer in octal, then take complement (~)
27712778 perms &= ~ int (umask , 8 )
2779+ dir_perms &= ~ int (umask , 8 )
27722780 self .log .debug ("Taking umask '%s' into account when ensuring read permissions to install dir" , umask )
27732781
2782+ self .log .debug ("Adding file read permissions in %s using '%s'" , self .installdir , oct (perms ))
27742783 adjust_permissions (self .installdir , perms , add = True , recursive = True , relative = True , ignore_errors = True )
2775- self .log .info ("Successfully added read permissions '%s' recursively on install dir" , oct (perms ))
2784+
2785+ # also ensure directories have exec permissions (so they can be opened)
2786+ self .log .debug ("Adding directory search permissions in %s using '%s'" , self .installdir , oct (dir_perms ))
2787+ adjust_permissions (self .installdir , dir_perms , add = True , recursive = True , relative = True , onlydirs = True ,
2788+ ignore_errors = True )
2789+
2790+ self .log .info ("Successfully added read permissions recursively on install dir %s" , self .installdir )
27762791
27772792 def test_cases_step (self ):
27782793 """
0 commit comments