def do_package(d):
    # Change the following version to cause sstate to invalidate the package
    # cache.  This is useful if an item this class depends on changes in a
    # way that the output of this class changes.  rpmdeps is a good example
    # as any change to rpmdeps requires this to be rerun.
    # PACKAGE_BBCLASS_VERSION = "5"

    # Init cachedpath
    global cpath
    cpath = oe.cachedpath.CachedPath()

    ###########################################################################
    # Sanity test the setup
    ###########################################################################

    packages = (d.getVar('PACKAGES') or "").split()
    if len(packages) < 1:
        bb.debug(1, "No packages to build, skipping do_package")
        return

    workdir = d.getVar('WORKDIR')
    outdir = d.getVar('DEPLOY_DIR')
    dest = d.getVar('D')
    dvar = d.getVar('PKGD')
    pn = d.getVar('PN')

    if not workdir or not outdir or not dest or not dvar or not pn:
        msg = "WORKDIR, DEPLOY_DIR, D, PN and PKGD all must be defined, unable to package"
        oe.qa.handle_error("var-undefined", msg, d)
        return

    bb.build.exec_func("package_setup_pkgv", d)
    bb.build.exec_func("package_convert_pr_autoinc", d)

    # Check for conflict between renamed packages and existing ones
    # for each package in PACKAGES, check if it will be renamed to an existing one
    for p in packages:
        rename = d.getVar('PKG:%s' % p)
        if rename and rename in packages:
            bb.fatal('package "%s" is renamed to "%s" using PKG:%s, but package name already exists' % (p, rename, p))

    ###########################################################################
    # Optimisations
    ###########################################################################

    # Continually expanding complex expressions is inefficient, particularly
    # when we write to the datastore and invalidate the expansion cache. This
    # code pre-expands some frequently used variables

    def expandVar(x, d):
        d.setVar(x, d.getVar(x))

    for x in 'PN', 'PV', 'BPN', 'TARGET_SYS', 'EXTENDPRAUTO':
        expandVar(x, d)

    ###########################################################################
    # Setup PKGD (from D)
    ###########################################################################

    bb.build.exec_func("package_prepare_pkgdata", d)
    bb.build.exec_func("perform_packagecopy", d)
    for f in (d.getVar('PACKAGE_PREPROCESS_FUNCS') or '').split():
        bb.build.exec_func(f, d)
    oe.package.process_split_and_strip_files(d)
    oe.package.fixup_perms(d)

    ###########################################################################
    # Split up PKGD into PKGDEST
    ###########################################################################

    cpath = oe.cachedpath.CachedPath()

    for f in (d.getVar('PACKAGESPLITFUNCS') or '').split():
        bb.build.exec_func(f, d)

    ###########################################################################
    # Process PKGDEST
    ###########################################################################

    # Build global list of files in each split package
    global pkgfiles
    pkgfiles = {}
    packages = d.getVar('PACKAGES').split()
    pkgdest = d.getVar('PKGDEST')
    for pkg in packages:
        pkgfiles[pkg] = []
        for walkroot, dirs, files in cpath.walk(pkgdest + "/" + pkg):
            for file in files:
                pkgfiles[pkg].append(walkroot + os.sep + file)


    # We want emit_pkgdata to run last, after everything
    for f in (d.getVar('PACKAGEFUNCS') or '').split():
        bb.build.exec_func(f, d)

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

    oe.qa.exit_if_errors(d)

do_package(d)

def package_setup_pkgv(d):
    pkgv = d.getVar("PKGV")
    # Expand SRCPV into PKGV if not present
    srcpv = bb.fetch.get_pkgv_string(d)
    if srcpv and "+" in pkgv:
        d.appendVar("PKGV", srcpv)
        pkgv = d.getVar("PKGV")

    # Adjust pkgv as necessary...
    if 'AUTOINC' in pkgv:
        d.setVar("PKGV", pkgv.replace("AUTOINC", "${PRSERV_PV_AUTOINC}"))

def package_do_split_locales(d):
    oe.package.split_locales(d)

def package_do_shlibs(d):
    oe.package.process_shlibs(pkgfiles, d)

def package_convert_pr_autoinc(d):
    # Change PRSERV_PV_AUTOINC and EXTENDPRAUTO usage to special values
    d.setVar('PRSERV_PV_AUTOINC', '@PRSERV_PV_AUTOINC@')
    d.setVar('EXTENDPRAUTO', '@EXTENDPRAUTO@')

def package_depchains(d):
    oe.package.process_depchains(pkgfiles, d)

def read_shlibdeps(d):
    pkglibdeps = oe.package.read_libdep_files(d)

    packages = d.getVar('PACKAGES').split()
    for pkg in packages:
        rdepends = bb.utils.explode_dep_versions2(d.getVar('RDEPENDS:' + pkg) or "")
        for dep in sorted(pkglibdeps[pkg]):
            # Add the dep if it's not already there, or if no comparison is set
            if dep not in rdepends:
                rdepends[dep] = []
            for v in pkglibdeps[pkg][dep]:
                if v not in rdepends[dep]:
                    rdepends[dep].append(v)
        d.setVar('RDEPENDS:' + pkg, bb.utils.join_deps(rdepends, commasep=False))

def populate_packages(d):
    oe.package.populate_packages(d)

def package_name_hook(d):
    # Export function set
    bb.build.exec_func('debian_package_name_hook', d)

def util_linux_libpackages(d):
    do_split_packages(d, root=d.getVar('UTIL_LINUX_LIBDIR'), file_regex=r'^lib(?!uuid)(.*)\.so\..*$',
                      output_pattern='${PN}-lib%s',
                      description='${PN} lib%s',
                      extra_depends='', prepend=True, allow_links=True)

