def do_package_qa(d):
    import oe.packagedata

    # Check for obsolete license references in main LICENSE (packages are checked below for any changes)
    main_licenses = oe.license.list_licenses(d.getVar('LICENSE'))
    obsolete = set(oe.license.obsolete_license_list()) & main_licenses
    if obsolete:
        oe.qa.handle_error("obsolete-license", "Recipe LICENSE includes obsolete licenses %s" % ' '.join(obsolete), d)

    bb.build.exec_func("read_subpackage_metadata", d)

    # Check non UTF-8 characters on recipe's metadata
    package_qa_check_encoding(['DESCRIPTION', 'SUMMARY', 'LICENSE', 'SECTION'], 'utf-8', d)

    logdir = d.getVar('T')
    pn = d.getVar('PN')

    # Scan the packages...
    packages = set((d.getVar('PACKAGES') or '').split())
    # no packages should be scanned
    if not packages:
        return

    global pkgfiles, cpath
    pkgfiles = {}
    cpath = oe.cachedpath.CachedPath()
    pkgdest = d.getVar('PKGDEST')
    for pkg in packages:
        pkgdir = os.path.join(pkgdest, pkg)
        pkgfiles[pkg] = []
        for walkroot, dirs, files in os.walk(pkgdir):
            # Don't walk into top-level CONTROL or DEBIAN directories as these
            # are temporary directories created by do_package.
            if walkroot == pkgdir:
                for removedir in ("CONTROL", "DEBIAN"):
                    try:
                        dirs.remove(removedir)
                    except ValueError:
                        pass
            pkgfiles[pkg].extend((os.path.join(walkroot, f) for f in files))

    import re
    # The package name matches the [a-z0-9.+-]+ regular expression
    pkgname_pattern = re.compile(r"^[a-z0-9.+-]+$")

    taskdepdata = d.getVar("BB_TASKDEPDATA", False)
    taskdeps = set()
    for dep in taskdepdata:
        taskdeps.add(taskdepdata[dep][0])

    for package in packages:
        skip = set((d.getVar('INSANE_SKIP') or "").split() +
                   (d.getVar('INSANE_SKIP:' + package) or "").split())
        if skip:
            bb.note("Package %s skipping QA tests: %s" % (package, str(skip)))

        bb.note("Checking Package: %s" % package)
        # Check package name
        if not pkgname_pattern.match(package):
            oe.qa.handle_error("pkgname",
                    "%s doesn't match the [a-z0-9.+-]+ regex" % package, d)

        checks = parse_test_matrix("QAPATHTEST", skip, d)
        package_qa_walk(checks, package, d)

        checks = parse_test_matrix("QAPKGTEST", skip, d)
        for func in checks:
            func(package, d)

        package_qa_check_rdepends(package, pkgdest, skip, taskdeps, packages, d)
        package_qa_check_deps(package, pkgdest, d)

    checks = parse_test_matrix("QARECIPETEST", skip, d)
    for func in checks:
        func(pn, d)

    package_qa_check_libdir(d)

    cpath = None
    oe.qa.exit_if_errors(d)

do_package_qa(d)

def package_qa_check_useless_rpaths(file, name, d, elf):
    """
    Check for RPATHs that are useless but not dangerous
    """
    def rpath_eq(a, b):
        return os.path.normpath(a) == os.path.normpath(b)

    if not elf:
        return

    libdir = d.getVar("libdir")
    base_libdir = d.getVar("base_libdir")

    phdrs = elf.run_objdump("-p", d)

    import re
    rpath_re = re.compile(r"\s+(?:RPATH|RUNPATH)\s+(.*)")
    for line in phdrs.split("\n"):
        m = rpath_re.match(line)
        if m:
            rpath = m.group(1)
            if rpath_eq(rpath, libdir) or rpath_eq(rpath, base_libdir):
                # The dynamic linker searches both these places anyway.  There is no point in
                # looking there again.
                oe.qa.handle_error("useless-rpaths", "%s: %s contains probably-redundant RPATH %s" % (name, package_qa_clean_path(file, d, name), rpath), d)

