示例#1
0
    def test_parentinfo(self):
        """Test the collection of parentinfo"""
        log_fr = fancylogger.getLogger(fname=False)  # rootfancylogger
        pi_fr = log_fr._get_parent_info()
        self.assertEqual(len(pi_fr), 2)

        log_l1 = fancylogger.getLogger("level1", fname=False)
        # fname=False is required to have the naming similar for child relations
        pi_l1 = log_l1._get_parent_info()
        self.assertEqual(len(pi_l1), 3)

        py_v_27 = sys.version_info >= (2, 7, 0)
        if py_v_27:
            log_l2a = log_l1.getChild("level2a")
            pi_l2a = log_l2a._get_parent_info()
            self.assertEqual(len(pi_l2a), 4)

        # this should be identical to getChild
        log_l2b = fancylogger.getLogger("level1.level2b", fname=False)
        # fname=False is required to have the name similar
        # cutoff last letter (a vs b)
        if py_v_27:
            self.assertEqual(log_l2a.name[:-1], log_l2b.name[:-1])
        pi_l2b = log_l2b._get_parent_info()
        # yes, this broken on several levels (incl in logging itself)
        # adding '.' in the name does not automatically create the parent/child relations
        # if the parent with the name exists, this works
        self.assertEqual(len(pi_l2b), 4)

        log_l2c = fancylogger.getLogger("level1a.level2c", fname=False)
        pi_l2c = log_l2c._get_parent_info()
        self.assertEqual(len(pi_l2c), 3)  # level1a as parent does not exist
    def setUp(self):
        """ dynamically replace Modules class with MockModule """
        # replace Modules class with something we have control over
        config.modules_tool = mock_module
        main.modules_tool = mock_module

        self.log = fancylogger.getLogger("RobotTest", fname=False)
        # redefine the main log when calling the main functions directly
        main._log = fancylogger.getLogger("main", fname=False)

        self.cwd = os.getcwd()

        self.base_easyconfig_dir = find_full_path(os.path.join("test", "framework", "easyconfigs"))
        self.assertTrue(self.base_easyconfig_dir)
示例#3
0
    def test_deprecated(self):
        """Test deprecated log function."""
        # truncate the logfile
        open(self.logfn, "w")

        # log message
        logger = fancylogger.getLogger("deprecated_test")

        max_ver = "1.0"

        # test whether deprecation works
        msgre_tpl_error = r"DEPRECATED\s*\(since v%s\).*%s" % (max_ver, MSG)
        self.assertErrorRegex(Exception, msgre_tpl_error, logger.deprecated, MSG, "1.1", max_ver)

        # test whether deprecated warning works
        logger.deprecated(MSG, "0.9", max_ver)
        msgre_tpl_warning = r"WARNING.*DEPRECATED\s*\(since v%s\).*%s" % (max_ver, MSG)
        msgre_warning = re.compile(msgre_tpl_warning)
        txt = open(self.logfn, "r").read()
        self.assertTrue(msgre_warning.search(txt))

        # test handling of non-UTF8 chars
        msg = MSG + "\x81"
        msgre_tpl_error = r"DEPRECATED\s*\(since v%s\).*%s" % (max_ver, msg)
        self.assertErrorRegex(Exception, msgre_tpl_error, logger.deprecated, msg, "1.1", max_ver)
        logger.deprecated(msg, "0.9", max_ver)
        txt = open(self.logfn, "r").read()
        self.assertTrue(msgre_warning.search(txt))
