Ejemplo n.º 1
0
def obtain_config_guess(download_source_path=None, search_source_paths=None):
    """
    Locate or download an up-to-date config.guess

    :param download_source_path: Path to download config.guess to
    :param search_source_paths: Paths to search for config.guess
    :return: Path to config.guess or None
    """
    log = fancylogger.getLogger('obtain_config_guess')

    eb_source_paths = source_paths()

    if download_source_path is None:
        download_source_path = eb_source_paths[0]
    else:
        log.deprecated(
            "Specifying custom source path to download config.guess via 'download_source_path'",
            '5.0')

    if search_source_paths is None:
        search_source_paths = eb_source_paths
    else:
        log.deprecated(
            "Specifying custom location to search for updated config.guess via 'search_source_paths'",
            '5.0')

    config_guess = 'config.guess'
    sourcepath_subdir = os.path.join('generic', 'eb_v%s' % EASYBLOCKS_VERSION,
                                     'ConfigureMake')

    config_guess_path = None

    # check if config.guess has already been downloaded to source path
    for path in search_source_paths:
        cand_config_guess_path = os.path.join(path, sourcepath_subdir,
                                              config_guess)
        if os.path.isfile(cand_config_guess_path) and check_config_guess(
                cand_config_guess_path):
            force_download = build_option('force_download')
            if force_download:
                print_warning(
                    "Found file %s at %s, but re-downloading it anyway..." %
                    (config_guess, cand_config_guess_path))
            else:
                config_guess_path = cand_config_guess_path
                log.info("Found %s at %s", config_guess, config_guess_path)
            break

    if not config_guess_path:
        cand_config_guess_path = os.path.join(download_source_path,
                                              sourcepath_subdir, config_guess)
        config_guess_url = CONFIG_GUESS_URL_STUB + CONFIG_GUESS_COMMIT_ID
        if not download_file(config_guess, config_guess_url,
                             cand_config_guess_path):
            print_warning("Failed to download recent %s to %s",
                          config_guess,
                          cand_config_guess_path,
                          log=log)
        elif not check_config_guess(cand_config_guess_path):
            print_warning("Verification failed for file %s, not using it!",
                          cand_config_guess_path,
                          log=log)
            remove_file(cand_config_guess_path)
        else:
            config_guess_path = cand_config_guess_path
            adjust_permissions(config_guess_path,
                               stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH,
                               add=True)
            log.info("Verified %s at %s, using it if required", config_guess,
                     config_guess_path)

    return config_guess_path
Ejemplo n.º 2
0
import tempfile
import time
from abc import ABCMeta

from easybuild.base import fancylogger
from easybuild.base.frozendict import FrozenDictKnownKeys
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.py2vs3 import ascii_letters, create_base_metaclass, string_type

try:
    import rich  # noqa
    HAVE_RICH = True
except ImportError:
    HAVE_RICH = False

_log = fancylogger.getLogger('config', fname=False)

ERROR = 'error'
IGNORE = 'ignore'
PURGE = 'purge'
UNLOAD = 'unload'
UNSET = 'unset'
WARN = 'warn'

PKG_TOOL_FPM = 'fpm'
PKG_TYPE_RPM = 'rpm'

CONT_IMAGE_FORMAT_EXT3 = 'ext3'
CONT_IMAGE_FORMAT_SANDBOX = 'sandbox'
CONT_IMAGE_FORMAT_SIF = 'sif'
CONT_IMAGE_FORMAT_SQUASHFS = 'squashfs'
Ejemplo n.º 3
0
"""
import math
import os
import re

from easybuild.base import fancylogger
from easybuild.framework.easyblock import get_easyblock_instance
from easybuild.framework.easyconfig.easyconfig import ActiveMNS
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.config import build_option, get_repository, get_repositorypath
from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version
from easybuild.tools.job.backend import job_backend
from easybuild.tools.repository.repository import init_repository


_log = fancylogger.getLogger('parallelbuild', fname=False)


def _to_key(dep):
    """Determine key for specified dependency."""
    return ActiveMNS().det_full_module_name(dep)


def build_easyconfigs_in_parallel(build_command, easyconfigs, output_dir='easybuild-build', prepare_first=True):
    """
    Build easyconfigs in parallel by submitting jobs to a batch-queuing system.
    Return list of jobs submitted.

    Argument `easyconfigs` is a list of easyconfigs which can be
    built: e.g. they have no unresolved dependencies.  This function
    will build them in parallel by submitting jobs.
Ejemplo n.º 4
0
"""
Easyconfig module that contains the default EasyConfig configuration parameters.

: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: Toon Willems (Ghent University)
"""
from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.config import MODULECLASS_BASE


_log = fancylogger.getLogger('easyconfig.default', fname=False)

# constants for different categories of easyconfig parameters
# use tuples so we can sort them based on the numbers
HIDDEN = (-1, 'hidden')
MANDATORY = (0, 'mandatory')
CUSTOM = (1, 'easyblock-specific')
TOOLCHAIN = (2, 'toolchain')
BUILD = (3, 'build')
FILEMANAGEMENT = (4, 'file-management')
DEPENDENCIES = (5, 'dependencies')
LICENSE = (6, 'license')
EXTENSIONS = (7, 'extensions')
MODULES = (8, 'modules')
OTHER = (9, 'other')
Ejemplo n.º 5
0
# #
"""
Module that contains a set of classes and function to generate variables to be used
e.g., in compiling or linking

:author: Stijn De Weirdt (Ghent University)
:author: Kenneth Hoste (Ghent University)
"""
import copy
import os

from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError


_log = fancylogger.getLogger('variables', fname=False)


def get_class(name, default_class, map_class=None):
    """Return class based on default
        map_class
             if key == str -> value = class
             else: key = class -> list of strings
    """
    if map_class is None:
        map_class = {}

    klass = default_class
    if name is not None:
        try:
            klass = map_class[name]
Ejemplo n.º 6
0
information obtained from provided file (easystack) with build specifications.

:author: Denis Kristak (Inuits)
:author: Pavel Grochal (Inuits)
"""

from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.filetools import read_file
from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version
from easybuild.tools.utilities import only_if_module_is_available
try:
    import yaml
except ImportError:
    pass
_log = fancylogger.getLogger('easystack', fname=False)


class EasyStack(object):
    """One class instance per easystack. General options + list of all SoftwareSpecs instances"""
    def __init__(self):
        self.easybuild_version = None
        self.robot = False
        self.software_list = []

    def compose_ec_filenames(self):
        """Returns a list of all easyconfig names"""
        ec_filenames = []
        for sw in self.software_list:
            full_ec_version = det_full_ec_version({
                'toolchain': {
Ejemplo n.º 7
0
# #
"""
Support for checking types of easyconfig parameter values.

