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