示例#4
0
    def __init__(self, modulePath=None):
        """
        Create a Modules object
        @param modulePath: A list of paths where the modules can be located
        @type modulePath: list
        """
        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
        # make sure we don't have the same path twice
        if modulePath:
            self.modulePath = set(modulePath)
        else:
            self.modulePath = None
        self.modules = []

        self.check_module_path()

	# make sure lmod is available somewhere
	eclm = subprocess.call(["which", "lmod"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

        # make sure environment-modules is installed
        ecem = subprocess.call(["which", "modulecmd"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        if ecem and eclm:
            msg = "Could not find the modulecmd command, environment-modules is not installed?\n"
            msg += "Exit code of 'which modulecmd': %d" % ecem
            self.log.error(msg)
            raise EasyBuildError(msg)
        elif ecem:
            self.modulecmd = "lmod"
            os.environ['LMOD_EXPERT'] = '1'
        elif eclm:
            self.modulecmd = "modulecmd"
示例#5
0
    def __init__(self, options=None, **kwargs):
        if not hasattr(self, 'log'):
            self.log = getLogger(self.__class__.__name__)
        if not hasattr(self, 'options'):
            self.options = options

        self.nodes = None
        self.nrnodes = None

        self.uniquenodes = None
        self.nruniquenodes = None

        self.mpinodes = None
        self.mpinrnodes = None
        self.mpitotalppn = None

        self.id = None

        self.foundppn = None
        self.ppn = None
        self.totalppn = None

        self.cpus = []

        # # collect data
        self.get_id()
        self.cores_on_this_node()
        self.which_cpus()

        self.get_node_list()
        self.get_unique_nodes()
        self.set_ppn()

        super(Sched, self).__init__(**kwargs)
示例#6
0
    def __init__(self, cmd=None, **kwargs):
        if kwargs.pop('disable_log', None):
            self.log = DummyFunction() ## No logging
        if not hasattr(self, 'log'):
            self.log = getLogger(self._get_log_name())

        self.cmd = cmd  ## actual command
        self.input = None

        self.startpath = None
        self._cwd_before_startpath = None

        self._process_module = None
        self._process = None

        self.readsize = 1024 ## number of bytes to read blocking

        self._shellcmd = None
        self._popen_named_args = None

        self._process_exitcode = None
        self._process_output = None

        self._post_exitcode_log_failure = self.log.error

        super(Run, self).__init__(**kwargs)
示例#7
0
    def __init__(self, filename, retain_old=False):
        """Initializer.

        Checks if the file can be accessed and load the data therein if any. If the file does not yet exist, start
        with an empty shelf. This ensures that old data is readily available when the FileCache instance is created.
        The file is closed after reading the data.

        @type filename: string

        @param filename: (absolute) path to the cache file.
        """

        self.log = fancylogger.getLogger(self.__class__.__name__)
        self.filename = filename
        self.retain_old = retain_old

        try:
            f = open(self.filename, 'rb')
            try:
                self.shelf = pickle.load(f)
            except:
                self.log.raiseException("Could not load pickle data from %s" % (self.filename))
            finally:
                f.close()

        except (OSError, IOError), err:
            self.log.error("Could not access the file cache at %s [%s]" % (self.filename, err))
            self.shelf = {}
示例#8
0
    def __init__(self, initcomm=True, log=None):
        self.log = log
        if self.log is None:
            self.log = fancylogger.getLogger(name=self.__class__.__name__, fname=False)

        self.comm = None
        self.size = -1
        self.rank = -1

        self.masterrank = MASTERRANK

        self.barriercounter = 0

        self.stopwithbarrier = True

        self.wait_iter_sleep = 60  # run through all active work, then wait wait_iter_sleep seconds

        self.allnodes = None  # Node info per rank
        self.topocomm = None
        self.tempcomm = []

        self.active_work = []

        self.dists = None
        self.thisnode = None

        if initcomm:
            self.log.debug(
                "Going to initialise the __init__ default communicators")
            self.init_comm()
        else:
            self.log.debug("No communicators initialised in __init__")
示例#9
0
    def __init__(self, exe, args):
        """Initialisation arguments determine executable to run and arguments of said executable"""
        self.log = fancylogger.getLogger(self.__class__.__name__)

        self.exe = exe
        self.args = args

        self.maxprocs = 2

        self.commself = None
        self.selfsize = None
        self.selfrank = None

        self.commclone = None
        self.clonesize = None
        self.clonerank = None

        self.comm = None
        self.size = None
        self.rank = None

        self.group = None
        self.groupsize = None
        self.grouprank = None

        self.stopwithbarrier = True
        self.workinitbarrier = True


        self.work = None
        self.dowork = None ## no work to be done is default 
示例#10
0
    def __init__(self, ranks):
        self.log = fancylogger.getLogger(self.__class__.__name__)
        MpiService.__init__(self, initcomm=False, log=self.log)

        self.allranks = ranks

        self.commands = {} ## dict with command : list of ranks
示例#11
0
    def __init__(self, script, name, env_vars=None, resources={}, conn=None, ppn=None):
        """
        create a new Job to be submitted to PBS
        env_vars is a dictionary with key-value pairs of environment variables that should be passed on to the job
        resources is a dictionary with optional keys: ['hours', 'cores'] both of these should be integer values.
        hours can be 1 - MAX_WALLTIME, cores depends on which cluster it is being run.
        """
        self.clean_conn = True
        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
        self.script = script
        if env_vars:
            self.env_vars = env_vars.copy()
        else:
            self.env_vars = {}
        self.name = name

        if pbs_import_failed:
            self.log.error(pbs_import_failed)

        try:
            self.pbs_server = pbs.pbs_default()
            if conn:
                self.pbsconn = conn
                self.clean_conn = False
            else:
                self.pbsconn = pbs.pbs_connect(self.pbs_server)
        except Exception, err:
            self.log.error("Failed to connect to the default pbs server: %s" % err)
示例#12
0
    def __init__(self, mod_paths=None):
        """
        Create a ModulesTool object
        @param mod_paths: A list of paths where the modules can be located
        @type mod_paths: list
        """
        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
        # make sure we don't have the same path twice
        if mod_paths:
            self.mod_paths = nub(mod_paths)
        else:
            self.mod_paths = None

        # DEPRECATED!
        self._modules = []

        self.check_module_path()

        # actual module command (i.e., not the 'module' wrapper function, but the binary)
        self.cmd = None

        # shell that should be used to run module command (specified above) in (if any)
        self.shell = None

        # version of modules tool
        self.version = None

        # terse command line option
        self.add_terse_opt_fn = lambda x: x.insert(0, '--terse')
示例#13
0
文件: cache.py 项目: piojo/vsc-base
    def __init__(self, filename, retain_old=False):
        """Initializer.

        Checks if the file can be accessed and load the data therein if any. If the file does not yet exist, start
        with an empty shelf. This ensures that old data is readily available when the FileCache instance is created.
        The file is closed after reading the data.

        @type filename: string

        @param filename: (absolute) path to the cache file.
        """

        self.log = fancylogger.getLogger(self.__class__.__name__)
        self.filename = filename
        self.retain_old = retain_old

        try:
            f = open(self.filename, 'rb')
            try:
                # FIXME: This double block is due to a workaround for Python 2.4
                # see http://stackoverflow.com/questions/820778/syntaxerror-in-finally-django
                try:
                    self.shelf = pickle.load(f)
                except:
                    self.log.raiseException("Could not load pickle data from %s" % (self.filename))
            finally:
                f.close()

        except (OSError, IOError), err:
            self.log.warning("Could not access the file cache at %s [%s]" % (self.filename, err))
            self.shelf = {}
 def setUp(self):
     """ create temporary easyconfig file """
     self.log = fancylogger.getLogger("EasyConfigTest", fname=False)
     self.cwd = os.getcwd()
     self.all_stops = [x[0] for x in EasyBlock.get_steps()]
     if os.path.exists(self.eb_file):
         os.remove(self.eb_file)
     config.variables['source_path'] = os.path.join(os.path.dirname(__file__), 'easyconfigs')
示例#15
0
文件: utils.py 项目: piojo/vsc-ldap
    def __init__(self):
        self.url = None
        self.password = None
        self.connection_dn = None
        self.validation_method = None
        self.check_server_certificate = False

        self.log = fancylogger.getLogger(self.__class__.__name__)
示例#16
0
def get_log(name=None):
    """
    Generate logger object
    """
    # fname is always get_log, useless
    log = fancylogger.getLogger(name, fname=False)
    log.info("Logger started for %s." % name)
    return log
 def request(self, method, url, body, headers):
   if self.username:
     headers['Authorization'] = self.auth_header
   fancylogger.getLogger().debug('cli request: %s, %s, %s %s', method, url, body, headers)
   #TODO: Context manager
   conn = self.get_connection()
   conn.request(method, url, body, headers)
   response = conn.getresponse()
   status = response.status
   body = response.read()
   try:
     pybody = json.loads(body)
   except ValueError:
     pybody = body
   fancylogger.getLogger().debug('reponse len: %s ', len(pybody))
   conn.close()
   return status, pybody
示例#18
0
文件: utils.py 项目: piojo/vsc-ldap
    def __init__(self, configuration):
        """Initialisation. Not done lazily.

        @type configuration: vsc.ldap.utils.Configuration subclass instance, implementing the actual functions to request
                             information.
        """
        self.log = fancylogger.getLogger(name=self.__class__.__name__)
        self.configuration = configuration
        self.ldap_connection = None
示例#19
0
 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, basestring):
         self.data = self._from_string(obj)
     else:
         self.log.error('unsupported type %s for %s: %s' % (type(obj), self.__class__.__name__, obj))
     super(Convert, self).__init__(self.data)
示例#20
0
    def setUp(self):
        """Set up everything for a unit test."""
        # initialize configuration so config.get_modules_tool function works
        eb_go = eboptions.parse_options()
        config.init(eb_go.options, eb_go.get_options_by_section('config'))

        # replace Modules class with something we have control over
        config.modules_tool = mock_module
        main.modules_tool = mock_module

        self.log = fancylogger.getLogger("RobotTest", fname=False)
        # redefine the main log when calling the main functions directly
        main._log = fancylogger.getLogger("main", fname=False)

        self.cwd = os.getcwd()

        self.base_easyconfig_dir = find_full_path(os.path.join("test", "framework", "easyconfigs"))
        self.assertTrue(self.base_easyconfig_dir)
示例#21
0
 def __init__(self, commands=None):
     """
     constructor
     """
     self.log = fancylogger.getLogger(self.__class__.__name__)
     if commands:
         self.commands = commands  # queue commands here, these will be run when doIt is called
     else:
         self.commands = []
示例#22
0
 def __init__(self, command=None, timeout=get_config("COMMAND_TIMEOUT"), host='localhost'):
     '''
     Constructor
     command is a string representing the command to be run
     '''
     self.log = fancylogger.getLogger(self.__class__.__name__)
     self.command = command
     self.host = host
     self.timeout = float(timeout)
示例#23
0
    def __init__(self, command=None, timeout=COMMAND_TIMEOUT):
        '''
        Constructor
        command is a string representing the command to be run
        '''
        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
        self.command = command
        self.timeout = timeout

        self.fake_pty = False
    def __init__(self, options):
        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)

        self.vars = {
            'cwd': None,
            'jobid': None,
        }
        self.jobid = None

        self.job_filter = None
示例#25
0
    def __init__(self):
        """Initialise the EasyConfigFormat class"""
        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)

        if not len(self.VERSION) == len(FORMAT_VERSION_TEMPLATE.split('.')):
            self.log.error('Invalid version number %s (incorrect length)' % self.VERSION)

        self.rawtext = None  # text version of the easyconfig
        self.header = None  # easyconfig header (e.g., format version, license, ...)
        self.docstring = None  # easyconfig docstring (e.g., author, maintainer, ...)
示例#26
0
 def __init__(self, nodelist, imms=False):
     '''
     Constructor
     nodelist is a list of hostnames for nodes to perform actions on
     if imms=True it will also do the same action on the imm nodes of the given nodelist nodes
     '''
     Worker.__init__(self)
     self.nodelist = nodelist
     self.log = fancylogger.getLogger(self.__class__.__name__)
     self.imms = imms
示例#27
0
    def setUp(self):
        """ create temporary easyconfig file """
        self.log = fancylogger.getLogger("EasyConfigTest", fname=False)
        if self.contents is not None:
            fd, self.eb_file = tempfile.mkstemp(prefix='easyconfig_test_file_', suffix='.eb')
            os.close(fd)
            write_file(self.eb_file, self.contents)
        self.cwd = os.getcwd()

        self.all_stops = [x[0] for x in EasyBlock.get_steps()]
示例#28
0
 def __init__(self, *args, **kwargs):
     """
         - disable_log : boolean use dummy logger
     """
     disable_log = kwargs.pop('disable_log', False)
     if disable_log:
         log = DummyFunction()
     else:
         log = getLogger(self.__class__.__name__)
     self.log = log
示例#29
0
 def __init__(self, repo_path, subdir=''):
     """
     Initialize a repository. self.repo and self.subdir will be set.
     self.wc will be set to None.
     Then, setupRepo and createWorkingCopy will be called (in that order)
     """
     self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
     self.subdir = subdir
     self.repo = repo_path
     self.wc = None
     self.initialized = False
示例#30
0
文件: utils.py 项目: piojo/vsc-ldap
    def __init__(self, object_classes=[]):
        """Initialisation.

        Note that the LdapQuery singleton should have been instantiated elsewhere.
        """
        self.ldap_query = LdapQuery(None)
        self.ldap_info = None

        self.object_classes = object_classes  # LDAP object class name for which the schema will be checked

        self.log = fancylogger.getLogger(self.__class__.__name__)
示例#31
0
@author: Toon Willems (Ghent University)
"""
import getpass
import os
import socket
import tempfile
import time
from vsc import fancylogger
from vsc.utils.missing import get_subclasses

from easybuild.framework.easyconfig.easyconfig import EasyConfig
from easybuild.framework.easyconfig.tools import stats_to_str
from easybuild.tools.filetools import rmtree2, read_file, write_file
from easybuild.tools.version import VERBOSE_VERSION

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

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

# GitPython
try:
    import git
    from git import GitCommandError
    HAVE_GIT = True
except ImportError:
    _log.debug('Failed to import git module')
    HAVE_GIT = False

# PySVN
示例#32
0
 def __init__(self, *args, **kwargs):
     super(StrList, self).__init__(*args, **kwargs)
     self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
示例#33
0
 def __init__(self, *args, **kwargs):
     super(cpu_set_t, self).__init__(*args, **kwargs)
     self.log = getLogger(self.__class__.__name__)
     self.cpus = None
示例#34
0
    def __init__(self):
        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)

        self.options_map = {}  # map between options name and value
        self.description = {}  # short description of the options
示例#35
0
    def __init__(self):
        """Initialise the list of version operators as an empty list."""
        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)

        self.versops = []
        self.datamap = {}
示例#36
0
from vsc import fancylogger
from vsc.utils.missing import get_subclasses

from easybuild.framework.easyconfig.format.version import EasyVersion

# format is mandatory major.minor
FORMAT_VERSION_KEYWORD = "EASYCONFIGFORMAT"
FORMAT_VERSION_TEMPLATE = "%(major)s.%(minor)s"
FORMAT_VERSION_HEADER_TEMPLATE = "# %s %s\n" % (
    FORMAT_VERSION_KEYWORD, FORMAT_VERSION_TEMPLATE)  # must end in newline
FORMAT_VERSION_REGEXP = re.compile(
    r'^#\s+%s\s*(?P<major>\d+)\.(?P<minor>\d+)\s*$' % FORMAT_VERSION_KEYWORD,
    re.M)
FORMAT_DEFAULT_VERSION = EasyVersion('1.0')

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


def get_format_version(txt):
    """Get the easyconfig format version as EasyVersion instance."""
    res = FORMAT_VERSION_REGEXP.search(txt)
    format_version = None
    if res is not None:
        try:
            maj_min = res.groupdict()
            format_version = EasyVersion(FORMAT_VERSION_TEMPLATE % maj_min)
        except (KeyError, TypeError), err:
            _log.error("Failed to get version from match %s: %s" %
                       (res.groups(), err))
    return format_version
示例#37
0
# You should have received a copy of the GNU General Public License
# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
##
"""
Module with useful functions for getting system information

@author: Jens Timmerman (Ghent University)
"""
import os
import platform
import re
from vsc import fancylogger

from easybuild.tools.filetools import read_file, run_cmd

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

INTEL = 'Intel'
AMD = 'AMD'


class SystemToolsException(Exception):
    """raised when systemtools fails"""


def get_core_count():
    """Try to detect the number of virtual or physical CPUs on this system.

    inspired by http://stackoverflow.com/questions/1006289/how-to-find-out-the-number-of-cpus-in-python/1006301#1006301
    """
示例#38
0
 def __init__(self, *args, **kwargs):
     """Initialize logger."""
     self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
示例#39
0
import glob
import os
import re
import sys
import tempfile
from distutils.version import LooseVersion
from vsc import fancylogger
from vsc.utils.missing import nub

from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.filetools import run_cmd, read_file, write_file
from easybuild.tools.ordereddict import OrderedDict
from easybuild.tools.utilities import quote_str
from easybuild.framework.easyconfig.easyconfig import EasyConfig, det_installversion

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


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_installversion(
                           ec['version'], ec['toolchain']['name'],
                           ec['toolchain']['version'], ec['versionprefix'],
                           ec['versionsuffix']))
示例#40
0
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,
        'optarch': 'test',
        'robot_path': get_paths_for("easyconfigs")[0],
        '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)
    config.set_tmpdir()
    del eb_go

    # mock 'exist' and 'load' methods of modules tool, which are used for 'craype' external module
    modtool = modules_tool()
    modtool.exist = lambda m: [True] * len(m)
    modtool.load = lambda m: True

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

    # make sure a logger is present for main
    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 self.parsed_easyconfigs:
            for spec in specs:
                self.parsed_easyconfigs.extend(process_easyconfig(spec))

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

        self.ordered_specs = resolve_dependencies(self.parsed_easyconfigs,
                                                  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 self.ordered_specs is None:
                self.process_all_easyconfigs()

            dep_graph(fn, self.ordered_specs, silent=True)

            try:
                os.remove(fn)
            except OSError, err:
                log.error("Failed to remove %s: %s" % (fn, err))
        else:
示例#41
0
# You should have received a copy of the GNU General Public License
# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
##
"""
Interface module to TORQUE (PBS).

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

import os
import time
from vsc import fancylogger

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

pbs_import_failed = None
try:
    from PBSQuery import PBSQuery
    import pbs
except ImportError:
    _log.debug("Failed to import pbs from pbs_python. Silently ignoring, is only a real issue with --job")
    pbs_import_failed = ("PBSQuery or pbs modules not available. "
                         "Please make sure pbs_python is installed and usable.")

MAX_WALLTIME = 72

def connect_to_server(pbs_server=None):
    """Connect to PBS server and return connection."""
    if pbs_import_failed:
示例#42
0
# 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/>.
##
"""
Utility module for modifying os.environ

@author: Toon Willems (Ghent University)
"""
import os
from vsc import fancylogger

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

changes = {}


def write_changes(filename):
    """
    Write current changes to filename and reset environment afterwards
    """
    script = None
    try:
        script = open(filename, 'w')

        for key in changes:
            script.write('export %s="%s"\n' % (key, changes[key]))
@author: Jens Timmerman (Ghent University)
"""

import os
import shutil
import tempfile

import easybuild.tools.environment as env
from easybuild.framework.easyblock import EasyBlock
from easybuild.framework.easyconfig import CUSTOM
from easybuild.tools.filetools import rmtree2, run_cmd

# required for deprecated log in static function (ie no self)
from easybuild.framework.easyconfig.licenses import License
from vsc import fancylogger
_log = fancylogger.getLogger('generic.intelbase')


class IntelBase(EasyBlock):
    """
    Base class for Intel software
    - no configure/make : binary release
    - add license_file variable
    """
    def __init__(self, *args, **kwargs):
        """Constructor, adds extra config options"""
        self.license_file = None

        self.home_subdir = os.path.join(os.getenv('HOME'), 'intel')
        self.home_subdir_local = os.path.join(tempfile.gettempdir(),
                                              os.getenv('USER'),
示例#44
0
def search_toolchain(name):
    """
    Find a toolchain with matching name
    returns toolchain (or None), found_toolchains
    """

    log = fancylogger.getLogger("search_toolchain")

    # import all available toolchains, so we know about them
    tc_modules = []
    for path in sys.path:
        for module in glob.glob(os.path.join(path, 'easybuild', 'toolchains', '*.py')):
            if not module.endswith('__init__.py'):
                modpath = "easybuild.toolchains.%s" % module.split(os.path.sep)[-1].split('.')[0]
                log.debug("importing toolchain module %s" % modpath)
                tc_modules.append(__import__(modpath, globals(), locals(), ['']))

    # make sure all defined toolchain constants are available in toolchain module
    package = easybuild.tools.toolchain
    tc_const_prefix = 'TC_CONSTANT_'
    tc_const_re = re.compile('^%s(.*)$' % tc_const_prefix)
    for tc_mod in tc_modules:
        # determine classes imported in this module
        mod_classes = []
        for elem in [getattr(tc_mod, x) for x in dir(tc_mod)]:
            if hasattr(elem, '__module__'):
                # exclude the toolchain class defined in that module
                if not tc_mod.__file__ == sys.modules[elem.__module__].__file__:
                    log.debug("Adding %s to list of imported classes used for looking for constants" % elem.__name__)
                    mod_classes.append(elem)

        # look for constants in modules of imported classes, and make them available
        for mod_class_mod in [sys.modules[mod_class.__module__] for mod_class in mod_classes]:
            for elem in dir(mod_class_mod):
                res = tc_const_re.match(elem)
                if res:
                    tc_const_name = res.group(1)
                    tc_const_value = getattr(mod_class_mod, elem)
                    log.debug("Found constant %s ('%s') in module %s, adding it to %s" % (tc_const_name,
                                                                                          tc_const_value,
                                                                                          mod_class_mod.__name__,
                                                                                          package.__name__))
                    if hasattr(package, tc_const_name):
                        cur_value = getattr(package, tc_const_name)
                        if not tc_const_value == cur_value:
                            log.error("Constant %s.%s defined as '%s', can't set it to '%s'." % (package.__name__,
                                                                                                 tc_const_name,
                                                                                                 cur_value,
                                                                                                 tc_const_value
                                                                                                ))
                    else:
                        setattr(package, tc_const_name, tc_const_value)

    # obtain all subclasses of toolchain
    found_tcs = nub(get_subclasses(Toolchain))

    # filter found toolchain subclasses based on whether they can be used a toolchains
    found_tcs = [tc for tc in found_tcs if tc._is_toolchain_for(None)]

    for tc in found_tcs:
        if tc._is_toolchain_for(name):
            return tc, found_tcs

    return None, found_tcs
@author: Kenneth Hoste (Ghent University)
@author: Stijn De Weirdt (Ghent University)
"""
import math
import os
import re

import easybuild.tools.config as config
from easybuild.framework.easyblock import get_class
from easybuild.tools.pbs_job import PbsJob, connect_to_server, disconnect_from_server, get_ppn
from easybuild.tools.config import get_repository, get_repositorypath
from easybuild.tools.filetools import read_file
from easybuild.tools.repository import init_repository
from vsc import fancylogger

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


def build_easyconfigs_in_parallel(build_command,
                                  easyconfigs,
                                  output_dir,
                                  robot_path=None):
    """
    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

    returns the jobs
    """
    _log.info("going to build these easyconfigs in parallel: %s", easyconfigs)
    job_module_dict = {}
    # dependencies have already been resolved,
 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)