:author: Caroline De Brouwer (Ghent University)
:author: Kenneth Hoste (Ghent University)
"""
from distutils.util import strtobool

from easybuild.base import fancylogger
from easybuild.framework.easyconfig.format.format import DEPENDENCY_PARAMETERS
from easybuild.framework.easyconfig.format.format import SANITY_CHECK_PATHS_DIRS, SANITY_CHECK_PATHS_FILES
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.py2vs3 import string_type

_log = fancylogger.getLogger('easyconfig.types', fname=False)


def as_hashable(dict_value):
    """Helper function, convert dict value to hashable equivalent via tuples."""
    res = []
    for key, val in sorted(dict_value.items()):
        if isinstance(val, list):
            val = tuple(val)
        elif isinstance(val, dict):
            val = as_hashable(val)
        res.append((key, val))
    return tuple(res)


def check_element_types(elems, allowed_types):
Ejemplo n.º 8
0
import copy
import os
import sys

from easybuild.base import fancylogger
from easybuild.framework.easyconfig.easyconfig import EASYCONFIGS_ARCHIVE_DIR, ActiveMNS, process_easyconfig
from easybuild.framework.easyconfig.easyconfig import robot_find_easyconfig, verify_easyconfig_filename
from easybuild.framework.easyconfig.tools import find_resolved_modules, skip_available
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.config import build_option
from easybuild.tools.filetools import det_common_path_prefix, search_file
from easybuild.tools.module_naming_scheme.easybuild_mns import EasyBuildMNS
from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version
from easybuild.tools.utilities import flatten, nub

_log = fancylogger.getLogger('tools.robot', fname=False)


def det_robot_path(robot_paths_option,
                   tweaked_ecs_paths,
                   pr_paths,
                   auto_robot=False):
    """Determine robot path."""
    robot_path = robot_paths_option[:]
    _log.info("Using robot path(s): %s", robot_path)

    tweaked_ecs_path, tweaked_ecs_deps_path = None, None
    # paths to tweaked easyconfigs or easyconfigs downloaded from a PR have priority
    if tweaked_ecs_paths is not None:
        tweaked_ecs_path, tweaked_ecs_deps_path = tweaked_ecs_paths
        # easyconfigs listed on the command line (and tweaked) should be found first
Ejemplo n.º 9
0
from easybuild.framework.easyconfig.constants import EASYCONFIG_CONSTANTS
from easybuild.framework.easyconfig.default import get_easyconfig_parameter_default
from easybuild.framework.easyconfig.easyconfig import EasyConfig, create_paths, process_easyconfig
from easybuild.framework.easyconfig.easyconfig import get_toolchain_hierarchy, ActiveMNS
from easybuild.framework.easyconfig.format.format import DEPENDENCY_PARAMETERS
from easybuild.toolchains.gcccore import GCCcore
from easybuild.tools.build_log import EasyBuildError, print_warning
from easybuild.tools.config import build_option
from easybuild.tools.filetools import read_file, write_file
from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version
from easybuild.tools.robot import resolve_dependencies, robot_find_easyconfig
from easybuild.tools.toolchain.toolchain import SYSTEM_TOOLCHAIN_NAME
from easybuild.tools.toolchain.toolchain import TOOLCHAIN_CAPABILITIES
from easybuild.tools.utilities import flatten, nub, quote_str

_log = fancylogger.getLogger('easyconfig.tweak', fname=False)

EASYCONFIG_TEMPLATE = "TEMPLATE"


def ec_filename_for(path):
    """
    Return a suiting file name for the easyconfig file at <path>,
    as determined by its contents.
    """
    ec = EasyConfig(path, validate=False)

    fn = "%s-%s.eb" % (ec['name'], det_full_ec_version(ec))

    return fn
Ejemplo n.º 10
0
"""
import os
import tempfile
import pprint

from easybuild.base import fancylogger
from easybuild.tools.config import PKG_TOOL_FPM, PKG_TYPE_RPM, Singleton
from easybuild.tools.config import build_option, get_package_naming_scheme, log_path
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.filetools import change_dir, which
from easybuild.tools.package.package_naming_scheme.pns import PackageNamingScheme
from easybuild.tools.py2vs3 import create_base_metaclass
from easybuild.tools.run import run_cmd
from easybuild.tools.utilities import get_subclasses, import_available_modules

_log = fancylogger.getLogger('tools.package')  # pylint: disable=C0103


def avail_package_naming_schemes():
    """
    Returns the list of valed naming schemes
    They are loaded from the easybuild.package.package_naming_scheme namespace
    """
    import_available_modules('easybuild.tools.package.package_naming_scheme')
    class_dict = dict([(x.__name__, x)
                       for x in get_subclasses(PackageNamingScheme)])
    return class_dict


def package(easyblock):
    """
Ejemplo n.º 11
0
# You should have received a copy of the GNU General Public License
# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
# #
"""
This module implements all supported formats and their converters

:author: Stijn De Weirdt (Ghent University)
"""
import re

from easybuild.base import fancylogger
from easybuild.base.wrapper import Wrapper
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.py2vs3 import string_type

_log = fancylogger.getLogger('tools.convert', fname=False)


class Convert(Wrapper):
    """
    Convert casts a string passed via the initialisation to a Convert (sub)class instance,
     mainly for typechecking and printing purposes.
    """
    SEPARATOR = None

    def __init__(self, obj):
        """Support the conversion of obj to something"""
        self.__dict__['log'] = fancylogger.getLogger(self.__class__.__name__,
                                                     fname=False)
        self.__dict__['data'] = None
        if isinstance(obj, string_type):
Ejemplo n.º 12
0
# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
#

"""
Easyconfig licenses module that provides all licenses that can
be used within an Easyconfig file.