def package_qa_check_libdir(d):
    """
    Check for wrong library installation paths. For instance, catch
    recipes installing /lib/bar.so when ${base_libdir}="lib32" or
    installing in /usr/lib64 when ${libdir}="/usr/lib"
    """
    import re

    pkgdest = d.getVar('PKGDEST')
    base_libdir = d.getVar("base_libdir") + os.sep
    libdir = d.getVar("libdir") + os.sep
    libexecdir = d.getVar("libexecdir") + os.sep
    exec_prefix = d.getVar("exec_prefix") + os.sep

    messages = []

    # The re's are purposely fuzzy, as some there are some .so.x.y.z files
    # that don't follow the standard naming convention. It checks later
    # that they are actual ELF files
    lib_re = re.compile(r"^/lib.+\.so(\..+)?$")
    exec_re = re.compile(r"^%s.*/lib.+\.so(\..+)?$" % exec_prefix)

    for root, dirs, files in os.walk(pkgdest):
        if root == pkgdest:
            # Skip subdirectories for any packages with libdir in INSANE_SKIP
            skippackages = []
            for package in dirs:
                if 'libdir' in (d.getVar('INSANE_SKIP:' + package) or "").split():
                    bb.note("Package %s skipping libdir QA test" % (package))
                    skippackages.append(package)
                elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-file-directory' and package.endswith("-dbg"):
                    bb.note("Package %s skipping libdir QA test for PACKAGE_DEBUG_SPLIT_STYLE equals debug-file-directory" % (package))
                    skippackages.append(package)
            for package in skippackages:
                dirs.remove(package)
        for file in files:
            full_path = os.path.join(root, file)
            rel_path = os.path.relpath(full_path, pkgdest)
            if os.sep in rel_path:
                package, rel_path = rel_path.split(os.sep, 1)
                rel_path = os.sep + rel_path
                if lib_re.match(rel_path):
                    if base_libdir not in rel_path:
                        # make sure it's an actual ELF file
                        elf = oe.qa.ELFFile(full_path)
                        try:
                            elf.open()
                            messages.append("%s: found library in wrong location: %s" % (package, rel_path))
                        except (oe.qa.NotELFFileError, FileNotFoundError):
                            pass
                if exec_re.match(rel_path):
                    if libdir not in rel_path and libexecdir not in rel_path:
                        # make sure it's an actual ELF file
                        elf = oe.qa.ELFFile(full_path)
                        try:
                            elf.open()
                            messages.append("%s: found library in wrong location: %s" % (package, rel_path))
                        except (oe.qa.NotELFFileError, FileNotFoundError):
                            pass

    if messages:
        oe.qa.handle_error("libdir", "\n".join(messages), d)

def package_qa_check_dev(path, name, d, elf):
    """
    Check for ".so" library symlinks in non-dev packages
    """
    global cpath
    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):
        oe.qa.handle_error("dev-so", "non -dev/-dbg/nativesdk- package %s contains symlink .so '%s'" % \
                 (name, package_qa_clean_path(path, d, name)), d)

def package_qa_check_infodir(path, name, d, elf):
    """
    Check that /usr/share/info/dir isn't shipped in a particular package
    """
    infodir = d.expand("${infodir}/dir")

    if infodir in path:
        oe.qa.handle_error("infodir", "The %s file is not meant to be shipped in a particular package." % infodir, d)

def package_qa_hash_style(path, name, d, elf):
    """
    Check if the binary has the right hash style...
    """

    if not elf:
        return

    gnu_hash = "--hash-style=gnu" in d.getVar('LDFLAGS')
    if not gnu_hash:
        gnu_hash = "--hash-style=both" in d.getVar('LDFLAGS')
    if not gnu_hash:
        return

    sane = False
    has_syms = False

    phdrs = elf.run_objdump("-p", d)

    # If this binary has symbols, we expect it to have GNU_HASH too.
    for line in phdrs.split("\n"):
        if "SYMTAB" in line:
            has_syms = True
        if "GNU_HASH" in line or "MIPS_XHASH" in line:
            sane = True
        if ("[mips32]" in line or "[mips64]" in line) and d.getVar('TCLIBC') == "musl":
            sane = True
    if has_syms and not sane:
        path = package_qa_clean_path(path, d, name)
        oe.qa.handle_error("ldflags", "File %s in package %s doesn't have GNU_HASH (didn't pass LDFLAGS?)" % (path, name), d)

def package_qa_check_symlink_to_sysroot(path, name, d, elf):
    """
    Check that the package doesn't contain any absolute symlinks to the sysroot.
    """
    global cpath
    if cpath.islink(path):
        target = os.readlink(path)
        if os.path.isabs(target):
            tmpdir = d.getVar('TMPDIR')
            if target.startswith(tmpdir):
                path = package_qa_clean_path(path, d, name)
                oe.qa.handle_error("symlink-to-sysroot", "Symlink %s in %s points to TMPDIR" % (path, name), d)

def package_qa_check_rpath(file, name, d, elf):
    """
    Check for dangerous RPATHs
    """
    if not elf:
        return

    bad_dirs = [d.getVar('BASE_WORKDIR'), d.getVar('STAGING_DIR_TARGET')]

    phdrs = elf.run_objdump("-p", d)

    import re
    rpath_re = re.compile(r"\s+(?:RPATH|RUNPATH)\s+(.*)")
    for line in phdrs.split("\n"):
        m = rpath_re.match(line)
        if m:
            rpath = m.group(1)
            for dir in bad_dirs:
                if dir in rpath:
                    oe.qa.handle_error("rpaths", "%s: %s contains bad RPATH %s" % (name, package_qa_clean_path(file, d, name), rpath), d)

