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