6464from easybuild .tools .config import install_path , log_path , package_path , source_paths
6565from easybuild .tools .environment import restore_env , sanitize_env
6666from easybuild .tools .filetools import DEFAULT_CHECKSUM
67- from easybuild .tools .filetools import adjust_permissions , apply_patch , convert_name , download_file , encode_class_name
68- from easybuild .tools .filetools import extract_file , mkdir , move_logs , read_file , rmtree2
69- from easybuild .tools .filetools import write_file , compute_checksum , verify_checksum , weld_paths
67+ from easybuild .tools .filetools import adjust_permissions , apply_patch , convert_name , derive_alt_pypi_url
68+ from easybuild .tools .filetools import download_file , encode_class_name , extract_file , is_alt_pypi_url , mkdir , move_logs
69+ from easybuild .tools .filetools import read_file , rmtree2 , write_file , compute_checksum , verify_checksum , weld_paths
7070from easybuild .tools .run import run_cmd
7171from easybuild .tools .jenkins import write_to_xml
7272from easybuild .tools .module_generator import ModuleGeneratorLua , ModuleGeneratorTcl , module_generator
7373from easybuild .tools .module_naming_scheme .utilities import det_full_ec_version
7474from easybuild .tools .modules import ROOT_ENV_VAR_NAME_PREFIX , VERSION_ENV_VAR_NAME_PREFIX , DEVEL_ENV_VAR_NAME_PREFIX
75- from easybuild .tools .modules import get_software_root_env_var_name , get_software_version_env_var_name
76- from easybuild .tools .modules import get_software_root , modules_tool
75+ from easybuild .tools .modules import invalidate_module_caches_for , get_software_root , get_software_root_env_var_name
76+ from easybuild .tools .modules import get_software_version_env_var_name
7777from easybuild .tools .package .utilities import package
7878from easybuild .tools .repository .repository import init_repository
7979from easybuild .tools .toolchain import DUMMY_TOOLCHAIN_NAME
101101
102102MODULE_ONLY_STEPS = [MODULE_STEP , PREPARE_STEP , READY_STEP , SANITYCHECK_STEP ]
103103
104+ # string part of URL for Python packages on PyPI that indicates needs to be rewritten (see derive_alt_pypi_url)
105+ PYPI_PKG_URL_PATTERN = 'pypi.python.org/packages/source/'
106+
104107
105108_log = fancylogger .getLogger ('easyblock' )
106109
@@ -154,8 +157,14 @@ def __init__(self, ec):
154157 self .skip = None
155158 self .module_extra_extensions = '' # extra stuff for module file required by extensions
156159
160+ # easyconfig for this application
161+ if isinstance (ec , EasyConfig ):
162+ self .cfg = ec
163+ else :
164+ raise EasyBuildError ("Value of incorrect type passed to EasyBlock constructor: %s ('%s')" , type (ec ), ec )
165+
157166 # modules interface with default MODULEPATH
158- self .modules_tool = modules_tool ()
167+ self .modules_tool = self . cfg . modules_tool
159168 # module generator
160169 self .module_generator = module_generator (self , fake = True )
161170
@@ -170,12 +179,6 @@ def __init__(self, ec):
170179 if modules_header_path is not None :
171180 self .modules_header = read_file (modules_header_path )
172181
173- # easyconfig for this application
174- if isinstance (ec , EasyConfig ):
175- self .cfg = ec
176- else :
177- raise EasyBuildError ("Value of incorrect type passed to EasyBlock constructor: %s ('%s')" , type (ec ), ec )
178-
179182 # determine install subdirectory, based on module name
180183 self .install_subdir = None
181184
@@ -622,6 +625,16 @@ def obtain_file(self, filename, extension=False, urls=None):
622625 self .log .warning ("Source URL %s is of unknown type, so ignoring it." % url )
623626 continue
624627
628+ # PyPI URLs may need to be converted due to change in format of these URLs,
629+ # cfr. https://bitbucket.org/pypa/pypi/issues/438
630+ if PYPI_PKG_URL_PATTERN in fullurl and not is_alt_pypi_url (fullurl ):
631+ alt_url = derive_alt_pypi_url (fullurl )
632+ if alt_url :
633+ _log .debug ("Using alternate PyPI URL for %s: %s" , fullurl , alt_url )
634+ fullurl = alt_url
635+ else :
636+ _log .debug ("Failed to derive alternate PyPI URL for %s, so retaining the original" , fullurl )
637+
625638 if self .dry_run :
626639 self .dry_run_msg (" * %s will be downloaded to %s" , filename , targetpath )
627640 if extension and urls :
@@ -693,6 +706,13 @@ def short_mod_name(self):
693706 """
694707 return self .cfg .short_mod_name
695708
709+ @property
710+ def mod_subdir (self ):
711+ """
712+ Subdirectory in module install path
713+ """
714+ return self .cfg .mod_subdir
715+
696716 @property
697717 def moduleGenerator (self ):
698718 """
@@ -905,7 +925,7 @@ def make_module_dep(self, unload_info=None):
905925 self .log .debug ("Full list of dependencies: %s" % deps )
906926
907927 # exclude dependencies that extend $MODULEPATH and form the path to the top of the module tree (if any)
908- full_mod_subdir = os .path .join (self .installdir_mod , self .cfg . mod_subdir )
928+ full_mod_subdir = os .path .join (self .installdir_mod , self .mod_subdir )
909929 init_modpaths = mns .det_init_modulepaths (self .cfg )
910930 top_paths = [self .installdir_mod ] + [os .path .join (self .installdir_mod , p ) for p in init_modpaths ]
911931 excluded_deps = self .modules_tool .path_to_top_of_module_tree (top_paths , self .cfg .short_mod_name ,
@@ -1132,7 +1152,14 @@ def load_module(self, mod_paths=None, purge=True):
11321152 if mod_paths is None :
11331153 mod_paths = []
11341154 all_mod_paths = mod_paths + ActiveMNS ().det_init_modulepaths (self .cfg )
1135- mods = [self .full_mod_name ]
1155+
1156+ # for flat module naming schemes, we can load the module directly;
1157+ # for non-flat (hierarchical) module naming schemes, we may need to load the toolchain module first
1158+ # to update $MODULEPATH such that the module can be loaded using the short module name
1159+ mods = [self .short_mod_name ]
1160+ if self .mod_subdir and self .toolchain .name != DUMMY_TOOLCHAIN_NAME :
1161+ mods .insert (0 , self .toolchain .det_short_module_name ())
1162+
11361163 self .modules_tool .load (mods , mod_paths = all_mod_paths , purge = purge , init_env = self .initial_environ )
11371164 else :
11381165 self .log .warning ("Not loading module, since self.full_mod_name is not set." )
@@ -1148,7 +1175,7 @@ def load_fake_module(self, purge=False):
11481175 fake_mod_path = self .make_module_step (fake = True )
11491176
11501177 # load fake module
1151- self .modules_tool .prepend_module_path (fake_mod_path )
1178+ self .modules_tool .prepend_module_path (os . path . join ( fake_mod_path , self . mod_subdir ) )
11521179 self .load_module (purge = purge )
11531180
11541181 return (fake_mod_path , env )
@@ -1159,16 +1186,16 @@ def clean_up_fake_module(self, fake_mod_data):
11591186 """
11601187 fake_mod_path , env = fake_mod_data
11611188 # unload module and remove temporary module directory
1162- # self.full_mod_name might not be set (e.g. during unit tests)
1163- if fake_mod_path and self .full_mod_name is not None :
1189+ # self.short_mod_name might not be set (e.g. during unit tests)
1190+ if fake_mod_path and self .short_mod_name is not None :
11641191 try :
1165- self .modules_tool .unload ([self .full_mod_name ])
1166- self .modules_tool .remove_module_path (fake_mod_path )
1192+ self .modules_tool .unload ([self .short_mod_name ])
1193+ self .modules_tool .remove_module_path (os . path . join ( fake_mod_path , self . mod_subdir ) )
11671194 rmtree2 (os .path .dirname (fake_mod_path ))
11681195 except OSError , err :
11691196 raise EasyBuildError ("Failed to clean up fake module dir %s: %s" , fake_mod_path , err )
1170- elif self .full_mod_name is None :
1171- self .log .warning ("Not unloading module, since self.full_mod_name is not set." )
1197+ elif self .short_mod_name is None :
1198+ self .log .warning ("Not unloading module, since self.short_mod_name is not set." )
11721199
11731200 # restore original environment
11741201 restore_env (env )
@@ -1581,6 +1608,9 @@ def extensions_step(self, fetch=False):
15811608 if not self .dry_run :
15821609 fake_mod_data = self .load_fake_module (purge = True )
15831610
1611+ # also load modules for build dependencies again, since those are not loaded by the fake module
1612+ self .modules_tool .load (dep ['short_mod_name' ] for dep in self .cfg ['builddependencies' ])
1613+
15841614 self .prepare_for_extensions ()
15851615
15861616 if fetch :
@@ -1666,6 +1696,17 @@ def extensions_step(self, fetch=False):
16661696 msg = "\n * installing extension %s %s using '%s' easyblock\n " % (ext ['name' ], ext ['version' ], eb_class )
16671697 self .dry_run_msg (msg )
16681698
1699+ self .log .debug ("List of loaded modules: %s" , self .modules_tool .list ())
1700+
1701+ # prepare toolchain build environment, but only when not doing a dry run
1702+ # since in that case the build environment is the same as for the parent
1703+ if self .dry_run :
1704+ self .dry_run_msg ("defining build environment based on toolchain (options) and dependencies..." )
1705+ else :
1706+ # don't reload modules for toolchain, there is no need since they will be loaded already;
1707+ # the (fake) module for the parent software gets loaded before installing extensions
1708+ inst .toolchain .prepare (onlymod = self .cfg ['onlytcmod' ], silent = True , loadmod = False )
1709+
16691710 # real work
16701711 inst .prerun ()
16711712 txt = inst .run ()
@@ -1951,10 +1992,19 @@ def make_module_step(self, fake=False):
19511992
19521993 else :
19531994 write_file (mod_filepath , txt )
1954-
19551995 self .log .info ("Module file %s written: %s" , mod_filepath , txt )
19561996
1957- # only update after generating final module file
1997+ # invalidate relevant 'module avail'/'module show' cache entries
1998+ modpath = self .module_generator .get_modules_path (fake = fake )
1999+ # consider both paths: for short module name, and subdir indicated by long module name
2000+ paths = [modpath ]
2001+ if self .mod_subdir :
2002+ paths .append (os .path .join (modpath , self .mod_subdir ))
2003+
2004+ for path in paths :
2005+ invalidate_module_caches_for (path )
2006+
2007+ # only update after generating final module file
19582008 if not fake :
19592009 self .modules_tool .update ()
19602010
@@ -1999,6 +2049,22 @@ def permissions_step(self):
19992049 adjust_permissions (self .installdir , perms , add = False , recursive = True , relative = True , ignore_errors = True )
20002050 self .log .info ("Successfully removed write permissions recursively for group/other on install dir." )
20012051
2052+ # add read permissions for everybody on all files, taking into account group (if any)
2053+ perms = stat .S_IRUSR | stat .S_IRGRP
2054+ self .log .debug ("Ensuring read permissions for user/group on install dir (recursively)" )
2055+ if self .group is None :
2056+ perms |= stat .S_IROTH
2057+ self .log .debug ("Also ensuring read permissions for others on install dir (no group specified)" )
2058+
2059+ umask = build_option ('umask' )
2060+ if umask is not None :
2061+ # umask is specified as a string, so interpret it first as integer in octal, then take complement (~)
2062+ perms &= ~ int (umask , 8 )
2063+ self .log .debug ("Taking umask '%s' into account when ensuring read permissions to install dir" , umask )
2064+
2065+ adjust_permissions (self .installdir , perms , add = True , recursive = True , relative = True , ignore_errors = True )
2066+ self .log .info ("Successfully added read permissions '%s' recursively on install dir" , oct (perms ))
2067+
20022068 def test_cases_step (self ):
20032069 """
20042070 Run provided test cases.
0 commit comments