summaryrefslogblamecommitdiffstats
path: root/wscript
blob: 6626fafb7476831dfe3dd59421a2fb10b663c65e (plain) (tree)









































                                                                               

                                                        












































































































                                                                          
























                                                             



























































































                                                                          
                                                 



                                                       

                                                 
                                                          
                            















































































































                                                                                                                      

                                              








































                                                                                
                                               
                      




                                                                           

                                                 







                                                                               













































































































                                                                             





                                                                              








                                                    
                                          































































                                                                   
                                                           


















                                                                          
                             






























                                                                           
                                                

































                                                                    


                                                                         



























































































































































































                                                                                       
                                                         














                                                                                
                                                         

















                                                                                         
                                                         









                                                                             


                                                                           
































































































































                                                                               


                                       

                                
                                                                              















                                                                           

                   
                                              



















                                                                            




                                                          












































































































































































































                                                                                                                                                                                                                                                                   

                                             












                                                                              
                                    

                           
                                   


                                   


                                                                              
                                                                          
 



































































































































                                                                                             
                                            




































































                                                                                
#!/usr/bin/env python

# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (C) 2020 Hesham Almatary <Hesham.Almatary@cl.cam.ac.uk>
# Copyright (C) 2019, 2020 embedded brains GmbH (http://www.embedded-brains.de)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from __future__ import print_function

import pickle
import os
import re
import stat
import string
import sys

try:
    import configparser
except:
    import ConfigParser as configparser

from waflib.TaskGen import after, before_method, feature

is_windows_host = os.name == "nt" or sys.platform in ["msys", "cygwin"]
default_prefix = "/opt/rtems/6"
compilers = ["gcc", "clang"]
items = {}
bsps = {}


def no_unicode(value):
    if sys.version_info[0] > 2:
        return value
    if isinstance(value, unicode):
        return str(value)
    return value


class VersionControlKeyHeader:
    _content = None

    @staticmethod
    def write(bld, filename):
        if VersionControlKeyHeader._content is None:
            from waflib.Build import Context
            from waflib.Errors import WafError

            content = """/*
 * Automatically generated. Do not edit.
 */
#if !defined(_RTEMS_VERSION_VC_KEY_H_)
#define _RTEMS_VERSION_VC_KEY_H_
"""
            try:
                rev = bld.cmd_and_log(
                    "git rev-parse HEAD", quiet=Context.STDOUT
                ).strip()
                content += """#define RTEMS_VERSION_VC_KEY "{}"
""".format(
                    rev
                )
            except WafError:
                content += """/* No version control key found; release? */
"""
            content += """#endif
"""
            VersionControlKeyHeader._content = content
        f = bld.bldnode.make_node(filename)
        f.parent.mkdir()
        try:
            if content != f.read():
                f.write(VersionControlKeyHeader._content)
        except:
            f.write(VersionControlKeyHeader._content)


class EnvWrapper(object):
    def __init__(self, env):
        self._env = env

    def __getitem__(self, name):
        fields = name.split(":")
        v = self._env[fields[0]]
        try:
            fmt = "{:" + fields[1] + "}"
        except IndexError:
            fmt = "{}"
        if isinstance(v, list):
            return " ".join([fmt.format(w) for w in v])
        return fmt.format(v)


class Template(string.Template):
    idpattern = "[_a-z][_a-z0-9:#]*"


def _is_enabled_op_and(enabled, enabled_by):
    for next_enabled_by in enabled_by:
        if not _is_enabled(enabled, next_enabled_by):
            return False
    return True


def _is_enabled_op_not(enabled, enabled_by):
    return not _is_enabled(enabled, enabled_by)


def _is_enabled_op_or(enabled, enabled_by):
    for next_enabled_by in enabled_by:
        if _is_enabled(enabled, next_enabled_by):
            return True
    return False


_IS_ENABLED_OP = {
    "and": _is_enabled_op_and,
    "not": _is_enabled_op_not,
    "or": _is_enabled_op_or,
}


def _is_enabled(enabled, enabled_by):
    if isinstance(enabled_by, bool):
        return enabled_by
    if isinstance(enabled_by, list):
        return _is_enabled_op_or(enabled, enabled_by)
    if isinstance(enabled_by, dict):
        key, value = next(iter(enabled_by.items()))
        return _IS_ENABLED_OP[key](enabled, value)
    return enabled_by in enabled


def _asm_explicit_target(self, node):
    task = self.create_task(
        "asm", node, self.bld.bldnode.make_node(self.target)
    )
    try:
        self.compiled_tasks.append(task)
    except AttributeError:
        self.compiled_tasks = [task]
    return task


@feature("asm_explicit_target")
@before_method("process_source")
def _enable_asm_explicit_target(self):
    self.mappings = dict(self.mappings)  # Copy
    self.mappings[".S"] = _asm_explicit_target


@after("apply_link")
@feature("cprogram", "cxxprogram")
def process_start_files(self):
    if getattr(self, "start_files", False):
        self.link_task.dep_nodes.extend(self.bld.start_files)


