mirror of
https://github.com/qca/qca-swiss-army-knife.git
synced 2025-12-10 07:44:42 +01:00
Update to the latest checkpatch. With this version there is a new warning that is tricky to fix cleanly, so add that warning to the checkpatch_filter list. Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
525 lines
14 KiB
Python
Executable file
525 lines
14 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (c) 2015-2017 Qualcomm Atheros, Inc.
|
|
# Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
|
|
#
|
|
# Permission to use, copy, modify, and/or distribute this software for any
|
|
# purpose with or without fee is hereby granted, provided that the above
|
|
# copyright notice and this permission notice appear in all copies.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
#
|
|
# Run 'ath11k-check --help' to see the instructions
|
|
#
|
|
|
|
import subprocess
|
|
import os
|
|
import logging
|
|
import sys
|
|
import argparse
|
|
import re
|
|
import tempfile
|
|
import queue
|
|
import threading
|
|
import string
|
|
import hashlib
|
|
import shutil
|
|
|
|
CHECKPATCH_COMMIT = '99b70ece33d87500ef7bee8e32cb99772c45ce14'
|
|
CHECKPATCH_MD5SUM = 'b3c97930952745672f3408dabc244843'
|
|
|
|
GIT_URL = 'https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/plain/scripts/checkpatch.pl?id=%s'
|
|
|
|
DRIVER_DIR = 'drivers/net/wireless/ath/ath11k/'
|
|
|
|
FILTER_REGEXP = r'/ath'
|
|
|
|
IGNORE_FILES = []
|
|
|
|
CHECKPATCH_IGNORE = ['MSLEEP',
|
|
'USLEEP_RANGE',
|
|
'PRINTK_WITHOUT_KERN_LEVEL',
|
|
|
|
# ath10k does not follow networking comment style
|
|
'NETWORKING_BLOCK_COMMENT_STYLE',
|
|
|
|
'LINUX_VERSION_CODE',
|
|
'COMPLEX_MACRO',
|
|
'PREFER_DEV_LEVEL',
|
|
'PREFER_PR_LEVEL',
|
|
'COMPARISON_TO_NULL',
|
|
'BIT_MACRO',
|
|
'CONSTANT_COMPARISON',
|
|
'MACRO_WITH_FLOW_CONTROL',
|
|
|
|
# Spams hundreds of lines useless 'struct should
|
|
# normally be const' warnings, maybe a bug in
|
|
# checkpatch?
|
|
'CONST_STRUCT',
|
|
|
|
# TODO: look like valid warnings, investigate
|
|
'MACRO_ARG_REUSE',
|
|
'OPEN_ENDED_LINE',
|
|
'FUNCTION_ARGUMENTS',
|
|
'CONFIG_DESCRIPTION',
|
|
'ASSIGNMENT_CONTINUATIONS',
|
|
'UNNECESSARY_PARENTHESES',
|
|
|
|
# Not sure if these really useful warnings,
|
|
# disable for now.
|
|
'MACRO_ARG_PRECEDENCE',
|
|
|
|
'BOOL_MEMBER',
|
|
|
|
# TODO: ath11k uses volatile for now, fix it
|
|
'VOLATILE',
|
|
|
|
# TODO: document all DT usage in ath11k
|
|
'UNDOCUMENTED_DT_STRING',
|
|
]
|
|
|
|
CHECKPATCH_OPTS = ['--strict', '-q', '--terse', '--no-summary',
|
|
'--max-line-length=90', '--show-types']
|
|
|
|
checkpatch_filter = [
|
|
('ath11k_read_simulate_fw_crash', 'LONG_LINE'),
|
|
('qmi_wlanfw_respond_mem_req_msg_v01', 'LONG_LINE'),
|
|
('DEFINE_EVENT', 'MACRO_ARG_UNUSED'),
|
|
]
|
|
|
|
sparse_filter = [r'warning: dubious: x & !y']
|
|
|
|
# global variables
|
|
|
|
logger = logging.getLogger('ath11k-check')
|
|
|
|
threads = 1
|
|
|
|
|
|
class CPWarning():
|
|
|
|
def __str__(self):
|
|
return 'CPWarning(%s, %s, %s, %s, %s)' % (self.path, self.lineno,
|
|
self.tag, self.type,
|
|
self.msg)
|
|
|
|
def __init__(self):
|
|
self.path = ''
|
|
self.lineno = ''
|
|
self.type = ''
|
|
self.msg = ''
|
|
self.tag = ''
|
|
|
|
|
|
def run_gcc(args):
|
|
# to disable utf-8 from gcc, easier to paste that way
|
|
os.environ['LC_CTYPE'] = 'C'
|
|
|
|
cmd = 'rm -f %s/*.o' % (DRIVER_DIR)
|
|
logger.debug('%s' % cmd)
|
|
subprocess.call(cmd, shell=True, universal_newlines=True)
|
|
|
|
cmd = ['make', '-k', '-j', str(threads)]
|
|
|
|
if not args.no_extra:
|
|
cmd.append('W=1')
|
|
|
|
cmd.append(DRIVER_DIR)
|
|
|
|
env = os.environ.copy()
|
|
|
|
# disable ccache in case it's in use, it's useless as we are
|
|
# compiling only few files and it also breaks GCC's
|
|
# -Wimplicit-fallthrough check
|
|
env['CCACHE_DISABLE'] = '1'
|
|
|
|
logger.debug('%s' % cmd)
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
env=env, universal_newlines=True)
|
|
(stdout, stderr) = p.communicate()
|
|
|
|
stderr = stderr.strip()
|
|
|
|
if len(stderr) > 0:
|
|
for line in stderr.splitlines():
|
|
match = re.search(FILTER_REGEXP, line)
|
|
if not args.no_filter and not match:
|
|
logger.debug('FILTERED: %s' % line)
|
|
continue
|
|
|
|
print(line.strip())
|
|
|
|
return p.returncode
|
|
|
|
|
|
def run_sparse(args):
|
|
cmd = ['make', '-k', '-j',
|
|
str(threads), DRIVER_DIR, 'C=2', 'CF="-D__CHECK_ENDIAN__"']
|
|
logger.debug('%s' % cmd)
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
universal_newlines=True)
|
|
(stdout, stderr) = p.communicate()
|
|
|
|
stderr = stderr.strip()
|
|
|
|
if len(stderr) > 0:
|
|
for line in stderr.splitlines():
|
|
match = re.search(FILTER_REGEXP, line)
|
|
if not args.no_filter and not match:
|
|
logger.debug('FILTERED: %s' % line)
|
|
continue
|
|
|
|
drop = False
|
|
|
|
for f in sparse_filter:
|
|
match = re.search(f, line)
|
|
if not args.no_filter and match:
|
|
logger.debug('sparse_filter: %s' % line)
|
|
drop = True
|
|
break
|
|
|
|
if not drop:
|
|
print(line.strip())
|
|
|
|
return p.returncode
|
|
|
|
|
|
def find_tagname(tag_map, filename, lineno):
|
|
if filename.find('Kconfig') != -1:
|
|
return None
|
|
|
|
if filename not in tag_map:
|
|
return None
|
|
|
|
# we need the tags sorted per linenumber
|
|
sorted_tags = sorted(tag_map[filename], key=lambda tup: tup[0])
|
|
|
|
lineno = int(lineno)
|
|
|
|
prev = None
|
|
|
|
# find the tag which is in lineno
|
|
for (l, tag) in sorted_tags:
|
|
if l > lineno:
|
|
return prev
|
|
|
|
prev = tag
|
|
|
|
return None
|
|
|
|
|
|
def parse_checkpatch_warning(line):
|
|
m = re.match(r'(.*?):(\d+): .*?:(.*?): (.*)', line, re.M | re.I)
|
|
result = CPWarning()
|
|
result.path = m.group(1)
|
|
result.lineno = m.group(2)
|
|
result.type = m.group(3)
|
|
result.msg = m.group(4)
|
|
|
|
return result
|
|
|
|
|
|
def is_filtered(cpwarning):
|
|
if cpwarning.tag is None:
|
|
return False
|
|
|
|
for (tag, type) in checkpatch_filter:
|
|
matchobj = re.match(tag, cpwarning.tag)
|
|
if matchobj is None:
|
|
continue
|
|
|
|
if cpwarning.type == type:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def get_checkpatch_md5sum():
|
|
path = shutil.which('checkpatch.pl')
|
|
f = open(path, 'rb')
|
|
md5sum = hashlib.md5(f.read()).hexdigest()
|
|
f.close()
|
|
|
|
return md5sum
|
|
|
|
def get_checkpatch_cmdline():
|
|
return ['checkpatch.pl'] + CHECKPATCH_OPTS + \
|
|
['--ignore', ",".join(CHECKPATCH_IGNORE)]
|
|
|
|
|
|
def run_checkpatch_cmd(args, q, tag_map):
|
|
checkpatch_cmd = get_checkpatch_cmdline() + ['-f']
|
|
|
|
while True:
|
|
try:
|
|
f = q.get_nowait()
|
|
except queue.Empty:
|
|
# no more files to check
|
|
break
|
|
|
|
cmd = checkpatch_cmd + [f]
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
universal_newlines=True)
|
|
(stdoutdata, stderrdata) = p.communicate()
|
|
|
|
if stdoutdata is None:
|
|
continue
|
|
|
|
lines = stdoutdata.splitlines()
|
|
for line in lines:
|
|
w = parse_checkpatch_warning(line)
|
|
w.tag = find_tagname(tag_map, f, w.lineno)
|
|
|
|
if not args.no_filter and is_filtered(w):
|
|
logger.debug('FILTERED: %s' % w)
|
|
continue
|
|
|
|
logger.debug(w)
|
|
print('%s:%s: %s' % (w.path, w.lineno, w.msg))
|
|
|
|
q.task_done()
|
|
|
|
|
|
def run_checkpatch(args):
|
|
md5sum = get_checkpatch_md5sum()
|
|
if md5sum != CHECKPATCH_MD5SUM:
|
|
print('WARNING: checkpatch.pl md5sum %s does not match with %s' %
|
|
(md5sum, CHECKPATCH_MD5SUM))
|
|
|
|
# get all files which need to be checked
|
|
cmd = 'git ls-tree HEAD %s | cut -f 2' % (DRIVER_DIR)
|
|
output = subprocess.check_output(cmd, shell=True, universal_newlines=True)
|
|
driver_files = output.splitlines()
|
|
|
|
# drop files we need to ignore
|
|
for name in IGNORE_FILES:
|
|
full_name = '%s%s' % (DRIVER_DIR, name)
|
|
if full_name in driver_files:
|
|
driver_files.remove(full_name)
|
|
|
|
logger.debug('driver_files: %s' % (driver_files))
|
|
|
|
# create global index file
|
|
(fd, tmpfilename) = tempfile.mkstemp()
|
|
f = os.fdopen(fd, 'w')
|
|
f.write('\n'.join(driver_files))
|
|
f.close()
|
|
|
|
# FIXME: do we need to call os.close(fd) still?
|
|
|
|
cmd = 'gtags -f %s' % (tmpfilename)
|
|
logger.debug('%s' % (cmd))
|
|
output = subprocess.check_output(cmd, shell=True, universal_newlines=True)
|
|
|
|
os.remove(tmpfilename)
|
|
|
|
# tag_map[FILENAME] = [(start line, tagname)]
|
|
tag_map = {}
|
|
|
|
# create tag mapping
|
|
for f in driver_files:
|
|
# global gives an error from Kconfig and Makefile
|
|
if f.endswith('Kconfig') or f.endswith('Makefile'):
|
|
continue
|
|
|
|
cmd = 'global -f %s' % (f)
|
|
output = subprocess.check_output(cmd, shell=True, universal_newlines=True)
|
|
lines = output.splitlines()
|
|
for l in lines:
|
|
columns = l.split()
|
|
tagname = columns[0]
|
|
line = int(columns[1])
|
|
|
|
if f not in tag_map:
|
|
tag_map[f] = []
|
|
|
|
tag_map[f].append((line, tagname))
|
|
|
|
q = queue.Queue()
|
|
|
|
for f in driver_files:
|
|
q.put(f)
|
|
|
|
# run checkpatch for all files
|
|
for i in range(threads):
|
|
t = threading.Thread(
|
|
target=run_checkpatch_cmd, args=(args, q, tag_map))
|
|
t.daemon = True
|
|
t.start()
|
|
|
|
q.join()
|
|
|
|
return 0
|
|
|
|
|
|
def show_version(args):
|
|
gcc_version = 'not found'
|
|
sparse_version = 'not found'
|
|
checkpatch_version = 'not found'
|
|
checkpatch_md5sum = 'N/A'
|
|
gtags_version = 'not found'
|
|
|
|
run = subprocess.check_output
|
|
|
|
f = open(sys.argv[0], 'rb')
|
|
ath11kcheck_md5sum = hashlib.md5(f.read()).hexdigest()
|
|
f.close()
|
|
|
|
try:
|
|
gcc_version = run(['gcc', '--version'],
|
|
universal_newlines=True).splitlines()[0]
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
sparse_version = run(['sparse', '--version'],
|
|
universal_newlines=True).splitlines()[0]
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
checkpatch_version = run(['checkpatch.pl', '--version'],
|
|
universal_newlines=True).splitlines()[1]
|
|
checkpatch_md5sum = get_checkpatch_md5sum()
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
gtags_version = run(['gtags', '--version'],
|
|
universal_newlines=True).splitlines()[0]
|
|
except:
|
|
pass
|
|
|
|
print('ath11k-check (md5sum %s)' % (ath11kcheck_md5sum))
|
|
print()
|
|
print('python:\t\t%s' % (sys.version))
|
|
print('gcc:\t\t%s' % (gcc_version))
|
|
print('sparse:\t\t%s' % (sparse_version))
|
|
print('checkpatch.pl:\t%s (md5sum %s)' % (checkpatch_version, checkpatch_md5sum))
|
|
print('gtags:\t\t%s' % (gtags_version))
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
def main():
|
|
global threads
|
|
|
|
checkpatch_url = GIT_URL % (CHECKPATCH_COMMIT)
|
|
|
|
description = '''ath11k source code checker
|
|
|
|
Runs various tests (gcc, sparse and checkpatch) with filtering
|
|
unnecessary warnings away, the goal is to have empty output from the
|
|
script.
|
|
|
|
Run this from the main kernel source directory which is preconfigured
|
|
with ath11k enabled. gcc recompilation is forced every time,
|
|
irrespective if there are any changes in source or not. So this can be
|
|
run multiple times and every time the same warnings will appear.
|
|
|
|
Requirements (all available in $PATH):
|
|
|
|
* gcc
|
|
* sparse
|
|
* checkpatch.pl
|
|
* gtags (from package global)
|
|
|
|
'''
|
|
|
|
s = '''Installation:
|
|
|
|
As checkpatch is evolving this script always matches a certain version
|
|
of checkpatch. Download the checkpatch version from the URL below and
|
|
install it somewhere in your $$PATH:
|
|
|
|
$CHECKPATCH_URL
|
|
|
|
Alternatively if you want manually run checkpatch with the same
|
|
settings as ath11k-check uses here's the command line:
|
|
|
|
$CHECKPATCH_CMDLINE
|
|
'''
|
|
|
|
checkpatch_cmdline = '%s foo.patch' % ' '.join(get_checkpatch_cmdline())
|
|
epilog = string.Template(s).substitute(CHECKPATCH_URL=checkpatch_url,
|
|
CHECKPATCH_CMDLINE=checkpatch_cmdline)
|
|
|
|
parser = argparse.ArgumentParser(description=description, epilog=epilog,
|
|
formatter_class=argparse.RawTextHelpFormatter)
|
|
|
|
parser.add_argument('-d', '--debug', action='store_true',
|
|
help='enable debug messages')
|
|
|
|
parser.add_argument('--fast', action='store_true',
|
|
help='run only tests which finish in few seconds')
|
|
|
|
parser.add_argument('--no-extra', action='store_true',
|
|
help='Do not run extra checks like W=1')
|
|
|
|
parser.add_argument('--no-filter', action='store_true',
|
|
help='Don\'t filter output with regexp: %r' % (FILTER_REGEXP))
|
|
|
|
parser.add_argument('--version', action='store_true',
|
|
help='Show version information about dependencies')
|
|
|
|
args = parser.parse_args()
|
|
|
|
timefmt = ''
|
|
|
|
if args.debug:
|
|
logger.setLevel(logging.DEBUG)
|
|
timefmt = '%(asctime)s '
|
|
|
|
logfmt = '%s%%(levelname)s: %%(message)s' % (timefmt)
|
|
|
|
logging.basicConfig(format=logfmt)
|
|
|
|
if args.version:
|
|
show_version(args)
|
|
|
|
if args.fast:
|
|
gcc = True
|
|
sparse = True
|
|
checkpatch = False
|
|
else:
|
|
gcc = True
|
|
sparse = True
|
|
checkpatch = True
|
|
|
|
try:
|
|
cores = subprocess.check_output(['nproc'], universal_newlines=True)
|
|
except OSError as xxx_todo_changeme:
|
|
subprocess.CalledProcessError = xxx_todo_changeme
|
|
cores = '4'
|
|
logger.warning('Failed to run nproc, assuming %s cores' % (cores))
|
|
|
|
threads = int(cores) + 2
|
|
|
|
logger.debug('threads %d' % (threads))
|
|
|
|
if gcc:
|
|
ret = run_gcc(args)
|
|
if ret != 0:
|
|
logger.debug('gcc failed: %d', ret)
|
|
sys.exit(1)
|
|
|
|
if sparse:
|
|
ret = run_sparse(args)
|
|
if ret != 0:
|
|
logger.debug('sparse failed: %d', ret)
|
|
sys.exit(2)
|
|
|
|
if checkpatch:
|
|
ret = run_checkpatch(args)
|
|
if ret != 0:
|
|
logger.debug('checkpatch failed: %d', ret)
|
|
sys.exit(3)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|