@@ -46,6 +46,57 @@ class SpackSoftwareEnvResult:
4646 path_setup : str
4747
4848
49+ def get_effective_spack_config (* , ctx : DeployContext ) -> dict :
50+ """Return deploy config merged with runtime Spack overrides."""
51+
52+ spack_cfg = ctx .config .get ('spack' , {})
53+ if not isinstance (spack_cfg , dict ):
54+ spack_cfg = {}
55+
56+ merged = dict (spack_cfg )
57+
58+ software_cfg = merged .get ('software' , {})
59+ if software_cfg is None :
60+ software_cfg = {}
61+ if isinstance (software_cfg , dict ):
62+ merged ['software' ] = dict (software_cfg )
63+
64+ runtime_spack = ctx .runtime .get ('spack' , {})
65+ if not isinstance (runtime_spack , dict ):
66+ return merged
67+
68+ for key , value in runtime_spack .items ():
69+ if key == 'software' and isinstance (value , dict ):
70+ base = merged .get ('software' , {})
71+ if not isinstance (base , dict ):
72+ base = {}
73+ merged ['software' ] = {** base , ** value }
74+ else :
75+ merged [key ] = value
76+
77+ return merged
78+
79+
80+ def spack_disabled_for_run (* , ctx : DeployContext ) -> bool :
81+ """Return True when the CLI explicitly disables all Spack use."""
82+
83+ return bool (getattr (ctx .args , 'no_spack' , False ))
84+
85+
86+ def spack_should_deploy_for_run (
87+ * , ctx : DeployContext , spack_cfg : dict
88+ ) -> bool :
89+ """Return True when Spack environments should be deployed for this run."""
90+
91+ if spack_disabled_for_run (ctx = ctx ):
92+ return False
93+
94+ if bool (getattr (ctx .args , 'deploy_spack' , False )):
95+ return True
96+
97+ return bool (spack_cfg .get ('deploy' ))
98+
99+
49100def deploy_spack_software_env (
50101 * ,
51102 ctx : DeployContext ,
@@ -62,7 +113,7 @@ def deploy_spack_software_env(
62113 No CLI flags control this environment.
63114 """
64115
65- spack_cfg = ctx . config . get ( 'spack' , {} )
116+ spack_cfg = get_effective_spack_config ( ctx = ctx )
66117 if not isinstance (spack_cfg , dict ):
67118 return None
68119
@@ -72,9 +123,7 @@ def deploy_spack_software_env(
72123 if not isinstance (software_cfg , dict ):
73124 raise ValueError ('spack.software must be a mapping if provided' )
74125
75- deploy_spack = bool (spack_cfg .get ('deploy' )) or bool (
76- getattr (ctx .args , 'deploy_spack' , False )
77- )
126+ deploy_spack = spack_should_deploy_for_run (ctx = ctx , spack_cfg = spack_cfg )
78127
79128 software_supported = bool (software_cfg .get ('supported' ))
80129
@@ -210,7 +259,7 @@ def deploy_spack_envs(
210259 A list of SpackDeployResult, one per (compiler, mpi)
211260 """
212261
213- spack_cfg = ctx . config . get ( 'spack' , {} )
262+ spack_cfg = get_effective_spack_config ( ctx = ctx )
214263 if not isinstance (spack_cfg , dict ):
215264 return []
216265
@@ -220,9 +269,7 @@ def deploy_spack_envs(
220269 if not isinstance (software_cfg , dict ):
221270 raise ValueError ('spack.software must be a mapping if provided' )
222271
223- deploy_spack = bool (spack_cfg .get ('deploy' )) or bool (
224- getattr (ctx .args , 'deploy_spack' , False )
225- )
272+ deploy_spack = spack_should_deploy_for_run (ctx = ctx , spack_cfg = spack_cfg )
226273
227274 library_supported = bool (spack_cfg .get ('supported' ))
228275
@@ -364,7 +411,7 @@ def load_existing_spack_envs(
364411) -> list [SpackDeployResult ]:
365412 """Load pre-existing Spack library environments for load scripts."""
366413
367- spack_cfg = ctx . config . get ( 'spack' , {} )
414+ spack_cfg = get_effective_spack_config ( ctx = ctx )
368415 if not isinstance (spack_cfg , dict ):
369416 return []
370417
@@ -374,15 +421,17 @@ def load_existing_spack_envs(
374421
375422 if not toolchain_pairs :
376423 raise ValueError (
377- 'Spack library environments are supported but no toolchain pairs '
378- 'were resolved. Provide toolchain.compiler/toolchain.mpi or '
379- '--compiler/--mpi.'
424+ 'Spack library environments are enabled for this run but no '
425+ 'toolchain pairs were resolved. Provide toolchain.compiler/'
426+ 'toolchain.mpi or --compiler/--mpi, or disable Spack for this '
427+ 'run with --no-spack or a pre_spack hook.'
380428 )
381429
382430 spack_path = _resolve_spack_path (
383431 ctx = ctx ,
384432 spack_cfg = spack_cfg ,
385- reason = 'support is enabled (load scripts require existing envs)' ,
433+ reason = 'support is enabled for this run (load scripts will reuse '
434+ 'existing envs)' ,
386435 )
387436
388437 e3sm_hdf5_netcdf = _get_machine_bool (
@@ -446,7 +495,7 @@ def load_existing_spack_software_env(
446495) -> SpackSoftwareEnvResult | None :
447496 """Load a pre-existing Spack software environment for load scripts."""
448497
449- spack_cfg = ctx . config . get ( 'spack' , {} )
498+ spack_cfg = get_effective_spack_config ( ctx = ctx )
450499 if not isinstance (spack_cfg , dict ):
451500 return None
452501
@@ -462,13 +511,17 @@ def load_existing_spack_software_env(
462511
463512 if ctx .machine is None :
464513 raise ValueError (
465- 'Spack software environment is supported but machine is not known.'
514+ 'Spack software environment is enabled for this run but machine '
515+ 'is not known. Pass --no-spack or disable '
516+ 'spack.software.supported '
517+ 'in a pre_spack hook for Pixi-only runs.'
466518 )
467519
468520 spack_path = _resolve_spack_path (
469521 ctx = ctx ,
470522 spack_cfg = spack_cfg ,
471- reason = 'support is enabled (load scripts require existing envs)' ,
523+ reason = 'support is enabled for this run (load scripts will reuse '
524+ 'existing envs)' ,
472525 )
473526
474527 compiler , mpi = _resolve_software_toolchain (
@@ -568,7 +621,8 @@ def _resolve_spack_path(
568621 '--spack-path, '
569622 "ctx.runtime['spack']['spack_path']"
570623 ' in a hook (preferred) or set spack.spack_path in '
571- 'deploy/config.yaml.j2'
624+ 'deploy/config.yaml.j2. To bypass Spack entirely for this run, '
625+ 'pass --no-spack.'
572626 )
573627 return os .path .abspath (os .path .expanduser (os .path .expandvars (spack_path )))
574628
0 commit comments