9898from easybuild .tools .filetools import is_sha256_checksum , mkdir , move_file , move_logs , read_file , remove_dir
9999from easybuild .tools .filetools import remove_file , remove_lock , symlink , verify_checksum , weld_paths , write_file
100100from easybuild .tools .hooks import (
101- BUILD_STEP , CLEANUP_STEP , CONFIGURE_STEP , EXTENSIONS_STEP , EXTRACT_STEP , FETCH_STEP , INSTALL_STEP , MODULE_STEP ,
102- MODULE_WRITE , PACKAGE_STEP , PATCH_STEP , PERMISSIONS_STEP , POSTITER_STEP , POSTPROC_STEP , PREPARE_STEP , READY_STEP ,
103- SANITYCHECK_STEP , SINGLE_EXTENSION , TEST_STEP , TESTCASES_STEP , load_hooks , run_hook ,
101+ BUILD_STEP , CLEANUP_STEP , CONFIGURE_STEP , EASYBLOCK , EXTENSIONS_STEP , EXTRACT_STEP , FETCH_STEP , INSTALL_STEP ,
102+ MODULE_STEP , MODULE_WRITE , PACKAGE_STEP , PATCH_STEP , PERMISSIONS_STEP , POSTITER_STEP , POSTPROC_STEP , PREPARE_STEP ,
103+ READY_STEP , SANITYCHECK_STEP , SINGLE_EXTENSION , TEST_STEP , TESTCASES_STEP , load_hooks , run_hook ,
104104)
105105from easybuild .tools .run import RunShellCmdError , raise_run_shell_cmd_error , run_shell_cmd
106106from easybuild .tools .jenkins import write_to_xml
@@ -311,6 +311,9 @@ def __init__(self, ec, logfile=None):
311311 # initialize logger
312312 self ._init_log ()
313313
314+ # number of iterations
315+ self .iter_cnt = - 1
316+
314317 # try and use the specified group (if any)
315318 group_name = build_option ('group' )
316319 group_spec = self .cfg ['group' ]
@@ -448,7 +451,7 @@ def get_checksum_for(self, checksums, filename=None, index=None):
448451 if checksum and chksum_input_git is not None :
449452 # ignore any checksum for given filename due to changes in https://github.com/python/cpython/issues/90021
450453 # tarballs made for git repos are not reproducible when created with Python < 3.9
451- if sys .version_info [ 0 ] >= 3 and sys . version_info [ 1 ] < 9 :
454+ if sys .version_info < ( 3 , 9 ) :
452455 print_warning (
453456 "Reproducible tarballs of Git repos are only possible when using Python 3.9+ to run EasyBuild. "
454457 f"Skipping checksum verification of { chksum_input } since Python < 3.9 is used."
@@ -2387,7 +2390,8 @@ def handle_iterate_opts(self):
23872390 self .log .debug ("Iterating opt %s: %s" , opt , self .iter_opts [opt ])
23882391
23892392 if self .iter_opts :
2390- print_msg ("starting iteration #%s ..." % self .iter_idx , log = self .log , silent = self .silent )
2393+ print_msg (f"starting iteration { self .iter_idx + 1 } /{ self .iter_cnt } ..." , log = self .log ,
2394+ silent = self .silent )
23912395 self .log .info ("Current iteration index: %s" , self .iter_idx )
23922396
23932397 # pop first element from all iterative easyconfig parameters as next value to use
@@ -2429,7 +2433,7 @@ def det_iter_cnt(self):
24292433
24302434 # we need to take into account that builddependencies is always a list
24312435 # we're only iterating over it if it's a list of lists
2432- builddeps = self .cfg [ 'builddependencies' ]
2436+ builddeps = self .cfg . get_ref ( 'builddependencies' )
24332437 if all (isinstance (x , list ) for x in builddeps ):
24342438 iter_opt_counts .append (len (builddeps ))
24352439
@@ -2830,8 +2834,6 @@ def patch_step(self, beginpath=None, patches=None):
28302834 self .log .info ("Applying patch %s" % patch ['name' ])
28312835 trace_msg ("applying patch %s" % patch ['name' ])
28322836
2833- # patch source at specified index (first source if not specified)
2834- srcind = patch .get ('source' , 0 )
28352837 # if patch level is specified, use that (otherwise let apply_patch derive patch level)
28362838 level = patch .get ('level' , None )
28372839 # determine suffix of source path to apply patch in (if any)
@@ -2840,16 +2842,14 @@ def patch_step(self, beginpath=None, patches=None):
28402842 copy_patch = 'copy' in patch and 'sourcepath' not in patch
28412843 options = patch .get ('opts' , None ) # Extra options for patch command
28422844
2843- self .log .debug ("Source index: %s; patch level: %s; source path suffix: %s; copy patch: %s; options: %s" ,
2844- srcind , level , srcpathsuffix , copy_patch , options )
2845+ self .log .debug ("Patch level: %s; source path suffix: %s; copy patch: %s; options: %s" ,
2846+ level , srcpathsuffix , copy_patch , options )
28452847
28462848 if beginpath is None :
2847- try :
2848- beginpath = self .src [srcind ]['finalpath' ]
2849- self .log .debug ("Determine begin path for patch %s: %s" % (patch ['name' ], beginpath ))
2850- except IndexError as err :
2851- raise EasyBuildError ("Can't apply patch %s to source at index %s of list %s: %s" ,
2852- patch ['name' ], srcind , self .src , err )
2849+ if not self .src :
2850+ raise EasyBuildError ("Can't apply patch %s to source if no sources are given" , patch ['name' ])
2851+ beginpath = self .src [0 ]['finalpath' ]
2852+ self .log .debug ("Determined begin path for patch %s: %s" % (patch ['name' ], beginpath ))
28532853 else :
28542854 self .log .debug ("Using specified begin path for patch %s: %s" % (patch ['name' ], beginpath ))
28552855
@@ -4662,18 +4662,19 @@ def run_step(self, step, step_methods):
46624662 run_hook (step , self .hooks , pre_step_hook = True , args = [self ])
46634663
46644664 for step_method in step_methods :
4665+ # step_method is a lambda function that takes an EasyBlock instance as an argument,
4666+ # and returns the actual method
4667+ current_method = step_method (self )
46654668 # Remove leading underscore from e.g. "_test_step"
4666- method_name = '_' . join ( step_method . __code__ . co_names ) .lstrip ('_' )
4669+ method_name = current_method . __name__ .lstrip ('_' )
46674670 self .log .info ("Running method %s part of step %s" , method_name , step )
46684671
46694672 if self .dry_run :
46704673 self .dry_run_msg ("[%s method]" , method_name )
46714674
46724675 # if an known possible error occurs, just report it and continue
46734676 try :
4674- # step_method is a lambda function that takes an EasyBlock instance as an argument,
4675- # and returns the actual method, so use () to execute it
4676- step_method (self )()
4677+ current_method ()
46774678 except Exception as err :
46784679 if build_option ('extended_dry_run_ignore_errors' ):
46794680 dry_run_warning ("ignoring error %s" % err , silent = self .silent )
@@ -4682,9 +4683,7 @@ def run_step(self, step, step_methods):
46824683 raise
46834684 self .dry_run_msg ('' )
46844685 else :
4685- # step_method is a lambda function that takes an EasyBlock instance as an argument,
4686- # and returns the actual method, so use () to execute it
4687- step_method (self )()
4686+ current_method ()
46884687
46894688 run_hook (step , self .hooks , post_step_hook = True , args = [self ])
46904689
@@ -4794,7 +4793,8 @@ def run_all_steps(self, run_test_cases):
47944793 if self .cfg ['stop' ] == 'cfg' :
47954794 return True
47964795
4797- steps = self .get_steps (run_test_cases = run_test_cases , iteration_count = self .det_iter_cnt ())
4796+ self .iter_cnt = self .det_iter_cnt ()
4797+ steps = self .get_steps (run_test_cases = run_test_cases , iteration_count = self .iter_cnt )
47984798
47994799 # figure out how many steps will actually be run (not be skipped)
48004800 step_cnt = 0
@@ -4927,7 +4927,7 @@ def copy_build_dirs_logs_failed_install(application_log, silent, app, easyconfig
49274927 msg = f"Build directory of failed installation copied to { build_dirs_path } "
49284928
49294929 def operation (src , dest ):
4930- copy_dir (src , dest , dirs_exist_ok = True )
4930+ copy_dir (src , dest , dirs_exist_ok = True , symlinks = True )
49314931
49324932 operation_args .append ((operation , [app .builddir ], build_dirs_path , msg ))
49334933
@@ -5001,6 +5001,8 @@ def build_and_install_one(ecdict, init_env):
50015001 _log .debug ("Skip set to %s" % skip )
50025002 app .cfg ['skip' ] = skip
50035003
5004+ hooks = load_hooks (build_option ('hooks' ))
5005+
50045006 # build easyconfig
50055007 error_msg = '(no error)'
50065008 exit_code = None
@@ -5022,6 +5024,8 @@ def build_and_install_one(ecdict, init_env):
50225024 else :
50235025 enabled_write_permissions = False
50245026
5027+ run_hook (EASYBLOCK , hooks , pre_step_hook = True , args = [app ])
5028+
50255029 result = app .run_all_steps (run_test_cases = run_test_cases )
50265030
50275031 if not dry_run :
@@ -5033,7 +5037,9 @@ def build_and_install_one(ecdict, init_env):
50335037 except EasyBuildError as err :
50345038 _log .warning ("Failed to create build environment dump for easyconfig %s: %s" , reprod_spec , err )
50355039
5036- # also add any extension easyblocks used during the build for reproducibility
5040+ # also add any component/extension easyblocks used during the build for reproducibility
5041+ if hasattr (app , 'comp_instances' ):
5042+ copy_easyblocks_for_reprod ([comp for cfg , comp in app .comp_instances ], reprod_dir )
50375043 if app .ext_instances :
50385044 copy_easyblocks_for_reprod (app .ext_instances , reprod_dir )
50395045 # If not already done remove the granted write permissions if we did so
@@ -5120,8 +5126,12 @@ def ensure_writable_log_dir(log_dir):
51205126 block = det_full_ec_version (app .cfg ) + ".block"
51215127 repo .add_easyconfig (ecdict ['original_spec' ], app .name , block , buildstats , currentbuildstats )
51225128 repo .add_easyconfig (spec , app .name , det_full_ec_version (app .cfg ), buildstats , currentbuildstats )
5123- for patch in app .patches :
5124- repo .add_patch (patch ['path' ], app .name )
5129+ patches = app .patches
5130+ for ext in app .exts :
5131+ patches += ext .get ('patches' , [])
5132+ for patch in patches :
5133+ if 'path' in patch :
5134+ repo .add_patch (patch ['path' ], app .name )
51255135 repo .commit ("Built %s" % app .full_mod_name )
51265136 del repo
51275137 except EasyBuildError as err :
@@ -5143,10 +5153,14 @@ def ensure_writable_log_dir(log_dir):
51435153 _log .debug ("Copied easyconfig file %s to %s" , spec , newspec )
51445154
51455155 # copy patches
5146- for patch in app .patches :
5147- target = os .path .join (new_log_dir , os .path .basename (patch ['path' ]))
5148- copy_file (patch ['path' ], target )
5149- _log .debug ("Copied patch %s to %s" , patch ['path' ], target )
5156+ patches = app .patches
5157+ for ext in app .exts :
5158+ patches += ext .get ('patches' , [])
5159+ for patch in patches :
5160+ if 'path' in patch :
5161+ target = os .path .join (new_log_dir , os .path .basename (patch ['path' ]))
5162+ copy_file (patch ['path' ], target )
5163+ _log .debug ("Copied patch %s to %s" , patch ['path' ], target )
51505164
51515165 if build_option ('read_only_installdir' ) and not app .cfg ['stop' ]:
51525166 # take away user write permissions (again)
@@ -5200,6 +5214,8 @@ def ensure_writable_log_dir(log_dir):
52005214 if not success :
52015215 copy_build_dirs_logs_failed_install (application_log , silent , app , ecdict ['ec' ])
52025216
5217+ run_hook (EASYBLOCK , hooks , post_step_hook = True , args = [app ])
5218+
52035219 del app
52045220
52055221 return (success , application_log , error_msg , exit_code )
0 commit comments