def check_32bit_symbols(path, packagename, d, elf):
    """
    Check that ELF files do not use any 32 bit time APIs from glibc.
    """
    thirtytwo_bit_time_archs = {'arm','armeb','mipsarcho32','powerpc','x86'}
    overrides = set(d.getVar('OVERRIDES').split(':'))
    if not (thirtytwo_bit_time_archs & overrides):
        return

    import re
    # This list is manually constructed by searching the image folder of the
    # glibc recipe for __USE_TIME_BITS64.  There is no good way to do this
    # automatically.
    api32 = {
        # /usr/include/time.h
        "clock_getres", "clock_gettime", "clock_nanosleep", "clock_settime",
        "ctime", "ctime_r", "difftime", "gmtime", "gmtime_r", "localtime",
        "localtime_r", "mktime", "nanosleep", "time", "timegm", "timelocal",
        "timer_gettime", "timer_settime", "timespec_get", "timespec_getres",
        # /usr/include/bits/time.h
        "clock_adjtime",
        # /usr/include/signal.h
        "sigtimedwait",
        # /usr/include/sys/time.h
        "adjtime",
        "futimes", "futimesat", "getitimer", "gettimeofday", "lutimes",
        "setitimer", "settimeofday", "utimes",
        # /usr/include/sys/timex.h
        "adjtimex", "ntp_adjtime", "ntp_gettime", "ntp_gettimex",
        # /usr/include/sys/wait.h
        "wait3", "wait4",
        # /usr/include/sys/stat.h
        "fstat", "fstat64", "fstatat", "fstatat64", "futimens", "lstat",
        "lstat64", "stat", "stat64", "utimensat",
        # /usr/include/sys/poll.h
        "ppoll",
        # /usr/include/sys/resource.h
        "getrusage",
        # /usr/include/sys/ioctl.h
        "ioctl",
        # /usr/include/sys/select.h
        "select", "pselect",
        # /usr/include/sys/prctl.h
        "prctl",
        # /usr/include/sys/epoll.h
        "epoll_pwait2",
        # /usr/include/sys/timerfd.h
        "timerfd_gettime", "timerfd_settime",
        # /usr/include/sys/socket.h
        "getsockopt", "recvmmsg", "recvmsg", "sendmmsg", "sendmsg",
        "setsockopt",
        # /usr/include/sys/msg.h
        "msgctl",
        # /usr/include/sys/sem.h
        "semctl", "semtimedop",
        # /usr/include/sys/shm.h
        "shmctl",
        # /usr/include/pthread.h
        "pthread_clockjoin_np", "pthread_cond_clockwait",
        "pthread_cond_timedwait", "pthread_mutex_clocklock",
        "pthread_mutex_timedlock", "pthread_rwlock_clockrdlock",
        "pthread_rwlock_clockwrlock", "pthread_rwlock_timedrdlock",
        "pthread_rwlock_timedwrlock", "pthread_timedjoin_np",
        # /usr/include/semaphore.h
        "sem_clockwait", "sem_timedwait",
        # /usr/include/threads.h
        "cnd_timedwait", "mtx_timedlock", "thrd_sleep",
        # /usr/include/aio.h
        "aio_cancel", "aio_error", "aio_read", "aio_return", "aio_suspend",
        "aio_write", "lio_listio",
        # /usr/include/mqueue.h
        "mq_timedreceive", "mq_timedsend",
        # /usr/include/glob.h
        "glob", "glob64", "globfree", "globfree64",
        # /usr/include/sched.h
        "sched_rr_get_interval",
        # /usr/include/fcntl.h
        "fcntl", "fcntl64",
        # /usr/include/utime.h
        "utime",
        # /usr/include/ftw.h
        "ftw", "ftw64", "nftw", "nftw64",
        # /usr/include/fts.h
        "fts64_children", "fts64_close", "fts64_open", "fts64_read",
        "fts64_set", "fts_children", "fts_close", "fts_open", "fts_read",
        "fts_set",
        # /usr/include/netdb.h
        "gai_suspend",
    }

    ptrn = re.compile(
        r'''
        (?P<value>[\da-fA-F]+) \s+
        (?P<flags>[lgu! ][w ][C ][W ][Ii ][dD ]F) \s+
        (?P<section>\*UND\*) \s+
        (?P<alignment>(?P<size>[\da-fA-F]+)) \s+
        (?P<symbol>
        ''' +
        r'(?P<notag>' + r'|'.join(sorted(api32)) + r')' +
        r'''
        (@+(?P<tag>GLIBC_\d+\.\d+\S*)))
        ''', re.VERBOSE
    )

    # elf is a oe.qa.ELFFile object
    if elf:
        phdrs = elf.run_objdump("-tw", d)
        syms = re.finditer(ptrn, phdrs)
        usedapis = {sym.group('notag') for sym in syms}
        if usedapis:
            elfpath = package_qa_clean_path(path, d, packagename)
            # Remove any .debug dir, heuristic that probably works
            # At this point, any symbol information is stripped into the debug
            # package, so that is the only place we will find them.
            elfpath = elfpath.replace('.debug/', '')
            allowed = "32bit-time" in (d.getVar('INSANE_SKIP') or '').split()
            if not allowed:
                msgformat = elfpath + " uses 32-bit api '%s'"
                for sym in usedapis:
                    oe.qa.handle_error('32bit-time', msgformat % sym, d)
                oe.qa.handle_error('32bit-time', 'Suppress with INSANE_SKIP = "32bit-time"', d)