:author: Stijn De Weirdt (Ghent University)
:author: Kenneth Hoste (Ghent University)
"""

from easybuild.base import fancylogger
from easybuild.tools.utilities import get_subclasses


_log = fancylogger.getLogger('easyconfig.licenses', fname=False)


class License(object):
    """EasyBuild easyconfig license class
        This is also the default restrictive license
    """
    HIDDEN = False  # disable subclasses from being seen/used
    NAME = None
    VERSION = None
    DESCRIPTION = None

    DISTRIBUTE_SOURCE = False  # does the license allows to (re)distribute the code
    GROUP_SOURCE = True  # does the license require to keep the source under dedicated group
    GROUP_BINARY = True  # does the license require to install the binaries under dedicated group
class EasyConfigTest(TestCase):
    """Baseclass for easyconfig testcases."""

    # initialize configuration (required for e.g. default modules_tool setting)
    eb_go = eboptions.parse_options()
    config.init(eb_go.options, eb_go.get_options_by_section('config'))
    build_options = {
        'check_osdeps': False,
        'external_modules_metadata': {},
        'force': True,
        'local_var_naming_check': 'error',
        'optarch': 'test',
        'robot_path': get_paths_for("easyconfigs")[0],
        'silent': True,
        'suffix_modules_path': GENERAL_CLASS,
        'valid_module_classes': config.module_classes(),
        'valid_stops': [x[0] for x in EasyBlock.get_steps()],
    }
    config.init_build_options(build_options=build_options)
    set_tmpdir()
    del eb_go

    # put dummy 'craype-test' module in place, which is required for parsing easyconfigs using Cray* toolchains
    TMPDIR = tempfile.mkdtemp()
    os.environ['MODULEPATH'] = TMPDIR
    write_file(os.path.join(TMPDIR, 'craype-test'), '#%Module\n')

    log = fancylogger.getLogger("EasyConfigTest", fname=False)

    # make sure a logger is present for main
    eb_main._log = log
    ordered_specs = None
    parsed_easyconfigs = []

    def process_all_easyconfigs(self):
        """Process all easyconfigs and resolve inter-easyconfig dependencies."""
        # all available easyconfig files
        easyconfigs_path = get_paths_for("easyconfigs")[0]
        specs = glob.glob('%s/*/*/*.eb' % easyconfigs_path)

        # parse all easyconfigs if they haven't been already
        if not EasyConfigTest.parsed_easyconfigs:
            for spec in specs:
                EasyConfigTest.parsed_easyconfigs.extend(
                    process_easyconfig(spec))

        # filter out external modules
        for ec in EasyConfigTest.parsed_easyconfigs:
            for dep in ec['dependencies'][:]:
                if dep.get('external_module', False):
                    ec['dependencies'].remove(dep)

        EasyConfigTest.ordered_specs = resolve_dependencies(
            EasyConfigTest.parsed_easyconfigs,
            modules_tool(),
            retain_all_deps=True)

    def test_dep_graph(self):
        """Unit test that builds a full dependency graph."""
        # pygraph dependencies required for constructing dependency graph are not available prior to Python 2.6
        if LooseVersion(
                sys.version) >= LooseVersion('2.6') and single_tests_ok:
            # temporary file for dep graph
            (hn, fn) = tempfile.mkstemp(suffix='.dot')
            os.close(hn)

            if EasyConfigTest.ordered_specs is None:
                self.process_all_easyconfigs()

            dep_graph(fn, EasyConfigTest.ordered_specs)

            remove_file(fn)
        else:
            print("(skipped dep graph test)")

    def test_conflicts(self):
        """Check whether any conflicts occur in software dependency graphs."""

        if not single_tests_ok:
            print("(skipped conflicts test)")
            return

        if EasyConfigTest.ordered_specs is None:
            self.process_all_easyconfigs()

        self.assertFalse(
            check_conflicts(EasyConfigTest.ordered_specs,
                            modules_tool(),
                            check_inter_ec_conflicts=False),
            "No conflicts detected")

    def check_dep_vars(self, dep, dep_vars):
        """Check whether available variants of a particular dependency are acceptable or not."""

        # 'guilty' until proven 'innocent'
        res = False

        # filter out wrapped Java versions
        # i.e. if the version of one is a prefix of the version of the other one (e.g. 1.8 & 1.8.0_181)
        if dep == 'Java':
            dep_vars_to_check = sorted(dep_vars.keys())

            retained_dep_vars = []

            while dep_vars_to_check:
                dep_var = dep_vars_to_check.pop()
                dep_var_version = dep_var.split(';')[0]

                # remove dep vars wrapped by current dep var
                dep_vars_to_check = [
                    x for x in dep_vars_to_check
                    if not x.startswith(dep_var_version + '.')
                ]

                retained_dep_vars = [
                    x for x in retained_dep_vars
                    if not x.startswith(dep_var_version + '.')
                ]

                retained_dep_vars.append(dep_var)

            for key in list(dep_vars.keys()):
                if key not in retained_dep_vars:
                    del dep_vars[key]

        # filter out binutils with empty versionsuffix which is used to build toolchain compiler
        if dep == 'binutils' and len(dep_vars) > 1:
            empty_vsuff_vars = [
                v for v in dep_vars.keys() if v.endswith('versionsuffix: ')
            ]
            if len(empty_vsuff_vars) == 1:
                dep_vars = dict((k, v) for (k, v) in dep_vars.items()
                                if k != empty_vsuff_vars[0])

        # multiple variants of HTSlib is OK as long as they are deps for a matching version of BCFtools
        if dep == 'HTSlib' and len(dep_vars) > 1:
            for key in list(dep_vars):
                ecs = dep_vars[key]
                # filter out HTSlib variants that are only used as dependency for BCFtools with same version
                htslib_ver = re.search('^version: (?P<ver>[^;]+);',
                                       key).group('ver')
                if all(
                        ec.startswith('BCFtools-%s-' % htslib_ver)
                        for ec in ecs):
                    dep_vars.pop(key)

        # multiple versions of Boost is OK as long as they are deps for a matching Boost.Python
        if dep == 'Boost' and len(dep_vars) > 1:
            for key in list(dep_vars):
                ecs = dep_vars[key]
                # filter out Boost variants that are only used as dependency for Boost.Python with same version
                boost_ver = re.search('^version: (?P<ver>[^;]+);',
                                      key).group('ver')
                if all(
                        ec.startswith('Boost.Python-%s-' % boost_ver)
                        for ec in ecs):
                    dep_vars.pop(key)

        # filter out FFTW and imkl with -serial versionsuffix which are used in non-MPI subtoolchains
        if dep in ['FFTW', 'imkl']:
            serial_vsuff_vars = [
                v for v in dep_vars.keys()
                if v.endswith('versionsuffix: -serial')
            ]
            if len(serial_vsuff_vars) == 1:
                dep_vars = dict((k, v) for (k, v) in dep_vars.items()
                                if k != serial_vsuff_vars[0])

        # for some dependencies, we allow exceptions for software that depends on a particular version,
        # as long as that's indicated by the versionsuffix
        if dep in ['ASE', 'Boost', 'Java', 'Lua', 'PLUMED', 'R'
                   ] and len(dep_vars) > 1:
            for key in list(dep_vars):
                dep_ver = re.search('^version: (?P<ver>[^;]+);',
                                    key).group('ver')
                # use version of Java wrapper rather than full Java version
                if dep == 'Java':
                    dep_ver = '.'.join(dep_ver.split('.')[:2])
                # filter out dep version if all easyconfig filenames using it include specific dep version
                if all(
                        re.search('-%s-%s' % (dep, dep_ver), v)
                        for v in dep_vars[key]):
                    dep_vars.pop(key)
                # always retain at least one dep variant
                if len(dep_vars) == 1:
                    break

            # filter R dep for a specific version of Python 2.x
            if dep == 'R' and len(dep_vars) > 1:
                for key in list(dep_vars):
                    if '; versionsuffix: -Python-2' in key:
                        dep_vars.pop(key)
                    # always retain at least one variant
                    if len(dep_vars) == 1:
                        break

        # filter out variants that are specific to a particular version of CUDA
        cuda_dep_vars = [v for v in dep_vars.keys() if '-CUDA' in v]
        if len(dep_vars) > len(cuda_dep_vars):
            for key in list(dep_vars):
                if re.search('; versionsuffix: .*-CUDA-[0-9.]+', key):
                    dep_vars.pop(key)

        # some software packages require an old version of a particular dependency
        old_dep_versions = {
            # libxc 2.x or 3.x is required by ABINIT, AtomPAW, CP2K, GPAW, PySCF, WIEN2k
            # (Qiskit depends on PySCF)
            'libxc': (r'[23]\.', [
                'ABINIT-', 'AtomPAW-', 'CP2K-', 'GPAW-', 'PySCF-', 'Qiskit-',
                'WIEN2k-'
            ]),
            # OPERA requires SAMtools 0.x
            'SAMtools':
            (r'0\.', ['ChimPipe-0.9.5', 'Cufflinks-2.2.1', 'OPERA-2.0.6']),
            # Kraken 1.x requires Jellyfish 1.x (Roary & metaWRAP depend on Kraken 1.x)
            'Jellyfish':
            (r'1\.', ['Kraken-1.', 'Roary-3.12.0', 'metaWRAP-1.2']),
            # EMAN2 2.3 requires Boost(.Python) 1.64.0
            'Boost': ('1.64.0;', ['Boost.Python-1.64.0-', 'EMAN2-2.3-']),
            'Boost.Python': ('1.64.0;', ['EMAN2-2.3-']),
        }
        if dep in old_dep_versions and len(dep_vars) > 1:
            for key in list(dep_vars):
                version_pattern, parents = old_dep_versions[dep]
                # filter out known old dependency versions
                if re.search('^version: %s' % version_pattern, key):
                    # only filter if the easyconfig using this dep variants is known
                    if all(
                            any(x.startswith(p) for p in parents)
                            for x in dep_vars[key]):
                        dep_vars.pop(key)

        # only single variant is always OK
        if len(dep_vars) == 1:
            res = True

        elif len(dep_vars) == 2 and dep in ['Python', 'Tkinter']:
            # for Python & Tkinter, it's OK to have on 2.x and one 3.x version
            v2_dep_vars = [
                x for x in dep_vars.keys() if x.startswith('version: 2.')
            ]
            v3_dep_vars = [
                x for x in dep_vars.keys() if x.startswith('version: 3.')
            ]
            if len(v2_dep_vars) == 1 and len(v3_dep_vars) == 1:
                res = True

        # two variants is OK if one is for Python 2.x and the other is for Python 3.x (based on versionsuffix)
        elif len(dep_vars) == 2:
            py2_dep_vars = [
                x for x in dep_vars.keys()
                if '; versionsuffix: -Python-2.' in x
            ]
            py3_dep_vars = [
                x for x in dep_vars.keys()
                if '; versionsuffix: -Python-3.' in x
            ]
            if len(py2_dep_vars) == 1 and len(py3_dep_vars) == 1:
                res = True

        return res

    def test_check_dep_vars(self):
        """Test check_dep_vars utility method."""

        # one single dep version: OK
        self.assertTrue(
            self.check_dep_vars(
                'testdep', {
                    'version: 1.2.3; versionsuffix:':
                    ['foo-1.2.3.eb', 'bar-4.5.6.eb'],
                }))
        self.assertTrue(
            self.check_dep_vars(
                'testdep', {
                    'version: 1.2.3; versionsuffix: -test':
                    ['foo-1.2.3.eb', 'bar-4.5.6.eb'],
                }))

        # two or more dep versions (no special case: not OK)
        self.assertFalse(
            self.check_dep_vars(
                'testdep', {
                    'version: 1.2.3; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 4.5.6; versionsuffix:': ['bar-4.5.6.eb'],
                }))
        self.assertFalse(
            self.check_dep_vars(
                'testdep', {
                    'version: 0.0; versionsuffix:': ['foobar-0.0.eb'],
                    'version: 1.2.3; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 4.5.6; versionsuffix:': ['bar-4.5.6.eb'],
                }))

        # Java is a special case, with wrapped Java versions
        self.assertTrue(
            self.check_dep_vars(
                'Java', {
                    'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
                }))
        # two Java wrappers is not OK
        self.assertFalse(
            self.check_dep_vars(
                'Java', {
                    'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 11.0.2; versionsuffix:': ['bar-4.5.6.eb'],
                    'version: 11; versionsuffix:': ['bar-4.5.6.eb'],
                }))
        # OK to have two or more wrappers if versionsuffix is used to indicate exception
        self.assertTrue(
            self.check_dep_vars(
                'Java', {
                    'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 11.0.2; versionsuffix:':
                    ['bar-4.5.6-Java-11.eb'],
                    'version: 11; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
                }))
        # versionsuffix must be there for all easyconfigs to indicate exception
        self.assertFalse(
            self.check_dep_vars(
                'Java', {
                    'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 11.0.2; versionsuffix:':
                    ['bar-4.5.6-Java-11.eb', 'bar-4.5.6.eb'],
                    'version: 11; versionsuffix:':
                    ['bar-4.5.6-Java-11.eb', 'bar-4.5.6.eb'],
                }))
        self.assertTrue(
            self.check_dep_vars(
                'Java', {
                    'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 11.0.2; versionsuffix:':
                    ['bar-4.5.6-Java-11.eb'],
                    'version: 11; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
                    'version: 12.1.6; versionsuffix:':
                    ['foobar-0.0-Java-12.eb'],
                    'version: 12; versionsuffix:': ['foobar-0.0-Java-12.eb'],
                }))

        # strange situation: odd number of Java versions
        # not OK: two Java wrappers (and no versionsuffix to indicate exception)
        self.assertFalse(
            self.check_dep_vars(
                'Java', {
                    'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 11; versionsuffix:': ['bar-4.5.6.eb'],
                }))
        # OK because of -Java-11 versionsuffix
        self.assertTrue(
            self.check_dep_vars(
                'Java', {
                    'version: 1.8.0_221; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 11; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
                }))
        # not OK: two Java wrappers (and no versionsuffix to indicate exception)
        self.assertFalse(
            self.check_dep_vars(
                'Java', {
                    'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 11.0.2; versionsuffix:': ['bar-4.5.6.eb'],
                    'version: 11; versionsuffix:': ['bar-4.5.6.eb'],
                }))
        # OK because of -Java-11 versionsuffix
        self.assertTrue(
            self.check_dep_vars(
                'Java', {
                    'version: 1.8; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 11.0.2; versionsuffix:':
                    ['bar-4.5.6-Java-11.eb'],
                    'version: 11; versionsuffix:': ['bar-4.5.6-Java-11.eb'],
                }))

        # two different versions of Boost is not OK
        self.assertFalse(
            self.check_dep_vars(
                'Boost', {
                    'version: 1.64.0; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 1.70.0; versionsuffix:': ['foo-2.3.4.eb'],
                }))

        # a different Boost version that is only used as dependency for a matching Boost.Python is fine
        self.assertTrue(
            self.check_dep_vars(
                'Boost', {
                    'version: 1.64.0; versionsuffix:':
                    ['Boost.Python-1.64.0-gompi-2019a.eb'],
                    'version: 1.70.0; versionsuffix:': ['foo-2.3.4.eb'],
                }))
        self.assertTrue(
            self.check_dep_vars(
                'Boost', {
                    'version: 1.64.0; versionsuffix:':
                    ['Boost.Python-1.64.0-gompi-2018b.eb'],
                    'version: 1.66.0; versionsuffix:':
                    ['Boost.Python-1.66.0-gompi-2019a.eb'],
                    'version: 1.70.0; versionsuffix:': ['foo-2.3.4.eb'],
                }))
        self.assertFalse(
            self.check_dep_vars(
                'Boost', {
                    'version: 1.64.0; versionsuffix:':
                    ['Boost.Python-1.64.0-gompi-2019a.eb'],
                    'version: 1.66.0; versionsuffix:': ['foo-1.2.3.eb'],
                    'version: 1.70.0; versionsuffix:': ['foo-2.3.4.eb'],
                }))

        self.assertTrue(
            self.check_dep_vars(
                'Boost', {
                    'version: 1.63.0; versionsuffix: -Python-2.7.14':
                    ['EMAN2-2.21a-foss-2018a-Python-2.7.14-Boost-1.63.0.eb'],
                    'version: 1.64.0; versionsuffix:':
                    ['Boost.Python-1.64.0-gompi-2018a.eb'],
                    'version: 1.66.0; versionsuffix:':
                    ['BLAST+-2.7.1-foss-2018a.eb'],
                }))

        self.assertTrue(
            self.check_dep_vars(
                'Boost', {
                    'version: 1.64.0; versionsuffix:': [
                        'Boost.Python-1.64.0-gompi-2019a.eb',
                        'EMAN2-2.3-foss-2019a-Python-2.7.15.eb',
                    ],
                    'version: 1.70.0; versionsuffix:': [
                        'BLAST+-2.9.0-gompi-2019a.eb',
                        'Boost.Python-1.70.0-gompi-2019a.eb',
                    ],
                }))

    def test_dep_versions_per_toolchain_generation(self):
        """
        Check whether there's only one dependency version per toolchain generation actively used.
        This is enforced to try and limit the chance of running into conflicts when multiple modules built with
        the same toolchain are loaded together.
        """
        if EasyConfigTest.ordered_specs is None:
            self.process_all_easyconfigs()

        def get_deps_for(ec):
            """Get list of (direct) dependencies for specified easyconfig."""
            deps = []
            for dep in ec['ec']['dependencies']:
                dep_mod_name = dep['full_mod_name']
                deps.append((dep['name'], dep['version'], dep['versionsuffix'],
                             dep_mod_name))
                res = [
                    x for x in EasyConfigTest.ordered_specs
                    if x['full_mod_name'] == dep_mod_name
                ]
                if len(res) == 1:
                    deps.extend(get_deps_for(res[0]))
                else:
                    raise EasyBuildError(
                        "Failed to find %s in ordered list of easyconfigs",
                        dep_mod_name)

            return deps

        # some software also follows <year>{a,b} versioning scheme,
        # which throws off the pattern matching done below for toolchain versions
        false_positives_regex = re.compile('^MATLAB-Engine-20[0-9][0-9][ab]')

        # restrict to checking dependencies of easyconfigs using common toolchains (start with 2018a)
        # and GCCcore subtoolchain for common toolchains, starting with GCCcore 7.x
        for pattern in [
                '201[89][ab]', '20[2-9][0-9][ab]', 'GCCcore-[7-9]\.[0-9]'
        ]:
            all_deps = {}
            regex = re.compile('^.*-(?P<tc_gen>%s).*\.eb$' % pattern)

            # collect variants for all dependencies of easyconfigs that use a toolchain that matches
            for ec in EasyConfigTest.ordered_specs:
                ec_file = os.path.basename(ec['spec'])

                # take into account software which also follows a <year>{a,b} versioning scheme
                ec_file = false_positives_regex.sub('', ec_file)

                res = regex.match(ec_file)
                if res:
                    tc_gen = res.group('tc_gen')
                    all_deps_tc_gen = all_deps.setdefault(tc_gen, {})
                    for dep_name, dep_ver, dep_versuff, dep_mod_name in get_deps_for(
                            ec):
                        dep_variants = all_deps_tc_gen.setdefault(dep_name, {})
                        # a variant is defined by version + versionsuffix
                        variant = "version: %s; versionsuffix: %s" % (
                            dep_ver, dep_versuff)
                        # keep track of which easyconfig this is a dependency
                        dep_variants.setdefault(variant, set()).add(ec_file)

            # check which dependencies have more than 1 variant
            multi_dep_vars, multi_dep_vars_msg = [], ''
            for tc_gen in sorted(all_deps.keys()):
                for dep in sorted(all_deps[tc_gen].keys()):
                    dep_vars = all_deps[tc_gen][dep]
                    if not self.check_dep_vars(dep, dep_vars):
                        multi_dep_vars.append(dep)
                        multi_dep_vars_msg += "\nfound %s variants of '%s' dependency " % (
                            len(dep_vars), dep)
                        multi_dep_vars_msg += "in easyconfigs using '%s' toolchain generation\n* " % tc_gen
                        multi_dep_vars_msg += '\n* '.join(
                            "%s as dep for %s" % v
                            for v in sorted(dep_vars.items()))
                        multi_dep_vars_msg += '\n'

            error_msg = "No multi-variant deps found for '%s' easyconfigs:\n%s" % (
                regex.pattern, multi_dep_vars_msg)
            self.assertFalse(multi_dep_vars, error_msg)

    def test_sanity_check_paths(self):
        """Make sure specified sanity check paths adher to the requirements."""

        if EasyConfigTest.ordered_specs is None:
            self.process_all_easyconfigs()

        for ec in EasyConfigTest.parsed_easyconfigs:
            ec_scp = ec['ec']['sanity_check_paths']
            if ec_scp != {}:
                # if sanity_check_paths is specified (i.e., non-default), it must adher to the requirements
                # both 'files' and 'dirs' keys, both with list values and with at least one a non-empty list
                error_msg = "sanity_check_paths for %s does not meet requirements: %s" % (
                    ec['spec'], ec_scp)
                self.assertEqual(sorted(ec_scp.keys()), ['dirs', 'files'],
                                 error_msg)
                self.assertTrue(isinstance(ec_scp['dirs'], list), error_msg)
                self.assertTrue(isinstance(ec_scp['files'], list), error_msg)
                self.assertTrue(ec_scp['dirs'] or ec_scp['files'], error_msg)

    def test_easyconfig_locations(self):
        """Make sure all easyconfigs files are in the right location."""
        easyconfig_dirs_regex = re.compile(
            r'/easybuild/easyconfigs/[0a-z]/[^/]+$')
        topdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
        for (dirpath, _, filenames) in os.walk(topdir):
            # ignore git/svn dirs & archived easyconfigs
            if '/.git/' in dirpath or '/.svn/' in dirpath or '__archive__' in dirpath:
                continue
            # check whether list of .eb files is non-empty
            easyconfig_files = [fn for fn in filenames if fn.endswith('eb')]
            if easyconfig_files:
                # check whether path matches required pattern
                if not easyconfig_dirs_regex.search(dirpath):
                    # only exception: TEMPLATE.eb
                    if not (dirpath.endswith('/easybuild/easyconfigs')
                            and filenames == ['TEMPLATE.eb']):
                        self.assertTrue(
                            False,
                            "List of easyconfig files in %s is empty: %s" %
                            (dirpath, filenames))

    def check_sha256_checksums(self, changed_ecs):
        """Make sure changed easyconfigs have SHA256 checksums in place."""

        # list of software for which checksums can not be required,
        # e.g. because 'source' files need to be constructed manually
        whitelist = ['Kent_tools-*', 'MATLAB-*', 'OCaml-*']

        # the check_sha256_checksums function (again) creates an EasyBlock instance
        # for easyconfigs using the Bundle easyblock, this is a problem because the 'sources' easyconfig parameter
        # is updated in place (sources for components are added the 'parent' sources) in Bundle's __init__;
        # therefore, we need to reset 'sources' to an empty list here if Bundle is used...
        # likewise for 'checksums'
        for ec in changed_ecs:
            if ec['easyblock'] == 'Bundle':
                ec['sources'] = []
                ec['checksums'] = []

        # filter out deprecated easyconfigs
        retained_changed_ecs = []
        for ec in changed_ecs:
            if not ec['deprecated']:
                retained_changed_ecs.append(ec)

        checksum_issues = check_sha256_checksums(retained_changed_ecs,
                                                 whitelist=whitelist)
        self.assertTrue(
            len(checksum_issues) == 0,
            "No checksum issues:\n%s" % '\n'.join(checksum_issues))

    def check_python_packages(self, changed_ecs):
        """Several checks for easyconfigs that install (bundles of) Python packages."""

        # MATLAB-Engine, PyTorch do not support installation with 'pip'
        whitelist_pip = ['MATLAB-Engine-*', 'PyTorch-*']

        failing_checks = []

        for ec in changed_ecs:

            ec_fn = os.path.basename(ec.path)
            easyblock = ec.get('easyblock')
            exts_defaultclass = ec.get('exts_defaultclass')

            download_dep_fail = ec.get('download_dep_fail')
            exts_download_dep_fail = ec.get('exts_download_dep_fail')
            use_pip = ec.get('use_pip')

            # download_dep_fail should be set when using PythonPackage
            if easyblock == 'PythonPackage':
                if download_dep_fail is None:
                    failing_checks.append("'download_dep_fail' set in %s" %
                                          ec_fn)

            # use_pip should be set when using PythonPackage or PythonBundle (except for whitelisted easyconfigs)
            if easyblock in ['PythonBundle', 'PythonPackage']:
                if use_pip is None and not any(
                        re.match(regex, ec_fn) for regex in whitelist_pip):
                    failing_checks.append("'use_pip' set in %s" % ec_fn)

            # download_dep_fail is enabled automatically in PythonBundle easyblock, so shouldn't be set
            if easyblock == 'PythonBundle':
                if download_dep_fail or exts_download_dep_fail:
                    fail = "'*download_dep_fail' set in %s (shouldn't, since PythonBundle easyblock is used)" % ec_fn
                    failing_checks.append(fail)

            elif exts_defaultclass == 'PythonPackage':
                # bundle of Python packages should use PythonBundle
                if easyblock == 'Bundle':
                    fail = "'PythonBundle' easyblock is used for bundle of Python packages in %s" % ec_fn
                    failing_checks.append(fail)
                else:
                    # both download_dep_fail and use_pip should be set via exts_default_options
                    # when installing Python packages as extensions
                    exts_default_options = ec.get('exts_default_options', {})
                    for key in ['download_dep_fail', 'use_pip']:
                        if exts_default_options.get(key) is None:
                            failing_checks.append(
                                "'%s' set in exts_default_options in %s" %
                                (key, ec_fn))

            # if Python is a dependency, that should be reflected in the versionsuffix
            # Tkinter is an exception, since its version always matches the Python version anyway
            if any(dep['name'] == 'Python'
                   for dep in ec['dependencies']) and ec.name != 'Tkinter':
                if not re.search(r'-Python-[23]\.[0-9]+\.[0-9]+',
                                 ec['versionsuffix']):
                    failing_checks.append(
                        "'-Python-%%(pyver)s' included in versionsuffix in %s"
                        % ec_fn)

        self.assertFalse(failing_checks, '\n'.join(failing_checks))

    def check_sanity_check_paths(self, changed_ecs):
        """Make sure a custom sanity_check_paths value is specified for easyconfigs that use a generic easyblock."""

        # PythonBundle & PythonPackage already have a decent customised sanity_check_paths
        # BuildEnv, ModuleRC and Toolchain easyblocks doesn't install anything so there is nothing to check.
        whitelist = [
            'CrayToolchain', 'ModuleRC', 'PythonBundle', 'PythonPackage',
            'Toolchain', 'BuildEnv'
        ]
        # GCC is just a bundle of GCCcore+binutils
        bundles_whitelist = ['GCC']

        failing_checks = []

        for ec in changed_ecs:

            easyblock = ec.get('easyblock')

            if is_generic_easyblock(
                    easyblock) and not ec.get('sanity_check_paths'):
                if easyblock in whitelist or (easyblock == 'Bundle' and
                                              ec['name'] in bundles_whitelist):
                    pass
                else:
                    ec_fn = os.path.basename(ec.path)
                    failing_checks.append(
                        "No custom sanity_check_paths found in %s" % ec_fn)

        self.assertFalse(failing_checks, '\n'.join(failing_checks))

    def check_https(self, changed_ecs):
        """Make sure https:// URL is used (if it exists) for homepage/source_urls (rather than http://)."""

        whitelist = [
            'Kaiju',  # invalid certificate at https://kaiju.binf.ku.dk
            'libxml2',  # https://xmlsoft.org works, but invalid certificate
            'p4vasp',  # https://www.p4vasp.at doesn't work
            'ITSTool',  # https://itstool.org/ doesn't work
            'UCX-',  # bad certificate for https://www.openucx.org
        ]

        http_regex = re.compile('http://[^"\'\n]+', re.M)

        failing_checks = []
        for ec in changed_ecs:
            ec_fn = os.path.basename(ec.path)

            # skip whitelisted easyconfigs
            if any(ec_fn.startswith(x) for x in whitelist):
                continue

            # ignore commented out lines in easyconfig files when checking for http:// URLs
            ec_txt = '\n'.join(l for l in ec.rawtxt.split('\n')
                               if not l.startswith('#'))

            for http_url in http_regex.findall(ec_txt):
                https_url = http_url.replace('http://', 'https://')
                try:
                    https_url_works = bool(urlopen(https_url))
                except Exception:
                    https_url_works = False

                if https_url_works:
                    failing_checks.append(
                        "Found http:// URL in %s, should be https:// : %s" %
                        (ec_fn, http_url))

        self.assertFalse(failing_checks, '\n'.join(failing_checks))

    def test_changed_files_pull_request(self):
        """Specific checks only done for the (easyconfig) files that were changed in a pull request."""

        # $TRAVIS_PULL_REQUEST should be a PR number, otherwise we're not running tests for a PR
        travis_pr_test = re.match(
            '^[0-9]+$', os.environ.get('TRAVIS_PULL_REQUEST', '(none)'))

        # when testing a PR in GitHub Actions, $GITHUB_EVENT_NAME will be set to 'pull_request'
        github_pr_test = os.environ.get('GITHUB_EVENT_NAME') == 'pull_request'

        if travis_pr_test or github_pr_test:

            # target branch should be anything other than 'master';
            # usually is 'develop', but could also be a release branch like '3.7.x'
            if travis_pr_test:
                target_branch = os.environ.get('TRAVIS_BRANCH', None)
            else:
                target_branch = os.environ.get('GITHUB_BASE_REF', None)

            if target_branch is None:
                self.assertTrue(
                    False,
                    "Failed to determine target branch for current pull request."
                )

            if target_branch != 'master':

                if not EasyConfigTest.parsed_easyconfigs:
                    self.process_all_easyconfigs()

                # relocate to top-level directory of repository to run 'git diff' command
                top_dir = os.path.dirname(
                    os.path.dirname(get_paths_for('easyconfigs')[0]))
                cwd = change_dir(top_dir)

                # get list of changed easyconfigs
                cmd = "git diff --name-only --diff-filter=AM %s...HEAD" % target_branch
                out, ec = run_cmd(cmd, simple=False)
                changed_ecs_filenames = [
                    os.path.basename(f) for f in out.strip().split('\n')
                    if f.endswith('.eb')
                ]
                print("\nList of changed easyconfig files in this PR: %s" %
                      '\n'.join(changed_ecs_filenames))

                change_dir(cwd)

                # grab parsed easyconfigs for changed easyconfig files
                changed_ecs = []
                for ec_fn in changed_ecs_filenames:
                    match = None
                    for ec in EasyConfigTest.parsed_easyconfigs:
                        if os.path.basename(ec['spec']) == ec_fn:
                            match = ec['ec']
                            break

                    if match:
                        changed_ecs.append(match)
                    else:
                        # if no easyconfig is found, it's possible some archived easyconfigs were touched in the PR...
                        # so as a last resort, try to find the easyconfig file in __archive__
                        easyconfigs_path = get_paths_for("easyconfigs")[0]
                        specs = glob.glob('%s/__archive__/*/*/%s' %
                                          (easyconfigs_path, ec_fn))
                        if len(specs) == 1:
                            ec = process_easyconfig(specs[0])[0]
                            changed_ecs.append(ec['ec'])
                        else:
                            error_msg = "Failed to find parsed easyconfig for %s" % ec_fn
                            error_msg += " (and could not isolate it in easyconfigs archive either)"
                            self.assertTrue(False, error_msg)

                # run checks on changed easyconfigs
                self.check_sha256_checksums(changed_ecs)
                self.check_python_packages(changed_ecs)
                self.check_sanity_check_paths(changed_ecs)
                self.check_https(changed_ecs)

    def test_zzz_cleanup(self):
        """Dummy test to clean up global temporary directory."""
        shutil.rmtree(self.TMPDIR)
