Use the new KCONFIG_STRICT Kconfiglib functionality, which makes the library itself warn for references to undefined Kconfig symbols. Also get rid of the chdir() hack. Kconfiglib now only looks up Kconfig files relative to $srctree when it's set, so the hack is no longer needed. Remove list_undef_kconfig_refs.py. It's no longer needed. Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
254 lines
7.3 KiB
Python
Executable File
254 lines
7.3 KiB
Python
Executable File
#!/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
|
|
}
|
|
|
|
# Put the Kconfiglib path first to make sure no local Kconfiglib version is
|
|
# used
|
|
sys.path.insert(0, os.path.join(repository_path, "scripts/kconfig"))
|
|
import kconfiglib
|
|
|
|
|
|
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),
|
|
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 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"] = "*"
|
|
|
|
# Enable strict Kconfig mode in Kconfiglib, which assumes there's just a
|
|
# single Kconfig tree and warns for all references to undefined symbols
|
|
os.environ["KCONFIG_STRICT"] = "y"
|
|
|
|
undef_ref_warnings = []
|
|
|
|
for warning in kconfiglib.Kconfig().warnings:
|
|
if "undefined symbol" in warning:
|
|
undef_ref_warnings.append(warning)
|
|
|
|
# Generating multiple JUnit <failure>s would be neater, but Shippable only
|
|
# seems to display the first one
|
|
if undef_ref_warnings:
|
|
failure = ET.SubElement(tc, "failure", type="failure",
|
|
message="undefined Kconfig symbols")
|
|
failure.text = "\n\n\n".join(undef_ref_warnings)
|
|
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",
|
|
},
|
|
"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",
|
|
}
|
|
}
|
|
|
|
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()
|
|
|