def systemd_populate_packages(d):
    import re
    import shlex

    if not bb.utils.contains('DISTRO_FEATURES', 'systemd', True, False, d):
        return

    def get_package_var(d, var, pkg):
        val = (d.getVar('%s:%s' % (var, pkg)) or "").strip()
        if val == "":
            val = (d.getVar(var) or "").strip()
        return val

    # Check if systemd-packages already included in PACKAGES
    def systemd_check_package(pkg_systemd):
        packages = d.getVar('PACKAGES')
        if not pkg_systemd in packages.split():
            bb.error('%s is marked for packaging systemd scripts, but it does not appear in package list, please add it to PACKAGES or adjust SYSTEMD_PACKAGES accordingly' % pkg_systemd)


    def systemd_generate_package_scripts(pkg):
        bb.debug(1, 'adding systemd calls to postinst/postrm for %s' % pkg)

        paths_escaped = ' '.join(shlex.quote(s) for s in d.getVar('SYSTEMD_SERVICE:' + pkg).split())
        d.setVar('SYSTEMD_SERVICE_ESCAPED:' + pkg, paths_escaped)

        # Add pkg to the overrides so that it finds the SYSTEMD_SERVICE:pkg
        # variable.
        localdata = d.createCopy()
        localdata.prependVar("OVERRIDES", pkg + ":")

        postinst = d.getVar('pkg_postinst:%s' % pkg)
        if not postinst:
            postinst = '#!/bin/sh\n'
        postinst += localdata.getVar('systemd_postinst')
        d.setVar('pkg_postinst:%s' % pkg, postinst)

        prerm = d.getVar('pkg_prerm:%s' % pkg)
        if not prerm:
            prerm = '#!/bin/sh\n'
        prerm += localdata.getVar('systemd_prerm')
        d.setVar('pkg_prerm:%s' % pkg, prerm)


    # Add files to FILES:*-systemd if existent and not already done
    def systemd_append_file(pkg_systemd, file_append):
        appended = False
        if os.path.exists(oe.path.join(d.getVar("D"), file_append)):
            var_name = "FILES:" + pkg_systemd
            files = d.getVar(var_name, False) or ""
            if file_append not in files.split():
                d.appendVar(var_name, " " + file_append)
                appended = True
        return appended

    # Add systemd files to FILES:*-systemd, parse for Also= and follow recursive
    def systemd_add_files_and_parse(pkg_systemd, path, service, keys):
        # avoid infinite recursion
        if systemd_append_file(pkg_systemd, oe.path.join(path, service)):
            fullpath = oe.path.join(d.getVar("D"), path, service)
            if service.find('.service') != -1:
                # for *.service add *@.service
                service_base = service.replace('.service', '')
                systemd_add_files_and_parse(pkg_systemd, path, service_base + '@.service', keys)
            if service.find('.socket') != -1:
                # for *.socket add *.service and *@.service
                service_base = service.replace('.socket', '')
                systemd_add_files_and_parse(pkg_systemd, path, service_base + '.service', keys)
                systemd_add_files_and_parse(pkg_systemd, path, service_base + '@.service', keys)
            for key in keys.split():
                # recurse all dependencies found in keys ('Also';'Conflicts';..) and add to files
                cmd = "grep %s %s | sed 's,%s=,,g' | tr ',' '\\n'" % (key, shlex.quote(fullpath), key)
                pipe = os.popen(cmd, 'r')
                line = pipe.readline()
                while line:
                    line = line.replace('\n', '')
                    systemd_add_files_and_parse(pkg_systemd, path, line, keys)
                    line = pipe.readline()
                pipe.close()

    # Check service-files and call systemd_add_files_and_parse for each entry
    def systemd_check_services():
        searchpaths = [oe.path.join(d.getVar("sysconfdir"), "systemd", "system"),]
        searchpaths.append(d.getVar("systemd_system_unitdir"))
        searchpaths.append(d.getVar("systemd_user_unitdir"))
        systemd_packages = d.getVar('SYSTEMD_PACKAGES')

        keys = 'Also'
        # scan for all in SYSTEMD_SERVICE[]
        for pkg_systemd in systemd_packages.split():
            for service in get_package_var(d, 'SYSTEMD_SERVICE', pkg_systemd).split():
                path_found = ''

                # Deal with adding, for example, 'ifplugd@eth0.service' from
                # 'ifplugd@.service'
                base = None
                at = service.find('@')
                if at != -1:
                    ext = service.rfind('.')
                    base = service[:at] + '@' + service[ext:]

                for path in searchpaths:
                    if os.path.lexists(oe.path.join(d.getVar("D"), path, service)):
                        path_found = path
                        break
                    elif base is not None:
                        if os.path.exists(oe.path.join(d.getVar("D"), path, base)):
                            path_found = path
                            break

                if path_found != '':
                    systemd_add_files_and_parse(pkg_systemd, path_found, service, keys)
                else:
                    bb.fatal("Didn't find service unit '{0}', specified in SYSTEMD_SERVICE:{1}. {2}".format(
                        service, pkg_systemd, "Also looked for service unit '{0}'.".format(base) if base is not None else ""))

    def systemd_create_presets(pkg, action):
        presetf = oe.path.join(d.getVar("PKGD"), d.getVar("systemd_unitdir"), "system-preset/98-%s.preset" % pkg)
        bb.utils.mkdirhier(os.path.dirname(presetf))
        with open(presetf, 'a') as fd:
            for service in d.getVar('SYSTEMD_SERVICE:%s' % pkg).split():
                fd.write("%s %s\n" % (action,service))
        d.appendVar("FILES:%s" % pkg, ' ' + oe.path.join(d.getVar("systemd_unitdir"), "system-preset/98-%s.preset" % pkg))

    # Run all modifications once when creating package
    if os.path.exists(d.getVar("D")):
        for pkg in d.getVar('SYSTEMD_PACKAGES').split():
            systemd_check_package(pkg)
            if d.getVar('SYSTEMD_SERVICE:' + pkg):
                systemd_generate_package_scripts(pkg)
                action = get_package_var(d, 'SYSTEMD_AUTO_ENABLE', pkg)
                if action in ("enable", "disable"):
                    systemd_create_presets(pkg, action)
                elif action not in ("mask", "preset"):
                    bb.fatal("SYSTEMD_AUTO_ENABLE:%s '%s' is not 'enable', 'disable', 'mask' or 'preset'" % (pkg, action))
        systemd_check_services()

def package_do_filedeps(d):
    oe.package.process_filedeps(pkgfiles, d)
    if update_alternatives_enabled(d):
        apply_update_alternative_provides(d)

def util_linux_binpackages(d):
    def pkg_hook(f, pkg, file_regex, output_pattern, modulename):
        pn = d.getVar('PN')
        d.appendVar('RRECOMMENDS:%s' % pn, ' %s' % pkg)

        if d.getVar('ALTERNATIVE:' + pkg):
            return
        if d.getVarFlag('ALTERNATIVE_LINK_NAME', modulename):
            d.setVar('ALTERNATIVE:' + pkg, modulename)

    bindirs = sorted(list(set(d.expand("${base_sbindir} ${base_bindir} ${sbindir} ${bindir}").split())))
    for dir in bindirs:
        do_split_packages(d, root=dir,
                          file_regex=r'(.*)', output_pattern='${PN}-%s',
                          description='${PN} %s',
                          hook=pkg_hook, extra_depends='')

    # There are some symlinks for some binaries which we have ignored
    # above. Add them to the package owning the binary they are
    # pointing to
    extras = {}
    dvar = d.getVar('PKGD')
    for root in bindirs:
        for walkroot, dirs, files in os.walk(dvar + root):
            for f in files:
                file = os.path.join(walkroot, f)
                if not os.path.islink(file):
                    continue

                pkg = os.path.basename(os.readlink(file))
                extras.setdefault(pkg, [])
                extras[pkg].append(file.replace(dvar, '', 1))

    pn = d.getVar('PN')
    for pkg, links in extras.items():
        of = d.getVar('FILES:' + pn + '-' + pkg)
        links = of + " " + " ".join(sorted(links))
        d.setVar('FILES:' + pn + '-' + pkg, links)