class Item(object):
    def __init__(self, uid, data):
        self.uid = uid
        self.data = data
        self.links = self._init_links

    def _init_links(self):
        self._links = []
        for link in self.data["links"]:
            if link["role"] == "build-dependency":
                uid = link["uid"]
                if not os.path.isabs(uid):
                    uid = os.path.normpath(
                        os.path.join(os.path.dirname(self.uid), uid)
                    )
                self._links.append(items[uid])
        self.links = self._yield_links
        for link in self._links:
            yield link

    def _yield_links(self):
        for link in self._links:
            yield link

    def get_enabled_by(self):
        return self.data["enabled-by"]

    def defaults(self, enable, variant):
        if _is_enabled(enable, self.get_enabled_by()):
            for p in self.links():
                p.defaults(enable, variant)
            self.do_defaults(variant)

    def configure(self, conf, cic):
        if _is_enabled(conf.env.ENABLE, self.get_enabled_by()):
            self.prepare_configure(conf, cic)
            for p in self.links():
                p.configure(conf, cic)
            self.do_configure(conf, cic)

    def build(self, bld, bic):
        if _is_enabled(bld.env.ENABLE, self.get_enabled_by()):
            bic = self.prepare_build(bld, bic)
            for p in self.links():
                p.build(bld, bic)
            self.do_build(bld, bic)

    def do_defaults(self, variant):
        return

    def prepare_configure(self, conf, cic):
        return

    def do_configure(self, conf, cic):
        return

    def prepare_build(self, bld, bic):
        return bic

    def do_build(self, bld, bic):
        return

    def substitute(self, ctx, value):
        if isinstance(value, str):
            try:
                return Template(value).substitute(EnvWrapper(ctx.env))
            except Exception as e:
                ctx.fatal(
                    "In item '{}' substitution in '{}' failed: {}".format(
                        self.uid, value, e
                    )
                )
        return value

    def get(self, ctx, name):
        return self.substitute(ctx, self.data[name])

    def get_values(self, ctx, name):
        more = []
        for value in self.data[name]:
            more.extend(self.substitute(ctx, value).split())
        return more

    def install_target(self, bld):
        install_path = self.data["install-path"]
        if install_path:
            bld.install_files(install_path, self.get(bld, "target"))

    def install_files(self, bld):
        for install in self.data["install"]:
            bld.install_files(install["destination"], install["source"])

    def asm(self, bld, bic, source, target=None):
        if target is None:
            target = os.path.splitext(source)[0] + ".o"
        bld(
            asflags=self.data["asflags"],
            cppflags=self.data["cppflags"],
            features="asm_explicit_target asm c",
            includes=bic.includes + self.data["includes"],
            source=[source],
            target=target,
        )
        return target

    def cc(self, bld, bic, source, target=None, deps=[], cppflags=[]):
        if target is None:
            target = os.path.splitext(source)[0] + ".o"
        bld(
            cflags=self.data["cflags"],
            cppflags=cppflags + self.data["cppflags"],
            features="c",
            includes=bic.includes + self.data["includes"],
            rule="${CC} ${CFLAGS} ${CPPFLAGS} ${DEFINES_ST:DEFINES} ${CPPPATH_ST:INCPATHS} -c ${SRC[0]} -o ${TGT}",
            source=[source] + deps,
            target=target,
        )
        return target

    def cxx(self, bld, bic, source, target=None, deps=[], cppflags=[]):
        if target is None:
            target = os.path.splitext(source)[0] + ".o"
        bld(
            cppflags=cppflags + self.data["cppflags"],
            cxxflags=self.data["cxxflags"],
            features="cxx",
            includes=bic.includes + self.data["includes"],
            rule="${CXX} ${CXXFLAGS} ${CPPFLAGS} ${DEFINES_ST:DEFINES} ${CPPPATH_ST:INCPATHS} -c ${SRC[0]} -o ${TGT}",
            source=[source] + deps,
            target=target,
        )
        return target

    def link(self, bld, bic, cmd, source, target):
        from waflib.Task import Task

        class link(Task):
            def __init__(self, item, bic, cmd, env):
                super(link, self).__init__(self, env=env)
                self.cmd = cmd
                self.ldflags = bic.ldflags + item.data["ldflags"]
                self.stlib = item.data["stlib"]
                self.use = (
                    item.data["use-before"] + bic.use + item.data["use-after"]
                )

            def run(self):
                cmd = [self.cmd]
                cmd.extend(self.env.LINKFLAGS)
                cmd.extend([i.abspath() for i in self.inputs])
                cmd.append("-o" + self.outputs[0].abspath())
                cmd.extend(self.ldflags)
                cmd.append("-L.")
                cmd.extend(["-l" + l for l in self.stlib])
                cmd.extend(["-l" + l for l in self.use])
                cmd.extend(self.env.LDFLAGS)
                return self.exec_command(cmd)

            def scan(self):
                return (
                    [
                        self.generator.bld.bldnode.make_node("lib" + u + ".a")
                        for u in self.use
                    ],
                    [],
                )

        tsk = link(self, bic, cmd, bld.env)
        tsk.set_inputs([bld.bldnode.make_node(s) for s in source])
        tsk.set_outputs(bld.bldnode.make_node(target))
        bld.add_to_group(tsk)
        return target

    def link_cc(self, bld, bic, source, target):
        return self.link(bld, bic, bld.env.LINK_CC[0], source, target)

    def link_cxx(self, bld, bic, source, target):
        return self.link(bld, bic, bld.env.LINK_CXX[0], source, target)

    def gnatmake(self, bld, bic, objdir, objs, main, target):
        from waflib.Task import Task

        class gnatmake(Task):
            def __init__(self, bld, bic, objdir, objs, main, target, item):
                super(gnatmake, self).__init__(self, env=bld.env)
                self.objdir = objdir
                self.objs = [bld.bldnode.make_node(o) for o in objs]
                self.main = bld.path.make_node(main)
                self.set_inputs(self.objs + [self.main])
                self.set_outputs(bld.bldnode.make_node(target))
                self.adaflags = item.data["adaflags"]
                self.adaincludes = []
                for i in item.data["adaincludes"]:
                    self.adaincludes.append(bld.bldnode.make_node(i))
                    self.adaincludes.append(bld.path.make_node(i))
                self.ldflags = bic.ldflags + item.data["ldflags"]
                self.stlib = item.data["stlib"]
                self.use = (
                    item.data["use-before"] + bic.use + item.data["use-after"]
                )

            def run(self):
                cwd = self.get_cwd()
                cmd = [
                    self.env.GNATMAKE[0],
                    "-D",
                    self.objdir,
                    "-bargs",
                    "-Mgnat_main",
                    "-margs",
                ]
                cmd.extend(self.adaflags)
                cmd.extend(["-I" + i.path_from(cwd) for i in self.adaincludes])
                cmd.append("-cargs")
                cmd.extend(self.env.ABI_FLAGS)
                cmd.append("-largs")
                cmd.extend([o.path_from(cwd) for o in self.objs])
                cmd.extend(self.env.LINKFLAGS)
                cmd.extend(self.ldflags)
                cmd.append("-L.")
                cmd.extend(["-l" + l for l in self.stlib])
                cmd.extend(["-l" + l for l in self.use])
                cmd.extend(self.env.LDFLAGS)
                cmd.extend(["-margs", "-a"])
                cmd.append(self.main.abspath())
                cmd.append("-o")
                cmd.append(self.outputs[0].abspath())
                return self.exec_command(cmd)

            def scan(self):
                return (
                    [
                        self.generator.bld.bldnode.make_node("lib" + u + ".a")
                        for u in self.use
                    ],
                    [],
                )

        tsk = gnatmake(bld, bic, objdir, objs, main, target, self)
        bld.add_to_group(tsk)
        return target

    def ar(self, bld, source, target):
        bld(rule="${AR} ${ARFLAGS} ${TGT} ${SRC}", source=source, target=target)
        return target

    def gzip(self, bld, source):
        target = source + ".gz"
        bld(rule="${GZIP} < ${SRC} > ${TGT}", source=source, target=target)
        return target

    def xz(self, bld, source):
        target = source + ".xz"
        bld(rule="${XZ} < ${SRC} > ${TGT}", source=source, target=target)
        return target

    def tar(self, bld, source, remove, target):
        def run(task):
            import tarfile

            tar = tarfile.TarFile(
                task.outputs[0].abspath(), "w", format=tarfile.USTAR_FORMAT
            )
            srcpath = bld.path.abspath() + "/"
            bldpath = bld.bldnode.abspath() + "/"
            for src in task.inputs:
                src = src.abspath()
                dst = src
                for r in remove:
                    dst = src.replace(srcpath + r, "").replace(bldpath + r, "")
                tar.add(src, dst)
            tar.close()
            return 0

        bld(rule=run, source=source, target=target)
        return target

    def bin2c(self, bld, source, name=None, target=None):
        def run(task):
            cmd = [bld.env.BIN2C[0]]
            if name is not None:
                cmd.extend(["-N", name])
            cmd.append(task.inputs[0].abspath())
            cmd.append(task.outputs[0].abspath())
            return task.exec_command(cmd)

        path, base = os.path.split(source)
        if target is None:
            target = path + "/" + base.replace(".", "-")
        target_c = target + ".c"
        target_h = target + ".h"
        bld(rule=run, source=source, target=[target_c, target_h])
        return target_c, target_h

    def rtems_syms(self, bld, source, target):
        bld(
            rule='${RTEMS_SYMS} -e -C ${CC} -c "${CFLAGS}" -o ${TGT} ${SRC}',
            source=source,
            target=target,
        )
        return target

    def rtems_rap(self, bld, base, objects, libs, target):
        def run(task):
            cmd = [
                bld.env.RTEMS_LD[0],
                "-C",
                bld.env.CC[0],
                "-c",
                " ".join(bld.env.CFLAGS),
                "-O",
                "rap",
                "-b",
                task.inputs[0].abspath(),
                "-e",
                "rtems_main",
                "-s",
                "-o",
            ]
            cmd.append(task.outputs[0].abspath())
            cmd.extend([i.abspath() for i in task.inputs[1:]])
            cmd.extend(["-l" + l for l in libs])
            return task.exec_command(cmd)

        bld(rule=run, source=[base] + objects, target=target)
        return target