def package_qa_check_xorg_driver_abi(path, name, d, elf):
    """
    Check that all packages containing Xorg drivers have ABI dependencies
    """

    # Skip dev, dbg or nativesdk packages
    if name.endswith("-dev") or name.endswith("-dbg") or name.startswith("nativesdk-"):
        return

    driverdir = d.expand("${libdir}/xorg/modules/drivers/")
    if driverdir in path and path.endswith(".so"):
        mlprefix = d.getVar('MLPREFIX') or ''
        for rdep in bb.utils.explode_deps(d.getVar('RDEPENDS:' + name) or ""):
            if rdep.startswith("%sxorg-abi-" % mlprefix):
                return
        oe.qa.handle_error("xorg-driver-abi", "Package %s contains Xorg driver (%s) but no xorg-abi- dependencies" % (name, os.path.basename(path)), d)

def package_qa_check_desktop(path, name, d, elf):
    """
    Run all desktop files through desktop-file-validate.
    """
    if path.endswith(".desktop"):
        desktop_file_validate = os.path.join(d.getVar('STAGING_BINDIR_NATIVE'),'desktop-file-validate')
        output = os.popen("%s %s" % (desktop_file_validate, path))
        # This only produces output on errors
        for l in output:
            oe.qa.handle_error("desktop", "Desktop file issue: " + l.strip(), d)

def package_qa_textrel(path, name, d, elf):
    """
    Check if the binary contains relocations in .text
    """

    if not elf:
        return

    phdrs = elf.run_objdump("-p", d)

    import re
    textrel_re = re.compile(r"\s+TEXTREL\s+")
    for line in phdrs.split("\n"):
        if textrel_re.match(line):
            path = package_qa_clean_path(path, d, name)
            oe.qa.handle_error("textrel", "%s: ELF binary %s has relocations in .text" % (name, path), d)
            return