def perform_packagecopy(d):
    enabled = oe.data.typed_value('LICENSE_CREATE_PACKAGE', d)
    if d.getVar('CLASSOVERRIDE') == 'class-target' and enabled:
        lic_files_paths = find_license_files(d)

        # LICENSE_FILES_DIRECTORY starts with '/' so os.path.join cannot be used to join D and LICENSE_FILES_DIRECTORY
        destdir = d.getVar('D') + os.path.join(d.getVar('LICENSE_FILES_DIRECTORY'), d.getVar('PN'))
        copy_license_files(lic_files_paths, destdir)
        add_package_and_files(d)
    import subprocess
    import shutil

    dest = d.getVar('D')
    dvar = d.getVar('PKGD')

    # Start by package population by taking a copy of the installed
    # files to operate on
    # Preserve sparse files and hard links
    cmd = 'tar --exclude=./sysroot-only -cf - -C %s -p -S . | tar -xf - -C %s' % (dest, dvar)
    subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)

    # replace RPATHs for the nativesdk binaries, to make them relocatable
    if bb.data.inherits_class('nativesdk', d) or bb.data.inherits_class('cross-canadian', d):
        rpath_replace (dvar, d)

def populate_packages_updatealternatives(d):
    if not update_alternatives_enabled(d):
        return

    # Do actual update alternatives processing
    for pkg in (d.getVar('PACKAGES') or "").split():
        # Create post install/removal scripts
        alt_setup_links = ""
        alt_remove_links = ""
        updates = update_alternatives_alt_targets(d, pkg)
        for alt_name, alt_link, alt_target, alt_priority in updates:
            alt_setup_links  += '\tupdate-alternatives --install %s %s %s %s\n' % (alt_link, alt_name, alt_target, alt_priority)
            alt_remove_links += '\tupdate-alternatives --remove  %s %s\n' % (alt_name, alt_target)

        if alt_setup_links:
            # RDEPENDS setup
            provider = d.getVar('VIRTUAL-RUNTIME_update-alternatives')
            if provider:
                #bb.note('adding runtime requirement for update-alternatives for %s' % pkg)
                d.appendVar('RDEPENDS:%s' % pkg, ' ' + d.getVar('MLPREFIX', False) + provider)

            bb.note('adding update-alternatives calls to postinst/prerm for %s' % pkg)
            bb.note('%s' % alt_setup_links)
            postinst = d.getVar('pkg_postinst:%s' % pkg)
            if postinst:
                postinst = alt_setup_links + postinst
            else:
                postinst = '#!/bin/sh\n' + alt_setup_links
            d.setVar('pkg_postinst:%s' % pkg, postinst)

            bb.note('%s' % alt_remove_links)
            prerm = d.getVar('pkg_prerm:%s' % pkg) or '#!/bin/sh\n'
            prerm += alt_remove_links
            d.setVar('pkg_prerm:%s' % pkg, prerm)

def apply_update_alternative_renames(d):
    if not update_alternatives_enabled(d):
       return

    import re

    def update_files(alt_target, alt_target_rename, pkg, d):
        f = d.getVar('FILES:' + pkg)
        if f:
            f = re.sub(r'(^|\s)%s(\s|$)' % re.escape (alt_target), r'\1%s\2' % alt_target_rename, f)
            d.setVar('FILES:' + pkg, f)

    # Check for deprecated usage...
    pn = d.getVar('BPN')
    if d.getVar('ALTERNATIVE_LINKS') != None:
        bb.fatal('%s: Use of ALTERNATIVE_LINKS/ALTERNATIVE_PATH/ALTERNATIVE_NAME is no longer supported, please convert to the updated syntax, see update-alternatives.bbclass for more info.' % pn)

    # Do actual update alternatives processing
    pkgdest = d.getVar('PKGD')
    for pkg in (d.getVar('PACKAGES') or "").split():
        # If the src == dest, we know we need to rename the dest by appending ${BPN}
        link_rename = []
        for alt_name in (d.getVar('ALTERNATIVE:%s' % pkg) or "").split():
            alt_link     = d.getVarFlag('ALTERNATIVE_LINK_NAME', alt_name)
            if not alt_link:
                alt_link = "%s/%s" % (d.getVar('bindir'), alt_name)
                d.setVarFlag('ALTERNATIVE_LINK_NAME', alt_name, alt_link)
            if alt_link.startswith(os.path.join(d.getVar('sysconfdir'), 'init.d')):
                # Managing init scripts does not work (bug #10433), foremost
                # because of a race with update-rc.d
                bb.fatal("Using update-alternatives for managing SysV init scripts is not supported")

            alt_target   = d.getVarFlag('ALTERNATIVE_TARGET_%s' % pkg, alt_name) or d.getVarFlag('ALTERNATIVE_TARGET', alt_name)
            alt_target   = alt_target or d.getVar('ALTERNATIVE_TARGET_%s' % pkg) or d.getVar('ALTERNATIVE_TARGET') or alt_link
            # Sometimes alt_target is specified as relative to the link name.
            alt_target   = os.path.join(os.path.dirname(alt_link), alt_target)

            # If the link and target are the same name, we need to rename the target.
            if alt_link == alt_target:
                src = '%s/%s' % (pkgdest, alt_target)
                alt_target_rename = '%s.%s' % (alt_target, pn)
                dest = '%s/%s' % (pkgdest, alt_target_rename)
                if os.path.lexists(dest):
                    bb.note('%s: Already renamed: %s' % (pn, alt_target_rename))
                elif os.path.lexists(src):
                    if os.path.islink(src):
                        # Delay rename of links
                        link_rename.append((alt_target, alt_target_rename))
                    else:
                        bb.note('%s: Rename %s -> %s' % (pn, alt_target, alt_target_rename))
                        bb.utils.rename(src, dest)
                        update_files(alt_target, alt_target_rename, pkg, d)
                else:
                    bb.warn("%s: alternative target (%s or %s) does not exist, skipping..." % (pn, alt_target, alt_target_rename))
                    continue
                d.setVarFlag('ALTERNATIVE_TARGET_%s' % pkg, alt_name, alt_target_rename)

        # Process delayed link names
        # Do these after other renames so we can correct broken links
        for (alt_target, alt_target_rename) in link_rename:
            src = '%s/%s' % (pkgdest, alt_target)
            dest = '%s/%s' % (pkgdest, alt_target_rename)
            link_target = oe.path.realpath(src, pkgdest, True)

            if os.path.lexists(link_target):
                # Ok, the link_target exists, we can rename
                bb.note('%s: Rename (link) %s -> %s' % (pn, alt_target, alt_target_rename))
                bb.utils.rename(src, dest)
            else:
                # Try to resolve the broken link to link.${BPN}
                link_maybe = '%s.%s' % (os.readlink(src), pn)
                if os.path.lexists(os.path.join(os.path.dirname(src), link_maybe)):
                    # Ok, the renamed link target exists.. create a new link, and remove the original
                    bb.note('%s: Creating new link %s -> %s' % (pn, alt_target_rename, link_maybe))
                    os.symlink(link_maybe, dest)
                    os.unlink(src)
                else:
                    bb.warn('%s: Unable to resolve dangling symlink: %s' % (pn, alt_target))
                    continue
            update_files(alt_target, alt_target_rename, pkg, d)

def emit_pkgdata(d):
    import oe.packagedata
    oe.packagedata.emit_pkgdata(pkgfiles, d)

