mirror of
https://github.com/godotengine/buildroot.git
synced 2026-01-05 14:09:53 +03:00
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>
(cherry picked from commit a1bb132a81)
Signed-off-by: Peter Korsgaard <peter@korsgaard.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]
|