def package_qa_check_rdepends(pkg, pkgdest, skip, taskdeps, packages, d):
    # Don't do this check for kernel/module recipes, there aren't too many debug/development
    # packages and you can get false positives e.g. on kernel-module-lirc-dev
    if bb.data.inherits_class("kernel", d) or bb.data.inherits_class("module-base", d):
        return

    if not "-dbg" in pkg and not "packagegroup-" in pkg and not "-image" in pkg:
        localdata = bb.data.createCopy(d)
        localdata.setVar('OVERRIDES', localdata.getVar('OVERRIDES') + ':' + pkg)

        # Now check the RDEPENDS
        rdepends = bb.utils.explode_deps(localdata.getVar('RDEPENDS') or "")

        # Now do the sanity check!!!
        if "build-deps" not in skip:
            def check_rdep(rdep_data, possible_pn):
                if rdep_data and "PN" in rdep_data:
                    possible_pn.add(rdep_data["PN"])
                    return rdep_data["PN"] in taskdeps
                return False

            for rdepend in rdepends:
                if "-dbg" in rdepend and "debug-deps" not in skip:
                    error_msg = "%s rdepends on %s" % (pkg,rdepend)
                    oe.qa.handle_error("debug-deps", error_msg, d)
                if (not "-dev" in pkg and not "-staticdev" in pkg) and rdepend.endswith("-dev") and "dev-deps" not in skip:
                    error_msg = "%s rdepends on %s" % (pkg, rdepend)
                    oe.qa.handle_error("dev-deps", error_msg, d)
                if rdepend not in packages:
                    possible_pn = set()
                    rdep_data = oe.packagedata.read_subpkgdata(rdepend, d)
                    if check_rdep(rdep_data, possible_pn):
                        continue

                    if any(check_rdep(rdep_data, possible_pn) for _, rdep_data in  oe.packagedata.foreach_runtime_provider_pkgdata(d, rdepend)):
                        continue

                    if possible_pn:
                        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))
                    else:
                        error_msg = "%s rdepends on %s, but it isn't a build dependency?" % (pkg, rdepend)
                    oe.qa.handle_error("build-deps", error_msg, d)

        if "file-rdeps" not in skip:
            ignored_file_rdeps = set(['/bin/sh', '/usr/bin/env', 'rtld(GNU_HASH)'])
            if bb.utils.contains('DISTRO_FEATURES', 'usrmerge', True, False, d):
                ignored_file_rdeps |= set(['/usr/bin/sh'])
            if bb.data.inherits_class('nativesdk', d):
                ignored_file_rdeps |= set(['/bin/bash', '/usr/bin/perl', 'perl'])
                if bb.utils.contains('DISTRO_FEATURES', 'usrmerge', True, False, d):
                    ignored_file_rdeps |= set(['/usr/bin/bash'])
            # For Saving the FILERDEPENDS
            filerdepends = {}
            rdep_data = oe.packagedata.read_subpkgdata(pkg, d)
            for key in rdep_data:
                if key.startswith("FILERDEPENDS:"):
                    for subkey in bb.utils.explode_deps(rdep_data[key]):
                        if subkey not in ignored_file_rdeps and \
                                not subkey.startswith('perl('):
                            # We already know it starts with FILERDEPENDS_
                            filerdepends[subkey] = key[13:]

            if filerdepends:
                done = rdepends[:]
                # Add the rprovides of itself
                if pkg not in done:
                    done.insert(0, pkg)

                # The python is not a package, but python-core provides it, so
                # skip checking /usr/bin/python if python is in the rdeps, in
                # case there is a RDEPENDS:pkg = "python" in the recipe.
                for py in [ d.getVar('MLPREFIX') + "python", "python" ]:
                    if py in done:
                        filerdepends.pop("/usr/bin/python",None)
                        done.remove(py)
                for rdep in done:
                    # The file dependencies may contain package names, e.g.,
                    # perl
                    filerdepends.pop(rdep,None)

                    for _, rdep_data in oe.packagedata.foreach_runtime_provider_pkgdata(d, rdep, True):
                        for key in rdep_data:
                            if key.startswith("FILERPROVIDES:") or key.startswith("RPROVIDES:"):
                                for subkey in bb.utils.explode_deps(rdep_data[key]):
                                    filerdepends.pop(subkey,None)
                            # Add the files list to the rprovides
                            if key.startswith("FILES_INFO:"):
                                # Use eval() to make it as a dict
                                for subkey in eval(rdep_data[key]):
                                    filerdepends.pop(subkey,None)

                    if not filerdepends:
                        # Break if all the file rdepends are met
                        break
            if filerdepends:
                for key in filerdepends:
                    error_msg = "%s contained in package %s requires %s, but no providers found in RDEPENDS:%s?" % \
                            (filerdepends[key].replace(":%s" % pkg, "").replace("@underscore@", "_"), pkg, key, pkg)
                    oe.qa.handle_error("file-rdeps", error_msg, d)

def package_qa_check_arch(path,name,d, elf):
    """
    Check if archs are compatible
    """
    import re, oe.elf

    if not elf:
        return

    host_os   = d.getVar('HOST_OS')
    host_arch = d.getVar('HOST_ARCH')
    provides  = d.getVar('PROVIDES')

    if host_arch == "allarch":
        oe.qa.handle_error("arch", "%s: inherits the allarch class, but has architecture-specific binaries %s" % \
            (name, package_qa_clean_path(path, d, name)), d)
        return

    # If this throws an exception, the machine_dict needs expanding
    (expected_machine, expected_osabi, expected_abiversion, expected_littleendian, expected_bits) \
        = oe.elf.machine_dict(d)[host_os][host_arch]

    actual_machine = elf.machine()
    actual_bits = elf.abiSize()
    actual_littleendian = elf.isLittleEndian()

    # BPF don't match the target
    if oe.qa.elf_machine_to_string(actual_machine) == "BPF":
        return

    # These targets have 32-bit userspace but 64-bit kernel, so fudge the expected values
    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'))):
        expected_bits = 64

    # Check the architecture and endiannes of the binary
    if expected_machine != actual_machine:
        oe.qa.handle_error("arch", "Architecture did not match (%s, expected %s) in %s" % \
                 (oe.qa.elf_machine_to_string(actual_machine), oe.qa.elf_machine_to_string(expected_machine), package_qa_clean_path(path, d, name)), d)

    if expected_bits != actual_bits:
        oe.qa.handle_error("arch", "Bit size did not match (%d, expected %d) in %s" % \
                 (actual_bits, expected_bits, package_qa_clean_path(path, d, name)), d)

    if expected_littleendian != actual_littleendian:
        oe.qa.handle_error("arch", "Endiannes did not match (%d, expected %d) in %s" % \
                 (actual_littleendian, expected_littleendian, package_qa_clean_path(path, d, name)), d)

