xref: /OK3568_Linux_fs/buildroot/utils/checkpackagelib/lib_mk.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1# See utils/checkpackagelib/readme.txt before editing this file.
2# There are already dependency checks during the build, so below check
3# functions don't need to check for things already checked by exploring the
4# menu options using "make menuconfig" and by running "make" with appropriate
5# packages enabled.
6
7import os
8import re
9
10from checkpackagelib.base import _CheckFunction
11from checkpackagelib.lib import ConsecutiveEmptyLines  # noqa: F401
12from checkpackagelib.lib import EmptyLastLine          # noqa: F401
13from checkpackagelib.lib import NewlineAtEof           # noqa: F401
14from checkpackagelib.lib import TrailingSpace          # noqa: F401
15from checkpackagelib.lib import Utf8Characters         # noqa: F401
16
17# used in more than one check
18start_conditional = ["ifdef", "ifeq", "ifndef", "ifneq"]
19continue_conditional = ["elif", "else"]
20end_conditional = ["endif"]
21
22
23class Indent(_CheckFunction):
24    COMMENT = re.compile(r"^\s*#")
25    CONDITIONAL = re.compile(r"^\s*({})\s".format("|".join(start_conditional + end_conditional + continue_conditional)))
26    ENDS_WITH_BACKSLASH = re.compile(r"^[^#].*\\$")
27    END_DEFINE = re.compile(r"^\s*endef\s")
28    MAKEFILE_TARGET = re.compile(r"^[^# \t]+:\s")
29    START_DEFINE = re.compile(r"^\s*define\s")
30
31    def before(self):
32        self.define = False
33        self.backslash = False
34        self.makefile_target = False
35
36    def check_line(self, lineno, text):
37        if self.START_DEFINE.search(text):
38            self.define = True
39            return
40        if self.END_DEFINE.search(text):
41            self.define = False
42            return
43
44        expect_tabs = False
45        if self.define or self.backslash or self.makefile_target:
46            expect_tabs = True
47        if not self.backslash and self.CONDITIONAL.search(text):
48            expect_tabs = False
49
50        # calculate for next line
51        if self.ENDS_WITH_BACKSLASH.search(text):
52            self.backslash = True
53        else:
54            self.backslash = False
55
56        if self.MAKEFILE_TARGET.search(text):
57            self.makefile_target = True
58            return
59        if text.strip() == "":
60            self.makefile_target = False
61            return
62
63        # comment can be indented or not inside define ... endef, so ignore it
64        if self.define and self.COMMENT.search(text):
65            return
66
67        if expect_tabs:
68            if not text.startswith("\t"):
69                return ["{}:{}: expected indent with tabs"
70                        .format(self.filename, lineno),
71                        text]
72        else:
73            if text.startswith("\t"):
74                return ["{}:{}: unexpected indent with tabs"
75                        .format(self.filename, lineno),
76                        text]
77
78
79class OverriddenVariable(_CheckFunction):
80    CONCATENATING = re.compile(r"^([A-Z0-9_]+)\s*(\+|:|)=\s*\$\(\\1\)")
81    END_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(end_conditional)))
82    OVERRIDING_ASSIGNMENTS = [':=', "="]
83    START_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(start_conditional)))
84    VARIABLE = re.compile(r"^([A-Z0-9_]+)\s*((\+|:|)=)")
85    USUALLY_OVERRIDDEN = re.compile(r"^[A-Z0-9_]+({})".format("|".join([
86        r"_ARCH\s*=\s*",
87        r"_CPU\s*=\s*",
88        r"_SITE\s*=\s*",
89        r"_SOURCE\s*=\s*",
90        r"_VERSION\s*=\s*"])))
91
92    def before(self):
93        self.conditional = 0
94        self.unconditionally_set = []
95        self.conditionally_set = []
96
97    def check_line(self, lineno, text):
98        if self.START_CONDITIONAL.search(text):
99            self.conditional += 1
100            return
101        if self.END_CONDITIONAL.search(text):
102            self.conditional -= 1
103            return
104
105        m = self.VARIABLE.search(text)
106        if m is None:
107            return
108        variable, assignment = m.group(1, 2)
109
110        if self.conditional == 0:
111            if variable in self.conditionally_set:
112                self.unconditionally_set.append(variable)
113                if assignment in self.OVERRIDING_ASSIGNMENTS:
114                    return ["{}:{}: unconditional override of variable {} previously conditionally set"
115                            .format(self.filename, lineno, variable),
116                            text]
117
118            if variable not in self.unconditionally_set:
119                self.unconditionally_set.append(variable)
120                return
121            if assignment in self.OVERRIDING_ASSIGNMENTS:
122                return ["{}:{}: unconditional override of variable {}"
123                        .format(self.filename, lineno, variable),
124                        text]
125        else:
126            if variable not in self.unconditionally_set:
127                self.conditionally_set.append(variable)
128                return
129            if self.CONCATENATING.search(text):
130                return ["{}:{}: immediate assignment to append to variable {}"
131                        .format(self.filename, lineno, variable),
132                        text]
133            if self.USUALLY_OVERRIDDEN.search(text):
134                return
135            if assignment in self.OVERRIDING_ASSIGNMENTS:
136                return ["{}:{}: conditional override of variable {}"
137                        .format(self.filename, lineno, variable),
138                        text]
139
140
141class PackageHeader(_CheckFunction):
142    def before(self):
143        self.skip = False
144
145    def check_line(self, lineno, text):
146        if self.skip or lineno > 6:
147            return
148
149        if lineno in [1, 5]:
150            if lineno == 1 and text.startswith("include "):
151                self.skip = True
152                return
153            if text.rstrip() != "#" * 80:
154                return ["{}:{}: should be 80 hashes ({}#writing-rules-mk)"
155                        .format(self.filename, lineno, self.url_to_manual),
156                        text,
157                        "#" * 80]
158        elif lineno in [2, 4]:
159            if text.rstrip() != "#":
160                return ["{}:{}: should be 1 hash ({}#writing-rules-mk)"
161                        .format(self.filename, lineno, self.url_to_manual),
162                        text]
163        elif lineno == 6:
164            if text.rstrip() != "":
165                return ["{}:{}: should be a blank line ({}#writing-rules-mk)"
166                        .format(self.filename, lineno, self.url_to_manual),
167                        text]
168
169
170class RemoveDefaultPackageSourceVariable(_CheckFunction):
171    packages_that_may_contain_default_source = ["binutils", "gcc", "gdb"]
172
173    def before(self):
174        package, _ = os.path.splitext(os.path.basename(self.filename))
175        package_upper = package.replace("-", "_").upper()
176        self.package = package
177        self.FIND_SOURCE = re.compile(
178            r"^{}_SOURCE\s*=\s*{}-\$\({}_VERSION\)\.tar\.gz"
179            .format(package_upper, package, package_upper))
180
181    def check_line(self, lineno, text):
182        if self.FIND_SOURCE.search(text):
183
184            if self.package in self.packages_that_may_contain_default_source:
185                return
186
187            return ["{}:{}: remove default value of _SOURCE variable "
188                    "({}#generic-package-reference)"
189                    .format(self.filename, lineno, self.url_to_manual),
190                    text]
191
192
193class SpaceBeforeBackslash(_CheckFunction):
194    TAB_OR_MULTIPLE_SPACES_BEFORE_BACKSLASH = re.compile(r"^.*(  |\t ?)\\$")
195
196    def check_line(self, lineno, text):
197        if self.TAB_OR_MULTIPLE_SPACES_BEFORE_BACKSLASH.match(text.rstrip()):
198            return ["{}:{}: use only one space before backslash"
199                    .format(self.filename, lineno),
200                    text]
201
202
203class TrailingBackslash(_CheckFunction):
204    ENDS_WITH_BACKSLASH = re.compile(r"^[^#].*\\$")
205
206    def before(self):
207        self.backslash = False
208
209    def check_line(self, lineno, text):
210        last_line_ends_in_backslash = self.backslash
211
212        # calculate for next line
213        if self.ENDS_WITH_BACKSLASH.search(text):
214            self.backslash = True
215            self.lastline = text
216            return
217        self.backslash = False
218
219        if last_line_ends_in_backslash and text.strip() == "":
220            return ["{}:{}: remove trailing backslash"
221                    .format(self.filename, lineno - 1),
222                    self.lastline]
223
224
225class TypoInPackageVariable(_CheckFunction):
226    ALLOWED = re.compile(r"|".join([
227        "ACLOCAL_DIR",
228        "ACLOCAL_HOST_DIR",
229        "ACLOCAL_PATH",
230        "BR_CCACHE_INITIAL_SETUP",
231        "BR_LIBC",
232        "BR_NO_CHECK_HASH_FOR",
233        "LINUX_EXTENSIONS",
234        "LINUX_POST_PATCH_HOOKS",
235        "LINUX_TOOLS",
236        "LUA_RUN",
237        "MKFS_JFFS2",
238        "MKIMAGE_ARCH",
239        "PACKAGES_PERMISSIONS_TABLE",
240        "PKG_CONFIG_HOST_BINARY",
241        "SUMTOOL",
242        "TARGET_FINALIZE_HOOKS",
243        "TARGETS_ROOTFS",
244        "XTENSA_CORE_NAME"]))
245    VARIABLE = re.compile(r"^([A-Z0-9_]+_[A-Z0-9_]+)\s*(\+|)=")
246
247    def before(self):
248        package, _ = os.path.splitext(os.path.basename(self.filename))
249        package = package.replace("-", "_").upper()
250        # linux tools do not use LINUX_TOOL_ prefix for variables
251        package = package.replace("LINUX_TOOL_", "")
252        # linux extensions do not use LINUX_EXT_ prefix for variables
253        package = package.replace("LINUX_EXT_", "")
254        self.package = package
255        self.REGEX = re.compile(r"^(HOST_|ROOTFS_)?({}_[A-Z0-9_]+)".format(package))
256        self.FIND_VIRTUAL = re.compile(
257            r"^{}_PROVIDES\s*(\+|)=\s*(.*)".format(package))
258        self.virtual = []
259
260    def check_line(self, lineno, text):
261        m = self.VARIABLE.search(text)
262        if m is None:
263            return
264
265        variable = m.group(1)
266
267        # allow to set variables for virtual package this package provides
268        v = self.FIND_VIRTUAL.search(text)
269        if v:
270            self.virtual += v.group(2).upper().split()
271            return
272        for virtual in self.virtual:
273            if variable.startswith("{}_".format(virtual)):
274                return
275
276        if self.ALLOWED.match(variable):
277            return
278        if self.REGEX.search(text) is None:
279            return ["{}:{}: possible typo: {} -> *{}*"
280                    .format(self.filename, lineno, variable, self.package),
281                    text]
282
283
284class UselessFlag(_CheckFunction):
285    DEFAULT_AUTOTOOLS_FLAG = re.compile(r"^.*{}".format("|".join([
286        r"_AUTORECONF\s*=\s*NO",
287        r"_LIBTOOL_PATCH\s*=\s*YES"])))
288    DEFAULT_GENERIC_FLAG = re.compile(r"^.*{}".format("|".join([
289        r"_INSTALL_IMAGES\s*=\s*NO",
290        r"_INSTALL_REDISTRIBUTE\s*=\s*YES",
291        r"_INSTALL_STAGING\s*=\s*NO",
292        r"_INSTALL_TARGET\s*=\s*YES"])))
293    END_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(end_conditional)))
294    START_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(start_conditional)))
295
296    def before(self):
297        self.conditional = 0
298
299    def check_line(self, lineno, text):
300        if self.START_CONDITIONAL.search(text):
301            self.conditional += 1
302            return
303        if self.END_CONDITIONAL.search(text):
304            self.conditional -= 1
305            return
306
307        # allow non-default conditionally overridden by default
308        if self.conditional > 0:
309            return
310
311        if self.DEFAULT_GENERIC_FLAG.search(text):
312            return ["{}:{}: useless default value ({}#"
313                    "_infrastructure_for_packages_with_specific_build_systems)"
314                    .format(self.filename, lineno, self.url_to_manual),
315                    text]
316
317        if self.DEFAULT_AUTOTOOLS_FLAG.search(text) and not text.lstrip().startswith("HOST_"):
318            return ["{}:{}: useless default value "
319                    "({}#_infrastructure_for_autotools_based_packages)"
320                    .format(self.filename, lineno, self.url_to_manual),
321                    text]
322
323
324class VariableWithBraces(_CheckFunction):
325    VARIABLE_WITH_BRACES = re.compile(r"^[^#].*[^$]\${\w+}")
326
327    def check_line(self, lineno, text):
328        if self.VARIABLE_WITH_BRACES.match(text.rstrip()):
329            return ["{}:{}: use $() to delimit variables, not ${{}}"
330                    .format(self.filename, lineno),
331                    text]
332