def package_prepare_pkgdata(d):
    import copy
    import glob

    taskdepdata = d.getVar("BB_TASKDEPDATA", False)
    mytaskname = d.getVar("BB_RUNTASK")
    if mytaskname.endswith("_setscene"):
        mytaskname = mytaskname.replace("_setscene", "")
    workdir = d.getVar("WORKDIR")
    pn = d.getVar("PN")
    stagingdir = d.getVar("PKGDATA_DIR")
    pkgdatadir = d.getVar("WORKDIR_PKGDATA")

    # Detect bitbake -b usage
    nodeps = d.getVar("BB_LIMITEDDEPS") or False
    if nodeps:
        staging_package_populate_pkgdata_dir(pkgdatadir, d)
        return

    start = None
    configuredeps = []
    for dep in taskdepdata:
        data = taskdepdata[dep]
        if data[1] == mytaskname and data[0] == pn:
            start = dep
            break
    if start is None:
        bb.fatal("Couldn't find ourself in BB_TASKDEPDATA?")

    # We need to figure out which sysroot files we need to expose to this task.
    # This needs to match what would get restored from sstate, which is controlled
    # ultimately by calls from bitbake to setscene_depvalid().
    # That function expects a setscene dependency tree. We build a dependency tree
    # condensed to inter-sstate task dependencies, similar to that used by setscene
    # tasks. We can then call into setscene_depvalid() and decide
    # which dependencies we can "see" and should expose in the recipe specific sysroot.
    setscenedeps = copy.deepcopy(taskdepdata)

    start = set([start])

    sstatetasks = d.getVar("SSTATETASKS").split()
    # Add recipe specific tasks referenced by setscene_depvalid()
    sstatetasks.append("do_stash_locale")

    # If start is an sstate task (like do_package) we need to add in its direct dependencies
    # else the code below won't recurse into them.
    for dep in set(start):
        for dep2 in setscenedeps[dep][3]:
            start.add(dep2)
        start.remove(dep)

    # Create collapsed do_populate_sysroot -> do_populate_sysroot tree
    for dep in taskdepdata:
        data = setscenedeps[dep]
        if data[1] not in sstatetasks:
            for dep2 in setscenedeps:
                data2 = setscenedeps[dep2]
                if dep in data2[3]:
                    data2[3].update(setscenedeps[dep][3])
                    data2[3].remove(dep)
            if dep in start:
                start.update(setscenedeps[dep][3])
                start.remove(dep)
            del setscenedeps[dep]

    # Remove circular references
    for dep in setscenedeps:
        if dep in setscenedeps[dep][3]:
            setscenedeps[dep][3].remove(dep)

    # Direct dependencies should be present and can be depended upon
    for dep in set(start):
        if setscenedeps[dep][1] == "do_packagedata":
            if dep not in configuredeps:
                configuredeps.append(dep)

    msgbuf = []
    # Call into setscene_depvalid for each sub-dependency and only copy sysroot files
    # for ones that would be restored from sstate.
    done = list(start)
    next = list(start)
    while next:
        new = []
        for dep in next:
            data = setscenedeps[dep]
            for datadep in data[3]:
                if datadep in done:
                    continue
                taskdeps = {}
                taskdeps[dep] = setscenedeps[dep][:2]
                taskdeps[datadep] = setscenedeps[datadep][:2]
                retval = setscene_depvalid(datadep, taskdeps, [], d, msgbuf)
                done.append(datadep)
                new.append(datadep)
                if retval:
                    msgbuf.append("Skipping setscene dependency %s" % datadep)
                    continue
                if datadep not in configuredeps and setscenedeps[datadep][1] == "do_packagedata":
                    configuredeps.append(datadep)
                    msgbuf.append("Adding dependency on %s" % setscenedeps[datadep][0])
                else:
                    msgbuf.append("Following dependency on %s" % setscenedeps[datadep][0])
        next = new

    # This logging is too verbose for day to day use sadly
    #bb.debug(2, "\n".join(msgbuf))

    seendirs = set()
    postinsts = []
    multilibs = {}
    manifests = {}

    msg_adding = []

    for dep in configuredeps:
        c = setscenedeps[dep][0]
        msg_adding.append(c)

        manifest, d2 = oe.sstatesig.find_sstate_manifest(c, setscenedeps[dep][2], "packagedata", d, multilibs)
        destsysroot = pkgdatadir

        if manifest:
            targetdir = destsysroot
            with open(manifest, "r") as f:
                manifests[dep] = manifest
                for l in f:
                    l = l.strip()
                    dest = targetdir + l.replace(stagingdir, "")
                    if l.endswith("/"):
                        staging_copydir(l, targetdir, dest, seendirs)
                        continue
                    staging_copyfile(l, targetdir, dest, postinsts, seendirs)

    bb.note("Installed into pkgdata-sysroot: %s" % str(msg_adding))

def package_do_pkgconfig(d):
    oe.package.process_pkgconfig(pkgfiles, d)

def package_fixsymlinks(d):
    oe.package.process_fixsymlinks(pkgfiles, d)

def update_alternatives_enabled(d):
    # Update Alternatives only works on target packages...
    if bb.data.inherits_class('native', d) or \
       bb.data.inherits_class('cross', d) or bb.data.inherits_class('crosssdk', d) or \
       bb.data.inherits_class('cross-canadian', d):
        return False

    # Disable when targeting mingw32 (no target support)
    if d.getVar("TARGET_OS") == "mingw32":
        return False

    return True

