4040from easybuild .easyblocks .generic .pythonpackage import det_pylibdir
4141from easybuild .framework .easyconfig import CUSTOM
4242from easybuild .tools .build_log import EasyBuildError
43+ from easybuild .tools .config import build_option
4344from easybuild .tools .modules import get_software_root , get_software_version
4445from easybuild .tools .run import run_shell_cmd
45- from easybuild .tools .filetools import remove_dir , which
46+ from easybuild .tools .filetools import clean_dir , which
4647from easybuild .tools .systemtools import get_shared_lib_ext
4748
4849
@@ -81,17 +82,26 @@ def __init__(self, *args, **kwargs):
8182 self .with_cuda = False
8283 self .with_mpi = False
8384
85+ if '-pmemd' in self .cfg ['versionsuffix' ]:
86+ self .pmemd = True
87+ else :
88+ self .pmemd = False
89+
8490 def extract_step (self ):
8591 """Extract sources; strip off parent directory during unpack"""
8692 self .cfg .update ('unpack_options' , "--strip-components=1" )
8793 super ().extract_step ()
8894
8995 def patch_step (self , * args , ** kwargs ):
90- """Patch Amber using 'update_amber' tool, prior to applying listed patch files (if any)."""
96+ """Patch Amber using update script, prior to applying listed patch files (if any)."""
97+ if self .pmemd :
98+ update_script = 'update_pmemd'
99+ else :
100+ update_script = 'update_amber'
91101
92- # Use the update_amber script if patchlevels is defined - if not then the easyconfig should apply the patches
102+ # Use update_script if patchlevels is defined - if not then the easyconfig should apply the patches
93103 if self .cfg ['patchlevels' ]:
94- # figure out which Python command to use to run the update_amber script;
104+ # figure out which Python command to use to run the update script;
95105 # by default it uses 'python', but this may not be available (on CentOS 8 for example);
96106 # note that the dependencies are not loaded yet at this point, so we're at the mercy of the OS here...
97107 python_cmd = None
@@ -101,21 +111,25 @@ def patch_step(self, *args, **kwargs):
101111 break
102112
103113 if python_cmd is None :
104- raise EasyBuildError ("No suitable Python command found to run update_amber script !" )
114+ raise EasyBuildError (f "No suitable Python command found to run { update_script } !" )
105115
106116 if self .cfg ['patchlevels' ] == "latest" :
107- cmd = "%s ./update_amber --update" % python_cmd
117+ cmd = f" { python_cmd } ./{ update_script } --update"
108118 # Run as many times as specified. It is the responsibility
109119 # of the easyconfig author to get this right, especially if
110120 # he or she selects "latest". (Note: "latest" is not
111121 # recommended for this reason and others.)
112122 for _ in range (self .cfg ['patchruns' ]):
113123 run_shell_cmd (cmd )
114124 else :
115- for (tree , patch_level ) in zip (['AmberTools' , 'Amber' ], self .cfg ['patchlevels' ]):
125+ if isinstance (self .cfg ['patchlevels' ], int ):
126+ patch_levels = [(self .name , self .cfg ['patchlevels' ])]
127+ else :
128+ patch_levels = zip (['AmberTools' , 'Amber' ], self .cfg ['patchlevels' ])
129+ for (tree , patch_level ) in patch_levels :
116130 if patch_level == 0 :
117131 continue
118- cmd = "%s ./update_amber --update-to %s/%s" % ( python_cmd , tree , patch_level )
132+ cmd = f" { python_cmd } ./{ update_script } --update-to { tree } / { patch_level } "
119133 # Run as many times as specified. It is the responsibility
120134 # of the easyconfig author to get this right.
121135 for _ in range (self .cfg ['patchruns' ]):
@@ -131,8 +145,8 @@ def configure_step(self):
131145 return
132146
133147 # CMake will search a previous install directory for Amber-compiled libs. We will therefore
134- # manually remove the install directory prior to configuration.
135- remove_dir (self .installdir )
148+ # manually clean the install directory prior to configuration.
149+ clean_dir (self .installdir )
136150
137151 external_libs_list = []
138152
@@ -161,7 +175,10 @@ def configure_step(self):
161175 self .cfg .update ('configopts' , '-DCUDA=TRUE' )
162176 if get_software_root ('NCCL' ):
163177 self .cfg .update ('configopts' , '-DNCCL=TRUE' )
164- external_libs_list .append ('nccl' )
178+ if self .pmemd :
179+ self .cfg .update ('configopts' , '-Dnccl_ENABLED=TRUE' )
180+ else :
181+ external_libs_list .append ('nccl' )
165182
166183 pythonroot = get_software_root ('Python' )
167184 if pythonroot :
@@ -184,18 +201,20 @@ def configure_step(self):
184201 self .cfg .update ('configopts' , f'-DPYTHON_LIBRARY={ python_library } ' )
185202 self .cfg .update ('configopts' , f'-DPYTHON_INCLUDE_DIR={ python_incdir } ' )
186203
187- if get_software_root ('FFTW' ):
188- external_libs_list .append ('fftw' )
189204 if get_software_root ('netCDF' ):
190205 external_libs_list .append ('netcdf' )
191206 if get_software_root ('netCDF-Fortran' ):
192207 external_libs_list .append ('netcdf-fortran' )
193208 if get_software_root ('zlib' ):
194209 external_libs_list .append ('zlib' )
195- if get_software_root ('Boost' ):
196- external_libs_list .append ('boost' )
197- if get_software_root ('PnetCDF' ):
198- external_libs_list .append ('pnetcdf' )
210+
211+ if not self .pmemd :
212+ if get_software_root ('FFTW' ):
213+ external_libs_list .append ('fftw' )
214+ if get_software_root ('Boost' ):
215+ external_libs_list .append ('boost' )
216+ if get_software_root ('PnetCDF' ):
217+ external_libs_list .append ('pnetcdf' )
199218
200219 # Force libs for available deps (see cmake/3rdPartyTools.cmake in Amber source for list of 3rd party libs)
201220 # This provides an extra layer of checking but should already be handled by TRUST_SYSTEM_LIBS=TRUE
@@ -217,6 +236,9 @@ def configure_step(self):
217236
218237 self .cfg .update ('configopts' , '-DCOMPILER=AUTO' )
219238
239+ if self .pmemd :
240+ self .cfg .update ('configopts' , '-DPMEMD_ONLY=TRUE' )
241+
220242 # configure using cmake
221243 super ().configure_step ()
222244
@@ -361,34 +383,47 @@ def install_step(self):
361383 'source %s/amber.sh && cd %s' % (self .installdir , testdir )
362384 ])
363385
386+ if build_option ('ignore_test_failure' ):
387+ fail_on_error = False
388+ else :
389+ fail_on_error = True
390+
364391 # serial tests
392+ if self .pmemd :
393+ pre = 'ln -sr config.h ../ && '
394+ else :
395+ pre = ''
365396 if LooseVersion (self .version ) >= LooseVersion ('24' ):
366- run_shell_cmd ("%s && make test" % pretestcommands )
397+ run_shell_cmd (f" { pre } { pretestcommands } && make test", fail_on_error = fail_on_error )
367398 else :
368- run_shell_cmd ("%s && make test.serial" % pretestcommands )
399+ run_shell_cmd (f" { pre } { pretestcommands } && make test.serial", fail_on_error = fail_on_error )
369400 if self .with_cuda :
370- res = run_shell_cmd (f"{ pretestcommands } && make { testname_cs } " )
401+ res = run_shell_cmd (f"{ pretestcommands } && make { testname_cs } " , fail_on_error = fail_on_error )
371402 if res .exit_code > 0 :
372403 self .log .warning ("Check the output of the Amber cuda tests for possible failures" )
373404
374405 # parallel tests
375406 if self .with_mpi :
376407 # Hard-code parallel tests to use 4 threads
377408 env .setvar ("DO_PARALLEL" , self .toolchain .mpi_cmd_for ('' , 4 ))
378- res = run_shell_cmd (f"{ pretestcommands } && make test.parallel" )
409+ res = run_shell_cmd (f"{ pretestcommands } && make test.parallel" , fail_on_error = fail_on_error )
379410 if res .exit_code > 0 :
380411 self .log .warning ("Check the output of the Amber parallel tests for possible failures" )
381412
382413 if self .with_mpi and self .with_cuda :
383414 # Hard-code CUDA parallel tests to use 2 threads
384415 env .setvar ("DO_PARALLEL" , self .toolchain .mpi_cmd_for ('' , 2 ))
385- res = run_shell_cmd (f"{ pretestcommands } && make { testname_cp } " )
416+ res = run_shell_cmd (f"{ pretestcommands } && make { testname_cp } " , fail_on_error = fail_on_error )
386417 if res .exit_code > 0 :
387418 self .log .warning ("Check the output of the Amber cuda_parallel tests for possible failures" )
388419
389420 def sanity_check_step (self ):
390421 """Custom sanity check for Amber."""
391- binaries = ['sander' , 'tleap' ]
422+ binaries = []
423+
424+ if not self .pmemd :
425+ binaries .extend (['sander' , 'tleap' ])
426+
392427 if self .name == 'Amber' :
393428 binaries .append ('pmemd' )
394429 if self .with_cuda :
@@ -398,15 +433,17 @@ def sanity_check_step(self):
398433 binaries .append ('pmemd.cuda.MPI' )
399434 else :
400435 binaries .append ('pmemd.cuda_DPFP.MPI' )
436+
401437 if self .name == 'AmberTools' :
402438 binaries .append ('gem.pmemd' )
403439
404440 if self .with_mpi :
405- binaries .extend (['sander.MPI' ])
406441 if self .name == 'Amber' :
407442 binaries .append ('pmemd.MPI' )
408443 if self .name == 'AmberTools' :
409444 binaries .append ('gem.pmemd.MPI' )
445+ if not self .pmemd :
446+ binaries .append ('sander.MPI' )
410447
411448 custom_paths = {
412449 'files' : [os .path .join (self .installdir , 'bin' , binary ) for binary in binaries ],
0 commit comments