buildroot/utils/check-package
Ricardo Martincoski d020368eea utils/checkpackagelib/lib_sysv: check SysV init scripts
Enable the common checks:
 - consecutive empty lines
 - empty last line
 - missing new line at end of file
 - trailing space
 - warn for executable files, with the hint to instead use
   '$(INSTALL) -D -m 0755' in the .mk file

Check indent with tabs:
 - add a simple check function to warn only when the indent is done
   using spaces or a mix of tabs and spaces. It does not check indenting
   levels, but it already makes the review easier, since it
   diferentiates spaces and tabs.

Check variables:
 - check DAEMON is defined
 - when DAEMON is defined, check the filename is in the form S01daemon
 - when PIDFILE is defined, expect it to be in /var/run and defined
   using $DAEMON.

Also add unit test for this.

Signed-off-by: Ricardo Martincoski <ricardo.martincoski@gmail.com>
[Arnout: avoid 'del NotExecutable_base' by importing the module instead
 of the class; refer to manual in warnings]
Signed-off-by: Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
2022-02-06 18:27:00 +01:00

221 lines
6.8 KiB
Python
Executable File

#!/usr/bin/env python3
# See utils/checkpackagelib/readme.txt before editing this file.
import argparse
import inspect
import os
import re
import six
import sys
import checkpackagelib.base
import checkpackagelib.lib_config
import checkpackagelib.lib_hash
import checkpackagelib.lib_mk
import checkpackagelib.lib_patch
import checkpackagelib.lib_sysv
VERBOSE_LEVEL_TO_SHOW_IGNORED_FILES = 3
flags = None # Command line arguments.
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("--manual-url", action="store",
default="http://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)")
return parser.parse_args()
CONFIG_IN_FILENAME = re.compile(r"Config\.\S*$")
DO_CHECK_INTREE = re.compile(r"|".join([
r"Config.in",
r"arch/",
r"boot/",
r"fs/",
r"linux/",
r"package/",
r"system/",
r"toolchain/",
]))
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"toolchain/helpers\.mk$",
r"toolchain/toolchain-external/pkg-toolchain-external\.mk$",
]))
SYSV_INIT_SCRIPT_FILENAME = re.compile(r"/S\d\d[^/]+$")
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 CONFIG_IN_FILENAME.search(fname):
return checkpackagelib.lib_config
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 None
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):
# Avoid the need to use 'return []' at the end of every check function.
if warnings is None:
return 0 # No warning generated.
for level, message in enumerate(warnings):
if flags.verbose >= level:
print(message.replace("\t", "< tab >").rstrip())
return 1 # One more warning to count.
def check_file_using_lib(fname):
# Count number of warnings generated and lines processed.
nwarnings = 0
nlines = 0
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 = [c[1](fname, flags.manual_url) for c in internal_functions]
for cf in objects:
nwarnings += print_warnings(cf.before())
if six.PY3:
f = open(fname, "r", errors="surrogateescape")
else:
f = open(fname, "r")
lastline = ""
for lineno, text in enumerate(f.readlines()):
nlines += 1
for cf in objects:
if cf.disable.search(lastline):
continue
nwarnings += print_warnings(cf.check_line(lineno + 1, text))
lastline = text
f.close()
for cf in objects:
nwarnings += print_warnings(cf.after())
tools = [c[1](fname) for c in external_tools]
for tool in tools:
nwarnings += print_warnings(tool.run())
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:
sys.exit(1)
__main__()