def setscene_depvalid(task, taskdependees, notneeded, d, log=None):
    # taskdependees is a dict of tasks which depend on task, each being a 3 item list of [PN, TASKNAME, FILENAME]
    # task is included in taskdependees too
    # Return - False - We need this dependency
    #        - True - We can skip this dependency
    import re

    def logit(msg, log):
        if log is not None:
            log.append(msg)
        else:
            bb.debug(2, msg)

    logit("Considering setscene task: %s" % (str(taskdependees[task])), log)

    directtasks = ["do_populate_lic", "do_deploy_source_date_epoch", "do_shared_workdir", "do_stash_locale", "do_gcc_stash_builddir", "do_create_spdx", "do_deploy_archives"]

    def isNativeCross(x):
        return x.endswith("-native") or "-cross-" in x or "-crosssdk" in x or x.endswith("-cross")

    # We only need to trigger deploy_source_date_epoch through direct dependencies
    if taskdependees[task][1] in directtasks:
        return True

    # We only need to trigger packagedata through direct dependencies
    # but need to preserve packagedata on packagedata links
    if taskdependees[task][1] == "do_packagedata":
        for dep in taskdependees:
            if taskdependees[dep][1] == "do_packagedata":
                return False
        return True

    for dep in taskdependees:
        logit("  considering dependency: %s" % (str(taskdependees[dep])), log)
        if task == dep:
            continue
        if dep in notneeded:
            continue
        # do_package_write_* and do_package doesn't need do_package
        if taskdependees[task][1] == "do_package" and taskdependees[dep][1] in ['do_package', 'do_package_write_deb', 'do_package_write_ipk', 'do_package_write_rpm', 'do_packagedata', 'do_package_qa']:
            continue
        # do_package_write_* need do_populate_sysroot as they're mainly postinstall dependencies
        if taskdependees[task][1] == "do_populate_sysroot" and taskdependees[dep][1] in ['do_package_write_deb', 'do_package_write_ipk', 'do_package_write_rpm']:
            return False
        # do_package/packagedata/package_qa/deploy don't need do_populate_sysroot
        if taskdependees[task][1] == "do_populate_sysroot" and taskdependees[dep][1] in ['do_package', 'do_packagedata', 'do_package_qa', 'do_deploy']:
            continue
        # Native/Cross packages don't exist and are noexec anyway
        if isNativeCross(taskdependees[dep][0]) and taskdependees[dep][1] in ['do_package_write_deb', 'do_package_write_ipk', 'do_package_write_rpm', 'do_packagedata', 'do_package', 'do_package_qa']:
            continue

        # Consider sysroot depending on sysroot tasks
        if taskdependees[task][1] == 'do_populate_sysroot' and taskdependees[dep][1] == 'do_populate_sysroot':
            # Allow excluding certain recursive dependencies. If a recipe needs it should add a
            # specific dependency itself, rather than relying on one of its dependees to pull
            # them in.
            # See also http://lists.openembedded.org/pipermail/openembedded-core/2018-January/146324.html
            not_needed = False
            excludedeps = d.getVar('_SSTATE_EXCLUDEDEPS_SYSROOT')
            if excludedeps is None:
                # Cache the regular expressions for speed
                excludedeps = []
                for excl in (d.getVar('SSTATE_EXCLUDEDEPS_SYSROOT') or "").split():
                    excludedeps.append((re.compile(excl.split('->', 1)[0]), re.compile(excl.split('->', 1)[1])))
                d.setVar('_SSTATE_EXCLUDEDEPS_SYSROOT', excludedeps)
            for excl in excludedeps:
                if excl[0].match(taskdependees[dep][0]):
                    if excl[1].match(taskdependees[task][0]):
                        not_needed = True
                        break
            if not_needed:
                continue
            # For meta-extsdk-toolchain we want all sysroot dependencies
            if taskdependees[dep][0] == 'meta-extsdk-toolchain':
                return False
            # Native/Cross populate_sysroot need their dependencies
            if isNativeCross(taskdependees[task][0]) and isNativeCross(taskdependees[dep][0]):
                return False
            # Target populate_sysroot depended on by cross tools need to be installed
            if isNativeCross(taskdependees[dep][0]):
                return False
            # Native/cross tools depended upon by target sysroot are not needed
            # Add an exception for shadow-native as required by useradd.bbclass
            if isNativeCross(taskdependees[task][0]) and taskdependees[task][0] != 'shadow-native':
                continue
            # Target populate_sysroot need their dependencies
            return False

        if taskdependees[dep][1] in directtasks:
            continue

        # Safe fallthrough default
        logit(" Default setscene dependency fall through due to dependency: %s" % (str(taskdependees[dep])), log)
        return False
    return True

def rpath_replace (path, d):
    bindirs = d.expand("${bindir} ${sbindir} ${base_sbindir} ${base_bindir} ${libdir} ${base_libdir} ${libexecdir} ${PREPROCESS_RELOCATE_DIRS}").split()

    for bindir in bindirs:
        #bb.note ("Processing directory " + bindir)
        directory = path + "/" + bindir
        process_dir (path, directory, d)

def apply_update_alternative_provides(d):
    pn = d.getVar('BPN')
    pkgdest = d.getVar('PKGDEST')

    for pkg in d.getVar('PACKAGES').split():
        for alt_name in (d.getVar('ALTERNATIVE:%s' % pkg) or "").split():
            alt_link     = d.getVarFlag('ALTERNATIVE_LINK_NAME', alt_name)
            alt_target   = d.getVarFlag('ALTERNATIVE_TARGET_%s' % pkg, alt_name) or d.getVarFlag('ALTERNATIVE_TARGET', alt_name)
            alt_target   = alt_target or d.getVar('ALTERNATIVE_TARGET_%s' % pkg) or d.getVar('ALTERNATIVE_TARGET') or alt_link

            if alt_link == alt_target:
                bb.warn('%s: alt_link == alt_target: %s == %s' % (pn, alt_link, alt_target))
                alt_target = '%s.%s' % (alt_target, pn)

            if not os.path.lexists('%s/%s/%s' % (pkgdest, pkg, alt_target)):
                continue

            # Add file provide
            trans_target = oe.package.file_translate(alt_target)
            d.appendVar('FILERPROVIDES:%s:%s' % (trans_target, pkg), " " + alt_link)
            if not trans_target in (d.getVar('FILERPROVIDESFLIST:%s' % pkg) or ""):
                d.appendVar('FILERPROVIDESFLIST:%s' % pkg, " " + trans_target)

def add_package_and_files(d):
    packages = d.getVar('PACKAGES')
    files = d.getVar('LICENSE_FILES_DIRECTORY')
    pn = d.getVar('PN')
    pn_lic = "%s%s" % (pn, d.getVar('LICENSE_PACKAGE_SUFFIX', False))
    if pn_lic in packages.split():
        bb.warn("%s package already existed in %s." % (pn_lic, pn))
    else:
        # first in PACKAGES to be sure that nothing else gets LICENSE_FILES_DIRECTORY
        d.setVar('PACKAGES', "%s %s" % (pn_lic, packages))
        d.setVar('FILES:' + pn_lic, files)