class GroupItem(Item):
    def __init__(self, uid, data):
        super(GroupItem, self).__init__(uid, data)

    def prepare_build(self, bld, bic):
        return BuildItemContext(
            bic.includes + self.get_values(bld, "includes"),
            self.data["use-before"] + bic.use + self.data["use-after"],
            bic.ldflags + self.get_values(bld, "ldflags"),
            bic.objects,
        )

    def do_build(self, bld, bic):
        self.install_files(bld)


class ConfigFileItem(Item):
    def __init__(self, uid, data):
        super(ConfigFileItem, self).__init__(uid, data)

    def do_configure(self, conf, cic):
        content = self.substitute(conf, self.data["content"])
        f = conf.bldnode.make_node(
            conf.env.VARIANT + "/" + self.get(conf, "target")
        )
        f.parent.mkdir()
        f.write(content)
        conf.env.append_value("cfg_files", f.abspath())

    def do_build(self, bld, bic):
        self.install_target(bld)


class ConfigHeaderItem(Item):
    def __init__(self, uid, data):
        super(ConfigHeaderItem, self).__init__(uid, data)

    def do_configure(self, conf, cic):
        conf.env.include_key = self.data["include-headers"]
        conf.write_config_header(
            conf.env.VARIANT + "/" + self.get(conf, "target"),
            guard=self.data["guard"],
            headers=True,
        )
        conf.env.include_key = None

    def do_build(self, bld, bic):
        self.install_target(bld)


class StartFileItem(Item):
    def __init__(self, uid, data):
        super(StartFileItem, self).__init__(uid, data)

    def do_build(self, bld, bic):
        tgt = self.asm(bld, bic, self.data["source"], self.get(bld, "target"))
        node = bld.bldnode.make_node(tgt)
        try:
            bld.start_files.append(node)
        except AttributeError:
            bld.start_files = [node]
        self.install_target(bld)


class ObjectsItem(Item):
    def __init__(self, uid, data):
        super(ObjectsItem, self).__init__(uid, data)

    def do_build(self, bld, bic):
        bld.objects(
            asflags=self.data["cppflags"],
            cflags=self.data["cflags"],
            cppflags=self.data["cppflags"],
            cxxflags=self.data["cxxflags"],
            includes=bic.includes + self.data["includes"],
            source=self.data["source"],
            target=self.uid,
        )
        bic.objects.append(self.uid)
        self.install_files(bld)


