xref: /openbmc/openbmc/poky/meta/classes-global/insane.bbclass (revision c9537f57ab488bf5d90132917b0184e2527970a5)
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