lib_mk.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  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. import os
  7. import re
  8. from checkpackagelib.base import _CheckFunction
  9. from checkpackagelib.lib import ConsecutiveEmptyLines # noqa: F401
  10. from checkpackagelib.lib import EmptyLastLine # noqa: F401
  11. from checkpackagelib.lib import NewlineAtEof # noqa: F401
  12. from checkpackagelib.lib import TrailingSpace # noqa: F401
  13. from checkpackagelib.lib import Utf8Characters # noqa: F401
  14. from checkpackagelib.tool import NotExecutable # noqa: F401
  15. # used in more than one check
  16. start_conditional = ["ifdef", "ifeq", "ifndef", "ifneq"]
  17. continue_conditional = ["elif", "else"]
  18. end_conditional = ["endif"]
  19. class DoNotInstallToHostdirUsr(_CheckFunction):
  20. INSTALL_TO_HOSTDIR_USR = re.compile(r"^[^#].*\$\(HOST_DIR\)/usr")
  21. def check_line(self, lineno, text):
  22. if self.INSTALL_TO_HOSTDIR_USR.match(text.rstrip()):
  23. return ["{}:{}: install files to $(HOST_DIR)/ instead of $(HOST_DIR)/usr/"
  24. .format(self.filename, lineno),
  25. text]
  26. class Ifdef(_CheckFunction):
  27. IFDEF = re.compile(r"^\s*(else\s+|)(ifdef|ifndef)\s")
  28. def check_line(self, lineno, text):
  29. m = self.IFDEF.search(text)
  30. if m is None:
  31. return
  32. word = m.group(2)
  33. if word == 'ifdef':
  34. return ["{}:{}: use ifeq ($(SYMBOL),y) instead of ifdef SYMBOL"
  35. .format(self.filename, lineno),
  36. text]
  37. else:
  38. return ["{}:{}: use ifneq ($(SYMBOL),y) instead of ifndef SYMBOL"
  39. .format(self.filename, lineno),
  40. text]
  41. def get_package_prefix_from_filename(filename):
  42. """Return a tuple (pkgname, PKGNAME) with the package name derived from the file name"""
  43. # Double splitext to support .mk.in
  44. package = os.path.splitext(os.path.splitext(os.path.basename(filename))[0])[0]
  45. # linux tools do not use LINUX_TOOL_ prefix for variables
  46. package = package.replace("linux-tool-", "")
  47. # linux extensions do not use LINUX_EXT_ prefix for variables
  48. package = package.replace("linux-ext-", "")
  49. package_upper = package.replace("-", "_").upper()
  50. return package, package_upper
  51. class Indent(_CheckFunction):
  52. COMMENT = re.compile(r"^\s*#")
  53. CONDITIONAL = re.compile(r"^\s*({})\s".format("|".join(start_conditional + end_conditional + continue_conditional)))
  54. ENDS_WITH_BACKSLASH = re.compile(r"^[^#].*\\$")
  55. END_DEFINE = re.compile(r"^\s*endef\s")
  56. MAKEFILE_TARGET = re.compile(r"^[^# \t]+:\s")
  57. START_DEFINE = re.compile(r"^\s*define\s")
  58. def before(self):
  59. self.define = False
  60. self.backslash = False
  61. self.makefile_target = False
  62. def check_line(self, lineno, text):
  63. if self.START_DEFINE.search(text):
  64. self.define = True
  65. return
  66. if self.END_DEFINE.search(text):
  67. self.define = False
  68. return
  69. expect_tabs = False
  70. if self.define or self.backslash or self.makefile_target:
  71. expect_tabs = True
  72. if not self.backslash and self.CONDITIONAL.search(text):
  73. expect_tabs = False
  74. # calculate for next line
  75. if self.ENDS_WITH_BACKSLASH.search(text):
  76. self.backslash = True
  77. else:
  78. self.backslash = False
  79. if self.MAKEFILE_TARGET.search(text):
  80. self.makefile_target = True
  81. return
  82. if text.strip() == "":
  83. self.makefile_target = False
  84. return
  85. # comment can be indented or not inside define ... endef, so ignore it
  86. if self.define and self.COMMENT.search(text):
  87. return
  88. if expect_tabs:
  89. if not text.startswith("\t"):
  90. return ["{}:{}: expected indent with tabs"
  91. .format(self.filename, lineno),
  92. text]
  93. else:
  94. if text.startswith("\t"):
  95. return ["{}:{}: unexpected indent with tabs"
  96. .format(self.filename, lineno),
  97. text]
  98. class OverriddenVariable(_CheckFunction):
  99. CONCATENATING = re.compile(r"^([A-Z0-9_]+)\s*(\+|:|)=\s*\$\(\1\)")
  100. END_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(end_conditional)))
  101. OVERRIDING_ASSIGNMENTS = [':=', "="]
  102. START_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(start_conditional)))
  103. VARIABLE = re.compile(r"^([A-Z0-9_]+)\s*((\+|:|)=)")
  104. USUALLY_OVERRIDDEN = re.compile(r"^[A-Z0-9_]+({})".format("|".join([
  105. r"_ARCH\s*=\s*",
  106. r"_CPU\s*=\s*",
  107. r"_SITE\s*=\s*",
  108. r"_SOURCE\s*=\s*",
  109. r"_VERSION\s*=\s*"])))
  110. FORBIDDEN_OVERRIDDEN = re.compile(r"^[A-Z0-9_]+({})".format("|".join([
  111. r"_CONF_OPTS\s*=\s*",
  112. r"_DEPENDENCIES\s*=\s*"])))
  113. def before(self):
  114. self.conditional = 0
  115. self.unconditionally_set = []
  116. self.conditionally_set = []
  117. def check_line(self, lineno, text):
  118. if self.START_CONDITIONAL.search(text):
  119. self.conditional += 1
  120. return
  121. if self.END_CONDITIONAL.search(text):
  122. self.conditional -= 1
  123. return
  124. m = self.VARIABLE.search(text)
  125. if m is None:
  126. return
  127. variable, assignment = m.group(1, 2)
  128. if self.conditional == 0:
  129. if variable in self.conditionally_set:
  130. self.unconditionally_set.append(variable)
  131. if assignment in self.OVERRIDING_ASSIGNMENTS:
  132. return ["{}:{}: unconditional override of variable {} previously conditionally set"
  133. .format(self.filename, lineno, variable),
  134. text]
  135. if variable not in self.unconditionally_set:
  136. self.unconditionally_set.append(variable)
  137. return
  138. if assignment in self.OVERRIDING_ASSIGNMENTS:
  139. return ["{}:{}: unconditional override of variable {}"
  140. .format(self.filename, lineno, variable),
  141. text]
  142. else:
  143. if self.FORBIDDEN_OVERRIDDEN.search(text):
  144. return ["{}:{}: conditional override of variable {}"
  145. .format(self.filename, lineno, variable),
  146. text]
  147. if variable not in self.unconditionally_set:
  148. self.conditionally_set.append(variable)
  149. return
  150. if self.CONCATENATING.search(text):
  151. return ["{}:{}: immediate assignment to append to variable {}"
  152. .format(self.filename, lineno, variable),
  153. text]
  154. if self.USUALLY_OVERRIDDEN.search(text):
  155. return
  156. if assignment in self.OVERRIDING_ASSIGNMENTS:
  157. return ["{}:{}: conditional override of variable {}"
  158. .format(self.filename, lineno, variable),
  159. text]
  160. class PackageHeader(_CheckFunction):
  161. def before(self):
  162. self.skip = False
  163. def check_line(self, lineno, text):
  164. if self.skip or lineno > 6:
  165. return
  166. if lineno in [1, 5]:
  167. if lineno == 1 and text.startswith("include "):
  168. self.skip = True
  169. return
  170. if text.rstrip() != "#" * 80:
  171. return ["{}:{}: should be 80 hashes ({}#writing-rules-mk)"
  172. .format(self.filename, lineno, self.url_to_manual),
  173. text,
  174. "#" * 80]
  175. elif lineno in [2, 4]:
  176. if text.rstrip() != "#":
  177. return ["{}:{}: should be 1 hash ({}#writing-rules-mk)"
  178. .format(self.filename, lineno, self.url_to_manual),
  179. text]
  180. elif lineno == 6:
  181. if text.rstrip() != "":
  182. return ["{}:{}: should be a blank line ({}#writing-rules-mk)"
  183. .format(self.filename, lineno, self.url_to_manual),
  184. text]
  185. class RemoveDefaultPackageSourceVariable(_CheckFunction):
  186. packages_that_may_contain_default_source = ["binutils", "gcc", "gdb"]
  187. def before(self):
  188. self.package, package_upper = get_package_prefix_from_filename(self.filename)
  189. self.FIND_SOURCE = re.compile(
  190. r"^{}_SOURCE\s*=\s*{}-\$\({}_VERSION\)\.tar\.gz"
  191. .format(package_upper, self.package, package_upper))
  192. def check_line(self, lineno, text):
  193. if self.FIND_SOURCE.search(text):
  194. if self.package in self.packages_that_may_contain_default_source:
  195. return
  196. return ["{}:{}: remove default value of _SOURCE variable "
  197. "({}#generic-package-reference)"
  198. .format(self.filename, lineno, self.url_to_manual),
  199. text]
  200. class SpaceBeforeBackslash(_CheckFunction):
  201. TAB_OR_MULTIPLE_SPACES_BEFORE_BACKSLASH = re.compile(r"^.*( |\t ?)\\$")
  202. def check_line(self, lineno, text):
  203. if self.TAB_OR_MULTIPLE_SPACES_BEFORE_BACKSLASH.match(text.rstrip()):
  204. return ["{}:{}: use only one space before backslash"
  205. .format(self.filename, lineno),
  206. text]
  207. class TrailingBackslash(_CheckFunction):
  208. ENDS_WITH_BACKSLASH = re.compile(r"^[^#].*\\$")
  209. def before(self):
  210. self.backslash = False
  211. def check_line(self, lineno, text):
  212. last_line_ends_in_backslash = self.backslash
  213. # calculate for next line
  214. if self.ENDS_WITH_BACKSLASH.search(text):
  215. self.backslash = True
  216. self.lastline = text
  217. return
  218. self.backslash = False
  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. class TypoInPackageVariable(_CheckFunction):
  224. ALLOWED = re.compile(r"|".join([
  225. "ACLOCAL_DIR",
  226. "ACLOCAL_HOST_DIR",
  227. "ACLOCAL_PATH",
  228. "BR_CCACHE_INITIAL_SETUP",
  229. "BR_LIBC",
  230. "BR_NO_CHECK_HASH_FOR",
  231. "GCC_TARGET",
  232. "LINUX_EXTENSIONS",
  233. "LINUX_POST_PATCH_HOOKS",
  234. "LINUX_TOOLS",
  235. "LUA_RUN",
  236. "MKFS_JFFS2",
  237. "MKIMAGE_ARCH",
  238. "PACKAGES_PERMISSIONS_TABLE",
  239. "PKG_CONFIG_HOST_BINARY",
  240. "SUMTOOL",
  241. "TARGET_FINALIZE_HOOKS",
  242. "TARGETS_ROOTFS",
  243. "XTENSA_CORE_NAME"]))
  244. VARIABLE = re.compile(r"^(define\s+)?([A-Z0-9_]+_[A-Z0-9_]+)")
  245. def before(self):
  246. _, self.package = get_package_prefix_from_filename(self.filename)
  247. self.REGEX = re.compile(r"(HOST_|ROOTFS_)?({}_[A-Z0-9_]+)".format(self.package))
  248. self.FIND_VIRTUAL = re.compile(
  249. r"^{}_PROVIDES\s*(\+|)=\s*(.*)".format(self.package))
  250. self.virtual = []
  251. def check_line(self, lineno, text):
  252. m = self.VARIABLE.search(text)
  253. if m is None:
  254. return
  255. variable = m.group(2)
  256. # allow to set variables for virtual package this package provides
  257. v = self.FIND_VIRTUAL.search(text)
  258. if v:
  259. self.virtual += v.group(2).upper().split()
  260. return
  261. for virtual in self.virtual:
  262. if variable.startswith("{}_".format(virtual)):
  263. return
  264. if self.ALLOWED.match(variable):
  265. return
  266. if self.REGEX.search(variable) is None:
  267. return ["{}:{}: possible typo, variable not properly prefixed: {} -> *{}_XXXX* ({}#_tips_and_tricks)"
  268. .format(self.filename, lineno, variable, self.package, self.url_to_manual),
  269. text]
  270. class UselessFlag(_CheckFunction):
  271. DEFAULT_AUTOTOOLS_FLAG = re.compile(r"^.*{}".format("|".join([
  272. r"_AUTORECONF\s*=\s*NO",
  273. r"_LIBTOOL_PATCH\s*=\s*YES"])))
  274. DEFAULT_GENERIC_FLAG = re.compile(r"^.*{}".format("|".join([
  275. r"_INSTALL_IMAGES\s*=\s*NO",
  276. r"_REDISTRIBUTE\s*=\s*YES",
  277. r"_INSTALL_STAGING\s*=\s*NO",
  278. r"_INSTALL_TARGET\s*=\s*YES"])))
  279. END_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(end_conditional)))
  280. START_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(start_conditional)))
  281. def before(self):
  282. self.conditional = 0
  283. def check_line(self, lineno, text):
  284. if self.START_CONDITIONAL.search(text):
  285. self.conditional += 1
  286. return
  287. if self.END_CONDITIONAL.search(text):
  288. self.conditional -= 1
  289. return
  290. # allow non-default conditionally overridden by default
  291. if self.conditional > 0:
  292. return
  293. if self.DEFAULT_GENERIC_FLAG.search(text):
  294. return ["{}:{}: useless default value ({}#"
  295. "_infrastructure_for_packages_with_specific_build_systems)"
  296. .format(self.filename, lineno, self.url_to_manual),
  297. text]
  298. if self.DEFAULT_AUTOTOOLS_FLAG.search(text) and not text.lstrip().startswith("HOST_"):
  299. return ["{}:{}: useless default value "
  300. "({}#_infrastructure_for_autotools_based_packages)"
  301. .format(self.filename, lineno, self.url_to_manual),
  302. text]
  303. class VariableWithBraces(_CheckFunction):
  304. VARIABLE_WITH_BRACES = re.compile(r"^[^#].*[^$]\${\w+}")
  305. def check_line(self, lineno, text):
  306. if self.VARIABLE_WITH_BRACES.match(text.rstrip()):
  307. return ["{}:{}: use $() to delimit variables, not ${{}}"
  308. .format(self.filename, lineno),
  309. text]
  310. class CPEVariables(_CheckFunction):
  311. """
  312. Check that the values for the CPE variables are not the default.
  313. - CPE_ID_* variables must not be set to their default
  314. - CPE_ID_VALID must not be set if a non-default CPE_ID variable is set
  315. """
  316. def before(self):
  317. pkg, _ = os.path.splitext(os.path.basename(self.filename))
  318. self.CPE_fields_defaults = {
  319. "VALID": "NO",
  320. "PREFIX": "cpe:2.3:a",
  321. "VENDOR": f"{pkg}_project",
  322. "PRODUCT": pkg,
  323. "VERSION": None,
  324. "UPDATE": "*",
  325. }
  326. self.valid = None
  327. self.non_defaults = 0
  328. self.CPE_FIELDS_RE = re.compile(
  329. r"^\s*(.+_CPE_ID_({}))\s*=\s*(.+)$"
  330. .format("|".join(self.CPE_fields_defaults)),
  331. )
  332. self.VERSION_RE = re.compile(
  333. rf"^(HOST_)?{pkg.upper().replace('-', '_')}_VERSION\s*=\s*(.+)$",
  334. )
  335. self.COMMENT_RE = re.compile(r"^\s*#.*")
  336. def check_line(self, lineno, text):
  337. text = self.COMMENT_RE.sub('', text.rstrip())
  338. # WARNING! The VERSION_RE can _also_ match the same lines as CPE_FIELDS_RE,
  339. # but not the other way around. So we must first check for CPE_FIELDS_RE,
  340. # and if not matched, then and only then check for VERSION_RE.
  341. match = self.CPE_FIELDS_RE.match(text)
  342. if match:
  343. var, field, val = match.groups()
  344. return self._check_field(lineno, text, field, var, val)
  345. match = self.VERSION_RE.match(text)
  346. if match:
  347. self.CPE_fields_defaults["VERSION"] = match.groups()[1]
  348. def after(self):
  349. # "VALID" counts in the non-defaults; so when "VALID" is present,
  350. # 1 non-default means only "VALID" is present, so that's OK.
  351. if self.valid and self.non_defaults > 1:
  352. return ["{}:{}: 'YES' is implied when a non-default CPE_ID field is specified: {} ({}#cpe-id)".format(
  353. self.filename,
  354. self.valid["lineno"],
  355. self.valid["text"],
  356. self.url_to_manual,
  357. )]
  358. def _check_field(self, lineno, text, field, var, val):
  359. if field == "VERSION" and self.CPE_fields_defaults[field] is None:
  360. return ["{}:{}: expecting package version to be set before CPE_ID_VERSION".format(
  361. self.filename,
  362. lineno,
  363. )]
  364. if val == self.CPE_fields_defaults[field]:
  365. return ["{}:{}: '{}' is the default value for {} ({}#cpe-id)".format(
  366. self.filename,
  367. lineno,
  368. val,
  369. var,
  370. self.url_to_manual,
  371. )]
  372. else:
  373. if field == "VALID":
  374. self.valid = {"lineno": lineno, "text": text}
  375. self.non_defaults += 1