xref: /OK3568_Linux_fs/buildroot/utils/check-package (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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