class BSPItem(Item):
    def __init__(self, uid, data):
        super(BSPItem, self).__init__(uid, data)
        arch_bsps = bsps.setdefault(data["arch"].strip(), {})
        arch_bsps[data["bsp"].strip()] = self

    def prepare_configure(self, conf, cic):
        conf.env.BSP_FAMILY = self.data["family"]

    def prepare_build(self, bld, bic):
        return BuildItemContext(
            bic.includes + bld.env.BSP_INCLUDES.split(), [], [], []
        )

    def do_build(self, bld, bic):
        bld(
            cflags=self.data["cflags"],
            cppflags=self.data["cppflags"],
            features="c cstlib",
            includes=bic.includes + self.data["includes"],
            install_path="${BSP_LIBDIR}",
            source=self.data["source"],
            target="rtemsbsp",
            use=bic.objects,
        )
        self.install_files(bld)


class LibraryItem(Item):
    def __init__(self, uid, data):
        super(LibraryItem, self).__init__(uid, data)

    def prepare_build(self, bld, bic):
        return BuildItemContext(bic.includes, [], [], [])

    def do_build(self, bld, bic):
        bld(
            cflags=self.data["cflags"],
            cppflags=self.data["cppflags"],
            cxxflags=self.data["cxxflags"],
            features="c cxx cstlib",
            includes=bic.includes + self.data["includes"],
            install_path=self.data["install-path"],
            source=self.data["source"],
            target=self.get(bld, "target"),
            use=bic.objects,
        )
        self.install_files(bld)


class TestProgramItem(Item):
    def __init__(self, uid, data):
        super(TestProgramItem, self).__init__(uid, data)
        name = uid.split("/")[-1].upper().replace("-", "_")
        self.exclude = "TEST_" + name + "_EXCLUDE"
        self.cppflags = "TEST_" + name + "_CPPFLAGS"

    def get_enabled_by(self):
        return [{"and": [{"not": self.exclude}, self.data["enabled-by"]]}]

    def prepare_build(self, bld, bic):
        return BuildItemContext(bic.includes, bic.use, bic.ldflags, [])

    def do_build(self, bld, bic):
        bld(
            cflags=self.data["cflags"],
            cppflags=bld.env[self.cppflags] + self.data["cppflags"],
            cxxflags=self.data["cxxflags"],
            features=self.data["features"],
            includes=bic.includes + self.data["includes"],
            install_path=None,
            ldflags=bic.ldflags + self.data["ldflags"],
            source=self.data["source"],
            start_files=True,
            stlib=self.data["stlib"],
            target=self.get(bld, "target"),
            use=self.data["use-before"] + bic.use + self.data["use-after"],
        )


class AdaTestProgramItem(TestProgramItem):
    def __init__(self, uid, data):
        super(AdaTestProgramItem, self).__init__(uid, data)

    def do_build(self, bld, bic):
        objs = []
        for s in self.data["source"]:
            objs.append(self.cc(bld, bic, s))
        self.gnatmake(
            bld,
            bic,
            self.data["ada-object-directory"],
            objs,
            self.data["ada-main"],
            self.data["target"],
        )


