insane.bbclass 70 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632
  1. #
  2. # Copyright OpenEmbedded Contributors
  3. #
  4. # SPDX-License-Identifier: MIT
  5. #
  6. # BB Class inspired by ebuild.sh
  7. #
  8. # This class will test files after installation for certain
  9. # security issues and other kind of issues.
  10. #
  11. # Checks we do:
  12. # -Check the ownership and permissions
  13. # -Check the RUNTIME path for the $TMPDIR
  14. # -Check if .la files wrongly point to workdir
  15. # -Check if .pc files wrongly point to workdir
  16. # -Check if packages contains .debug directories or .so files
  17. # where they should be in -dev or -dbg
  18. # -Check if config.log contains traces to broken autoconf tests
  19. # -Check invalid characters (non-utf8) on some package metadata
  20. # -Ensure that binaries in base_[bindir|sbindir|libdir] do not link
  21. # into exec_prefix
  22. # -Check that scripts in base_[bindir|sbindir|libdir] do not reference
  23. # files under exec_prefix
  24. # -Check if the package name is upper case
  25. # These tests are required to be enabled and pass for Yocto Project Compatible Status
  26. # for a layer. To change this list, please contact the Yocto Project TSC.
  27. CHECKLAYER_REQUIRED_TESTS = "\
  28. configure-gettext configure-unsafe debug-files dep-cmp expanded-d files-invalid \
  29. host-user-contaminated incompatible-license infodir installed-vs-shipped invalid-chars \
  30. invalid-packageconfig la \
  31. license-checksum license-exception license-exists license-file-missing license-format license-no-generic license-syntax \
  32. mime mime-xdg missing-update-alternatives multilib obsolete-license \
  33. packages-list patch-fuzz patch-status perllocalpod perm-config perm-line perm-link recipe-naming \
  34. pkgconfig pkgvarcheck pkgv-undefined pn-overrides shebang-size src-uri-bad symlink-to-sysroot \
  35. unhandled-features-check unknown-configure-option unlisted-pkg-lics uppercase-pn useless-rpaths \
  36. virtual-slash xorg-driver-abi"
  37. # Elect whether a given type of error is a warning or error, they may
  38. # have been set by other files.
  39. WARN_QA ?= "32bit-time native-last pep517-backend"
  40. ERROR_QA ?= "\
  41. already-stripped arch buildpaths build-deps debug-deps dev-deps dev-elf dev-so empty-dirs file-rdeps \
  42. ldflags libdir missing-ptest rpaths staticdev textrel version-going-backwards \
  43. ${CHECKLAYER_REQUIRED_TESTS}"
  44. # Add usrmerge QA check based on distro feature
  45. ERROR_QA:append = "${@bb.utils.contains('DISTRO_FEATURES', 'usrmerge', ' usrmerge', '', d)}"
  46. WARN_QA:append:layer-core = " missing-metadata missing-maintainer"
  47. FAKEROOT_QA = "host-user-contaminated"
  48. FAKEROOT_QA[doc] = "QA tests which need to run under fakeroot. If any \
  49. enabled tests are listed here, the do_package_qa task will run under fakeroot."
  50. UNKNOWN_CONFIGURE_OPT_IGNORE ?= "--enable-nls --disable-nls --disable-silent-rules --disable-dependency-tracking --disable-static"
  51. # This is a list of directories that are expected to be empty.
  52. QA_EMPTY_DIRS ?= " \
  53. /dev/pts \
  54. /media \
  55. /proc \
  56. /run \
  57. /tmp \
  58. ${localstatedir}/run \
  59. ${localstatedir}/volatile \
  60. "
  61. # It is possible to specify why a directory is expected to be empty by defining
  62. # QA_EMPTY_DIRS_RECOMMENDATION:<path>, which will then be included in the error
  63. # message if the directory is not empty. If it is not specified for a directory,
  64. # then "but it is expected to be empty" will be used.
  65. def package_qa_clean_path(path, d, pkg=None):
  66. """
  67. Remove redundant paths from the path for display. If pkg isn't set then
  68. TMPDIR is stripped, otherwise PKGDEST/pkg is stripped.
  69. """
  70. if pkg:
  71. path = path.replace(os.path.join(d.getVar("PKGDEST"), pkg), "/")
  72. return path.replace(d.getVar("TMPDIR"), "/").replace("//", "/")
  73. QAPATHTEST[shebang-size] = "package_qa_check_shebang_size"
  74. def package_qa_check_shebang_size(path, name, d, elf):
  75. global cpath
  76. if elf or cpath.islink(path) or not cpath.isfile(path):
  77. return
  78. try:
  79. with open(path, 'rb') as f:
  80. stanza = f.readline(130)
  81. except IOError:
  82. return
  83. if stanza.startswith(b'#!'):
  84. try:
  85. stanza.decode("utf-8")
  86. except UnicodeDecodeError:
  87. #If it is not a text file, it is not a script
  88. return
  89. if len(stanza) > 129:
  90. oe.qa.handle_error("shebang-size", "%s: %s maximum shebang size exceeded, the maximum size is 128." % (name, package_qa_clean_path(path, d, name)), d)
  91. return
  92. QAPATHTEST[libexec] = "package_qa_check_libexec"
  93. def package_qa_check_libexec(path,name, d, elf):
  94. # Skip the case where the default is explicitly /usr/libexec
  95. libexec = d.getVar('libexecdir')
  96. if libexec == "/usr/libexec":
  97. return
  98. if 'libexec' in path.split(os.path.sep):
  99. oe.qa.handle_error("libexec", "%s: %s is using libexec please relocate to %s" % (name, package_qa_clean_path(path, d, name), libexec), d)
  100. QAPATHTEST[rpaths] = "package_qa_check_rpath"
  101. def package_qa_check_rpath(file, name, d, elf):
  102. """
  103. Check for dangerous RPATHs
  104. """
  105. if not elf:
  106. return
  107. bad_dirs = [d.getVar('BASE_WORKDIR'), d.getVar('STAGING_DIR_TARGET')]
  108. phdrs = elf.run_objdump("-p", d)
  109. import re
  110. rpath_re = re.compile(r"\s+(?:RPATH|RUNPATH)\s+(.*)")
  111. for line in phdrs.split("\n"):
  112. m = rpath_re.match(line)
  113. if m:
  114. rpath = m.group(1)
  115. for dir in bad_dirs:
  116. if dir in rpath:
  117. oe.qa.handle_error("rpaths", "%s: %s contains bad RPATH %s" % (name, package_qa_clean_path(file, d, name), rpath), d)
  118. QAPATHTEST[useless-rpaths] = "package_qa_check_useless_rpaths"
  119. def package_qa_check_useless_rpaths(file, name, d, elf):
  120. """
  121. Check for RPATHs that are useless but not dangerous
  122. """
  123. def rpath_eq(a, b):
  124. return os.path.normpath(a) == os.path.normpath(b)
  125. if not elf:
  126. return
  127. libdir = d.getVar("libdir")
  128. base_libdir = d.getVar("base_libdir")
  129. phdrs = elf.run_objdump("-p", d)
  130. import re
  131. rpath_re = re.compile(r"\s+(?:RPATH|RUNPATH)\s+(.*)")
  132. for line in phdrs.split("\n"):
  133. m = rpath_re.match(line)
  134. if m:
  135. rpath = m.group(1)
  136. if rpath_eq(rpath, libdir) or rpath_eq(rpath, base_libdir):
  137. # The dynamic linker searches both these places anyway. There is no point in
  138. # looking there again.
  139. oe.qa.handle_error("useless-rpaths", "%s: %s contains probably-redundant RPATH %s" % (name, package_qa_clean_path(file, d, name), rpath), d)
  140. QAPATHTEST[dev-so] = "package_qa_check_dev"
  141. def package_qa_check_dev(path, name, d, elf):
  142. """
  143. Check for ".so" library symlinks in non-dev packages
  144. """
  145. global cpath
  146. if not name.endswith("-dev") and not name.endswith("-dbg") and not name.endswith("-ptest") and not name.startswith("nativesdk-") and path.endswith(".so") and cpath.islink(path):
  147. oe.qa.handle_error("dev-so", "non -dev/-dbg/nativesdk- package %s contains symlink .so '%s'" % \
  148. (name, package_qa_clean_path(path, d, name)), d)
  149. QAPATHTEST[dev-elf] = "package_qa_check_dev_elf"
  150. def package_qa_check_dev_elf(path, name, d, elf):
  151. """
  152. Check that -dev doesn't contain real shared libraries. The test has to
  153. check that the file is not a link and is an ELF object as some recipes
  154. install link-time .so files that are linker scripts.
  155. """
  156. global cpath
  157. if name.endswith("-dev") and path.endswith(".so") and not cpath.islink(path) and elf:
  158. oe.qa.handle_error("dev-elf", "-dev package %s contains non-symlink .so '%s'" % \
  159. (name, package_qa_clean_path(path, d, name)), d)
  160. QAPATHTEST[staticdev] = "package_qa_check_staticdev"
  161. def package_qa_check_staticdev(path, name, d, elf):
  162. """
  163. Check for ".a" library in non-staticdev packages
  164. There are a number of exceptions to this rule, -pic packages can contain
  165. static libraries, the _nonshared.a belong with their -dev packages and
  166. libgcc.a, libgcov.a will be skipped in their packages
  167. """
  168. if not name.endswith("-pic") and not name.endswith("-staticdev") and not name.endswith("-ptest") and path.endswith(".a") and not path.endswith("_nonshared.a") and not '/usr/lib/debug-static/' in path and not '/.debug-static/' in path:
  169. oe.qa.handle_error("staticdev", "non -staticdev package contains static .a library: %s path '%s'" % \
  170. (name, package_qa_clean_path(path, d, name)), d)
  171. QAPATHTEST[mime] = "package_qa_check_mime"
  172. def package_qa_check_mime(path, name, d, elf):
  173. """
  174. Check if package installs mime types to /usr/share/mime/packages
  175. while no inheriting mime.bbclass
  176. """
  177. if d.getVar("datadir") + "/mime/packages" in path and path.endswith('.xml') and not bb.data.inherits_class("mime", d):
  178. oe.qa.handle_error("mime", "package contains mime types but does not inherit mime: %s path '%s'" % \
  179. (name, package_qa_clean_path(path, d, name)), d)
  180. QAPATHTEST[mime-xdg] = "package_qa_check_mime_xdg"
  181. def package_qa_check_mime_xdg(path, name, d, elf):
  182. """
  183. Check if package installs desktop file containing MimeType and requires
  184. mime-types.bbclass to create /usr/share/applications/mimeinfo.cache
  185. """
  186. if d.getVar("datadir") + "/applications" in path and path.endswith('.desktop') and not bb.data.inherits_class("mime-xdg", d):
  187. mime_type_found = False
  188. try:
  189. with open(path, 'r') as f:
  190. for line in f.read().split('\n'):
  191. if 'MimeType' in line:
  192. mime_type_found = True
  193. break;
  194. except:
  195. # At least libreoffice installs symlinks with absolute paths that are dangling here.
  196. # We could implement some magic but for few (one) recipes it is not worth the effort so just warn:
  197. wstr = "%s cannot open %s - is it a symlink with absolute path?\n" % (name, package_qa_clean_path(path, d, name))
  198. wstr += "Please check if (linked) file contains key 'MimeType'.\n"
  199. pkgname = name
  200. if name == d.getVar('PN'):
  201. pkgname = '${PN}'
  202. wstr += "If yes: add \'inhert mime-xdg\' and \'MIME_XDG_PACKAGES += \"%s\"\' / if no add \'INSANE_SKIP:%s += \"mime-xdg\"\' to recipe." % (pkgname, pkgname)
  203. oe.qa.handle_error("mime-xdg", wstr, d)
  204. if mime_type_found:
  205. oe.qa.handle_error("mime-xdg", "%s: contains desktop file with key 'MimeType' but does not inhert mime-xdg: %s" % \
  206. (name, package_qa_clean_path(path, d, name)), d)
  207. def package_qa_check_libdir(d):
  208. """
  209. Check for wrong library installation paths. For instance, catch
  210. recipes installing /lib/bar.so when ${base_libdir}="lib32" or
  211. installing in /usr/lib64 when ${libdir}="/usr/lib"
  212. """
  213. import re
  214. pkgdest = d.getVar('PKGDEST')
  215. base_libdir = d.getVar("base_libdir") + os.sep
  216. libdir = d.getVar("libdir") + os.sep
  217. libexecdir = d.getVar("libexecdir") + os.sep
  218. exec_prefix = d.getVar("exec_prefix") + os.sep
  219. messages = []
  220. # The re's are purposely fuzzy, as some there are some .so.x.y.z files
  221. # that don't follow the standard naming convention. It checks later
  222. # that they are actual ELF files
  223. lib_re = re.compile(r"^/lib.+\.so(\..+)?$")
  224. exec_re = re.compile(r"^%s.*/lib.+\.so(\..+)?$" % exec_prefix)
  225. for root, dirs, files in os.walk(pkgdest):
  226. if root == pkgdest:
  227. # Skip subdirectories for any packages with libdir in INSANE_SKIP
  228. skippackages = []
  229. for package in dirs:
  230. if 'libdir' in (d.getVar('INSANE_SKIP:' + package) or "").split():
  231. bb.note("Package %s skipping libdir QA test" % (package))
  232. skippackages.append(package)
  233. elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-file-directory' and package.endswith("-dbg"):
  234. bb.note("Package %s skipping libdir QA test for PACKAGE_DEBUG_SPLIT_STYLE equals debug-file-directory" % (package))
  235. skippackages.append(package)
  236. for package in skippackages:
  237. dirs.remove(package)
  238. for file in files:
  239. full_path = os.path.join(root, file)
  240. rel_path = os.path.relpath(full_path, pkgdest)
  241. if os.sep in rel_path:
  242. package, rel_path = rel_path.split(os.sep, 1)
  243. rel_path = os.sep + rel_path
  244. if lib_re.match(rel_path):
  245. if base_libdir not in rel_path:
  246. # make sure it's an actual ELF file
  247. elf = oe.qa.ELFFile(full_path)
  248. try:
  249. elf.open()
  250. messages.append("%s: found library in wrong location: %s" % (package, rel_path))
  251. except (oe.qa.NotELFFileError, FileNotFoundError):
  252. pass
  253. if exec_re.match(rel_path):
  254. if libdir not in rel_path and libexecdir not in rel_path:
  255. # make sure it's an actual ELF file
  256. elf = oe.qa.ELFFile(full_path)
  257. try:
  258. elf.open()
  259. messages.append("%s: found library in wrong location: %s" % (package, rel_path))
  260. except (oe.qa.NotELFFileError, FileNotFoundError):
  261. pass
  262. if messages:
  263. oe.qa.handle_error("libdir", "\n".join(messages), d)
  264. QAPATHTEST[debug-files] = "package_qa_check_dbg"
  265. def package_qa_check_dbg(path, name, d, elf):
  266. """
  267. Check for ".debug" files or directories outside of the dbg package
  268. """
  269. if not "-dbg" in name and not "-ptest" in name:
  270. if '.debug' in path.split(os.path.sep):
  271. oe.qa.handle_error("debug-files", "%s: non debug package contains .debug directory %s" % \
  272. (name, package_qa_clean_path(path, d, name)), d)
  273. QAPATHTEST[arch] = "package_qa_check_arch"
  274. def package_qa_check_arch(path,name,d, elf):
  275. """
  276. Check if archs are compatible
  277. """
  278. import re, oe.elf
  279. if not elf:
  280. return
  281. host_os = d.getVar('HOST_OS')
  282. host_arch = d.getVar('HOST_ARCH')
  283. provides = d.getVar('PROVIDES')
  284. if host_arch == "allarch":
  285. oe.qa.handle_error("arch", "%s: inherits the allarch class, but has architecture-specific binaries %s" % \
  286. (name, package_qa_clean_path(path, d, name)), d)
  287. return
  288. # If this throws an exception, the machine_dict needs expanding
  289. (expected_machine, expected_osabi, expected_abiversion, expected_littleendian, expected_bits) \
  290. = oe.elf.machine_dict(d)[host_os][host_arch]
  291. actual_machine = elf.machine()
  292. actual_bits = elf.abiSize()
  293. actual_littleendian = elf.isLittleEndian()
  294. # BPF don't match the target
  295. if oe.qa.elf_machine_to_string(actual_machine) == "BPF":
  296. return
  297. # These targets have 32-bit userspace but 64-bit kernel, so fudge the expected values
  298. if (("virtual/kernel" in provides) or bb.data.inherits_class("module", d)) and (host_os in ("linux-gnux32", "linux-muslx32", "linux-gnu_ilp32") or re.match(r'mips64.*32', d.getVar('DEFAULTTUNE'))):
  299. expected_bits = 64
  300. # Check the architecture and endiannes of the binary
  301. if expected_machine != actual_machine:
  302. oe.qa.handle_error("arch", "Architecture did not match (%s, expected %s) in %s" % \
  303. (oe.qa.elf_machine_to_string(actual_machine), oe.qa.elf_machine_to_string(expected_machine), package_qa_clean_path(path, d, name)), d)
  304. if expected_bits != actual_bits:
  305. oe.qa.handle_error("arch", "Bit size did not match (%d, expected %d) in %s" % \
  306. (actual_bits, expected_bits, package_qa_clean_path(path, d, name)), d)
  307. if expected_littleendian != actual_littleendian:
  308. oe.qa.handle_error("arch", "Endiannes did not match (%d, expected %d) in %s" % \
  309. (actual_littleendian, expected_littleendian, package_qa_clean_path(path, d, name)), d)
  310. package_qa_check_arch[vardepsexclude] = "DEFAULTTUNE"
  311. QAPATHTEST[desktop] = "package_qa_check_desktop"
  312. def package_qa_check_desktop(path, name, d, elf):
  313. """
  314. Run all desktop files through desktop-file-validate.
  315. """
  316. if path.endswith(".desktop"):
  317. desktop_file_validate = os.path.join(d.getVar('STAGING_BINDIR_NATIVE'),'desktop-file-validate')
  318. output = os.popen("%s %s" % (desktop_file_validate, path))
  319. # This only produces output on errors
  320. for l in output:
  321. oe.qa.handle_error("desktop", "Desktop file issue: " + l.strip(), d)
  322. QAPATHTEST[textrel] = "package_qa_textrel"
  323. def package_qa_textrel(path, name, d, elf):
  324. """
  325. Check if the binary contains relocations in .text
  326. """
  327. if not elf:
  328. return
  329. phdrs = elf.run_objdump("-p", d)
  330. import re
  331. textrel_re = re.compile(r"\s+TEXTREL\s+")
  332. for line in phdrs.split("\n"):
  333. if textrel_re.match(line):
  334. path = package_qa_clean_path(path, d, name)
  335. oe.qa.handle_error("textrel", "%s: ELF binary %s has relocations in .text" % (name, path), d)
  336. return
  337. QAPATHTEST[ldflags] = "package_qa_hash_style"
  338. def package_qa_hash_style(path, name, d, elf):
  339. """
  340. Check if the binary has the right hash style...
  341. """
  342. if not elf:
  343. return
  344. gnu_hash = "--hash-style=gnu" in d.getVar('LDFLAGS')
  345. if not gnu_hash:
  346. gnu_hash = "--hash-style=both" in d.getVar('LDFLAGS')
  347. if not gnu_hash:
  348. return
  349. sane = False
  350. has_syms = False
  351. phdrs = elf.run_objdump("-p", d)
  352. # If this binary has symbols, we expect it to have GNU_HASH too.
  353. for line in phdrs.split("\n"):
  354. if "SYMTAB" in line:
  355. has_syms = True
  356. if "GNU_HASH" in line or "MIPS_XHASH" in line:
  357. sane = True
  358. if ("[mips32]" in line or "[mips64]" in line) and d.getVar('TCLIBC') == "musl":
  359. sane = True
  360. if has_syms and not sane:
  361. path = package_qa_clean_path(path, d, name)
  362. oe.qa.handle_error("ldflags", "File %s in package %s doesn't have GNU_HASH (didn't pass LDFLAGS?)" % (path, name), d)
  363. package_qa_hash_style[vardepsexclude] = "TCLIBC"
  364. QAPATHTEST[buildpaths] = "package_qa_check_buildpaths"
  365. def package_qa_check_buildpaths(path, name, d, elf):
  366. """
  367. Check for build paths inside target files and error if paths are not
  368. explicitly ignored.
  369. """
  370. import stat
  371. # Ignore symlinks/devs/fifos
  372. mode = os.lstat(path).st_mode
  373. if stat.S_ISLNK(mode) or stat.S_ISBLK(mode) or stat.S_ISFIFO(mode) or stat.S_ISCHR(mode) or stat.S_ISSOCK(mode):
  374. return
  375. tmpdir = bytes(d.getVar('TMPDIR'), encoding="utf-8")
  376. with open(path, 'rb') as f:
  377. file_content = f.read()
  378. if tmpdir in file_content:
  379. path = package_qa_clean_path(path, d, name)
  380. oe.qa.handle_error("buildpaths", "File %s in package %s contains reference to TMPDIR" % (path, name), d)
  381. QAPATHTEST[xorg-driver-abi] = "package_qa_check_xorg_driver_abi"
  382. def package_qa_check_xorg_driver_abi(path, name, d, elf):
  383. """
  384. Check that all packages containing Xorg drivers have ABI dependencies
  385. """
  386. # Skip dev, dbg or nativesdk packages
  387. if name.endswith("-dev") or name.endswith("-dbg") or name.startswith("nativesdk-"):
  388. return
  389. driverdir = d.expand("${libdir}/xorg/modules/drivers/")
  390. if driverdir in path and path.endswith(".so"):
  391. mlprefix = d.getVar('MLPREFIX') or ''
  392. for rdep in bb.utils.explode_deps(d.getVar('RDEPENDS:' + name) or ""):
  393. if rdep.startswith("%sxorg-abi-" % mlprefix):
  394. return
  395. oe.qa.handle_error("xorg-driver-abi", "Package %s contains Xorg driver (%s) but no xorg-abi- dependencies" % (name, os.path.basename(path)), d)
  396. QAPATHTEST[infodir] = "package_qa_check_infodir"
  397. def package_qa_check_infodir(path, name, d, elf):
  398. """
  399. Check that /usr/share/info/dir isn't shipped in a particular package
  400. """
  401. infodir = d.expand("${infodir}/dir")
  402. if infodir in path:
  403. oe.qa.handle_error("infodir", "The %s file is not meant to be shipped in a particular package." % infodir, d)
  404. QAPATHTEST[symlink-to-sysroot] = "package_qa_check_symlink_to_sysroot"
  405. def package_qa_check_symlink_to_sysroot(path, name, d, elf):
  406. """
  407. Check that the package doesn't contain any absolute symlinks to the sysroot.
  408. """
  409. global cpath
  410. if cpath.islink(path):
  411. target = os.readlink(path)
  412. if os.path.isabs(target):
  413. tmpdir = d.getVar('TMPDIR')
  414. if target.startswith(tmpdir):
  415. path = package_qa_clean_path(path, d, name)
  416. oe.qa.handle_error("symlink-to-sysroot", "Symlink %s in %s points to TMPDIR" % (path, name), d)
  417. QAPATHTEST[32bit-time] = "check_32bit_symbols"
  418. def check_32bit_symbols(path, packagename, d, elf):
  419. """
  420. Check that ELF files do not use any 32 bit time APIs from glibc.
  421. """
  422. thirtytwo_bit_time_archs = {'arm','armeb','mipsarcho32','powerpc','x86'}
  423. overrides = set(d.getVar('OVERRIDES').split(':'))
  424. if not (thirtytwo_bit_time_archs & overrides):
  425. return
  426. import re
  427. # This list is manually constructed by searching the image folder of the
  428. # glibc recipe for __USE_TIME_BITS64. There is no good way to do this
  429. # automatically.
  430. api32 = {
  431. # /usr/include/time.h
  432. "clock_getres", "clock_gettime", "clock_nanosleep", "clock_settime",
  433. "ctime", "ctime_r", "difftime", "gmtime", "gmtime_r", "localtime",
  434. "localtime_r", "mktime", "nanosleep", "time", "timegm", "timelocal",
  435. "timer_gettime", "timer_settime", "timespec_get", "timespec_getres",
  436. # /usr/include/bits/time.h
  437. "clock_adjtime",
  438. # /usr/include/signal.h
  439. "sigtimedwait",
  440. # /usr/include/sys/time.h
  441. "adjtime",
  442. "futimes", "futimesat", "getitimer", "gettimeofday", "lutimes",
  443. "setitimer", "settimeofday", "utimes",
  444. # /usr/include/sys/timex.h
  445. "adjtimex", "ntp_adjtime", "ntp_gettime", "ntp_gettimex",
  446. # /usr/include/sys/wait.h
  447. "wait3", "wait4",
  448. # /usr/include/sys/stat.h
  449. "fstat", "fstat64", "fstatat", "fstatat64", "futimens", "lstat",
  450. "lstat64", "stat", "stat64", "utimensat",
  451. # /usr/include/sys/poll.h
  452. "ppoll",
  453. # /usr/include/sys/resource.h
  454. "getrusage",
  455. # /usr/include/sys/ioctl.h
  456. "ioctl",
  457. # /usr/include/sys/select.h
  458. "select", "pselect",
  459. # /usr/include/sys/prctl.h
  460. "prctl",
  461. # /usr/include/sys/epoll.h
  462. "epoll_pwait2",
  463. # /usr/include/sys/timerfd.h
  464. "timerfd_gettime", "timerfd_settime",
  465. # /usr/include/sys/socket.h
  466. "getsockopt", "recvmmsg", "recvmsg", "sendmmsg", "sendmsg",
  467. "setsockopt",
  468. # /usr/include/sys/msg.h
  469. "msgctl",
  470. # /usr/include/sys/sem.h
  471. "semctl", "semtimedop",
  472. # /usr/include/sys/shm.h
  473. "shmctl",
  474. # /usr/include/pthread.h
  475. "pthread_clockjoin_np", "pthread_cond_clockwait",
  476. "pthread_cond_timedwait", "pthread_mutex_clocklock",
  477. "pthread_mutex_timedlock", "pthread_rwlock_clockrdlock",
  478. "pthread_rwlock_clockwrlock", "pthread_rwlock_timedrdlock",
  479. "pthread_rwlock_timedwrlock", "pthread_timedjoin_np",
  480. # /usr/include/semaphore.h
  481. "sem_clockwait", "sem_timedwait",
  482. # /usr/include/threads.h
  483. "cnd_timedwait", "mtx_timedlock", "thrd_sleep",
  484. # /usr/include/aio.h
  485. "aio_cancel", "aio_error", "aio_read", "aio_return", "aio_suspend",
  486. "aio_write", "lio_listio",
  487. # /usr/include/mqueue.h
  488. "mq_timedreceive", "mq_timedsend",
  489. # /usr/include/glob.h
  490. "glob", "glob64", "globfree", "globfree64",
  491. # /usr/include/sched.h
  492. "sched_rr_get_interval",
  493. # /usr/include/fcntl.h
  494. "fcntl", "fcntl64",
  495. # /usr/include/utime.h
  496. "utime",
  497. # /usr/include/ftw.h
  498. "ftw", "ftw64", "nftw", "nftw64",
  499. # /usr/include/fts.h
  500. "fts64_children", "fts64_close", "fts64_open", "fts64_read",
  501. "fts64_set", "fts_children", "fts_close", "fts_open", "fts_read",
  502. "fts_set",
  503. # /usr/include/netdb.h
  504. "gai_suspend",
  505. }
  506. ptrn = re.compile(
  507. r'''
  508. (?P<value>[\da-fA-F]+) \s+
  509. (?P<flags>[lgu! ][w ][C ][W ][Ii ][dD ]F) \s+
  510. (?P<section>\*UND\*) \s+
  511. (?P<alignment>(?P<size>[\da-fA-F]+)) \s+
  512. (?P<symbol>
  513. ''' +
  514. r'(?P<notag>' + r'|'.join(sorted(api32)) + r')' +
  515. r'''
  516. (@+(?P<tag>GLIBC_\d+\.\d+\S*)))
  517. ''', re.VERBOSE
  518. )
  519. # elf is a oe.qa.ELFFile object
  520. if elf:
  521. phdrs = elf.run_objdump("-tw", d)
  522. syms = re.finditer(ptrn, phdrs)
  523. usedapis = {sym.group('notag') for sym in syms}
  524. if usedapis:
  525. elfpath = package_qa_clean_path(path, d, packagename)
  526. # Remove any .debug dir, heuristic that probably works
  527. # At this point, any symbol information is stripped into the debug
  528. # package, so that is the only place we will find them.
  529. elfpath = elfpath.replace('.debug/', '')
  530. allowed = "32bit-time" in (d.getVar('INSANE_SKIP') or '').split()
  531. if not allowed:
  532. msgformat = elfpath + " uses 32-bit api '%s'"
  533. for sym in usedapis:
  534. oe.qa.handle_error('32bit-time', msgformat % sym, d)
  535. oe.qa.handle_error('32bit-time', 'Suppress with INSANE_SKIP = "32bit-time"', d)
  536. check_32bit_symbols[vardepsexclude] = "OVERRIDES"
  537. # Check license variables
  538. do_populate_lic[postfuncs] += "populate_lic_qa_checksum"
  539. python populate_lic_qa_checksum() {
  540. """
  541. Check for changes in the license files.
  542. """
  543. lic_files = d.getVar('LIC_FILES_CHKSUM') or ''
  544. lic = d.getVar('LICENSE')
  545. pn = d.getVar('PN')
  546. if lic == "CLOSED":
  547. return
  548. if not lic_files and d.getVar('SRC_URI'):
  549. oe.qa.handle_error("license-checksum", pn + ": Recipe file fetches files and does not have license file information (LIC_FILES_CHKSUM)", d)
  550. srcdir = d.getVar('S')
  551. corebase_licensefile = d.getVar('COREBASE') + "/LICENSE"
  552. for url in lic_files.split():
  553. try:
  554. (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(url)
  555. except bb.fetch.MalformedUrl:
  556. oe.qa.handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM contains an invalid URL: " + url, d)
  557. continue
  558. srclicfile = os.path.join(srcdir, path)
  559. if not os.path.isfile(srclicfile):
  560. oe.qa.handle_error("license-checksum", pn + ": LIC_FILES_CHKSUM points to an invalid file: " + srclicfile, d)
  561. continue
  562. if (srclicfile == corebase_licensefile):
  563. bb.warn("${COREBASE}/LICENSE is not a valid license file, please use '${COMMON_LICENSE_DIR}/MIT' for a MIT License file in LIC_FILES_CHKSUM. This will become an error in the future")
  564. recipemd5 = parm.get('md5', '')
  565. beginline, endline = 0, 0
  566. if 'beginline' in parm:
  567. beginline = int(parm['beginline'])
  568. if 'endline' in parm:
  569. endline = int(parm['endline'])
  570. if (not beginline) and (not endline):
  571. md5chksum = bb.utils.md5_file(srclicfile)
  572. with open(srclicfile, 'r', errors='replace') as f:
  573. license = f.read().splitlines()
  574. else:
  575. with open(srclicfile, 'rb') as f:
  576. import hashlib
  577. lineno = 0
  578. license = []
  579. try:
  580. m = hashlib.new('MD5', usedforsecurity=False)
  581. except TypeError:
  582. m = hashlib.new('MD5')
  583. for line in f:
  584. lineno += 1
  585. if (lineno >= beginline):
  586. if ((lineno <= endline) or not endline):
  587. m.update(line)
  588. license.append(line.decode('utf-8', errors='replace').rstrip())
  589. else:
  590. break
  591. md5chksum = m.hexdigest()
  592. if recipemd5 == md5chksum:
  593. bb.note (pn + ": md5 checksum matched for ", url)
  594. else:
  595. if recipemd5:
  596. msg = pn + ": The LIC_FILES_CHKSUM does not match for " + url
  597. msg = msg + "\n" + pn + ": The new md5 checksum is " + md5chksum
  598. max_lines = int(d.getVar('QA_MAX_LICENSE_LINES') or 20)
  599. if not license or license[-1] != '':
  600. # Ensure that our license text ends with a line break
  601. # (will be added with join() below).
  602. license.append('')
  603. remove = len(license) - max_lines
  604. if remove > 0:
  605. start = max_lines // 2
  606. end = start + remove - 1
  607. del license[start:end]
  608. license.insert(start, '...')
  609. msg = msg + "\n" + pn + ": Here is the selected license text:" + \
  610. "\n" + \
  611. "{:v^70}".format(" beginline=%d " % beginline if beginline else "") + \
  612. "\n" + "\n".join(license) + \
  613. "{:^^70}".format(" endline=%d " % endline if endline else "")
  614. if beginline:
  615. if endline:
  616. srcfiledesc = "%s (lines %d through to %d)" % (srclicfile, beginline, endline)
  617. else:
  618. srcfiledesc = "%s (beginning on line %d)" % (srclicfile, beginline)
  619. elif endline:
  620. srcfiledesc = "%s (ending on line %d)" % (srclicfile, endline)
  621. else:
  622. srcfiledesc = srclicfile
  623. msg = msg + "\n" + pn + ": Check if the license information has changed in %s to verify that the LICENSE value \"%s\" remains valid" % (srcfiledesc, lic)
  624. else:
  625. msg = pn + ": LIC_FILES_CHKSUM is not specified for " + url
  626. msg = msg + "\n" + pn + ": The md5 checksum is " + md5chksum
  627. oe.qa.handle_error("license-checksum", msg, d)
  628. oe.qa.exit_if_errors(d)
  629. }
  630. def qa_check_staged(path,d):
  631. """
  632. Check staged la and pc files for common problems like references to the work
  633. directory.
  634. As this is run after every stage we should be able to find the one
  635. responsible for the errors easily even if we look at every .pc and .la file.
  636. """
  637. tmpdir = d.getVar('TMPDIR')
  638. workdir = os.path.join(tmpdir, "work")
  639. recipesysroot = d.getVar("RECIPE_SYSROOT")
  640. if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d):
  641. pkgconfigcheck = workdir
  642. else:
  643. pkgconfigcheck = tmpdir
  644. skip = (d.getVar('INSANE_SKIP') or "").split()
  645. skip_la = False
  646. if 'la' in skip:
  647. bb.note("Recipe %s skipping qa checking: la" % d.getVar('PN'))
  648. skip_la = True
  649. skip_pkgconfig = False
  650. if 'pkgconfig' in skip:
  651. bb.note("Recipe %s skipping qa checking: pkgconfig" % d.getVar('PN'))
  652. skip_pkgconfig = True
  653. skip_shebang_size = False
  654. if 'shebang-size' in skip:
  655. bb.note("Recipe %s skipping qa checkking: shebang-size" % d.getVar('PN'))
  656. skip_shebang_size = True
  657. # find all .la and .pc files
  658. # read the content
  659. # and check for stuff that looks wrong
  660. for root, dirs, files in os.walk(path):
  661. for file in files:
  662. path = os.path.join(root,file)
  663. if file.endswith(".la") and not skip_la:
  664. with open(path) as f:
  665. file_content = f.read()
  666. file_content = file_content.replace(recipesysroot, "")
  667. if workdir in file_content:
  668. error_msg = "%s failed sanity test (workdir) in path %s" % (file,root)
  669. oe.qa.handle_error("la", error_msg, d)
  670. elif file.endswith(".pc") and not skip_pkgconfig:
  671. with open(path) as f:
  672. file_content = f.read()
  673. file_content = file_content.replace(recipesysroot, "")
  674. if pkgconfigcheck in file_content:
  675. error_msg = "%s failed sanity test (tmpdir) in path %s" % (file,root)
  676. oe.qa.handle_error("pkgconfig", error_msg, d)
  677. if not skip_shebang_size:
  678. global cpath
  679. cpath = oe.cachedpath.CachedPath()
  680. package_qa_check_shebang_size(path, "", d, None)
  681. cpath = None
  682. # Walk over all files in a directory and call func
  683. def package_qa_walk(checkfuncs, package, d):
  684. global cpath
  685. elves = {}
  686. for path in pkgfiles[package]:
  687. elf = None
  688. if cpath.isfile(path) and not cpath.islink(path):
  689. elf = oe.qa.ELFFile(path)
  690. try:
  691. elf.open()
  692. elf.close()
  693. except oe.qa.NotELFFileError:
  694. elf = None
  695. if elf:
  696. elves[path] = elf
  697. def prepopulate_objdump_p(elf, d):
  698. output = elf.run_objdump("-p", d)
  699. return (elf.name, output)
  700. results = oe.utils.multiprocess_launch(prepopulate_objdump_p, elves.values(), d, extraargs=(d,))
  701. for item in results:
  702. elves[item[0]].set_objdump("-p", item[1])
  703. for path in pkgfiles[package]:
  704. elf = elves.get(path)
  705. if elf:
  706. elf.open()
  707. for func in checkfuncs:
  708. func(path, package, d, elf)
  709. if elf:
  710. elf.close()
  711. def package_qa_check_rdepends(pkg, pkgdest, skip, taskdeps, packages, d):
  712. # Don't do this check for kernel/module recipes, there aren't too many debug/development
  713. # packages and you can get false positives e.g. on kernel-module-lirc-dev
  714. if bb.data.inherits_class("kernel", d) or bb.data.inherits_class("module-base", d):
  715. return
  716. if not "-dbg" in pkg and not "packagegroup-" in pkg and not "-image" in pkg:
  717. localdata = bb.data.createCopy(d)
  718. localdata.setVar('OVERRIDES', localdata.getVar('OVERRIDES') + ':' + pkg)
  719. # Now check the RDEPENDS
  720. rdepends = bb.utils.explode_deps(localdata.getVar('RDEPENDS') or "")
  721. # Now do the sanity check!!!
  722. if "build-deps" not in skip:
  723. def check_rdep(rdep_data, possible_pn):
  724. if rdep_data and "PN" in rdep_data:
  725. possible_pn.add(rdep_data["PN"])
  726. return rdep_data["PN"] in taskdeps
  727. return False
  728. for rdepend in rdepends:
  729. if rdepend.endswith("-dbg") and "debug-deps" not in skip:
  730. error_msg = "%s rdepends on %s" % (pkg,rdepend)
  731. oe.qa.handle_error("debug-deps", error_msg, d)
  732. if (not "-dev" in pkg and not "-staticdev" in pkg) and rdepend.endswith("-dev") and "dev-deps" not in skip:
  733. error_msg = "%s rdepends on %s" % (pkg, rdepend)
  734. oe.qa.handle_error("dev-deps", error_msg, d)
  735. if rdepend not in packages:
  736. possible_pn = set()
  737. rdep_data = oe.packagedata.read_subpkgdata(rdepend, d)
  738. if check_rdep(rdep_data, possible_pn):
  739. continue
  740. if any(check_rdep(rdep_data, possible_pn) for _, rdep_data in oe.packagedata.foreach_runtime_provider_pkgdata(d, rdepend)):
  741. continue
  742. if possible_pn:
  743. error_msg = "%s rdepends on %s, but it isn't a build dependency, missing one of %s in DEPENDS or PACKAGECONFIG?" % (pkg, rdepend, ", ".join(possible_pn))
  744. else:
  745. error_msg = "%s rdepends on %s, but it isn't a build dependency?" % (pkg, rdepend)
  746. oe.qa.handle_error("build-deps", error_msg, d)
  747. if "file-rdeps" not in skip:
  748. ignored_file_rdeps = set(['/bin/sh', '/usr/bin/env', 'rtld(GNU_HASH)'])
  749. if bb.utils.contains('DISTRO_FEATURES', 'usrmerge', True, False, d):
  750. ignored_file_rdeps |= set(['/usr/bin/sh'])
  751. if bb.data.inherits_class('nativesdk', d):
  752. ignored_file_rdeps |= set(['/bin/bash', '/usr/bin/perl', 'perl'])
  753. if bb.utils.contains('DISTRO_FEATURES', 'usrmerge', True, False, d):
  754. ignored_file_rdeps |= set(['/usr/bin/bash'])
  755. # For Saving the FILERDEPENDS
  756. filerdepends = {}
  757. rdep_data = oe.packagedata.read_subpkgdata(pkg, d)
  758. for key in rdep_data:
  759. if key.startswith("FILERDEPENDS:"):
  760. for subkey in bb.utils.explode_deps(rdep_data[key]):
  761. if subkey not in ignored_file_rdeps and \
  762. not subkey.startswith('perl('):
  763. # We already know it starts with FILERDEPENDS_
  764. filerdepends[subkey] = key[13:]
  765. if filerdepends:
  766. done = rdepends[:]
  767. # Add the rprovides of itself
  768. if pkg not in done:
  769. done.insert(0, pkg)
  770. # The python is not a package, but python-core provides it, so
  771. # skip checking /usr/bin/python if python is in the rdeps, in
  772. # case there is a RDEPENDS:pkg = "python" in the recipe.
  773. for py in [ d.getVar('MLPREFIX') + "python", "python" ]:
  774. if py in done:
  775. filerdepends.pop("/usr/bin/python",None)
  776. done.remove(py)
  777. for rdep in done:
  778. # The file dependencies may contain package names, e.g.,
  779. # perl
  780. filerdepends.pop(rdep,None)
  781. for _, rdep_data in oe.packagedata.foreach_runtime_provider_pkgdata(d, rdep, True):
  782. for key in rdep_data:
  783. if key.startswith("FILERPROVIDES:") or key.startswith("RPROVIDES:"):
  784. for subkey in bb.utils.explode_deps(rdep_data[key]):
  785. filerdepends.pop(subkey,None)
  786. # Add the files list to the rprovides
  787. if key.startswith("FILES_INFO:"):
  788. # Use eval() to make it as a dict
  789. for subkey in eval(rdep_data[key]):
  790. filerdepends.pop(subkey,None)
  791. if not filerdepends:
  792. # Break if all the file rdepends are met
  793. break
  794. if filerdepends:
  795. for key in filerdepends:
  796. error_msg = "%s contained in package %s requires %s, but no providers found in RDEPENDS:%s?" % \
  797. (filerdepends[key].replace(":%s" % pkg, "").replace("@underscore@", "_"), pkg, key, pkg)
  798. oe.qa.handle_error("file-rdeps", error_msg, d)
  799. package_qa_check_rdepends[vardepsexclude] = "OVERRIDES"
  800. def package_qa_check_deps(pkg, pkgdest, d):
  801. localdata = bb.data.createCopy(d)
  802. localdata.setVar('OVERRIDES', pkg)
  803. def check_valid_deps(var):
  804. try:
  805. rvar = bb.utils.explode_dep_versions2(localdata.getVar(var) or "")
  806. except ValueError as e:
  807. bb.fatal("%s:%s: %s" % (var, pkg, e))
  808. for dep in rvar:
  809. for v in rvar[dep]:
  810. if v and not v.startswith(('< ', '= ', '> ', '<= ', '>=')):
  811. error_msg = "%s:%s is invalid: %s (%s) only comparisons <, =, >, <=, and >= are allowed" % (var, pkg, dep, v)
  812. oe.qa.handle_error("dep-cmp", error_msg, d)
  813. check_valid_deps('RDEPENDS')
  814. check_valid_deps('RRECOMMENDS')
  815. check_valid_deps('RSUGGESTS')
  816. check_valid_deps('RPROVIDES')
  817. check_valid_deps('RREPLACES')
  818. check_valid_deps('RCONFLICTS')
  819. QAPKGTEST[usrmerge] = "package_qa_check_usrmerge"
  820. def package_qa_check_usrmerge(pkg, d):
  821. global cpath
  822. pkgdest = d.getVar('PKGDEST')
  823. pkg_dir = pkgdest + os.sep + pkg + os.sep
  824. merged_dirs = ['bin', 'sbin', 'lib'] + d.getVar('MULTILIB_VARIANTS').split()
  825. for f in merged_dirs:
  826. if cpath.exists(pkg_dir + f) and not cpath.islink(pkg_dir + f):
  827. msg = "%s package is not obeying usrmerge distro feature. /%s should be relocated to /usr." % (pkg, f)
  828. oe.qa.handle_error("usrmerge", msg, d)
  829. return
  830. QAPKGTEST[perllocalpod] = "package_qa_check_perllocalpod"
  831. def package_qa_check_perllocalpod(pkg, d):
  832. """
  833. Check that the recipe didn't ship a perlocal.pod file, which shouldn't be
  834. installed in a distribution package. cpan.bbclass sets NO_PERLLOCAL=1 to
  835. handle this for most recipes.
  836. """
  837. import glob
  838. pkgd = oe.path.join(d.getVar('PKGDEST'), pkg)
  839. podpath = oe.path.join(pkgd, d.getVar("libdir"), "perl*", "*", "*", "perllocal.pod")
  840. matches = glob.glob(podpath)
  841. if matches:
  842. matches = [package_qa_clean_path(path, d, pkg) for path in matches]
  843. msg = "%s contains perllocal.pod (%s), should not be installed" % (pkg, " ".join(matches))
  844. oe.qa.handle_error("perllocalpod", msg, d)
  845. QAPKGTEST[expanded-d] = "package_qa_check_expanded_d"
  846. def package_qa_check_expanded_d(package, d):
  847. """
  848. Check for the expanded D (${D}) value in pkg_* and FILES
  849. variables, warn the user to use it correctly.
  850. """
  851. expanded_d = d.getVar('D')
  852. for var in 'FILES','pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm':
  853. bbvar = d.getVar(var + ":" + package) or ""
  854. if expanded_d in bbvar:
  855. if var == 'FILES':
  856. oe.qa.handle_error("expanded-d", "FILES in %s recipe should not contain the ${D} variable as it references the local build directory not the target filesystem, best solution is to remove the ${D} reference" % package, d)
  857. else:
  858. oe.qa.handle_error("expanded-d", "%s in %s recipe contains ${D}, it should be replaced by $D instead" % (var, package), d)
  859. QAPKGTEST[unlisted-pkg-lics] = "package_qa_check_unlisted_pkg_lics"
  860. def package_qa_check_unlisted_pkg_lics(package, d):
  861. """
  862. Check that all licenses for a package are among the licenses for the recipe.
  863. """
  864. pkg_lics = d.getVar('LICENSE:' + package)
  865. if not pkg_lics:
  866. return
  867. recipe_lics_set = oe.license.list_licenses(d.getVar('LICENSE'))
  868. package_lics = oe.license.list_licenses(pkg_lics)
  869. unlisted = package_lics - recipe_lics_set
  870. if unlisted:
  871. oe.qa.handle_error("unlisted-pkg-lics",
  872. "LICENSE:%s includes licenses (%s) that are not "
  873. "listed in LICENSE" % (package, ' '.join(unlisted)), d)
  874. obsolete = set(oe.license.obsolete_license_list()) & package_lics - recipe_lics_set
  875. if obsolete:
  876. oe.qa.handle_error("obsolete-license",
  877. "LICENSE:%s includes obsolete licenses %s" % (package, ' '.join(obsolete)), d)
  878. QAPKGTEST[empty-dirs] = "package_qa_check_empty_dirs"
  879. def package_qa_check_empty_dirs(pkg, d):
  880. """
  881. Check for the existence of files in directories that are expected to be
  882. empty.
  883. """
  884. global cpath
  885. pkgd = oe.path.join(d.getVar('PKGDEST'), pkg)
  886. for dir in (d.getVar('QA_EMPTY_DIRS') or "").split():
  887. empty_dir = oe.path.join(pkgd, dir)
  888. if cpath.exists(empty_dir) and os.listdir(empty_dir):
  889. recommendation = (d.getVar('QA_EMPTY_DIRS_RECOMMENDATION:' + dir) or
  890. "but it is expected to be empty")
  891. msg = "%s installs files in %s, %s" % (pkg, dir, recommendation)
  892. oe.qa.handle_error("empty-dirs", msg, d)
  893. def package_qa_check_encoding(keys, encode, d):
  894. def check_encoding(key, enc):
  895. sane = True
  896. value = d.getVar(key)
  897. if value:
  898. try:
  899. s = value.encode(enc)
  900. except UnicodeDecodeError as e:
  901. error_msg = "%s has non %s characters" % (key,enc)
  902. sane = False
  903. oe.qa.handle_error("invalid-chars", error_msg, d)
  904. return sane
  905. for key in keys:
  906. sane = check_encoding(key, encode)
  907. if not sane:
  908. break
  909. HOST_USER_UID := "${@os.getuid()}"
  910. HOST_USER_GID := "${@os.getgid()}"
  911. QAPATHTEST[host-user-contaminated] = "package_qa_check_host_user"
  912. def package_qa_check_host_user(path, name, d, elf):
  913. """Check for paths outside of /home which are owned by the user running bitbake."""
  914. global cpath
  915. if not cpath.lexists(path):
  916. return
  917. dest = d.getVar('PKGDEST')
  918. pn = d.getVar('PN')
  919. home = os.path.join(dest, name, 'home')
  920. if path == home or path.startswith(home + os.sep):
  921. return
  922. try:
  923. stat = os.lstat(path)
  924. except OSError as exc:
  925. import errno
  926. if exc.errno != errno.ENOENT:
  927. raise
  928. else:
  929. check_uid = int(d.getVar('HOST_USER_UID'))
  930. if stat.st_uid == check_uid:
  931. oe.qa.handle_error("host-user-contaminated", "%s: %s is owned by uid %d, which is the same as the user running bitbake. This may be due to host contamination" % (pn, package_qa_clean_path(path, d, name), check_uid), d)
  932. check_gid = int(d.getVar('HOST_USER_GID'))
  933. if stat.st_gid == check_gid:
  934. oe.qa.handle_error("host-user-contaminated", "%s: %s is owned by gid %d, which is the same as the user running bitbake. This may be due to host contamination" % (pn, package_qa_clean_path(path, d, name), check_gid), d)
  935. package_qa_check_host_user[vardepsexclude] = "HOST_USER_UID HOST_USER_GID"
  936. QARECIPETEST[unhandled-features-check] = "package_qa_check_unhandled_features_check"
  937. def package_qa_check_unhandled_features_check(pn, d):
  938. if not bb.data.inherits_class('features_check', d):
  939. var_set = False
  940. for kind in ['DISTRO', 'MACHINE', 'COMBINED']:
  941. for var in ['ANY_OF_' + kind + '_FEATURES', 'REQUIRED_' + kind + '_FEATURES', 'CONFLICT_' + kind + '_FEATURES']:
  942. if d.getVar(var) is not None or d.hasOverrides(var):
  943. var_set = True
  944. if var_set:
  945. oe.qa.handle_error("unhandled-features-check", "%s: recipe doesn't inherit features_check" % pn, d)
  946. QARECIPETEST[missing-update-alternatives] = "package_qa_check_missing_update_alternatives"
  947. def package_qa_check_missing_update_alternatives(pn, d):
  948. # Look at all packages and find out if any of those sets ALTERNATIVE variable
  949. # without inheriting update-alternatives class
  950. for pkg in (d.getVar('PACKAGES') or '').split():
  951. if d.getVar('ALTERNATIVE:%s' % pkg) and not bb.data.inherits_class('update-alternatives', d):
  952. oe.qa.handle_error("missing-update-alternatives", "%s: recipe defines ALTERNATIVE:%s but doesn't inherit update-alternatives. This might fail during do_rootfs later!" % (pn, pkg), d)
  953. def parse_test_matrix(matrix_name, skip, d):
  954. testmatrix = d.getVarFlags(matrix_name) or {}
  955. g = globals()
  956. checks = []
  957. for w in (d.getVar("WARN_QA") or "").split():
  958. if w in skip:
  959. continue
  960. if w in testmatrix and testmatrix[w] in g:
  961. checks.append(g[testmatrix[w]])
  962. for e in (d.getVar("ERROR_QA") or "").split():
  963. if e in skip:
  964. continue
  965. if e in testmatrix and testmatrix[e] in g:
  966. checks.append(g[testmatrix[e]])
  967. return checks
  968. parse_test_matrix[vardepsexclude] = "ERROR_QA WARN_QA"
  969. # The PACKAGE FUNC to scan each package
  970. python do_package_qa () {
  971. import oe.packagedata
  972. # Check for obsolete license references in main LICENSE (packages are checked below for any changes)
  973. main_licenses = oe.license.list_licenses(d.getVar('LICENSE'))
  974. obsolete = set(oe.license.obsolete_license_list()) & main_licenses
  975. if obsolete:
  976. oe.qa.handle_error("obsolete-license", "Recipe LICENSE includes obsolete licenses %s" % ' '.join(obsolete), d)
  977. bb.build.exec_func("read_subpackage_metadata", d)
  978. # Check non UTF-8 characters on recipe's metadata
  979. package_qa_check_encoding(['DESCRIPTION', 'SUMMARY', 'LICENSE', 'SECTION'], 'utf-8', d)
  980. logdir = d.getVar('T')
  981. pn = d.getVar('PN')
  982. # Scan the packages...
  983. packages = set((d.getVar('PACKAGES') or '').split())
  984. # no packages should be scanned
  985. if not packages:
  986. return
  987. global pkgfiles, cpath
  988. pkgfiles = {}
  989. cpath = oe.cachedpath.CachedPath()
  990. pkgdest = d.getVar('PKGDEST')
  991. for pkg in packages:
  992. pkgdir = os.path.join(pkgdest, pkg)
  993. pkgfiles[pkg] = []
  994. for walkroot, dirs, files in os.walk(pkgdir):
  995. # Don't walk into top-level CONTROL or DEBIAN directories as these
  996. # are temporary directories created by do_package.
  997. if walkroot == pkgdir:
  998. for removedir in ("CONTROL", "DEBIAN"):
  999. try:
  1000. dirs.remove(removedir)
  1001. except ValueError:
  1002. pass
  1003. pkgfiles[pkg].extend((os.path.join(walkroot, f) for f in files))
  1004. import re
  1005. # The package name matches the [a-z0-9.+-]+ regular expression
  1006. pkgname_pattern = re.compile(r"^[a-z0-9.+-]+$")
  1007. taskdepdata = d.getVar("BB_TASKDEPDATA", False)
  1008. taskdeps = set()
  1009. for dep in taskdepdata:
  1010. taskdeps.add(taskdepdata[dep][0])
  1011. for package in packages:
  1012. skip = set((d.getVar('INSANE_SKIP') or "").split() +
  1013. (d.getVar('INSANE_SKIP:' + package) or "").split())
  1014. if skip:
  1015. bb.note("Package %s skipping QA tests: %s" % (package, str(skip)))
  1016. bb.note("Checking Package: %s" % package)
  1017. # Check package name
  1018. if not pkgname_pattern.match(package):
  1019. oe.qa.handle_error("pkgname",
  1020. "%s doesn't match the [a-z0-9.+-]+ regex" % package, d)
  1021. checks = parse_test_matrix("QAPATHTEST", skip, d)
  1022. package_qa_walk(checks, package, d)
  1023. checks = parse_test_matrix("QAPKGTEST", skip, d)
  1024. for func in checks:
  1025. func(package, d)
  1026. package_qa_check_rdepends(package, pkgdest, skip, taskdeps, packages, d)
  1027. package_qa_check_deps(package, pkgdest, d)
  1028. checks = parse_test_matrix("QARECIPETEST", skip, d)
  1029. for func in checks:
  1030. func(pn, d)
  1031. package_qa_check_libdir(d)
  1032. cpath = None
  1033. oe.qa.exit_if_errors(d)
  1034. }
  1035. # binutils is used for most checks, so need to set as dependency
  1036. # POPULATESYSROOTDEPS is defined in staging class.
  1037. do_package_qa[depends] += "${POPULATESYSROOTDEPS}"
  1038. do_package_qa[vardeps] = "${@bb.utils.contains('ERROR_QA', 'empty-dirs', 'QA_EMPTY_DIRS', '', d)}"
  1039. do_package_qa[vardepsexclude] = "BB_TASKDEPDATA"
  1040. do_package_qa[rdeptask] = "do_packagedata"
  1041. addtask do_package_qa after do_packagedata do_package before do_build
  1042. do_build[rdeptask] += "do_package_qa"
  1043. # Add the package specific INSANE_SKIPs to the sstate dependencies
  1044. python() {
  1045. pkgs = (d.getVar('PACKAGES') or '').split()
  1046. for pkg in pkgs:
  1047. d.appendVarFlag("do_package_qa", "vardeps", " INSANE_SKIP:{}".format(pkg))
  1048. funcs = d.getVarFlags("QAPATHTEST")
  1049. funcs.update(d.getVarFlags("QAPKGTEST"))
  1050. funcs.update(d.getVarFlags("QARECIPETEST"))
  1051. d.appendVarFlag("do_package_qa", "vardeps", " ".join(funcs.values()))
  1052. }
  1053. SSTATETASKS += "do_package_qa"
  1054. do_package_qa[sstate-inputdirs] = ""
  1055. do_package_qa[sstate-outputdirs] = ""
  1056. python do_package_qa_setscene () {
  1057. sstate_setscene(d)
  1058. }
  1059. addtask do_package_qa_setscene
  1060. python do_qa_sysroot() {
  1061. bb.note("QA checking do_populate_sysroot")
  1062. sysroot_destdir = d.expand('${SYSROOT_DESTDIR}')
  1063. for sysroot_dir in d.expand('${SYSROOT_DIRS}').split():
  1064. qa_check_staged(sysroot_destdir + sysroot_dir, d)
  1065. oe.qa.exit_with_message_if_errors("do_populate_sysroot for this recipe installed files with QA issues", d)
  1066. }
  1067. do_populate_sysroot[postfuncs] += "do_qa_sysroot"
  1068. python do_qa_patch() {
  1069. import subprocess
  1070. ###########################################################################
  1071. # Check patch.log for fuzz warnings
  1072. #
  1073. # Further information on why we check for patch fuzz warnings:
  1074. # http://lists.openembedded.org/pipermail/openembedded-core/2018-March/148675.html
  1075. # https://bugzilla.yoctoproject.org/show_bug.cgi?id=10450
  1076. ###########################################################################
  1077. logdir = d.getVar('T')
  1078. patchlog = os.path.join(logdir,"log.do_patch")
  1079. if os.path.exists(patchlog):
  1080. fuzzheader = '--- Patch fuzz start ---'
  1081. fuzzfooter = '--- Patch fuzz end ---'
  1082. statement = "grep -e '%s' %s > /dev/null" % (fuzzheader, patchlog)
  1083. if subprocess.call(statement, shell=True) == 0:
  1084. msg = "Fuzz detected:\n\n"
  1085. fuzzmsg = ""
  1086. inFuzzInfo = False
  1087. f = open(patchlog, "r")
  1088. for line in f:
  1089. if fuzzheader in line:
  1090. inFuzzInfo = True
  1091. fuzzmsg = ""
  1092. elif fuzzfooter in line:
  1093. fuzzmsg = fuzzmsg.replace('\n\n', '\n')
  1094. msg += fuzzmsg
  1095. msg += "\n"
  1096. inFuzzInfo = False
  1097. elif inFuzzInfo and not 'Now at patch' in line:
  1098. fuzzmsg += line
  1099. f.close()
  1100. msg += "The context lines in the patches can be updated with devtool:\n"
  1101. msg += "\n"
  1102. msg += " devtool modify %s\n" % d.getVar('PN')
  1103. msg += " devtool finish --force-patch-refresh %s <layer_path>\n\n" % d.getVar('PN')
  1104. msg += "Don't forget to review changes done by devtool!\n"
  1105. msg += "\nPatch log indicates that patches do not apply cleanly."
  1106. oe.qa.handle_error("patch-fuzz", msg, d)
  1107. # Check if the patch contains a correctly formatted and spelled Upstream-Status
  1108. import re
  1109. from oe import patch
  1110. for url in patch.src_patches(d):
  1111. (_, _, fullpath, _, _, _) = bb.fetch.decodeurl(url)
  1112. msg = oe.qa.check_upstream_status(fullpath)
  1113. if msg:
  1114. oe.qa.handle_error("patch-status", msg, d)
  1115. ###########################################################################
  1116. # Check for missing ptests
  1117. ###########################################################################
  1118. def match_line_in_files(toplevel, filename_glob, line_regex):
  1119. import pathlib
  1120. try:
  1121. toppath = pathlib.Path(toplevel)
  1122. for entry in toppath.glob(filename_glob):
  1123. try:
  1124. with open(entry, 'r', encoding='utf-8', errors='ignore') as f:
  1125. for line in f.readlines():
  1126. if re.match(line_regex, line):
  1127. return True
  1128. except FileNotFoundError:
  1129. # Broken symlink in source
  1130. pass
  1131. except FileNotFoundError:
  1132. # pathlib.Path.glob() might throw this when file/directory
  1133. # disappear while scanning.
  1134. bb.note("unimplemented-ptest: FileNotFoundError exception while scanning (disappearing file while scanning?). Check was ignored." % d.getVar('PN'))
  1135. pass
  1136. return False
  1137. srcdir = d.getVar('S')
  1138. if not bb.utils.contains('DISTRO_FEATURES', 'ptest', True, False, d):
  1139. pass
  1140. elif not (bb.utils.contains('ERROR_QA', 'unimplemented-ptest', True, False, d) or bb.utils.contains('WARN_QA', 'unimplemented-ptest', True, False, d)):
  1141. pass
  1142. elif bb.data.inherits_class('ptest', d):
  1143. bb.note("Package %s QA: skipping unimplemented-ptest: ptest implementation detected" % d.getVar('PN'))
  1144. # Detect perl Test:: based tests
  1145. elif os.path.exists(os.path.join(srcdir, "t")) and any(filename.endswith('.t') for filename in os.listdir(os.path.join(srcdir, 't'))):
  1146. oe.qa.handle_error("unimplemented-ptest", "%s: perl Test:: based tests detected" % d.getVar('PN'), d)
  1147. # Detect pytest-based tests
  1148. elif match_line_in_files(srcdir, "**/*.py", r'\s*(?:import\s*pytest|from\s*pytest)'):
  1149. oe.qa.handle_error("unimplemented-ptest", "%s: pytest-based tests detected" % d.getVar('PN'), d)
  1150. # Detect meson-based tests
  1151. elif os.path.exists(os.path.join(srcdir, "meson.build")) and match_line_in_files(srcdir, "**/meson.build", r'\s*test\s*\('):
  1152. oe.qa.handle_error("unimplemented-ptest", "%s: meson-based tests detected" % d.getVar('PN'), d)
  1153. # Detect cmake-based tests
  1154. elif os.path.exists(os.path.join(srcdir, "CMakeLists.txt")) and match_line_in_files(srcdir, "**/CMakeLists.txt", r'\s*(?:add_test|enable_testing)\s*\('):
  1155. oe.qa.handle_error("unimplemented-ptest", "%s: cmake-based tests detected" % d.getVar('PN'), d)
  1156. # Detect autotools-based·tests
  1157. elif os.path.exists(os.path.join(srcdir, "Makefile.in")) and (match_line_in_files(srcdir, "**/Makefile.in", r'\s*TESTS\s*\+?=') or match_line_in_files(srcdir,"**/*.at",r'.*AT_INIT')):
  1158. oe.qa.handle_error("unimplemented-ptest", "%s: autotools-based tests detected" % d.getVar('PN'), d)
  1159. # Detect cargo-based tests
  1160. elif os.path.exists(os.path.join(srcdir, "Cargo.toml")) and (
  1161. match_line_in_files(srcdir, "**/*.rs", r'\s*#\s*\[\s*test\s*\]') or
  1162. match_line_in_files(srcdir, "**/*.rs", r'\s*#\s*\[\s*cfg\s*\(\s*test\s*\)\s*\]')
  1163. ):
  1164. oe.qa.handle_error("unimplemented-ptest", "%s: cargo-based tests detected" % d.getVar('PN'), d)
  1165. # Last resort, detect a test directory in sources
  1166. elif os.path.exists(srcdir) and any(filename.lower() in ["test", "tests"] for filename in os.listdir(srcdir)):
  1167. oe.qa.handle_error("unimplemented-ptest", "%s: test subdirectory detected" % d.getVar('PN'), d)
  1168. oe.qa.exit_if_errors(d)
  1169. }
  1170. python do_qa_configure() {
  1171. import subprocess
  1172. ###########################################################################
  1173. # Check config.log for cross compile issues
  1174. ###########################################################################
  1175. configs = []
  1176. workdir = d.getVar('WORKDIR')
  1177. skip = (d.getVar('INSANE_SKIP') or "").split()
  1178. skip_configure_unsafe = False
  1179. if 'configure-unsafe' in skip:
  1180. bb.note("Recipe %s skipping qa checking: configure-unsafe" % d.getVar('PN'))
  1181. skip_configure_unsafe = True
  1182. if bb.data.inherits_class('autotools', d) and not skip_configure_unsafe:
  1183. bb.note("Checking autotools environment for common misconfiguration")
  1184. for root, dirs, files in os.walk(workdir):
  1185. statement = "grep -q -F -e 'is unsafe for cross-compilation' %s" % \
  1186. os.path.join(root,"config.log")
  1187. if "config.log" in files:
  1188. if subprocess.call(statement, shell=True) == 0:
  1189. error_msg = """This autoconf log indicates errors, it looked at host include and/or library paths while determining system capabilities.
  1190. Rerun configure task after fixing this."""
  1191. oe.qa.handle_error("configure-unsafe", error_msg, d)
  1192. if "configure.ac" in files:
  1193. configs.append(os.path.join(root,"configure.ac"))
  1194. if "configure.in" in files:
  1195. configs.append(os.path.join(root, "configure.in"))
  1196. ###########################################################################
  1197. # Check gettext configuration and dependencies are correct
  1198. ###########################################################################
  1199. skip_configure_gettext = False
  1200. if 'configure-gettext' in skip:
  1201. bb.note("Recipe %s skipping qa checking: configure-gettext" % d.getVar('PN'))
  1202. skip_configure_gettext = True
  1203. cnf = d.getVar('EXTRA_OECONF') or ""
  1204. if not ("gettext" in d.getVar('P') or "gcc-runtime" in d.getVar('P') or \
  1205. "--disable-nls" in cnf or skip_configure_gettext):
  1206. ml = d.getVar("MLPREFIX") or ""
  1207. if bb.data.inherits_class('cross-canadian', d):
  1208. gt = "nativesdk-gettext"
  1209. else:
  1210. gt = "gettext-native"
  1211. deps = bb.utils.explode_deps(d.getVar('DEPENDS') or "")
  1212. if gt not in deps:
  1213. for config in configs:
  1214. gnu = "grep \"^[[:space:]]*AM_GNU_GETTEXT\" %s >/dev/null" % config
  1215. if subprocess.call(gnu, shell=True) == 0:
  1216. error_msg = "AM_GNU_GETTEXT used but no inherit gettext"
  1217. oe.qa.handle_error("configure-gettext", error_msg, d)
  1218. ###########################################################################
  1219. # Check unrecognised configure options (with a white list)
  1220. ###########################################################################
  1221. if bb.data.inherits_class("autotools", d):
  1222. bb.note("Checking configure output for unrecognised options")
  1223. try:
  1224. if bb.data.inherits_class("autotools", d):
  1225. flag = "WARNING: unrecognized options:"
  1226. log = os.path.join(d.getVar('B'), 'config.log')
  1227. output = subprocess.check_output(['grep', '-F', flag, log]).decode("utf-8").replace(', ', ' ').replace('"', '')
  1228. options = set()
  1229. for line in output.splitlines():
  1230. options |= set(line.partition(flag)[2].split())
  1231. ignore_opts = set(d.getVar("UNKNOWN_CONFIGURE_OPT_IGNORE").split())
  1232. options -= ignore_opts
  1233. if options:
  1234. pn = d.getVar('PN')
  1235. error_msg = pn + ": configure was passed unrecognised options: " + " ".join(options)
  1236. oe.qa.handle_error("unknown-configure-option", error_msg, d)
  1237. except subprocess.CalledProcessError:
  1238. pass
  1239. oe.qa.exit_if_errors(d)
  1240. }
  1241. python do_qa_unpack() {
  1242. src_uri = d.getVar('SRC_URI')
  1243. s_dir = d.getVar('S')
  1244. s_dir_orig = d.getVar('S', False)
  1245. if s_dir_orig == '${WORKDIR}/git' or s_dir_orig == '${UNPACKDIR}/git':
  1246. bb.fatal('Recipes that set S = "${WORKDIR}/git" or S = "${UNPACKDIR}/git" should remove that assignment, as S set by bitbake.conf in oe-core now works.')
  1247. if '${WORKDIR}' in s_dir_orig:
  1248. bb.fatal('S should be set relative to UNPACKDIR, e.g. replace WORKDIR with UNPACKDIR in "S = {}"'.format(s_dir_orig))
  1249. if src_uri and not os.path.exists(s_dir):
  1250. bb.warn('%s: the directory %s (%s) pointed to by the S variable doesn\'t exist - please set S within the recipe to point to where the source has been unpacked to' % (d.getVar('PN'), d.getVar('S', False), s_dir))
  1251. }
  1252. python do_recipe_qa() {
  1253. import re
  1254. def test_naming(pn, d):
  1255. if pn.endswith("-native") and not bb.data.inherits_class("native", d):
  1256. oe.qa.handle_error("recipe-naming", "Recipe %s appears native but is not, should inherit native" % pn, d)
  1257. if pn.startswith("nativesdk-") and not bb.data.inherits_class("nativesdk", d):
  1258. oe.qa.handle_error("recipe-naming", "Recipe %s appears nativesdk but is not, should inherit nativesdk" % pn, d)
  1259. def test_missing_metadata(pn, d):
  1260. fn = d.getVar("FILE")
  1261. srcfile = d.getVar('SRC_URI').split()
  1262. # Check that SUMMARY is not the same as the default from bitbake.conf
  1263. if d.getVar('SUMMARY') == d.expand("${PN} version ${PV}-${PR}"):
  1264. oe.qa.handle_error("missing-metadata", "Recipe {} in {} does not contain a SUMMARY. Please add an entry.".format(pn, fn), d)
  1265. if not d.getVar('HOMEPAGE'):
  1266. if srcfile and srcfile[0].startswith('file') or not d.getVar('SRC_URI'):
  1267. # We are only interested in recipes SRC_URI fetched from external sources
  1268. pass
  1269. else:
  1270. oe.qa.handle_error("missing-metadata", "Recipe {} in {} does not contain a HOMEPAGE. Please add an entry.".format(pn, fn), d)
  1271. def test_missing_maintainer(pn, d):
  1272. fn = d.getVar("FILE")
  1273. if pn.endswith("-native") or pn.startswith("nativesdk-") or "packagegroup-" in pn or "core-image-ptest-" in pn:
  1274. return
  1275. if not d.getVar('RECIPE_MAINTAINER'):
  1276. oe.qa.handle_error("missing-maintainer", "Recipe {} in {} does not have an assigned maintainer. Please add an entry into meta/conf/distro/include/maintainers.inc.".format(pn, fn), d)
  1277. def test_srcuri(pn, d):
  1278. skip = (d.getVar('INSANE_SKIP') or "").split()
  1279. if 'src-uri-bad' in skip:
  1280. bb.note("Recipe %s skipping qa checking: src-uri-bad" % pn)
  1281. return
  1282. if "${PN}" in d.getVar("SRC_URI", False):
  1283. oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses PN not BPN" % pn, d)
  1284. for url in d.getVar("SRC_URI").split():
  1285. # Search for github and gitlab URLs that pull unstable archives (comment for future greppers)
  1286. if re.search(r"git(hu|la)b\.com/.+/.+/archive/.+", url) or "//codeload.github.com/" in url:
  1287. oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses unstable GitHub/GitLab archives, convert recipe to use git protocol" % pn, d)
  1288. def test_packageconfig(pn, d):
  1289. pkgconfigs = (d.getVar("PACKAGECONFIG") or "").split()
  1290. if pkgconfigs:
  1291. pkgconfigflags = d.getVarFlags("PACKAGECONFIG") or {}
  1292. invalid_pkgconfigs = set(pkgconfigs) - set(pkgconfigflags)
  1293. if invalid_pkgconfigs:
  1294. error_msg = "%s: invalid PACKAGECONFIG(s): %s" % (pn, " ".join(sorted(invalid_pkgconfigs)))
  1295. oe.qa.handle_error("invalid-packageconfig", error_msg, d)
  1296. pn = d.getVar('PN')
  1297. test_naming(pn, d)
  1298. test_missing_metadata(pn, d)
  1299. test_missing_maintainer(pn, d)
  1300. test_srcuri(pn, d)
  1301. test_packageconfig(pn, d)
  1302. oe.qa.exit_if_errors(d)
  1303. }
  1304. addtask do_recipe_qa before do_fetch do_package_qa do_build
  1305. SSTATETASKS += "do_recipe_qa"
  1306. do_recipe_qa[sstate-inputdirs] = ""
  1307. do_recipe_qa[sstate-outputdirs] = ""
  1308. python do_recipe_qa_setscene () {
  1309. sstate_setscene(d)
  1310. }
  1311. addtask do_recipe_qa_setscene
  1312. # Check for patch fuzz
  1313. do_patch[postfuncs] += "do_qa_patch "
  1314. # Check broken config.log files, for packages requiring Gettext which
  1315. # don't have it in DEPENDS.
  1316. #addtask qa_configure after do_configure before do_compile
  1317. do_configure[postfuncs] += "do_qa_configure "
  1318. # Check does S exist.
  1319. do_unpack[postfuncs] += "do_qa_unpack"
  1320. python () {
  1321. import re
  1322. if bb.utils.contains('ERROR_QA', 'desktop', True, False, d) or bb.utils.contains('WARN_QA', 'desktop', True, False, d):
  1323. d.appendVar("PACKAGE_DEPENDS", " desktop-file-utils-native")
  1324. ###########################################################################
  1325. # Check various variables
  1326. ###########################################################################
  1327. # Checking ${FILESEXTRAPATHS}
  1328. extrapaths = (d.getVar("FILESEXTRAPATHS") or "")
  1329. if '__default' not in extrapaths.split(":"):
  1330. msg = "FILESEXTRAPATHS-variable, must always use :prepend (or :append)\n"
  1331. msg += "type of assignment, and don't forget the colon.\n"
  1332. msg += "Please assign it with the format of:\n"
  1333. msg += " FILESEXTRAPATHS:append := \":${THISDIR}/Your_Files_Path\" or\n"
  1334. msg += " FILESEXTRAPATHS:prepend := \"${THISDIR}/Your_Files_Path:\"\n"
  1335. msg += "in your bbappend file\n\n"
  1336. msg += "Your incorrect assignment is:\n"
  1337. msg += "%s\n" % extrapaths
  1338. bb.warn(msg)
  1339. overrides = d.getVar('OVERRIDES').split(':')
  1340. pn = d.getVar('PN')
  1341. if pn in overrides:
  1342. msg = 'Recipe %s has PN of "%s" which is in OVERRIDES, this can result in unexpected behaviour.' % (d.getVar("FILE"), pn)
  1343. oe.qa.handle_error("pn-overrides", msg, d)
  1344. prog = re.compile(r'[A-Z]')
  1345. if prog.search(pn):
  1346. oe.qa.handle_error("uppercase-pn", 'PN: %s is upper case, this can result in unexpected behavior.' % pn, d)
  1347. sourcedir = d.getVar("S")
  1348. builddir = d.getVar("B")
  1349. workdir = d.getVar("WORKDIR")
  1350. unpackdir = d.getVar("UNPACKDIR")
  1351. if sourcedir == workdir:
  1352. bb.fatal("Using S = ${WORKDIR} is no longer supported")
  1353. if builddir == workdir:
  1354. bb.fatal("Using B = ${WORKDIR} is no longer supported")
  1355. if unpackdir == workdir:
  1356. bb.fatal("Using UNPACKDIR = ${WORKDIR} is not supported")
  1357. if sourcedir[-1] == '/':
  1358. bb.warn("Recipe %s sets S variable with trailing slash '%s', remove it" % (d.getVar("PN"), d.getVar("S")))
  1359. if builddir[-1] == '/':
  1360. bb.warn("Recipe %s sets B variable with trailing slash '%s', remove it" % (d.getVar("PN"), d.getVar("B")))
  1361. # Some people mistakenly use DEPENDS:${PN} instead of DEPENDS and wonder
  1362. # why it doesn't work.
  1363. if (d.getVar(d.expand('DEPENDS:${PN}'))):
  1364. oe.qa.handle_error("pkgvarcheck", "recipe uses DEPENDS:${PN}, should use DEPENDS", d)
  1365. # virtual/ is meaningless for these variables
  1366. for k in ['RDEPENDS', 'RPROVIDES']:
  1367. for var in bb.utils.explode_deps(d.getVar(k + ':' + pn) or ""):
  1368. if var.startswith("virtual/"):
  1369. oe.qa.handle_error("virtual-slash", "%s is set to %s but the substring 'virtual/' holds no meaning in this context. It only works for build time dependencies, not runtime ones. It is suggested to use 'VIRTUAL-RUNTIME_' variables instead." % (k, var), d)
  1370. issues = []
  1371. if (d.getVar('PACKAGES') or "").split():
  1372. for dep in (d.getVar('QADEPENDS') or "").split():
  1373. d.appendVarFlag('do_package_qa', 'depends', " %s:do_populate_sysroot" % dep)
  1374. for var in 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RCONFLICTS', 'RPROVIDES', 'RREPLACES', 'FILES', 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', 'ALLOW_EMPTY':
  1375. if d.getVar(var, False):
  1376. issues.append(var)
  1377. if bb.utils.contains('ERROR_QA', 'host-user-contaminated', True, False, d) or bb.utils.contains('WARN_QA', 'host-user-contaminated', True, False, d):
  1378. d.setVarFlag('do_package_qa', 'fakeroot', '1')
  1379. d.appendVarFlag('do_package_qa', 'depends', ' virtual/fakeroot-native:do_populate_sysroot')
  1380. else:
  1381. d.setVarFlag('do_package_qa', 'rdeptask', '')
  1382. for i in issues:
  1383. oe.qa.handle_error("pkgvarcheck", "%s: Variable %s is set as not being package specific, please fix this." % (d.getVar("FILE"), i), d)
  1384. if 'native-last' not in (d.getVar('INSANE_SKIP') or "").split():
  1385. for native_class in ['native', 'nativesdk']:
  1386. if bb.data.inherits_class(native_class, d):
  1387. inherited_classes = d.getVar('__inherit_cache', False) or []
  1388. needle = "/" + native_class
  1389. bbclassextend = (d.getVar('BBCLASSEXTEND') or '').split()
  1390. # BBCLASSEXTEND items are always added in the end
  1391. skip_classes = bbclassextend
  1392. if bb.data.inherits_class('native', d) or 'native' in bbclassextend:
  1393. # native also inherits nopackages and relocatable bbclasses
  1394. skip_classes.extend(['nopackages', 'relocatable'])
  1395. broken_order = []
  1396. for class_item in reversed(inherited_classes):
  1397. if needle not in class_item:
  1398. for extend_item in skip_classes:
  1399. if '/%s.bbclass' % extend_item in class_item:
  1400. break
  1401. else:
  1402. pn = d.getVar('PN')
  1403. broken_order.append(os.path.basename(class_item))
  1404. else:
  1405. break
  1406. if broken_order:
  1407. oe.qa.handle_error("native-last", "%s: native/nativesdk class is not inherited last, this can result in unexpected behaviour. "
  1408. "Classes inherited after native/nativesdk: %s" % (pn, " ".join(broken_order)), d)
  1409. oe.qa.exit_if_errors(d)
  1410. }