7171from easybuild .tools .filetools import CHECKSUM_TYPE_MD5 , CHECKSUM_TYPE_SHA256
7272from easybuild .tools .filetools import adjust_permissions , apply_patch , back_up_file , change_dir , convert_name
7373from easybuild .tools .filetools import compute_checksum , copy_file , derive_alt_pypi_url , diff_files , download_file
74- from easybuild .tools .filetools import encode_class_name , extract_file , is_alt_pypi_url , mkdir , move_logs , read_file
75- from easybuild .tools .filetools import remove_file , rmtree2 , verify_checksum , weld_paths , write_file
74+ from easybuild .tools .filetools import encode_class_name , extract_file , is_alt_pypi_url , is_sha256_checksum , mkdir
75+ from easybuild .tools .filetools import move_logs , read_file , remove_file , rmtree2 , verify_checksum , weld_paths
76+ from easybuild .tools .filetools import write_file
7677from easybuild .tools .hooks import BUILD_STEP , CLEANUP_STEP , CONFIGURE_STEP , EXTENSIONS_STEP , FETCH_STEP , INSTALL_STEP
7778from easybuild .tools .hooks import MODULE_STEP , PACKAGE_STEP , PATCH_STEP , PERMISSIONS_STEP , POSTPROC_STEP , PREPARE_STEP
78- from easybuild .tools .hooks import READY_STEP , SANITYCHECK_STEP , SOURCE_STEP , TEST_STEP , TESTCASES_STEP , run_hook
79+ from easybuild .tools .hooks import READY_STEP , SANITYCHECK_STEP , SOURCE_STEP , TEST_STEP , TESTCASES_STEP
80+ from easybuild .tools .hooks import load_hooks , run_hook
7981from easybuild .tools .run import run_cmd
8082from easybuild .tools .jenkins import write_to_xml
8183from easybuild .tools .module_generator import ModuleGeneratorLua , ModuleGeneratorTcl , module_generator , dependencies_for
@@ -122,7 +124,7 @@ def extra_options(extra=None):
122124 #
123125 # INIT
124126 #
125- def __init__ (self , ec , hooks = None ):
127+ def __init__ (self , ec ):
126128 """
127129 Initialize the EasyBlock instance.
128130 :param ec: a parsed easyconfig file (EasyConfig instance)
@@ -132,7 +134,7 @@ def __init__(self, ec, hooks=None):
132134 self .orig_workdir = os .getcwd ()
133135
134136 # list of pre- and post-step hooks
135- self .hooks = hooks or []
137+ self .hooks = load_hooks ( build_option ( ' hooks' ))
136138
137139 # list of patch/source files, along with checksums
138140 self .patches = []
@@ -488,7 +490,7 @@ def fetch_extension_sources(self, skip_checksums=False):
488490 'options' : ext_options ,
489491 }
490492
491- checksums = ext_options .get ('checksums' , None )
493+ checksums = ext_options .get ('checksums' , [] )
492494
493495 if ext_options .get ('source_tmpl' , None ):
494496 fn = resolve_template (ext_options ['source_tmpl' ], ext_src )
@@ -511,14 +513,15 @@ def fetch_extension_sources(self, skip_checksums=False):
511513 src_checksum = compute_checksum (src_fn , checksum_type = checksum_type )
512514 self .log .info ("%s checksum for %s: %s" , checksum_type , src_fn , src_checksum )
513515
514- if checksums :
515- fn_checksum = self .get_checksum_for (checksums , filename = src_fn , index = 0 )
516- if verify_checksum (src_fn , fn_checksum ):
517- self .log .info ('Checksum for extension source %s verified' , fn )
518- elif build_option ('ignore_checksums' ):
519- print_warning ("Ignoring failing checksum verification for %s" % fn )
520- else :
521- raise EasyBuildError ('Checksum verification for extension source %s failed' , fn )
516+ # verify checksum (if provided)
517+ self .log .debug ('Verifying checksums for extension source...' )
518+ fn_checksum = self .get_checksum_for (checksums , filename = src_fn , index = 0 )
519+ if verify_checksum (src_fn , fn_checksum ):
520+ self .log .info ('Checksum for extension source %s verified' , fn )
521+ elif build_option ('ignore_checksums' ):
522+ print_warning ("Ignoring failing checksum verification for %s" % fn )
523+ else :
524+ raise EasyBuildError ('Checksum verification for extension source %s failed' , fn )
522525
523526 ext_patches = self .fetch_patches (patch_specs = ext_options .get ('patches' , []), extension = True )
524527 if ext_patches :
@@ -533,16 +536,16 @@ def fetch_extension_sources(self, skip_checksums=False):
533536 checksum = compute_checksum (patch , checksum_type = checksum_type )
534537 self .log .info ("%s checksum for %s: %s" , checksum_type , patch , checksum )
535538
536- if checksums :
537- self .log .debug ('Verifying checksums for extension patches...' )
538- for idx , patch in enumerate (ext_patches ):
539- checksum = self .get_checksum_for (checksums [1 :], filename = patch , index = idx )
540- if verify_checksum (patch , checksum ):
541- self .log .info ('Checksum for extension patch %s verified' , patch )
542- elif build_option ('ignore_checksums' ):
543- print_warning ("Ignoring failing checksum verification for %s" % patch )
544- else :
545- raise EasyBuildError ('Checksum for extension patch %s failed' , patch )
539+ # verify checksum ( if provided)
540+ self .log .debug ('Verifying checksums for extension patches...' )
541+ for idx , patch in enumerate (ext_patches ):
542+ checksum = self .get_checksum_for (checksums [1 :], filename = patch , index = idx )
543+ if verify_checksum (patch , checksum ):
544+ self .log .info ('Checksum for extension patch %s verified' , patch )
545+ elif build_option ('ignore_checksums' ):
546+ print_warning ("Ignoring failing checksum verification for %s" % patch )
547+ else :
548+ raise EasyBuildError ('Checksum for extension patch %s failed' , patch )
546549 else :
547550 self .log .debug ('No patches found for extension %s.' % ext_name )
548551
@@ -1678,6 +1681,63 @@ def checksum_step(self):
16781681 else :
16791682 raise EasyBuildError ("Checksum verification for %s using %s failed." , fil ['path' ], fil ['checksum' ])
16801683
1684+ def check_checksums_for (self , ent , sub = '' , source_cnt = None ):
1685+ """
1686+ Utility method: check whether checksums for all sources/patches are available, for given entity
1687+ """
1688+ ec_fn = os .path .basename (self .cfg .path )
1689+ checksum_issues = []
1690+
1691+ sources = ent .get ('sources' , [])
1692+ patches = ent .get ('patches' , [])
1693+ checksums = ent .get ('checksums' , [])
1694+
1695+ if source_cnt is None :
1696+ source_cnt = len (sources )
1697+ patch_cnt , checksum_cnt = len (patches ), len (checksums )
1698+
1699+ if (source_cnt + patch_cnt ) != checksum_cnt :
1700+ if sub :
1701+ sub = "%s in %s" % (sub , ec_fn )
1702+ else :
1703+ sub = "in %s" % ec_fn
1704+ msg = "Checksums missing for one or more sources/patches %s: " % sub
1705+ msg += "found %d sources + %d patches " % (source_cnt , patch_cnt )
1706+ msg += "vs %d checksums" % checksum_cnt
1707+ checksum_issues .append (msg )
1708+
1709+ for fn , checksum in zip (sources + patches , checksums ):
1710+ if not is_sha256_checksum (checksum ):
1711+ msg = "Non-SHA256 checksum found for %s: %s" % (fn , checksum )
1712+ checksum_issues .append (msg )
1713+
1714+ return checksum_issues
1715+
1716+ def check_checksums (self ):
1717+ """
1718+ Check whether a SHA256 checksum is available for all sources & patches (incl. extensions).
1719+
1720+ :return: list of strings describing checksum issues (missing checksums, wrong checksum type, etc.)
1721+ """
1722+ checksum_issues = []
1723+
1724+ # check whether a checksum if available for every source + patch
1725+ checksum_issues .extend (self .check_checksums_for (self .cfg ))
1726+
1727+ # also check checksums for extensions
1728+ for ext in self .cfg ['exts_list' ]:
1729+ # just skip extensions for which only a name is specified
1730+ # those are just there to check for things that are in the "standard library"
1731+ if not isinstance (ext , basestring ):
1732+ ext_name = ext [0 ]
1733+ # take into account that extension may be a 2-tuple with just name/version
1734+ ext_opts = ext [2 ] if len (ext ) == 3 else {}
1735+ # only a single source per extension is supported (see source_tmpl)
1736+ res = self .check_checksums_for (ext_opts , sub = "of extension %s" % ext_name , source_cnt = 1 )
1737+ checksum_issues .extend (res )
1738+
1739+ return checksum_issues
1740+
16811741 def extract_step (self ):
16821742 """
16831743 Unpack the source files.
@@ -2337,6 +2397,17 @@ def cleanup_step(self):
23372397
23382398 self .restore_iterate_opts ()
23392399
2400+ def invalidate_module_caches (self , modpath ):
2401+ """Helper method to invalidate module caches for specified module path."""
2402+ # invalidate relevant 'module avail'/'module show' cache entries
2403+ # consider both paths: for short module name, and subdir indicated by long module name
2404+ paths = [modpath ]
2405+ if self .mod_subdir :
2406+ paths .append (os .path .join (modpath , self .mod_subdir ))
2407+
2408+ for path in paths :
2409+ invalidate_module_caches_for (path )
2410+
23402411 def make_module_step (self , fake = False ):
23412412 """
23422413 Generate module file
@@ -2386,14 +2457,7 @@ def make_module_step(self, fake=False):
23862457 self .log .info (diff_msg )
23872458 print_msg (diff_msg , log = self .log )
23882459
2389- # invalidate relevant 'module avail'/'module show' cache entries
2390- # consider both paths: for short module name, and subdir indicated by long module name
2391- paths = [modpath ]
2392- if self .mod_subdir :
2393- paths .append (os .path .join (modpath , self .mod_subdir ))
2394-
2395- for path in paths :
2396- invalidate_module_caches_for (path )
2460+ self .invalidate_module_caches (modpath )
23972461
23982462 # only update after generating final module file
23992463 if not fake :
@@ -2498,8 +2562,11 @@ def _skip_step(self, step, skippable):
24982562 force = build_option ('force' ) or build_option ('rebuild' )
24992563 skip = False
25002564
2501- # skip step if specified as individual (skippable) step
2502- if skippable and (self .skip or step in self .cfg ['skipsteps' ]):
2565+ # under --skip, sanity check is not skipped
2566+ cli_skip = self .skip and step != SANITYCHECK_STEP
2567+
2568+ # skip step if specified as individual (skippable) step, or if --skip is used
2569+ if skippable and (cli_skip or step in self .cfg ['skipsteps' ]):
25032570 self .log .info ("Skipping %s step (skip: %s, skipsteps: %s)" , step , self .skip , self .cfg ['skipsteps' ])
25042571 skip = True
25052572
@@ -2645,7 +2712,7 @@ def install_step_spec(initial):
26452712 steps_part3 = [
26462713 (EXTENSIONS_STEP , 'taking care of extensions' , [lambda x : x .extensions_step ], False ),
26472714 (POSTPROC_STEP , 'postprocessing' , [lambda x : x .post_install_step ], True ),
2648- (SANITYCHECK_STEP , 'sanity checking' , [lambda x : x .sanity_check_step ], False ),
2715+ (SANITYCHECK_STEP , 'sanity checking' , [lambda x : x .sanity_check_step ], True ),
26492716 (CLEANUP_STEP , 'cleaning up' , [lambda x : x .cleanup_step ], False ),
26502717 (MODULE_STEP , 'creating module' , [lambda x : x .make_module_step ], False ),
26512718 (PERMISSIONS_STEP , 'permissions' , [lambda x : x .permissions_step ], False ),
@@ -2708,7 +2775,7 @@ def print_dry_run_note(loc, silent=True):
27082775 dry_run_msg (msg , silent = silent )
27092776
27102777
2711- def build_and_install_one (ecdict , init_env , hooks = None ):
2778+ def build_and_install_one (ecdict , init_env ):
27122779 """
27132780 Build the software
27142781 :param ecdict: dictionary contaning parsed easyconfig + metadata
@@ -2746,7 +2813,7 @@ def build_and_install_one(ecdict, init_env, hooks=None):
27462813 try :
27472814 app_class = get_easyblock_class (easyblock , name = name )
27482815
2749- app = app_class (ecdict ['ec' ], hooks = hooks )
2816+ app = app_class (ecdict ['ec' ])
27502817 _log .info ("Obtained application instance of for %s (easyblock: %s)" % (name , easyblock ))
27512818 except EasyBuildError , err :
27522819 print_error ("Failed to get application instance for %s (easyblock: %s): %s" % (name , easyblock , err .msg ),
@@ -2807,13 +2874,14 @@ def build_and_install_one(ecdict, init_env, hooks=None):
28072874 buildstats = get_build_stats (app , start_time , build_option ('command_line' ))
28082875 _log .info ("Build stats: %s" % buildstats )
28092876
2810- if build_option ("minimal_toolchains" ):
2811- # for reproducability we dump out the parsed easyconfig since the contents are affected when
2812- # --minimal-toolchains (and --use-existing-modules) is used
2813- # TODO --try-toolchain needs to be fixed so this doesn't play havoc with it's usability
2814- reprod_spec = os .path .join (new_log_dir , 'reprod' , ec_filename )
2877+ # for reproducability we dump out the fully processed easyconfig since the contents can be affected
2878+ # by subtoolchain resolution (and related options) and/or hooks
2879+ reprod_spec = os .path .join (new_log_dir , 'reprod' , ec_filename )
2880+ try :
28152881 app .cfg .dump (reprod_spec )
2816- _log .debug ("Dumped easyconfig tweaked via --minimal-toolchains to %s" , reprod_spec )
2882+ _log .info ("Dumped fully processed easyconfig to %s" , reprod_spec )
2883+ except NotImplementedError as err :
2884+ _log .warn ("Unable to dumped fully processed easyconfig to %s: %s" , reprod_spec , err )
28172885
28182886 try :
28192887 # upload easyconfig (and patch files) to central repository
0 commit comments