class OptionItem(Item):
    def __init__(self, uid, data):
        super(OptionItem, self).__init__(uid, data)

    @staticmethod
    def _is_variant(variants, variant):
        for pattern in variants:
            if re.match(pattern + "$", variant):
                return True
        return False

    def default_value(self, variant):
        value = self.data["default"]
        for default in self.data["default-by-variant"]:
            if OptionItem._is_variant(default["variants"], variant):
                value = default["value"]
                break
        if value is None:
            return value
        if isinstance(value, list):
            return " ".join(value)
        if isinstance(value, bool):
            return value
        return self.data["format"].format(value)

    def do_defaults(self, variant):
        value = self.default_value(variant)
        if value is None:
            return
        description = self.data["description"]
        if description:
            import textwrap

            tw = textwrap.TextWrapper()
            tw.drop_whitespace = True
            tw.initial_indent = "# "
            tw.subsequent_indent = "# "
            for line in tw.wrap(description):
                print(line)
        print("{} = {}".format(self.data["name"], value))

    def _do_append_test_cppflags(self, conf, name, state):
        conf.env.append_value(
            "TEST_" + name.upper().replace("-", "_") + "_CPPFLAGS", state
        )

    def _append_test_cppflags(self, conf, cic, value, arg):
        self._do_append_test_cppflags(conf, arg, value)
        return value

    def _assert_aligned(self, conf, cic, value, arg):
        if value % arg != 0:
            conf.fatal(
                "Value '{}' for option '{}' is not aligned by '{}'".format(
                    value, self.data["name"], arg
                )
            )
        return value

    def _assert_eq(self, conf, cic, value, arg):
        if value != arg:
            conf.fatal(
                "Value '{}' for option '{}' is not equal to {}".format(
                    value, self.data["name"], arg
                )
            )
        return value

    def _assert_ge(self, conf, cic, value, arg):
        if value < arg:
            conf.fatal(
                "Value '{}' for option '{}' is not greater than or equal to {}".format(
                    value, self.data["name"], arg
                )
            )
        return value

    def _assert_gt(self, conf, cic, value, arg):
        if value <= arg:
            conf.fatal(
                "Value '{}' for option '{}' is not greater than {}".format(
                    value, self.data["name"], arg
                )
            )
        return value

    def _assert_in_interval(self, conf, cic, value, arg):
        if value < arg[0] or value > arg[1]:
            conf.fatal(
                "Value '{}' for option '{}' is not in closed interval [{}, {}]".format(
                    value, self.data["name"], arg[0], arg[1]
                )
            )
        return value

    def _assert_int8(self, conf, cic, value, arg):
        return self._assert_in_interval(conf, cic, value, [-128, 127])

    def _assert_int16(self, conf, cic, value, arg):
        return self._assert_in_interval(conf, cic, value, [-32768, 32767])

    def _assert_int32(self, conf, cic, value, arg):
        return self._assert_in_interval(
            conf, cic, value, [-2147483648, 2147483647]
        )

    def _assert_int64(self, conf, cic, value, arg):
        return self._assert_in_interval(
            conf, cic, value, [-9223372036854775808, 9223372036854775807]
        )

    def _assert_le(self, conf, cic, value, arg):
        if value > arg:
            conf.fatal(
                "Value '{}' for option '{}' is not less than or equal to {}".format(
                    value, self.data["name"], arg
                )
            )
        return value

    def _assert_lt(self, conf, cic, value, arg):
        if value >= arg:
            conf.fatal(
                "Value '{}' for option '{}' is not less than {}".format(
                    value, self.data["name"], arg
                )
            )
        return value

    def _assert_ne(self, conf, cic, value, arg):
        if value == arg:
            conf.fatal(
                "Value '{}' for option '{}' is not unequal to {}".format(
                    value, self.data["name"], arg
                )
            )
        return value

    def _assert_power_of_two(self, conf, cic, value, arg):
        if value <= 0 or (value & (value - 1)) != 0:
            conf.fatal(
                "Value '{}' for option '{}' is not a power of two".format(
                    value, self.data["name"]
                )
            )
        return value

    def _assert_uint8(self, conf, cic, value, arg):
        return self._assert_in_interval(conf, cic, value, [0, 255])

    def _assert_uint16(self, conf, cic, value, arg):
        return self._assert_in_interval(conf, cic, value, [0, 65535])

    def _assert_uint32(self, conf, cic, value, arg):
        return self._assert_in_interval(conf, cic, value, [0, 4294967295])

    def _assert_uint64(self, conf, cic, value, arg):
        return self._assert_in_interval(
            conf, cic, value, [0, 18446744073709551615]
        )

    def _check_cc(self, conf, cic, value, arg):
        result = conf.check_cc(
            fragment=arg["fragment"],
            cflags=arg["cflags"],
            msg="Checking for " + arg["message"],
            mandatory=False,
        )
        return value and result

    def _check_cxx(self, conf, cic, value, arg):
        result = conf.check_cxx(
            fragment=arg["fragment"],
            cxxflags=arg["cxxflags"],
            msg="Checking for " + arg["message"],
            mandatory=False,
        )
        return value and result

    def _define_condition(self, conf, cic, value, arg):
        name = self.data["name"] if arg is None else arg
        conf.define_cond(name, value)
        return value

    def _define(self, conf, cic, value, arg):
        name = self.data["name"] if arg is None else arg
        if value is not None:
            conf.define(name, value)
        else:
            conf.define_cond(name, False)
        return value

    def _define_unquoted(self, conf, cic, value, arg):
        name = self.data["name"] if arg is None else arg
        if value is not None:
            conf.define(name, value, quote=False)
        else:
            conf.define_cond(name, False)
        return value

    def _env_append(self, conf, cic, value, arg):
        name = self.data["name"] if arg is None else arg
        conf.env.append_value(name, value)
        return value

    def _env_assign(self, conf, cic, value, arg):
        name = self.data["name"] if arg is None else arg
        conf.env[name] = value
        return value

    def _env_enable(self, conf, cic, value, arg):
        if value:
            name = self.data["name"] if arg is None else arg
            conf.env.append_value("ENABLE", name)
        return value

    def _find_program(self, conf, cic, value, arg):
        return conf.find_program(value, path_list=cic.path_list)

    def _format_and_define(self, conf, cic, value, arg):
        name = self.data["name"] if arg is None else arg
        if value is not None:
            conf.define(name, self.data["format"].format(value), quote=False)
        else:
            conf.define_cond(name, False)
        return value

    def _get_boolean(self, conf, cic, value, arg):
        name = self.data["name"]
        try:
            value = cic.cp.getboolean(conf.variant, name)
            cic.add_option(name)
        except configparser.NoOptionError:
            value = self.default_value(conf.env.ARCH_BSP)
        except ValueError as ve:
            conf.fatal(
                "Invalid value for configuration option {}: {}".format(name, ve)
            )
        return value

    def _get_env(self, conf, cic, value, arg):
        return conf.env[arg]

    def _get_integer(self, conf, cic, value, arg):
        name = self.data["name"]
        try:
            value = cic.cp.get(conf.variant, name)
            cic.add_option(name)
        except configparser.NoOptionError:
            value = self.default_value(conf.env.ARCH_BSP)
            if value is None:
                return value
        try:
            return eval(value)
        except Exception as e:
            conf.fatal(
                "Value '{}' for option '{}' is an invalid integer expression: {}".format(
                    value, name, e
                )
            )

    def _get_string(self, conf, cic, value, arg):
        name = self.data["name"]
        try:
            value = cic.cp.get(conf.variant, name)
            cic.add_option(name)
            value = no_unicode(value)
        except configparser.NoOptionError:
            value = self.default_value(conf.env.ARCH_BSP)
        return value

    def _script(self, conf, cic, value, arg):
        exec(arg)
        return value

    def _test_state_benchmark(self, conf, name):
        self._do_append_test_cppflags(conf, name, "-DTEST_STATE_BENCHMARK=1")

    def _test_state_exclude(self, conf, name):
        conf.env.append_value(
            "ENABLE", "TEST_" + name.upper().replace("-", "_") + "_EXCLUDE"
        )

    def _test_state_expected_fail(self, conf, name):
        self._do_append_test_cppflags(
            conf, name, "-DTEST_STATE_EXPECTED_FAIL=1"
        )

    def _test_state_indeterminate(self, conf, name):
        self._do_append_test_cppflags(
            conf, name, "-DTEST_STATE_INDETERMINATE=1"
        )

    def _test_state_user_input(self, conf, name):
        self._do_append_test_cppflags(conf, name, "-DTEST_STATE_USER_INPUT=1")

    def _set_test_state(self, conf, cic, value, arg):
        actions = {
            "benchmark": self._test_state_benchmark,
            "exclude": self._test_state_exclude,
            "expected-fail": self._test_state_expected_fail,
            "indeterminate": self._test_state_indeterminate,
            "user-input": self._test_state_user_input,
        }
        for k, v in arg.items():
            actions[v](conf, k)
        return value

    def _set_value(self, conf, cic, value, arg):
        return arg

    def _split(self, conf, cic, value, arg):
        return value.split()

    def _substitute(self, conf, cic, value, arg):
        if isinstance(value, list):
            return [self.substitute(conf, v) for v in value]
        else:
            return self.substitute(conf, value)

    def do_configure(self, conf, cic):
        actions = {
            "append-test-cppflags": self._append_test_cppflags,
            "assert-aligned": self._assert_aligned,
            "assert-eq": self._assert_eq,
            "assert-ge": self._assert_ge,
            "assert-gt": self._assert_gt,
            "assert-int8": self._assert_int8,
            "assert-int16": self._assert_int16,
            "assert-int32": self._assert_int32,
            "assert-int64": self._assert_int64,
            "assert-le": self._assert_le,
            "assert-lt": self._assert_lt,
            "assert-ne": self._assert_ne,
            "assert-power-of-two": self._assert_power_of_two,
            "assert-uint8": self._assert_uint8,
            "assert-uint16": self._assert_uint16,
            "assert-uint32": self._assert_uint32,
            "assert-uint64": self._assert_uint64,
            "check-cc": self._check_cc,
            "check-cxx": self._check_cxx,
            "define-condition": self._define_condition,
            "define": self._define,
            "define-unquoted": self._define_unquoted,
            "env-append": self._env_append,
            "env-assign": self._env_assign,
            "env-enable": self._env_enable,
            "find-program": self._find_program,
            "format-and-define": self._format_and_define,
            "get-boolean": self._get_boolean,
            "get-env": self._get_env,
            "get-integer": self._get_integer,
            "get-string": self._get_string,
            "script": self._script,
            "set-test-state": self._set_test_state,
            "set-value": self._set_value,
            "split": self._split,
            "substitute": self._substitute,
        }
        value = None
        for action in self.data["actions"]:
            for action_arg in action.items():
                value = actions[action_arg[0]](conf, cic, value, action_arg[1])


