diff --git a/easyblocks/s/siesta.py b/easyblocks/s/siesta.py new file mode 100644 index 0000000000000000000000000000000000000000..435e2f7afc2453c372be3bd3b2326c28b950a824 --- /dev/null +++ b/easyblocks/s/siesta.py @@ -0,0 +1,463 @@ +## +# Copyright 2009-2021 Ghent University +# +# This file is part of EasyBuild, +# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), +# with support of Ghent University (http://ugent.be/hpc), +# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be), +# Flemish Research Foundation (FWO) (http://www.fwo.be/en) +# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). +# +# https://github.com/easybuilders/easybuild +# +# EasyBuild is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation v2. +# +# EasyBuild is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>. +## +""" +EasyBuild support for building and installing Siesta, implemented as an easyblock + +@author: Miguel Dias Costa (National University of Singapore) +@author: Ake Sandgren (Umea University) +""" +import os +import stat + +import easybuild.tools.toolchain as toolchain +from distutils.version import LooseVersion +from easybuild.easyblocks.generic.configuremake import ConfigureMake +from easybuild.framework.easyconfig import CUSTOM +from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.config import build_option +from easybuild.tools.filetools import adjust_permissions, apply_regex_substitutions +from easybuild.tools.filetools import change_dir, copy_dir, copy_file, mkdir +from easybuild.tools.modules import get_software_root, get_software_version +from easybuild.tools.run import run_cmd +from easybuild.tools.systemtools import get_shared_lib_ext + + +class EB_Siesta(ConfigureMake): + """ + Support for building/installing Siesta. + - avoid parallel build for older versions + """ + + @staticmethod + def extra_options(extra_vars=None): + """Define extra options for Siesta""" + extra = { + 'with_transiesta': [True, "Build transiesta", CUSTOM], + 'with_utils': [True, "Build all utils", CUSTOM], + } + return ConfigureMake.extra_options(extra_vars=extra) + + def configure_step(self): + """ + Custom configure and build procedure for Siesta. + - There are two main builds to do, siesta and transiesta + - In addition there are multiple support tools to build + """ + + start_dir = self.cfg['start_dir'] + obj_dir = os.path.join(start_dir, 'Obj') + arch_make = os.path.join(obj_dir, 'arch.make') + bindir = os.path.join(start_dir, 'bin') + + loose_ver = LooseVersion(self.version) + + par = '' + if loose_ver >= LooseVersion('4.1'): + par = '-j %s' % self.cfg['parallel'] + + # enable OpenMP support if desired + env_var_suff = '' + if self.toolchain.options.get('openmp', None): + env_var_suff = '_MT' + + scalapack = os.environ['LIBSCALAPACK' + env_var_suff] + blacs = os.environ['LIBSCALAPACK' + env_var_suff] + lapack = os.environ['LIBLAPACK' + env_var_suff] + blas = os.environ['LIBBLAS' + env_var_suff] + if get_software_root('imkl') or get_software_root('FFTW'): + fftw = os.environ['LIBFFT' + env_var_suff] + else: + fftw = None + + regex_newlines = [] + regex_subs = [ + ('dc_lapack.a', ''), + (r'^NETCDF_INTERFACE\s*=.*$', ''), + ('libsiestaBLAS.a', ''), + ('libsiestaLAPACK.a', ''), + # Needed here to allow 4.1-b1 to be built with openmp + (r"^(LDFLAGS\s*=).*$", r"\1 %s %s" % (os.environ['FCFLAGS'], os.environ['LDFLAGS'])), + ] + + regex_subs_gfortran = [ + (r"^(FCFLAGS_free_f90\s*=.*)$", r"\1 -ffree-line-length-none"), + (r"^(FPPFLAGS_free_F90\s*=.*)$", r"\1 -ffree-line-length-none"), + ] + + netcdff_loc = get_software_root('netCDF-Fortran') + if netcdff_loc: + # Needed for gfortran at least + regex_newlines.append((r"^(ARFLAGS_EXTRA\s*=.*)$", r"\1\nNETCDF_INCFLAGS = -I%s/include" % netcdff_loc)) + + if fftw: + fft_inc, fft_lib = os.environ['FFT_INC_DIR'], os.environ['FFT_LIB_DIR'] + fppflags = r"\1\nFFTW_INCFLAGS = -I%s\nFFTW_LIBS = -L%s %s" % (fft_inc, fft_lib, fftw) + regex_newlines.append((r'(FPPFLAGS\s*:?=.*)$', fppflags)) + + # Make a temp installdir during the build of the various parts + mkdir(bindir) + + # change to actual build dir + change_dir(obj_dir) + + # Populate start_dir with makefiles + run_cmd(os.path.join(start_dir, 'Src', 'obj_setup.sh'), log_all=True, simple=True, log_output=True) + + if loose_ver < LooseVersion('4.1-b2'): + # MPI? + if self.toolchain.options.get('usempi', None): + self.cfg.update('configopts', '--enable-mpi') + + # BLAS and LAPACK + self.cfg.update('configopts', '--with-blas="%s"' % blas) + self.cfg.update('configopts', '--with-lapack="%s"' % lapack) + + # ScaLAPACK (and BLACS) + self.cfg.update('configopts', '--with-scalapack="%s"' % scalapack) + self.cfg.update('configopts', '--with-blacs="%s"' % blacs) + + # NetCDF-Fortran + if netcdff_loc: + self.cfg.update('configopts', '--with-netcdf=-lnetcdff') + + # Configure is run in obj_dir, configure script is in ../Src + super(EB_Siesta, self).configure_step(cmd_prefix='../Src/') + + if loose_ver > LooseVersion('4.0'): + regex_subs_Makefile = [ + (r'CFLAGS\)-c', r'CFLAGS) -c'), + ] + apply_regex_substitutions('Makefile', regex_subs_Makefile) + + if self.toolchain.comp_family() in [toolchain.GCC]: + apply_regex_substitutions(arch_make, regex_subs_gfortran) + + else: # there's no configure on newer versions + + if self.toolchain.comp_family() in [toolchain.INTELCOMP]: + copy_file(os.path.join(obj_dir, 'intel.make'), arch_make) + elif self.toolchain.comp_family() in [toolchain.GCC]: + copy_file(os.path.join(obj_dir, 'gfortran.make'), arch_make) + else: + raise EasyBuildError("There is currently no support for compiler: %s", self.toolchain.comp_family()) + + regex_subs.append((r"^(FPPFLAGS\s*:?=.*)$", r"\1 -DF2003")) + + if self.toolchain.options.get('usempi', None): + regex_subs.extend([ + (r"^(CC\s*=\s*).*$", r"\1%s" % os.environ['MPICC']), + (r"^(FC\s*=\s*).*$", r"\1%s" % os.environ['MPIF90']), + (r"^(FPPFLAGS\s*:?=.*)$", r"\1 -DMPI"), + ]) + regex_newlines.append((r"^(FPPFLAGS\s*:?=.*)$", r"\1\nMPI_INTERFACE = libmpi_f90.a\nMPI_INCLUDE = .")) + complibs = scalapack + else: + complibs = lapack + + regex_subs.extend([ + (r"^(LIBS\s*=).*$", r"\1 %s" % complibs), + # Needed for a couple of the utils + (r"^(FFLAGS\s*=\s*).*$", r"\1 -fPIC %s" % os.environ['FCFLAGS']), + ]) + regex_newlines.append((r"^(COMP_LIBS\s*=.*)$", r"\1\nWXML = libwxml.a")) + + if self.toolchain.comp_family() in [toolchain.GCC]: + regex_subs.extend(regex_subs_gfortran) + + if netcdff_loc: + regex_subs.extend([ + (r"^(LIBS\s*=.*)$", r"\1 $(NETCDF_LIBS)"), + (r"^(FPPFLAGS\s*:?=.*)$", r"\1 -DCDF -DNCDF -DNCDF_4 -DNCDF_PARALLEL $(NETCDF_INCLUDE)"), + (r"^(COMP_LIBS\s*=.*)$", r"\1 libncdf.a libfdict.a"), + ]) + netcdf_lib_and_inc = "NETCDF_LIBS = -lnetcdff\nNETCDF_INCLUDE = -I%s/include" % netcdff_loc + netcdf_lib_and_inc += "\nINCFLAGS = $(NETCDF_INCLUDE)" + regex_newlines.append((r"^(COMP_LIBS\s*=.*)$", r"\1\n%s" % netcdf_lib_and_inc)) + + xmlf90 = get_software_root('xmlf90') + if xmlf90: + regex_subs.append((r"^(XMLF90_ROOT\s*=).*$", r"\1%s" % xmlf90)) + + libpsml = get_software_root('libPSML') + if libpsml: + regex_subs.append((r"^(PSML_ROOT\s*=).*$.*", r"\1%s" % libpsml)) + + libgridxc = get_software_root('libGridXC') + if libgridxc: + regex_subs.append((r"^(GRIDXC_ROOT\s*=).*$", r"\1%s" % libgridxc)) + + libxc = get_software_root('libxc') + if libxc: + regex_subs.append((r"^#(LIBXC_ROOT\s*=).*$", r"\1 %s" % libxc)) + + elpa = get_software_root('ELPA') + if elpa: + elpa_ver = get_software_version('ELPA') + regex_subs.extend([ + (r"^(FPPFLAGS\s*:?=.*)$", r"\1 -DSIESTA__ELPA"), + (r"^(FPPFLAGS\s*:?=.*)$", r"\1 -I%s/include/elpa-%s/modules" % (elpa, elpa_ver)), + (r"^(LIBS\s*=.*)$", r"\1 -L%s/lib -lelpa" % elpa), + ]) + + elsi = get_software_root('ELSI') + if elsi: + if not os.path.isfile(os.path.join(elsi, 'lib', 'libelsi.%s' % get_shared_lib_ext())): + raise EasyBuildError("This easyblock requires ELSI shared libraries instead of static") + + regex_subs.extend([ + (r"^(FPPFLAGS\s*:?=.*)$", r"\1 -DSIESTA__ELSI"), + (r"^(FPPFLAGS\s*:?=.*)$", r"\1 -I%s/include" % elsi), + (r"^(LIBS\s*=.*)$", r"\1 $(FFTW_LIBS) -L%s/lib -lelsi" % elsi), + ]) + + metis = get_software_root('METIS') + if metis: + regex_subs.extend([ + (r"^(FPPFLAGS\s*:?=.*)$", r"\1 -DSIESTA__METIS"), + (r"^(LIBS\s*=.*)$", r"\1 -L%s/lib -lmetis" % metis), + ]) + + apply_regex_substitutions(arch_make, regex_subs) + + # individually apply substitutions that add lines + for regex_nl in regex_newlines: + apply_regex_substitutions(arch_make, [regex_nl]) + + run_cmd('make %s' % par, log_all=True, simple=True, log_output=True) + + # Put binary in temporary install dir + copy_file(os.path.join(obj_dir, 'siesta'), bindir) + + if self.cfg['with_utils']: + # Make the utils + change_dir(os.path.join(start_dir, 'Util')) + + if loose_ver >= LooseVersion('4'): + # clean_all.sh might be missing executable bit... + adjust_permissions('./clean_all.sh', stat.S_IXUSR, recursive=False, relative=True) + run_cmd('./clean_all.sh', log_all=True, simple=True, log_output=True) + + if loose_ver >= LooseVersion('4.1'): + regex_subs_TS = [ + (r"^default:.*$", r""), + (r"^EXE\s*=.*$", r""), + (r"^(include\s*..ARCH_MAKE.*)$", r"EXE=tshs2tshs\ndefault: $(EXE)\n\1"), + (r"^(INCFLAGS.*)$", r"\1 -I%s" % obj_dir), + ] + + makefile = os.path.join(start_dir, 'Util', 'TS', 'tshs2tshs', 'Makefile') + apply_regex_substitutions(makefile, regex_subs_TS) + + if loose_ver >= LooseVersion('4'): + # SUFFIX rules in wrong place + regex_subs_suffix = [ + (r'^(\.SUFFIXES:.*)$', r''), + (r'^(include\s*\$\(ARCH_MAKE\).*)$', r'\1\n.SUFFIXES:\n.SUFFIXES: .c .f .F .o .a .f90 .F90'), + ] + makefile = os.path.join(start_dir, 'Util', 'Sockets', 'Makefile') + apply_regex_substitutions(makefile, regex_subs_suffix) + makefile = os.path.join(start_dir, 'Util', 'SiestaSubroutine', 'SimpleTest', 'Src', 'Makefile') + apply_regex_substitutions(makefile, regex_subs_suffix) + + regex_subs_UtilLDFLAGS = [ + (r'(\$\(FC\)\s*-o\s)', r'$(FC) %s %s -o ' % (os.environ['FCFLAGS'], os.environ['LDFLAGS'])), + ] + makefile = os.path.join(start_dir, 'Util', 'Optimizer', 'Makefile') + apply_regex_substitutions(makefile, regex_subs_UtilLDFLAGS) + if loose_ver >= LooseVersion('4'): + makefile = os.path.join(start_dir, 'Util', 'JobList', 'Src', 'Makefile') + apply_regex_substitutions(makefile, regex_subs_UtilLDFLAGS) + + # remove clean at the end of default target + # And yes, they are re-introducing this bug. + is_ver40_to_401 = loose_ver >= LooseVersion('4.0') and loose_ver < LooseVersion('4.0.2') + if (is_ver40_to_401 or loose_ver == LooseVersion('4.1-b3')): + makefile = os.path.join(start_dir, 'Util', 'SiestaSubroutine', 'SimpleTest', 'Src', 'Makefile') + apply_regex_substitutions(makefile, [(r"simple_mpi_parallel clean", r"simple_mpi_parallel")]) + makefile = os.path.join(start_dir, 'Util', 'SiestaSubroutine', 'ProtoNEB', 'Src', 'Makefile') + apply_regex_substitutions(makefile, [(r"protoNEB clean", r"protoNEB")]) + + # build_all.sh might be missing executable bit... + adjust_permissions('./build_all.sh', stat.S_IXUSR, recursive=False, relative=True) + run_cmd('./build_all.sh', log_all=True, simple=True, log_output=True) + + # Now move all the built utils to the temp installdir + expected_utils = [ + 'CMLComp/ccViz', + 'Contrib/APostnikov/eig2bxsf', 'Contrib/APostnikov/fmpdos', + 'Contrib/APostnikov/md2axsf', 'Contrib/APostnikov/rho2xsf', + 'Contrib/APostnikov/vib2xsf', 'Contrib/APostnikov/xv2xsf', + 'COOP/fat', 'COOP/mprop', + 'Denchar/Src/denchar', + 'DensityMatrix/cdf2dm', 'DensityMatrix/dm2cdf', + 'Eig2DOS/Eig2DOS', + 'Gen-basis/ionplot.sh', + 'Grid/cdf2grid', 'Grid/cdf2xsf', 'Grid/cdf_laplacian', + 'Grid/g2c_ng', 'Grid/grid2cdf', 'Grid/grid2cube', + 'Grid/grid2val', 'Grid/grid_rotate', + 'Helpers/get_chem_labels', + 'HSX/hs2hsx', 'HSX/hsx2hs', + 'JobList/Src/countJobs', 'JobList/Src/getResults', + 'JobList/Src/horizontal', 'JobList/Src/runJobs', + 'Macroave/Src/macroave', + 'ON/lwf2cdf', + 'Optimizer/simplex', 'Optimizer/swarm', + 'pdosxml/pdosxml', + 'Projections/orbmol_proj', + 'SiestaSubroutine/FmixMD/Src/driver', + 'SiestaSubroutine/FmixMD/Src/para', + 'SiestaSubroutine/FmixMD/Src/simple', + 'STM/ol-stm/Src/stm', 'STM/simple-stm/plstm', + 'Vibra/Src/fcbuild', 'Vibra/Src/vibra', + 'WFS/readwf', 'WFS/readwfx', 'WFS/wfs2wfsx', + 'WFS/wfsnc2wfsx', 'WFS/wfsx2wfs', + ] + + # skip broken utils in 4.1-MaX-1.0 release, hopefully will be fixed later + if self.version != '4.1-MaX-1.0': + expected_utils.extend([ + 'VCA/fractional', 'VCA/mixps', + ]) + + if loose_ver >= LooseVersion('3.2'): + expected_utils.extend([ + 'Bands/eigfat2plot', + ]) + + if loose_ver >= LooseVersion('4.0'): + if self.version != '4.1-MaX-1.0': + expected_utils.extend([ + 'SiestaSubroutine/ProtoNEB/Src/protoNEB', + 'SiestaSubroutine/SimpleTest/Src/simple_pipes_parallel', + 'SiestaSubroutine/SimpleTest/Src/simple_pipes_serial', + 'SiestaSubroutine/SimpleTest/Src/simple_sockets_parallel', + 'SiestaSubroutine/SimpleTest/Src/simple_sockets_serial', + ]) + expected_utils.extend([ + 'Sockets/f2fmaster', 'Sockets/f2fslave', + ]) + if self.toolchain.options.get('usempi', None): + if self.version != '4.1-MaX-1.0': + expected_utils.extend([ + 'SiestaSubroutine/SimpleTest/Src/simple_mpi_parallel', + 'SiestaSubroutine/SimpleTest/Src/simple_mpi_serial', + ]) + + if loose_ver < LooseVersion('4.1'): + expected_utils.append('WFS/info_wfsx') + if loose_ver >= LooseVersion('4.0'): + expected_utils.extend([ + 'COOP/dm_creator', + 'TBTrans_rep/tbtrans', + ]) + else: + expected_utils.extend([ + 'TBTrans/tbtrans', + ]) + + if loose_ver < LooseVersion('4.0.2'): + expected_utils.extend([ + 'Bands/new.gnubands', + ]) + else: + expected_utils.extend([ + 'Bands/gnubands', + ]) + # Need to revisit this when 4.1 is officialy released. + # This is based on b1-b3 releases + if loose_ver < LooseVersion('4.1'): + expected_utils.extend([ + 'Contour/grid1d', 'Contour/grid2d', + 'Optical/optical', 'Optical/optical_input', + 'sies2arc/sies2arc', + ]) + + if loose_ver >= LooseVersion('4.1'): + expected_utils.extend([ + 'DensityMatrix/dmbs2dm', 'DensityMatrix/dmUnblock', + 'Grimme/fdf2grimme', + 'SpPivot/pvtsp', + 'TS/TBtrans/tbtrans', 'TS/tselecs.sh', + 'TS/ts2ts/ts2ts', + ]) + if self.version != '4.1-MaX-1.0': + expected_utils.extend([ + 'TS/tshs2tshs/tshs2tshs', + ]) + + for util in expected_utils: + copy_file(os.path.join(start_dir, 'Util', util), bindir) + + if self.cfg['with_transiesta']: + # Build transiesta + change_dir(obj_dir) + + ts_clean_target = 'clean' + if loose_ver >= LooseVersion('4.1-b4'): + ts_clean_target += '-transiesta' + + run_cmd('make %s' % ts_clean_target, log_all=True, simple=True, log_output=True) + run_cmd('make %s transiesta' % par, log_all=True, simple=True, log_output=True) + + copy_file(os.path.join(obj_dir, 'transiesta'), bindir) + + def build_step(self): + """No build step for Siesta.""" + pass + + def test_step(self): + """Custom test step for Siesta.""" + change_dir(os.path.join(self.cfg['start_dir'], 'Obj', 'Tests')) + super(EB_Siesta, self).test_step() + + def install_step(self): + """Custom install procedure for Siesta: copy binaries.""" + bindir = os.path.join(self.installdir, 'bin') + copy_dir(os.path.join(self.cfg['start_dir'], 'bin'), bindir) + + def sanity_check_step(self): + """Custom sanity check for Siesta.""" + + bins = ['bin/siesta'] + + if self.cfg['with_transiesta']: + bins.append('bin/transiesta') + + if self.cfg['with_utils']: + bins.append('bin/denchar') + + custom_paths = { + 'files': bins, + 'dirs': [], + } + custom_commands = [] + if self.toolchain.options.get('usempi', None) and build_option('mpi_tests'): + # make sure Siesta was indeed built with support for running in parallel + # The "cd to builddir" is required to not contaminate the install dir with cruft from running siesta + mpi_test_cmd = "cd %s && " % self.builddir + mpi_test_cmd = mpi_test_cmd + "echo 'SystemName test' | mpirun -np 2 siesta 2>/dev/null | grep PARALLEL" + custom_commands.append(mpi_test_cmd) + + super(EB_Siesta, self).sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands)