7272from easybuild .tools .filetools import CHECKSUM_TYPE_MD5 , CHECKSUM_TYPE_SHA256
7373from easybuild .tools .filetools import adjust_permissions , apply_patch , back_up_file , change_dir , convert_name
7474from easybuild .tools .filetools import compute_checksum , copy_file , derive_alt_pypi_url , diff_files , download_file
75- from easybuild .tools .filetools import encode_class_name , extract_file , is_alt_pypi_url , is_sha256_checksum
76- from easybuild .tools .filetools import get_source_tarball_from_git , mkdir , move_logs , read_file , remove_file , rmtree2
75+ from easybuild .tools .filetools import encode_class_name , extract_file , get_source_tarball_from_git , is_alt_pypi_url
76+ from easybuild .tools .filetools import is_sha256_checksum , mkdir , move_logs , read_file , remove_file , rmtree2
7777from easybuild .tools .filetools import verify_checksum , weld_paths , write_file
7878from easybuild .tools .hooks import BUILD_STEP , CLEANUP_STEP , CONFIGURE_STEP , EXTENSIONS_STEP , FETCH_STEP , INSTALL_STEP
7979from easybuild .tools .hooks import MODULE_STEP , PACKAGE_STEP , PATCH_STEP , PERMISSIONS_STEP , POSTPROC_STEP , PREPARE_STEP
80- from easybuild .tools .hooks import READY_STEP , SANITYCHECK_STEP , SOURCE_STEP , TEST_STEP , TESTCASES_STEP , run_hook
80+ from easybuild .tools .hooks import READY_STEP , SANITYCHECK_STEP , SOURCE_STEP , TEST_STEP , TESTCASES_STEP
81+ from easybuild .tools .hooks import load_hooks , run_hook
8182from easybuild .tools .run import run_cmd
8283from easybuild .tools .jenkins import write_to_xml
8384from easybuild .tools .module_generator import ModuleGeneratorLua , ModuleGeneratorTcl , module_generator , dependencies_for
@@ -124,7 +125,7 @@ def extra_options(extra=None):
124125 #
125126 # INIT
126127 #
127- def __init__ (self , ec , hooks = None ):
128+ def __init__ (self , ec ):
128129 """
129130 Initialize the EasyBlock instance.
130131 :param ec: a parsed easyconfig file (EasyConfig instance)
@@ -134,7 +135,7 @@ def __init__(self, ec, hooks=None):
134135 self .orig_workdir = os .getcwd ()
135136
136137 # list of pre- and post-step hooks
137- self .hooks = hooks or []
138+ self .hooks = load_hooks ( build_option ( ' hooks' ))
138139
139140 # list of patch/source files, along with checksums
140141 self .patches = []
@@ -492,7 +493,7 @@ def fetch_extension_sources(self, skip_checksums=False):
492493 'options' : ext_options ,
493494 }
494495
495- checksums = ext_options .get ('checksums' , None )
496+ checksums = ext_options .get ('checksums' , [] )
496497
497498 if ext_options .get ('source_tmpl' , None ):
498499 fn = resolve_template (ext_options ['source_tmpl' ], ext_src )
@@ -515,14 +516,15 @@ def fetch_extension_sources(self, skip_checksums=False):
515516 src_checksum = compute_checksum (src_fn , checksum_type = checksum_type )
516517 self .log .info ("%s checksum for %s: %s" , checksum_type , src_fn , src_checksum )
517518
518- if checksums :
519- fn_checksum = self .get_checksum_for (checksums , filename = src_fn , index = 0 )
520- if verify_checksum (src_fn , fn_checksum ):
521- self .log .info ('Checksum for extension source %s verified' , fn )
522- elif build_option ('ignore_checksums' ):
523- print_warning ("Ignoring failing checksum verification for %s" % fn )
524- else :
525- raise EasyBuildError ('Checksum verification for extension source %s failed' , fn )
519+ # verify checksum (if provided)
520+ self .log .debug ('Verifying checksums for extension source...' )
521+ fn_checksum = self .get_checksum_for (checksums , filename = src_fn , index = 0 )
522+ if verify_checksum (src_fn , fn_checksum ):
523+ self .log .info ('Checksum for extension source %s verified' , fn )
524+ elif build_option ('ignore_checksums' ):
525+ print_warning ("Ignoring failing checksum verification for %s" % fn )
526+ else :
527+ raise EasyBuildError ('Checksum verification for extension source %s failed' , fn )
526528
527529 ext_patches = self .fetch_patches (patch_specs = ext_options .get ('patches' , []), extension = True )
528530 if ext_patches :
@@ -537,16 +539,16 @@ def fetch_extension_sources(self, skip_checksums=False):
537539 checksum = compute_checksum (patch , checksum_type = checksum_type )
538540 self .log .info ("%s checksum for %s: %s" , checksum_type , patch , checksum )
539541
540- if checksums :
541- self .log .debug ('Verifying checksums for extension patches...' )
542- for idx , patch in enumerate (ext_patches ):
543- checksum = self .get_checksum_for (checksums [1 :], filename = patch , index = idx )
544- if verify_checksum (patch , checksum ):
545- self .log .info ('Checksum for extension patch %s verified' , patch )
546- elif build_option ('ignore_checksums' ):
547- print_warning ("Ignoring failing checksum verification for %s" % patch )
548- else :
549- raise EasyBuildError ('Checksum for extension patch %s failed' , patch )
542+ # verify checksum ( if provided)
543+ self .log .debug ('Verifying checksums for extension patches...' )
544+ for idx , patch in enumerate (ext_patches ):
545+ checksum = self .get_checksum_for (checksums [1 :], filename = patch , index = idx )
546+ if verify_checksum (patch , checksum ):
547+ self .log .info ('Checksum for extension patch %s verified' , patch )
548+ elif build_option ('ignore_checksums' ):
549+ print_warning ("Ignoring failing checksum verification for %s" % patch )
550+ else :
551+ raise EasyBuildError ('Checksum for extension patch %s failed' , patch )
550552 else :
551553 self .log .debug ('No patches found for extension %s.' % ext_name )
552554
@@ -1687,6 +1689,63 @@ def checksum_step(self):
16871689 else :
16881690 raise EasyBuildError ("Checksum verification for %s using %s failed." , fil ['path' ], fil ['checksum' ])
16891691
1692+ def check_checksums_for (self , ent , sub = '' , source_cnt = None ):
1693+ """
1694+ Utility method: check whether checksums for all sources/patches are available, for given entity
1695+ """
1696+ ec_fn = os .path .basename (self .cfg .path )
1697+ checksum_issues = []
1698+
1699+ sources = ent .get ('sources' , [])
1700+ patches = ent .get ('patches' , [])
1701+ checksums = ent .get ('checksums' , [])
1702+
1703+ if source_cnt is None :
1704+ source_cnt = len (sources )
1705+ patch_cnt , checksum_cnt = len (patches ), len (checksums )
1706+
1707+ if (source_cnt + patch_cnt ) != checksum_cnt :
1708+ if sub :
1709+ sub = "%s in %s" % (sub , ec_fn )
1710+ else :
1711+ sub = "in %s" % ec_fn
1712+ msg = "Checksums missing for one or more sources/patches %s: " % sub
1713+ msg += "found %d sources + %d patches " % (source_cnt , patch_cnt )
1714+ msg += "vs %d checksums" % checksum_cnt
1715+ checksum_issues .append (msg )
1716+
1717+ for fn , checksum in zip (sources + patches , checksums ):
1718+ if not is_sha256_checksum (checksum ):
1719+ msg = "Non-SHA256 checksum found for %s: %s" % (fn , checksum )
1720+ checksum_issues .append (msg )
1721+
1722+ return checksum_issues
1723+
1724+ def check_checksums (self ):
1725+ """
1726+ Check whether a SHA256 checksum is available for all sources & patches (incl. extensions).
1727+
1728+ :return: list of strings describing checksum issues (missing checksums, wrong checksum type, etc.)
1729+ """
1730+ checksum_issues = []
1731+
1732+ # check whether a checksum if available for every source + patch
1733+ checksum_issues .extend (self .check_checksums_for (self .cfg ))
1734+
1735+ # also check checksums for extensions
1736+ for ext in self .cfg ['exts_list' ]:
1737+ # just skip extensions for which only a name is specified
1738+ # those are just there to check for things that are in the "standard library"
1739+ if not isinstance (ext , basestring ):
1740+ ext_name = ext [0 ]
1741+ # take into account that extension may be a 2-tuple with just name/version
1742+ ext_opts = ext [2 ] if len (ext ) == 3 else {}
1743+ # only a single source per extension is supported (see source_tmpl)
1744+ res = self .check_checksums_for (ext_opts , sub = "of extension %s" % ext_name , source_cnt = 1 )
1745+ checksum_issues .extend (res )
1746+
1747+ return checksum_issues
1748+
16901749 def extract_step (self ):
16911750 """
16921751 Unpack the source files.
@@ -2346,6 +2405,17 @@ def cleanup_step(self):
23462405
23472406 self .restore_iterate_opts ()
23482407
2408+ def invalidate_module_caches (self , modpath ):
2409+ """Helper method to invalidate module caches for specified module path."""
2410+ # invalidate relevant 'module avail'/'module show' cache entries
2411+ # consider both paths: for short module name, and subdir indicated by long module name
2412+ paths = [modpath ]
2413+ if self .mod_subdir :
2414+ paths .append (os .path .join (modpath , self .mod_subdir ))
2415+
2416+ for path in paths :
2417+ invalidate_module_caches_for (path )
2418+
23492419 def make_module_step (self , fake = False ):
23502420 """
23512421 Generate module file
@@ -2395,14 +2465,7 @@ def make_module_step(self, fake=False):
23952465 self .log .info (diff_msg )
23962466 print_msg (diff_msg , log = self .log )
23972467
2398- # invalidate relevant 'module avail'/'module show' cache entries
2399- # consider both paths: for short module name, and subdir indicated by long module name
2400- paths = [modpath ]
2401- if self .mod_subdir :
2402- paths .append (os .path .join (modpath , self .mod_subdir ))
2403-
2404- for path in paths :
2405- invalidate_module_caches_for (path )
2468+ self .invalidate_module_caches (modpath )
24062469
24072470 # only update after generating final module file
24082471 if not fake :
@@ -2507,8 +2570,11 @@ def _skip_step(self, step, skippable):
25072570 force = build_option ('force' ) or build_option ('rebuild' )
25082571 skip = False
25092572
2510- # skip step if specified as individual (skippable) step
2511- if skippable and (self .skip or step in self .cfg ['skipsteps' ]):
2573+ # under --skip, sanity check is not skipped
2574+ cli_skip = self .skip and step != SANITYCHECK_STEP
2575+
2576+ # skip step if specified as individual (skippable) step, or if --skip is used
2577+ if skippable and (cli_skip or step in self .cfg ['skipsteps' ]):
25122578 self .log .info ("Skipping %s step (skip: %s, skipsteps: %s)" , step , self .skip , self .cfg ['skipsteps' ])
25132579 skip = True
25142580
@@ -2654,7 +2720,7 @@ def install_step_spec(initial):
26542720 steps_part3 = [
26552721 (EXTENSIONS_STEP , 'taking care of extensions' , [lambda x : x .extensions_step ], False ),
26562722 (POSTPROC_STEP , 'postprocessing' , [lambda x : x .post_install_step ], True ),
2657- (SANITYCHECK_STEP , 'sanity checking' , [lambda x : x .sanity_check_step ], False ),
2723+ (SANITYCHECK_STEP , 'sanity checking' , [lambda x : x .sanity_check_step ], True ),
26582724 (CLEANUP_STEP , 'cleaning up' , [lambda x : x .cleanup_step ], False ),
26592725 (MODULE_STEP , 'creating module' , [lambda x : x .make_module_step ], False ),
26602726 (PERMISSIONS_STEP , 'permissions' , [lambda x : x .permissions_step ], False ),
@@ -2717,7 +2783,7 @@ def print_dry_run_note(loc, silent=True):
27172783 dry_run_msg (msg , silent = silent )
27182784
27192785
2720- def build_and_install_one (ecdict , init_env , hooks = None ):
2786+ def build_and_install_one (ecdict , init_env ):
27212787 """
27222788 Build the software
27232789 :param ecdict: dictionary contaning parsed easyconfig + metadata
@@ -2755,7 +2821,7 @@ def build_and_install_one(ecdict, init_env, hooks=None):
27552821 try :
27562822 app_class = get_easyblock_class (easyblock , name = name )
27572823
2758- app = app_class (ecdict ['ec' ], hooks = hooks )
2824+ app = app_class (ecdict ['ec' ])
27592825 _log .info ("Obtained application instance of for %s (easyblock: %s)" % (name , easyblock ))
27602826 except EasyBuildError , err :
27612827 print_error ("Failed to get application instance for %s (easyblock: %s): %s" % (name , easyblock , err .msg ),
@@ -2816,13 +2882,14 @@ def build_and_install_one(ecdict, init_env, hooks=None):
28162882 buildstats = get_build_stats (app , start_time , build_option ('command_line' ))
28172883 _log .info ("Build stats: %s" % buildstats )
28182884
2819- if build_option ("minimal_toolchains" ):
2820- # for reproducability we dump out the parsed easyconfig since the contents are affected when
2821- # --minimal-toolchains (and --use-existing-modules) is used
2822- # TODO --try-toolchain needs to be fixed so this doesn't play havoc with it's usability
2823- reprod_spec = os .path .join (new_log_dir , 'reprod' , ec_filename )
2885+ # for reproducability we dump out the fully processed easyconfig since the contents can be affected
2886+ # by subtoolchain resolution (and related options) and/or hooks
2887+ reprod_spec = os .path .join (new_log_dir , 'reprod' , ec_filename )
2888+ try :
28242889 app .cfg .dump (reprod_spec )
2825- _log .debug ("Dumped easyconfig tweaked via --minimal-toolchains to %s" , reprod_spec )
2890+ _log .info ("Dumped fully processed easyconfig to %s" , reprod_spec )
2891+ except NotImplementedError as err :
2892+ _log .warn ("Unable to dumped fully processed easyconfig to %s: %s" , reprod_spec , err )
28262893
28272894 try :
28282895 # upload easyconfig (and patch files) to central repository
0 commit comments