class ScriptItem(Item):
    def __init__(self, uid, data):
        super(ScriptItem, self).__init__(uid, data)

    def prepare_configure(self, conf, cic):
        script = self.data["prepare-configure"]
        if script:
            exec(script)

    def do_configure(self, conf, cic):
        script = self.data["do-configure"]
        if script:
            exec(script)

    def prepare_build(self, bld, bic):
        script = self.data["prepare-build"]
        if script:
            exec(script)
        return bic

    def do_build(self, bld, bic):
        script = self.data["do-build"]
        if script:
            exec(script)


class ConfigItemContext(object):
    def __init__(self, cp, path_list):
        self.cp = cp
        self.options = set()
        self.path_list = path_list

    def add_option(self, name):
        self.options.add(name.upper())


class BuildItemContext(object):
    def __init__(self, includes, use, ldflags, objects):
        self.includes = includes
        self.use = use
        self.ldflags = ldflags
        self.objects = objects


def is_one_item_newer(ctx, path, mtime):
    try:
        mtime2 = os.path.getmtime(path)
        if mtime <= mtime2:
            return True
        names = os.listdir(path)
    except Exception as e:
        ctx.fatal("Cannot access build specification directory: {}".format(e))
    for name in names:
        path2 = os.path.join(path, name)
        if name.endswith(".yml") and not name.startswith("."):
            mtime2 = os.path.getmtime(path2)
            if mtime <= mtime2:
                return True
        else:
            mode = os.lstat(path2).st_mode
            if stat.S_ISDIR(mode) and is_one_item_newer(ctx, path2, mtime):
                return True
    return False


def must_update_item_cache(ctx, path, cache_file):
    try:
        mtime = os.path.getmtime(cache_file)
    except:
        return True
    return is_one_item_newer(ctx, path, mtime)


def load_from_yaml(load, ctx, data_by_uid, base, path):
    try:
        names = os.listdir(path)
    except Exception as e:
        ctx.fatal("Cannot list build specification directory: {}".format(e))
    for name in names:
        path2 = os.path.join(path, name)
        if name.endswith(".yml") and not name.startswith("."):
            uid = "/" + os.path.relpath(path2, base).replace(".yml", "")
            with open(path2, "r") as f:
                data_by_uid[uid] = load(f.read())
        else:
            mode = os.lstat(path2).st_mode
            if stat.S_ISDIR(mode):
                load_from_yaml(load, ctx, data_by_uid, base, path2)


def load_items_in_directory(ctx, ctors, path):
    p = "c4che/" + re.sub(r"[^\w]", "_", path) + ".pickle"
    try:
        f = ctx.bldnode.make_node(p)
    except AttributeError:
        f = ctx.path.make_node("build/" + p)
    f.parent.mkdir()
    cache_file = f.abspath()
    data_by_uid = {}
    if must_update_item_cache(ctx, path, cache_file):
        from waflib import Logs

        Logs.warn(
            "Regenerate build specification cache (needs a couple of seconds)..."
        )

        #
        # Do not use a system provided yaml module and instead import it from
        # the project.  This reduces the host system requirements to a simple
        # Python 2.7 or 3 installation without extra modules.
        #
        if sys.version_info[0] == 2:
            yaml_path = "yaml/lib"
        else:
            yaml_path = "yaml/lib3"
        sys.path += [yaml_path]
        from yaml import safe_load

        load_from_yaml(safe_load, ctx, data_by_uid, path, path)
        with open(cache_file, "wb") as f:
            pickle.dump(data_by_uid, f)
    else:
        with open(cache_file, "rb") as f:
            data_by_uid = pickle.load(f)
    for uid, data in data_by_uid.items():
        if data["type"] == "build":
            items[uid] = ctors[data["build-type"]](uid, data)