Ejemplo n.º 14
0
 def setUp(self):
     """Setup test."""
     self.log = fancylogger.getLogger("EasyblocksInitTest", fname=False)
     fd, self.eb_file = tempfile.mkstemp(prefix='easyblocks_init_test_',
                                         suffix='.eb')
     os.close(fd)
Ejemplo n.º 15
0
:author: Riccardo Murri (University of Zurich)
:author: Kenneth Hoste (Ghent University)
"""
from distutils.version import LooseVersion
import os
from time import gmtime, strftime
import time

from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError, print_msg, print_warning
from easybuild.tools.config import JOB_DEPS_TYPE_ABORT_ON_ERROR, JOB_DEPS_TYPE_ALWAYS_RUN, build_option
from easybuild.tools.job.backend import JobBackend
from easybuild.tools.utilities import only_if_module_is_available

_log = fancylogger.getLogger('gc3pie', fname=False)

try:
    import gc3libs
    import gc3libs.exceptions
    from gc3libs import Application, Run, create_engine
    from gc3libs.quantity import hours as hr
    from gc3libs.workflow import AbortOnError, DependentTaskCollection

    # inject EasyBuild logger into GC3Pie
    gc3libs.log = fancylogger.getLogger('gc3pie', fname=False)
    # make handling of log.error compatible with stdlib logging
    gc3libs.log.raiseError = False

    # instruct GC3Pie to not ignore errors, but raise exceptions instead
    gc3libs.UNIGNORE_ALL_ERRORS = True
Ejemplo n.º 16
0
# #
"""
Hook support.