def do_split_packages(d, root, file_regex, output_pattern, description, postinst=None, recursive=False, hook=None, extra_depends=None, aux_files_pattern=None, postrm=None, allow_dirs=False, prepend=False, match_path=False, aux_files_pattern_verbatim=None, allow_links=False, summary=None):
    """
    Used in .bb files to split up dynamically generated subpackages of a
    given package, usually plugins or modules.

    Arguments:
    root           -- the path in which to search
    file_regex     -- regular expression to match searched files. Use
                      parentheses () to mark the part of this expression
                      that should be used to derive the module name (to be
                      substituted where %s is used in other function
                      arguments as noted below)
    output_pattern -- pattern to use for the package names. Must include %s.
    description    -- description to set for each package. Must include %s.
    postinst       -- postinstall script to use for all packages (as a
                      string)
    recursive      -- True to perform a recursive search - default False
    hook           -- a hook function to be called for every match. The
                      function will be called with the following arguments
                      (in the order listed):
                        f: full path to the file/directory match
                        pkg: the package name
                        file_regex: as above
                        output_pattern: as above
                        modulename: the module name derived using file_regex
    extra_depends  -- extra runtime dependencies (RDEPENDS) to be set for
                      all packages. The default value of None causes a
                      dependency on the main package (${PN}) - if you do
                      not want this, pass '' for this parameter.
    aux_files_pattern -- extra item(s) to be added to FILES for each
                      package. Can be a single string item or a list of
                      strings for multiple items.  Must include %s.
    postrm         -- postrm script to use for all packages (as a string)
    allow_dirs     -- True allow directories to be matched - default False
    prepend        -- if True, prepend created packages to PACKAGES instead
                      of the default False which appends them
    match_path     -- match file_regex on the whole relative path to the
                      root rather than just the file name
    aux_files_pattern_verbatim -- extra item(s) to be added to FILES for
                      each package, using the actual derived module name
                      rather than converting it to something legal for a
                      package name. Can be a single string item or a list
                      of strings for multiple items. Must include %s.
    allow_links    -- True to allow symlinks to be matched - default False
    summary        -- Summary to set for each package. Must include %s;
                      defaults to description if not set.

    """

    dvar = d.getVar('PKGD')
    root = d.expand(root)
    output_pattern = d.expand(output_pattern)
    extra_depends = d.expand(extra_depends)

    # If the root directory doesn't exist, don't error out later but silently do
    # no splitting.
    if not os.path.exists(dvar + root):
        return []

    ml = d.getVar("MLPREFIX")
    if ml:
        if not output_pattern.startswith(ml):
            output_pattern = ml + output_pattern

        newdeps = []
        for dep in (extra_depends or "").split():
            if dep.startswith(ml):
                newdeps.append(dep)
            else:
                newdeps.append(ml + dep)
        if newdeps:
            extra_depends = " ".join(newdeps)


    packages = d.getVar('PACKAGES').split()
    split_packages = set()

    if postinst:
        postinst = '#!/bin/sh\n' + postinst + '\n'
    if postrm:
        postrm = '#!/bin/sh\n' + postrm + '\n'
    if not recursive:
        objs = os.listdir(dvar + root)
    else:
        objs = []
        for walkroot, dirs, files in os.walk(dvar + root):
            for file in files:
                relpath = os.path.join(walkroot, file).replace(dvar + root + '/', '', 1)
                if relpath:
                    objs.append(relpath)

    if extra_depends == None:
        extra_depends = d.getVar("PN")

    if not summary:
        summary = description

    for o in sorted(objs):
        import re, stat
        if match_path:
            m = re.match(file_regex, o)
        else:
            m = re.match(file_regex, os.path.basename(o))

        if not m:
            continue
        f = os.path.join(dvar + root, o)
        mode = os.lstat(f).st_mode
        if not (stat.S_ISREG(mode) or (allow_links and stat.S_ISLNK(mode)) or (allow_dirs and stat.S_ISDIR(mode))):
            continue
        on = oe.package.legitimize_package_name(m.group(1))
        pkg = output_pattern % on
        split_packages.add(pkg)
        if not pkg in packages:
            if prepend:
                packages = [pkg] + packages
            else:
                packages.append(pkg)
        oldfiles = d.getVar('FILES:' + pkg)
        newfile = os.path.join(root, o)
        # These names will be passed through glob() so if the filename actually
        # contains * or ? (rare, but possible) we need to handle that specially
        newfile = newfile.replace('*', '[*]')
        newfile = newfile.replace('?', '[?]')
        if not oldfiles:
            the_files = [newfile]
            if aux_files_pattern:
                if type(aux_files_pattern) is list:
                    for fp in aux_files_pattern:
                        the_files.append(fp % on)
                else:
                    the_files.append(aux_files_pattern % on)
            if aux_files_pattern_verbatim:
                if type(aux_files_pattern_verbatim) is list:
                    for fp in aux_files_pattern_verbatim:
                        the_files.append(fp % m.group(1))
                else:
                    the_files.append(aux_files_pattern_verbatim % m.group(1))
            d.setVar('FILES:' + pkg, " ".join(the_files))
        else:
            d.setVar('FILES:' + pkg, oldfiles + " " + newfile)
        if extra_depends != '':
            d.appendVar('RDEPENDS:' + pkg, ' ' + extra_depends)
        if not d.getVar('DESCRIPTION:' + pkg):
            d.setVar('DESCRIPTION:' + pkg, description % on)
        if not d.getVar('SUMMARY:' + pkg):
            d.setVar('SUMMARY:' + pkg, summary % on)
        if postinst:
            d.setVar('pkg_postinst:' + pkg, postinst)
        if postrm:
            d.setVar('pkg_postrm:' + pkg, postrm)
        if callable(hook):
            hook(f, pkg, file_regex, output_pattern, m.group(1))

    d.setVar('PACKAGES', ' '.join(packages))
    return list(split_packages)

def update_alternatives_alt_targets(d, pkg):
    """
    Returns the update-alternatives metadata for a package.

    The returned format is a list of tuples where the tuple contains:
    alt_name:     The binary name
    alt_link:     The path for the binary (Shared by different packages)
    alt_target:   The path for the renamed binary (Unique per package)
    alt_priority: The priority of the alt_target

    All the alt_targets will be installed into the sysroot. The alt_link is
    a symlink pointing to the alt_target with the highest priority.
    """

    pn = d.getVar('BPN')
    pkgdest = d.getVar('PKGD')
    updates = list()
    for alt_name in (d.getVar('ALTERNATIVE:%s' % pkg) or "").split():
        alt_link     = d.getVarFlag('ALTERNATIVE_LINK_NAME', alt_name)
        alt_target   = d.getVarFlag('ALTERNATIVE_TARGET_%s' % pkg, alt_name) or \
                       d.getVarFlag('ALTERNATIVE_TARGET', alt_name) or \
                       d.getVar('ALTERNATIVE_TARGET_%s' % pkg) or \
                       d.getVar('ALTERNATIVE_TARGET') or \
                       alt_link
        alt_priority = d.getVarFlag('ALTERNATIVE_PRIORITY_%s' % pkg,  alt_name) or \
                       d.getVarFlag('ALTERNATIVE_PRIORITY',  alt_name) or \
                       d.getVar('ALTERNATIVE_PRIORITY_%s' % pkg) or  \
                       d.getVar('ALTERNATIVE_PRIORITY')

        # This shouldn't trigger, as it should have been resolved earlier!
        if alt_link == alt_target:
            bb.note('alt_link == alt_target: %s == %s -- correcting, this should not happen!' % (alt_link, alt_target))
            alt_target = '%s.%s' % (alt_target, pn)

        if not os.path.lexists('%s/%s' % (pkgdest, alt_target)):
            bb.warn('%s: NOT adding alternative provide %s: %s does not exist' % (pn, alt_link, alt_target))
            continue

        alt_target = os.path.normpath(alt_target)
        updates.append( (alt_name, alt_link, alt_target, alt_priority) )

    return updates

def staging_copyfile(c, target, dest, postinsts, seendirs):
    import errno

    destdir = os.path.dirname(dest)
    if destdir not in seendirs:
        bb.utils.mkdirhier(destdir)
        seendirs.add(destdir)
    if "/usr/bin/postinst-" in c:
        postinsts.append(dest)
    if os.path.islink(c):
        linkto = os.readlink(c)
        if os.path.lexists(dest):
            if not os.path.islink(dest):
                raise OSError(errno.EEXIST, "Link %s already exists as a file" % dest, dest)
            if os.readlink(dest) == linkto:
                return dest
            raise OSError(errno.EEXIST, "Link %s already exists to a different location? (%s vs %s)" % (dest, os.readlink(dest), linkto), dest)
        os.symlink(linkto, dest)
        #bb.warn(c)
    else:
        try:
            os.link(c, dest)
        except OSError as err:
            if err.errno == errno.EXDEV:
                bb.utils.copyfile(c, dest)
            else:
                raise
    return dest