示例#47
0
#
"""
SCOOP support
    http://code.google.com/p/scoop/
    based on 0.6.0  code
"""
import itertools
import os
import sys
from collections import namedtuple
from distutils.version import LooseVersion
from vsc.fancylogger import getLogger
from vsc.mympirun.mpi.mpi import MPI
from vsc.mympirun.exceptions import WrongPythonVersionExcpetion, InitImportException

_logger = getLogger("MYSCOOP")

# requires Python 2.6 at least (str.format)
if LooseVersion(".".join(["%s" % x
                          for x in sys.version_info])) < LooseVersion('2.6'):
    _logger.raiseException("MYSCOOP / scoop requires python 2.6 or later",
                           WrongPythonVersionExcpetion)

try:
    import scoop
except:
    _logger.raiseException(
        "MYSCOOP requires the scoop module and scoop requires (amongst others) pyzmq",
        InitImportException)

from scoop.__main__ import ScoopApp
示例#48
0
 def setUp(self):
     self.log = fancylogger.getLogger(self.__class__.__name__)
     self.legacySetUp()
示例#49
0
        self.raiseError = False
        fancylogger.FancyLogger.exception(self, newMsg, *args)
        self.raiseError = True

        raise EasyBuildError(newMsg)


# set format for logger
LOGGING_FORMAT = EB_MSG_PREFIX + ' %(asctime)s %(name)s %(levelname)s %(message)s'
fancylogger.setLogFormat(LOGGING_FORMAT)

