diff --git a/easybuild/easyblocks/l/lammps.py b/easybuild/easyblocks/l/lammps.py index 1ca118d50e..b47dbd4f04 100644 --- a/easybuild/easyblocks/l/lammps.py +++ b/easybuild/easyblocks/l/lammps.py @@ -43,7 +43,7 @@ from easybuild.framework.easyconfig import CUSTOM, MANDATORY from easybuild.tools.build_log import EasyBuildError, print_warning, print_msg from easybuild.tools.config import build_option -from easybuild.tools.filetools import copy_dir, mkdir +from easybuild.tools.filetools import copy_dir, copy_file, mkdir, read_file from easybuild.tools.modules import get_software_root, get_software_version from easybuild.tools.run import run_shell_cmd from easybuild.tools.systemtools import AARCH64, get_cpu_architecture, get_shared_lib_ext @@ -159,11 +159,8 @@ _log = fancylogger.getLogger('easyblocks.lammps') -def translate_lammps_version(version): +def translate_lammps_version(version, path=None): """Translate the LAMMPS version into something that can be used in a comparison""" - items = [x for x in re.split('(\\d+)', version) if x] - if len(items) < 3: - raise ValueError("Version %s does not have (at least) 3 elements" % version) month_map = { "JAN": '01', "FEB": '02', @@ -178,7 +175,26 @@ def translate_lammps_version(version): "NOV": '11', "DEC": '12' } - return '.'.join([items[2], month_map[items[1].upper()], '%02d' % int(items[0])]) + items = [x for x in re.split('(\\d+)', version) if x] + + try: + return '.'.join([items[2], month_map[items[1].upper()], '%02d' % int(items[0])]) + except (IndexError, KeyError): + # avoid failing miserably under --module-only --force + if path and os.path.exists(path) and os.listdir(path): + version_file = os.path.join(path, 'src', 'version.h') + if os.path.exists(version_file): + txt = read_file(os.path.join(path, 'src', 'version.h')) + result = re.search(r'(?<=LAMMPS_VERSION ")\d+ \S+ \d+', txt) + if result: + day, month, year = result.group().split(' ') + else: + raise EasyBuildError(f"Failed to parse LAMMPS version: '{txt}'") + return '.'.join([year, month_map[month.upper()], '%02d' % int(day)]) + else: + raise EasyBuildError(f"Expected to find version file at {version_file}, but it doesn't exist") + else: + raise ValueError("LAMMPS version {version} cannot be translated") class EB_LAMMPS(CMakeMake): @@ -194,28 +210,23 @@ def __init__(self, *args, **kwargs): cuda_toolchain = hasattr(self.toolchain, 'COMPILER_CUDA_FAMILY') self.cuda = cuda_dep or cuda_toolchain - # version 1.3.2 is used in the test suite to check easyblock can be initialised - if self.version != '1.3.2': - self.cur_version = translate_lammps_version(self.version) - else: - self.cur_version = self.version - self.ref_version = translate_lammps_version(ref_version) + self.cur_version = None - self.pkg_prefix = 'PKG_' - if LooseVersion(self.cur_version) >= LooseVersion(self.ref_version): - self.pkg_user_prefix = self.pkg_prefix - else: - self.pkg_user_prefix = self.pkg_prefix + 'USER-' + def update_kokkos_cpu_mapping(self): + """ + Update mapping to Kokkos CPU targets based on LAMMPS version + """ + if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('31Mar2017')): + self.kokkos_cpu_mapping['neoverse_n1'] = 'ARMV81' + self.kokkos_cpu_mapping['neoverse_v1'] = 'ARMV81' - if LooseVersion(self.cur_version) >= LooseVersion(self.ref_version): - self.kokkos_prefix = 'Kokkos' - else: - self.kokkos_prefix = 'KOKKOS' - for cc in KOKKOS_GPU_ARCH_TABLE.keys(): - KOKKOS_GPU_ARCH_TABLE[cc] = KOKKOS_GPU_ARCH_TABLE[cc].lower().title() + if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('21sep2021')): + self.kokkos_cpu_mapping['a64fx'] = 'A64FX' + self.kokkos_cpu_mapping['zen4'] = 'ZEN3' - self.kokkos_cpu_mapping = copy.deepcopy(KOKKOS_CPU_MAPPING) - self.update_kokkos_cpu_mapping() + if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('2Aug2023')): + self.kokkos_cpu_mapping['icelake'] = 'ICX' + self.kokkos_cpu_mapping['sapphirerapids'] = 'SPR' @staticmethod def extra_options(**kwargs): @@ -231,23 +242,36 @@ def extra_options(**kwargs): extra_vars['separate_build_dir'][0] = True return extra_vars - def update_kokkos_cpu_mapping(self): + def prepare_step(self, *args, **kwargs): + """Custom prepare step for LAMMPS.""" + super().prepare_step(*args, **kwargs) - if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('31Mar2017')): - self.kokkos_cpu_mapping['neoverse_n1'] = 'ARMV81' - self.kokkos_cpu_mapping['neoverse_v1'] = 'ARMV81' + # version 1.3.2 is used in the test suite to check easyblock can be initialised + if self.version != '1.3.2': + # take into account that build directory may not be available (in case of --module-only) + if os.path.exists(self.start_dir) and os.listdir(self.start_dir): + self.cur_version = translate_lammps_version(self.version, path=self.start_dir) + else: + self.cur_version = translate_lammps_version(self.version, path=self.installdir) + else: + self.cur_version = self.version + self.ref_version = translate_lammps_version(ref_version) - if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('21sep2021')): - self.kokkos_cpu_mapping['a64fx'] = 'A64FX' - self.kokkos_cpu_mapping['zen4'] = 'ZEN3' + self.pkg_prefix = 'PKG_' + if LooseVersion(self.cur_version) >= LooseVersion(self.ref_version): + self.pkg_user_prefix = self.pkg_prefix + else: + self.pkg_user_prefix = self.pkg_prefix + 'USER-' - if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('2Aug2023')): - self.kokkos_cpu_mapping['icelake'] = 'ICX' - self.kokkos_cpu_mapping['sapphirerapids'] = 'SPR' + if LooseVersion(self.cur_version) >= LooseVersion(self.ref_version): + self.kokkos_prefix = 'Kokkos' + else: + self.kokkos_prefix = 'KOKKOS' + for cc in KOKKOS_GPU_ARCH_TABLE.keys(): + KOKKOS_GPU_ARCH_TABLE[cc] = KOKKOS_GPU_ARCH_TABLE[cc].lower().title() - def prepare_step(self, *args, **kwargs): - """Custom prepare step for LAMMPS.""" - super().prepare_step(*args, **kwargs) + self.kokkos_cpu_mapping = copy.deepcopy(KOKKOS_CPU_MAPPING) + self.update_kokkos_cpu_mapping() # Unset LIBS when using both KOKKOS and CUDA - it will mix lib paths otherwise if self.cfg['kokkos'] and self.cuda: @@ -464,12 +488,20 @@ def configure_step(self, **kwargs): def install_step(self): """Install LAMMPS and examples/potentials.""" super().install_step() + + # Copy LICENSE and version file so these can be used with `--module-only` + version_file = os.path.join(self.start_dir, 'src', 'version.h') + copy_file(version_file, os.path.join(self.installdir, 'src', 'version.h')) + license_file = os.path.join(self.start_dir, 'LICENSE') + copy_file(license_file, os.path.join(self.installdir, 'LICENSE')) + # Copy over the examples so we can repeat the sanity check # (some symlinks may be broken) examples_dir = os.path.join(self.start_dir, 'examples') copy_dir(examples_dir, os.path.join(self.installdir, 'examples'), symlinks=True) potentials_dir = os.path.join(self.start_dir, 'potentials') copy_dir(potentials_dir, os.path.join(self.installdir, 'potentials')) + if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('2Aug2023')): # From ver 2Aug2023: # "make install in a CMake based installation will no longer install @@ -500,6 +532,10 @@ def install_step(self): def sanity_check_step(self, *args, **kwargs): """Run custom sanity checks for LAMMPS files, dirs and commands.""" + # Set cur_version when running --sanity-check-only + if self.cur_version is None: + self.cur_version = translate_lammps_version(self.version, path=self.installdir) + # Output files need to go somewhere (and has to work for --module-only as well) execution_dir = tempfile.mkdtemp() diff --git a/test/easyblocks/easyblock_specific.py b/test/easyblocks/easyblock_specific.py index f3cabe926d..1df35a02a3 100644 --- a/test/easyblocks/easyblock_specific.py +++ b/test/easyblocks/easyblock_specific.py @@ -40,6 +40,7 @@ import easybuild.tools.options as eboptions import easybuild.easyblocks.generic.pythonpackage as pythonpackage +import easybuild.easyblocks.l.lammps as lammps import easybuild.easyblocks.p.python as python from easybuild.base.testing import TestCase from easybuild.easyblocks.generic.cmakemake import det_cmake_version @@ -463,6 +464,28 @@ def test_symlink_dist_site_packages(self): self.assertTrue(os.path.isdir(lib64_site_path)) self.assertFalse(os.path.islink(lib64_site_path)) + def test_translate_lammps_version(self): + """Test translate_lammps_version function from LAMMPS easyblock""" + lammps_versions = { + '23Jun2022': '2022.06.23', + '2Aug2023_update2': '2023.08.02', + '29Aug2024': '2024.08.29', + '29Aug2024_update2': '2024.08.29', + '28Oct2024': '2024.10.28', + } + for key in lammps_versions: + self.assertEqual(lammps.translate_lammps_version(key), lammps_versions[key]) + + version_file = os.path.join(self.tmpdir, 'src', 'version.h') + version_txt = '\n'.join([ + '#define LAMMPS_VERSION "2 Apr 2025"', + '#define LAMMPS_UPDATE "Development"', + ]) + write_file(version_file, version_txt) + + self.assertEqual(lammps.translate_lammps_version('d3adb33f', path=self.tmpdir), '2025.04.02') + self.assertEqual(lammps.translate_lammps_version('devel', path=self.tmpdir), '2025.04.02') + def suite(): """Return all easyblock-specific tests."""