Back to home page

LXR

 
 

    


Warning, /wscript is written in an unsupported language. File is not indexed.

0001 #!/usr/bin/env python
0002 
0003 # SPDX-License-Identifier: BSD-2-Clause
0004 #
0005 # Copyright (C) 2020 Hesham Almatary <Hesham.Almatary@cl.cam.ac.uk>
0006 # Copyright (C) 2019, 2020 embedded brains GmbH & Co. KG
0007 # Copyright (C) 2024 Contemporary Software <chris@contemporary.software>
0008 #
0009 # Redistribution and use in source and binary forms, with or without
0010 # modification, are permitted provided that the following conditions
0011 # are met:
0012 # 1. Redistributions of source code must retain the above copyright
0013 #    notice, this list of conditions and the following disclaimer.
0014 # 2. Redistributions in binary form must reproduce the above copyright
0015 #    notice, this list of conditions and the following disclaimer in the
0016 #    documentation and/or other materials provided with the distribution.
0017 #
0018 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0020 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0021 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
0022 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
0023 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
0024 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0025 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
0026 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0027 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0028 # POSSIBILITY OF SUCH DAMAGE.
0029 
0030 import pickle
0031 import os
0032 import re
0033 import stat
0034 import string
0035 import sys
0036 
0037 try:
0038     import configparser
0039 except:
0040     import ConfigParser as configparser
0041 
0042 from waflib.TaskGen import after, before_method, feature
0043 
0044 version = {
0045     "__RTEMS_MAJOR__": "6",  # version
0046     "__RTEMS_MINOR__": "0",  # revision
0047     "__RTEMS_REVISION__": "0",  # not used
0048     "RTEMS_RELEASE_VERSION_LABEL": "not-released"
0049 }
0050 default_prefix = "/opt/rtems/" + version["__RTEMS_MAJOR__"]
0051 compilers = ["gcc", "clang"]
0052 items = {}
0053 bsps = {}
0054 
0055 
0056 def get_repo_release_label(ctx):
0057     from waflib.Build import Context
0058     from waflib.Errors import WafError
0059     release_label = "not-released"
0060     try:
0061         modified = ctx.cmd_and_log("git ls-files --modified",
0062                                    quiet=Context.BOTH).strip()
0063         release_label = ctx.cmd_and_log("git rev-parse HEAD",
0064                                         quiet=Context.BOTH).strip()
0065         if modified:
0066             release_label += "-modified"
0067     except WafError:
0068         pass
0069     return release_label
0070 
0071 
0072 #
0073 # This class is not aligned the project's release labelling. It will
0074 # not be changed for RTEMS 6. A VC key is a type of release label and
0075 # there are more types.
0076 #
0077 # The term `key` and a repo normally relates to the signing key of
0078 # repository which is security related and it is important we do not
0079 # imply this here. They are unrelated.
0080 #
0081 class VersionControlKeyHeader:
0082     _content = None
0083 
0084     @staticmethod
0085     def write(bld, filename):
0086         content = VersionControlKeyHeader._content
0087         if content is None:
0088             content = """/*
0089  * Automatically generated. Do not edit.
0090  *
0091  * Warning: This interface will be replaced with release labels
0092  *          after RTEMS 6.
0093  */
0094 #if !defined(_RTEMS_VERSION_VC_KEY_H_)
0095 #define _RTEMS_VERSION_VC_KEY_H_
0096 """
0097             release_label = bld.env.RTEMS_RELEASE_VERSION_LABEL
0098             if release_label == "not-released":
0099                 release_label = get_repo_release_label(bld)
0100             if release_label:
0101                 content += """#define RTEMS_VERSION_CONTROL_KEY "{}"
0102 """.format(release_label)
0103             content += """#endif
0104 """
0105             VersionControlKeyHeader._content = content
0106         f = bld.bldnode.make_node(filename)
0107         f.parent.mkdir()
0108         try:
0109             if content != f.read():
0110                 f.write(content)
0111         except:
0112             f.write(content)
0113 
0114 
0115 class EnvWrapper(object):
0116 
0117     def __init__(self, env):
0118         self._env = env
0119 
0120     def __getitem__(self, name):
0121         k, c, f = name.partition(":")
0122         v = self._env[k]
0123         fmt = "{" + c + f + "}"
0124         if isinstance(v, list):
0125             return " ".join([fmt.format(w) for w in v])
0126         return fmt.format(v)
0127 
0128 
0129 class Template(string.Template):
0130     idpattern = "[_A-Za-z][_A-Za-z0-9:#]*"
0131 
0132 
0133 _VAR_PATTERN = re.compile("\\$\\{?(" + Template.idpattern + ")\\}?$")
0134 
0135 
0136 def _is_enabled_op_and(enabled, enabled_by):
0137     for next_enabled_by in enabled_by:
0138         if not _is_enabled(enabled, next_enabled_by):
0139             return False
0140     return True
0141 
0142 
0143 def _is_enabled_op_not(enabled, enabled_by):
0144     return not _is_enabled(enabled, enabled_by)
0145 
0146 
0147 def _is_enabled_op_or(enabled, enabled_by):
0148     for next_enabled_by in enabled_by:
0149         if _is_enabled(enabled, next_enabled_by):
0150             return True
0151     return False
0152 
0153 
0154 _IS_ENABLED_OP = {
0155     "and": _is_enabled_op_and,
0156     "not": _is_enabled_op_not,
0157     "or": _is_enabled_op_or,
0158 }
0159 
0160 
0161 def _is_enabled(enabled, enabled_by):
0162     if isinstance(enabled_by, bool):
0163         return enabled_by
0164     if isinstance(enabled_by, list):
0165         return _is_enabled_op_or(enabled, enabled_by)
0166     if isinstance(enabled_by, dict):
0167         key, value = next(iter(enabled_by.items()))
0168         return _IS_ENABLED_OP[key](enabled, value)
0169     return enabled_by in enabled
0170 
0171 
0172 def _asm_explicit_target(self, node):
0173     task = self.create_task("asm", node,
0174                             self.bld.bldnode.make_node(self.target))
0175     try:
0176         self.compiled_tasks.append(task)
0177     except AttributeError:
0178         self.compiled_tasks = [task]
0179     return task
0180 
0181 
0182 @feature("asm_explicit_target")
0183 @before_method("process_source")
0184 def _enable_asm_explicit_target(self):
0185     self.mappings = dict(self.mappings)  # Copy
0186     self.mappings[".S"] = _asm_explicit_target
0187 
0188 
0189 @after("apply_link")
0190 @feature("cprogram", "cxxprogram")
0191 def process_start_files(self):
0192     if getattr(self, "start_files", False):
0193         self.link_task.dep_nodes.extend(self.bld.start_files)
0194 
0195 
0196 def make_tar_info_reproducible(info):
0197     info.uid = 0
0198     info.gid = 0
0199     info.mtime = 0
0200     info.uname = "root"
0201     info.gname = "root"
0202     return info
0203 
0204 
0205 class Item(object):
0206 
0207     def __init__(self, uid, data):
0208         self.uid = uid
0209         self.data = data
0210         self.links = self._init_links
0211 
0212     def _init_links(self):
0213         self._links = []
0214         for link in self.data["links"]:
0215             if link["role"] == "build-dependency":
0216                 uid = link["uid"]
0217                 if not os.path.isabs(uid):
0218                     uid = os.path.normpath(
0219                         os.path.join(os.path.dirname(self.uid),
0220                                      uid)).replace("\\", "/")
0221                 self._links.append(items[uid])
0222         self.links = self._yield_links
0223         for link in self._links:
0224             yield link
0225 
0226     def _yield_links(self):
0227         for link in self._links:
0228             yield link
0229 
0230     def get_enabled_by(self):
0231         return self.data["enabled-by"]
0232 
0233     def defaults(self, enabled):
0234         if _is_enabled(enabled, self.get_enabled_by()):
0235             for p in self.links():
0236                 p.defaults(enabled)
0237             self.do_defaults(enabled)
0238 
0239     def configure(self, conf, cic):
0240         if _is_enabled(conf.env.ENABLE, self.get_enabled_by()):
0241             self.prepare_configure(conf, cic)
0242             for p in self.links():
0243                 p.configure(conf, cic)
0244             try:
0245                 self.do_configure(conf, cic)
0246             except Exception as e:
0247                 raise type(e)(
0248                     "Configuration error related to item spec:{}: {}".format(
0249                         self.uid, str(e)))
0250 
0251     def build(self, bld, bic):
0252         if _is_enabled(bld.env.ENABLE, self.get_enabled_by()):
0253             bic = self.prepare_build(bld, bic)
0254             for p in self.links():
0255                 p.build(bld, bic)
0256             try:
0257                 self.do_build(bld, bic)
0258             except Exception as e:
0259                 raise type(e)("Build error related to item spec:{}: {}".format(
0260                     self.uid, str(e)))
0261 
0262     def do_defaults(self, enabled):
0263         return
0264 
0265     def prepare_configure(self, conf, cic):
0266         return
0267 
0268     def do_configure(self, conf, cic):
0269         return
0270 
0271     def prepare_build(self, bld, bic):
0272         return bic
0273 
0274     def do_build(self, bld, bic):
0275         return
0276 
0277     def substitute(self, ctx, value):
0278         if isinstance(value, str):
0279             try:
0280                 return Template(value).substitute(EnvWrapper(ctx.env))
0281             except Exception as e:
0282                 ctx.fatal(
0283                     "In item '{}' substitution in '{}' failed: {}".format(
0284                         self.uid, value, e))
0285         if isinstance(value, list):
0286             more = []
0287             for item in value:
0288                 if isinstance(item, str):
0289                     m = _VAR_PATTERN.match(item)
0290                 else:
0291                     m = None
0292                 if m:
0293                     more.extend(ctx.env[m.group(1).strip("{}")])
0294                 else:
0295                     more.append(self.substitute(ctx, item))
0296             return more
0297         return value
0298 
0299     def get(self, ctx, name):
0300         return self.substitute(ctx, self.data[name])
0301 
0302     def install_target(self, bld):
0303         install_path = self.data["install-path"]
0304         if install_path:
0305             bld.install_files(install_path, self.get(bld, "target"))
0306 
0307     def install_files(self, bld):
0308         for install in self.data["install"]:
0309             bld.install_files(install["destination"], install["source"])
0310 
0311     def asm(self, bld, bic, source, target=None):
0312         if target is None:
0313             target = os.path.splitext(source)[0] + ".o"
0314         bld(
0315             asflags=self.substitute(bld, self.data["asflags"]),
0316             cppflags=bic.cppflags +
0317             self.substitute(bld, self.data["cppflags"]),
0318             features="asm_explicit_target asm c",
0319             includes=bic.includes +
0320             self.substitute(bld, self.data["includes"]),
0321             source=[source],
0322             target=target,
0323         )
0324         return target
0325 
0326     def cc(self, bld, bic, source, target=None, deps=[], cppflags=[]):
0327         if target is None:
0328             target = os.path.splitext(source)[0] + ".o"
0329         bld(
0330             cflags=bic.cflags + self.substitute(bld, self.data["cflags"]),
0331             cppflags=bic.cppflags + cppflags +
0332             self.substitute(bld, self.data["cppflags"]),
0333             features="c",
0334             includes=bic.includes +
0335             self.substitute(bld, self.data["includes"]),
0336             rule=
0337             "${CC} ${CFLAGS} ${CPPFLAGS} ${DEFINES_ST:DEFINES} ${CPPPATH_ST:INCPATHS} -c ${SRC[0]} -o ${TGT}",
0338             source=[source] + deps,
0339             target=target,
0340         )
0341         return target
0342 
0343     def cxx(self, bld, bic, source, target=None, deps=[], cppflags=[]):
0344         if target is None:
0345             target = os.path.splitext(source)[0] + ".o"
0346         bld(
0347             cppflags=bic.cppflags + cppflags +
0348             self.substitute(bld, self.data["cppflags"]),
0349             cxxflags=bic.cxxflags +
0350             self.substitute(bld, self.data["cxxflags"]),
0351             features="cxx",
0352             includes=bic.includes +
0353             self.substitute(bld, self.data["includes"]),
0354             rule=
0355             "${CXX} ${CXXFLAGS} ${CPPFLAGS} ${DEFINES_ST:DEFINES} ${CPPPATH_ST:INCPATHS} -c ${SRC[0]} -o ${TGT}",
0356             source=[source] + deps,
0357             target=target,
0358         )
0359         return target
0360 
0361     def link(self, bld, bic, cmd, source, target):
0362         from waflib.Task import Task
0363 
0364         class link(Task):
0365 
0366             def __init__(self, item, bic, cmd, env, ldflags):
0367                 super(link, self).__init__(self, env=env)
0368                 self.cmd = cmd
0369                 self.ldflags = ldflags
0370                 self.stlib = item.data["stlib"]
0371                 self.use = (item.data["use-before"] + bic.use +
0372                             item.data["use-after"])
0373 
0374             def run(self):
0375                 cmd = [self.cmd]
0376                 cmd.extend(self.env.LINKFLAGS)
0377                 cmd.extend([i.abspath() for i in self.inputs])
0378                 cmd.append("-o" + self.outputs[0].abspath())
0379                 cmd.append("-L.")
0380                 cmd.extend(["-l" + l for l in self.stlib])
0381                 cmd.extend(["-l" + l for l in self.use])
0382                 cmd.extend(self.env.LDFLAGS)
0383                 cmd.extend(self.ldflags)
0384                 return self.exec_command(cmd)
0385 
0386             def scan(self):
0387                 return (
0388                     [
0389                         self.generator.bld.bldnode.make_node("lib" + u + ".a")
0390                         for u in self.use
0391                     ],
0392                     [],
0393                 )
0394 
0395         tsk = link(self, bic, cmd, bld.env,
0396                    bic.ldflags + self.substitute(bld, self.data["ldflags"]))
0397         tsk.set_inputs([bld.bldnode.make_node(s) for s in source])
0398         tsk.set_outputs(bld.bldnode.make_node(target))
0399         bld.add_to_group(tsk)
0400         return target
0401 
0402     def link_cc(self, bld, bic, source, target):
0403         return self.link(bld, bic, bld.env.LINK_CC[0], source, target)
0404 
0405     def link_cxx(self, bld, bic, source, target):
0406         return self.link(bld, bic, bld.env.LINK_CXX[0], source, target)
0407 
0408     def gnatmake(self, bld, bic, objdir, objs, main, target):
0409         from waflib.Task import Task
0410 
0411         class gnatmake(Task):
0412 
0413             def __init__(self, bld, bic, objdir, objs, main, target, item):
0414                 super(gnatmake, self).__init__(self, env=bld.env)
0415                 self.objdir = objdir
0416                 self.objs = [bld.bldnode.make_node(o) for o in objs]
0417                 self.main = bld.path.make_node(main)
0418                 self.set_inputs(self.objs + [self.main])
0419                 self.set_outputs(bld.bldnode.make_node(target))
0420                 self.adaflags = item.data["adaflags"]
0421                 self.adaincludes = []
0422                 for i in item.data["adaincludes"]:
0423                     self.adaincludes.append(bld.bldnode.make_node(i))
0424                     self.adaincludes.append(bld.path.make_node(i))
0425                 self.ldflags = bic.ldflags + item.data["ldflags"]
0426                 self.stlib = item.data["stlib"]
0427                 self.use = (item.data["use-before"] + bic.use +
0428                             item.data["use-after"])
0429 
0430             def run(self):
0431                 cwd = self.get_cwd()
0432                 cmd = [
0433                     self.env.GNATMAKE[0],
0434                     "-D",
0435                     self.objdir,
0436                     "-bargs",
0437                     "-Mgnat_main",
0438                     "-margs",
0439                 ]
0440                 cmd.extend(self.adaflags)
0441                 cmd.extend(["-I" + i.path_from(cwd) for i in self.adaincludes])
0442                 cmd.append("-cargs")
0443                 cmd.extend(self.env.ABI_FLAGS)
0444                 cmd.append("-largs")
0445                 cmd.extend([o.path_from(cwd) for o in self.objs])
0446                 cmd.extend(self.env.LINKFLAGS)
0447                 cmd.extend(self.ldflags)
0448                 cmd.append("-L.")
0449                 cmd.extend(["-l" + l for l in self.stlib])
0450                 cmd.extend(["-l" + l for l in self.use])
0451                 cmd.extend(self.env.LDFLAGS)
0452                 cmd.extend(["-margs", "-a"])
0453                 cmd.append(self.main.abspath())
0454                 cmd.append("-o")
0455                 cmd.append(self.outputs[0].abspath())
0456                 return self.exec_command(cmd)
0457 
0458             def scan(self):
0459                 return (
0460                     [
0461                         self.generator.bld.bldnode.make_node("lib" + u + ".a")
0462                         for u in self.use
0463                     ],
0464                     [],
0465                 )
0466 
0467         tsk = gnatmake(bld, bic, objdir, objs, main, target, self)
0468         bld.add_to_group(tsk)
0469         return target
0470 
0471     def ar(self, bld, source, target):
0472         bld(rule="${AR} ${ARFLAGS} ${TGT} ${SRC}",
0473             source=source,
0474             target=target)
0475         return target
0476 
0477     def gzip(self, bld, source):
0478         target = source + ".gz"
0479         bld(rule="${GZIP} -n < ${SRC} > ${TGT}", source=source, target=target)
0480         return target
0481 
0482     def xz(self, bld, source):
0483         target = source + ".xz"
0484         bld(rule="${XZ} < ${SRC} > ${TGT}", source=source, target=target)
0485         return target
0486 
0487     def tar(self, bld, source, remove, target):
0488 
0489         def run(task):
0490             import tarfile
0491 
0492             tar = tarfile.TarFile(task.outputs[0].abspath(),
0493                                   "w",
0494                                   format=tarfile.USTAR_FORMAT)
0495             srcpath = bld.path.abspath() + "/"
0496             bldpath = bld.bldnode.abspath() + "/"
0497             for src in task.inputs:
0498                 src = src.abspath()
0499                 dst = src
0500                 for r in remove:
0501                     dst = src.replace(srcpath + r, "").replace(bldpath + r, "")
0502                 tar.add(src, dst, filter=make_tar_info_reproducible)
0503             tar.close()
0504             return 0
0505 
0506         bld(rule=run, source=source, target=target)
0507         return target
0508 
0509     def bin2c(self, bld, source, name=None, target=None):
0510 
0511         def run(task):
0512             cmd = [bld.env.BIN2C[0]]
0513             if name is not None:
0514                 cmd.extend(["-N", name])
0515             cmd.append(task.inputs[0].abspath())
0516             cmd.append(task.outputs[0].abspath())
0517             return task.exec_command(cmd)
0518 
0519         path, base = os.path.split(source)
0520         if target is None:
0521             target = path + "/" + base.replace(".", "-")
0522         target_c = target + ".c"
0523         target_h = target + ".h"
0524         bld(rule=run, source=source, target=[target_c, target_h])
0525         return target_c, target_h
0526 
0527     def rtems_syms(self, bld, bic, source, target):
0528         syms_source = os.path.splitext(target)[0] + ".c"
0529         bld(
0530             rule='${RTEMS_SYMS} -e -S ${TGT} ${SRC}',
0531             source=source,
0532             target=syms_source,
0533         )
0534         return self.cc(bld, bic, syms_source, target)
0535 
0536     def rtems_rap(self, bld, base, objects, libs, target):
0537 
0538         def run(task):
0539             cmd = [
0540                 bld.env.RTEMS_LD[0],
0541                 "-C",
0542                 bld.env.CC[0],
0543                 "-c",
0544                 " ".join(bld.env.CFLAGS),
0545                 "-O",
0546                 "rap",
0547                 "-b",
0548                 task.inputs[0].abspath(),
0549                 "-e",
0550                 "rtems_main",
0551                 "-s",
0552                 "-o",
0553             ]
0554             cmd.append(task.outputs[0].abspath())
0555             cmd.extend([i.abspath() for i in task.inputs[1:]])
0556             cmd.extend(["-l" + l for l in libs])
0557             return task.exec_command(cmd)
0558 
0559         bld(rule=run, source=[base] + objects, target=target)
0560         return target
0561 
0562 
0563 class GroupItem(Item):
0564 
0565     def __init__(self, uid, data):
0566         super(GroupItem, self).__init__(uid, data)
0567 
0568     def prepare_build(self, bld, bic):
0569         return BuildItemContext(
0570             bic.includes + self.substitute(bld, self.data["includes"]),
0571             bic.cppflags + self.substitute(bld, self.data["cppflags"]),
0572             bic.cflags + self.substitute(bld, self.data["cflags"]),
0573             bic.cxxflags + self.substitute(bld, self.data["cxxflags"]),
0574             self.data["use-before"] + bic.use + self.data["use-after"],
0575             bic.ldflags + self.substitute(bld, self.data["ldflags"]),
0576             bic.objects,
0577         )
0578 
0579     def do_build(self, bld, bic):
0580         self.install_files(bld)
0581 
0582 
0583 class ConfigFileItem(Item):
0584 
0585     def __init__(self, uid, data):
0586         super(ConfigFileItem, self).__init__(uid, data)
0587 
0588     def do_configure(self, conf, cic):
0589         content = self.substitute(conf, self.data["content"])
0590         f = conf.bldnode.make_node(conf.env.VARIANT + "/" +
0591                                    self.get(conf, "target"))
0592         f.parent.mkdir()
0593         f.write(content)
0594         conf.env.append_value("cfg_files", f.abspath())
0595 
0596     def do_build(self, bld, bic):
0597         self.install_target(bld)
0598 
0599 
0600 class ConfigHeaderItem(Item):
0601 
0602     def __init__(self, uid, data):
0603         super(ConfigHeaderItem, self).__init__(uid, data)
0604 
0605     def do_configure(self, conf, cic):
0606         conf.env.include_key = self.data["include-headers"]
0607         conf.write_config_header(
0608             conf.env.VARIANT + "/" + self.get(conf, "target"),
0609             guard=self.data["guard"],
0610             headers=True,
0611         )
0612         conf.env.include_key = None
0613 
0614     def do_build(self, bld, bic):
0615         self.install_target(bld)
0616 
0617 
0618 class StartFileItem(Item):
0619 
0620     def __init__(self, uid, data):
0621         super(StartFileItem, self).__init__(uid, data)
0622 
0623     def do_build(self, bld, bic):
0624         source = self.data["source"]
0625         if os.path.splitext(source[0])[1] == ".S":
0626             tgt = self.asm(bld, bic, source, self.get(bld, "target"))
0627         else:
0628             tgt = self.cc(bld, bic, source, self.get(bld, "target"))
0629         node = bld.bldnode.make_node(tgt)
0630         try:
0631             bld.start_files.append(node)
0632         except AttributeError:
0633             bld.start_files = [node]
0634         self.install_target(bld)
0635 
0636 
0637 class ObjectsItem(Item):
0638 
0639     def __init__(self, uid, data):
0640         super(ObjectsItem, self).__init__(uid, data)
0641 
0642     def prepare_build(self, bld, bic):
0643         return BuildItemContext(
0644             bic.includes + self.substitute(bld, self.data["includes"]),
0645             bic.cppflags + self.substitute(bld, self.data["cppflags"]),
0646             bic.cflags + self.substitute(bld, self.data["cflags"]),
0647             bic.cxxflags + self.substitute(bld, self.data["cxxflags"]),
0648             bic.use,
0649             bic.ldflags,
0650             bic.objects,
0651         )
0652 
0653     def do_build(self, bld, bic):
0654         bld.objects(
0655             asflags=bic.cppflags,
0656             cflags=bic.cflags,
0657             cppflags=bic.cppflags,
0658             cxxflags=bic.cxxflags,
0659             includes=bic.includes,
0660             source=self.data["source"],
0661             target=self.uid,
0662         )
0663         bic.objects.append(self.uid)
0664         self.install_files(bld)
0665 
0666 
0667 class BSPItem(Item):
0668 
0669     def __init__(self, uid, data):
0670         super(BSPItem, self).__init__(uid, data)
0671         arch_bsps = bsps.setdefault(data["arch"].strip(), {})
0672         arch_bsps[data["bsp"].strip()] = self
0673 
0674     def prepare_build(self, bld, bic):
0675         return BuildItemContext(
0676             bic.includes + bld.env.BSP_INCLUDES +
0677             self.substitute(bld, self.data["includes"]),
0678             self.substitute(bld, self.data["cppflags"]),
0679             bld.env.BSP_CFLAGS + self.substitute(bld, self.data["cflags"]),
0680             [],
0681             [],
0682             [],
0683             [],
0684         )
0685 
0686     def do_build(self, bld, bic):
0687         bld(
0688             cflags=bic.cflags,
0689             cppflags=bic.cppflags,
0690             features="c cstlib",
0691             includes=bic.includes,
0692             install_path="${BSP_LIBDIR}",
0693             source=self.data["source"],
0694             target="rtemsbsp",
0695             use=bic.objects,
0696         )
0697         self.install_files(bld)
0698 
0699 
0700 class LibraryItem(Item):
0701 
0702     def __init__(self, uid, data):
0703         super(LibraryItem, self).__init__(uid, data)
0704 
0705     def prepare_build(self, bld, bic):
0706         return BuildItemContext(
0707             bic.includes + self.substitute(bld, self.data["includes"]),
0708             bic.cppflags + self.substitute(bld, self.data["cppflags"]),
0709             bic.cflags + self.substitute(bld, self.data["cflags"]),
0710             bic.cxxflags + self.substitute(bld, self.data["cxxflags"]),
0711             bic.use,
0712             bic.ldflags,
0713             [],
0714         )
0715 
0716     def do_build(self, bld, bic):
0717         bld(
0718             cflags=bic.cflags,
0719             cppflags=bic.cppflags,
0720             cxxflags=bic.cxxflags,
0721             features="c cxx cstlib",
0722             includes=bic.includes,
0723             install_path=self.data["install-path"],
0724             source=self.data["source"],
0725             target=self.get(bld, "target"),
0726             use=bic.objects,
0727         )
0728         self.install_files(bld)
0729 
0730 
0731 class TestProgramItem(Item):
0732 
0733     def __init__(self, uid, data):
0734         super(TestProgramItem, self).__init__(uid, data)
0735         name = uid.split("/")[-1].upper().replace("-", "_")
0736         self.exclude = "TEST_" + name + "_EXCLUDE"
0737         self.cppflags = "TEST_" + name + "_CPPFLAGS"
0738 
0739     def get_enabled_by(self):
0740         return [{"and": [{"not": self.exclude}, self.data["enabled-by"]]}]
0741 
0742     def prepare_build(self, bld, bic):
0743         return BuildItemContext(
0744             bic.includes + self.substitute(bld, self.data["includes"]),
0745             bic.cppflags + bld.env[self.cppflags] +
0746             self.substitute(bld, self.data["cppflags"]),
0747             bic.cflags + self.substitute(bld, self.data["cflags"]),
0748             bic.cxxflags + self.substitute(bld, self.data["cxxflags"]),
0749             self.data["use-before"] + bic.use + self.data["use-after"],
0750             bic.ldflags + self.substitute(bld, self.data["ldflags"]),
0751             [],
0752         )
0753 
0754     def do_build(self, bld, bic):
0755         bld(
0756             cflags=bic.cflags,
0757             cppflags=bic.cppflags,
0758             cxxflags=bic.cxxflags,
0759             features=self.data["features"],
0760             includes=bic.includes,
0761             install_path=None,
0762             ldflags=bic.ldflags,
0763             source=self.data["source"],
0764             start_files=True,
0765             stlib=self.data["stlib"],
0766             target=self.get(bld, "target"),
0767             use=bic.objects + bic.use,
0768         )
0769 
0770 
0771 class AdaTestProgramItem(TestProgramItem):
0772 
0773     def __init__(self, uid, data):
0774         super(AdaTestProgramItem, self).__init__(uid, data)
0775 
0776     def do_build(self, bld, bic):
0777         objs = []
0778         for s in self.data["source"]:
0779             objs.append(self.cc(bld, bic, s))
0780         self.gnatmake(
0781             bld,
0782             bic,
0783             self.data["ada-object-directory"],
0784             objs,
0785             self.data["ada-main"],
0786             self.data["target"],
0787         )
0788 
0789 
0790 class OptionItem(Item):
0791 
0792     def __init__(self, uid, data):
0793         super(OptionItem, self).__init__(uid, data)
0794 
0795     def default_value(self, enabled):
0796         value = None
0797         for default in self.data["default"]:
0798             if _is_enabled(enabled, default["enabled-by"]):
0799                 value = default["value"]
0800                 break
0801         if value is None:
0802             return value
0803         if isinstance(value, list):
0804             return " ".join(value)
0805         if isinstance(value, bool):
0806             return value
0807         return self.data["format"].format(value)
0808 
0809     def do_defaults(self, enabled):
0810         value = self.default_value(enabled)
0811         if value is None:
0812             return
0813         description = self.data["description"]
0814         if description:
0815             import textwrap
0816 
0817             tw = textwrap.TextWrapper()
0818             tw.drop_whitespace = True
0819             tw.initial_indent = "# "
0820             tw.subsequent_indent = "# "
0821             for line in tw.wrap(description):
0822                 print(line)
0823         print("{} = {}".format(self.data["name"], value))
0824 
0825     def _do_append_test_cppflags(self, conf, name, state):
0826         conf.env.append_value(
0827             "TEST_" + name.upper().replace("-", "_") + "_CPPFLAGS", state)
0828 
0829     def _append_test_cppflags(self, conf, cic, value, arg):
0830         self._do_append_test_cppflags(conf, arg, value)
0831         return value
0832 
0833     def _assert_aligned(self, conf, cic, value, arg):
0834         if value is not None and value % arg != 0:
0835             conf.fatal(
0836                 "Value '{}' for option '{}' is not aligned by '{}'".format(
0837                     value, self.data["name"], arg))
0838         return value
0839 
0840     def _assert_eq(self, conf, cic, value, arg):
0841         if value is not None and value != arg:
0842             conf.fatal("Value '{}' for option '{}' is not equal to {}".format(
0843                 value, self.data["name"], arg))
0844         return value
0845 
0846     def _assert_ge(self, conf, cic, value, arg):
0847         if value is not None and value < arg:
0848             conf.fatal(
0849                 "Value '{}' for option '{}' is not greater than or equal to {}"
0850                 .format(value, self.data["name"], arg))
0851         return value
0852 
0853     def _assert_gt(self, conf, cic, value, arg):
0854         if value is not None and value <= arg:
0855             conf.fatal(
0856                 "Value '{}' for option '{}' is not greater than {}".format(
0857                     value, self.data["name"], arg))
0858         return value
0859 
0860     def _assert_in_set(self, conf, cic, value, arg):
0861         if value is not None and value not in arg:
0862             conf.fatal(
0863                 "Value '{}' for option '{}' is not an element of {}".format(
0864                     value, self.data["name"], arg))
0865         return value
0866 
0867     def _assert_in_interval(self, conf, cic, value, arg):
0868         if value is not None and (value < arg[0] or value > arg[1]):
0869             conf.fatal(
0870                 "Value '{}' for option '{}' is not in closed interval [{}, {}]"
0871                 .format(value, self.data["name"], arg[0], arg[1]))
0872         return value
0873 
0874     def _assert_int8(self, conf, cic, value, arg):
0875         return self._assert_in_interval(conf, cic, value, [-128, 127])
0876 
0877     def _assert_int16(self, conf, cic, value, arg):
0878         return self._assert_in_interval(conf, cic, value, [-32768, 32767])
0879 
0880     def _assert_int32(self, conf, cic, value, arg):
0881         return self._assert_in_interval(conf, cic, value,
0882                                         [-2147483648, 2147483647])
0883 
0884     def _assert_int64(self, conf, cic, value, arg):
0885         return self._assert_in_interval(
0886             conf, cic, value, [-9223372036854775808, 9223372036854775807])
0887 
0888     def _assert_le(self, conf, cic, value, arg):
0889         if value is not None and value > arg:
0890             conf.fatal(
0891                 "Value '{}' for option '{}' is not less than or equal to {}".
0892                 format(value, self.data["name"], arg))
0893         return value
0894 
0895     def _assert_lt(self, conf, cic, value, arg):
0896         if value is not None and value >= arg:
0897             conf.fatal("Value '{}' for option '{}' is not less than {}".format(
0898                 value, self.data["name"], arg))
0899         return value
0900 
0901     def _assert_ne(self, conf, cic, value, arg):
0902         if value is not None and value == arg:
0903             conf.fatal(
0904                 "Value '{}' for option '{}' is not unequal to {}".format(
0905                     value, self.data["name"], arg))
0906         return value
0907 
0908     def _assert_power_of_two(self, conf, cic, value, arg):
0909         if value is not None and (value <= 0 or (value & (value - 1)) != 0):
0910             conf.fatal(
0911                 "Value '{}' for option '{}' is not a power of two".format(
0912                     value, self.data["name"]))
0913         return value
0914 
0915     def _assert_uint8(self, conf, cic, value, arg):
0916         return self._assert_in_interval(conf, cic, value, [0, 255])
0917 
0918     def _assert_uint16(self, conf, cic, value, arg):
0919         return self._assert_in_interval(conf, cic, value, [0, 65535])
0920 
0921     def _assert_uint32(self, conf, cic, value, arg):
0922         return self._assert_in_interval(conf, cic, value, [0, 4294967295])
0923 
0924     def _assert_uint64(self, conf, cic, value, arg):
0925         return self._assert_in_interval(conf, cic, value,
0926                                         [0, 18446744073709551615])
0927 
0928     def _check_cc(self, conf, cic, value, arg):
0929         result = conf.check_cc(
0930             fragment=arg["fragment"],
0931             cflags=arg["cflags"],
0932             msg="Checking for " + arg["message"],
0933             mandatory=False,
0934         )
0935         return value and result
0936 
0937     def _check_cxx(self, conf, cic, value, arg):
0938         result = conf.check_cxx(
0939             fragment=arg["fragment"],
0940             cxxflags=arg["cxxflags"],
0941             msg="Checking for " + arg["message"],
0942             mandatory=False,
0943         )
0944         return value and result
0945 
0946     def _comment(self, conf, cic, value, arg):
0947         return value
0948 
0949     def _define_condition(self, conf, cic, value, arg):
0950         name = self.data["name"] if arg is None else arg
0951         conf.define_cond(name, value)
0952         return value
0953 
0954     def _define(self, conf, cic, value, arg):
0955         name = self.data["name"] if arg is None else arg
0956         if value is not None:
0957             conf.define(name, value)
0958         else:
0959             conf.define_cond(name, False)
0960         return value
0961 
0962     def _define_unquoted(self, conf, cic, value, arg):
0963         name = self.data["name"] if arg is None else arg
0964         if value is not None:
0965             conf.define(name, value, quote=False)
0966         else:
0967             conf.define_cond(name, False)
0968         return value
0969 
0970     def _env_append(self, conf, cic, value, arg):
0971         name = self.data["name"] if arg is None else arg
0972         conf.env.append_value(name, value)
0973         return value
0974 
0975     def _env_assign(self, conf, cic, value, arg):
0976         name = self.data["name"] if arg is None else arg
0977         conf.env[name] = value
0978         return value
0979 
0980     def _env_enable(self, conf, cic, value, arg):
0981         if value:
0982             name = self.data["name"] if arg is None else arg
0983             conf.env.append_value("ENABLE", name)
0984         return value
0985 
0986     def _find_program(self, conf, cic, value, arg):
0987         return conf.find_program(value, path_list=cic.path_list)
0988 
0989     def _format_and_define(self, conf, cic, value, arg):
0990         name = self.data["name"] if arg is None else arg
0991         if value is not None:
0992             conf.define(name, self.data["format"].format(value), quote=False)
0993         else:
0994             conf.define_cond(name, False)
0995         return value
0996 
0997     def _get_boolean(self, conf, cic, value, arg):
0998         name = self.data["name"]
0999         try:
1000             value = cic.cp.getboolean(conf.variant, name)
1001             cic.add_option(name)
1002         except configparser.NoOptionError:
1003             value = self.default_value(conf.env.ENABLE)
1004         except ValueError as ve:
1005             conf.fatal("Invalid value for configuration option {}: {}".format(
1006                 name, ve))
1007         return value
1008 
1009     def _get_env(self, conf, cic, value, arg):
1010         return conf.env[arg]
1011 
1012     def _get_integer(self, conf, cic, value, arg):
1013         name = self.data["name"]
1014         try:
1015             value = cic.cp.get(conf.variant, name)
1016             cic.add_option(name)
1017         except configparser.NoOptionError:
1018             value = self.default_value(conf.env.ENABLE)
1019         if not value:
1020             return None
1021         try:
1022             return eval(value)
1023         except Exception as e:
1024             conf.fatal(
1025                 "Value '{}' for option '{}' is an invalid integer expression: {}"
1026                 .format(value, name, e))
1027 
1028     def _get_string(self, conf, cic, value, arg):
1029         name = self.data["name"]
1030         try:
1031             value = cic.cp.get(conf.variant, name)
1032             cic.add_option(name)
1033         except configparser.NoOptionError:
1034             value = self.default_value(conf.env.ENABLE)
1035         return value
1036 
1037     def _script(self, conf, cic, value, arg):
1038         local_variables = {
1039             "self": self,
1040             "conf": conf,
1041             "cic": cic,
1042             "value": value
1043         }
1044         exec(arg, None, local_variables)
1045         return local_variables["value"]
1046 
1047     def _test_state_benchmark(self, conf, name):
1048         self._do_append_test_cppflags(conf, name, "-DTEST_STATE_BENCHMARK=1")
1049 
1050     def _test_state_exclude(self, conf, name):
1051         conf.env.append_value(
1052             "ENABLE", "TEST_" + name.upper().replace("-", "_") + "_EXCLUDE")
1053 
1054     def _test_state_expected_fail(self, conf, name):
1055         self._do_append_test_cppflags(conf, name,
1056                                       "-DTEST_STATE_EXPECTED_FAIL=1")
1057 
1058     def _test_state_indeterminate(self, conf, name):
1059         self._do_append_test_cppflags(conf, name,
1060                                       "-DTEST_STATE_INDETERMINATE=1")
1061 
1062     def _test_state_user_input(self, conf, name):
1063         self._do_append_test_cppflags(conf, name, "-DTEST_STATE_USER_INPUT=1")
1064 
1065     def _set_test_state(self, conf, cic, value, arg):
1066         actions = {
1067             "benchmark": self._test_state_benchmark,
1068             "exclude": self._test_state_exclude,
1069             "expected-fail": self._test_state_expected_fail,
1070             "indeterminate": self._test_state_indeterminate,
1071             "user-input": self._test_state_user_input,
1072         }
1073         action = actions[arg["state"]]
1074         for test in arg["tests"]:
1075             action(conf, test)
1076         return value
1077 
1078     def _set_value(self, conf, cic, value, arg):
1079         return arg
1080 
1081     def _set_value_enabled_by(self, conf, cic, value, arg):
1082         for value_enabled_by in arg:
1083             if _is_enabled(conf.env.ENABLE, value_enabled_by["enabled-by"]):
1084                 return value_enabled_by["value"]
1085         return None
1086 
1087     def _split(self, conf, cic, value, arg):
1088         return value.split()
1089 
1090     def _substitute(self, conf, cic, value, arg):
1091         if isinstance(value, list):
1092             return [self.substitute(conf, v) for v in value]
1093         else:
1094             return self.substitute(conf, value)
1095 
1096     def do_configure(self, conf, cic):
1097         actions = {
1098             "append-test-cppflags": self._append_test_cppflags,
1099             "assert-aligned": self._assert_aligned,
1100             "assert-eq": self._assert_eq,
1101             "assert-ge": self._assert_ge,
1102             "assert-gt": self._assert_gt,
1103             "assert-in-set": self._assert_in_set,
1104             "assert-int8": self._assert_int8,
1105             "assert-int16": self._assert_int16,
1106             "assert-int32": self._assert_int32,
1107             "assert-int64": self._assert_int64,
1108             "assert-le": self._assert_le,
1109             "assert-lt": self._assert_lt,
1110             "assert-ne": self._assert_ne,
1111             "assert-power-of-two": self._assert_power_of_two,
1112             "assert-uint8": self._assert_uint8,
1113             "assert-uint16": self._assert_uint16,
1114             "assert-uint32": self._assert_uint32,
1115             "assert-uint64": self._assert_uint64,
1116             "check-cc": self._check_cc,
1117             "check-cxx": self._check_cxx,
1118             "comment": self._comment,
1119             "define-condition": self._define_condition,
1120             "define": self._define,
1121             "define-unquoted": self._define_unquoted,
1122             "env-append": self._env_append,
1123             "env-assign": self._env_assign,
1124             "env-enable": self._env_enable,
1125             "find-program": self._find_program,
1126             "format-and-define": self._format_and_define,
1127             "get-boolean": self._get_boolean,
1128             "get-env": self._get_env,
1129             "get-integer": self._get_integer,
1130             "get-string": self._get_string,
1131             "script": self._script,
1132             "set-test-state": self._set_test_state,
1133             "set-value": self._set_value,
1134             "set-value-enabled-by": self._set_value_enabled_by,
1135             "split": self._split,
1136             "substitute": self._substitute,
1137         }
1138         value = None
1139         for action in self.data["actions"]:
1140             for action_arg in action.items():
1141                 value = actions[action_arg[0]](conf, cic, value, action_arg[1])
1142 
1143 
1144 class ScriptItem(Item):
1145 
1146     def __init__(self, uid, data):
1147         super(ScriptItem, self).__init__(uid, data)
1148 
1149     def prepare_configure(self, conf, cic):
1150         script = self.data["prepare-configure"]
1151         if script:
1152             exec(script)
1153 
1154     def do_configure(self, conf, cic):
1155         script = self.data["do-configure"]
1156         if script:
1157             exec(script)
1158 
1159     def prepare_build(self, bld, bic):
1160         script = self.data["prepare-build"]
1161         if script:
1162             exec(script)
1163         return bic
1164 
1165     def do_build(self, bld, bic):
1166         script = self.data["do-build"]
1167         if script:
1168             exec(script)
1169 
1170 
1171 class ConfigItemContext(object):
1172 
1173     def __init__(self, cp, path_list):
1174         self.cp = cp
1175         self.options = set()
1176         self.path_list = path_list
1177 
1178     def add_option(self, name):
1179         self.options.add(name.upper())
1180 
1181 
1182 class BuildItemContext(object):
1183 
1184     def __init__(self, includes, cppflags, cflags, cxxflags, use, ldflags,
1185                  objects):
1186         self.includes = includes
1187         self.cppflags = cppflags
1188         self.cflags = cflags
1189         self.cxxflags = cxxflags
1190         self.use = use
1191         self.ldflags = ldflags
1192         self.objects = objects
1193 
1194 
1195 def is_one_item_newer(ctx, path, mtime):
1196     try:
1197         mtime2 = os.path.getmtime(path)
1198         if mtime <= mtime2:
1199             return True
1200         names = os.listdir(path)
1201     except Exception as e:
1202         ctx.fatal("Cannot access build specification directory: {}".format(e))
1203     for name in names:
1204         path2 = os.path.join(path, name)
1205         if name.endswith(".yml") and not name.startswith("."):
1206             mtime2 = os.path.getmtime(path2)
1207             if mtime <= mtime2:
1208                 return True
1209         else:
1210             mode = os.lstat(path2).st_mode
1211             if stat.S_ISDIR(mode) and is_one_item_newer(ctx, path2, mtime):
1212                 return True
1213     return False
1214 
1215 
1216 def must_update_item_cache(ctx, path, cache_file):
1217     try:
1218         mtime = os.path.getmtime(cache_file)
1219     except:
1220         return True
1221     return is_one_item_newer(ctx, path, mtime)
1222 
1223 
1224 def load_from_yaml(load, ctx, data_by_uid, base, path):
1225     try:
1226         names = os.listdir(path)
1227     except Exception as e:
1228         ctx.fatal("Cannot list build specification directory: {}".format(e))
1229     for name in names:
1230         path2 = os.path.join(path, name)
1231         if name.endswith(".yml") and not name.startswith("."):
1232             uid = "/" + os.path.relpath(path2, base).replace(
1233                 ".yml", "").replace("\\", "/")
1234             with open(path2, "r") as f:
1235                 data_by_uid[uid] = load(f.read())
1236         else:
1237             mode = os.lstat(path2).st_mode
1238             if stat.S_ISDIR(mode):
1239                 load_from_yaml(load, ctx, data_by_uid, base, path2)
1240 
1241 
1242 def load_items_in_directory(ctx, ctors, path):
1243     p = re.sub(r"[^\w]", "_", path) + ".pickle"
1244     try:
1245         f = ctx.bldnode.make_node(p)
1246     except AttributeError:
1247         f = ctx.path.make_node("build/" + p)
1248     f.parent.mkdir()
1249     cache_file = f.abspath()
1250     data_by_uid = {}
1251     if must_update_item_cache(ctx, path, cache_file):
1252         from waflib import Logs
1253 
1254         Logs.warn(
1255             "Regenerate build specification cache (needs a couple of seconds)..."
1256         )
1257 
1258         #
1259         # Do not use a system provided yaml module and instead import it from
1260         # the project.  This reduces the host system requirements to a simple
1261         # Python 3 installation without extra modules.
1262         #
1263         sys.path.append("yaml/lib3")
1264         from yaml import safe_load
1265 
1266         load_from_yaml(safe_load, ctx, data_by_uid, path, path)
1267         with open(cache_file, "wb") as f:
1268             pickle.dump(data_by_uid, f)
1269     else:
1270         with open(cache_file, "rb") as f:
1271             data_by_uid = pickle.load(f)
1272     for uid, data in data_by_uid.items():
1273         if data["type"] == "build":
1274             items[uid] = ctors[data["build-type"]](uid, data)
1275 
1276 
1277 def load_items(ctx, specs):
1278     if items:
1279         return
1280 
1281     ctors = {
1282         "ada-test-program": AdaTestProgramItem,
1283         "bsp": BSPItem,
1284         "config-file": ConfigFileItem,
1285         "config-header": ConfigHeaderItem,
1286         "test-program": TestProgramItem,
1287         "group": GroupItem,
1288         "library": LibraryItem,
1289         "objects": ObjectsItem,
1290         "option": OptionItem,
1291         "script": ScriptItem,
1292         "start-file": StartFileItem,
1293     }
1294 
1295     for path in specs:
1296         load_items_in_directory(ctx, ctors, path)
1297 
1298 
1299 def load_items_from_options(ctx):
1300     specs = ctx.options.rtems_specs
1301     if specs is not None:
1302         specs = specs.split(",")
1303     else:
1304         specs = ["spec/build"]
1305     load_items(ctx, specs)
1306     return specs
1307 
1308 
1309 def options(ctx):
1310     load_version(ctx)
1311     prefix = ctx.parser.get_option("--prefix")
1312     prefix.default = default_prefix
1313     prefix.help = "installation prefix [default: '{}']".format(default_prefix)
1314     rg = ctx.add_option_group("RTEMS options")
1315     rg.add_option(
1316         "--rtems-bsps",
1317         metavar="REGEX,...",
1318         help=
1319         "a comma-separated list of Python regular expressions which select the desired BSP variants (e.g. 'sparc/erc32'); it may be used in the bspdefaults and bsps commands",
1320     )
1321     rg.add_option(
1322         "--rtems-compiler",
1323         metavar="COMPILER",
1324         help=
1325         "determines which compiler is used to list the BSP option defaults [default: 'gcc']; it may be used in the bspdefaults command; valid compilers are: {}"
1326         .format(", ".join(compilers)),
1327     )
1328     rg.add_option(
1329         "--rtems-config",
1330         metavar="CONFIG.INI,...",
1331         help=
1332         "a comma-separated list of paths to the BSP configuration option files [default: 'config.ini']; default option values can be obtained via the bspdefaults command; it may be used in the configure command",
1333     )
1334     rg.add_option(
1335         "--rtems-specs",
1336         metavar="SPECDIRS,...",
1337         help=
1338         "a comma-separated list of directory paths to build specification items [default: 'spec/build']; it may be used in the bspdefaults, bsps, and configure commands",
1339     )
1340     rg.add_option(
1341         "--rtems-tools",
1342         metavar="PREFIX,...",
1343         help=
1344         "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",
1345     )
1346     rg.add_option(
1347         "--rtems-top-group",
1348         metavar="UID",
1349         help=
1350         "the UID of the top-level group [default: '/grp']; it may be used in the bspdefaults and configure commands",
1351     )
1352 
1353 
1354 def check_environment(conf):
1355     for ev in [
1356             "AR",
1357             "AS",
1358             "ASFLAGS",
1359             "CC",
1360             "CFLAGS",
1361             "CPPFLAGS",
1362             "CXX",
1363             "CXXFLAGS",
1364             "IFLAGS",
1365             "LD",
1366             "LIB",
1367             "LINK_CC",
1368             "LINK_CXX",
1369             "LINKFLAGS",
1370             "MFLAGS",
1371             "RFLAGS",
1372             "WFLAGS",
1373     ]:
1374         if ev in os.environ:
1375             conf.msg("Environment variable set", ev, color="RED")
1376 
1377 
1378 def load_version(ctx):
1379     global default_prefix
1380 
1381     def _check_num(s):
1382         try:
1383             i = int(s)
1384         except:
1385             ctx.fatal(
1386                 "Invalid VERSION number: version number is not a number: " + s)
1387 
1388     cp = configparser.ConfigParser()
1389     version_file = "VERSION"
1390     version_major = None
1391     version_minor = None
1392     version_revision = None
1393     version_label = None
1394     prefix = None
1395     if cp.read([version_file]):
1396         try:
1397             value = cp.get("version", "revision")
1398             # The revision is <major>.<minor>[-label]
1399             # break is up and update the version
1400             if "." not in value:
1401                 ctx.fatal(
1402                     "Invalid VERSION revision: no number (dot) separator")
1403             # version-string => major.minor[.revsion][-label]
1404             vs = value.split(".", 2)
1405             _check_num(vs[0])
1406             version_major = vs[0]
1407             if "-" in vs[-1]:
1408                 ls = vs[-1].split("-", 1)
1409                 vs[-1] = ls[0]
1410                 version_label = ls[1]
1411             _check_num(vs[1])
1412             version_minor = vs[1]
1413             if len(vs) == 3:
1414                 _check_num(vs[2])
1415                 version_revision = vs[2]
1416             prefix = "/opt/rtems/" + version_major
1417         except configparser.NoOptionError:
1418             pass
1419     if version_label is None and version_revision is not None:
1420         ctx.fatal(
1421             "Invalid VERSION revision: a revision number requires a label")
1422     if version_major is not None:
1423         version["__RTEMS_MAJOR__"] = version_major
1424     if version_minor is not None:
1425         version["__RTEMS_MINOR__"] = version_minor
1426     if version_revision is not None:
1427         version["__RTEMS_REVISION__"] = version_revision
1428     if version_label is not None:
1429         version["RTEMS_RELEASE_VERSION_LABEL"] = version_label
1430     # Checking minor insures major and minor are valid
1431     if version_minor is None:
1432         version_label = get_repo_release_label(ctx)
1433     return version_label
1434 
1435 
1436 def configure_version(conf):
1437     version_label = load_version(conf)
1438     v_str = version["__RTEMS_MAJOR__"] + "." + version["__RTEMS_MINOR__"]
1439     if int(version["__RTEMS_REVISION__"]) != 0:
1440         v_str += "." + version["__RTEMS_REVISION__"]
1441     if version_label is not None and version_label != "":
1442             v_str += "." + version_label
1443     conf.msg("Configure RTEMS version", v_str, color="YELLOW")
1444 
1445 
1446 def load_config_files(ctx):
1447     cp = configparser.ConfigParser()
1448     files = ctx.options.rtems_config
1449     if files is not None:
1450         files = files.split(",")
1451     else:
1452         files = ["config.ini"]
1453     actual_files = cp.read(files)
1454     for o in files:
1455         if not o in actual_files:
1456             ctx.fatal("Option file '{}' was not readable".format(o))
1457     return cp
1458 
1459 
1460 def inherit(conf, cp, bsp_map, arch, bsp, path):
1461     variant = arch + "/" + bsp
1462     if variant in path:
1463         path = " -> ".join(path + [variant])
1464         conf.fatal("Recursion in BSP options inheritance: {}".format(path))
1465 
1466     try:
1467         base = cp.get(variant, "INHERIT")
1468         cp.remove_option(variant, "INHERIT")
1469         base_variant = arch + "/" + base
1470         conf.msg(
1471             "Inherit options from '{}'".format(base_variant),
1472             variant,
1473             color="YELLOW",
1474         )
1475         if not cp.has_section(base_variant):
1476             if (not arch in bsps) or (not base in bsps[arch]):
1477                 conf.fatal(
1478                     "BSP variant '{}' cannot inherit options from not existing variant '{}'"
1479                     .format(variant, base_variant))
1480             bsp_map[bsp] = base
1481             return base
1482         top = inherit(conf, cp, bsp_map, arch, base, path + [variant])
1483         for i in cp.items(base_variant):
1484             name = i[0]
1485             if not cp.has_option(variant, name):
1486                 cp.set(variant, name, i[1])
1487         bsp_map[bsp] = top
1488         return top
1489     except configparser.NoOptionError:
1490         return bsp_map.get(bsp, bsp)
1491 
1492 
1493 def resolve_option_inheritance(conf, cp):
1494     bsp_map = {}
1495     for variant in cp.sections():
1496         try:
1497             arch, bsp = variant.split("/")
1498         except:
1499             conf.fatal(
1500                 "Section name '{}' is a malformed 'arch/bsp' tuple".format(
1501                     variant))
1502         inherit(conf, cp, bsp_map, arch, bsp, [])
1503     return bsp_map
1504 
1505 
1506 def check_compiler(ctx, compiler):
1507     if compiler not in compilers:
1508         ctx.fatal("Specified compiler '{}' is not one of {}".format(
1509             compiler, compilers))
1510 
1511 
1512 def get_compiler(conf, cp, variant):
1513     try:
1514         value = cp.get(variant, "COMPILER")
1515         cp.remove_option(variant, "COMPILER")
1516         check_compiler(conf, value)
1517     except configparser.NoOptionError:
1518         value = "gcc"
1519     return value
1520 
1521 
1522 def configure_variant(conf, cp, bsp_map, path_list, top_group, variant):
1523     conf.msg("Configure board support package (BSP)", variant, color="YELLOW")
1524 
1525     conf.setenv(variant)
1526     arch, bsp_name = variant.split("/")
1527     bsp_base = bsp_map.get(bsp_name, bsp_name)
1528 
1529     try:
1530         bsp_item = bsps[arch][bsp_base]
1531     except KeyError:
1532         conf.fatal("No such base BSP: '{}'".format(variant))
1533 
1534     family = bsp_item.data["family"]
1535 
1536     arch_bsp = arch + "/" + bsp_base
1537     arch_family = arch + "/" + family
1538 
1539     for key, value in version.items():
1540         conf.env[key] = value
1541 
1542     conf.env["ARCH"] = arch
1543     conf.env["ARCH_BSP"] = arch_bsp
1544     conf.env["ARCH_FAMILY"] = arch_family
1545     conf.env["BSP_BASE"] = bsp_base
1546     conf.env["BSP_NAME"] = bsp_name
1547     conf.env["BSP_FAMILY"] = family
1548     conf.env["DEST_OS"] = "rtems"
1549 
1550     # For the enabled-by evaluation we have to use the base BSP defined by the
1551     # build specification and not the BSP name provided by the user.
1552     conf.env["ENABLE"] = [
1553         get_compiler(conf, cp, variant),
1554         arch,
1555         "bsps/" + arch_family,
1556         arch_bsp,
1557     ]
1558 
1559     conf.env["TOP"] = conf.path.abspath()
1560     conf.env["TOPGROUP"] = top_group
1561     conf.env["VARIANT"] = variant
1562 
1563     cic = ConfigItemContext(cp, path_list)
1564     items[conf.env.TOPGROUP].configure(conf, cic)
1565     bsp_item.configure(conf, cic)
1566 
1567     options = set([o[0].upper() for o in cp.items(variant)])
1568     for o in options.difference(cic.options):
1569         conf.msg("Unknown configuration option", o.upper(), color="RED")
1570 
1571 
1572 def check_forbidden_options(ctx, opts):
1573     for o in opts:
1574         if getattr(ctx.options, "rtems_" + o):
1575             ctx.fatal(
1576                 "The --rtems-{} command line option is not allowed in the {} command"
1577                 .format(o.replace("_", "-"), ctx.cmd))
1578 
1579 
1580 def get_path_list(conf):
1581     path_list = []
1582     tools = conf.options.rtems_tools
1583     if tools is not None:
1584         for t in tools.split(","):
1585             path_list.extend([t + "/bin", t])
1586     path_list.append(conf.env.PREFIX + "/bin")
1587     path_list.extend(os.environ.get("PATH", "").split(os.pathsep))
1588     return path_list
1589 
1590 
1591 def get_top_group(ctx):
1592     top_group = ctx.options.rtems_top_group
1593     if top_group is None:
1594         top_group = "/grp"
1595     if top_group not in items:
1596         ctx.fatal(
1597             "There is no top-level group with UID '{}' in the specification".
1598             format(top_group))
1599     return top_group
1600 
1601 
1602 def configure(conf):
1603     check_forbidden_options(conf, ["compiler"])
1604     configure_version(conf)
1605     check_environment(conf)
1606     conf.env["SPECS"] = load_items_from_options(conf)
1607     top_group = get_top_group(conf)
1608     cp = load_config_files(conf)
1609     bsp_map = resolve_option_inheritance(conf, cp)
1610     path_list = get_path_list(conf)
1611     variant_list = []
1612     for variant in cp.sections():
1613         variant_list.append(variant)
1614         configure_variant(conf, cp, bsp_map, path_list, top_group, variant)
1615     conf.setenv("")
1616     conf.env["VARIANTS"] = variant_list
1617 
1618 
1619 def append_variant_builds(bld):
1620     import waflib.Options
1621     from waflib.Build import (
1622         BuildContext,
1623         CleanContext,
1624         InstallContext,
1625         UninstallContext,
1626     )
1627 
1628     for var in bld.env["VARIANTS"]:
1629         for c in (BuildContext, CleanContext, InstallContext,
1630                   UninstallContext):
1631             name = c.__name__.replace("Context", "").lower()
1632 
1633             class magic(c):
1634                 cmd = name + "_" + var
1635                 variant = var
1636 
1637         waflib.Options.commands.append(bld.cmd + "_" + var)
1638 
1639 
1640 def build(bld):
1641     if not bld.variant:
1642         check_forbidden_options(
1643             bld,
1644             ["compiler", "config", "specs", "tools", "top_group"],
1645         )
1646         load_items(bld, bld.env.SPECS)
1647         append_variant_builds(bld)
1648         return
1649     bic = BuildItemContext(bld.env.ARCH_INCLUDES.split(), [], [], [], [], [],
1650                            [])
1651     bsps[bld.env.ARCH][bld.env.BSP_BASE].build(bld, bic)
1652     items[bld.env.TOPGROUP].build(bld, bic)
1653 
1654 
1655 def add_log_filter(name):
1656     msg = "'" + name + "' finished successfully"
1657 
1658     class Filter:
1659 
1660         def filter(self, rec):
1661             return not msg in rec.getMessage()
1662 
1663     import logging
1664 
1665     logging.getLogger("waflib").addFilter(Filter())
1666 
1667 
1668 def get_white_list(ctx):
1669     white_list = ctx.options.rtems_bsps
1670     if white_list:
1671         white_list = white_list.split(",")
1672     return white_list
1673 
1674 
1675 def is_in_white_list(variant, white_list):
1676     if not white_list:
1677         return True
1678     for pattern in white_list:
1679         if re.match(pattern + "$", variant):
1680             return True
1681     return False
1682 
1683 
1684 def no_matches_error(ctx, white_list):
1685     if white_list:
1686         ctx.fatal("No BSP matches with the specified patterns: '{}'".format(
1687             "', '".join(white_list)))
1688     else:
1689         ctx.fatal("The build specification contains no BSPs")
1690 
1691 
1692 def bspdefaults(ctx):
1693     """get all options with default values for base BSP variants"""
1694     check_forbidden_options(ctx, ["config", "tools"])
1695     add_log_filter(ctx.cmd)
1696     load_items_from_options(ctx)
1697     top_group = get_top_group(ctx)
1698     white_list = get_white_list(ctx)
1699     compiler = ctx.options.rtems_compiler
1700     if compiler is not None:
1701         check_compiler(ctx, compiler)
1702     else:
1703         compiler = "gcc"
1704     first = True
1705     for arch in sorted(bsps):
1706         for bsp in sorted(bsps[arch]):
1707             variant = arch + "/" + bsp
1708             if is_in_white_list(variant, white_list):
1709                 if not first:
1710                     print("")
1711                 first = False
1712                 print("""[{}]
1713 # Selects the compiler used to build the BSP (allowed values are "gcc" and
1714 # "clang").  Please note that the values of some options depend on the compiler
1715 # selection and changing the compiler may lead to unpredictable behaviour if
1716 # these options are not adjusted as well.  Use the --rtems-compiler command line
1717 # option to get the default values for a particular compiler via
1718 # ./waf bspdefaults.
1719 COMPILER = {}""".format(variant, compiler))
1720                 enable = [compiler, arch, variant]
1721                 bsp_item = bsps[arch][bsp]
1722                 family = "bsps/" + arch + "/" + bsp_item.data["family"]
1723                 enabled = [compiler, arch, family, variant]
1724                 items[top_group].defaults(enabled)
1725                 bsp_item.defaults(enabled)
1726     if first:
1727         no_matches_error(ctx, white_list)
1728 
1729 
1730 def bsplist(ctx):
1731     """lists base BSP variants"""
1732     check_forbidden_options(ctx, ["compiler", "config", "tools", "top_group"])
1733     add_log_filter(ctx.cmd)
1734     load_items_from_options(ctx)
1735     white_list = get_white_list(ctx)
1736     first = True
1737     for arch in sorted(bsps):
1738         for bsp in sorted(bsps[arch]):
1739             variant = arch + "/" + bsp
1740             if is_in_white_list(variant, white_list):
1741                 first = False
1742                 print(variant)
1743     if first:
1744         no_matches_error(ctx, white_list)