| # See utils/checkpackagelib/readme.txt before editing this file. |
| # There are already dependency checks during the build, so below check |
| # functions don't need to check for things already checked by exploring the |
| # menu options using "make menuconfig" and by running "make" with appropriate |
| # packages enabled. |
| |
| import os |
| import re |
| |
| from checkpackagelib.base import _CheckFunction |
| from checkpackagelib.lib import ConsecutiveEmptyLines # noqa: F401 |
| from checkpackagelib.lib import EmptyLastLine # noqa: F401 |
| from checkpackagelib.lib import NewlineAtEof # noqa: F401 |
| from checkpackagelib.lib import TrailingSpace # noqa: F401 |
| from checkpackagelib.lib import Utf8Characters # noqa: F401 |
| |
| # used in more than one check |
| start_conditional = ["ifdef", "ifeq", "ifndef", "ifneq"] |
| continue_conditional = ["elif", "else"] |
| end_conditional = ["endif"] |
| |
| |
| class Indent(_CheckFunction): |
| COMMENT = re.compile(r"^\s*#") |
| CONDITIONAL = re.compile(r"^\s*({})\s".format("|".join(start_conditional + end_conditional + continue_conditional))) |
| ENDS_WITH_BACKSLASH = re.compile(r"^[^#].*\\$") |
| END_DEFINE = re.compile(r"^\s*endef\s") |
| MAKEFILE_TARGET = re.compile(r"^[^# \t]+:\s") |
| START_DEFINE = re.compile(r"^\s*define\s") |
| |
| def before(self): |
| self.define = False |
| self.backslash = False |
| self.makefile_target = False |
| |
| def check_line(self, lineno, text): |
| if self.START_DEFINE.search(text): |
| self.define = True |
| return |
| if self.END_DEFINE.search(text): |
| self.define = False |
| return |
| |
| expect_tabs = False |
| if self.define or self.backslash or self.makefile_target: |
| expect_tabs = True |
| if not self.backslash and self.CONDITIONAL.search(text): |
| expect_tabs = False |
| |
| # calculate for next line |
| if self.ENDS_WITH_BACKSLASH.search(text): |
| self.backslash = True |
| else: |
| self.backslash = False |
| |
| if self.MAKEFILE_TARGET.search(text): |
| self.makefile_target = True |
| return |
| if text.strip() == "": |
| self.makefile_target = False |
| return |
| |
| # comment can be indented or not inside define ... endef, so ignore it |
| if self.define and self.COMMENT.search(text): |
| return |
| |
| if expect_tabs: |
| if not text.startswith("\t"): |
| return ["{}:{}: expected indent with tabs" |
| .format(self.filename, lineno), |
| text] |
| else: |
| if text.startswith("\t"): |
| return ["{}:{}: unexpected indent with tabs" |
| .format(self.filename, lineno), |
| text] |
| |
| |
| class OverriddenVariable(_CheckFunction): |
| CONCATENATING = re.compile(r"^([A-Z0-9_]+)\s*(\+|:|)=\s*\$\(\\1\)") |
| END_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(end_conditional))) |
| OVERRIDING_ASSIGNMENTS = [':=', "="] |
| START_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(start_conditional))) |
| VARIABLE = re.compile(r"^([A-Z0-9_]+)\s*((\+|:|)=)") |
| USUALLY_OVERRIDDEN = re.compile(r"^[A-Z0-9_]+({})".format("|".join([ |
| r"_ARCH\s*=\s*", |
| r"_CPU\s*=\s*", |
| r"_SITE\s*=\s*", |
| r"_SOURCE\s*=\s*", |
| r"_VERSION\s*=\s*"]))) |
| |
| def before(self): |
| self.conditional = 0 |
| self.unconditionally_set = [] |
| self.conditionally_set = [] |
| |
| def check_line(self, lineno, text): |
| if self.START_CONDITIONAL.search(text): |
| self.conditional += 1 |
| return |
| if self.END_CONDITIONAL.search(text): |
| self.conditional -= 1 |
| return |
| |
| m = self.VARIABLE.search(text) |
| if m is None: |
| return |
| variable, assignment = m.group(1, 2) |
| |
| if self.conditional == 0: |
| if variable in self.conditionally_set: |
| self.unconditionally_set.append(variable) |
| if assignment in self.OVERRIDING_ASSIGNMENTS: |
| return ["{}:{}: unconditional override of variable {} previously conditionally set" |
| .format(self.filename, lineno, variable), |
| text] |
| |
| if variable not in self.unconditionally_set: |
| self.unconditionally_set.append(variable) |
| return |
| if assignment in self.OVERRIDING_ASSIGNMENTS: |
| return ["{}:{}: unconditional override of variable {}" |
| .format(self.filename, lineno, variable), |
| text] |
| else: |
| if variable not in self.unconditionally_set: |
| self.conditionally_set.append(variable) |
| return |
| if self.CONCATENATING.search(text): |
| return ["{}:{}: immediate assignment to append to variable {}" |
| .format(self.filename, lineno, variable), |
| text] |
| if self.USUALLY_OVERRIDDEN.search(text): |
| return |
| if assignment in self.OVERRIDING_ASSIGNMENTS: |
| return ["{}:{}: conditional override of variable {}" |
| .format(self.filename, lineno, variable), |
| text] |
| |
| |
| class PackageHeader(_CheckFunction): |
| def before(self): |
| self.skip = False |
| |
| def check_line(self, lineno, text): |
| if self.skip or lineno > 6: |
| return |
| |
| if lineno in [1, 5]: |
| if lineno == 1 and text.startswith("include "): |
| self.skip = True |
| return |
| if text.rstrip() != "#" * 80: |
| return ["{}:{}: should be 80 hashes ({}#writing-rules-mk)" |
| .format(self.filename, lineno, self.url_to_manual), |
| text, |
| "#" * 80] |
| elif lineno in [2, 4]: |
| if text.rstrip() != "#": |
| return ["{}:{}: should be 1 hash ({}#writing-rules-mk)" |
| .format(self.filename, lineno, self.url_to_manual), |
| text] |
| elif lineno == 6: |
| if text.rstrip() != "": |
| return ["{}:{}: should be a blank line ({}#writing-rules-mk)" |
| .format(self.filename, lineno, self.url_to_manual), |
| text] |
| |
| |
| class RemoveDefaultPackageSourceVariable(_CheckFunction): |
| packages_that_may_contain_default_source = ["binutils", "gcc", "gdb"] |
| |
| def before(self): |
| package, _ = os.path.splitext(os.path.basename(self.filename)) |
| package_upper = package.replace("-", "_").upper() |
| self.package = package |
| self.FIND_SOURCE = re.compile( |
| r"^{}_SOURCE\s*=\s*{}-\$\({}_VERSION\)\.tar\.gz" |
| .format(package_upper, package, package_upper)) |
| |
| def check_line(self, lineno, text): |
| if self.FIND_SOURCE.search(text): |
| |
| if self.package in self.packages_that_may_contain_default_source: |
| return |
| |
| return ["{}:{}: remove default value of _SOURCE variable " |
| "({}#generic-package-reference)" |
| .format(self.filename, lineno, self.url_to_manual), |
| text] |
| |
| |
| class SpaceBeforeBackslash(_CheckFunction): |
| TAB_OR_MULTIPLE_SPACES_BEFORE_BACKSLASH = re.compile(r"^.*( |\t ?)\\$") |
| |
| def check_line(self, lineno, text): |
| if self.TAB_OR_MULTIPLE_SPACES_BEFORE_BACKSLASH.match(text.rstrip()): |
| return ["{}:{}: use only one space before backslash" |
| .format(self.filename, lineno), |
| text] |
| |
| |
| class TrailingBackslash(_CheckFunction): |
| ENDS_WITH_BACKSLASH = re.compile(r"^[^#].*\\$") |
| |
| def before(self): |
| self.backslash = False |
| |
| def check_line(self, lineno, text): |
| last_line_ends_in_backslash = self.backslash |
| |
| # calculate for next line |
| if self.ENDS_WITH_BACKSLASH.search(text): |
| self.backslash = True |
| self.lastline = text |
| return |
| self.backslash = False |
| |
| if last_line_ends_in_backslash and text.strip() == "": |
| return ["{}:{}: remove trailing backslash" |
| .format(self.filename, lineno - 1), |
| self.lastline] |
| |
| |
| class TypoInPackageVariable(_CheckFunction): |
| ALLOWED = re.compile(r"|".join([ |
| "ACLOCAL_DIR", |
| "ACLOCAL_HOST_DIR", |
| "ACLOCAL_PATH", |
| "BR_CCACHE_INITIAL_SETUP", |
| "BR_LIBC", |
| "BR_NO_CHECK_HASH_FOR", |
| "LINUX_EXTENSIONS", |
| "LINUX_POST_PATCH_HOOKS", |
| "LINUX_TOOLS", |
| "LUA_RUN", |
| "MKFS_JFFS2", |
| "MKIMAGE_ARCH", |
| "PACKAGES_PERMISSIONS_TABLE", |
| "PKG_CONFIG_HOST_BINARY", |
| "SUMTOOL", |
| "TARGET_FINALIZE_HOOKS", |
| "TARGETS_ROOTFS", |
| "XTENSA_CORE_NAME"])) |
| VARIABLE = re.compile(r"^([A-Z0-9_]+_[A-Z0-9_]+)\s*(\+|)=") |
| |
| def before(self): |
| package, _ = os.path.splitext(os.path.basename(self.filename)) |
| package = package.replace("-", "_").upper() |
| # linux tools do not use LINUX_TOOL_ prefix for variables |
| package = package.replace("LINUX_TOOL_", "") |
| # linux extensions do not use LINUX_EXT_ prefix for variables |
| package = package.replace("LINUX_EXT_", "") |
| self.package = package |
| self.REGEX = re.compile(r"^(HOST_|ROOTFS_)?({}_[A-Z0-9_]+)".format(package)) |
| self.FIND_VIRTUAL = re.compile( |
| r"^{}_PROVIDES\s*(\+|)=\s*(.*)".format(package)) |
| self.virtual = [] |
| |
| def check_line(self, lineno, text): |
| m = self.VARIABLE.search(text) |
| if m is None: |
| return |
| |
| variable = m.group(1) |
| |
| # allow to set variables for virtual package this package provides |
| v = self.FIND_VIRTUAL.search(text) |
| if v: |
| self.virtual += v.group(2).upper().split() |
| return |
| for virtual in self.virtual: |
| if variable.startswith("{}_".format(virtual)): |
| return |
| |
| if self.ALLOWED.match(variable): |
| return |
| if self.REGEX.search(text) is None: |
| return ["{}:{}: possible typo: {} -> *{}*" |
| .format(self.filename, lineno, variable, self.package), |
| text] |
| |
| |
| class UselessFlag(_CheckFunction): |
| DEFAULT_AUTOTOOLS_FLAG = re.compile(r"^.*{}".format("|".join([ |
| r"_AUTORECONF\s*=\s*NO", |
| r"_LIBTOOL_PATCH\s*=\s*YES"]))) |
| DEFAULT_GENERIC_FLAG = re.compile(r"^.*{}".format("|".join([ |
| r"_INSTALL_IMAGES\s*=\s*NO", |
| r"_INSTALL_REDISTRIBUTE\s*=\s*YES", |
| r"_INSTALL_STAGING\s*=\s*NO", |
| r"_INSTALL_TARGET\s*=\s*YES"]))) |
| END_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(end_conditional))) |
| START_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(start_conditional))) |
| |
| def before(self): |
| self.conditional = 0 |
| |
| def check_line(self, lineno, text): |
| if self.START_CONDITIONAL.search(text): |
| self.conditional += 1 |
| return |
| if self.END_CONDITIONAL.search(text): |
| self.conditional -= 1 |
| return |
| |
| # allow non-default conditionally overridden by default |
| if self.conditional > 0: |
| return |
| |
| if self.DEFAULT_GENERIC_FLAG.search(text): |
| return ["{}:{}: useless default value ({}#" |
| "_infrastructure_for_packages_with_specific_build_systems)" |
| .format(self.filename, lineno, self.url_to_manual), |
| text] |
| |
| if self.DEFAULT_AUTOTOOLS_FLAG.search(text) and not text.lstrip().startswith("HOST_"): |
| return ["{}:{}: useless default value " |
| "({}#_infrastructure_for_autotools_based_packages)" |
| .format(self.filename, lineno, self.url_to_manual), |
| text] |
| |
| |
| class VariableWithBraces(_CheckFunction): |
| VARIABLE_WITH_BRACES = re.compile(r"^[^#].*[^$]\${\w+}") |
| |
| def check_line(self, lineno, text): |
| if self.VARIABLE_WITH_BRACES.match(text.rstrip()): |
| return ["{}:{}: use $() to delimit variables, not ${{}}" |
| .format(self.filename, lineno), |
| text] |