1#!/usr/bin/env python3 2# See utils/checkpackagelib/readme.txt before editing this file. 3 4import argparse 5import inspect 6import os 7import re 8import six 9import sys 10 11import checkpackagelib.lib_config 12import checkpackagelib.lib_hash 13import checkpackagelib.lib_mk 14import checkpackagelib.lib_patch 15 16VERBOSE_LEVEL_TO_SHOW_IGNORED_FILES = 3 17flags = None # Command line arguments. 18 19 20def parse_args(): 21 parser = argparse.ArgumentParser() 22 23 # Do not use argparse.FileType("r") here because only files with known 24 # format will be open based on the filename. 25 parser.add_argument("files", metavar="F", type=str, nargs="*", 26 help="list of files") 27 28 parser.add_argument("--br2-external", "-b", dest='intree_only', action="store_false", 29 help="do not apply the pathname filters used for intree files") 30 31 parser.add_argument("--manual-url", action="store", 32 default="http://nightly.buildroot.org/", 33 help="default: %(default)s") 34 parser.add_argument("--verbose", "-v", action="count", default=0) 35 parser.add_argument("--quiet", "-q", action="count", default=0) 36 37 # Now the debug options in the order they are processed. 38 parser.add_argument("--include-only", dest="include_list", action="append", 39 help="run only the specified functions (debug)") 40 parser.add_argument("--exclude", dest="exclude_list", action="append", 41 help="do not run the specified functions (debug)") 42 parser.add_argument("--dry-run", action="store_true", help="print the " 43 "functions that would be called for each file (debug)") 44 45 return parser.parse_args() 46 47 48CONFIG_IN_FILENAME = re.compile(r"Config\.\S*$") 49DO_CHECK_INTREE = re.compile(r"|".join([ 50 r"Config.in", 51 r"arch/", 52 r"boot/", 53 r"fs/", 54 r"linux/", 55 r"package/", 56 r"system/", 57 r"toolchain/", 58 ])) 59DO_NOT_CHECK_INTREE = re.compile(r"|".join([ 60 r"boot/barebox/barebox\.mk$", 61 r"fs/common\.mk$", 62 r"package/doc-asciidoc\.mk$", 63 r"package/pkg-\S*\.mk$", 64 r"toolchain/helpers\.mk$", 65 r"toolchain/toolchain-external/pkg-toolchain-external\.mk$", 66 ])) 67 68 69def get_lib_from_filename(fname): 70 if flags.intree_only: 71 if DO_CHECK_INTREE.match(fname) is None: 72 return None 73 if DO_NOT_CHECK_INTREE.match(fname): 74 return None 75 else: 76 if os.path.basename(fname) == "external.mk" and \ 77 os.path.exists(fname[:-2] + "desc"): 78 return None 79 if CONFIG_IN_FILENAME.search(fname): 80 return checkpackagelib.lib_config 81 if fname.endswith(".hash"): 82 return checkpackagelib.lib_hash 83 if fname.endswith(".mk"): 84 return checkpackagelib.lib_mk 85 if fname.endswith(".patch"): 86 return checkpackagelib.lib_patch 87 return None 88 89 90def is_a_check_function(m): 91 if not inspect.isclass(m): 92 return False 93 # do not call the base class 94 if m.__name__.startswith("_"): 95 return False 96 if flags.include_list and m.__name__ not in flags.include_list: 97 return False 98 if flags.exclude_list and m.__name__ in flags.exclude_list: 99 return False 100 return True 101 102 103def print_warnings(warnings): 104 # Avoid the need to use 'return []' at the end of every check function. 105 if warnings is None: 106 return 0 # No warning generated. 107 108 for level, message in enumerate(warnings): 109 if flags.verbose >= level: 110 print(message.replace("\t", "< tab >").rstrip()) 111 return 1 # One more warning to count. 112 113 114def check_file_using_lib(fname): 115 # Count number of warnings generated and lines processed. 116 nwarnings = 0 117 nlines = 0 118 119 lib = get_lib_from_filename(fname) 120 if not lib: 121 if flags.verbose >= VERBOSE_LEVEL_TO_SHOW_IGNORED_FILES: 122 print("{}: ignored".format(fname)) 123 return nwarnings, nlines 124 classes = inspect.getmembers(lib, is_a_check_function) 125 126 if flags.dry_run: 127 functions_to_run = [c[0] for c in classes] 128 print("{}: would run: {}".format(fname, functions_to_run)) 129 return nwarnings, nlines 130 131 objects = [c[1](fname, flags.manual_url) for c in classes] 132 133 for cf in objects: 134 nwarnings += print_warnings(cf.before()) 135 if six.PY3: 136 f = open(fname, "r", errors="surrogateescape") 137 else: 138 f = open(fname, "r") 139 lastline = "" 140 for lineno, text in enumerate(f.readlines()): 141 nlines += 1 142 for cf in objects: 143 if cf.disable.search(lastline): 144 continue 145 nwarnings += print_warnings(cf.check_line(lineno + 1, text)) 146 lastline = text 147 f.close() 148 for cf in objects: 149 nwarnings += print_warnings(cf.after()) 150 151 return nwarnings, nlines 152 153 154def __main__(): 155 global flags 156 flags = parse_args() 157 158 if flags.intree_only: 159 # change all paths received to be relative to the base dir 160 base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 161 files_to_check = [os.path.relpath(os.path.abspath(f), base_dir) for f in flags.files] 162 # move current dir so the script find the files 163 os.chdir(base_dir) 164 else: 165 files_to_check = flags.files 166 167 if len(files_to_check) == 0: 168 print("No files to check style") 169 sys.exit(1) 170 171 # Accumulate number of warnings generated and lines processed. 172 total_warnings = 0 173 total_lines = 0 174 175 for fname in files_to_check: 176 nwarnings, nlines = check_file_using_lib(fname) 177 total_warnings += nwarnings 178 total_lines += nlines 179 180 # The warning messages are printed to stdout and can be post-processed 181 # (e.g. counted by 'wc'), so for stats use stderr. Wait all warnings are 182 # printed, for the case there are many of them, before printing stats. 183 sys.stdout.flush() 184 185 if not flags.quiet: 186 print("{} lines processed".format(total_lines), file=sys.stderr) 187 print("{} warnings generated".format(total_warnings), file=sys.stderr) 188 189 if total_warnings > 0: 190 sys.exit(1) 191 192 193__main__() 194