def load_items(ctx, specs):
    if items:
        return

    ctors = {
        "ada-test-program": AdaTestProgramItem,
        "bsp": BSPItem,
        "config-file": ConfigFileItem,
        "config-header": ConfigHeaderItem,
        "test-program": TestProgramItem,
        "group": GroupItem,
        "library": LibraryItem,
        "objects": ObjectsItem,
        "option": OptionItem,
        "script": ScriptItem,
        "start-file": StartFileItem,
    }

    for path in specs:
        load_items_in_directory(ctx, ctors, path)


def load_items_from_options(ctx):
    specs = ctx.options.rtems_specs
    if specs is not None:
        specs = specs.split(",")
    else:
        specs = ["spec/build"]
    load_items(ctx, specs)
    return specs


def options(ctx):
    prefix = ctx.parser.get_option("--prefix")
    prefix.default = default_prefix
    prefix.help = "installation prefix [default: '{}']".format(default_prefix)
    rg = ctx.add_option_group("RTEMS options")
    rg.add_option(
        "--rtems-bsps",
        metavar="REGEX,...",
        help="a comma-separated list of Python regular expressions which select the desired BSP variants (e.g. 'sparc/erc32'); it may be used in the bsp_defaults and bsp_list commands",
    )
    rg.add_option(
        "--rtems-compiler",
        metavar="COMPILER",
        help="determines which compiler is used to list the BSP option defaults [default: 'gcc']; it may be used in the bsp_defaults command; valid compilers are: {}".format(
            ", ".join(compilers)
        ),
    )
    rg.add_option(
        "--rtems-config",
        metavar="CONFIG.INI,...",
        help="a comma-separated list of paths to the BSP configuration option files [default: 'config.ini']; default option values can be obtained via the bsp_defaults command; it may be used in the configure command",
    )
    rg.add_option(
        "--rtems-specs",
        metavar="SPECDIRS,...",
        help="a comma-separated list of directory paths to build specification items [default: 'spec/build']; it may be used in the bsp_defaults, bsp_list, and configure commands",
    )
    rg.add_option(
        "--rtems-tools",
        metavar="PREFIX,...",
        help="a comma-separated list of prefix paths to tools, e.g. compiler, linker, etc. [default: the installation prefix]; tools are searched in the prefix path and also in a 'bin' subdirectory of the prefix path; it may be used in the configure command",
    )
    rg.add_option(
        "--rtems-top-group",
        metavar="UID",
        help="the UID of the top-level group [default: '/grp']; it may be used in the bsp_defaults and configure commands",
    )


def check_environment(conf):
    for ev in [
        "AR",
        "AS",
        "ASFLAGS",
        "CC",
        "CFLAGS",
        "CPPFLAGS",
        "CXX",
        "CXXFLAGS",
        "IFLAGS",
        "LD",
        "LIB",
        "LINK_CC",
        "LINK_CXX",
        "LINKFLAGS",
        "MFLAGS",
        "RFLAGS",
        "WFLAGS",
    ]:
        if ev in os.environ:
            conf.msg("Environment variable set", ev, color="RED")


def load_config_files(ctx):
    cp = configparser.ConfigParser()
    files = ctx.options.rtems_config
    if files is not None:
        files = files.split(",")
    else:
        files = ["config.ini"]
    actual_files = cp.read(files)
    for o in files:
        if not o in actual_files:
            ctx.fatal("Option file '{}' was not readable".format(o))
    return cp


def inherit(conf, cp, bsp_map, arch, bsp, path):
    variant = arch + "/" + bsp
    if variant in path:
        path = " -> ".join(path + [variant])
        conf.fatal("Recursion in BSP options inheritance: {}".format(path))

    try:
        base = cp.get(variant, "INHERIT")
        cp.remove_option(variant, "INHERIT")
        base = no_unicode(base)
        base_variant = arch + "/" + base
        conf.msg(
            "Inherit options from '{}'".format(base_variant),
            variant,
            color="YELLOW",
        )
        if not cp.has_section(base_variant):
            if (not arch in bsps) or (not base in bsps[arch]):
                conf.fatal(
                    "BSP variant '{}' cannot inherit options from not existing variant '{}'".format(
                        variant, base_variant
                    )
                )
            bsp_map[bsp] = base
            return base
        top = inherit(conf, cp, bsp_map, arch, base, path + [variant])
        for i in cp.items(base_variant):
            name = i[0]
            if not cp.has_option(variant, name):
                cp.set(variant, name, i[1])
        bsp_map[bsp] = top
        return top
    except configparser.NoOptionError:
        return bsp_map.get(bsp, bsp)


def resolve_option_inheritance(conf, cp):
    bsp_map = {}
    for variant in cp.sections():
        variant = no_unicode(variant)
        try:
            arch, bsp = variant.split("/")
        except:
            conf.fatal(
                "Section name '{}' is a malformed 'arch/bsp' tuple".format(
                    variant
                )
            )
        inherit(conf, cp, bsp_map, arch, bsp, [])
    return bsp_map


def check_compiler(ctx, compiler):
    if compiler not in compilers:
        ctx.fatal(
            "Specified compiler '{}' is not one of {}".format(
                compiler, compilers
            )
        )


def get_compiler(conf, cp, variant):
    try:
        value = cp.get(variant, "COMPILER")
        cp.remove_option(variant, "COMPILER")
        value = no_unicode(value)
        check_compiler(conf, value)
    except configparser.NoOptionError:
        value = "gcc"
    return value