def debian_package_name_hook(d):
    import glob, copy, stat, errno, re, pathlib, subprocess

    pkgdest = d.getVar("PKGDEST")
    packages = d.getVar('PACKAGES')
    so_re = re.compile(r"lib.*\.so")

    def socrunch(s):
        s = s.lower().replace('_', '-')
        m = re.match(r"^(.*)(.)\.so\.(.*)$", s)
        if m is None:
            return None
        if m.group(2) in '0123456789':
            bin = '%s%s-%s' % (m.group(1), m.group(2), m.group(3))
        else:
            bin = m.group(1) + m.group(2) + m.group(3)
        dev = m.group(1) + m.group(2)
        return (bin, dev)

    def isexec(path):
        try:
            s = os.stat(path)
        except (os.error, AttributeError):
            return 0
        return (s[stat.ST_MODE] & stat.S_IEXEC)

    def add_rprovides(pkg, d):
        newpkg = d.getVar('PKG:' + pkg)
        if newpkg and newpkg != pkg:
            provs = (d.getVar('RPROVIDES:' + pkg) or "").split()
            if pkg not in provs:
                d.appendVar('RPROVIDES:' + pkg, " " + pkg + " (=" + d.getVar("PKGV") + ")")

    def auto_libname(packages, orig_pkg):
        p = lambda var: pathlib.PurePath(d.getVar(var))
        libdirs = (p("base_libdir"), p("libdir"))
        bindirs = (p("base_bindir"), p("base_sbindir"), p("bindir"), p("sbindir"))

        sonames = []
        has_bins = 0
        has_libs = 0
        for f in pkgfiles[orig_pkg]:
            # This is .../packages-split/orig_pkg/
            pkgpath = pathlib.PurePath(pkgdest, orig_pkg)
            # Strip pkgpath off the full path to a file in the package, re-root
            # so it is absolute, and then get the parent directory of the file.
            path = pathlib.PurePath("/") / (pathlib.PurePath(f).relative_to(pkgpath).parent)
            if path in bindirs:
                has_bins = 1
            if path in libdirs:
                has_libs = 1
                if so_re.match(os.path.basename(f)):
                    try:
                        cmd = [d.expand("${TARGET_PREFIX}objdump"), "-p", f]
                        output = subprocess.check_output(cmd).decode("utf-8")
                        for m in re.finditer(r"\s+SONAME\s+([^\s]+)", output):
                            if m.group(1) not in sonames:
                                sonames.append(m.group(1))
                    except subprocess.CalledProcessError:
                        pass
        bb.debug(1, 'LIBNAMES: pkg %s libs %d bins %d sonames %s' % (orig_pkg, has_libs, has_bins, sonames))
        soname = None
        if len(sonames) == 1:
            soname = sonames[0]
        elif len(sonames) > 1:
            lead = d.getVar('LEAD_SONAME')
            if lead:
                r = re.compile(lead)
                filtered = []
                for s in sonames:
                    if r.match(s):
                        filtered.append(s)
                if len(filtered) == 1:
                    soname = filtered[0]
                elif len(filtered) > 1:
                    bb.note("Multiple matches (%s) for LEAD_SONAME '%s'" % (", ".join(filtered), lead))
                else:
                    bb.note("Multiple libraries (%s) found, but LEAD_SONAME '%s' doesn't match any of them" % (", ".join(sonames), lead))
            else:
                bb.note("Multiple libraries (%s) found and LEAD_SONAME not defined" % ", ".join(sonames))

        if has_libs and not has_bins and soname:
            soname_result = socrunch(soname)
            if soname_result:
                (pkgname, devname) = soname_result
                for pkg in packages.split():
                    if (d.getVar('PKG:' + pkg, False) or d.getVar('DEBIAN_NOAUTONAME:' + pkg, False)):
                        add_rprovides(pkg, d)
                        continue
                    debian_pn = d.getVar('DEBIANNAME:' + pkg, False)
                    if debian_pn:
                        newpkg = debian_pn
                    elif pkg == orig_pkg:
                        newpkg = pkgname
                    else:
                        newpkg = pkg.replace(orig_pkg, devname, 1)
                    mlpre=d.getVar('MLPREFIX')
                    if mlpre:
                        if not newpkg.find(mlpre) == 0:
                            newpkg = mlpre + newpkg
                    if newpkg != pkg:
                        bb.note("debian: renaming %s to %s" % (pkg, newpkg))
                        d.setVar('PKG:' + pkg, newpkg)
                        add_rprovides(pkg, d)
        else:
            add_rprovides(orig_pkg, d)

    # reversed sort is needed when some package is substring of another
    # ie in ncurses we get without reverse sort:
    # DEBUG: LIBNAMES: pkgname libtic5 devname libtic pkg ncurses-libtic orig_pkg ncurses-libtic debian_pn None newpkg libtic5
    # and later
    # DEBUG: LIBNAMES: pkgname libtic5 devname libtic pkg ncurses-libticw orig_pkg ncurses-libtic debian_pn None newpkg libticw
    # so we need to handle ncurses-libticw->libticw5 before ncurses-libtic->libtic5
    for pkg in sorted((d.getVar('AUTO_LIBNAME_PKGS') or "").split(), reverse=True):
        auto_libname(packages, pkg)