def package_qa_check_encoding(keys, encode, d):
    def check_encoding(key, enc):
        sane = True
        value = d.getVar(key)
        if value:
            try:
                s = value.encode(enc)
            except UnicodeDecodeError as e:
                error_msg = "%s has non %s characters" % (key,enc)
                sane = False
                oe.qa.handle_error("invalid-chars", error_msg, d)
        return sane

    for key in keys:
        sane = check_encoding(key, encode)
        if not sane:
            break

def package_qa_check_deps(pkg, pkgdest, d):

    localdata = bb.data.createCopy(d)
    localdata.setVar('OVERRIDES', pkg)

    def check_valid_deps(var):
        try:
            rvar = bb.utils.explode_dep_versions2(localdata.getVar(var) or "")
        except ValueError as e:
            bb.fatal("%s:%s: %s" % (var, pkg, e))
        for dep in rvar:
            for v in rvar[dep]:
                if v and not v.startswith(('< ', '= ', '> ', '<= ', '>=')):
                    error_msg = "%s:%s is invalid: %s (%s)   only comparisons <, =, >, <=, and >= are allowed" % (var, pkg, dep, v)
                    oe.qa.handle_error("dep-cmp", error_msg, d)

    check_valid_deps('RDEPENDS')
    check_valid_deps('RRECOMMENDS')
    check_valid_deps('RSUGGESTS')
    check_valid_deps('RPROVIDES')
    check_valid_deps('RREPLACES')
    check_valid_deps('RCONFLICTS')

def package_qa_check_dbg(path, name, d, elf):
    """
    Check for ".debug" files or directories outside of the dbg package
    """

    if not "-dbg" in name and not "-ptest" in name:
        if '.debug' in path.split(os.path.sep):
            oe.qa.handle_error("debug-files", "%s: non debug package contains .debug directory %s" % \
                     (name, package_qa_clean_path(path, d, name)), d)

def package_qa_check_host_user(path, name, d, elf):
    """Check for paths outside of /home which are owned by the user running bitbake."""
    global cpath

    if not cpath.lexists(path):
        return

    dest = d.getVar('PKGDEST')
    pn = d.getVar('PN')
    home = os.path.join(dest, name, 'home')
    if path == home or path.startswith(home + os.sep):
        return

    try:
        stat = os.lstat(path)
    except OSError as exc:
        import errno
        if exc.errno != errno.ENOENT:
            raise
    else:
        check_uid = int(d.getVar('HOST_USER_UID'))
        if stat.st_uid == check_uid:
            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)

        check_gid = int(d.getVar('HOST_USER_GID'))
        if stat.st_gid == check_gid:
            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)

def package_qa_check_unhandled_features_check(pn, d):
    if not bb.data.inherits_class('features_check', d):
        var_set = False
        for kind in ['DISTRO', 'MACHINE', 'COMBINED']:
            for var in ['ANY_OF_' + kind + '_FEATURES', 'REQUIRED_' + kind + '_FEATURES', 'CONFLICT_' + kind + '_FEATURES']:
                if d.getVar(var) is not None or d.hasOverrides(var):
                    var_set = True
        if var_set:
            oe.qa.handle_error("unhandled-features-check", "%s: recipe doesn't inherit features_check" % pn, d)

def package_qa_walk(checkfuncs, package, d):
    global cpath

    elves = {}
    for path in pkgfiles[package]:
            elf = None
            if cpath.isfile(path) and not cpath.islink(path):
                elf = oe.qa.ELFFile(path)
                try:
                    elf.open()
                    elf.close()
                except oe.qa.NotELFFileError:
                    elf = None
            if elf:
                elves[path] = elf

    def prepopulate_objdump_p(elf, d):
        output = elf.run_objdump("-p", d)
        return (elf.name, output)

    results = oe.utils.multiprocess_launch(prepopulate_objdump_p, elves.values(), d, extraargs=(d,))
    for item in results:
        elves[item[0]].set_objdump("-p", item[1])

    for path in pkgfiles[package]:
        elf = elves.get(path)
        if elf:
            elf.open()
        for func in checkfuncs:
            func(path, package, d, elf)
        if elf:
            elf.close()

def package_qa_check_libexec(path,name, d, elf):

    # Skip the case where the default is explicitly /usr/libexec
    libexec = d.getVar('libexecdir')
    if libexec == "/usr/libexec":
        return

    if 'libexec' in path.split(os.path.sep):
        oe.qa.handle_error("libexec", "%s: %s is using libexec please relocate to %s" % (name, package_qa_clean_path(path, d, name), libexec), d)

def package_qa_check_dev_elf(path, name, d, elf):
    """
    Check that -dev doesn't contain real shared libraries.  The test has to
    check that the file is not a link and is an ELF object as some recipes
    install link-time .so files that are linker scripts.
    """
    global cpath
    if name.endswith("-dev") and path.endswith(".so") and not cpath.islink(path) and elf:
        oe.qa.handle_error("dev-elf", "-dev package %s contains non-symlink .so '%s'" % \
                 (name, package_qa_clean_path(path, d, name)), d)