:author: Kenneth Hoste (Ghent University)
"""
import difflib
import imp
import os

from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError, print_msg
from easybuild.tools.config import build_option


_log = fancylogger.getLogger('hooks', fname=False)

BUILD_STEP = 'build'
CLEANUP_STEP = 'cleanup'
CONFIGURE_STEP = 'configure'
EXTENSIONS_STEP = 'extensions'
FETCH_STEP = 'fetch'
INSTALL_STEP = 'install'
MODULE_STEP = 'module'
PACKAGE_STEP = 'package'
PATCH_STEP = 'patch'
PERMISSIONS_STEP = 'permissions'
POSTITER_STEP = 'postiter'
POSTPROC_STEP = 'postproc'
PREPARE_STEP = 'prepare'
READY_STEP = 'ready'
Ejemplo n.º 17
0
"""
import copy
import os
import tempfile
from distutils.version import LooseVersion

from easybuild.base import fancylogger
import easybuild.tools.environment as env
import easybuild.tools.toolchain as toolchain
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.config import build_option
from easybuild.tools.filetools import write_file
from easybuild.tools.toolchain.constants import COMPILER_VARIABLES, MPI_COMPILER_TEMPLATE, SEQ_COMPILER_TEMPLATE
from easybuild.tools.toolchain.toolchain import Toolchain

