Skip to content
Snippets Groups Projects
wien2k.py 26.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • easybuild's avatar
    easybuild committed
    # Copyright 2009-2023 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 WIEN2k, implemented as an easyblock
    
    @author: Stijn De Weirdt (Ghent University)
    @author: Dries Verdegem (Ghent University)
    @author: Kenneth Hoste (Ghent University)
    @author: Pieter De Baets (Ghent University)
    @author: Jens Timmerman (Ghent University)
    @author: Michael Sluydts (Ghent University)
    
    """
    import fileinput
    import os
    import re
    import shutil
    import sys
    import tempfile
    from distutils.version import LooseVersion
    
    import easybuild.tools.environment as env
    import easybuild.tools.toolchain as toolchain
    from easybuild.framework.easyblock import EasyBlock
    from easybuild.framework.easyconfig import CUSTOM
    from easybuild.tools.build_log import EasyBuildError
    
    easybuild's avatar
    easybuild committed
    from easybuild.tools.filetools import apply_regex_substitutions, change_dir, extract_file, mkdir, read_file
    from easybuild.tools.filetools import remove_dir, write_file
    
    from easybuild.tools.modules import get_software_root, get_software_version
    from easybuild.tools.run import run_cmd, run_cmd_qa
    
    
    class EB_WIEN2k(EasyBlock):
        """Support for building/installing WIEN2k."""
    
        def __init__(self, *args, **kwargs):
            """Enable building in install dir."""
            super(EB_WIEN2k, self).__init__(*args, **kwargs)
            self.build_in_installdir = True
    
        @staticmethod
        def extra_options():
            testdata_urls = ["http://www.wien2k.at/reg_user/benchmark/test_case.tar.gz",
                             "http://www.wien2k.at/reg_user/benchmark/mpi-benchmark.tar.gz"]
    
            extra_vars = {
                'runtest': [True, "Run WIEN2k tests", CUSTOM],
                'testdata': [testdata_urls, "test data URL for WIEN2k benchmark test", CUSTOM],
                'wien_mpirun': [None, "MPI wrapper command to use", CUSTOM],
                'remote': [None, "Remote command to use (e.g. pbsssh, ...)", CUSTOM],
                'use_remote': [True, "Whether to remotely login to initiate the k-point parallellization calls", CUSTOM],
                'mpi_remote': [False, "Whether to initiate MPI calls locally or remotely", CUSTOM],
                'wien_granularity': [True, "Granularity for parallel execution (see manual)", CUSTOM],
                'taskset': [None, "Specifies an optional command for binding a process to a specific core", CUSTOM],
    
    easybuild's avatar
    easybuild committed
                'nmatmax': [19000, "Specifies the maximum matrix size", CUSTOM],
                'nume': [6000, "Specifies the number of states to output.", CUSTOM],
    
            }
            return EasyBlock.extra_options(extra_vars)
    
        def extract_step(self):
            """Unpack WIEN2k sources using gunzip and provided expand_lapw script."""
            super(EB_WIEN2k, self).extract_step()
    
            cmd = "gunzip *gz"
            run_cmd(cmd, log_all=True, simple=True)
    
            cmd = "./expand_lapw"
            qanda = {'continue (y/n)': 'y'}
            no_qa = [
    
    easybuild's avatar
    easybuild committed
                'tar -xf.*',
                '.*copied and linked.*',
            ]
    
    
            run_cmd_qa(cmd, qanda, no_qa=no_qa, log_all=True, simple=True)
    
        def configure_step(self):
            """Configure WIEN2k build by patching siteconfig_lapw script and running it."""
    
            self.cfgscript = "siteconfig_lapw"
    
            # patch config file first
    
            # toolchain-dependent values
            comp_answer = None
            if self.toolchain.comp_family() == toolchain.INTELCOMP:  # @UndefinedVariable
    
    easybuild's avatar
    easybuild committed
                if get_software_root('icc'):
                    intelver = get_software_version('icc')
                elif get_software_root('intel-compilers'):
                    intelver = get_software_version('intel-compilers')
                if LooseVersion(intelver) >= LooseVersion("2011"):
    
                    if LooseVersion(self.version) < LooseVersion("17"):
                        comp_answer = 'I'  # Linux (Intel ifort 12.0 compiler + mkl )
                    else:
                        comp_answer = 'LI'  # Linux (Intel ifort compiler (12.0 or later)+mkl+intelmpi))
                else:
                    comp_answer = "K1"  # Linux (Intel ifort 11.1 compiler + mkl )
            elif self.toolchain.comp_family() == toolchain.GCC:  # @UndefinedVariable
                if LooseVersion(self.version) < LooseVersion("17"):
                    comp_answer = 'V'  # Linux (gfortran compiler + gotolib)
                else:
                    comp_answer = 'LG'  # Linux (gfortran compiler + OpenBlas)
            else:
                raise EasyBuildError("Failed to determine toolchain-dependent answers.")
    
            # libraries
            liblapack = os.getenv('LIBLAPACK_MT').replace('static', 'dynamic')
            libscalapack = os.getenv('LIBSCALAPACK_MT').replace('static', 'dynamic')
            rlibs = "%s %s" % (liblapack, self.toolchain.get_flag('openmp'))
            rplibs = [libscalapack, liblapack]
            fftwver = get_software_version('FFTW')
            if fftwver:
                suff = ''
                if LooseVersion(fftwver) >= LooseVersion("3"):
                    suff = '3'
                rplibs.insert(0, "-lfftw%(suff)s_mpi -lfftw%(suff)s" % {'suff': suff})
            else:
                rplibs.append(os.getenv('LIBFFT'))
    
            rplibs = ' '.join(rplibs)
    
            vars = {
                'FC': '%s' % os.getenv('F90'),
                'FOPT': '%s' % os.getenv('FFLAGS'),
                'MPF': '%s' % os.getenv('MPIF90'),
                'FPOPT': '%s' % os.getenv('FFLAGS'),
                'CC': os.getenv('CC'),
                'LDFLAGS': '$(FOPT) %s ' % os.getenv('LDFLAGS'),
                'R_LIBS': rlibs,  # libraries for 'real' (not 'complex') binary
                'RP_LIBS': rplibs,  # libraries for 'real' parallel binary
                'MPIRUN': '',
            }
    
            for line in fileinput.input(self.cfgscript, inplace=1, backup='.orig'):
                # set config parameters
                for (key, val) in vars.items():
                    regexp = re.compile('^([a-z0-9]+):%s:(.*)' % key)
                    res = regexp.search(line)
                    if res:
                        # we need to exclude the lines with 'current', otherwise we break the script
                        if not res.group(1) == "current":
                            if 'OPT' in key:
                                # append instead of replace
                                line = regexp.sub('\\1:%s:%s %s' % (key, res.group(2), val), line)
                            else:
                                line = regexp.sub('\\1:%s:%s' % (key, val), line)
                # avoid exit code > 0 at end of configuration
    
    easybuild's avatar
    easybuild committed
                line = re.sub(r'(\s+)exit 1', '\\1exit 0', line)
    
                sys.stdout.write(line)
    
            # set correct compilers
            env.setvar('bin', os.getcwd())
    
            dc = {
                'COMPILERC': os.getenv('CC'),
                'COMPILER': os.getenv('F90'),
                'COMPILERP': os.getenv('MPIF90'),
            }
    
            if LooseVersion(self.version) < LooseVersion("17"):
                for (key, val) in dc.items():
                    write_file(key, val)
            else:
                dc['cc'] = dc.pop('COMPILERC')
                dc['fortran'] = dc.pop('COMPILER')
                dc['parallel'] = dc.pop('COMPILERP')
    
    easybuild's avatar
    easybuild committed
                write_file('WIEN2k_COMPILER', '\n'.join(['%s:%s' % (k, v) for k, v in dc.items()]))
    
    
            # configure with patched configure script
            self.log.debug('%s part I (configure)' % self.cfgscript)
    
    
    easybuild's avatar
    easybuild committed
            if LooseVersion(self.version) >= LooseVersion('21'):
                perlroot = get_software_root('Perl')
                if perlroot is None:
                    raise EasyBuildError("Perl is a required dependency of WIEN2k as of version 21")
                self.perlbin = os.path.join(perlroot, 'bin', 'perl')
            else:
                self.perlbin = ''
    
    
            cmd = "./%s" % self.cfgscript
            qanda = {
    
    easybuild's avatar
    easybuild committed
                'Press RETURN to continue': '',
                'Your compiler:': '',
                'Hit Enter to continue': '',
                'Remote shell (default is ssh) =': '',
                'Remote copy (default is scp) =': '',
                'and you need to know details about your installed  mpi ..) (y/n)': 'y',
                'Q to quit Selection:': 'Q',
                'A Compile all programs (suggested) Q Quit Selection:': 'Q',
                'Please enter the full path of the perl program: ': self.perlbin,
                'continue or stop (c/s)': 'c',
                '(like taskset -c). Enter N / your_specific_command:': 'N',
    
            }
            if LooseVersion(self.version) >= LooseVersion("13"):
                fftw_root = get_software_root('FFTW')
                if fftw_root:
                    fftw_maj = get_software_version('FFTW').split('.')[0]
                    fftw_spec = 'FFTW%s' % fftw_maj
                else:
                    raise EasyBuildError("Required FFTW dependency is missing")
                qanda.update({
    
    easybuild's avatar
    easybuild committed
                    ') Selection:': comp_answer,
                    'Shared Memory Architecture? (y/N):': 'N',
                    'Set MPI_REMOTE to  0 / 1:': '0',
                    'You need to KNOW details about your installed  MPI and FFTW ) (y/n)': 'y',
                    'Do you want to use FFTW (recommended, but for sequential code not required)? (Y,n):': 'y',
                    'Please specify whether you want to use FFTW3 (default) or FFTW2  (FFTW3 / FFTW2):': fftw_spec,
                    'Please specify the ROOT-path of your FFTW installation (like /opt/fftw3):': fftw_root,
                    'is this correct? enter Y (default) or n:': 'Y',
    
                })
    
                libxcroot = get_software_root('libxc')
    
    easybuild's avatar
    easybuild committed
    
                if LooseVersion(self.version) < LooseVersion("17"):
                    libxcstr1 = ' before'
                    libxcstr3 = ''
                elif LooseVersion(self.version) > LooseVersion("19"):
                    libxcstr1 = ' - usually not needed'
                    libxcstr3 = 'root-'
                else:
                    libxcstr1 = ''
                    libxcstr3 = ''
                
                if LooseVersion(self.version) > LooseVersion("23"):
                    libxcquestion1 = 'Would you like to use LIBXC (needed ONLY for self-consistent gKS mGGA calculations, for the stress tensor and experts who want to play with different DFT options. It must have been installed before)? (y,N): '
                else:
                    libxcquestion1 = 'LIBXC (that you have installed%s)? (y,N):' % libxcstr1
                
                libxcquestion2 = 'Do you want to automatically search for LIBXC installations? (Y,n):'
                libxcquestion3 = 'Please enter the %sdirectory of your LIBXC-installation!:' % libxcstr3
                libxcquestion4 = 'Please enter the lib-directory of your LIBXC-installation (usually lib or lib64)!:'
                libxcquestion5 = 'LIBXC (usually not needed, ONLY for experts who want to play with different DFT options. '
                libxcquestion5 += 'It must have been installed before)? (y,N):'
    
    
                if libxcroot:
                    qanda.update({
    
    easybuild's avatar
    easybuild committed
                        libxcquestion1: 'y',
                        libxcquestion2: 'n',
                        libxcquestion3: libxcroot,
                        libxcquestion4: 'lib',
                        libxcquestion5: 'y',
    
    easybuild's avatar
    easybuild committed
                    qanda.update({
                        libxcquestion1: 'N',
                        libxcquestion5: 'N',
                    })
    
    
                if LooseVersion(self.version) >= LooseVersion("17"):
                    scalapack_libs = os.getenv('LIBSCALAPACK').split()
                    scalapack = next((lib[2:] for lib in scalapack_libs if 'scalapack' in lib), 'scalapack')
                    blacs = next((lib[2:] for lib in scalapack_libs if 'blacs' in lib), 'openblas')
                    qanda.update({
    
    easybuild's avatar
    easybuild committed
                        'You need to KNOW details about your installed MPI, ELPA, and FFTW ) (y/N)': 'y',
                        'Do you want to use a present ScaLAPACK installation? (Y,n):': 'y',
                        'Do you want to use the MKL version of ScaLAPACK? (Y,n):': 'n',  # we set it ourselves below
                        'Do you use Intel MPI? (Y,n):': 'y',
                        'Is this correct? (Y,n):': 'y',
                        'Please specify the target architecture of your ScaLAPACK libraries (e.g. intel64)!:': '',
                        'ScaLAPACK root:': os.getenv('MKLROOT') or os.getenv('EBROOTSCALAPACK'),
                        'ScaLAPACK library:': scalapack,
                        'BLACS root:': os.getenv('MKLROOT') or os.getenv('EBROOTOPENBLAS'),
                        'BLACS library:': blacs,
                        'Please enter your choice of additional libraries!:': '',
                        'Do you want to use a present FFTW installation? (Y,n):': 'y',
                        'Please specify the path of your FFTW installation (like /opt/fftw3/) '
                        'or accept present choice (enter):': fftw_root,
                        'Please specify the target achitecture of your FFTW library (e.g. lib64) '
                        'or accept present choice (enter):': '',
                        'Do you want to automatically search for FFTW installations? (Y,n):': 'n',
                        'Please specify the ROOT-path of your FFTW installation (like /opt/fftw3/) '
                        'or accept present choice (enter):': fftw_root,
                        'Is this correct? enter Y (default) or n:': 'Y',
                        'Please specify the name of your FFTW library or accept present choice (enter):': '',
                        'or accept the recommendations (Enter - default)!:': '',
                        # the temporary directory is hardcoded into execution scripts and must exist at runtime
                        'Please enter the full path to your temporary directory:': '/tmp',
                    })
    
    easybuild's avatar
    easybuild committed
                    std_qa = {}
    
                    elparoot = get_software_root('ELPA')
                    if elparoot:
    
    easybuild's avatar
    easybuild committed
    
                        apply_regex_substitutions(self.cfgscript, [(r'cat elpahelp2$', 'cat -n elpahelp2')])
    
                        elpa_dict = {
                            'root': elparoot,
                            'version': get_software_version('ELPA'),
                            'variant': 'elpa_openmp' if self.toolchain.get_flag('openmp') else 'elpa'}
    
                        elpa_dir = "%(root)s/include/%(variant)s-%(version)s" % elpa_dict
                        std_qa.update({
                            r".*(?P<number>[0-9]+)\t%s\n(.*\n)*" % elpa_dir: "%(number)s",
                        })
    
    
                        qanda.update({
                            'Do you want to use ELPA? (y,N):': 'y',
                            'Do you want to automatically search for ELPA installations? (Y,n):': 'n',
                            'Please specify the ROOT-path of your ELPA installation (like /usr/local/elpa/) '
                            'or accept present path (Enter):': elparoot,
    
    easybuild's avatar
    easybuild committed
                            'Please specify the lib-directory of your ELPA installation (e.g. lib or lib64)!:': 'lib',
                            'Please specify the lib-directory of your ELPA installation (e.g. lib or lib64):': 'lib',
                            'Please specify the name of your installed ELPA library (e.g. elpa or elpa_openmp)!:':
                                elpa_dict['variant'],
                            'Please specify the name of your installed ELPA library (e.g. elpa or elpa_openmp):':
                                elpa_dict['variant'],
    
                        })
                    else:
                        qanda.update({'Do you want to use ELPA? (y,N):': 'n'})
            else:
                qanda.update({
    
    easybuild's avatar
    easybuild committed
                    'compiler) Selection:': comp_answer,
                    'Shared Memory Architecture? (y/n):': 'n',
                    'If you are using mpi2 set MPI_REMOTE to 0  Set MPI_REMOTE to 0 / 1:': '0',
                    'Do you have MPI and Scalapack installed and intend to run '
                    'finegrained parallel? (This is usefull only for BIG cases '
                    '(50 atoms and more / unit cell) and you need to know details '
                    'about your installed  mpi and fftw ) (y/n)': 'y',
    
                })
    
            no_qa = [
                'You have the following mkl libraries in %s :' % os.getenv('MKLROOT'),
                "%s[ \t]*.*" % os.getenv('MPIF90'),
                "%s[ \t]*.*" % os.getenv('F90'),
                "%s[ \t]*.*" % os.getenv('CC'),
                ".*SRC_.*",
            ]
    
    
    easybuild's avatar
    easybuild committed
            std_qa.update({
    
                r'S\s+Save and Quit[\s\n]+To change an item select option.[\s\n]+Selection:': 'S',
                'Recommended setting for parallel f90 compiler: .* Current selection: Your compiler:': os.getenv('MPIF90'),
                r'process or you can change single items in "Compiling Options".[\s\n]+Selection:': 'S',
                r'A\s+Compile all programs (suggested)[\s\n]+Q\s*Quit[\s\n]+Selection:': 'Q',
    
    easybuild's avatar
    easybuild committed
            })
    
    
            run_cmd_qa(cmd, qanda, no_qa=no_qa, std_qa=std_qa, log_all=True, simple=True)
    
            # post-configure patches
            parallel_options = {}
            parallel_options_fp = os.path.join(self.cfg['start_dir'], 'parallel_options')
    
            if self.cfg['wien_mpirun']:
                parallel_options.update({'WIEN_MPIRUN': self.cfg['wien_mpirun']})
    
            if self.cfg['taskset'] is None:
                self.cfg['taskset'] = 'no'
            parallel_options.update({'TASKSET': self.cfg['taskset']})
    
            for opt in ['use_remote', 'mpi_remote', 'wien_granularity']:
                parallel_options.update({opt.upper(): int(self.cfg[opt])})
    
            write_file(parallel_options_fp, '\n'.join(['setenv %s "%s"' % tup for tup in parallel_options.items()]))
    
            if self.cfg['remote']:
                if self.cfg['remote'] == 'pbsssh':
                    extratxt = '\n'.join([
                        '',
                        "set remote = pbsssh",
                        "setenv PBSSSHENV 'LD_LIBRARY_PATH PATH'",
                        '',
                    ])
                    write_file(parallel_options_fp, extratxt, append=True)
                else:
                    raise EasyBuildError("Don't know how to handle remote %s", self.cfg['remote'])
    
            self.log.debug("Patched file %s: %s", parallel_options_fp, read_file(parallel_options_fp))
    
    
    easybuild's avatar
    easybuild committed
            # Set configurable parameters for size of problems.
            param_subs = [
                (r'\s+PARAMETER\s+\(\s*NMATMAX\s*=\s*\d+\)', r'      PARAMETER (NMATMAX=%s)' % self.cfg['nmatmax']),
                (r'\s+PARAMETER\s+\(\s*NUME\s*=\s*\d+\)', r'      PARAMETER (NUME=%s)' % self.cfg['nume']),
            ]
            self.log.debug("param_subs = %s" % param_subs)
            apply_regex_substitutions('SRC_lapw1/param.inc', param_subs)
            self.log.debug("Patched file %s: %s", 'SRC_lapw1/param.inc', read_file('SRC_lapw1/param.inc'))
    
    
        def build_step(self):
            """Build WIEN2k by running siteconfig_lapw script again."""
    
            self.log.debug('%s part II (build_step)' % self.cfgscript)
    
            cmd = "./%s" % self.cfgscript
    
            qanda = {
                'Press RETURN to continue': '\nQ',  # also answer on first qanda pattern with 'Q' to quit
    
    easybuild's avatar
    easybuild committed
                'Please enter the full path of the perl program: ': self.perlbin,
            }
    
    
            if LooseVersion(self.version) < LooseVersion("17"):
                qanda.update({
    
    easybuild's avatar
    easybuild committed
                    'L Perl path (if not in /usr/bin/perl) Q Quit Selection:': 'R',
                    'A Compile all programs S Select program Q Quit Selection:': 'A',
    
                })
            else:
                qanda.update({
    
    easybuild's avatar
    easybuild committed
                    'program Q Quit Selection:': 'A',
                    'Path Q Quit Selection:': 'R',
    
    easybuild's avatar
    easybuild committed
                "%s[ \t]*.*" % os.getenv('MPIF90'),
                "%s[ \t]*.*" % os.getenv('F90'),
                "%s[ \t]*.*" % os.getenv('CC'),
                "mv[ \t]*.*",
                ".*SRC_.*",
                ".*: warning .*",
                ".*Stop.",
                "Compile time errors (if any) were:",
            ]
    
    
            self.log.debug("no_qa for %s: %s" % (cmd, no_qa))
            run_cmd_qa(cmd, qanda, no_qa=no_qa, log_all=True, simple=True)
    
        def test_step(self):
            """Run WIEN2k test benchmarks. """
    
            def run_wien2k_test(cmd_arg):
                """Run a WPS command, and check for success."""
    
                cmd = "x_lapw lapw1 %s" % cmd_arg
                (out, _) = run_cmd(cmd, log_all=True, simple=False)
    
    
    easybuild's avatar
    easybuild committed
                re_success = re.compile(r"LAPW1\s+END")
    
                if not re_success.search(out):
                    raise EasyBuildError("Test '%s' in %s failed (pattern '%s' not found)?",
                                         cmd, os.getcwd(), re_success.pattern)
                else:
                    self.log.info("Test '%s' seems to have run successfully: %s" % (cmd, out))
    
            if self.cfg['runtest']:
                if not self.cfg['testdata']:
                    raise EasyBuildError("List of URLs for testdata not provided.")
    
                # prepend $PATH with install directory, define $SCRATCH which is used by the tests
                env.setvar('PATH', "%s:%s" % (self.installdir, os.environ['PATH']))
                try:
                    cwd = os.getcwd()
    
                    # create temporary directory
                    tmpdir = tempfile.mkdtemp()
                    os.chdir(tmpdir)
                    self.log.info("Running tests in %s" % tmpdir)
    
                    scratch = os.path.join(tmpdir, 'scratch')
                    mkdir(scratch)
                    env.setvar('SCRATCH', scratch)
    
                    # download data
                    testdata_paths = {}
                    for testdata in self.cfg['testdata']:
                        td_path = self.obtain_file(testdata)
                        if not td_path:
                            raise EasyBuildError("Downloading file from %s failed?", testdata)
                        testdata_paths.update({os.path.basename(testdata): td_path})
    
                    self.log.debug('testdata_paths: %s' % testdata_paths)
    
                    # unpack serial benchmark
                    serial_test_name = "test_case"
    
    easybuild's avatar
    easybuild committed
                    srcdir = extract_file(testdata_paths['%s.tar.gz' % serial_test_name], tmpdir, change_into_dir=False)
                    change_dir(srcdir)
    
    
                    # run serial benchmark
                    os.chdir(os.path.join(tmpdir, serial_test_name))
                    run_wien2k_test("-c")
    
                    # unpack parallel benchmark (in serial benchmark dir)
                    parallel_test_name = "mpi-benchmark"
    
    easybuild's avatar
    easybuild committed
                    srcdir = extract_file(testdata_paths['%s.tar.gz' % parallel_test_name], tmpdir, change_into_dir=False)
                    change_dir(srcdir)
    
    
                    # run parallel benchmark
                    os.chdir(os.path.join(tmpdir, serial_test_name))
                    run_wien2k_test("-p")
    
                    os.chdir(cwd)
    
    easybuild's avatar
    easybuild committed
                    remove_dir(tmpdir)
    
    
                except OSError as err:
                    raise EasyBuildError("Failed to run WIEN2k benchmark tests: %s", err)
    
                self.log.debug("Current dir: %s" % os.getcwd())
    
        def test_cases_step(self):
            """Run test cases, if specified."""
    
            for test in self.cfg['tests']:
    
                # check expected format
                if not len(test) == 4:
                    raise EasyBuildError("WIEN2k test case not specified in expected format: "
                                         "(testcase_name, init_lapw_args, run_lapw_args, [scf_regexp_pattern])")
                test_name = test[0]
                init_args = test[1]
                run_args = test[2]
                scf_regexp_patterns = test[3]
    
                try:
                    cwd = os.getcwd()
                    # WIEN2k enforces that working dir has same name as test case
                    tmpdir = os.path.join(tempfile.mkdtemp(), test_name)
    
                    scratch = os.path.join(tmpdir, 'scratch')
                    mkdir(scratch, parents=True)
                    env.setvar('SCRATCH', scratch)
    
                    os.chdir(tmpdir)
                    self.log.info("Running test case %s in %s" % (test_name, tmpdir))
                except OSError as err:
                    raise EasyBuildError("Failed to create temporary directory for test %s: %s", test_name, err)
    
                # try and find struct file for test
                test_fp = self.obtain_file("%s.struct" % test_name)
    
                try:
                    shutil.copy2(test_fp, tmpdir)
                except OSError as err:
                    raise EasyBuildError("Failed to copy %s: %s", test_fp, err)
    
                # run test
                cmd = "init_lapw %s" % init_args
                run_cmd(cmd, log_all=True, simple=True)
    
                cmd = "run_lapw %s" % run_args
                run_cmd(cmd, log_all=True, simple=True)
    
                # check output
                scf_fn = "%s.scf" % test_name
                self.log.debug("Checking output of test %s in %s" % (str(test), scf_fn))
                scftxt = read_file(scf_fn)
                for regexp_pat in scf_regexp_patterns:
                    regexp = re.compile(regexp_pat, re.M)
                    if not regexp.search(scftxt):
                        raise EasyBuildError("Failed to find pattern %s in %s", regexp.pattern, scf_fn)
                    else:
                        self.log.debug("Found pattern %s in %s" % (regexp.pattern, scf_fn))
    
                # cleanup
                try:
                    os.chdir(cwd)
    
    easybuild's avatar
    easybuild committed
                    remove_dir(tmpdir)
    
                except OSError as err:
                    raise EasyBuildError("Failed to clean up temporary test dir: %s", err)
    
        def install_step(self):
            """Fix broken symlinks after build/installation."""
            # fix broken symlink
            os.remove(os.path.join(self.installdir, "SRC_w2web", "htdocs", "usersguide"))
            os.symlink(os.path.join(self.installdir, "SRC_usersguide_html"),
                       os.path.join(self.installdir, "SRC_w2web", "htdocs", "usersguide"))
    
        def sanity_check_step(self):
            """Custom sanity check for WIEN2k."""
    
            lapwfiles = []
            for suffix in ['0', '0_mpi', '1', '1_mpi', '1c', '1c_mpi', '2', '2_mpi', '2c', '2c_mpi',
                           '3', '3c', '5', '5c', '7', '7c', 'dm', 'dmc', 'so']:
                p = os.path.join(self.installdir, "lapw%s" % suffix)
                lapwfiles.append(p)
    
            custom_paths = {
                'files': lapwfiles,
                'dirs': [],
            }
    
            super(EB_WIEN2k, self).sanity_check_step(custom_paths=custom_paths)
    
        def make_module_extra(self):
            """Set WIENROOT environment variable, and correctly prepend PATH."""
    
            txt = super(EB_WIEN2k, self).make_module_extra()
    
            txt += self.module_generator.set_environment("WIENROOT", self.installdir)
            txt += self.module_generator.prepend_paths("PATH", [""])
    
            return txt