def package_qa_check_mime_xdg(path, name, d, elf):
    """
    Check if package installs desktop file containing MimeType and requires
    mime-types.bbclass to create /usr/share/applications/mimeinfo.cache
    """

    if d.getVar("datadir") + "/applications" in path and path.endswith('.desktop') and not bb.data.inherits_class("mime-xdg", d):
        mime_type_found = False
        try:
            with open(path, 'r') as f:
                for line in f.read().split('\n'):
                    if 'MimeType' in line:
                        mime_type_found = True
                        break;
        except:
            # At least libreoffice installs symlinks with absolute paths that are dangling here.
            # We could implement some magic but for few (one) recipes it is not worth the effort so just warn:
            wstr = "%s cannot open %s - is it a symlink with absolute path?\n" % (name, package_qa_clean_path(path, d, name))
            wstr += "Please check if (linked) file contains key 'MimeType'.\n"
            pkgname = name
            if name == d.getVar('PN'):
                pkgname = '${PN}'
            wstr += "If yes: add \'inhert mime-xdg\' and \'MIME_XDG_PACKAGES += \"%s\"\' / if no add \'INSANE_SKIP:%s += \"mime-xdg\"\' to recipe." % (pkgname, pkgname)
            oe.qa.handle_error("mime-xdg", wstr, d)
        if mime_type_found:
            oe.qa.handle_error("mime-xdg", "%s: contains desktop file with key 'MimeType' but does not inhert mime-xdg: %s" % \
                    (name, package_qa_clean_path(path, d, name)), d)

def package_qa_check_mime(path, name, d, elf):
    """
    Check if package installs mime types to /usr/share/mime/packages
    while no inheriting mime.bbclass
    """

    if d.getVar("datadir") + "/mime/packages" in path and path.endswith('.xml') and not bb.data.inherits_class("mime", d):
        oe.qa.handle_error("mime", "package contains mime types but does not inherit mime: %s path '%s'" % \
                 (name, package_qa_clean_path(path, d, name)), d)

def package_qa_check_expanded_d(package, d):
    """
    Check for the expanded D (${D}) value in pkg_* and FILES
    variables, warn the user to use it correctly.
    """
    expanded_d = d.getVar('D')

    for var in 'FILES','pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm':
        bbvar = d.getVar(var + ":" + package) or ""
        if expanded_d in bbvar:
            if var == 'FILES':
                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)
            else:
                oe.qa.handle_error("expanded-d", "%s in %s recipe contains ${D}, it should be replaced by $D instead" % (var, package), d)

def read_subpackage_metadata(d):
    import oe.packagedata

    vars = {
        "PN" : d.getVar('PN'),
        "PE" : d.getVar('PE'),
        "PV" : d.getVar('PV'),
        "PR" : d.getVar('PR'),
    }

    data = oe.packagedata.read_pkgdata(vars["PN"], d)

    for key in data.keys():
        d.setVar(key, data[key])

    for pkg in d.getVar('PACKAGES').split():
        sdata = oe.packagedata.read_subpkgdata(pkg, d)
        for key in sdata.keys():
            if key in vars:
                if sdata[key] != vars[key]:
                    if key == "PN":
                        bb.fatal("Recipe %s is trying to create package %s which was already written by recipe %s. This will cause corruption, please resolve this and only provide the package from one recipe or the other or only build one of the recipes." % (vars[key], pkg, sdata[key]))
                    bb.fatal("Recipe %s is trying to change %s from '%s' to '%s'. This will cause do_package_write_* failures since the incorrect data will be used and they will be unable to find the right workdir." % (vars["PN"], key, vars[key], sdata[key]))
                continue
            #
            # If we set unsuffixed variables here there is a chance they could clobber override versions
            # of that variable, e.g. DESCRIPTION could clobber DESCRIPTION:<pkgname>
            # We therefore don't clobber for the unsuffixed variable versions
            #
            if key.endswith(":" + pkg):
                d.setVar(key, sdata[key])
            else:
                d.setVar(key, sdata[key], parsing=True)

def package_qa_check_staticdev(path, name, d, elf):
    """
    Check for ".a" library in non-staticdev packages
    There are a number of exceptions to this rule, -pic packages can contain
    static libraries, the _nonshared.a belong with their -dev packages and
    libgcc.a, libgcov.a will be skipped in their packages
    """

    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:
        oe.qa.handle_error("staticdev", "non -staticdev package contains static .a library: %s path '%s'" % \
                 (name, package_qa_clean_path(path, d, name)), d)

