diff options
Diffstat (limited to 'meta/classes/sstate.bbclass')
| -rw-r--r-- | meta/classes/sstate.bbclass | 558 |
1 files changed, 558 insertions, 0 deletions
diff --git a/meta/classes/sstate.bbclass b/meta/classes/sstate.bbclass new file mode 100644 index 000000000..ae019379b --- /dev/null +++ b/meta/classes/sstate.bbclass @@ -0,0 +1,558 @@ +SSTATE_VERSION = "2" + +SSTATE_MANIFESTS ?= "${TMPDIR}/sstate-control" +SSTATE_MANFILEBASE = "${SSTATE_MANIFESTS}/manifest-${SSTATE_MANMACH}-" +SSTATE_MANFILEPREFIX = "${SSTATE_MANFILEBASE}${PN}" + + +SSTATE_PKGARCH = "${PACKAGE_ARCH}" +SSTATE_PKGSPEC = "sstate-${PN}-${PACKAGE_ARCH}${TARGET_VENDOR}-${TARGET_OS}-${PV}-${PR}-${SSTATE_PKGARCH}-${SSTATE_VERSION}-" +SSTATE_PKGNAME = "${SSTATE_PKGSPEC}${BB_TASKHASH}" +SSTATE_PKG = "${SSTATE_DIR}/${SSTATE_PKGNAME}" + +SSTATE_SCAN_FILES ?= "*.la *-config *_config" +SSTATE_SCAN_CMD ?= 'find ${SSTATE_BUILDDIR} \( -name "${@"\" -o -name \"".join(d.getVar("SSTATE_SCAN_FILES", True).split())}" \) -type f' + +BB_HASHFILENAME = "${SSTATE_PKGNAME}" + +SSTATE_MANMACH ?= "${SSTATE_PKGARCH}" + +SSTATEPREINSTFUNCS ?= "" +SSTATEPOSTINSTFUNCS ?= "" + +python () { + if bb.data.inherits_class('native', d): + d.setVar('SSTATE_PKGARCH', d.getVar('BUILD_ARCH')) + elif bb.data.inherits_class('cross', d): + d.setVar('SSTATE_PKGARCH', d.expand("${BUILD_ARCH}_${TUNE_PKGARCH}")) + d.setVar('SSTATE_MANMACH', d.expand("${BUILD_ARCH}_${MACHINE}")) + elif bb.data.inherits_class('crosssdk', d): + d.setVar('SSTATE_PKGARCH', d.expand("${BUILD_ARCH}_${PACKAGE_ARCH}")) + elif bb.data.inherits_class('nativesdk', d): + d.setVar('SSTATE_PKGARCH', d.expand("${SDK_ARCH}")) + elif bb.data.inherits_class('cross-canadian', d): + d.setVar('SSTATE_PKGARCH', d.expand("${SDK_ARCH}_${PACKAGE_ARCH}")) + else: + d.setVar('SSTATE_MANMACH', d.expand("${MACHINE}")) + + # These classes encode staging paths into their scripts data so can only be + # reused if we manipulate the paths + if bb.data.inherits_class('native', d) or bb.data.inherits_class('cross', d) or bb.data.inherits_class('sdk', d) or bb.data.inherits_class('crosssdk', d): + scan_cmd = "grep -Irl ${STAGING_DIR} ${SSTATE_BUILDDIR}" + d.setVar('SSTATE_SCAN_CMD', scan_cmd) + + unique_tasks = set((d.getVar('SSTATETASKS', True) or "").split()) + d.setVar('SSTATETASKS', " ".join(unique_tasks)) + namemap = [] + for task in unique_tasks: + namemap.append(d.getVarFlag(task, 'sstate-name')) + d.prependVarFlag(task, 'prefuncs', "sstate_task_prefunc ") + d.appendVarFlag(task, 'postfuncs', " sstate_task_postfunc") + d.setVar('SSTATETASKNAMES', " ".join(namemap)) +} + +def sstate_init(name, task, d): + ss = {} + ss['task'] = task + ss['name'] = name + 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', True) + if not task: + bb.fatal("sstate code running without task context?!") + task = task.replace("_setscene", "") + + name = d.getVarFlag("do_" + task, 'sstate-name', True) + inputs = (d.getVarFlag("do_" + task, 'sstate-inputdirs', True) or "").split() + outputs = (d.getVarFlag("do_" + task, 'sstate-outputdirs', True) or "").split() + plaindirs = (d.getVarFlag("do_" + task, 'sstate-plaindirs', True) or "").split() + lockfiles = (d.getVarFlag("do_" + task, 'sstate-lockfile', True) or "").split() + lockfilesshared = (d.getVarFlag("do_" + task, 'sstate-lockfile-shared', True) or "").split() + interceptfuncs = (d.getVarFlag("do_" + task, 'sstate-interceptfuncs', True) or "").split() + if not name or len(inputs) != len(outputs): + bb.fatal("sstate variables not setup correctly?!") + + ss = sstate_init(name, 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 + return ss + +def sstate_add(ss, source, dest, d): + srcbase = os.path.basename(source) + ss['dirs'].append([srcbase, source, dest]) + return ss + +def sstate_install(ss, d): + import oe.path + + sharedfiles = [] + shareddirs = [] + bb.mkdirhier(d.expand("${SSTATE_MANIFESTS}")) + manifest = d.expand("${SSTATE_MANFILEPREFIX}.%s" % ss['name']) + + if os.access(manifest, os.R_OK): + bb.fatal("Package already staged (%s)?!" % manifest) + + 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']: + oe.path.copytree(state[1], state[2]) + for walkroot, dirs, files in os.walk(state[1]): + bb.debug(2, "Staging files from %s to %s" % (state[1], state[2])) + 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 not dstdir.endswith("/"): + dstdir = dstdir + "/" + shareddirs.append(dstdir) + 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() + + for postinst in (d.getVar('SSTATEPOSTINSTFUNCS', True) or '').split(): + bb.build.exec_func(postinst, d) + + for lock in locks: + bb.utils.unlockfile(lock) + +def sstate_installpkg(ss, d): + import oe.path + + def prepdir(dir): + # remove dir if it exists, ensure any parent directories do exist + if os.path.exists(dir): + oe.path.remove(dir) + bb.mkdirhier(dir) + oe.path.remove(dir) + + sstateinst = d.expand("${WORKDIR}/sstate-install-%s/" % ss['name']) + sstatepkg = d.getVar('SSTATE_PKG', True) + '_' + ss['name'] + ".tgz" + + if not os.path.exists(sstatepkg): + pstaging_fetch(sstatepkg, d) + + if not os.path.isfile(sstatepkg): + bb.note("Staging package %s does not exist" % sstatepkg) + return False + + sstate_clean(ss, d) + + d.setVar('SSTATE_INSTDIR', sstateinst) + d.setVar('SSTATE_PKG', sstatepkg) + + for preinst in (d.getVar('SSTATEPREINSTFUNCS', True) or '').split(): + bb.build.exec_func(preinst, d) + + bb.build.exec_func('sstate_unpack_package', d) + + # Fixup hardcoded paths + # + # Note: The logic below must match the reverse logic in + # sstate_hardcode_path(d) + + fixmefn = sstateinst + "fixmepath" + if os.path.isfile(fixmefn): + staging = d.getVar('STAGING_DIR', True) + staging_target = d.getVar('STAGING_DIR_TARGET', True) + staging_host = d.getVar('STAGING_DIR_HOST', True) + + if bb.data.inherits_class('native', d) or bb.data.inherits_class('nativesdk', d) or bb.data.inherits_class('crosssdk', d) or bb.data.inherits_class('cross-canadian', d): + sstate_sed_cmd = "sed -i -e 's:FIXMESTAGINGDIR:%s:g'" % (staging) + elif bb.data.inherits_class('cross', d): + sstate_sed_cmd = "sed -i -e 's:FIXMESTAGINGDIRTARGET:%s:g; s:FIXMESTAGINGDIR:%s:g'" % (staging_target, staging) + else: + sstate_sed_cmd = "sed -i -e 's:FIXMESTAGINGDIRHOST:%s:g'" % (staging_host) + + # 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) + + print "Replacing fixme paths in sstate package: %s" % (sstate_hardcode_cmd) + os.system(sstate_hardcode_cmd) + + # Need to remove this or we'd copy it into the target directory and may + # conflict with another writer + os.remove(fixmefn) + + for state in ss['dirs']: + prepdir(state[1]) + os.rename(sstateinst + state[0], state[1]) + sstate_install(ss, d) + + for plain in ss['plaindirs']: + workdir = d.getVar('WORKDIR', True) + src = sstateinst + "/" + plain.replace(workdir, '') + dest = plain + bb.mkdirhier(src) + prepdir(dest) + os.rename(src, dest) + + return True + +def sstate_clean_cachefile(ss, d): + import oe.path + + sstatepkgdir = d.getVar('SSTATE_DIR', True) + sstatepkgfile = sstatepkgdir + '/' + d.getVar('SSTATE_PKGSPEC', True) + "*_" + ss['name'] + ".tgz*" + bb.note("Removing %s" % sstatepkgfile) + oe.path.remove(sstatepkgfile) + +def sstate_clean_cachefiles(d): + for task in (d.getVar('SSTATETASKS', True) or "").split(): + ss = sstate_state_fromvars(d, task[3:]) + sstate_clean_cachefile(ss, d) + +def sstate_clean_manifest(manifest, d): + import oe.path + + mfile = open(manifest) + entries = mfile.readlines() + mfile.close() + + for entry in entries: + entry = entry.strip() + 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: + os.rmdir(entry[:-1]) + else: + oe.path.remove(entry) + except OSError: + pass + + oe.path.remove(manifest) + +def sstate_clean(ss, d): + import oe.path + + manifest = d.expand("${SSTATE_MANFILEPREFIX}.%s" % ss['name']) + + 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) + + for lock in locks: + bb.utils.unlockfile(lock) + + stfile = d.getVar("STAMP", True) + ".do_" + ss['task'] + extrainf = d.getVarFlag("do_" + ss['task'], 'stamp-extra-info', True) + oe.path.remove(stfile) + oe.path.remove(stfile + "_setscene") + if extrainf: + oe.path.remove(stfile + ".*" + extrainf) + oe.path.remove(stfile + "_setscene" + ".*" + extrainf) + else: + oe.path.remove(stfile + ".*") + oe.path.remove(stfile + "_setscene" + ".*") + +CLEANFUNCS += "sstate_cleanall" + +python sstate_cleanall() { + import fnmatch + + bb.note("Removing shared state for package %s" % d.getVar('PN', True)) + + manifest_dir = d.getVar('SSTATE_MANIFESTS', True) + manifest_prefix = d.getVar("SSTATE_MANFILEPREFIX", True) + manifest_pattern = os.path.basename(manifest_prefix) + ".*" + + if not os.path.exists(manifest_dir): + return + + for manifest in (os.listdir(manifest_dir)): + if fnmatch.fnmatch(manifest, manifest_pattern): + name = manifest.replace(manifest_pattern[:-1], "") + namemap = d.getVar('SSTATETASKNAMES', True).split() + tasks = d.getVar('SSTATETASKS', True).split() + if name not in namemap: + continue + taskname = tasks[namemap.index(name)] + shared_state = sstate_state_fromvars(d, taskname[3:]) + sstate_clean(shared_state, d) +} + +def sstate_hardcode_path(d): + # 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 = d.getVar('STAGING_DIR', True) + staging_target = d.getVar('STAGING_DIR_TARGET', True) + staging_host = d.getVar('STAGING_DIR_HOST', True) + sstate_builddir = d.getVar('SSTATE_BUILDDIR', True) + + if bb.data.inherits_class('native', d) or bb.data.inherits_class('nativesdk', d) or bb.data.inherits_class('crosssdk', d) or bb.data.inherits_class('cross-canadian', d): + sstate_grep_cmd = "grep -l -e '%s'" % (staging) + sstate_sed_cmd = "sed -i -e 's:%s:FIXMESTAGINGDIR:g'" % (staging) + elif bb.data.inherits_class('cross', d): + sstate_grep_cmd = "grep -l -e '(%s|%s)'" % (staging_target, staging) + sstate_sed_cmd = "sed -i -e 's:%s:FIXMESTAGINGDIRTARGET:g; s:%s:FIXMESTAGINGDIR:g'" % (staging_target, staging) + else: + sstate_grep_cmd = "grep -l -e '%s'" % (staging_host) + sstate_sed_cmd = "sed -i -e 's:%s:FIXMESTAGINGDIRHOST:g'" % (staging_host) + + fixmefn = sstate_builddir + "fixmepath" + + sstate_scan_cmd = d.getVar('SSTATE_SCAN_CMD', True) + 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) + + # 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 --no-run-if-empty %s" % (sstate_scan_cmd, sstate_grep_cmd, sstate_filelist_cmd, sstate_sed_cmd) + + print "Removing hardcoded paths from sstate package: '%s'" % (sstate_hardcode_cmd) + os.system(sstate_hardcode_cmd) + + # If the fixmefn is empty, remove it.. + if os.stat(fixmefn).st_size == 0: + os.remove(fixmefn) + else: + print "Replacing absolute paths in fixmepath file: '%s'" % (sstate_filelist_relative_cmd) + os.system(sstate_filelist_relative_cmd) + +def sstate_package(ss, d): + import oe.path + + def make_relative_symlink(path, outputpath, d): + # Replace out absolute TMPDIR paths in symlinks with relative ones + if not os.path.islink(path): + return + link = os.readlink(path) + if not os.path.isabs(link): + return + if not link.startswith(tmpdir): + return + + depth = link.rpartition(tmpdir)[2].count('/') + base = link.partition(tmpdir)[2].strip() + while depth > 1: + base = "../" + base + depth -= 1 + + bb.debug(2, "Replacing absolute path %s with relative path %s" % (link, base)) + os.remove(path) + os.symlink(base, path) + + tmpdir = d.getVar('TMPDIR', True) + + sstatebuild = d.expand("${WORKDIR}/sstate-build-%s/" % ss['name']) + sstatepkg = d.getVar('SSTATE_PKG', True) + '_'+ ss['name'] + ".tgz" + bb.mkdirhier(sstatebuild) + bb.mkdirhier(os.path.dirname(sstatepkg)) + for state in ss['dirs']: + srcbase = state[0].rstrip("/").rsplit('/', 1)[0] + for walkroot, dirs, files in os.walk(state[1]): + for file in files: + srcpath = os.path.join(walkroot, file) + dstpath = srcpath.replace(state[1], sstatebuild + state[0]) + make_relative_symlink(srcpath, dstpath, d) + for dir in dirs: + srcpath = os.path.join(walkroot, dir) + dstpath = srcpath.replace(state[1], sstatebuild + state[0]) + make_relative_symlink(srcpath, dstpath, d) + bb.debug(2, "Preparing tree %s for packaging at %s" % (state[1], sstatebuild + state[0])) + oe.path.copytree(state[1], sstatebuild + state[0]) + + workdir = d.getVar('WORKDIR', True) + for plain in ss['plaindirs']: + pdir = plain.replace(workdir, sstatebuild) + bb.mkdirhier(plain) + bb.mkdirhier(pdir) + oe.path.copytree(plain, pdir) + + d.setVar('SSTATE_BUILDDIR', sstatebuild) + d.setVar('SSTATE_PKG', sstatepkg) + sstate_hardcode_path(d) + bb.build.exec_func('sstate_create_package', d) + + bb.siggen.dump_this_task(sstatepkg + ".siginfo", d) + + return + +def pstaging_fetch(sstatepkg, d): + import bb.fetch2 + + # Only try and fetch if the user has configured a mirror + mirrors = d.getVar('SSTATE_MIRRORS', True) + if not mirrors: + return + + # Copy the data object and override DL_DIR and SRC_URI + localdata = bb.data.createCopy(d) + bb.data.update_data(localdata) + + dldir = localdata.expand("${SSTATE_DIR}") + srcuri = "file://" + os.path.basename(sstatepkg) + + bb.mkdirhier(dldir) + + localdata.setVar('DL_DIR', dldir) + localdata.setVar('PREMIRRORS', mirrors) + localdata.setVar('SRC_URI', srcuri) + + # Try a fetch from the sstate mirror, if it fails just return and + # we will build the package + try: + fetcher = bb.fetch2.Fetch([srcuri], localdata, cache=False) + fetcher.download() + + # Need to optimise this, if using file:// urls, the fetcher just changes the local path + # For now work around by symlinking + localpath = bb.data.expand(fetcher.localpath(srcuri), localdata) + if localpath != sstatepkg and os.path.exists(localpath) and not os.path.exists(sstatepkg): + os.symlink(localpath, sstatepkg) + + except bb.fetch2.BBFetchException: + pass + +def sstate_setscene(d): + shared_state = sstate_state_fromvars(d) + accelerate = sstate_installpkg(shared_state, d) + if not accelerate: + raise bb.build.FuncFailed("No suitable staging package found") + +python sstate_task_prefunc () { + shared_state = sstate_state_fromvars(d) + sstate_clean(shared_state, d) +} + +python sstate_task_postfunc () { + shared_state = sstate_state_fromvars(d) + sstate_install(shared_state, d) + for intercept in shared_state['interceptfuncs']: + bb.build.exec_func(intercept, d) + sstate_package(shared_state, d) +} + + +# +# Shell function to generate a sstate package from a directory +# set as SSTATE_BUILDDIR +# +sstate_create_package () { + cd ${SSTATE_BUILDDIR} + TFILE=`mktemp ${SSTATE_PKG}.XXXXXXXX` + # Need to handle empty directories + if [ "$(ls -A)" ]; then + tar -czf $TFILE * + else + tar -cz --file=$TFILE --files-from=/dev/null + fi + chmod 0664 $TFILE + mv $TFILE ${SSTATE_PKG} + + cd ${WORKDIR} + rm -rf ${SSTATE_BUILDDIR} +} + +# +# Shell function to decompress and prepare a package for installation +# +sstate_unpack_package () { + mkdir -p ${SSTATE_INSTDIR} + cd ${SSTATE_INSTDIR} + tar -xvzf ${SSTATE_PKG} +} + +BB_HASHCHECK_FUNCTION = "sstate_checkhashes" + +def sstate_checkhashes(sq_fn, sq_task, sq_hash, sq_hashfn, d): + + ret = [] + # This needs to go away, FIXME + mapping = { + "do_populate_sysroot" : "populate-sysroot", + "do_populate_lic" : "populate-lic", + "do_package_write_ipk" : "deploy-ipk", + "do_package_write_deb" : "deploy-deb", + "do_package_write_rpm" : "deploy-rpm", + "do_package" : "package", + "do_deploy" : "deploy", + } + + for task in range(len(sq_fn)): + sstatefile = d.expand("${SSTATE_DIR}/" + sq_hashfn[task] + "_" + mapping[sq_task[task]] + ".tgz") + sstatefile = sstatefile.replace("${BB_TASKHASH}", sq_hash[task]) + if os.path.exists(sstatefile): + bb.debug(2, "SState: Found valid sstate file %s" % sstatefile) + ret.append(task) + continue + else: + bb.debug(2, "SState: Looked for but didn't find file %s" % sstatefile) + + mirrors = d.getVar("SSTATE_MIRRORS", True) + if mirrors: + # Copy the data object and override DL_DIR and SRC_URI + localdata = bb.data.createCopy(d) + bb.data.update_data(localdata) + + dldir = localdata.expand("${SSTATE_DIR}") + localdata.setVar('DL_DIR', dldir) + localdata.setVar('PREMIRRORS', mirrors) + + bb.debug(2, "SState using premirror of: %s" % mirrors) + + for task in range(len(sq_fn)): + if task in ret: + continue + + sstatefile = d.expand("${SSTATE_DIR}/" + sq_hashfn[task] + "_" + mapping[sq_task[task]] + ".tgz") + sstatefile = sstatefile.replace("${BB_TASKHASH}", sq_hash[task]) + + srcuri = "file://" + os.path.basename(sstatefile) + localdata.setVar('SRC_URI', srcuri) + bb.debug(2, "SState: Attempting to fetch %s" % srcuri) + + try: + fetcher = bb.fetch2.Fetch(srcuri.split(), localdata) + fetcher.checkstatus() + bb.debug(2, "SState: Successful fetch test for %s" % srcuri) + ret.append(task) + except: + bb.debug(2, "SState: Unsuccessful fetch test for %s" % srcuri) + pass + + return ret + |
