a1bb132a81
An 'else' or 'elif' clause inside a make conditional should not be indented in the same way as the if/endif clause. check-package did not recognize the else statement and expected an indentation. For example: ifdef FOOBAR interesting else more interesting endif would, according to check-package, need to become: ifdef FOOBAR interesting else more interesting endif Treat 'else' and 'elif' the same as if-like keywords in the Indent test, but take into account that 'else' is also valid shell, so we need to correctly handle line continuation to prevent complaining about the 'else' in: ifdef FOOBAR if true; \ ... \ else \ ... \ fi endif We don't add the 'else' and 'elif' statements to start_conditional, because it would cause incorrect nesting counting in class OverriddenVariable. Signed-off-by: Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
332 lines
12 KiB
Python
332 lines
12 KiB
Python
# 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]
|