_log = fancylogger.getLogger('tools.toolchain.mpi', fname=False)


def get_mpi_cmd_template(mpi_family, params, mpi_version=None):
    """
    Return template for MPI command, for specified MPI family.

    :param mpi_family: MPI family to use to determine MPI command template
    """

    params = copy.deepcopy(params)

    mpi_cmd_template = build_option('mpi_cmd_template')
    if mpi_cmd_template:
        _log.info("Using specified template for MPI commands: %s",
                  mpi_cmd_template)
Ejemplo n.º 18
0
from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.options import set_tmpdir

import test.easyblocks.easyblock_specific as e
import test.easyblocks.general as g
import test.easyblocks.init_easyblocks as i
import test.easyblocks.module as m

# initialize logger for all the unit tests
fd, log_fn = tempfile.mkstemp(prefix='easybuild-easyblocks-tests-', suffix='.log')
os.close(fd)
os.remove(log_fn)
fancylogger.logToFile(log_fn)
log = fancylogger.getLogger()
log.setLevelName('DEBUG')

try:
    tmpdir = set_tmpdir(raise_error=True)
except EasyBuildError as err:
    sys.stderr.write("No execution rights on temporary files, specify another location via $TMPDIR: %s\n" % err)
    sys.exit(1)

os.environ['EASYBUILD_TMP_LOGDIR'] = tempfile.mkdtemp(prefix='easyblocks_test_')

