Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

#!/usr/bin/env python3
import sys
import subprocess
import re
import os
import xml.etree.ElementTree as ET
from email.utils import parseaddr
import sh
import logging
import argparse
#from check_identity import verify_signed_off

if "ZEPHYR_BASE" not in os.environ:
    logging.error("$ZEPHYR_BASE environment variable undefined.\n")
    exit(1)

logger = None
DOCS_WARNING_FILE = "doc.warnings"

repository_path = os.environ['ZEPHYR_BASE']
sh_special_args = {
    '_tty_out': False,
    '_cwd': repository_path
}


def init_logs():
    global logger
    log_lev = os.environ.get('LOG_LEVEL', None)
    level = logging.INFO
    if log_lev == "DEBUG":
        level = logging.DEBUG
    elif log_lev == "ERROR":
        level = logging.ERROR

    console = logging.StreamHandler()
    format = logging.Formatter('%(levelname)-8s: %(message)s')
    console.setFormatter(format)
    logger = logging.getLogger('')
    logger.addHandler(console)
    logger.setLevel(level)

    logging.debug("Log init completed")

def parse_args():
    parser = argparse.ArgumentParser(
                description="Check for coding style and documentation warnings.")
    parser.add_argument('-c', '--commits', default=None,
            help="Commit range in the form: a..b")
    return parser.parse_args()

def get_shas(refspec):

    sha_list = sh.git("rev-list",
        '--max-count={0}'.format(-1 if "." in refspec else 1),
        refspec, **sh_special_args).split()

    return sha_list



def run_gitlint(tc, commit_range):
    proc = subprocess.Popen('gitlint --commits %s' %(commit_range),
            cwd=repository_path, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    msg = ""
    if proc.wait() != 0:
        msg = proc.stdout.read()

    if msg != "":
        failure = ET.SubElement(tc, 'failure', type="failure", message="commit message error on range: %s" %commit_range)
        failure.text = (msg.decode('utf8'))
        return 1

    return 0


def run_checkpatch(tc, commit_range):
    output = None
    out = ""

    diff = subprocess.Popen(('git', 'diff', '%s' %(commit_range)), stdout=subprocess.PIPE)
    try:
        output = subprocess.check_output(('%s/scripts/checkpatch.pl' %repository_path,
            '--mailback', '--no-tree', '-'), stdin=diff.stdout,
            stderr=subprocess.STDOUT, shell=True)

    except subprocess.CalledProcessError as ex:
        m = re.search("([1-9][0-9]*) errors,", ex.output.decode('utf8'))
        if m:
            failure = ET.SubElement(tc, 'failure', type="failure", message="checkpatch issues")
            failure.text = (ex.output.decode('utf8'))
            return 1

    return 0

def verify_signed_off(tc, commit):

    signed = []
    author = ""
    sha = ""
    parsed_addr = None
    for line in commit.split("\n"):
        match = re.search("^commit\s([^\s]*)", line)
        if match:
            sha = match.group(1)
        match = re.search("^Author:\s(.*)", line)
        if match:
            author = match.group(1)
            parsed_addr = parseaddr(author)
        match = re.search("signed-off-by:\s(.*)", line, re.IGNORECASE)
        if match:
            signed.append(match.group(1))

    error1 = "%s: author email (%s) needs to match one of the signed-off-by entries." %(sha, author)
    error2 = "%s: author email (%s) does not follow the syntax: First Last <email>." %(sha, author)
    error = 0
    failure = None
    if author not in signed:
        failure = ET.SubElement(tc, 'failure', type="failure", message="identity error")
        failure.text = error1
        error = 1
    if not parsed_addr or len(parsed_addr[0].split(" ")) < 2:
        if not failure:
            failure = ET.SubElement(tc, 'failure', type="failure", message="identity error")
            failure.text = error2
        else:
            failure.text = failure.text + "\n" + error2
        error = 1

    return error

def run_check_identity(tc, range):
    error = 0
    for f in get_shas(range):
        commit = sh.git("log","--decorate=short", "-n 1", f, **sh_special_args)
        error += verify_signed_off(tc, commit)

    return error


def check_doc(tc, range):

    if os.path.exists(DOCS_WARNING_FILE) and os.path.getsize(DOCS_WARNING_FILE) > 0:
        with open(DOCS_WARNING_FILE, "rb") as f:
            log = f.read()
            failure = ET.SubElement(tc, 'failure', type="failure",
                        message="documentation issues")
            failure.text = (log.decode('utf8'))
        return 1

    return 0



tests = {
        "gitlint": {
            "call": run_gitlint,
            "name": "Commit message style",
            },
        "identity": {
            "call": run_check_identity,
            "name": "Author Identity verification",
            },
        "checkpatch": {
            "call": run_checkpatch,
            "name": "Code style check using checkpatch",
            },
        "documentation": {
            "call": check_doc,
            "name": "New warnings and errors when building documentation",
            }
        }

def run_tests(range):
    run = "Commit Message / Documentation / Coding Style"
    eleTestsuite = None
    fails = 0
    passes = 0
    errors = 0
    total = 0
    filename = "compliance.xml"

    eleTestsuites = ET.Element('testsuites')
    eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite', name=run, time="0",
            tests="%d" %(errors + passes + fails),  failures="%d" %fails,  errors="%d" %errors, skip="0")

    for test in tests.keys():

        total += 1
        eleTestcase = ET.SubElement(eleTestsuite, 'testcase', classname="%s"
                %(test), name="%s" %(tests[test]['name']), time="0")

        fails += tests[test]['call'](eleTestcase, range)

    eleTestsuite.set("tests", "%s" %total)
    eleTestsuite.set("failures",  "%s" %fails)

    result = ET.tostring(eleTestsuites)
    f = open(filename, 'wb')
    f.write(result)
    f.close()
    return fails


def main():
    args = parse_args()
    if not args.commits:
        exit(1)
    fails = run_tests(args.commits)
    print(fails)
    sys.exit(fails)

if __name__ == "__main__":
    #init_logs()
    main()