def configure_variant(conf, cp, bsp_map, path_list, top_group, variant):
    conf.msg("Configure board support package (BSP)", variant, color="YELLOW")

    conf.setenv(variant)
    arch, bsp_name = variant.split("/")
    bsp_base = bsp_map.get(bsp_name, bsp_name)
    arch_bsp = arch + "/" + bsp_base

    conf.env["ARCH"] = arch
    conf.env["ARCH_BSP"] = arch_bsp
    conf.env["BSP_BASE"] = bsp_base
    conf.env["BSP_NAME"] = bsp_name
    conf.env["DEST_OS"] = "rtems"

    # For the enabled-by evaluation we have to use the base BSP defined by the
    # build specification and not the BSP name provided by the user.
    conf.env["ENABLE"] = [get_compiler(conf, cp, variant), arch, arch_bsp]

    conf.env["TOP"] = conf.path.abspath()
    conf.env["TOPGROUP"] = top_group
    conf.env["VARIANT"] = variant

    cic = ConfigItemContext(cp, path_list)

    items[conf.env.TOPGROUP].configure(conf, cic)

    try:
        bsp_item = bsps[arch][bsp_base]
    except KeyError:
        conf.fatal("No such base BSP: '{}'".format(variant))
    bsp_item.configure(conf, cic)

    options = set([o[0].upper() for o in cp.items(variant)])
    for o in options.difference(cic.options):
        conf.msg("Unknown configuration option", o.upper(), color="RED")


def check_forbidden_options(ctx, opts):
    for o in opts:
        if getattr(ctx.options, "rtems_" + o):
            ctx.fatal(
                "The --rtems-{} command line option is not allowed in the {} command".format(
                    o.replace("_", "-"), ctx.cmd
                )
            )


def get_path_list(conf):
    path_list = []
    tools = conf.options.rtems_tools
    if tools is not None:
        for t in tools.split(","):
            path_list.extend([t + "/bin", t])
    path_list.append(conf.env.PREFIX + "/bin")
    path_list.extend(os.environ.get("PATH", "").split(os.pathsep))
    return path_list


def get_top_group(ctx):
    top_group = ctx.options.rtems_top_group
    if top_group is None:
        top_group = "/grp"
    if top_group not in items:
        ctx.fatal(
            "There is no top-level group with UID '{}' in the specification".format(
                top_group
            )
        )
    return top_group


def configure(conf):
    check_forbidden_options(conf, ["compiler"])
    check_environment(conf)
    conf.env["SPECS"] = load_items_from_options(conf)
    top_group = get_top_group(conf)
    cp = load_config_files(conf)
    bsp_map = resolve_option_inheritance(conf, cp)
    path_list = get_path_list(conf)
    variant_list = []
    for variant in cp.sections():
        variant = no_unicode(variant)
        variant_list.append(variant)
        configure_variant(conf, cp, bsp_map, path_list, top_group, variant)
    conf.setenv("")
    conf.env["VARIANTS"] = variant_list


def append_variant_builds(bld):
    import waflib.Options
    from waflib.Build import (
        BuildContext,
        CleanContext,
        InstallContext,
        UninstallContext,
    )

    for var in bld.env["VARIANTS"]:
        for c in (BuildContext, CleanContext, InstallContext, UninstallContext):
            name = c.__name__.replace("Context", "").lower()

            class magic(c):
                cmd = name + "_" + var
                variant = var

        waflib.Options.commands.append(bld.cmd + "_" + var)


def long_command_line_workaround(bld):
    if is_windows_host:
        bld.load("long_gcc")


def build(bld):
    if not bld.variant:
        check_forbidden_options(
            bld, ["compiler", "config", "specs", "tools", "top_group"]
        )
        load_items(bld, bld.env.SPECS)
        append_variant_builds(bld)
        return
    long_command_line_workaround(bld)
    bic = BuildItemContext(bld.env.ARCH_INCLUDES.split(), [], [], [])
    bsps[bld.env.ARCH][bld.env.BSP_BASE].build(bld, bic)
    items[bld.env.TOPGROUP].build(bld, bic)


def add_log_filter(name):
    msg = "'" + name + "' finished successfully"

    class Filter:
        def filter(self, rec):
            return not msg in rec.getMessage()

    import logging

    logging.getLogger("waflib").addFilter(Filter())


def get_white_list(ctx):
    white_list = ctx.options.rtems_bsps
    if white_list:
        white_list = white_list.split(",")
    return white_list


def is_in_white_list(variant, white_list):
    if not white_list:
        return True
    for pattern in white_list:
        if re.match(pattern + "$", variant):
            return True
    return False


def no_matches_error(ctx, white_list):
    if white_list:
        ctx.fatal(
            "No BSP matches with the specified patterns: '{}'".format(
                "', '".join(white_list)
            )
        )
    else:
        ctx.fatal("The build specification contains no BSPs")


def bsp_defaults(ctx):
    """get all options with default values for base BSP variants"""
    check_forbidden_options(ctx, ["config", "tools"])
    add_log_filter(ctx.cmd)
    load_items_from_options(ctx)
    top_group = get_top_group(ctx)
    white_list = get_white_list(ctx)
    compiler = ctx.options.rtems_compiler
    if compiler is not None:
        check_compiler(ctx, compiler)
    else:
        compiler = "gcc"
    first = True
    for arch in sorted(bsps):
        for bsp in sorted(bsps[arch]):
            variant = arch + "/" + bsp
            if is_in_white_list(variant, white_list):
                if not first:
                    print("")
                first = False
                print(
                    """[{}]
# Selects the compiler used to build the BSP (allowed values are "gcc" and
# "clang").  Please note that the values of some options depend on the compiler
# selection and changing the compiler may lead to unpredictable behaviour if
# these options are not adjusted as well.  Use the --rtems-compiler command line
# option to get the default values for a particular compiler via
# ./waf bsp_defaults.
COMPILER = {}""".format(
                        variant, compiler
                    )
                )
                enable = [compiler, arch, variant]
                items[top_group].defaults(enable, variant)
                bsps[arch][bsp].defaults(enable, variant)
    if first:
        no_matches_error(ctx, white_list)


def bsp_list(ctx):
    """lists base BSP variants"""
    check_forbidden_options(ctx, ["compiler", "config", "tools", "top_group"])
    add_log_filter(ctx.cmd)
    load_items_from_options(ctx)
    white_list = get_white_list(ctx)
    first = True
    for arch in sorted(bsps):
        for bsp in sorted(bsps[arch]):
            variant = arch + "/" + bsp
            if is_in_white_list(variant, white_list):
                first = False
                print(variant)
    if first:
        no_matches_error(ctx, white_list)