@@ -92,6 +92,8 @@ def __init__(self, *args, **kwargs):
9292 # (like adding component sources to top-level sources easyconfig parameter)
9393 self .cfg = self .cfg .copy ()
9494
95+ self .comp_to_src_idxs = []
96+
9597 # disable templating to avoid premature resolving of template values
9698 # Note that self.cfg.update also resolves templates!
9799 with self .cfg .disable_templating ():
@@ -166,6 +168,11 @@ def __init__(self, *args, **kwargs):
166168 for key in comp_specs :
167169 comp_cfg [key ] = comp_specs [key ]
168170
171+ # Combine all component sources into the top-level sources parameter
172+ # This allows reusing top-level source_urls and unpacking them all in the extract_step
173+
174+ # Keep track of sources for each component to restore them: (Component instance, start idx [in self.src], end idx)
175+ old_num_srcs = len (self .cfg .get ('sources' , resolve = False ))
169176 # Don't require that all template values can be resolved at this point but still resolve them.
170177 # This is important to ensure that template values like %(name)s and %(version)s
171178 # are correctly resolved with the component name/version before values are copied over to self.cfg
@@ -195,19 +202,15 @@ def __init__(self, *args, **kwargs):
195202 comp_checksums = comp_cfg ['checksums' ]
196203 if comp_checksums :
197204 src_cnt = len (comp_sources )
198-
199205 # add per-component checksums for sources to list of checksums
200206 self .cfg .update ('checksums' , comp_checksums [:src_cnt ])
201207
202- # add per-component checksums for patches to list of checksums for patches
203- checksums_patches .extend (comp_checksums [src_cnt :])
204-
205- with comp_cfg .allow_unresolved_templates ():
206- comp_patches = comp_cfg ['patches' ]
207- if comp_patches :
208- self .cfg .update ('patches' , comp_patches )
209-
210208 self .comp_instances .append ((comp_cfg , comp_cfg .easyblock (comp_cfg , logfile = self .logfile )))
209+ new_num_srcs = len (self .cfg .get ('sources' , resolve = False ))
210+ self .comp_to_src_idxs .append ((self .comp_instances [- 1 ], old_num_srcs , new_num_srcs ))
211+ # check if sanity checks are enabled for the component
212+ if self .cfg ['sanity_check_all_components' ] or comp_cfg ['name' ] in self .cfg ['sanity_check_components' ]:
213+ self .comp_cfgs_sanity_check .append (self .comp_instances [- 1 ])
211214
212215 self .cfg .update ('checksums' , checksums_patches )
213216
@@ -229,6 +232,26 @@ def check_checksums(self):
229232
230233 return checksum_issues
231234
235+ def fetch_step (self ):
236+ """Fetch sources of all extensions"""
237+ super ().fetch_step ()
238+ # Init src attribute as usually done by fetch_step
239+ for (_ , comp ), start_idx , end_idx in self .comp_to_src_idxs :
240+ comp .src = self .src [start_idx :end_idx ]
241+ # need to run fetch_patches to ensure per-component patches are gathered
242+ comp .fetch_patches ()
243+
244+ def prepare_step (self , * args , ** kwargs ):
245+ """Init required properties for components"""
246+ super ().prepare_step (* args , ** kwargs )
247+ for _ , comp in self .comp_instances :
248+ # correct build/install dirs
249+ comp .builddir = self .builddir
250+ comp .install_subdir , comp .installdir = self .install_subdir , self .installdir
251+
252+ # make sure we can build in parallel
253+ comp .set_parallel ()
254+
232255 def patch_step (self ):
233256 """Patch step must be a no-op for bundle, since there are no top-level sources/patches."""
234257 pass
@@ -260,47 +283,15 @@ def install_step(self):
260283 (cfg ['name' ], cfg ['version' ], idx + 1 , comp_cnt ))
261284 self .log .info ("Installing component %s v%s using easyblock %s" , cfg ['name' ], cfg ['version' ], cfg .easyblock )
262285
263- # correct build/install dirs
264- comp .builddir = self .builddir
265- comp .install_subdir , comp .installdir = self .install_subdir , self .installdir
266-
267- # make sure we can build in parallel
268- comp .set_parallel ()
269-
270286 # figure out correct start directory
271- comp .guess_start_dir ()
272-
273- # need to run fetch_patches to ensure per-component patches are applied
274- comp .fetch_patches ()
275-
287+ # Compatibility with ECs expecting the previous behavior where src wasn't populated at this point
288+ tmp_src = comp .src
276289 comp .src = []
290+ comp .guess_start_dir ()
291+ comp .src = tmp_src
277292
278- # find matching entries in self.src for this component
279- with comp .cfg .allow_unresolved_templates ():
280- comp_sources = comp .cfg ['sources' ]
281- for source in comp_sources :
282- if isinstance (source , str ):
283- comp_src_fn = source
284- elif isinstance (source , dict ):
285- if 'filename' in source :
286- comp_src_fn = source ['filename' ]
287- else :
288- raise EasyBuildError ("Encountered source file specified as dict without 'filename': %s" , source )
289- else :
290- raise EasyBuildError ("Specification of unknown type for source file: %s" , source )
291-
292- found = False
293- for src in self .src :
294- if src ['name' ] == comp_src_fn :
295- self .log .info ("Found spec for source %s for component %s: %s" , comp_src_fn , comp .name , src )
296- comp .src .append (src )
297- found = True
298- break
299- if not found :
300- raise EasyBuildError ("Failed to find spec for source %s for component %s" , comp_src_fn , comp .name )
301-
302- # location of first unpacked source is used to determine where to apply patch(es)
303- comp .src [- 1 ]['finalpath' ] = comp .cfg ['start_dir' ]
293+ # location of first unpacked source is used to determine where to apply patch(es)
294+ comp .src [0 ]['finalpath' ] = comp .cfg ['start_dir' ]
304295
305296 # check if sanity checks are enabled for the component
306297 if self .cfg ['sanity_check_all_components' ] or comp .cfg ['name' ] in self .cfg ['sanity_check_components' ]:
0 commit comments