7931f78c16
Now that we do have support for checking hashes for custom versions (for the few packages for which we do support custom versions, like the kernel, some bootloaders...), we want to ensure that our defconfig files, when they enable one or more such custom version, do enable checking the hashes for those versions, and thus we want to require all our defconfigs do enable BR2_DOWNLOAD_FORCE_CHECK_HASHES. Add a check for that condition. We need to be careful that we only check Buildroot's defconfig, whether in-tree or in a br2-external, and not kernel or other kconfig-based defconfig files, like those in board/ sub-directories. So we only match defconfig files that are in a configs/ directory, whether at the toplevel (for in-tree defconfigs), or not (for br2-external defconfigs). Since we only have two defconfigs that check hashes for custom versions, regnerate .checkpackageignore to ignore all so-far broken defconfigs. Suggested-by: Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be> Signed-off-by: Yann E. MORIN <yann.morin.1998@free.fr> Cc: Ricardo Martincoski <ricardo.martincoski@datacom.com.br> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
326 lines
10 KiB
Python
Executable File
326 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# See utils/checkpackagelib/readme.txt before editing this file.
|
|
|
|
import argparse
|
|
import inspect
|
|
import magic
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import checkpackagelib.base
|
|
import checkpackagelib.lib_config
|
|
import checkpackagelib.lib_defconfig
|
|
import checkpackagelib.lib_hash
|
|
import checkpackagelib.lib_ignore
|
|
import checkpackagelib.lib_mk
|
|
import checkpackagelib.lib_patch
|
|
import checkpackagelib.lib_python
|
|
import checkpackagelib.lib_shellscript
|
|
import checkpackagelib.lib_sysv
|
|
|
|
VERBOSE_LEVEL_TO_SHOW_IGNORED_FILES = 3
|
|
flags = None # Command line arguments.
|
|
|
|
# There are two Python packages called 'magic':
|
|
# https://pypi.org/project/file-magic/
|
|
# https://pypi.org/project/python-magic/
|
|
# Both allow to return a MIME file type, but with a slightly different
|
|
# interface. Detect which one of the two we have based on one of the
|
|
# attributes.
|
|
if hasattr(magic, 'FileMagic'):
|
|
# https://pypi.org/project/file-magic/
|
|
def get_filetype(fname):
|
|
return magic.detect_from_filename(fname).mime_type
|
|
else:
|
|
# https://pypi.org/project/python-magic/
|
|
def get_filetype(fname):
|
|
return magic.from_file(fname, mime=True)
|
|
|
|
|
|
def get_ignored_parsers_per_file(intree_only, ignore_filename):
|
|
ignored = dict()
|
|
entry_base_dir = ''
|
|
|
|
if not ignore_filename:
|
|
return ignored
|
|
|
|
filename = os.path.abspath(ignore_filename)
|
|
entry_base_dir = os.path.join(os.path.dirname(filename))
|
|
|
|
with open(filename, "r") as f:
|
|
for line in f.readlines():
|
|
filename, warnings_str = line.split(' ', 1)
|
|
warnings = warnings_str.split()
|
|
ignored[os.path.join(entry_base_dir, filename)] = warnings
|
|
return ignored
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser()
|
|
|
|
# Do not use argparse.FileType("r") here because only files with known
|
|
# format will be open based on the filename.
|
|
parser.add_argument("files", metavar="F", type=str, nargs="*",
|
|
help="list of files")
|
|
|
|
parser.add_argument("--br2-external", "-b", dest='intree_only', action="store_false",
|
|
help="do not apply the pathname filters used for intree files")
|
|
parser.add_argument("--ignore-list", dest='ignore_filename', action="store",
|
|
help='override the default list of ignored warnings')
|
|
|
|
parser.add_argument("--manual-url", action="store",
|
|
default="https://nightly.buildroot.org/",
|
|
help="default: %(default)s")
|
|
parser.add_argument("--verbose", "-v", action="count", default=0)
|
|
parser.add_argument("--quiet", "-q", action="count", default=0)
|
|
|
|
# Now the debug options in the order they are processed.
|
|
parser.add_argument("--include-only", dest="include_list", action="append",
|
|
help="run only the specified functions (debug)")
|
|
parser.add_argument("--exclude", dest="exclude_list", action="append",
|
|
help="do not run the specified functions (debug)")
|
|
parser.add_argument("--dry-run", action="store_true", help="print the "
|
|
"functions that would be called for each file (debug)")
|
|
parser.add_argument("--failed-only", action="store_true", help="print only"
|
|
" the name of the functions that failed (debug)")
|
|
|
|
flags = parser.parse_args()
|
|
|
|
flags.ignore_list = get_ignored_parsers_per_file(flags.intree_only, flags.ignore_filename)
|
|
|
|
if flags.failed_only:
|
|
flags.dry_run = False
|
|
flags.verbose = -1
|
|
|
|
return flags
|
|
|
|
|
|
def get_lib_from_filetype(fname):
|
|
if not os.path.isfile(fname):
|
|
return None
|
|
filetype = get_filetype(fname)
|
|
if filetype == "text/x-shellscript":
|
|
return checkpackagelib.lib_shellscript
|
|
if filetype in ["text/x-python", "text/x-script.python"]:
|
|
return checkpackagelib.lib_python
|
|
return None
|
|
|
|
|
|
CONFIG_IN_FILENAME = re.compile(r"Config\.\S*$")
|
|
DO_CHECK_INTREE = re.compile(r"|".join([
|
|
r".checkpackageignore",
|
|
r"Config.in",
|
|
r"arch/",
|
|
r"board/",
|
|
r"boot/",
|
|
r"configs/",
|
|
r"fs/",
|
|
r"linux/",
|
|
r"package/",
|
|
r"support/",
|
|
r"system/",
|
|
r"toolchain/",
|
|
r"utils/",
|
|
]))
|
|
DO_NOT_CHECK_INTREE = re.compile(r"|".join([
|
|
r"boot/barebox/barebox\.mk$",
|
|
r"fs/common\.mk$",
|
|
r"package/doc-asciidoc\.mk$",
|
|
r"package/pkg-\S*\.mk$",
|
|
r"support/dependencies/[^/]+\.mk$",
|
|
r"support/gnuconfig/config\.",
|
|
r"support/kconfig/",
|
|
r"support/misc/[^/]+\.mk$",
|
|
r"support/testing/tests/.*br2-external/",
|
|
r"toolchain/helpers\.mk$",
|
|
r"toolchain/toolchain-external/pkg-toolchain-external\.mk$",
|
|
]))
|
|
|
|
SYSV_INIT_SCRIPT_FILENAME = re.compile(r"/S\d\d[^/]+$")
|
|
|
|
# For defconfigs: avoid matching kernel, uboot... defconfig files, so
|
|
# limit to defconfig files in a configs/ directory, either in-tree or
|
|
# in a br2-external tree.
|
|
BR_DEFCONFIG_FILENAME = re.compile(r"^(.+/)?configs/[^/]+_defconfig$")
|
|
|
|
|
|
def get_lib_from_filename(fname):
|
|
if flags.intree_only:
|
|
if DO_CHECK_INTREE.match(fname) is None:
|
|
return None
|
|
if DO_NOT_CHECK_INTREE.match(fname):
|
|
return None
|
|
else:
|
|
if os.path.basename(fname) == "external.mk" and \
|
|
os.path.exists(fname[:-2] + "desc"):
|
|
return None
|
|
if fname == ".checkpackageignore":
|
|
return checkpackagelib.lib_ignore
|
|
if CONFIG_IN_FILENAME.search(fname):
|
|
return checkpackagelib.lib_config
|
|
if BR_DEFCONFIG_FILENAME.search(fname):
|
|
return checkpackagelib.lib_defconfig
|
|
if fname.endswith(".hash"):
|
|
return checkpackagelib.lib_hash
|
|
if fname.endswith(".mk"):
|
|
return checkpackagelib.lib_mk
|
|
if fname.endswith(".patch"):
|
|
return checkpackagelib.lib_patch
|
|
if SYSV_INIT_SCRIPT_FILENAME.search(fname):
|
|
return checkpackagelib.lib_sysv
|
|
return get_lib_from_filetype(fname)
|
|
|
|
|
|
def common_inspect_rules(m):
|
|
# do not call the base class
|
|
if m.__name__.startswith("_"):
|
|
return False
|
|
if flags.include_list and m.__name__ not in flags.include_list:
|
|
return False
|
|
if flags.exclude_list and m.__name__ in flags.exclude_list:
|
|
return False
|
|
return True
|
|
|
|
|
|
def is_a_check_function(m):
|
|
if not inspect.isclass(m):
|
|
return False
|
|
if not issubclass(m, checkpackagelib.base._CheckFunction):
|
|
return False
|
|
return common_inspect_rules(m)
|
|
|
|
|
|
def is_external_tool(m):
|
|
if not inspect.isclass(m):
|
|
return False
|
|
if not issubclass(m, checkpackagelib.base._Tool):
|
|
return False
|
|
return common_inspect_rules(m)
|
|
|
|
|
|
def print_warnings(warnings, xfail):
|
|
# Avoid the need to use 'return []' at the end of every check function.
|
|
if warnings is None:
|
|
return 0, 0 # No warning generated.
|
|
|
|
if xfail:
|
|
return 0, 1 # Warning not generated, fail expected for this file.
|
|
for level, message in enumerate(warnings):
|
|
if flags.verbose >= level:
|
|
print(message.replace("\t", "< tab >").rstrip())
|
|
return 1, 1 # One more warning to count.
|
|
|
|
|
|
def check_file_using_lib(fname):
|
|
# Count number of warnings generated and lines processed.
|
|
nwarnings = 0
|
|
nlines = 0
|
|
xfail = flags.ignore_list.get(os.path.abspath(fname), [])
|
|
failed = set()
|
|
|
|
lib = get_lib_from_filename(fname)
|
|
if not lib:
|
|
if flags.verbose >= VERBOSE_LEVEL_TO_SHOW_IGNORED_FILES:
|
|
print("{}: ignored".format(fname))
|
|
return nwarnings, nlines
|
|
internal_functions = inspect.getmembers(lib, is_a_check_function)
|
|
external_tools = inspect.getmembers(lib, is_external_tool)
|
|
all_checks = internal_functions + external_tools
|
|
|
|
if flags.dry_run:
|
|
functions_to_run = [c[0] for c in all_checks]
|
|
print("{}: would run: {}".format(fname, functions_to_run))
|
|
return nwarnings, nlines
|
|
|
|
objects = [[f"{lib.__name__[16:]}.{c[0]}", c[1](fname, flags.manual_url)] for c in internal_functions]
|
|
|
|
for name, cf in objects:
|
|
warn, fail = print_warnings(cf.before(), name in xfail)
|
|
if fail > 0:
|
|
failed.add(name)
|
|
nwarnings += warn
|
|
|
|
lastline = ""
|
|
with open(fname, "r", errors="surrogateescape") as f:
|
|
for lineno, text in enumerate(f):
|
|
nlines += 1
|
|
for name, cf in objects:
|
|
if cf.disable.search(lastline):
|
|
continue
|
|
line_sts = cf.check_line(lineno + 1, text)
|
|
warn, fail = print_warnings(line_sts, name in xfail)
|
|
if fail > 0:
|
|
failed.add(name)
|
|
nwarnings += warn
|
|
lastline = text
|
|
|
|
for name, cf in objects:
|
|
warn, fail = print_warnings(cf.after(), name in xfail)
|
|
if fail > 0:
|
|
failed.add(name)
|
|
nwarnings += warn
|
|
|
|
tools = [[c[0], c[1](fname)] for c in external_tools]
|
|
|
|
for name, tool in tools:
|
|
warn, fail = print_warnings(tool.run(), name in xfail)
|
|
if fail > 0:
|
|
failed.add(name)
|
|
nwarnings += warn
|
|
|
|
for should_fail in xfail:
|
|
if should_fail not in failed:
|
|
print("{}:0: {} was expected to fail, did you fix the file and forget to update {}?"
|
|
.format(fname, should_fail, flags.ignore_filename))
|
|
nwarnings += 1
|
|
|
|
if flags.failed_only:
|
|
if len(failed) > 0:
|
|
f = " ".join(sorted(failed))
|
|
print("{} {}".format(fname, f))
|
|
|
|
return nwarnings, nlines
|
|
|
|
|
|
def __main__():
|
|
global flags
|
|
flags = parse_args()
|
|
|
|
if flags.intree_only:
|
|
# change all paths received to be relative to the base dir
|
|
base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
files_to_check = [os.path.relpath(os.path.abspath(f), base_dir) for f in flags.files]
|
|
# move current dir so the script find the files
|
|
os.chdir(base_dir)
|
|
else:
|
|
files_to_check = flags.files
|
|
|
|
if len(files_to_check) == 0:
|
|
print("No files to check style")
|
|
sys.exit(1)
|
|
|
|
# Accumulate number of warnings generated and lines processed.
|
|
total_warnings = 0
|
|
total_lines = 0
|
|
|
|
for fname in files_to_check:
|
|
nwarnings, nlines = check_file_using_lib(fname)
|
|
total_warnings += nwarnings
|
|
total_lines += nlines
|
|
|
|
# The warning messages are printed to stdout and can be post-processed
|
|
# (e.g. counted by 'wc'), so for stats use stderr. Wait all warnings are
|
|
# printed, for the case there are many of them, before printing stats.
|
|
sys.stdout.flush()
|
|
|
|
if not flags.quiet:
|
|
print("{} lines processed".format(total_lines), file=sys.stderr)
|
|
print("{} warnings generated".format(total_warnings), file=sys.stderr)
|
|
|
|
if total_warnings > 0 and not flags.failed_only:
|
|
sys.exit(1)
|
|
|
|
|
|
__main__()
|