Commit 5bfc7ff2 authored by Ulf Magnusson's avatar Ulf Magnusson Committed by Carles Cufi
Browse files

kconfig: Fail in CI if Kconfig files reference undefined symbols



Add a helper module scripts/ci/list_undef_kconfig_refs.py that searches
the entire Kconfig tree and reports any references to undefined Kconfig
symbols. Use it to add a new check to scripts/ci/check-compliance.py.

Also allow list_undef_kconfig_refs.py to be run standalone.

Example error:

  Error: Found references to undefined Kconfig symbols:

  BAR
  ===

  - Referenced at Kconfig:12:

  config FOO
        bool
        depends on BAR

  - Referenced at Kconfig:16:

  menu "menu"
        depends on BAR

Signed-off-by: default avatarUlf Magnusson <Ulf.Magnusson@nordicsemi.no>
parent 88de5bd8
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -23,6 +23,10 @@ sh_special_args = {
    '_cwd': repository_path
}

# list_undef_kconfig_refs.py makes use of Kconfiglib
sys.path.append(os.path.join(repository_path, "scripts/kconfig"))
import list_undef_kconfig_refs


def init_logs():
    global logger
@@ -94,6 +98,23 @@ def run_checkpatch(tc, commit_range):

    return 0


def run_kconfig_undef_ref_check(tc, commit_range):
    # Parse the entire Kconfig tree, to make sure we see all symbols
    os.environ["ENV_VAR_BOARD_DIR"] = "boards/*/*"
    os.environ["ENV_VAR_ARCH"] = "*"

    # Returns the empty string if there are no references to undefined symbols
    msg = list_undef_kconfig_refs.report()
    if msg:
        failure = ET.SubElement(tc, "failure", type="failure",
                                message="undefined Kconfig symbols")
        failure.text = msg
        return 1

    return 0


def verify_signed_off(tc, commit):

    signed = []
@@ -166,6 +187,10 @@ tests = {
            "call": run_checkpatch,
            "name": "Code style check using checkpatch",
            },
        "checkkconfig": {
            "call": run_kconfig_undef_ref_check,
            "name": "Check Kconfig files for references to undefined symbols",
            },
        "documentation": {
            "call": check_doc,
            "name": "New warnings and errors when building documentation",
+88 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3

# Prints a message giving the locations of all references to undefined symbols
# within the Kconfig files. Prints nothing if there are no undefined
# references.
#
# The top-level Kconfig file is assumed to be called 'Kconfig'.

import sys

import kconfiglib


def report():
    # Returns a message (string) giving the locations of all references to
    # undefined Kconfig symbols within the Kconfig files, or the empty string
    # if there are no references to undefined symbols.
    #
    # Note: This function is called directly during CI.

    kconf = kconfiglib.Kconfig()

    undef_msg = ""

    for name, sym in kconf.syms.items():
        # - sym.nodes empty means the symbol is undefined (has no definition
        #   locations)
        #
        # - Due to Kconfig internals, numbers show up as undefined Kconfig
        #   symbols, but shouldn't be flagged
        #
        # - The MODULES symbol always exists but is unused in Zephyr
        if not sym.nodes and not is_num(name) and name != "MODULES":
            undef_msg += ref_locations_str(sym)

    if undef_msg:
        return "Error: Found references to undefined Kconfig symbols:\n" \
               + undef_msg

    return ""


def is_num(name):
    # Heuristic to see if a symbol name looks like a number. Internally,
    # everything is a symbol, only undefined symbols (which numbers usually
    # are) get their name as their value.

    try:
        int(name)
        return True
    except ValueError:
        # Require hex constants to be prefixed with 0x in Kconfig files, so
        # that we can tell that e.g. F00 is an undefined symbol reference.
        if not name.startswith(("0x", "0X")):
            return False

        try:
            int(name, 16)
            return True
        except ValueError:
            return False


def ref_locations_str(sym):
    # Prints all locations where sym is referenced, along with the Kconfig
    # definitions of the referencing items

    msg = "\n{}\n{}".format(sym.name, len(sym.name)*"=")

    def search(node):
        nonlocal msg

        while node:
            if sym in node.referenced():
                msg += "\n\n- Referenced at {}:{}:\n\n{}" \
                       .format(node.filename, node.linenr, node)

            if node.list:
                search(node.list)

            node = node.next

    search(sym.kconfig.top_node)
    return msg


if __name__ == "__main__":
    sys.stdout.write(report())