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 \ 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 var-undefined 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 "-dbg" in rdepend 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 if src_uri and not os.path.exists(s_dir): 1435 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)) 1436} 1437 1438python do_recipe_qa() { 1439 import re 1440 1441 def test_missing_metadata(pn, d): 1442 fn = d.getVar("FILE") 1443 srcfile = d.getVar('SRC_URI').split() 1444 # Check that SUMMARY is not the same as the default from bitbake.conf 1445 if d.getVar('SUMMARY') == d.expand("${PN} version ${PV}-${PR}"): 1446 oe.qa.handle_error("missing-metadata", "Recipe {} in {} does not contain a SUMMARY. Please add an entry.".format(pn, fn), d) 1447 if not d.getVar('HOMEPAGE'): 1448 if srcfile and srcfile[0].startswith('file') or not d.getVar('SRC_URI'): 1449 # We are only interested in recipes SRC_URI fetched from external sources 1450 pass 1451 else: 1452 oe.qa.handle_error("missing-metadata", "Recipe {} in {} does not contain a HOMEPAGE. Please add an entry.".format(pn, fn), d) 1453 1454 def test_missing_maintainer(pn, d): 1455 fn = d.getVar("FILE") 1456 if pn.endswith("-native") or pn.startswith("nativesdk-") or "packagegroup-" in pn or "core-image-ptest-" in pn: 1457 return 1458 if not d.getVar('RECIPE_MAINTAINER'): 1459 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) 1460 1461 def test_srcuri(pn, d): 1462 skip = (d.getVar('INSANE_SKIP') or "").split() 1463 if 'src-uri-bad' in skip: 1464 bb.note("Recipe %s skipping qa checking: src-uri-bad" % pn) 1465 return 1466 1467 if "${PN}" in d.getVar("SRC_URI", False): 1468 oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses PN not BPN" % pn, d) 1469 1470 for url in d.getVar("SRC_URI").split(): 1471 # Search for github and gitlab URLs that pull unstable archives (comment for future greppers) 1472 if re.search(r"git(hu|la)b\.com/.+/.+/archive/.+", url) or "//codeload.github.com/" in url: 1473 oe.qa.handle_error("src-uri-bad", "%s: SRC_URI uses unstable GitHub/GitLab archives, convert recipe to use git protocol" % pn, d) 1474 1475 def test_packageconfig(pn, d): 1476 pkgconfigs = (d.getVar("PACKAGECONFIG") or "").split() 1477 if pkgconfigs: 1478 pkgconfigflags = d.getVarFlags("PACKAGECONFIG") or {} 1479 invalid_pkgconfigs = set(pkgconfigs) - set(pkgconfigflags) 1480 if invalid_pkgconfigs: 1481 error_msg = "%s: invalid PACKAGECONFIG(s): %s" % (pn, " ".join(sorted(invalid_pkgconfigs))) 1482 oe.qa.handle_error("invalid-packageconfig", error_msg, d) 1483 1484 pn = d.getVar('PN') 1485 test_missing_metadata(pn, d) 1486 test_missing_maintainer(pn, d) 1487 test_srcuri(pn, d) 1488 test_packageconfig(pn, d) 1489 oe.qa.exit_if_errors(d) 1490} 1491 1492addtask do_recipe_qa before do_fetch do_package_qa do_build 1493 1494SSTATETASKS += "do_recipe_qa" 1495do_recipe_qa[sstate-inputdirs] = "" 1496do_recipe_qa[sstate-outputdirs] = "" 1497python do_recipe_qa_setscene () { 1498 sstate_setscene(d) 1499} 1500addtask do_recipe_qa_setscene 1501 1502# Check for patch fuzz 1503do_patch[postfuncs] += "do_qa_patch " 1504 1505# Check broken config.log files, for packages requiring Gettext which 1506# don't have it in DEPENDS. 1507#addtask qa_configure after do_configure before do_compile 1508do_configure[postfuncs] += "do_qa_configure " 1509 1510# Check does S exist. 1511do_unpack[postfuncs] += "do_qa_unpack" 1512 1513python () { 1514 import re 1515 1516 if bb.utils.contains('ERROR_QA', 'desktop', True, False, d) or bb.utils.contains('WARN_QA', 'desktop', True, False, d): 1517 d.appendVar("PACKAGE_DEPENDS", " desktop-file-utils-native") 1518 1519 ########################################################################### 1520 # Check various variables 1521 ########################################################################### 1522 1523 # Checking ${FILESEXTRAPATHS} 1524 extrapaths = (d.getVar("FILESEXTRAPATHS") or "") 1525 if '__default' not in extrapaths.split(":"): 1526 msg = "FILESEXTRAPATHS-variable, must always use :prepend (or :append)\n" 1527 msg += "type of assignment, and don't forget the colon.\n" 1528 msg += "Please assign it with the format of:\n" 1529 msg += " FILESEXTRAPATHS:append := \":${THISDIR}/Your_Files_Path\" or\n" 1530 msg += " FILESEXTRAPATHS:prepend := \"${THISDIR}/Your_Files_Path:\"\n" 1531 msg += "in your bbappend file\n\n" 1532 msg += "Your incorrect assignment is:\n" 1533 msg += "%s\n" % extrapaths 1534 bb.warn(msg) 1535 1536 overrides = d.getVar('OVERRIDES').split(':') 1537 pn = d.getVar('PN') 1538 if pn in overrides: 1539 msg = 'Recipe %s has PN of "%s" which is in OVERRIDES, this can result in unexpected behaviour.' % (d.getVar("FILE"), pn) 1540 oe.qa.handle_error("pn-overrides", msg, d) 1541 prog = re.compile(r'[A-Z]') 1542 if prog.search(pn): 1543 oe.qa.handle_error("uppercase-pn", 'PN: %s is upper case, this can result in unexpected behavior.' % pn, d) 1544 1545 sourcedir = d.getVar("S") 1546 builddir = d.getVar("B") 1547 workdir = d.getVar("WORKDIR") 1548 unpackdir = d.getVar("UNPACKDIR") 1549 if sourcedir == workdir: 1550 bb.fatal("Using S = ${WORKDIR} is no longer supported") 1551 if builddir == workdir: 1552 bb.fatal("Using B = ${WORKDIR} is no longer supported") 1553 if unpackdir == workdir: 1554 bb.fatal("Using UNPACKDIR = ${WORKDIR} is not supported") 1555 if sourcedir[-1] == '/': 1556 bb.warn("Recipe %s sets S variable with trailing slash '%s', remove it" % (d.getVar("PN"), d.getVar("S"))) 1557 if builddir[-1] == '/': 1558 bb.warn("Recipe %s sets B variable with trailing slash '%s', remove it" % (d.getVar("PN"), d.getVar("B"))) 1559 1560 # Some people mistakenly use DEPENDS:${PN} instead of DEPENDS and wonder 1561 # why it doesn't work. 1562 if (d.getVar(d.expand('DEPENDS:${PN}'))): 1563 oe.qa.handle_error("pkgvarcheck", "recipe uses DEPENDS:${PN}, should use DEPENDS", d) 1564 1565 # virtual/ is meaningless for these variables 1566 for k in ['RDEPENDS', 'RPROVIDES']: 1567 for var in bb.utils.explode_deps(d.getVar(k + ':' + pn) or ""): 1568 if var.startswith("virtual/"): 1569 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) 1570 1571 issues = [] 1572 if (d.getVar('PACKAGES') or "").split(): 1573 for dep in (d.getVar('QADEPENDS') or "").split(): 1574 d.appendVarFlag('do_package_qa', 'depends', " %s:do_populate_sysroot" % dep) 1575 for var in 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RCONFLICTS', 'RPROVIDES', 'RREPLACES', 'FILES', 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', 'ALLOW_EMPTY': 1576 if d.getVar(var, False): 1577 issues.append(var) 1578 1579 if bb.utils.contains('ERROR_QA', 'host-user-contaminated', True, False, d) or bb.utils.contains('WARN_QA', 'host-user-contaminated', True, False, d): 1580 d.setVarFlag('do_package_qa', 'fakeroot', '1') 1581 d.appendVarFlag('do_package_qa', 'depends', ' virtual/fakeroot-native:do_populate_sysroot') 1582 else: 1583 d.setVarFlag('do_package_qa', 'rdeptask', '') 1584 for i in issues: 1585 oe.qa.handle_error("pkgvarcheck", "%s: Variable %s is set as not being package specific, please fix this." % (d.getVar("FILE"), i), d) 1586 1587 if 'native-last' not in (d.getVar('INSANE_SKIP') or "").split(): 1588 for native_class in ['native', 'nativesdk']: 1589 if bb.data.inherits_class(native_class, d): 1590 1591 inherited_classes = d.getVar('__inherit_cache', False) or [] 1592 needle = "/" + native_class 1593 1594 bbclassextend = (d.getVar('BBCLASSEXTEND') or '').split() 1595 # BBCLASSEXTEND items are always added in the end 1596 skip_classes = bbclassextend 1597 if bb.data.inherits_class('native', d) or 'native' in bbclassextend: 1598 # native also inherits nopackages and relocatable bbclasses 1599 skip_classes.extend(['nopackages', 'relocatable']) 1600 1601 broken_order = [] 1602 for class_item in reversed(inherited_classes): 1603 if needle not in class_item: 1604 for extend_item in skip_classes: 1605 if '/%s.bbclass' % extend_item in class_item: 1606 break 1607 else: 1608 pn = d.getVar('PN') 1609 broken_order.append(os.path.basename(class_item)) 1610 else: 1611 break 1612 if broken_order: 1613 oe.qa.handle_error("native-last", "%s: native/nativesdk class is not inherited last, this can result in unexpected behaviour. " 1614 "Classes inherited after native/nativesdk: %s" % (pn, " ".join(broken_order)), d) 1615 1616 oe.qa.exit_if_errors(d) 1617} 1618