# set the default LoggerClass to EasyBuildLog
fancylogger.logging.setLoggerClass(EasyBuildLog)

# you can't easily set another LoggerClass before fancylogger calls getLogger on import
_init_fancylog = fancylogger.getLogger(fname=False)
del _init_fancylog.manager.loggerDict[_init_fancylog.name]

# we need to make sure there is a handler
fancylogger.logToFile(filename=os.devnull)

# EasyBuildLog
_init_easybuildlog = fancylogger.getLogger(fname=False)


def get_log(name=None):
    """
    Generate logger object
    """
    # fname is always get_log, useless
    log = fancylogger.getLogger(name, fname=False)
示例#50
0
@author: Jens Timmerman (Ghent University)
@author: Toon Willems (Ghent University)
"""

import os
import random
import string
import tempfile
import time
from vsc import fancylogger
from vsc.utils.missing import nub

import easybuild.tools.build_log  # this import is required to obtain a correct (EasyBuild) logger!
from easybuild.tools.environment import read_environment as _read_environment

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

# class constant to prepare migration to generaloption as only way of configuration (maybe for v2.X)
SUPPORT_OLDSTYLE = True

DEFAULT_LOGFILE_FORMAT = (
    "easybuild", "easybuild-%(name)s-%(version)s-%(date)s.%(time)s.log")

DEFAULT_PATH_SUBDIRS = {
    'buildpath': 'build',
    'installpath': '',
    'repositorypath': 'ebfiles_repo',
    'sourcepath': 'sources',
    'subdir_modules': 'modules',
    'subdir_software': 'software',
}
示例#51
0
    def __init__(self, path, extra_options=None, validate=True, valid_module_classes=None, valid_stops=None):
        """
        initialize an easyconfig.
        path should be a path to a file that can be parsed
        extra_options is a dict of extra variables that can be set in this specific instance
        validate specifies whether validations should happen
        """

        self.template_values = None
        self.enable_templating = True  # a boolean to control templating

        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)

        self.valid_module_classes = None
        if valid_module_classes:
            self.valid_module_classes = valid_module_classes
            self.log.info("Obtained list of valid module classes: %s" % self.valid_module_classes)
        else:
            self.valid_module_classes = ['base', 'compiler', 'lib']  # legacy module classes

        # replace the category name with the category
        self._config = {}
        for k, [def_val, descr, cat] in copy.deepcopy(DEFAULT_CONFIG).items():
            self._config[k] = [def_val, descr, ALL_CATEGORIES[cat]]

        if extra_options is None:
            extra_options = {}
        elif isinstance(extra_options, (list, tuple,)):
            # TODO legacy behaviour. should be more strictly enforced. do we log here?
            extra_options = dict(extra_options)

        self._legacy_license(extra_options)

        self._config.update(extra_options)

        self.path = path
        self.mandatory = MANDATORY_PARAMS[:]

        # extend mandatory keys
        for key, value in extra_options.items():
            if value[2] == ALL_CATEGORIES['MANDATORY']:
                self.mandatory.append(key)

        # set valid stops
        self.valid_stops = []
        if valid_stops:
            self.valid_stops = valid_stops
            self.log.debug("List of valid stops obtained: %s" % self.valid_stops)

        # store toolchain
        self._toolchain = None

        if not os.path.isfile(path):
            self.log.error("EasyConfig __init__ expected a valid path")

        self.validations = {
                            'moduleclass': self.valid_module_classes,
                            'stop': self.valid_stops,
                            }

        # parse easyconfig file
        self.parse(path)

        # handle allowed system dependencies
        self.handle_allowed_system_deps()

        # perform validations
        self.validation = validate
        if self.validation:
            self.validate()
示例#52
0
import tempfile
import unittest
from vsc import fancylogger

from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.config import set_tmpdir

import test.easyblocks.init_easyblocks as i
import test.easyblocks.module_only 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, 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 [i, m]])

# uses XMLTestRunner if possible, so we can output an XML file that can be supplied to Jenkins
xml_msg = ""
示例#53
0
def main(testing_data=(None, None)):
    """
    Main function:
    @arg options: a tuple: (options, paths, logger, logfile, hn) as defined in parse_options
    This function will:
    - read easyconfig
    - build software
    """
    # disallow running EasyBuild as root
    if os.getuid() == 0:
        sys.stderr.write("ERROR: You seem to be running EasyBuild with root privileges.\n" \
                        "That's not wise, so let's end this here.\n" \
                        "Exiting.\n")
        sys.exit(1)

    # steer behavior when testing main
    testing = testing_data[0] is not None
    args, logfile = testing_data

    # initialise options
    eb_go = eboptions.parse_options(args=args)
    options = eb_go.options
    orig_paths = eb_go.args

    # initialise logging for main
    if options.logtostdout:
        fancylogger.logToScreen(enable=True, stdout=True)
    else:
        if logfile is None:
            # mkstemp returns (fd,filename), fd is from os.open, not regular open!
            fd, logfile = tempfile.mkstemp(suffix='.log', prefix='easybuild-')
            os.close(fd)

        fancylogger.logToFile(logfile)
        print_msg('temporary log file in case of crash %s' % (logfile), log=None, silent=testing)

    global _log
    _log = fancylogger.getLogger(fname=False)

    # hello world!
    _log.info(this_is_easybuild())

    # set strictness of filetools module
    if options.strict:
        filetools.strictness = options.strict

    if not options.robot is None:
        if options.robot:
            _log.info("Using robot path: %s" % options.robot)
        else:
            _log.error("No robot path specified, and unable to determine easybuild-easyconfigs install path.")

    # determine easybuild-easyconfigs package install path
    easyconfigs_paths = get_paths_for("easyconfigs", robot_path=options.robot)
    easyconfigs_pkg_full_path = None

    search_path = os.getcwd()
    if easyconfigs_paths:
        easyconfigs_pkg_full_path = easyconfigs_paths[0]
        if not options.robot:
            search_path = easyconfigs_pkg_full_path
        else:
            search_path = options.robot
    else:
        _log.info("Failed to determine install path for easybuild-easyconfigs package.")

    if options.robot:
        easyconfigs_paths = [options.robot] + easyconfigs_paths

    # initialise the easybuild configuration
    config.init(options, eb_go.get_options_by_section('config'))

    # search for easyconfigs
    if options.search:
        search_file(search_path, options.search, silent=testing)

    # process software build specifications (if any), i.e.
    # software name/version, toolchain name/version, extra patches, ...
    (try_to_generate, software_build_specs) = process_software_build_specs(options)

    paths = []
    if len(orig_paths) == 0:
        if software_build_specs.has_key('name'):
            paths = [obtain_path(software_build_specs, easyconfigs_paths,
                                 try_to_generate=try_to_generate, exit_on_error=not testing)]
        elif not any([options.aggregate_regtest, options.search, options.regtest]):
            print_error(("Please provide one or multiple easyconfig files, or use software build "
                  "options to make EasyBuild search for easyconfigs"),
                  log=_log, opt_parser=eb_go.parser, exit_on_error=not testing)
    else:
        # look for easyconfigs with relative paths in easybuild-easyconfigs package,
        # unless they we found at the given relative paths

        if easyconfigs_pkg_full_path:
            # create a mapping from filename to path in easybuild-easyconfigs package install path
            easyconfigs_map = {}
            for (subpath, _, filenames) in os.walk(easyconfigs_pkg_full_path):
                for filename in filenames:
                    easyconfigs_map.update({filename: os.path.join(subpath, filename)})

            # try and find non-existing non-absolute eaysconfig paths in easybuild-easyconfigs package install path
            for idx, orig_path in enumerate(orig_paths):
                if not os.path.isabs(orig_path) and not os.path.exists(orig_path):
                    if orig_path in easyconfigs_map:
                        _log.info("Found %s in %s: %s" % (orig_path, easyconfigs_pkg_full_path,
                                                         easyconfigs_map[orig_path]))
                        orig_paths[idx] = easyconfigs_map[orig_path]

        # indicate that specified paths do not contain generated easyconfig files
        paths = [(path, False) for path in orig_paths]

    _log.debug("Paths: %s" % paths)

    # run regtest
    if options.regtest or options.aggregate_regtest:
        _log.info("Running regression test")
        if paths:
            regtest_ok = regtest(options, [path[0] for path in paths])
        else:  # fallback: easybuild-easyconfigs install path
            regtest_ok = regtest(options, [easyconfigs_pkg_full_path])

        if not regtest_ok:
            _log.info("Regression test failed (partially)!")
            sys.exit(31)  # exit -> 3x1t -> 31

    if any([options.search, options.regtest]):
        cleanup_logfile_and_exit(logfile, testing, True)

    # building a dependency graph implies force, so that all dependencies are retained
    # and also skips validation of easyconfigs (e.g. checking os dependencies)
    validate_easyconfigs = True
    retain_all_deps = False
    if options.dep_graph:
        _log.info("Enabling force to generate dependency graph.")
        options.force = True
        validate_easyconfigs = False
        retain_all_deps = True

    # read easyconfig files
    easyconfigs = []
    for (path, generated) in paths:
        path = os.path.abspath(path)
        if not (os.path.exists(path)):
            print_error("Can't find path %s" % path)

        try:
            files = find_easyconfigs(path)
            for f in files:
                if not generated and try_to_generate and software_build_specs:
                    ec_file = easyconfig.tools.tweak(f, None, software_build_specs)
                else:
                    ec_file = f
                easyconfigs.extend(process_easyconfig(ec_file, options.only_blocks,
                                                      validate=validate_easyconfigs))
        except IOError, err:
            _log.error("Processing easyconfigs in path %s failed: %s" % (path, err))
示例#54
0
    #
    # Any modules with a name that does not match the regex constructed below, will be HIDDEN from EasyBuild
    #
    'available':
    re.compile(
        r"""
        ^(?!-*\s)                     # disallow lines starting with (empty) list of '-'s followed by a space
        (?P<mod_name>                 # start named group for module name
            [^\s\(]*[^:/]             # module name must not have '(' or whitespace in it, must not end with ':' or '/'
        )                             # end named group for module name
        (?P<default>\(default\))?     # optional '(default)' that's not part of module name
        \s*$                          # ignore whitespace at the end of the line
        """, re.VERBOSE),
}

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


class ModulesTool(object):
    """An abstract interface to a tool that deals with modules."""
    def __init__(self, mod_paths=None):
        """
        Create a ModulesTool object
        @param mod_paths: A list of paths where the modules can be located
        @type mod_paths: list
        """
        self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)
        # make sure we don't have the same path twice
        if mod_paths:
            self.mod_paths = nub(mod_paths)
        else:
示例#55
0
# 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/>.
#
"""
Easyconfig licenses module that provides all licenses that can
be used within an Easyconfig file.

@author: Stijn De Weirdt (Ghent University)
"""

from vsc import fancylogger
from vsc.utils.missing import get_subclasses

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


class License(object):
    """EasyBuild easyconfig license class"""
    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

    CLASSNAME_PREFIX = 'License_'
示例#56
0
import os
import re
import shutil
import signal
import stat
import subprocess
import tempfile
import time
import urllib
from vsc import fancylogger

import easybuild.tools.environment as env
from easybuild.tools.asyncprocess import Popen, PIPE, STDOUT
from easybuild.tools.asyncprocess import send_all, recv_some

_log = fancylogger.getLogger('filetools', fname=False)
errorsFoundInLog = 0

# constants for strictness levels
IGNORE = 'ignore'
WARN = 'warn'
ERROR = 'error'

# default strictness level
strictness = WARN

# easyblock class prefix
EASYBLOCK_CLASS_PREFIX = 'EB_'

# character map for encoding strings
STRING_ENCODING_CHARMAP = {
示例#57
0
#
# You should have received a copy of the GNU General Public License
# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
# #
"""
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)
"""
from vsc import fancylogger
import copy
import os

_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]
@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: Fotis Georgatos (Uni.Lu)
"""
import os
import tempfile
from vsc import fancylogger

