summaryrefslogtreecommitdiffstats
path: root/meta/classes-global/sstate.bbclass
diff options
context:
space:
mode:
Diffstat (limited to 'meta/classes-global/sstate.bbclass')
-rw-r--r--meta/classes-global/sstate.bbclass1390
1 files changed, 1390 insertions, 0 deletions
diff --git a/meta/classes-global/sstate.bbclass b/meta/classes-global/sstate.bbclass
new file mode 100644
index 0000000000..04539bbb99
--- /dev/null
+++ b/meta/classes-global/sstate.bbclass
@@ -0,0 +1,1390 @@
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: MIT
+#
+
+SSTATE_VERSION = "12"
+
+SSTATE_ZSTD_CLEVEL ??= "8"
+
+SSTATE_MANIFESTS ?= "${TMPDIR}/sstate-control"
+SSTATE_MANFILEPREFIX = "${SSTATE_MANIFESTS}/manifest-${SSTATE_MANMACH}-${PN}"
+
+def generate_sstatefn(spec, hash, taskname, siginfo, d):
+ if taskname is None:
+ return ""
+ extension = ".tar.zst"
+ # 8 chars reserved for siginfo
+ limit = 254 - 8
+ if siginfo:
+ limit = 254
+ extension = ".tar.zst.siginfo"
+ if not hash:
+ hash = "INVALID"
+ fn = spec + hash + "_" + taskname + extension
+ # If the filename is too long, attempt to reduce it
+ if len(fn) > limit:
+ components = spec.split(":")
+ # Fields 0,5,6 are mandatory, 1 is most useful, 2,3,4 are just for information
+ # 7 is for the separators
+ avail = (limit - len(hash + "_" + taskname + extension) - len(components[0]) - len(components[1]) - len(components[5]) - len(components[6]) - 7) // 3
+ components[2] = components[2][:avail]
+ components[3] = components[3][:avail]
+ components[4] = components[4][:avail]
+ spec = ":".join(components)
+ fn = spec + hash + "_" + taskname + extension
+ if len(fn) > limit:
+ bb.fatal("Unable to reduce sstate name to less than 255 chararacters")
+ return hash[:2] + "/" + hash[2:4] + "/" + fn
+
+SSTATE_PKGARCH = "${PACKAGE_ARCH}"
+SSTATE_PKGSPEC = "sstate:${PN}:${PACKAGE_ARCH}${TARGET_VENDOR}-${TARGET_OS}:${PV}:${PR}:${SSTATE_PKGARCH}:${SSTATE_VERSION}:"
+SSTATE_SWSPEC = "sstate:${PN}::${PV}:${PR}::${SSTATE_VERSION}:"
+SSTATE_PKGNAME = "${SSTATE_EXTRAPATH}${@generate_sstatefn(d.getVar('SSTATE_PKGSPEC'), d.getVar('BB_UNIHASH'), d.getVar('SSTATE_CURRTASK'), False, d)}"
+SSTATE_PKG = "${SSTATE_DIR}/${SSTATE_PKGNAME}"
+SSTATE_EXTRAPATH = ""
+SSTATE_EXTRAPATHWILDCARD = ""
+SSTATE_PATHSPEC = "${SSTATE_DIR}/${SSTATE_EXTRAPATHWILDCARD}*/*/${SSTATE_PKGSPEC}*_${SSTATE_PATH_CURRTASK}.tar.zst*"
+
+# explicitly make PV to depend on evaluated value of PV variable
+PV[vardepvalue] = "${PV}"
+
+# We don't want the sstate to depend on things like the distro string
+# of the system, we let the sstate paths take care of this.
+SSTATE_EXTRAPATH[vardepvalue] = ""
+SSTATE_EXTRAPATHWILDCARD[vardepvalue] = ""
+
+# Avoid docbook/sgml catalog warnings for now
+SSTATE_ALLOW_OVERLAP_FILES += "${STAGING_ETCDIR_NATIVE}/sgml ${STAGING_DATADIR_NATIVE}/sgml"
+# sdk-provides-dummy-nativesdk and nativesdk-buildtools-perl-dummy overlap for different SDKMACHINE
+SSTATE_ALLOW_OVERLAP_FILES += "${DEPLOY_DIR_RPM}/sdk_provides_dummy_nativesdk/ ${DEPLOY_DIR_IPK}/sdk-provides-dummy-nativesdk/"
+SSTATE_ALLOW_OVERLAP_FILES += "${DEPLOY_DIR_RPM}/buildtools_dummy_nativesdk/ ${DEPLOY_DIR_IPK}/buildtools-dummy-nativesdk/"
+# target-sdk-provides-dummy overlaps that allarch is disabled when multilib is used
+SSTATE_ALLOW_OVERLAP_FILES += "${COMPONENTS_DIR}/sdk-provides-dummy-target/ ${DEPLOY_DIR_RPM}/sdk_provides_dummy_target/ ${DEPLOY_DIR_IPK}/sdk-provides-dummy-target/"
+# Archive the sources for many architectures in one deploy folder
+SSTATE_ALLOW_OVERLAP_FILES += "${DEPLOY_DIR_SRC}"
+# ovmf/grub-efi/systemd-boot/intel-microcode multilib recipes can generate identical overlapping files
+SSTATE_ALLOW_OVERLAP_FILES += "${DEPLOY_DIR_IMAGE}/ovmf"
+SSTATE_ALLOW_OVERLAP_FILES += "${DEPLOY_DIR_IMAGE}/grub-efi"
+SSTATE_ALLOW_OVERLAP_FILES += "${DEPLOY_DIR_IMAGE}/systemd-boot"
+SSTATE_ALLOW_OVERLAP_FILES += "${DEPLOY_DIR_IMAGE}/microcode"
+
+SSTATE_SCAN_FILES ?= "*.la *-config *_config postinst-*"
+SSTATE_SCAN_CMD ??= 'find ${SSTATE_BUILDDIR} \( -name "${@"\" -o -name \"".join(d.getVar("SSTATE_SCAN_FILES").split())}" \) -type f'
+SSTATE_SCAN_CMD_NATIVE ??= 'grep -Irl -e ${RECIPE_SYSROOT} -e ${RECIPE_SYSROOT_NATIVE} -e ${HOSTTOOLS_DIR} ${SSTATE_BUILDDIR}'
+SSTATE_HASHEQUIV_FILEMAP ?= " \
+ populate_sysroot:*/postinst-useradd-*:${TMPDIR} \
+ populate_sysroot:*/postinst-useradd-*:${COREBASE} \
+ populate_sysroot:*/postinst-useradd-*:regex-\s(PATH|PSEUDO_IGNORE_PATHS|HOME|LOGNAME|OMP_NUM_THREADS|USER)=.*\s \
+ populate_sysroot:*/crossscripts/*:${TMPDIR} \
+ populate_sysroot:*/crossscripts/*:${COREBASE} \
+ "
+
+BB_HASHFILENAME = "False ${SSTATE_PKGSPEC} ${SSTATE_SWSPEC}"
+
+SSTATE_ARCHS_TUNEPKG ??= "${TUNE_PKGARCH}"
+SSTATE_ARCHS = " \
+ ${BUILD_ARCH} \
+ ${BUILD_ARCH}_${ORIGNATIVELSBSTRING} \
+ ${BUILD_ARCH}_${SDK_ARCH}_${SDK_OS} \
+ ${SDK_ARCH}_${SDK_OS} \
+ ${SDK_ARCH}_${SDK_ARCH}-${SDKPKGSUFFIX} \
+ allarch \
+ ${SSTATE_ARCHS_TUNEPKG} \
+ ${PACKAGE_EXTRA_ARCHS} \
+ ${MACHINE_ARCH}"
+SSTATE_ARCHS[vardepsexclude] = "ORIGNATIVELSBSTRING"
+
+SSTATE_MANMACH ?= "${SSTATE_PKGARCH}"
+
+SSTATECREATEFUNCS += "sstate_hardcode_path"
+SSTATECREATEFUNCS[vardeps] = "SSTATE_SCAN_FILES"
+SSTATEPOSTCREATEFUNCS = ""
+SSTATEPREINSTFUNCS = ""
+SSTATEPOSTUNPACKFUNCS = "sstate_hardcode_path_unpack"
+SSTATEPOSTINSTFUNCS = ""
+EXTRA_STAGING_FIXMES ?= "HOSTTOOLS_DIR"
+
+# Check whether sstate exists for tasks that support sstate and are in the
+# locked signatures file.
+SIGGEN_LOCKEDSIGS_SSTATE_EXISTS_CHECK ?= 'error'
+
+# Check whether the task's computed hash matches the task's hash in the
+# locked signatures file.
+SIGGEN_LOCKEDSIGS_TASKSIG_CHECK ?= "error"
+
+# The GnuPG key ID and passphrase to use to sign sstate archives (or unset to
+# not sign)
+SSTATE_SIG_KEY ?= ""
+SSTATE_SIG_PASSPHRASE ?= ""
+# Whether to verify the GnUPG signatures when extracting sstate archives
+SSTATE_VERIFY_SIG ?= "0"
+# List of signatures to consider valid.
+SSTATE_VALID_SIGS ??= ""
+SSTATE_VALID_SIGS[vardepvalue] = ""
+
+SSTATE_HASHEQUIV_METHOD ?= "oe.sstatesig.OEOuthashBasic"
+SSTATE_HASHEQUIV_METHOD[doc] = "The fully-qualified function used to calculate \
+ the output hash for a task, which in turn is used to determine equivalency. \
+ "
+
+SSTATE_HASHEQUIV_REPORT_TASKDATA ?= "0"
+SSTATE_HASHEQUIV_REPORT_TASKDATA[doc] = "Report additional useful data to the \
+ hash equivalency server, such as PN, PV, taskname, etc. This information \
+ is very useful for developers looking at task data, but may leak sensitive \
+ data if the equivalence server is public. \
+ "
+
+python () {
+ if bb.data.inherits_class('native', d):
+ d.setVar('SSTATE_PKGARCH', d.getVar('BUILD_ARCH', False))
+ elif bb.data.inherits_class('crosssdk', d):
+ d.setVar('SSTATE_PKGARCH', d.expand("${BUILD_ARCH}_${SDK_ARCH}_${SDK_OS}"))
+ elif bb.data.inherits_class('cross', d):
+ d.setVar('SSTATE_PKGARCH', d.expand("${BUILD_ARCH}"))
+ elif bb.data.inherits_class('nativesdk', d):
+ d.setVar('SSTATE_PKGARCH', d.expand("${SDK_ARCH}_${SDK_OS}"))
+ elif bb.data.inherits_class('cross-canadian', d):
+ d.setVar('SSTATE_PKGARCH', d.expand("${SDK_ARCH}_${PACKAGE_ARCH}"))
+ elif bb.data.inherits_class('allarch', d) and d.getVar("PACKAGE_ARCH") == "all":
+ d.setVar('SSTATE_PKGARCH', "allarch")
+ else:
+ d.setVar('SSTATE_MANMACH', d.expand("${PACKAGE_ARCH}"))
+
+ if bb.data.inherits_class('native', d) or bb.data.inherits_class('crosssdk', d) or bb.data.inherits_class('cross', d):
+ d.setVar('SSTATE_EXTRAPATH', "${NATIVELSBSTRING}/")
+ d.setVar('BB_HASHFILENAME', "True ${SSTATE_PKGSPEC} ${SSTATE_SWSPEC}")
+ d.setVar('SSTATE_EXTRAPATHWILDCARD', "${NATIVELSBSTRING}/")
+
+ unique_tasks = sorted(set((d.getVar('SSTATETASKS') or "").split()))
+ d.setVar('SSTATETASKS', " ".join(unique_tasks))
+ for task in unique_tasks:
+ d.prependVarFlag(task, 'prefuncs', "sstate_task_prefunc ")
+ d.appendVarFlag(task, 'postfuncs', " sstate_task_postfunc")
+ d.setVarFlag(task, 'network', '1')
+ d.setVarFlag(task + "_setscene", 'network', '1')
+}
+
+def sstate_init(task, d):
+ ss = {}
+ ss['task'] = task
+ ss['dirs'] = []
+ ss['plaindirs'] = []
+ ss['lockfiles'] = []
+ ss['lockfiles-shared'] = []
+ return ss
+
+def sstate_state_fromvars(d, task = None):
+ if task is None:
+ task = d.getVar('BB_CURRENTTASK')
+ if not task:
+ bb.fatal("sstate code running without task context?!")
+ task = task.replace("_setscene", "")
+
+ if task.startswith("do_"):
+ task = task[3:]
+ inputs = (d.getVarFlag("do_" + task, 'sstate-inputdirs') or "").split()
+ outputs = (d.getVarFlag("do_" + task, 'sstate-outputdirs') or "").split()
+ plaindirs = (d.getVarFlag("do_" + task, 'sstate-plaindirs') or "").split()
+ lockfiles = (d.getVarFlag("do_" + task, 'sstate-lockfile') or "").split()
+ lockfilesshared = (d.getVarFlag("do_" + task, 'sstate-lockfile-shared') or "").split()
+ interceptfuncs = (d.getVarFlag("do_" + task, 'sstate-interceptfuncs') or "").split()
+ fixmedir = d.getVarFlag("do_" + task, 'sstate-fixmedir') or ""
+ if not task or len(inputs) != len(outputs):
+ bb.fatal("sstate variables not setup correctly?!")
+
+ if task == "populate_lic":
+ d.setVar("SSTATE_PKGSPEC", "${SSTATE_SWSPEC}")
+ d.setVar("SSTATE_EXTRAPATH", "")
+ d.setVar('SSTATE_EXTRAPATHWILDCARD', "")
+
+ ss = sstate_init(task, d)
+ for i in range(len(inputs)):
+ sstate_add(ss, inputs[i], outputs[i], d)
+ ss['lockfiles'] = lockfiles
+ ss['lockfiles-shared'] = lockfilesshared
+ ss['plaindirs'] = plaindirs
+ ss['interceptfuncs'] = interceptfuncs
+ ss['fixmedir'] = fixmedir
+ return ss
+
+def sstate_add(ss, source, dest, d):
+ if not source.endswith("/"):
+ source = source + "/"
+ if not dest.endswith("/"):
+ dest = dest + "/"
+ source = os.path.normpath(source)
+ dest = os.path.normpath(dest)
+ srcbase = os.path.basename(source)
+ ss['dirs'].append([srcbase, source, dest])
+ return ss
+
+def sstate_install(ss, d):
+ import oe.path
+ import oe.sstatesig
+ import subprocess
+
+ sharedfiles = []
+ shareddirs = []
+ bb.utils.mkdirhier(d.expand("${SSTATE_MANIFESTS}"))
+
+ sstateinst = d.expand("${WORKDIR}/sstate-install-%s/" % ss['task'])
+
+ manifest, d2 = oe.sstatesig.sstate_get_manifest_filename(ss['task'], d)
+
+ if os.access(manifest, os.R_OK):
+ bb.fatal("Package already staged (%s)?!" % manifest)
+
+ d.setVar("SSTATE_INST_POSTRM", manifest + ".postrm")
+
+ locks = []
+ for lock in ss['lockfiles-shared']:
+ locks.append(bb.utils.lockfile(lock, True))
+ for lock in ss['lockfiles']:
+ locks.append(bb.utils.lockfile(lock))
+
+ for state in ss['dirs']:
+ bb.debug(2, "Staging files from %s to %s" % (state[1], state[2]))
+ for walkroot, dirs, files in os.walk(state[1]):
+ for file in files:
+ srcpath = os.path.join(walkroot, file)
+ dstpath = srcpath.replace(state[1], state[2])
+ #bb.debug(2, "Staging %s to %s" % (srcpath, dstpath))
+ sharedfiles.append(dstpath)
+ for dir in dirs:
+ srcdir = os.path.join(walkroot, dir)
+ dstdir = srcdir.replace(state[1], state[2])
+ #bb.debug(2, "Staging %s to %s" % (srcdir, dstdir))
+ if os.path.islink(srcdir):
+ sharedfiles.append(dstdir)
+ continue
+ if not dstdir.endswith("/"):
+ dstdir = dstdir + "/"
+ shareddirs.append(dstdir)
+
+ # Check the file list for conflicts against files which already exist
+ overlap_allowed = (d.getVar("SSTATE_ALLOW_OVERLAP_FILES") or "").split()
+ match = []
+ for f in sharedfiles:
+ if os.path.exists(f):
+ f = os.path.normpath(f)
+ realmatch = True
+ for w in overlap_allowed:
+ w = os.path.normpath(w)
+ if f.startswith(w):
+ realmatch = False
+ break
+ if realmatch:
+ match.append(f)
+ sstate_search_cmd = "grep -rlF '%s' %s --exclude=index-* | sed -e 's:^.*/::'" % (f, d.expand("${SSTATE_MANIFESTS}"))
+ search_output = subprocess.Popen(sstate_search_cmd, shell=True, stdout=subprocess.PIPE).communicate()[0]
+ if search_output:
+ match.append(" (matched in %s)" % search_output.decode('utf-8').rstrip())
+ else:
+ match.append(" (not matched to any task)")
+ if match:
+ bb.fatal("Recipe %s is trying to install files into a shared " \
+ "area when those files already exist. The files and the manifests listing " \
+ "them are:\n %s\n"
+ "Please adjust the recipes so only one recipe provides a given file. " % \
+ (d.getVar('PN'), "\n ".join(match)))
+
+ if ss['fixmedir'] and os.path.exists(ss['fixmedir'] + "/fixmepath.cmd"):
+ sharedfiles.append(ss['fixmedir'] + "/fixmepath.cmd")
+ sharedfiles.append(ss['fixmedir'] + "/fixmepath")
+
+ # Write out the manifest
+ f = open(manifest, "w")
+ for file in sharedfiles:
+ f.write(file + "\n")
+
+ # We want to ensure that directories appear at the end of the manifest
+ # so that when we test to see if they should be deleted any contents
+ # added by the task will have been removed first.
+ dirs = sorted(shareddirs, key=len)
+ # Must remove children first, which will have a longer path than the parent
+ for di in reversed(dirs):
+ f.write(di + "\n")
+ f.close()
+
+ # Append to the list of manifests for this PACKAGE_ARCH
+
+ i = d2.expand("${SSTATE_MANIFESTS}/index-${SSTATE_MANMACH}")
+ l = bb.utils.lockfile(i + ".lock")
+ filedata = d.getVar("STAMP") + " " + d2.getVar("SSTATE_MANFILEPREFIX") + " " + d.getVar("WORKDIR") + "\n"
+ manifests = []
+ if os.path.exists(i):
+ with open(i, "r") as f:
+ manifests = f.readlines()
+ # We append new entries, we don't remove older entries which may have the same
+ # manifest name but different versions from stamp/workdir. See below.
+ if filedata not in manifests:
+ with open(i, "a+") as f:
+ f.write(filedata)
+ bb.utils.unlockfile(l)
+
+ # Run the actual file install
+ for state in ss['dirs']:
+ if os.path.exists(state[1]):
+ oe.path.copyhardlinktree(state[1], state[2])
+
+ for postinst in (d.getVar('SSTATEPOSTINSTFUNCS') or '').split():
+ # All hooks should run in the SSTATE_INSTDIR
+ bb.build.exec_func(postinst, d, (sstateinst,))
+
+ for lock in locks:
+ bb.utils.unlockfile(lock)
+
+sstate_install[vardepsexclude] += "SSTATE_ALLOW_OVERLAP_FILES SSTATE_MANMACH SSTATE_MANFILEPREFIX"
+sstate_install[vardeps] += "${SSTATEPOSTINSTFUNCS}"
+
+def sstate_installpkg(ss, d):
+ from oe.gpg_sign import get_signer
+
+ sstateinst = d.expand("${WORKDIR}/sstate-install-%s/" % ss['task'])
+ d.setVar("SSTATE_CURRTASK", ss['task'])
+ sstatefetch = d.getVar('SSTATE_PKGNAME')
+ sstatepkg = d.getVar('SSTATE_PKG')
+ verify_sig = bb.utils.to_boolean(d.getVar("SSTATE_VERIFY_SIG"), False)
+
+ if not os.path.exists(sstatepkg) or (verify_sig and not os.path.exists(sstatepkg + '.sig')):
+ pstaging_fetch(sstatefetch, d)
+
+ if not os.path.isfile(sstatepkg):
+ bb.note("Sstate package %s does not exist" % sstatepkg)
+ return False
+
+ sstate_clean(ss, d)
+
+ d.setVar('SSTATE_INSTDIR', sstateinst)
+
+ if verify_sig:
+ if not os.path.isfile(sstatepkg + '.sig'):
+ bb.warn("No signature file for sstate package %s, skipping acceleration..." % sstatepkg)
+ return False
+ signer = get_signer(d, 'local')
+ if not signer.verify(sstatepkg + '.sig', d.getVar("SSTATE_VALID_SIGS")):
+ bb.warn("Cannot verify signature on sstate package %s, skipping acceleration..." % sstatepkg)
+ return False
+
+ # Empty sstateinst directory, ensure its clean
+ if os.path.exists(sstateinst):
+ oe.path.remove(sstateinst)
+ bb.utils.mkdirhier(sstateinst)
+
+ sstateinst = d.getVar("SSTATE_INSTDIR")
+ d.setVar('SSTATE_FIXMEDIR', ss['fixmedir'])
+
+ for f in (d.getVar('SSTATEPREINSTFUNCS') or '').split() + ['sstate_unpack_package']:
+ # All hooks should run in the SSTATE_INSTDIR
+ bb.build.exec_func(f, d, (sstateinst,))
+
+ return sstate_installpkgdir(ss, d)
+
+def sstate_installpkgdir(ss, d):
+ import oe.path
+ import subprocess
+
+ sstateinst = d.getVar("SSTATE_INSTDIR")
+ d.setVar('SSTATE_FIXMEDIR', ss['fixmedir'])
+
+ for f in (d.getVar('SSTATEPOSTUNPACKFUNCS') or '').split():
+ # All hooks should run in the SSTATE_INSTDIR
+ bb.build.exec_func(f, d, (sstateinst,))
+
+ def prepdir(dir):
+ # remove dir if it exists, ensure any parent directories do exist
+ if os.path.exists(dir):
+ oe.path.remove(dir)
+ bb.utils.mkdirhier(dir)
+ oe.path.remove(dir)
+
+ for state in ss['dirs']:
+ prepdir(state[1])
+ bb.utils.rename(sstateinst + state[0], state[1])
+ sstate_install(ss, d)
+
+ for plain in ss['plaindirs']:
+ workdir = d.getVar('WORKDIR')
+ sharedworkdir = os.path.join(d.getVar('TMPDIR'), "work-shared")
+ src = sstateinst + "/" + plain.replace(workdir, '')
+ if sharedworkdir in plain:
+ src = sstateinst + "/" + plain.replace(sharedworkdir, '')
+ dest = plain
+ bb.utils.mkdirhier(src)
+ prepdir(dest)
+ bb.utils.rename(src, dest)
+
+ return True
+
+python sstate_hardcode_path_unpack () {
+ # Fixup hardcoded paths
+ #
+ # Note: The logic below must match the reverse logic in
+ # sstate_hardcode_path(d)
+ import subprocess
+
+ sstateinst = d.getVar('SSTATE_INSTDIR')
+ sstatefixmedir = d.getVar('SSTATE_FIXMEDIR')
+ fixmefn = sstateinst + "fixmepath"
+ if os.path.isfile(fixmefn):
+ staging_target = d.getVar('RECIPE_SYSROOT')
+ staging_host = d.getVar('RECIPE_SYSROOT_NATIVE')
+
+ if bb.data.inherits_class('native', d) or bb.data.inherits_class('cross-canadian', d):
+ sstate_sed_cmd = "sed -i -e 's:FIXMESTAGINGDIRHOST:%s:g'" % (staging_host)
+ elif bb.data.inherits_class('cross', d) or bb.data.inherits_class('crosssdk', d):
+ sstate_sed_cmd = "sed -i -e 's:FIXMESTAGINGDIRTARGET:%s:g; s:FIXMESTAGINGDIRHOST:%s:g'" % (staging_target, staging_host)
+ else:
+ sstate_sed_cmd = "sed -i -e 's:FIXMESTAGINGDIRTARGET:%s:g'" % (staging_target)
+
+ extra_staging_fixmes = d.getVar('EXTRA_STAGING_FIXMES') or ''
+ for fixmevar in extra_staging_fixmes.split():
+ fixme_path = d.getVar(fixmevar)
+ sstate_sed_cmd += " -e 's:FIXME_%s:%s:g'" % (fixmevar, fixme_path)
+
+ # Add sstateinst to each filename in fixmepath, use xargs to efficiently call sed
+ sstate_hardcode_cmd = "sed -e 's:^:%s:g' %s | xargs %s" % (sstateinst, fixmefn, sstate_sed_cmd)
+
+ # Defer do_populate_sysroot relocation command
+ if sstatefixmedir:
+ bb.utils.mkdirhier(sstatefixmedir)
+ with open(sstatefixmedir + "/fixmepath.cmd", "w") as f:
+ sstate_hardcode_cmd = sstate_hardcode_cmd.replace(fixmefn, sstatefixmedir + "/fixmepath")
+ sstate_hardcode_cmd = sstate_hardcode_cmd.replace(sstateinst, "FIXMEFINALSSTATEINST")
+ sstate_hardcode_cmd = sstate_hardcode_cmd.replace(staging_host, "FIXMEFINALSSTATEHOST")
+ sstate_hardcode_cmd = sstate_hardcode_cmd.replace(staging_target, "FIXMEFINALSSTATETARGET")
+ f.write(sstate_hardcode_cmd)
+ bb.utils.copyfile(fixmefn, sstatefixmedir + "/fixmepath")
+ return
+
+ bb.note("Replacing fixme paths in sstate package: %s" % (sstate_hardcode_cmd))
+ subprocess.check_call(sstate_hardcode_cmd, shell=True)
+
+ # Need to remove this or we'd copy it into the target directory and may
+ # conflict with another writer
+ os.remove(fixmefn)
+}
+
+def sstate_clean_cachefile(ss, d):
+ import oe.path
+
+ if d.getVarFlag('do_%s' % ss['task'], 'task'):
+ d.setVar("SSTATE_PATH_CURRTASK", ss['task'])
+ sstatepkgfile = d.getVar('SSTATE_PATHSPEC')
+ bb.note("Removing %s" % sstatepkgfile)
+ oe.path.remove(sstatepkgfile)
+
+def sstate_clean_cachefiles(d):
+ for task in (d.getVar('SSTATETASKS') or "").split():
+ ld = d.createCopy()
+ ss = sstate_state_fromvars(ld, task)
+ sstate_clean_cachefile(ss, ld)
+
+def sstate_clean_manifest(manifest, d, canrace=False, prefix=None):
+ import oe.path
+
+ mfile = open(manifest)
+ entries = mfile.readlines()
+ mfile.close()
+
+ for entry in entries:
+ entry = entry.strip()
+ if prefix and not entry.startswith("/"):
+ entry = prefix + "/" + entry
+ bb.debug(2, "Removing manifest: %s" % entry)
+ # We can race against another package populating directories as we're removing them
+ # so we ignore errors here.
+ try:
+ if entry.endswith("/"):
+ if os.path.islink(entry[:-1]):
+ os.remove(entry[:-1])
+ elif os.path.exists(entry) and len(os.listdir(entry)) == 0 and not canrace:
+ # Removing directories whilst builds are in progress exposes a race. Only
+ # do it in contexts where it is safe to do so.
+ os.rmdir(entry[:-1])
+ else:
+ os.remove(entry)
+ except OSError:
+ pass
+
+ postrm = manifest + ".postrm"
+ if os.path.exists(manifest + ".postrm"):
+ import subprocess
+ os.chmod(postrm, 0o755)
+ subprocess.check_call(postrm, shell=True)
+ oe.path.remove(postrm)
+
+ oe.path.remove(manifest)
+
+def sstate_clean(ss, d):
+ import oe.path
+ import glob
+
+ d2 = d.createCopy()
+ stamp_clean = d.getVar("STAMPCLEAN")
+ extrainf = d.getVarFlag("do_" + ss['task'], 'stamp-extra-info')
+ if extrainf:
+ d2.setVar("SSTATE_MANMACH", extrainf)
+ wildcard_stfile = "%s.do_%s*.%s" % (stamp_clean, ss['task'], extrainf)
+ else:
+ wildcard_stfile = "%s.do_%s*" % (stamp_clean, ss['task'])
+
+ manifest = d2.expand("${SSTATE_MANFILEPREFIX}.%s" % ss['task'])
+
+ if os.path.exists(manifest):
+ locks = []
+ for lock in ss['lockfiles-shared']:
+ locks.append(bb.utils.lockfile(lock))
+ for lock in ss['lockfiles']:
+ locks.append(bb.utils.lockfile(lock))
+
+ sstate_clean_manifest(manifest, d, canrace=True)
+
+ for lock in locks:
+ bb.utils.unlockfile(lock)
+
+ # Remove the current and previous stamps, but keep the sigdata.
+ #
+ # The glob() matches do_task* which may match multiple tasks, for
+ # example: do_package and do_package_write_ipk, so we need to
+ # exactly match *.do_task.* and *.do_task_setscene.*
+ rm_stamp = '.do_%s.' % ss['task']
+ rm_setscene = '.do_%s_setscene.' % ss['task']
+ # For BB_SIGNATURE_HANDLER = "noop"
+ rm_nohash = ".do_%s" % ss['task']
+ for stfile in glob.glob(wildcard_stfile):
+ # Keep the sigdata
+ if ".sigdata." in stfile or ".sigbasedata." in stfile:
+ continue
+ # Preserve taint files in the stamps directory
+ if stfile.endswith('.taint'):
+ continue
+ if rm_stamp in stfile or rm_setscene in stfile or \
+ stfile.endswith(rm_nohash):
+ oe.path.remove(stfile)
+
+sstate_clean[vardepsexclude] = "SSTATE_MANFILEPREFIX"
+
+CLEANFUNCS += "sstate_cleanall"
+
+python sstate_cleanall() {
+ bb.note("Removing shared state for package %s" % d.getVar('PN'))
+
+ manifest_dir = d.getVar('SSTATE_MANIFESTS')
+ if not os.path.exists(manifest_dir):
+ return
+
+ tasks = d.getVar('SSTATETASKS').split()
+ for name in tasks:
+ ld = d.createCopy()
+ shared_state = sstate_state_fromvars(ld, name)
+ sstate_clean(shared_state, ld)
+}
+
+python sstate_hardcode_path () {
+ import subprocess, platform
+
+ # Need to remove hardcoded paths and fix these when we install the
+ # staging packages.
+ #
+ # Note: the logic in this function needs to match the reverse logic
+ # in sstate_installpkg(ss, d)
+
+ staging_target = d.getVar('RECIPE_SYSROOT')
+ staging_host = d.getVar('RECIPE_SYSROOT_NATIVE')
+ sstate_builddir = d.getVar('SSTATE_BUILDDIR')
+
+ sstate_sed_cmd = "sed -i -e 's:%s:FIXMESTAGINGDIRHOST:g'" % staging_host
+ if bb.data.inherits_class('native', d) or bb.data.inherits_class('cross-canadian', d):
+ sstate_grep_cmd = "grep -l -e '%s'" % (staging_host)
+ elif bb.data.inherits_class('cross', d) or bb.data.inherits_class('crosssdk', d):
+ sstate_grep_cmd = "grep -l -e '%s' -e '%s'" % (staging_target, staging_host)
+ sstate_sed_cmd += " -e 's:%s:FIXMESTAGINGDIRTARGET:g'" % staging_target
+ else:
+ sstate_grep_cmd = "grep -l -e '%s' -e '%s'" % (staging_target, staging_host)
+ sstate_sed_cmd += " -e 's:%s:FIXMESTAGINGDIRTARGET:g'" % staging_target
+
+ extra_staging_fixmes = d.getVar('EXTRA_STAGING_FIXMES') or ''
+ for fixmevar in extra_staging_fixmes.split():
+ fixme_path = d.getVar(fixmevar)
+ sstate_sed_cmd += " -e 's:%s:FIXME_%s:g'" % (fixme_path, fixmevar)
+ sstate_grep_cmd += " -e '%s'" % (fixme_path)
+
+ fixmefn = sstate_builddir + "fixmepath"
+
+ sstate_scan_cmd = d.getVar('SSTATE_SCAN_CMD')
+ sstate_filelist_cmd = "tee %s" % (fixmefn)
+
+ # fixmepath file needs relative paths, drop sstate_builddir prefix
+ sstate_filelist_relative_cmd = "sed -i -e 's:^%s::g' %s" % (sstate_builddir, fixmefn)
+
+ xargs_no_empty_run_cmd = '--no-run-if-empty'
+ if platform.system() == 'Darwin':
+ xargs_no_empty_run_cmd = ''
+
+ # Limit the fixpaths and sed operations based on the initial grep search
+ # This has the side effect of making sure the vfs cache is hot
+ sstate_hardcode_cmd = "%s | xargs %s | %s | xargs %s %s" % (sstate_scan_cmd, sstate_grep_cmd, sstate_filelist_cmd, xargs_no_empty_run_cmd, sstate_sed_cmd)
+
+ bb.note("Removing hardcoded paths from sstate package: '%s'" % (sstate_hardcode_cmd))
+ subprocess.check_output(sstate_hardcode_cmd, shell=True, cwd=sstate_builddir)
+
+ # If the fixmefn is empty, remove it..
+ if os.stat(fixmefn).st_size == 0:
+ os.remove(fixmefn)
+ else:
+ bb.note("Replacing absolute paths in fixmepath file: '%s'" % (sstate_filelist_relative_cmd))
+ subprocess.check_output(sstate_filelist_relative_cmd, shell=True)
+}
+
+def sstate_package(ss, d):
+ import oe.path
+ import time
+
+ tmpdir = d.getVar('TMPDIR')
+
+ fixtime = False
+ if ss['task'] == "package":
+ fixtime = True
+
+ def fixtimestamp(root, path):
+ f = os.path.join(root, path)
+ if os.lstat(f).st_mtime > sde:
+ os.utime(f, (sde, sde), follow_symlinks=False)
+
+ sstatebuild = d.expand("${WORKDIR}/sstate-build-%s/" % ss['task'])
+ sde = int(d.getVar("SOURCE_DATE_EPOCH") or time.time())
+ d.setVar("SSTATE_CURRTASK", ss['task'])
+ bb.utils.remove(sstatebuild, recurse=True)
+ bb.utils.mkdirhier(sstatebuild)
+ for state in ss['dirs']:
+ if not os.path.exists(state[1]):
+ continue
+ srcbase = state[0].rstrip("/").rsplit('/', 1)[0]
+ # Find and error for absolute symlinks. We could attempt to relocate but its not
+ # clear where the symlink is relative to in this context. We could add that markup
+ # to sstate tasks but there aren't many of these so better just avoid them entirely.
+ for walkroot, dirs, files in os.walk(state[1]):
+ for file in files + dirs:
+ if fixtime:
+ fixtimestamp(walkroot, file)
+ srcpath = os.path.join(walkroot, file)
+ if not os.path.islink(srcpath):
+ continue
+ link = os.readlink(srcpath)
+ if not os.path.isabs(link):
+ continue
+ if not link.startswith(tmpdir):
+ continue
+ bb.error("sstate found an absolute path symlink %s pointing at %s. Please replace this with a relative link." % (srcpath, link))
+ bb.debug(2, "Preparing tree %s for packaging at %s" % (state[1], sstatebuild + state[0]))
+ bb.utils.rename(state[1], sstatebuild + state[0])
+
+ workdir = d.getVar('WORKDIR')
+ sharedworkdir = os.path.join(d.getVar('TMPDIR'), "work-shared")
+ for plain in ss['plaindirs']:
+ pdir = plain.replace(workdir, sstatebuild)
+ if sharedworkdir in plain:
+ pdir = plain.replace(sharedworkdir, sstatebuild)
+ bb.utils.mkdirhier(plain)
+ bb.utils.mkdirhier(pdir)
+ bb.utils.rename(plain, pdir)
+ if fixtime:
+ fixtimestamp(pdir, "")
+ for walkroot, dirs, files in os.walk(pdir):
+ for file in files + dirs:
+ fixtimestamp(walkroot, file)
+
+ d.setVar('SSTATE_BUILDDIR', sstatebuild)
+ d.setVar('SSTATE_INSTDIR', sstatebuild)
+
+ if d.getVar('SSTATE_SKIP_CREATION') == '1':
+ return
+
+ sstate_create_package = ['sstate_report_unihash', 'sstate_create_and_sign_package']
+
+ for f in (d.getVar('SSTATECREATEFUNCS') or '').split() + \
+ sstate_create_package + \
+ (d.getVar('SSTATEPOSTCREATEFUNCS') or '').split():
+ # All hooks should run in SSTATE_BUILDDIR.
+ bb.build.exec_func(f, d, (sstatebuild,))
+
+ # SSTATE_PKG may have been changed by sstate_report_unihash
+ siginfo = d.getVar('SSTATE_PKG') + ".siginfo"
+ if not os.path.exists(siginfo):
+ bb.siggen.dump_this_task(siginfo, d)
+ else:
+ try:
+ os.utime(siginfo, None)
+ except PermissionError:
+ pass
+ except OSError as e:
+ # Handle read-only file systems gracefully
+ import errno
+ if e.errno != errno.EROFS:
+ raise e
+
+ return
+
+sstate_package[vardepsexclude] += "SSTATE_SIG_KEY"
+
+def pstaging_fetch(sstatefetch, d):
+ import bb.fetch2
+
+ # Only try and fetch if the user has configured a mirror
+ mirrors = d.getVar('SSTATE_MIRRORS')
+ if not mirrors:
+ return
+
+ # Copy the data object and override DL_DIR and SRC_URI
+ localdata = bb.data.createCopy(d)
+
+ dldir = localdata.expand("${SSTATE_DIR}")
+ bb.utils.mkdirhier(dldir)
+
+ localdata.delVar('MIRRORS')
+ localdata.setVar('FILESPATH', dldir)
+ localdata.setVar('DL_DIR', dldir)
+ localdata.setVar('PREMIRRORS', mirrors)
+
+ # if BB_NO_NETWORK is set but we also have SSTATE_MIRROR_ALLOW_NETWORK,
+ # we'll want to allow network access for the current set of fetches.
+ if bb.utils.to_boolean(localdata.getVar('BB_NO_NETWORK')) and \
+ bb.utils.to_boolean(localdata.getVar('SSTATE_MIRROR_ALLOW_NETWORK')):
+ localdata.delVar('BB_NO_NETWORK')
+
+ # Try a fetch from the sstate mirror, if it fails just return and
+ # we will build the package
+ uris = ['file://{0};downloadfilename={0}'.format(sstatefetch),
+ 'file://{0}.siginfo;downloadfilename={0}.siginfo'.format(sstatefetch)]
+ if bb.utils.to_boolean(d.getVar("SSTATE_VERIFY_SIG"), False):
+ uris += ['file://{0}.sig;downloadfilename={0}.sig'.format(sstatefetch)]
+
+ for srcuri in uris:
+ localdata.delVar('SRC_URI')
+ localdata.setVar('SRC_URI', srcuri)
+ try:
+ fetcher = bb.fetch2.Fetch([srcuri], localdata, cache=False)
+ fetcher.checkstatus()
+ fetcher.download()
+
+ except bb.fetch2.BBFetchException:
+ pass
+
+def sstate_setscene(d):
+ shared_state = sstate_state_fromvars(d)
+ accelerate = sstate_installpkg(shared_state, d)
+ if not accelerate:
+ msg = "No sstate archive obtainable, will run full task instead."
+ bb.warn(msg)
+ raise bb.BBHandledException(msg)
+
+python sstate_task_prefunc () {
+ shared_state = sstate_state_fromvars(d)
+ sstate_clean(shared_state, d)
+}
+sstate_task_prefunc[dirs] = "${WORKDIR}"
+
+python sstate_task_postfunc () {
+ shared_state = sstate_state_fromvars(d)
+
+ for intercept in shared_state['interceptfuncs']:
+ bb.build.exec_func(intercept, d, (d.getVar("WORKDIR"),))
+
+ omask = os.umask(0o002)
+ if omask != 0o002:
+ bb.note("Using umask 0o002 (not %0o) for sstate packaging" % omask)
+ sstate_package(shared_state, d)
+ os.umask(omask)
+
+ sstateinst = d.getVar("SSTATE_INSTDIR")
+ d.setVar('SSTATE_FIXMEDIR', shared_state['fixmedir'])
+
+ sstate_installpkgdir(shared_state, d)
+
+ bb.utils.remove(d.getVar("SSTATE_BUILDDIR"), recurse=True)
+}
+sstate_task_postfunc[dirs] = "${WORKDIR}"
+
+# Create a sstate package
+# If enabled, sign the package.
+# Package and signature are created in a sub-directory
+# and renamed in place once created.
+python sstate_create_and_sign_package () {
+ from pathlib import Path
+
+ # Best effort touch
+ def touch(file):
+ try:
+ file.touch()
+ except:
+ pass
+
+ def update_file(src, dst, force=False):
+ if dst.is_symlink() and not dst.exists():
+ force=True
+ try:
+ # This relies on that src is a temporary file that can be renamed
+ # or left as is.
+ if force:
+ src.rename(dst)
+ else:
+ os.link(src, dst)
+ return True
+ except:
+ pass
+
+ if dst.exists():
+ touch(dst)
+
+ return False
+
+ sign_pkg = (
+ bb.utils.to_boolean(d.getVar("SSTATE_VERIFY_SIG")) and
+ bool(d.getVar("SSTATE_SIG_KEY"))
+ )
+
+ sstate_pkg = Path(d.getVar("SSTATE_PKG"))
+ sstate_pkg_sig = Path(str(sstate_pkg) + ".sig")
+ if sign_pkg:
+ if sstate_pkg.exists() and sstate_pkg_sig.exists():
+ touch(sstate_pkg)
+ touch(sstate_pkg_sig)
+ return
+ else:
+ if sstate_pkg.exists():
+ touch(sstate_pkg)
+ return
+
+ # Create the required sstate directory if it is not present.
+ if not sstate_pkg.parent.is_dir():
+ with bb.utils.umask(0o002):
+ bb.utils.mkdirhier(str(sstate_pkg.parent))
+
+ if sign_pkg:
+ from tempfile import TemporaryDirectory
+ with TemporaryDirectory(dir=sstate_pkg.parent) as tmp_dir:
+ tmp_pkg = Path(tmp_dir) / sstate_pkg.name
+ d.setVar("TMP_SSTATE_PKG", str(tmp_pkg))
+ bb.build.exec_func('sstate_archive_package', d)
+
+ from oe.gpg_sign import get_signer
+ signer = get_signer(d, 'local')
+ signer.detach_sign(str(tmp_pkg), d.getVar('SSTATE_SIG_KEY'), None,
+ d.getVar('SSTATE_SIG_PASSPHRASE'), armor=False)
+
+ tmp_pkg_sig = Path(tmp_dir) / sstate_pkg_sig.name
+ if not update_file(tmp_pkg_sig, sstate_pkg_sig):
+ # If the created signature file could not be copied into place,
+ # then we should not use the sstate package either.
+ return
+
+ # If the .sig file was updated, then the sstate package must also
+ # be updated.
+ update_file(tmp_pkg, sstate_pkg, force=True)
+ else:
+ from tempfile import NamedTemporaryFile
+ with NamedTemporaryFile(prefix=sstate_pkg.name, dir=sstate_pkg.parent) as tmp_pkg_fd:
+ tmp_pkg = tmp_pkg_fd.name
+ d.setVar("TMP_SSTATE_PKG", str(tmp_pkg))
+ bb.build.exec_func('sstate_archive_package',d)
+ update_file(tmp_pkg, sstate_pkg)
+ # update_file() may have renamed tmp_pkg, which must exist when the
+ # NamedTemporaryFile() context handler ends.
+ touch(Path(tmp_pkg))
+
+}
+
+# Shell function to generate a sstate package from a directory
+# set as SSTATE_BUILDDIR. Will be run from within SSTATE_BUILDDIR.
+# The calling function handles moving the sstate package into the final
+# destination.
+sstate_archive_package () {
+ OPT="-cS"
+ ZSTD="zstd -${SSTATE_ZSTD_CLEVEL} -T${ZSTD_THREADS}"
+ # Use pzstd if available
+ if [ -x "$(command -v pzstd)" ]; then
+ ZSTD="pzstd -${SSTATE_ZSTD_CLEVEL} -p ${ZSTD_THREADS}"
+ fi
+
+ # Need to handle empty directories
+ if [ "$(ls -A)" ]; then
+ set +e
+ tar -I "$ZSTD" $OPT -f ${TMP_SSTATE_PKG} *
+ ret=$?
+ if [ $ret -ne 0 ] && [ $ret -ne 1 ]; then
+ exit 1
+ fi
+ set -e
+ else
+ tar -I "$ZSTD" $OPT --file=${TMP_SSTATE_PKG} --files-from=/dev/null
+ fi
+ chmod 0664 ${TMP_SSTATE_PKG}
+}
+
+
+python sstate_report_unihash() {
+ report_unihash = getattr(bb.parse.siggen, 'report_unihash', None)
+
+ if report_unihash:
+ ss = sstate_state_fromvars(d)
+ report_unihash(os.getcwd(), ss['task'], d)
+}
+
+#
+# Shell function to decompress and prepare a package for installation
+# Will be run from within SSTATE_INSTDIR.
+#
+sstate_unpack_package () {
+ ZSTD="zstd -T${ZSTD_THREADS}"
+ # Use pzstd if available
+ if [ -x "$(command -v pzstd)" ]; then
+ ZSTD="pzstd -p ${ZSTD_THREADS}"
+ fi
+
+ tar -I "$ZSTD" -xvpf ${SSTATE_PKG}
+ # update .siginfo atime on local/NFS mirror if it is a symbolic link
+ [ ! -h ${SSTATE_PKG}.siginfo ] || [ ! -e ${SSTATE_PKG}.siginfo ] || touch -a ${SSTATE_PKG}.siginfo 2>/dev/null || true
+ # update each symbolic link instead of any referenced file
+ touch --no-dereference ${SSTATE_PKG} 2>/dev/null || true
+ [ ! -e ${SSTATE_PKG}.sig ] || touch --no-dereference ${SSTATE_PKG}.sig 2>/dev/null || true
+ [ ! -e ${SSTATE_PKG}.siginfo ] || touch --no-dereference ${SSTATE_PKG}.siginfo 2>/dev/null || true
+}
+
+BB_HASHCHECK_FUNCTION = "sstate_checkhashes"
+
+def sstate_checkhashes(sq_data, d, siginfo=False, currentcount=0, summary=True, **kwargs):
+ import itertools
+
+ found = set()
+ missed = set()
+
+ def gethash(task):
+ return sq_data['unihash'][task]
+
+ def getpathcomponents(task, d):
+ # Magic data from BB_HASHFILENAME
+ splithashfn = sq_data['hashfn'][task].split(" ")
+ spec = splithashfn[1]
+ if splithashfn[0] == "True":
+ extrapath = d.getVar("NATIVELSBSTRING") + "/"
+ else:
+ extrapath = ""
+
+ tname = bb.runqueue.taskname_from_tid(task)[3:]
+
+ if tname in ["fetch", "unpack", "patch", "populate_lic", "preconfigure"] and splithashfn[2]:
+ spec = splithashfn[2]
+ extrapath = ""
+
+ return spec, extrapath, tname
+
+ def getsstatefile(tid, siginfo, d):
+ spec, extrapath, tname = getpathcomponents(tid, d)
+ return extrapath + generate_sstatefn(spec, gethash(tid), tname, siginfo, d)
+
+ for tid in sq_data['hash']:
+
+ sstatefile = d.expand("${SSTATE_DIR}/" + getsstatefile(tid, siginfo, d))
+
+ if os.path.exists(sstatefile):
+ oe.utils.touch(sstatefile)
+ found.add(tid)
+ bb.debug(2, "SState: Found valid sstate file %s" % sstatefile)
+ else:
+ missed.add(tid)
+ bb.debug(2, "SState: Looked for but didn't find file %s" % sstatefile)
+
+ foundLocal = len(found)
+ mirrors = d.getVar("SSTATE_MIRRORS")
+ if mirrors:
+ # Copy the data object and override DL_DIR and SRC_URI
+ localdata = bb.data.createCopy(d)
+
+ dldir = localdata.expand("${SSTATE_DIR}")
+ localdata.delVar('MIRRORS')
+ localdata.setVar('FILESPATH', dldir)
+ localdata.setVar('DL_DIR', dldir)
+ localdata.setVar('PREMIRRORS', mirrors)
+
+ bb.debug(2, "SState using premirror of: %s" % mirrors)
+
+ # if BB_NO_NETWORK is set but we also have SSTATE_MIRROR_ALLOW_NETWORK,
+ # we'll want to allow network access for the current set of fetches.
+ if bb.utils.to_boolean(localdata.getVar('BB_NO_NETWORK')) and \
+ bb.utils.to_boolean(localdata.getVar('SSTATE_MIRROR_ALLOW_NETWORK')):
+ localdata.delVar('BB_NO_NETWORK')
+
+ from bb.fetch2 import FetchConnectionCache
+ def checkstatus_init():
+ while not connection_cache_pool.full():
+ connection_cache_pool.put(FetchConnectionCache())
+
+ def checkstatus_end():
+ while not connection_cache_pool.empty():
+ connection_cache = connection_cache_pool.get()
+ connection_cache.close_connections()
+
+ def checkstatus(arg):
+ (tid, sstatefile) = arg
+
+ connection_cache = connection_cache_pool.get()
+ localdata2 = bb.data.createCopy(localdata)
+ srcuri = "file://" + sstatefile
+ localdata2.setVar('SRC_URI', srcuri)
+ bb.debug(2, "SState: Attempting to fetch %s" % srcuri)
+
+ import traceback
+
+ try:
+ fetcher = bb.fetch2.Fetch(srcuri.split(), localdata2,
+ connection_cache=connection_cache)
+ fetcher.checkstatus()
+ bb.debug(2, "SState: Successful fetch test for %s" % srcuri)
+ found.add(tid)
+ missed.remove(tid)
+ except bb.fetch2.FetchError as e:
+ bb.debug(2, "SState: Unsuccessful fetch test for %s (%s)\n%s" % (srcuri, repr(e), traceback.format_exc()))
+ except Exception as e:
+ bb.error("SState: cannot test %s: %s\n%s" % (srcuri, repr(e), traceback.format_exc()))
+
+ connection_cache_pool.put(connection_cache)
+
+ if progress:
+ bb.event.fire(bb.event.ProcessProgress(msg, next(cnt_tasks_done)), d)
+ bb.event.check_for_interrupts(d)
+
+ tasklist = []
+ for tid in missed:
+ sstatefile = d.expand(getsstatefile(tid, siginfo, d))
+ tasklist.append((tid, sstatefile))
+
+ if tasklist:
+ nproc = min(int(d.getVar("BB_NUMBER_THREADS")), len(tasklist))
+
+ ## thread-safe counter
+ cnt_tasks_done = itertools.count(start = 1)
+ progress = len(tasklist) >= 100
+ if progress:
+ msg = "Checking sstate mirror object availability"
+ bb.event.fire(bb.event.ProcessStarted(msg, len(tasklist)), d)
+
+ # Have to setup the fetcher environment here rather than in each thread as it would race
+ fetcherenv = bb.fetch2.get_fetcher_environment(d)
+ with bb.utils.environment(**fetcherenv):
+ bb.event.enable_threadlock()
+ import concurrent.futures
+ from queue import Queue
+ connection_cache_pool = Queue(nproc)
+ checkstatus_init()
+ with concurrent.futures.ThreadPoolExecutor(max_workers=nproc) as executor:
+ executor.map(checkstatus, tasklist.copy())
+ checkstatus_end()
+ bb.event.disable_threadlock()
+
+ if progress:
+ bb.event.fire(bb.event.ProcessFinished(msg), d)
+
+ inheritlist = d.getVar("INHERIT")
+ if "toaster" in inheritlist:
+ evdata = {'missed': [], 'found': []};
+ for tid in missed:
+ sstatefile = d.expand(getsstatefile(tid, False, d))
+ evdata['missed'].append((bb.runqueue.fn_from_tid(tid), bb.runqueue.taskname_from_tid(tid), gethash(tid), sstatefile ) )
+ for tid in found:
+ sstatefile = d.expand(getsstatefile(tid, False, d))
+ evdata['found'].append((bb.runqueue.fn_from_tid(tid), bb.runqueue.taskname_from_tid(tid), gethash(tid), sstatefile ) )
+ bb.event.fire(bb.event.MetadataEvent("MissedSstate", evdata), d)
+
+ if summary:
+ # Print some summary statistics about the current task completion and how much sstate
+ # reuse there was. Avoid divide by zero errors.
+ total = len(sq_data['hash'])
+ complete = 0
+ if currentcount:
+ complete = (len(found) + currentcount) / (total + currentcount) * 100
+ match = 0
+ if total:
+ match = len(found) / total * 100
+ bb.plain("Sstate summary: Wanted %d Local %d Mirrors %d Missed %d Current %d (%d%% match, %d%% complete)" %
+ (total, foundLocal, len(found)-foundLocal, len(missed), currentcount, match, complete))
+
+ if hasattr(bb.parse.siggen, "checkhashes"):
+ bb.parse.siggen.checkhashes(sq_data, missed, found, d)
+
+ return found
+setscene_depvalid[vardepsexclude] = "SSTATE_EXCLUDEDEPS_SYSROOT"
+
+BB_SETSCENE_DEPVALID = "setscene_depvalid"
+
+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
+
+addhandler sstate_eventhandler
+sstate_eventhandler[eventmask] = "bb.build.TaskSucceeded"
+python sstate_eventhandler() {
+ d = e.data
+ writtensstate = d.getVar('SSTATE_CURRTASK')
+ if not writtensstate:
+ taskname = d.getVar("BB_RUNTASK")[3:]
+ spec = d.getVar('SSTATE_PKGSPEC')
+ swspec = d.getVar('SSTATE_SWSPEC')
+ if taskname in ["fetch", "unpack", "patch", "populate_lic", "preconfigure"] and swspec:
+ d.setVar("SSTATE_PKGSPEC", "${SSTATE_SWSPEC}")
+ d.setVar("SSTATE_EXTRAPATH", "")
+ d.setVar("SSTATE_CURRTASK", taskname)
+ siginfo = d.getVar('SSTATE_PKG') + ".siginfo"
+ if not os.path.exists(siginfo):
+ bb.siggen.dump_this_task(siginfo, d)
+ else:
+ oe.utils.touch(siginfo)
+}
+
+SSTATE_PRUNE_OBSOLETEWORKDIR ?= "1"
+
+#
+# Event handler which removes manifests and stamps file for recipes which are no
+# longer 'reachable' in a build where they once were. 'Reachable' refers to
+# whether a recipe is parsed so recipes in a layer which was removed would no
+# longer be reachable. Switching between systemd and sysvinit where recipes
+# became skipped would be another example.
+#
+# Also optionally removes the workdir of those tasks/recipes
+#
+addhandler sstate_eventhandler_reachablestamps
+sstate_eventhandler_reachablestamps[eventmask] = "bb.event.ReachableStamps"
+python sstate_eventhandler_reachablestamps() {
+ import glob
+ d = e.data
+ stamps = e.stamps.values()
+ removeworkdir = (d.getVar("SSTATE_PRUNE_OBSOLETEWORKDIR", False) == "1")
+ preservestampfile = d.expand('${SSTATE_MANIFESTS}/preserve-stamps')
+ preservestamps = []
+ if os.path.exists(preservestampfile):
+ with open(preservestampfile, 'r') as f:
+ preservestamps = f.readlines()
+ seen = []
+
+ # The machine index contains all the stamps this machine has ever seen in this build directory.
+ # We should only remove things which this machine once accessed but no longer does.
+ machineindex = set()
+ bb.utils.mkdirhier(d.expand("${SSTATE_MANIFESTS}"))
+ mi = d.expand("${SSTATE_MANIFESTS}/index-machine-${MACHINE}")
+ if os.path.exists(mi):
+ with open(mi, "r") as f:
+ machineindex = set(line.strip() for line in f.readlines())
+
+ for a in sorted(list(set(d.getVar("SSTATE_ARCHS").split()))):
+ toremove = []
+ i = d.expand("${SSTATE_MANIFESTS}/index-" + a)
+ if not os.path.exists(i):
+ continue
+ manseen = set()
+ ignore = []
+ with open(i, "r") as f:
+ lines = f.readlines()
+ for l in reversed(lines):
+ try:
+ (stamp, manifest, workdir) = l.split()
+ # The index may have multiple entries for the same manifest as the code above only appends
+ # new entries and there may be an entry with matching manifest but differing version in stamp/workdir.
+ # The last entry in the list is the valid one, any earlier entries with matching manifests
+ # should be ignored.
+ if manifest in manseen:
+ ignore.append(l)
+ continue
+ manseen.add(manifest)
+ if stamp not in stamps and stamp not in preservestamps and stamp in machineindex:
+ toremove.append(l)
+ if stamp not in seen:
+ bb.debug(2, "Stamp %s is not reachable, removing related manifests" % stamp)
+ seen.append(stamp)
+ except ValueError:
+ bb.fatal("Invalid line '%s' in sstate manifest '%s'" % (l, i))
+
+ if toremove:
+ msg = "Removing %d recipes from the %s sysroot" % (len(toremove), a)
+ bb.event.fire(bb.event.ProcessStarted(msg, len(toremove)), d)
+
+ removed = 0
+ for r in toremove:
+ (stamp, manifest, workdir) = r.split()
+ for m in glob.glob(manifest + ".*"):
+ if m.endswith(".postrm"):
+ continue
+ sstate_clean_manifest(m, d)
+ bb.utils.remove(stamp + "*")
+ if removeworkdir:
+ bb.utils.remove(workdir, recurse = True)
+ lines.remove(r)
+ removed = removed + 1
+ bb.event.fire(bb.event.ProcessProgress(msg, removed), d)
+ bb.event.check_for_interrupts(d)
+
+ bb.event.fire(bb.event.ProcessFinished(msg), d)
+
+ with open(i, "w") as f:
+ for l in lines:
+ if l in ignore:
+ continue
+ f.write(l)
+ machineindex |= set(stamps)
+ with open(mi, "w") as f:
+ for l in machineindex:
+ f.write(l + "\n")
+
+ if preservestamps:
+ os.remove(preservestampfile)
+}
+
+
+#
+# Bitbake can generate an event showing which setscene tasks are 'stale',
+# i.e. which ones will be rerun. These are ones where a stamp file is present but
+# it is stable (e.g. taskhash doesn't match). With that list we can go through
+# the manifests for matching tasks and "uninstall" those manifests now. We do
+# this now rather than mid build since the distribution of files between sstate
+# objects may have changed, new tasks may run first and if those new tasks overlap
+# with the stale tasks, we'd see overlapping files messages and failures. Thankfully
+# removing these files is fast.
+#
+addhandler sstate_eventhandler_stalesstate
+sstate_eventhandler_stalesstate[eventmask] = "bb.event.StaleSetSceneTasks"
+python sstate_eventhandler_stalesstate() {
+ d = e.data
+ tasks = e.tasks
+
+ bb.utils.mkdirhier(d.expand("${SSTATE_MANIFESTS}"))
+
+ for a in list(set(d.getVar("SSTATE_ARCHS").split())):
+ toremove = []
+ i = d.expand("${SSTATE_MANIFESTS}/index-" + a)
+ if not os.path.exists(i):
+ continue
+ with open(i, "r") as f:
+ lines = f.readlines()
+ for l in lines:
+ try:
+ (stamp, manifest, workdir) = l.split()
+ for tid in tasks:
+ for s in tasks[tid]:
+ if s.startswith(stamp):
+ taskname = bb.runqueue.taskname_from_tid(tid)[3:]
+ manname = manifest + "." + taskname
+ if os.path.exists(manname):
+ bb.debug(2, "Sstate for %s is stale, removing related manifest %s" % (tid, manname))
+ toremove.append((manname, tid, tasks[tid]))
+ break
+ except ValueError:
+ bb.fatal("Invalid line '%s' in sstate manifest '%s'" % (l, i))
+
+ if toremove:
+ msg = "Removing %d stale sstate objects for arch %s" % (len(toremove), a)
+ bb.event.fire(bb.event.ProcessStarted(msg, len(toremove)), d)
+
+ removed = 0
+ for (manname, tid, stamps) in toremove:
+ sstate_clean_manifest(manname, d)
+ for stamp in stamps:
+ bb.utils.remove(stamp)
+ removed = removed + 1
+ bb.event.fire(bb.event.ProcessProgress(msg, removed), d)
+ bb.event.check_for_interrupts(d)
+
+ bb.event.fire(bb.event.ProcessFinished(msg), d)
+}