def package_qa_check_missing_update_alternatives(pn, d):
    # Look at all packages and find out if any of those sets ALTERNATIVE variable
    # without inheriting update-alternatives class
    for pkg in (d.getVar('PACKAGES') or '').split():
        if d.getVar('ALTERNATIVE:%s' % pkg) and not bb.data.inherits_class('update-alternatives', d):
            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)

def package_qa_check_perllocalpod(pkg, d):
    """
    Check that the recipe didn't ship a perlocal.pod file, which shouldn't be
    installed in a distribution package.  cpan.bbclass sets NO_PERLLOCAL=1 to
    handle this for most recipes.
    """
    import glob
    pkgd = oe.path.join(d.getVar('PKGDEST'), pkg)
    podpath = oe.path.join(pkgd, d.getVar("libdir"), "perl*", "*", "*", "perllocal.pod")

    matches = glob.glob(podpath)
    if matches:
        matches = [package_qa_clean_path(path, d, pkg) for path in matches]
        msg = "%s contains perllocal.pod (%s), should not be installed" % (pkg, " ".join(matches))
        oe.qa.handle_error("perllocalpod", msg, d)

def package_qa_check_empty_dirs(pkg, d):
    """
    Check for the existence of files in directories that are expected to be
    empty.
    """

    global cpath
    pkgd = oe.path.join(d.getVar('PKGDEST'), pkg)
    for dir in (d.getVar('QA_EMPTY_DIRS') or "").split():
        empty_dir = oe.path.join(pkgd, dir)
        if cpath.exists(empty_dir) and os.listdir(empty_dir):
            recommendation = (d.getVar('QA_EMPTY_DIRS_RECOMMENDATION:' + dir) or
                              "but it is expected to be empty")
            msg = "%s installs files in %s, %s" % (pkg, dir, recommendation)
            oe.qa.handle_error("empty-dirs", msg, d)

def package_qa_check_buildpaths(path, name, d, elf):
    """
    Check for build paths inside target files and error if paths are not
    explicitly ignored.
    """
    import stat
    # Ignore symlinks/devs/fifos
    mode = os.lstat(path).st_mode
    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):
        return

    tmpdir = bytes(d.getVar('TMPDIR'), encoding="utf-8")
    with open(path, 'rb') as f:
        file_content = f.read()
        if tmpdir in file_content:
            path = package_qa_clean_path(path, d, name)
            oe.qa.handle_error("buildpaths", "File %s in package %s contains reference to TMPDIR" % (path, name), d)

def package_qa_check_unlisted_pkg_lics(package, d):
    """
    Check that all licenses for a package are among the licenses for the recipe.
    """
    pkg_lics = d.getVar('LICENSE:' + package)
    if not pkg_lics:
        return

    recipe_lics_set = oe.license.list_licenses(d.getVar('LICENSE'))
    package_lics = oe.license.list_licenses(pkg_lics)
    unlisted = package_lics - recipe_lics_set
    if unlisted:
        oe.qa.handle_error("unlisted-pkg-lics",
                               "LICENSE:%s includes licenses (%s) that are not "
                               "listed in LICENSE" % (package, ' '.join(unlisted)), d)
    obsolete = set(oe.license.obsolete_license_list()) & package_lics - recipe_lics_set
    if obsolete:
        oe.qa.handle_error("obsolete-license",
                               "LICENSE:%s includes obsolete licenses %s" % (package, ' '.join(obsolete)), d)

def package_qa_check_usrmerge(pkg, d):
    global cpath
    pkgdest = d.getVar('PKGDEST')
    pkg_dir = pkgdest + os.sep + pkg + os.sep
    merged_dirs = ['bin', 'sbin', 'lib'] + d.getVar('MULTILIB_VARIANTS').split()
    for f in merged_dirs:
        if cpath.exists(pkg_dir + f) and not cpath.islink(pkg_dir + f):
            msg = "%s package is not obeying usrmerge distro feature. /%s should be relocated to /usr." % (pkg, f)
            oe.qa.handle_error("usrmerge", msg, d)
            return

def parse_test_matrix(matrix_name, skip, d):
        testmatrix = d.getVarFlags(matrix_name) or {}
        g = globals()
        checks = []
        for w in (d.getVar("WARN_QA") or "").split():
            if w in skip:
               continue
            if w in testmatrix and testmatrix[w] in g:
                checks.append(g[testmatrix[w]])

        for e in (d.getVar("ERROR_QA") or "").split():
            if e in skip:
               continue
            if e in testmatrix and testmatrix[e] in g:
                checks.append(g[testmatrix[e]])
        return checks

def package_qa_clean_path(path, d, pkg=None):
    """
    Remove redundant paths from the path for display.  If pkg isn't set then
    TMPDIR is stripped, otherwise PKGDEST/pkg is stripped.
    """
    if pkg:
        path = path.replace(os.path.join(d.getVar("PKGDEST"), pkg), "/")
    return path.replace(d.getVar("TMPDIR"), "/").replace("//", "/")