from easybuild.tools.config import install_path
from easybuild.tools.utilities import quote_str

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

# general module class
GENERAL_CLASS = 'all'


class ModuleGenerator(object):
    """
    Class for generating module files.
    """
    def __init__(self, application, fake=False):
        self.app = application
        self.fake = fake
        self.filename = None
        self.tmpdir = None
示例#59
0
"""
Linux cpu affinity.
    - Based on C{sched.h} and C{bits/sched.h},
    - see man pages for  C{sched_getaffinity} and C{sched_setaffinity}
    - also provides a C{cpuset} class to convert between human readable cpusets and the bit version
Linux priority
    - Based on sys/resources.h and bits/resources.h see man pages for
      C{getpriority} and C{setpriority}
"""

import ctypes
import os
from ctypes.util import find_library
from vsc.fancylogger import getLogger, setLogLevelDebug

_logger = getLogger("affinity")

_libc_lib = find_library('c')
_libc = ctypes.cdll.LoadLibrary(_libc_lib)

#/* Type for array elements in 'cpu_set_t'.  */
#typedef unsigned long int __cpu_mask;
cpu_mask_t = ctypes.c_ulong

##define __CPU_SETSIZE  1024
##define __NCPUBITS     (8 * sizeof(__cpu_mask))
CPU_SETSIZE = 1024
NCPUBITS = 8 * ctypes.sizeof(cpu_mask_t)
NMASKBITS = CPU_SETSIZE / NCPUBITS

#/* Priority limits.  */
示例#60
0
# You should have received a copy of the GNU General Public License
# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
#
"""
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 platform
from vsc import fancylogger

from easybuild.tools.systemtools import get_shared_lib_ext, get_os_name, get_os_type, get_os_version

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

# constants that can be used in easyconfig
EASYCONFIG_CONSTANTS = [
    ('SYS_PYTHON_VERSION', platform.python_version(),
     "System Python version (platform.python_version())"),
    ('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"),
]


def constant_documentation():
    """Generate the easyconfig constant documentation"""
    indent_l0 = " " * 2
    indent_l1 = indent_l0 + " " * 2