# call suite() for each module and then run them all
SUITE = unittest.TestSuite([x.suite() for x in [g, i, m, e]])
res = unittest.TextTestRunner().run(SUITE)

fancylogger.logToFile(log_fn, enable=False)
Ejemplo n.º 19
0
:author: Caroline De Brouwer (Ghent University)
:author: Kenneth Hoste (Ghent University)
"""

import os
import platform
from distutils.version import LooseVersion

from easybuild.base import fancylogger
from easybuild.framework.easyconfig.format.format import EasyConfigFormat
from easybuild.framework.easyconfig.format.pyheaderconfigobj import build_easyconfig_constants_dict
from easybuild.tools.py2vs3 import string_type
from easybuild.tools.utilities import INDENT_4SPACES, only_if_module_is_available, quote_str

_log = fancylogger.getLogger('easyconfig.format.yeb', fname=False)

YAML_DIR = r'%YAML'
YAML_SEP = '---'
YEB_FORMAT_EXTENSION = '.yeb'
YAML_SPECIAL_CHARS = set(":{}[],&*#?|-<>=!%@\\")


def yaml_join(loader, node):
    """
    defines custom YAML join function.
    see http://stackoverflow.com/questions/5484016/
        how-can-i-do-string-concatenation-or-string-replacement-in-yaml/23212524#23212524
    :param loader: the YAML Loader
    :param node: the YAML (sequence) node
    """
Ejemplo n.º 20
0
:author: Fotis Georgatos (Uni.Lu, NTUA)
"""
import getpass
import os
import socket
import tempfile
import time
from easybuild.base import fancylogger

from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.filetools import rmtree2
from easybuild.tools.repository.filerepo import FileRepository
from easybuild.tools.utilities import only_if_module_is_available
from easybuild.tools.version import VERSION

_log = fancylogger.getLogger('gitrepo', fname=False)

# optional Python packages, these might be missing
# failing imports are just ignored
# a NameError should be catched where these are used

# GitPython (http://gitorious.org/git-python)
try:
    import git
    from git import GitCommandError
    HAVE_GIT = True
except ImportError:
    _log.debug('Failed to import git module')
    HAVE_GIT = False

Ejemplo n.º 21
0
    def setUp(self):
        """Set up testcase."""
        super(EnhancedTestCase, self).setUp()

        # make sure option parser doesn't pick up any cmdline arguments/options
        while len(sys.argv) > 1:
            sys.argv.pop()

        # keep track of log handlers
        log = fancylogger.getLogger(fname=False)
        self.orig_log_handlers = log.handlers[:]

        log.info("setting up test %s" % self.id())

        self.orig_tmpdir = tempfile.gettempdir()
        # use a subdirectory for this test (which we can clean up easily after the test completes)
        self.test_prefix = set_tmpdir()

        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
        fd, self.logfile = tempfile.mkstemp(suffix='.log', prefix='eb-test-')
        os.close(fd)
        self.cwd = os.getcwd()

        # keep track of original environment to restore
        self.orig_environ = copy.deepcopy(os.environ)

        # keep track of original environment/Python search path to restore
        self.orig_sys_path = sys.path[:]

        testdir = os.path.dirname(os.path.abspath(__file__))

        self.test_sourcepath = os.path.join(testdir, 'sandbox', 'sources')
        os.environ['EASYBUILD_SOURCEPATH'] = self.test_sourcepath
        os.environ['EASYBUILD_PREFIX'] = self.test_prefix
        self.test_buildpath = tempfile.mkdtemp()
        os.environ['EASYBUILD_BUILDPATH'] = self.test_buildpath
        self.test_installpath = tempfile.mkdtemp()
        os.environ['EASYBUILD_INSTALLPATH'] = self.test_installpath

        # make sure that the tests only pick up easyconfigs provided with the tests
        os.environ['EASYBUILD_ROBOT_PATHS'] = os.path.join(testdir, 'easyconfigs', 'test_ecs')

        # make sure no deprecated behaviour is being triggered (unless intended by the test)
        # trip *all* log.deprecated statements by setting deprecation version ridiculously high
        self.orig_current_version = eb_build_log.CURRENT_VERSION
        os.environ['EASYBUILD_DEPRECATED'] = '10000000'

        init_config()

        import easybuild
        # try to import easybuild.easyblocks(.generic) packages
        # it's OK if it fails here, but important to import first before fiddling with sys.path
        try:
            import easybuild.easyblocks
            import easybuild.easyblocks.generic
        except ImportError:
            pass

        # add sandbox to Python search path, update namespace packages
        sys.path.append(os.path.join(testdir, 'sandbox'))

        # required to make sure the 'easybuild' dir in the sandbox is picked up;
        # this relates to the other 'reload' statements below
        reload(easybuild)

        # required to 'reset' easybuild.tools.module_naming_scheme namespace
        reload(easybuild.tools)
        reload(easybuild.tools.module_naming_scheme)

        # remove any entries in Python search path that seem to provide easyblocks (except the sandbox)
        for path in sys.path[:]:
            if os.path.exists(os.path.join(path, 'easybuild', 'easyblocks', '__init__.py')):
                if not os.path.samefile(path, os.path.join(testdir, 'sandbox')):
                    sys.path.remove(path)

        # hard inject location to (generic) test easyblocks into Python search path
        # only prepending to sys.path is not enough due to 'pkgutil.extend_path' in easybuild/easyblocks/__init__.py
        easybuild.__path__.insert(0, os.path.join(testdir, 'sandbox', 'easybuild'))
        import easybuild.easyblocks
        test_easyblocks_path = os.path.join(testdir, 'sandbox', 'easybuild', 'easyblocks')
        easybuild.easyblocks.__path__.insert(0, test_easyblocks_path)
        reload(easybuild.easyblocks)

        import easybuild.easyblocks.generic
        test_easyblocks_path = os.path.join(test_easyblocks_path, 'generic')
        easybuild.easyblocks.generic.__path__.insert(0, test_easyblocks_path)
        reload(easybuild.easyblocks.generic)

        # save values of $PATH & $PYTHONPATH, so they can be restored later
        # this is important in case EasyBuild was installed as a module, since that module may be unloaded,
        # for example due to changes to $MODULEPATH in case EasyBuild was installed in a module hierarchy
        # cfr. https://github.com/easybuilders/easybuild-framework/issues/1685
        self.env_path = os.environ.get('PATH')
        self.env_pythonpath = os.environ.get('PYTHONPATH')

        self.modtool = modules_tool()
        self.reset_modulepath([os.path.join(testdir, 'modules')])
        reset_module_caches()
Ejemplo n.º 22
0
"""
import copy
import re
import sys

import easybuild.tools.toolchain
from easybuild.base import fancylogger
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.toolchain.toolchain import Toolchain
from easybuild.tools.utilities import get_subclasses, import_available_modules, nub

TC_CONST_PREFIX = 'TC_CONSTANT_'

_initial_toolchain_instances = {}

_log = fancylogger.getLogger("toolchain.utilities")


def search_toolchain(name):
    """
    Obtain a Toolchain instance for the toolchain with specified name, next to a list of available toolchains.
    :param name: toolchain name
    :return: Toolchain instance (or None), found_toolchains
    """

    package = easybuild.tools.toolchain
    check_attr_name = '%s_PROCESSED' % TC_CONST_PREFIX

    if not hasattr(package, check_attr_name) or not getattr(
            package, check_attr_name):
        # import all available toolchains, so we know about them
Ejemplo n.º 23
0
# these are imported just to we can reload them later
import easybuild.tools.module_naming_scheme
import easybuild.toolchains
import easybuild.toolchains.compiler
import easybuild.toolchains.fft
import easybuild.toolchains.linalg
import easybuild.toolchains.mpi
# importing easyblocks namespace may fail if easybuild-easyblocks is not available
# for now, we don't really care
try:
    import easybuild.easyblocks
    import easybuild.easyblocks.generic
except ImportError:
    pass

_log = fancylogger.getLogger('tools.include', fname=False)

# body for __init__.py file in package directory, which takes care of making sure the package can be distributed
# across multiple directories
PKG_INIT_BODY = """
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
"""

# more extensive __init__.py specific to easybuild.easyblocks package;
# this is required because of the way in which the easyblock Python modules are organised in the easybuild-easyblocks
# repository, i.e. in first-letter subdirectories
EASYBLOCKS_PKG_INIT_BODY = """
import pkgutil