def find_license_files(d):
    """
    Creates list of files used in LIC_FILES_CHKSUM and generic LICENSE files.
    """
    import shutil
    import oe.license
    from collections import defaultdict, OrderedDict

    # All the license files for the package
    lic_files = d.getVar('LIC_FILES_CHKSUM') or ""
    pn = d.getVar('PN')
    # The license files are located in S/LIC_FILE_CHECKSUM.
    srcdir = d.getVar('S')
    # Directory we store the generic licenses as set in the distro configuration
    generic_directory = d.getVar('COMMON_LICENSE_DIR')
    # List of basename, path tuples
    lic_files_paths = []
    # hash for keep track generic lics mappings
    non_generic_lics = {}
    # Entries from LIC_FILES_CHKSUM
    lic_chksums = {}
    license_source_dirs = []
    license_source_dirs.append(generic_directory)
    try:
        additional_lic_dirs = d.getVar('LICENSE_PATH').split()
        for lic_dir in additional_lic_dirs:
            license_source_dirs.append(lic_dir)
    except:
        pass

    class FindVisitor(oe.license.LicenseVisitor):
        def visit_Str(self, node):
            #
            # Until I figure out what to do with
            # the two modifiers I support (or greater = +
            # and "with exceptions" being *
            # we'll just strip out the modifier and put
            # the base license.
            find_license(node.s.replace("+", "").replace("*", ""))
            self.generic_visit(node)

        def visit_Constant(self, node):
            find_license(node.value.replace("+", "").replace("*", ""))
            self.generic_visit(node)

    def find_license(license_type):
        try:
            bb.utils.mkdirhier(gen_lic_dest)
        except:
            pass
        spdx_generic = None
        license_source = None
        # If the generic does not exist we need to check to see if there is an SPDX mapping to it,
        # unless NO_GENERIC_LICENSE is set.
        for lic_dir in license_source_dirs:
            if not os.path.isfile(os.path.join(lic_dir, license_type)):
                if d.getVarFlag('SPDXLICENSEMAP', license_type) != None:
                    # Great, there is an SPDXLICENSEMAP. We can copy!
                    bb.debug(1, "We need to use a SPDXLICENSEMAP for %s" % (license_type))
                    spdx_generic = d.getVarFlag('SPDXLICENSEMAP', license_type)
                    license_source = lic_dir
                    break
            elif os.path.isfile(os.path.join(lic_dir, license_type)):
                spdx_generic = license_type
                license_source = lic_dir
                break

        non_generic_lic = d.getVarFlag('NO_GENERIC_LICENSE', license_type)
        if spdx_generic and license_source:
            # we really should copy to generic_ + spdx_generic, however, that ends up messing the manifest
            # audit up. This should be fixed in emit_pkgdata (or, we actually got and fix all the recipes)

            lic_files_paths.append(("generic_" + license_type, os.path.join(license_source, spdx_generic),
                                    None, None))

            # The user may attempt to use NO_GENERIC_LICENSE for a generic license which doesn't make sense
            # and should not be allowed, warn the user in this case.
            if d.getVarFlag('NO_GENERIC_LICENSE', license_type):
                oe.qa.handle_error("license-no-generic",
                    "%s: %s is a generic license, please don't use NO_GENERIC_LICENSE for it." % (pn, license_type), d)

        elif non_generic_lic and non_generic_lic in lic_chksums:
            # if NO_GENERIC_LICENSE is set, we copy the license files from the fetched source
            # of the package rather than the license_source_dirs.
            lic_files_paths.append(("generic_" + license_type,
                                    os.path.join(srcdir, non_generic_lic), None, None))
            non_generic_lics[non_generic_lic] = license_type
        else:
            # Explicitly avoid the CLOSED license because this isn't generic
            if license_type != 'CLOSED':
                # And here is where we warn people that their licenses are lousy
                oe.qa.handle_error("license-exists",
                    "%s: No generic license file exists for: %s in any provider" % (pn, license_type), d)
            pass

    if not generic_directory:
        bb.fatal("COMMON_LICENSE_DIR is unset. Please set this in your distro config")

    for url in lic_files.split():
        try:
            (method, host, path, user, pswd, parm) = bb.fetch.decodeurl(url)
            if method != "file" or not path:
                raise bb.fetch.MalformedUrl()
        except bb.fetch.MalformedUrl:
            bb.fatal("%s: LIC_FILES_CHKSUM contains an invalid URL:  %s" % (d.getVar('PF'), url))
        # We want the license filename and path
        chksum = parm.get('md5', None)
        beginline = parm.get('beginline')
        endline = parm.get('endline')
        lic_chksums[path] = (chksum, beginline, endline)

    v = FindVisitor()
    try:
        v.visit_string(d.getVar('LICENSE'))
    except oe.license.InvalidLicense as exc:
        bb.fatal('%s: %s' % (d.getVar('PF'), exc))
    except SyntaxError:
        oe.qa.handle_error("license-syntax",
            "%s: Failed to parse LICENSE: %s" % (d.getVar('PF'), d.getVar('LICENSE')), d)
    # Add files from LIC_FILES_CHKSUM to list of license files
    lic_chksum_paths = defaultdict(OrderedDict)
    for path, data in sorted(lic_chksums.items()):
        lic_chksum_paths[os.path.basename(path)][data] = (os.path.join(srcdir, path), data[1], data[2])
    for basename, files in lic_chksum_paths.items():
        if len(files) == 1:
            # Don't copy again a LICENSE already handled as non-generic
            if basename in non_generic_lics:
                continue
            data = list(files.values())[0]
            lic_files_paths.append(tuple([basename] + list(data)))
        else:
            # If there are multiple different license files with identical
            # basenames we rename them to <file>.0, <file>.1, ...
            for i, data in enumerate(files.values()):
                lic_files_paths.append(tuple(["%s.%d" % (basename, i)] + list(data)))

    return lic_files_paths

def staging_copydir(c, target, dest, seendirs):
    if dest not in seendirs:
        bb.utils.mkdirhier(dest)
        seendirs.add(dest)

def copy_license_files(lic_files_paths, destdir):
    import shutil
    import errno

    bb.utils.mkdirhier(destdir)
    for (basename, path, beginline, endline) in lic_files_paths:
        try:
            src = path
            dst = os.path.join(destdir, basename)
            if os.path.exists(dst):
                os.remove(dst)
            if os.path.islink(src):
                src = os.path.realpath(src)
            canlink = os.access(src, os.W_OK) and (os.stat(src).st_dev == os.stat(destdir).st_dev) and beginline is None and endline is None
            if canlink:
                try:
                    os.link(src, dst)
                except OSError as err:
                    if err.errno == errno.EXDEV:
                        # Copy license files if hardlink is not possible even if st_dev is the
                        # same on source and destination (docker container with device-mapper?)
                        canlink = False
                    else:
                        raise
                # Only chown if we did hardlink and we're running under pseudo
                if canlink and os.environ.get('PSEUDO_DISABLED') == '0':
                    os.chown(dst,0,0)
            if not canlink:
                begin_idx = max(0, int(beginline) - 1) if beginline is not None else None
                end_idx = max(0, int(endline)) if endline is not None else None
                if begin_idx is None and end_idx is None:
                    shutil.copyfile(src, dst)
                else:
                    with open(src, 'rb') as src_f:
                        with open(dst, 'wb') as dst_f:
                            dst_f.write(b''.join(src_f.readlines()[begin_idx:end_idx]))

        except Exception as e:
            bb.warn("Could not copy license file %s to %s: %s" % (src, dst, e))

def process_dir(rootdir, directory, d, break_hardlinks = False):
    bb.debug(2, "Checking %s for binaries to process" % directory)
    if not os.path.exists(directory):
        return

    import stat

    rootdir = os.path.normpath(rootdir)
    cmd = d.expand('${CHRPATH_BIN}')
    tmpdir = os.path.normpath(d.getVar('TMPDIR', False))
    baseprefix = os.path.normpath(d.expand('${base_prefix}'))
    hostos = d.getVar("HOST_OS")

    if "linux" in hostos:
        process_file = process_file_linux
    elif "darwin" in hostos:
        process_file = process_file_darwin
    else:
        # Relocations not supported
        return

    dirs = os.listdir(directory)
    for file in dirs:
        fpath = directory + "/" + file
        fpath = os.path.normpath(fpath)
        if os.path.islink(fpath):
            # Skip symlinks
            continue

        if os.path.isdir(fpath):
            process_dir(rootdir, fpath, d, break_hardlinks = break_hardlinks)
        else:
            #bb.note("Testing %s for relocatability" % fpath)

            # We need read and write permissions for chrpath, if we don't have
            # them then set them temporarily. Take a copy of the files
            # permissions so that we can restore them afterwards.
            perms = os.stat(fpath)[stat.ST_MODE]
            if os.access(fpath, os.W_OK|os.R_OK):
                perms = None
            else:
                # Temporarily make the file writeable so we can chrpath it
                os.chmod(fpath, perms|stat.S_IRWXU)

            process_file(cmd, fpath, rootdir, baseprefix, tmpdir, d, break_hardlinks = break_hardlinks)

            if perms:
                os.chmod(fpath, perms)