# extend path so Python finds our easyblocks in the subdirectories where they are located
subdirs = [chr(char) for char in range(ord('a'), ord('z') + 1)] + ['0']
:author: Kenneth Hoste (Ghent University)
"""
import copy
import re
import sys

from easybuild.base import fancylogger
from easybuild.framework.easyconfig.constants import EASYCONFIG_CONSTANTS
from easybuild.framework.easyconfig.format.format import get_format_version, EasyConfigFormat
from easybuild.framework.easyconfig.licenses import EASYCONFIG_LICENSES_DICT
from easybuild.framework.easyconfig.templates import TEMPLATE_CONSTANTS
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.configobj import ConfigObj
from easybuild.tools.systemtools import get_shared_lib_ext

_log = fancylogger.getLogger('easyconfig.format.pyheaderconfigobj',
                             fname=False)


def build_easyconfig_constants_dict():
    """Make a dictionary with all constants that can be used"""
    all_consts = [
        ('TEMPLATE_CONSTANTS', dict([(x[0], x[1])
                                     for x in TEMPLATE_CONSTANTS])),
        ('EASYCONFIG_CONSTANTS',
         dict([(key, val[0]) for key, val in EASYCONFIG_CONSTANTS.items()])),
        ('EASYCONFIG_LICENSES',
         dict([(klass().name, name)
               for name, klass in EASYCONFIG_LICENSES_DICT.items()])),
    ]
    err = []
    const_dict = {}
Ejemplo n.º 25
0
 def __init__(self, *args, **kwargs):
     super(Variables, self).__init__(*args, **kwargs)
     self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
Ejemplo n.º 26
0
 def __init__(self):
     """initialize logger."""
     self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
:author: Kenneth Hoste (Ghent University)
"""
import copy
import os

from easybuild.base import fancylogger
from easybuild.framework.easyblock import EasyBlock
from easybuild.framework.easyconfig import CUSTOM
from easybuild.framework.extension import Extension
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.filetools import change_dir, extract_file
from easybuild.tools.utilities import remove_unwanted_chars, trace_msg


_log = fancylogger.getLogger('extensioneasyblock', fname=False)


class ExtensionEasyBlock(EasyBlock, Extension):
    """
    Install an extension as a separate module, or as an extension.

    Deriving classes should implement the following functions:
    * required EasyBlock functions:
      - configure_step
      - build_step
      - install_step
    * required Extension functions
      - run
    """
Ejemplo n.º 28
0
"""
Easyconfig constants module that provides all constants that can
be used within an Easyconfig file.

:author: Stijn De Weirdt (Ghent University)
:author: Kenneth Hoste (Ghent University)
"""
import os
import platform

from easybuild.base import fancylogger
from easybuild.tools.systemtools import get_os_name, get_os_type, get_os_version


_log = fancylogger.getLogger('easyconfig.constants', fname=False)


EXTERNAL_MODULE_MARKER = 'EXTERNAL_MODULE'

# constants that can be used in easyconfig
EASYCONFIG_CONSTANTS = {
    'ARCH': (platform.uname()[4], "CPU architecture of current system (aarch64, x86_64, ppc64le, ...)"),
    'EXTERNAL_MODULE': (EXTERNAL_MODULE_MARKER, "External module marker"),
    'HOME': (os.path.expanduser('~'), "Home directory ($HOME)"),
    'OS_TYPE': (get_os_type(), "System type (e.g. 'Linux' or 'Darwin')"),
    'OS_NAME': (get_os_name(), "System name (e.g. 'fedora' or 'RHEL')"),
    'OS_VERSION': (get_os_version(), "System version"),
    'SYS_PYTHON_VERSION': (platform.python_version(), "System Python version (platform.python_version())"),
    'SYSTEM': ({'name': 'system', 'version': 'system'}, "System toolchain"),
Ejemplo n.º 29
0
def pick_python_cmd(req_maj_ver=None, req_min_ver=None):
    """
    Pick 'python' command to use, based on specified version requirements.
    If the major version is specified, it must be an exact match (==).
    If the minor version is specified, it is considered a minimal minor version (>=).

    List of considered 'python' commands (in order)
    * 'python' available through $PATH
    * 'python<major_ver>' available through $PATH
    * 'python<major_ver>.<minor_ver>' available through $PATH
    * Python executable used in current session (sys.executable)
    """
    log = fancylogger.getLogger('pick_python_cmd', fname=False)

    def check_python_cmd(python_cmd):
        """Check whether specified Python command satisfies requirements."""

        # check whether specified Python command is available
        if os.path.isabs(python_cmd):
            if not os.path.isfile(python_cmd):
                log.debug("Python command '%s' does not exist", python_cmd)
                return False
        else:
            python_cmd_path = which(python_cmd)
            if python_cmd_path is None:
                log.debug("Python command '%s' not available through $PATH",
                          python_cmd)
                return False

        if req_maj_ver is not None:
            if req_min_ver is None:
                req_majmin_ver = '%s.0' % req_maj_ver
            else:
                req_majmin_ver = '%s.%s' % (req_maj_ver, req_min_ver)

            pyver = det_python_version(python_cmd)

            # (strict) check for major version
            maj_ver = pyver.split('.')[0]
            if maj_ver != str(req_maj_ver):
                log.debug("Major Python version does not match: %s vs %s",
                          maj_ver, req_maj_ver)
                return False

            # check for minimal minor version
            if LooseVersion(pyver) < LooseVersion(req_majmin_ver):
                log.debug(
                    "Minimal requirement for minor Python version not satisfied: %s vs %s",
                    pyver, req_majmin_ver)
                return False

        # all check passed
        log.debug("All check passed for Python command '%s'!", python_cmd)
        return True

    # compose list of 'python' commands to consider
    python_cmds = ['python']
    if req_maj_ver:
        python_cmds.append('python%s' % req_maj_ver)
        if req_min_ver:
            python_cmds.append('python%s.%s' % (req_maj_ver, req_min_ver))
    python_cmds.append(sys.executable)
    log.debug("Considering Python commands: %s", ', '.join(python_cmds))

    # try and find a 'python' command that satisfies the requirements
    res = None
    for python_cmd in python_cmds:
        if check_python_cmd(python_cmd):
            log.debug("Python command '%s' satisfies version requirements!",
                      python_cmd)
            if os.path.isabs(python_cmd):
                res = python_cmd
            else:
                res = which(python_cmd)
            log.debug("Absolute path to retained Python command: %s", res)
            break
        else:
            log.debug(
                "Python command '%s' does not satisfy version requirements (maj: %s, min: %s), moving on",
                python_cmd, req_maj_ver, req_min_ver)

    return res
Ejemplo n.º 30
0
from easybuild.base import fancylogger
from easybuild.framework.easyblock import build_easyconfigs
from easybuild.framework.easyconfig.tools import process_easyconfig
from easybuild.framework.easyconfig.tools import skip_available
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.config import build_option
from easybuild.tools.filetools import find_easyconfigs, mkdir, read_file, write_file
from easybuild.tools.github import GITHUB_EASYBLOCKS_REPO, GITHUB_EASYCONFIGS_REPO, create_gist, post_comment_in_issue
from easybuild.tools.jenkins import aggregate_xml_in_dirs
from easybuild.tools.parallelbuild import build_easyconfigs_in_parallel
from easybuild.tools.robot import resolve_dependencies
from easybuild.tools.systemtools import UNKNOWN, get_system_info
from easybuild.tools.version import FRAMEWORK_VERSION, EASYBLOCKS_VERSION

_log = fancylogger.getLogger('testing', fname=False)


def regtest(easyconfig_paths, modtool, build_specs=None):
    """
    Run regression test, using easyconfigs available in given path
    :param easyconfig_paths: path of easyconfigs to run regtest on
    :param modtool: ModulesTool instance to use
    :param build_specs: dictionary specifying build specifications (e.g. version, toolchain, ...)
    """

    cur_dir = os.getcwd()

    aggregate_regtest = build_option('aggregate_regtest')
    if aggregate_regtest is not None:
        output_file = os.path.join(