diff options
Diffstat (limited to 'meta/lib/oe')
-rw-r--r-- | meta/lib/oe/copy_buildsystem.py | 102 | ||||
-rw-r--r-- | meta/lib/oe/image.py | 24 | ||||
-rw-r--r-- | meta/lib/oe/license.py | 3 | ||||
-rw-r--r-- | meta/lib/oe/lsb.py | 18 | ||||
-rw-r--r-- | meta/lib/oe/package.py | 28 | ||||
-rw-r--r-- | meta/lib/oe/package_manager.py | 73 | ||||
-rw-r--r-- | meta/lib/oe/patch.py | 161 | ||||
-rw-r--r-- | meta/lib/oe/recipeutils.py | 279 | ||||
-rw-r--r-- | meta/lib/oe/rootfs.py | 148 | ||||
-rw-r--r-- | meta/lib/oe/sdk.py | 2 | ||||
-rw-r--r-- | meta/lib/oe/sstatesig.py | 13 | ||||
-rw-r--r-- | meta/lib/oe/terminal.py | 67 | ||||
-rw-r--r-- | meta/lib/oe/utils.py | 16 |
13 files changed, 828 insertions, 106 deletions
diff --git a/meta/lib/oe/copy_buildsystem.py b/meta/lib/oe/copy_buildsystem.py new file mode 100644 index 0000000000..cf7fada7f0 --- /dev/null +++ b/meta/lib/oe/copy_buildsystem.py @@ -0,0 +1,102 @@ +# This class should provide easy access to the different aspects of the +# buildsystem such as layers, bitbake location, etc. +import stat +import shutil + +def _smart_copy(src, dest): + # smart_copy will choose the correct function depending on whether the + # source is a file or a directory. + mode = os.stat(src).st_mode + if stat.S_ISDIR(mode): + shutil.copytree(src, dest, symlinks=True) + else: + shutil.copyfile(src, dest) + shutil.copymode(src, dest) + +class BuildSystem(object): + def __init__(self, d): + self.d = d + self.layerdirs = d.getVar('BBLAYERS', True).split() + + def copy_bitbake_and_layers(self, destdir): + # Copy in all metadata layers + bitbake (as repositories) + layers_copied = [] + bb.utils.mkdirhier(destdir) + layers = list(self.layerdirs) + + corebase = self.d.getVar('COREBASE', True) + layers.append(corebase) + + corebase_files = self.d.getVar('COREBASE_FILES', True).split() + + # bitbake belongs in corebase so make sure it goes there + if "bitbake" not in corebase_files: + corebase_files.append("bitbake") + corebase_files = [corebase + '/' +x for x in corebase_files] + + for layer in layers: + layerconf = os.path.join(layer, 'conf', 'layer.conf') + if os.path.exists(layerconf): + with open(layerconf, 'r') as f: + if f.readline().startswith("# ### workspace layer auto-generated by devtool ###"): + bb.warn("Skipping local workspace layer %s" % layer) + continue + + # If the layer was already under corebase, leave it there + # since layers such as meta have issues when moved. + layerdestpath = destdir + if corebase == os.path.dirname(layer): + layerdestpath += '/' + os.path.basename(corebase) + layerdestpath += '/' + os.path.basename(layer) + + layer_relative = os.path.relpath(layerdestpath, + destdir) + layers_copied.append(layer_relative) + + # Treat corebase as special since it typically will contain + # build directories or other custom items. + if corebase == layer: + bb.utils.mkdirhier(layerdestpath) + for f in corebase_files: + f_basename = os.path.basename(f) + destname = os.path.join(layerdestpath, f_basename) + _smart_copy(f, destname) + else: + if os.path.exists(layerdestpath): + bb.note("Skipping layer %s, already handled" % layer) + else: + _smart_copy(layer, layerdestpath) + + return layers_copied + +def generate_locked_sigs(sigfile, d): + bb.utils.mkdirhier(os.path.dirname(sigfile)) + depd = d.getVar('BB_TASKDEPDATA', True) + tasks = ['%s.%s' % (v[2], v[1]) for v in depd.itervalues()] + bb.parse.siggen.dump_lockedsigs(sigfile, tasks) + +def prune_lockedsigs(allowed_tasks, excluded_targets, lockedsigs, pruned_output): + with open(lockedsigs, 'r') as infile: + bb.utils.mkdirhier(os.path.dirname(pruned_output)) + with open(pruned_output, 'w') as f: + invalue = False + for line in infile: + if invalue: + if line.endswith('\\\n'): + splitval = line.strip().split(':') + if splitval[1] in allowed_tasks and not splitval[0] in excluded_targets: + f.write(line) + else: + f.write(line) + invalue = False + elif line.startswith('SIGGEN_LOCKEDSIGS'): + invalue = True + f.write(line) + +def create_locked_sstate_cache(lockedsigs, input_sstate_cache, output_sstate_cache, d, fixedlsbstring=""): + bb.note('Generating sstate-cache...') + + bb.process.run("gen-lockedsig-cache %s %s %s" % (lockedsigs, input_sstate_cache, output_sstate_cache)) + if fixedlsbstring: + os.rename(output_sstate_cache + '/' + d.getVar('NATIVELSBSTRING', True), + output_sstate_cache + '/' + fixedlsbstring) diff --git a/meta/lib/oe/image.py b/meta/lib/oe/image.py index 7e080b00dd..0ce303d570 100644 --- a/meta/lib/oe/image.py +++ b/meta/lib/oe/image.py @@ -48,11 +48,13 @@ class ImageDepGraph(object): graph = dict() def add_node(node): + base_type = self._image_base_type(node) deps = (self.d.getVar('IMAGE_TYPEDEP_' + node, True) or "") - if deps != "": + base_deps = (self.d.getVar('IMAGE_TYPEDEP_' + base_type, True) or "") + if deps != "" or base_deps != "": graph[node] = deps - for dep in deps.split(): + for dep in deps.split() + base_deps.split(): if not dep in graph: add_node(dep) else: @@ -72,6 +74,18 @@ class ImageDepGraph(object): for item in remove_list: self.graph.pop(item, None) + def _image_base_type(self, type): + ctypes = self.d.getVar('COMPRESSIONTYPES', True).split() + if type in ["vmdk", "live", "iso", "hddimg"]: + type = "ext3" + basetype = type + for ctype in ctypes: + if type.endswith("." + ctype): + basetype = type[:-len("." + ctype)] + break + + return basetype + def _compute_dependencies(self): """ returns dict object of nodes with [no_of_depends_on, no_of_depended_by] @@ -282,7 +296,11 @@ class Image(ImageDepGraph): bb.data.update_data(localdata) localdata.setVar('type', type) - cmds.append("\t" + localdata.getVar("IMAGE_CMD", True)) + image_cmd = localdata.getVar("IMAGE_CMD", True) + if image_cmd: + cmds.append("\t" + image_cmd) + else: + bb.fatal("No IMAGE_CMD defined for IMAGE_FSTYPES entry '%s' - possibly invalid type name or missing support class" % type) cmds.append(localdata.expand("\tcd ${DEPLOY_DIR_IMAGE}")) if type in cimages: diff --git a/meta/lib/oe/license.py b/meta/lib/oe/license.py index 340da61102..31ca15b574 100644 --- a/meta/lib/oe/license.py +++ b/meta/lib/oe/license.py @@ -25,7 +25,8 @@ class InvalidLicense(LicenseError): def __str__(self): return "invalid characters in license '%s'" % self.license -license_operator = re.compile('([&|() ])') +license_operator_chars = '&|() ' +license_operator = re.compile('([' + license_operator_chars + '])') license_pattern = re.compile('[a-zA-Z0-9.+_\-]+$') class LicenseVisitor(ast.NodeVisitor): diff --git a/meta/lib/oe/lsb.py b/meta/lib/oe/lsb.py index b53f361035..ddfe71b6b5 100644 --- a/meta/lib/oe/lsb.py +++ b/meta/lib/oe/lsb.py @@ -9,6 +9,7 @@ def release_dict(): data = {} for line in output.splitlines(): + if line.startswith("-e"): line = line[3:] try: key, value = line.split(":\t", 1) except ValueError: @@ -36,14 +37,6 @@ def release_dict_file(): if match: data['DISTRIB_ID'] = match.group(1) data['DISTRIB_RELEASE'] = match.group(2) - elif os.path.exists('/etc/SuSE-release'): - data = {} - data['DISTRIB_ID'] = 'SUSE LINUX' - with open('/etc/SuSE-release') as f: - for line in f: - if line.startswith('VERSION = '): - data['DISTRIB_RELEASE'] = line[10:].rstrip() - break elif os.path.exists('/etc/os-release'): data = {} with open('/etc/os-release') as f: @@ -52,6 +45,15 @@ def release_dict_file(): data['DISTRIB_ID'] = line[5:].rstrip().strip('"') if line.startswith('VERSION_ID='): data['DISTRIB_RELEASE'] = line[11:].rstrip().strip('"') + elif os.path.exists('/etc/SuSE-release'): + data = {} + data['DISTRIB_ID'] = 'SUSE LINUX' + with open('/etc/SuSE-release') as f: + for line in f: + if line.startswith('VERSION = '): + data['DISTRIB_RELEASE'] = line[10:].rstrip() + break + except IOError: return None return data diff --git a/meta/lib/oe/package.py b/meta/lib/oe/package.py index a26a631837..8bc56c6e88 100644 --- a/meta/lib/oe/package.py +++ b/meta/lib/oe/package.py @@ -31,7 +31,7 @@ def runstrip(arg): extraflags = "--remove-section=.comment --remove-section=.note" # Use mv to break hardlinks - stripcmd = "'%s' %s '%s' -o '%s.tmp' && mv '%s.tmp' '%s'" % (strip, extraflags, file, file, file, file) + stripcmd = "'%s' %s '%s' -o '%s.tmp' && chown --reference='%s' '%s.tmp' && mv '%s.tmp' '%s'" % (strip, extraflags, file, file, file, file, file, file) bb.debug(1, "runstrip: %s" % stripcmd) ret = subprocess.call(stripcmd, shell=True) @@ -98,3 +98,29 @@ def filedeprunner(arg): raise e return (pkg, provides, requires) + + +def read_shlib_providers(d): + import re + + shlib_provider = {} + shlibs_dirs = d.getVar('SHLIBSDIRS', True).split() + list_re = re.compile('^(.*)\.list$') + # Go from least to most specific since the last one found wins + for dir in reversed(shlibs_dirs): + bb.debug(2, "Reading shlib providers in %s" % (dir)) + if not os.path.exists(dir): + continue + for file in os.listdir(dir): + m = list_re.match(file) + if m: + dep_pkg = m.group(1) + fd = open(os.path.join(dir, file)) + lines = fd.readlines() + fd.close() + for l in lines: + s = l.strip().split(":") + if s[0] not in shlib_provider: + shlib_provider[s[0]] = {} + shlib_provider[s[0]][s[1]] = (dep_pkg, s[2]) + return shlib_provider diff --git a/meta/lib/oe/package_manager.py b/meta/lib/oe/package_manager.py index 411b9d6309..0460415699 100644 --- a/meta/lib/oe/package_manager.py +++ b/meta/lib/oe/package_manager.py @@ -111,11 +111,15 @@ class RpmIndexer(Indexer): index_cmds = [] rpm_dirs_found = False for arch in archs: + dbpath = os.path.join(self.d.getVar('WORKDIR', True), 'rpmdb', arch) + if os.path.exists(dbpath): + bb.utils.remove(dbpath, True) arch_dir = os.path.join(self.deploy_dir, arch) if not os.path.isdir(arch_dir): continue - index_cmds.append("%s --update -q %s" % (rpm_createrepo, arch_dir)) + index_cmds.append("%s --dbpath %s --update -q %s" % \ + (rpm_createrepo, dbpath, arch_dir)) rpm_dirs_found = True @@ -169,7 +173,35 @@ class OpkgIndexer(Indexer): class DpkgIndexer(Indexer): + def _create_configs(self): + bb.utils.mkdirhier(self.apt_conf_dir) + bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "lists", "partial")) + bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "apt.conf.d")) + bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "preferences.d")) + + with open(os.path.join(self.apt_conf_dir, "preferences"), + "w") as prefs_file: + pass + with open(os.path.join(self.apt_conf_dir, "sources.list"), + "w+") as sources_file: + pass + + with open(self.apt_conf_file, "w") as apt_conf: + with open(os.path.join(self.d.expand("${STAGING_ETCDIR_NATIVE}"), + "apt", "apt.conf.sample")) as apt_conf_sample: + for line in apt_conf_sample.read().split("\n"): + line = re.sub("#ROOTFS#", "/dev/null", line) + line = re.sub("#APTCONF#", self.apt_conf_dir, line) + apt_conf.write(line + "\n") + def write_index(self): + self.apt_conf_dir = os.path.join(self.d.expand("${APTCONF_TARGET}"), + "apt-ftparchive") + self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf") + self._create_configs() + + os.environ['APT_CONFIG'] = self.apt_conf_file + pkg_archs = self.d.getVar('PACKAGE_ARCHS', True) if pkg_archs is not None: arch_list = pkg_archs.split() @@ -529,7 +561,7 @@ class PackageManager(object): return cmd = [bb.utils.which(os.getenv('PATH'), "oe-pkgdata-util"), - "glob", self.d.getVar('PKGDATA_DIR', True), installed_pkgs_file, + "-p", self.d.getVar('PKGDATA_DIR', True), "glob", installed_pkgs_file, globs] exclude = self.d.getVar('PACKAGE_EXCLUDE_COMPLEMENTARY', True) if exclude: @@ -578,7 +610,8 @@ class RpmPM(PackageManager): self.fullpkglist = list() self.deploy_dir = self.d.getVar('DEPLOY_DIR_RPM', True) self.etcrpm_dir = os.path.join(self.target_rootfs, "etc/rpm") - self.install_dir = os.path.join(self.target_rootfs, "install") + self.install_dir_name = "oe_install" + self.install_dir_path = os.path.join(self.target_rootfs, self.install_dir_name) self.rpm_cmd = bb.utils.which(os.getenv('PATH'), "rpm") self.smart_cmd = bb.utils.which(os.getenv('PATH'), "smart") self.smart_opt = "--quiet --data-dir=" + os.path.join(target_rootfs, @@ -679,10 +712,10 @@ class RpmPM(PackageManager): def _search_pkg_name_in_feeds(self, pkg, feed_archs): for arch in feed_archs: arch = arch.replace('-', '_') + regex_match = re.compile(r"^%s-[^-]*-[^-]*@%s$" % \ + (re.escape(pkg), re.escape(arch))) for p in self.fullpkglist: - regex_match = r"^%s-[^-]*-[^-]*@%s$" % \ - (re.escape(pkg), re.escape(arch)) - if re.match(regex_match, p) is not None: + if regex_match.match(p) is not None: # First found is best match # bb.note('%s -> %s' % (pkg, pkg + '@' + arch)) return pkg + '@' + arch @@ -749,9 +782,9 @@ class RpmPM(PackageManager): bb.utils.mkdirhier(self.etcrpm_dir) # Setup temporary directory -- install... - if os.path.exists(self.install_dir): - bb.utils.remove(self.install_dir, True) - bb.utils.mkdirhier(os.path.join(self.install_dir, 'tmp')) + if os.path.exists(self.install_dir_path): + bb.utils.remove(self.install_dir_path, True) + bb.utils.mkdirhier(os.path.join(self.install_dir_path, 'tmp')) channel_priority = 5 platform_dir = os.path.join(self.etcrpm_dir, "platform") @@ -838,7 +871,7 @@ class RpmPM(PackageManager): self._invoke_smart('config --set rpm-dbpath=/var/lib/rpm') self._invoke_smart('config --set rpm-extra-macros._var=%s' % self.d.getVar('localstatedir', True)) - cmd = 'config --set rpm-extra-macros._tmppath=/install/tmp' + cmd = "config --set rpm-extra-macros._tmppath=/%s/tmp" % (self.install_dir_name) prefer_color = self.d.getVar('RPM_PREFER_ELF_ARCH', True) if prefer_color: @@ -856,6 +889,7 @@ class RpmPM(PackageManager): % prefer_color) self._invoke_smart(cmd) + self._invoke_smart('config --set rpm-ignoresize=1') # Write common configuration for host and target usage self._invoke_smart('config --set rpm-nolinktos=1') @@ -903,8 +937,10 @@ class RpmPM(PackageManager): # if self.rpm_version == 4: scriptletcmd = "$2 $3 $4\n" + scriptpath = "$3" else: scriptletcmd = "$2 $1/$3 $4\n" + scriptpath = "$1/$3" SCRIPTLET_FORMAT = "#!/bin/bash\n" \ "\n" \ @@ -922,10 +958,10 @@ class RpmPM(PackageManager): " mkdir -p $1/etc/rpm-postinsts\n" \ " num=100\n" \ " while [ -e $1/etc/rpm-postinsts/${num}-* ]; do num=$((num + 1)); done\n" \ - " name=`head -1 $1/$3 | cut -d\' \' -f 2`\n" \ + " name=`head -1 " + scriptpath + " | cut -d\' \' -f 2`\n" \ ' echo "#!$2" > $1/etc/rpm-postinsts/${num}-${name}\n' \ ' echo "# Arg: $4" >> $1/etc/rpm-postinsts/${num}-${name}\n' \ - " cat $1/$3 >> $1/etc/rpm-postinsts/${num}-${name}\n" \ + " cat " + scriptpath + " >> $1/etc/rpm-postinsts/${num}-${name}\n" \ " chmod +x $1/etc/rpm-postinsts/${num}-${name}\n" \ " else\n" \ ' echo "Error: pre/post remove scriptlet failed"\n' \ @@ -991,7 +1027,7 @@ class RpmPM(PackageManager): cmd += "--dbpath=/var/lib/rpm " cmd += "--define='_cross_scriptlet_wrapper %s' " % \ self.scriptlet_wrapper - cmd += "--define='_tmppath /install/tmp' %s" % ' '.join(pkgs) + cmd += "--define='_tmppath /%s/tmp' %s" % (self.install_dir_name, ' '.join(pkgs)) else: # for pkg in pkgs: # bb.note('Debug: What required: %s' % pkg) @@ -1026,7 +1062,7 @@ class RpmPM(PackageManager): bb.utils.remove(os.path.join(self.target_rootfs, 'var/lib/opkg'), True) # remove temp directory - bb.utils.remove(self.d.expand('${IMAGE_ROOTFS}/install'), True) + bb.utils.remove(self.install_dir_path, True) def backup_packaging_data(self): # Save the rpmlib for increment rpm image generation @@ -1630,10 +1666,10 @@ class DpkgPM(PackageManager): def remove(self, pkgs, with_dependencies=True): if with_dependencies: os.environ['APT_CONFIG'] = self.apt_conf_file - cmd = "%s remove %s" % (self.apt_get_cmd, ' '.join(pkgs)) + cmd = "%s purge %s" % (self.apt_get_cmd, ' '.join(pkgs)) else: cmd = "%s --admindir=%s/var/lib/dpkg --instdir=%s" \ - " -r --force-depends %s" % \ + " -P --force-depends %s" % \ (bb.utils.which(os.getenv('PATH'), "dpkg"), self.target_rootfs, self.target_rootfs, ' '.join(pkgs)) @@ -1725,9 +1761,12 @@ class DpkgPM(PackageManager): with open(self.d.expand("${STAGING_ETCDIR_NATIVE}/apt/apt.conf.sample")) as apt_conf_sample: for line in apt_conf_sample.read().split("\n"): match_arch = re.match(" Architecture \".*\";$", line) + architectures = "" if match_arch: for base_arch in base_arch_list: - apt_conf.write(" Architecture \"%s\";\n" % base_arch) + architectures += "\"%s\";" % base_arch + apt_conf.write(" Architectures {%s};\n" % architectures); + apt_conf.write(" Architecture \"%s\";\n" % base_archs) else: line = re.sub("#ROOTFS#", self.target_rootfs, line) line = re.sub("#APTCONF#", self.apt_conf_dir, line) diff --git a/meta/lib/oe/patch.py b/meta/lib/oe/patch.py index b085c9d6b5..f68d40f8c8 100644 --- a/meta/lib/oe/patch.py +++ b/meta/lib/oe/patch.py @@ -199,10 +199,108 @@ class PatchTree(PatchSet): self.Pop(all=True) class GitApplyTree(PatchTree): + patch_line_prefix = '%% original patch' + def __init__(self, dir, d): PatchTree.__init__(self, dir, d) + @staticmethod + def extractPatchHeader(patchfile): + """ + Extract just the header lines from the top of a patch file + """ + lines = [] + with open(patchfile, 'r') as f: + for line in f.readlines(): + if line.startswith('Index: ') or line.startswith('diff -') or line.startswith('---'): + break + lines.append(line) + return lines + + @staticmethod + def prepareCommit(patchfile): + """ + Prepare a git commit command line based on the header from a patch file + (typically this is useful for patches that cannot be applied with "git am" due to formatting) + """ + import tempfile + import re + author_re = re.compile('[\S ]+ <\S+@\S+\.\S+>') + # Process patch header and extract useful information + lines = GitApplyTree.extractPatchHeader(patchfile) + outlines = [] + author = None + date = None + for line in lines: + if line.startswith('Subject: '): + subject = line.split(':', 1)[1] + # Remove any [PATCH][oe-core] etc. + subject = re.sub(r'\[.+?\]\s*', '', subject) + outlines.insert(0, '%s\n\n' % subject.strip()) + continue + if line.startswith('From: ') or line.startswith('Author: '): + authorval = line.split(':', 1)[1].strip().replace('"', '') + # git is fussy about author formatting i.e. it must be Name <email@domain> + if author_re.match(authorval): + author = authorval + continue + if line.startswith('Date: '): + if date is None: + dateval = line.split(':', 1)[1].strip() + # Very crude check for date format, since git will blow up if it's not in the right + # format. Without e.g. a python-dateutils dependency we can't do a whole lot more + if len(dateval) > 12: + date = dateval + continue + if line.startswith('Signed-off-by: '): + authorval = line.split(':', 1)[1].strip().replace('"', '') + # git is fussy about author formatting i.e. it must be Name <email@domain> + if author_re.match(authorval): + author = authorval + outlines.append(line) + # Write out commit message to a file + with tempfile.NamedTemporaryFile('w', delete=False) as tf: + tmpfile = tf.name + for line in outlines: + tf.write(line) + # Prepare git command + cmd = ["git", "commit", "-F", tmpfile] + # git doesn't like plain email addresses as authors + if author and '<' in author: + cmd.append('--author="%s"' % author) + if date: + cmd.append('--date="%s"' % date) + return (tmpfile, cmd) + + @staticmethod + def extractPatches(tree, startcommit, outdir): + import tempfile + import shutil + tempdir = tempfile.mkdtemp(prefix='oepatch') + try: + shellcmd = ["git", "format-patch", startcommit, "-o", tempdir] + out = runcmd(["sh", "-c", " ".join(shellcmd)], tree) + if out: + for srcfile in out.split(): + patchlines = [] + outfile = None + with open(srcfile, 'r') as f: + for line in f: + if line.startswith(GitApplyTree.patch_line_prefix): + outfile = line.split()[-1].strip() + continue + patchlines.append(line) + if not outfile: + outfile = os.path.basename(srcfile) + with open(os.path.join(outdir, outfile), 'w') as of: + for line in patchlines: + of.write(line) + finally: + shutil.rmtree(tempdir) + def _applypatch(self, patch, force = False, reverse = False, run = True): + import shutil + def _applypatchhelper(shellcmd, patch, force = False, reverse = False, run = True): if reverse: shellcmd.append('-R') @@ -214,12 +312,65 @@ class GitApplyTree(PatchTree): return runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + # Add hooks which add a pointer to the original patch file name in the commit message + reporoot = (runcmd("git rev-parse --show-toplevel".split(), self.dir) or '').strip() + if not reporoot: + raise Exception("Cannot get repository root for directory %s" % self.dir) + commithook = os.path.join(reporoot, '.git', 'hooks', 'commit-msg') + commithook_backup = commithook + '.devtool-orig' + applyhook = os.path.join(reporoot, '.git', 'hooks', 'applypatch-msg') + applyhook_backup = applyhook + '.devtool-orig' + if os.path.exists(commithook): + shutil.move(commithook, commithook_backup) + if os.path.exists(applyhook): + shutil.move(applyhook, applyhook_backup) + with open(commithook, 'w') as f: + # NOTE: the formatting here is significant; if you change it you'll also need to + # change other places which read it back + f.write('echo >> $1\n') + f.write('echo "%s: $PATCHFILE" >> $1\n' % GitApplyTree.patch_line_prefix) + os.chmod(commithook, 0755) + shutil.copy2(commithook, applyhook) try: - shellcmd = ["git", "--work-tree=.", "am", "-3", "-p%s" % patch['strippath']] - return _applypatchhelper(shellcmd, patch, force, reverse, run) - except CmdError: - shellcmd = ["git", "--git-dir=.", "apply", "-p%s" % patch['strippath']] - return _applypatchhelper(shellcmd, patch, force, reverse, run) + patchfilevar = 'PATCHFILE="%s"' % os.path.basename(patch['file']) + try: + shellcmd = [patchfilevar, "git", "--work-tree=.", "am", "-3", "--keep-cr", "-p%s" % patch['strippath']] + return _applypatchhelper(shellcmd, patch, force, reverse, run) + except CmdError: + # Need to abort the git am, or we'll still be within it at the end + try: + shellcmd = ["git", "--work-tree=.", "am", "--abort"] + runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + except CmdError: + pass + # Fall back to git apply + shellcmd = ["git", "--git-dir=.", "apply", "-p%s" % patch['strippath']] + try: + output = _applypatchhelper(shellcmd, patch, force, reverse, run) + except CmdError: + # Fall back to patch + output = PatchTree._applypatch(self, patch, force, reverse, run) + # Add all files + shellcmd = ["git", "add", "-f", "."] + output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + # Exclude the patches directory + shellcmd = ["git", "reset", "HEAD", self.patchdir] + output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + # Commit the result + (tmpfile, shellcmd) = self.prepareCommit(patch['file']) + try: + shellcmd.insert(0, patchfilevar) + output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + finally: + os.remove(tmpfile) + return output + finally: + os.remove(commithook) + os.remove(applyhook) + if os.path.exists(commithook_backup): + shutil.move(commithook_backup, commithook) + if os.path.exists(applyhook_backup): + shutil.move(applyhook_backup, applyhook) class QuiltTree(PatchSet): diff --git a/meta/lib/oe/recipeutils.py b/meta/lib/oe/recipeutils.py new file mode 100644 index 0000000000..159a103719 --- /dev/null +++ b/meta/lib/oe/recipeutils.py @@ -0,0 +1,279 @@ +# Utility functions for reading and modifying recipes +# +# Some code borrowed from the OE layer index +# +# Copyright (C) 2013-2014 Intel Corporation +# + +import sys +import os +import os.path +import tempfile +import textwrap +import difflib +import utils +import shutil +import re +from collections import OrderedDict, defaultdict + + +# Help us to find places to insert values +recipe_progression = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION', 'LICENSE', 'LIC_FILES_CHKSUM', 'PROVIDES', 'DEPENDS', 'PR', 'PV', 'SRCREV', 'SRC_URI', 'S', 'do_fetch', 'do_unpack', 'do_patch', 'EXTRA_OECONF', 'do_configure', 'EXTRA_OEMAKE', 'do_compile', 'do_install', 'do_populate_sysroot', 'INITSCRIPT', 'USERADD', 'GROUPADD', 'PACKAGES', 'FILES', 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RPROVIDES', 'RREPLACES', 'RCONFLICTS', 'ALLOW_EMPTY', 'do_package', 'do_deploy'] +# Variables that sometimes are a bit long but shouldn't be wrapped +nowrap_vars = ['SUMMARY', 'HOMEPAGE', 'BUGTRACKER'] +list_vars = ['SRC_URI', 'LIC_FILES_CHKSUM'] +meta_vars = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION'] + + +def pn_to_recipe(cooker, pn): + """Convert a recipe name (PN) to the path to the recipe file""" + import bb.providers + + if pn in cooker.recipecache.pkg_pn: + filenames = cooker.recipecache.pkg_pn[pn] + best = bb.providers.findBestProvider(pn, cooker.data, cooker.recipecache, cooker.recipecache.pkg_pn) + return best[3] + else: + return None + + +def get_unavailable_reasons(cooker, pn): + """If a recipe could not be found, find out why if possible""" + import bb.taskdata + taskdata = bb.taskdata.TaskData(None, skiplist=cooker.skiplist) + return taskdata.get_reasons(pn) + + +def parse_recipe(fn, d): + """Parse an individual recipe""" + import bb.cache + envdata = bb.cache.Cache.loadDataFull(fn, [], d) + return envdata + + +def get_var_files(fn, varlist, d): + """Find the file in which each of a list of variables is set. + Note: requires variable history to be enabled when parsing. + """ + envdata = parse_recipe(fn, d) + varfiles = {} + for v in varlist: + history = envdata.varhistory.variable(v) + files = [] + for event in history: + if 'file' in event and not 'flag' in event: + files.append(event['file']) + if files: + actualfile = files[-1] + else: + actualfile = None + varfiles[v] = actualfile + + return varfiles + + +def patch_recipe_file(fn, values, patch=False, relpath=''): + """Update or insert variable values into a recipe file (assuming you + have already identified the exact file you want to update.) + Note that some manual inspection/intervention may be required + since this cannot handle all situations. + """ + remainingnames = {} + for k in values.keys(): + remainingnames[k] = recipe_progression.index(k) if k in recipe_progression else -1 + remainingnames = OrderedDict(sorted(remainingnames.iteritems(), key=lambda x: x[1])) + + with tempfile.NamedTemporaryFile('w', delete=False) as tf: + def outputvalue(name): + rawtext = '%s = "%s"\n' % (name, values[name]) + if name in nowrap_vars: + tf.write(rawtext) + elif name in list_vars: + splitvalue = values[name].split() + if len(splitvalue) > 1: + linesplit = ' \\\n' + (' ' * (len(name) + 4)) + tf.write('%s = "%s%s"\n' % (name, linesplit.join(splitvalue), linesplit)) + else: + tf.write(rawtext) + else: + wrapped = textwrap.wrap(rawtext) + for wrapline in wrapped[:-1]: + tf.write('%s \\\n' % wrapline) + tf.write('%s\n' % wrapped[-1]) + + tfn = tf.name + with open(fn, 'r') as f: + # First runthrough - find existing names (so we know not to insert based on recipe_progression) + # Second runthrough - make the changes + existingnames = [] + for runthrough in [1, 2]: + currname = None + for line in f: + if not currname: + insert = False + for k in remainingnames.keys(): + for p in recipe_progression: + if re.match('^%s(_prepend|_append)*[ ?:=(]' % p, line): + if remainingnames[k] > -1 and recipe_progression.index(p) > remainingnames[k] and runthrough > 1 and not k in existingnames: + outputvalue(k) + del remainingnames[k] + break + for k in remainingnames.keys(): + if re.match('^%s[ ?:=]' % k, line): + currname = k + if runthrough == 1: + existingnames.append(k) + else: + del remainingnames[k] + break + if currname and runthrough > 1: + outputvalue(currname) + + if currname: + sline = line.rstrip() + if not sline.endswith('\\'): + currname = None + continue + if runthrough > 1: + tf.write(line) + f.seek(0) + if remainingnames: + tf.write('\n') + for k in remainingnames.keys(): + outputvalue(k) + + with open(tfn, 'U') as f: + tolines = f.readlines() + if patch: + with open(fn, 'U') as f: + fromlines = f.readlines() + relfn = os.path.relpath(fn, relpath) + diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn) + os.remove(tfn) + return diff + else: + with open(fn, 'w') as f: + f.writelines(tolines) + os.remove(tfn) + return None + +def localise_file_vars(fn, varfiles, varlist): + """Given a list of variables and variable history (fetched with get_var_files()) + find where each variable should be set/changed. This handles for example where a + recipe includes an inc file where variables might be changed - in most cases + we want to update the inc file when changing the variable value rather than adding + it to the recipe itself. + """ + fndir = os.path.dirname(fn) + os.sep + + first_meta_file = None + for v in meta_vars: + f = varfiles.get(v, None) + if f: + actualdir = os.path.dirname(f) + os.sep + if actualdir.startswith(fndir): + first_meta_file = f + break + + filevars = defaultdict(list) + for v in varlist: + f = varfiles[v] + # Only return files that are in the same directory as the recipe or in some directory below there + # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable + # in if we were going to set a value specific to this recipe) + if f: + actualfile = f + else: + # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it + if first_meta_file: + actualfile = first_meta_file + else: + actualfile = fn + + actualdir = os.path.dirname(actualfile) + os.sep + if not actualdir.startswith(fndir): + actualfile = fn + filevars[actualfile].append(v) + + return filevars + +def patch_recipe(d, fn, varvalues, patch=False, relpath=''): + """Modify a list of variable values in the specified recipe. Handles inc files if + used by the recipe. + """ + varlist = varvalues.keys() + varfiles = get_var_files(fn, varlist, d) + locs = localise_file_vars(fn, varfiles, varlist) + patches = [] + for f,v in locs.iteritems(): + vals = {k: varvalues[k] for k in v} + patchdata = patch_recipe_file(f, vals, patch, relpath) + if patch: + patches.append(patchdata) + + if patch: + return patches + else: + return None + + + +def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True): + """Copy (local) recipe files, including both files included via include/require, + and files referred to in the SRC_URI variable.""" + import bb.fetch2 + import oe.path + + # FIXME need a warning if the unexpanded SRC_URI value contains variable references + + uris = (d.getVar('SRC_URI', True) or "").split() + fetch = bb.fetch2.Fetch(uris, d) + if download: + fetch.download() + + # Copy local files to target directory and gather any remote files + bb_dir = os.path.dirname(d.getVar('FILE', True)) + os.sep + remotes = [] + includes = [path for path in d.getVar('BBINCLUDED', True).split() if + path.startswith(bb_dir) and os.path.exists(path)] + for path in fetch.localpaths() + includes: + # Only import files that are under the meta directory + if path.startswith(bb_dir): + if not whole_dir: + relpath = os.path.relpath(path, bb_dir) + subdir = os.path.join(tgt_dir, os.path.dirname(relpath)) + if not os.path.exists(subdir): + os.makedirs(subdir) + shutil.copy2(path, os.path.join(tgt_dir, relpath)) + else: + remotes.append(path) + # Simply copy whole meta dir, if requested + if whole_dir: + shutil.copytree(bb_dir, tgt_dir) + + return remotes + + +def get_recipe_patches(d): + """Get a list of the patches included in SRC_URI within a recipe.""" + patchfiles = [] + # Execute src_patches() defined in patch.bbclass - this works since that class + # is inherited globally + patches = bb.utils.exec_flat_python_func('src_patches', d) + for patch in patches: + _, _, local, _, _, parm = bb.fetch.decodeurl(patch) + patchfiles.append(local) + return patchfiles + + +def validate_pn(pn): + """Perform validation on a recipe name (PN) for a new recipe.""" + reserved_names = ['forcevariable', 'append', 'prepend', 'remove'] + if not re.match('[0-9a-z-]+', pn): + return 'Recipe name "%s" is invalid: only characters 0-9, a-z and - are allowed' % pn + elif pn in reserved_names: + return 'Recipe name "%s" is invalid: is a reserved keyword' % pn + elif pn.startswith('pn-'): + return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn + return '' + diff --git a/meta/lib/oe/rootfs.py b/meta/lib/oe/rootfs.py index c4735f2755..6fb749f049 100644 --- a/meta/lib/oe/rootfs.py +++ b/meta/lib/oe/rootfs.py @@ -40,6 +40,43 @@ class Rootfs(object): def _log_check(self): pass + def _log_check_warn(self): + r = re.compile('^(warn|Warn|NOTE: warn|NOTE: Warn|WARNING:)') + log_path = self.d.expand("${T}/log.do_rootfs") + with open(log_path, 'r') as log: + for line in log.read().split('\n'): + if 'log_check' in line or 'NOTE:' in line: + continue + + m = r.search(line) + if m: + bb.warn('log_check: There is a warn message in the logfile') + bb.warn('log_check: Matched keyword: [%s]' % m.group()) + bb.warn('log_check: %s\n' % line) + + def _log_check_error(self): + r = re.compile(self.log_check_regex) + log_path = self.d.expand("${T}/log.do_rootfs") + with open(log_path, 'r') as log: + found_error = 0 + message = "\n" + for line in log.read().split('\n'): + if 'log_check' in line: + continue + + m = r.search(line) + if m: + found_error = 1 + bb.warn('log_check: There were error messages in the logfile') + bb.warn('log_check: Matched keyword: [%s]\n\n' % m.group()) + + if found_error >= 1 and found_error <= 5: + message += line + '\n' + found_error += 1 + + if found_error == 6: + bb.fatal(message) + def _insert_feed_uris(self): if bb.utils.contains("IMAGE_FEATURES", "package-management", True, False, self.d): @@ -118,17 +155,19 @@ class Rootfs(object): if self.d.getVar('USE_DEVFS', True) != "1": self._create_devfs() - self._uninstall_uneeded() + self._uninstall_unneeded() self._insert_feed_uris() self._run_ldconfig() - self._generate_kernel_module_deps() + if self.d.getVar('USE_DEPMOD', True) != "0": + self._generate_kernel_module_deps() self._cleanup() + self._log_check() - def _uninstall_uneeded(self): + def _uninstall_unneeded(self): # Remove unneeded init script symlinks delayed_postinsts = self._get_delayed_postinsts() if delayed_postinsts is None: @@ -137,34 +176,41 @@ class Rootfs(object): self.d.getVar('IMAGE_ROOTFS', True), "run-postinsts", "remove"]) - # Remove unneeded package-management related components - if bb.utils.contains("IMAGE_FEATURES", "package-management", - True, False, self.d): - return + runtime_pkgmanage = bb.utils.contains("IMAGE_FEATURES", "package-management", + True, False, self.d) + if not runtime_pkgmanage: + # Remove components that we don't need if we're not going to install + # additional packages at runtime + if delayed_postinsts is None: + installed_pkgs_dir = self.d.expand('${WORKDIR}/installed_pkgs.txt') + pkgs_to_remove = list() + with open(installed_pkgs_dir, "r+") as installed_pkgs: + pkgs_installed = installed_pkgs.read().split('\n') + for pkg_installed in pkgs_installed[:]: + pkg = pkg_installed.split()[0] + if pkg in ["update-rc.d", + "base-passwd", + "shadow", + "update-alternatives", + self.d.getVar("ROOTFS_BOOTSTRAP_INSTALL", True) + ]: + pkgs_to_remove.append(pkg) + pkgs_installed.remove(pkg_installed) + + if len(pkgs_to_remove) > 0: + self.pm.remove(pkgs_to_remove, False) + # Update installed_pkgs.txt + open(installed_pkgs_dir, "w+").write('\n'.join(pkgs_installed)) - if delayed_postinsts is None: - installed_pkgs_dir = self.d.expand('${WORKDIR}/installed_pkgs.txt') - pkgs_to_remove = list() - with open(installed_pkgs_dir, "r+") as installed_pkgs: - pkgs_installed = installed_pkgs.read().split('\n') - for pkg_installed in pkgs_installed[:]: - pkg = pkg_installed.split()[0] - if pkg in ["update-rc.d", - "base-passwd", - self.d.getVar("ROOTFS_BOOTSTRAP_INSTALL", True) - ]: - pkgs_to_remove.append(pkg) - pkgs_installed.remove(pkg_installed) - - if len(pkgs_to_remove) > 0: - self.pm.remove(pkgs_to_remove, False) - # Update installed_pkgs.txt - open(installed_pkgs_dir, "w+").write('\n'.join(pkgs_installed)) + else: + self._save_postinsts() - else: - self._save_postinsts() + post_uninstall_cmds = self.d.getVar("ROOTFS_POSTUNINSTALL_COMMAND", True) + execute_pre_post_process(self.d, post_uninstall_cmds) - self.pm.remove_packaging_data() + if not runtime_pkgmanage: + # Remove the package manager data files + self.pm.remove_packaging_data() def _run_intercepts(self): intercepts_dir = os.path.join(self.d.getVar('WORKDIR', True), @@ -209,16 +255,17 @@ class Rootfs(object): 'new', '-v']) def _generate_kernel_module_deps(self): - kernel_abi_ver_file = os.path.join(self.d.getVar('STAGING_KERNEL_DIR', True), + kernel_abi_ver_file = oe.path.join(self.d.getVar('PKGDATA_DIR', True), "kernel-depmod", 'kernel-abiversion') - if os.path.exists(kernel_abi_ver_file): - kernel_ver = open(kernel_abi_ver_file).read().strip(' \n') - modules_dir = os.path.join(self.image_rootfs, 'lib', 'modules', kernel_ver) + if not os.path.exists(kernel_abi_ver_file): + bb.fatal("No kernel-abiversion file found (%s), cannot run depmod, aborting" % kernel_abi_ver_file) - bb.utils.mkdirhier(modules_dir) + kernel_ver = open(kernel_abi_ver_file).read().strip(' \n') + modules_dir = os.path.join(self.image_rootfs, 'lib', 'modules', kernel_ver) - self._exec_shell_cmd(['depmodwrapper', '-a', '-b', self.image_rootfs, - kernel_ver]) + bb.utils.mkdirhier(modules_dir) + + self._exec_shell_cmd(['depmodwrapper', '-a', '-b', self.image_rootfs, kernel_ver]) """ Create devfs: @@ -248,7 +295,7 @@ class Rootfs(object): class RpmRootfs(Rootfs): def __init__(self, d, manifest_dir): super(RpmRootfs, self).__init__(d) - + self.log_check_regex = '(unpacking of archive failed|Cannot find package|exit 1|ERR|Fail)' self.manifest = RpmManifest(d, manifest_dir) self.pm = RpmPM(d, @@ -320,8 +367,6 @@ class RpmRootfs(Rootfs): self.pm.install_complementary() - self._log_check() - if self.inc_rpm_image_gen == "1": self.pm.backup_packaging_data() @@ -347,20 +392,6 @@ class RpmRootfs(Rootfs): # already saved in /etc/rpm-postinsts pass - def _log_check_warn(self): - r = re.compile('(warn|Warn)') - log_path = self.d.expand("${T}/log.do_rootfs") - with open(log_path, 'r') as log: - for line in log.read().split('\n'): - if 'log_check' in line: - continue - - m = r.search(line) - if m: - bb.warn('log_check: There is a warn message in the logfile') - bb.warn('log_check: Matched keyword: [%s]' % m.group()) - bb.warn('log_check: %s\n' % line) - def _log_check_error(self): r = re.compile('(unpacking of archive failed|Cannot find package|exit 1|ERR|Fail)') log_path = self.d.expand("${T}/log.do_rootfs") @@ -402,12 +433,16 @@ class RpmRootfs(Rootfs): # __db.00* (Berkeley DB files that hold locks, rpm specific environment # settings, etc.), that should not get into the final rootfs self.pm.unlock_rpm_db() - bb.utils.remove(self.image_rootfs + "/install", True) + if os.path.isdir(self.pm.install_dir_path + "/tmp") and not os.listdir(self.pm.install_dir_path + "/tmp"): + bb.utils.remove(self.pm.install_dir_path + "/tmp", True) + if os.path.isdir(self.pm.install_dir_path) and not os.listdir(self.pm.install_dir_path): + bb.utils.remove(self.pm.install_dir_path, True) class DpkgRootfs(Rootfs): def __init__(self, d, manifest_dir): super(DpkgRootfs, self).__init__(d) + self.log_check_regex = '^E:' bb.utils.remove(self.image_rootfs, True) bb.utils.remove(self.d.getVar('MULTILIB_TEMP_ROOTFS', True), True) @@ -479,7 +514,8 @@ class DpkgRootfs(Rootfs): self.pm.mark_packages("unpacked", registered_pkgs.split()) def _log_check(self): - pass + self._log_check_warn() + self._log_check_error() def _cleanup(self): pass @@ -488,6 +524,7 @@ class DpkgRootfs(Rootfs): class OpkgRootfs(Rootfs): def __init__(self, d, manifest_dir): super(OpkgRootfs, self).__init__(d) + self.log_check_regex = '(exit 1|Collected errors)' self.manifest = OpkgManifest(d, manifest_dir) self.opkg_conf = self.d.getVar("IPKGCONF_TARGET", True) @@ -749,7 +786,8 @@ class OpkgRootfs(Rootfs): self.pm.mark_packages("unpacked", registered_pkgs.split()) def _log_check(self): - pass + self._log_check_warn() + self._log_check_error() def _cleanup(self): pass diff --git a/meta/lib/oe/sdk.py b/meta/lib/oe/sdk.py index c57a441941..a6767412c7 100644 --- a/meta/lib/oe/sdk.py +++ b/meta/lib/oe/sdk.py @@ -269,6 +269,8 @@ class DpkgSdk(Sdk): bb.note("Installing TARGET packages") self._populate_sysroot(self.target_pm, self.target_manifest) + self.target_pm.install_complementary(self.d.getVar('SDKIMAGE_INSTALL_COMPLEMENTARY', True)) + execute_pre_post_process(self.d, self.d.getVar("POPULATE_SDK_POST_TARGET_COMMAND", True)) self._copy_apt_dir_to(os.path.join(self.sdk_target_sysroot, "etc", "apt")) diff --git a/meta/lib/oe/sstatesig.py b/meta/lib/oe/sstatesig.py index af7617ee61..9d6d7c42fc 100644 --- a/meta/lib/oe/sstatesig.py +++ b/meta/lib/oe/sstatesig.py @@ -137,13 +137,16 @@ class SignatureGeneratorOEBasicHash(bb.siggen.SignatureGeneratorBasicHash): return super(bb.siggen.SignatureGeneratorBasicHash, self).dump_sigtask(fn, task, stampbase, runtime) - def dump_lockedsigs(self, sigfile=None): + def dump_lockedsigs(self, sigfile=None, taskfilter=None): if not sigfile: sigfile = os.getcwd() + "/locked-sigs.inc" bb.plain("Writing locked sigs to %s" % sigfile) types = {} for k in self.runtaskdeps: + if taskfilter: + if not k in taskfilter: + continue fn = k.rsplit(".",1)[0] t = self.lockedhashfn[fn].split(" ")[1].split(":")[5] t = 't-' + t.replace('_', '-') @@ -203,9 +206,6 @@ def find_siginfo(pn, taskname, taskhashlist, d): if key.startswith('virtual:native:'): pn = pn + '-native' - if taskname in ['do_fetch', 'do_unpack', 'do_patch', 'do_populate_lic']: - pn.replace("-native", "") - filedates = {} # First search in stamps dir @@ -246,7 +246,10 @@ def find_siginfo(pn, taskname, taskhashlist, d): localdata.setVar('PV', '*') localdata.setVar('PR', '*') localdata.setVar('BB_TASKHASH', hashval) - if pn.endswith('-native') or "-cross-" in pn or "-crosssdk-" in pn: + swspec = localdata.getVar('SSTATE_SWSPEC', True) + if taskname in ['do_fetch', 'do_unpack', 'do_patch', 'do_populate_lic', 'do_preconfigure'] and swspec: + localdata.setVar('SSTATE_PKGSPEC', '${SSTATE_SWSPEC}') + elif pn.endswith('-native') or "-cross-" in pn or "-crosssdk-" in pn: localdata.setVar('SSTATE_EXTRAPATH', "${NATIVELSBSTRING}/") sstatename = taskname[3:] filespec = '%s_%s.*.siginfo' % (localdata.getVar('SSTATE_PKG', True), sstatename) diff --git a/meta/lib/oe/terminal.py b/meta/lib/oe/terminal.py index 0a623c75b1..4f5c611615 100644 --- a/meta/lib/oe/terminal.py +++ b/meta/lib/oe/terminal.py @@ -2,6 +2,7 @@ import logging import oe.classutils import shlex from bb.process import Popen, ExecutionError +from distutils.version import LooseVersion logger = logging.getLogger('BitBake.OE.Terminal') @@ -52,9 +53,17 @@ class XTerminal(Terminal): raise UnsupportedTerminal(self.name) class Gnome(XTerminal): - command = 'gnome-terminal -t "{title}" -x {command}' + command = 'gnome-terminal -t "{title}" --disable-factory -x {command}' priority = 2 + def __init__(self, sh_cmd, title=None, env=None, d=None): + # Check version + vernum = check_terminal_version("gnome-terminal") + if vernum and LooseVersion(vernum) >= '3.10': + logger.debug(1, 'Gnome-Terminal 3.10 or later does not support --disable-factory') + self.command = 'gnome-terminal -t "{title}" -x {command}' + XTerminal.__init__(self, sh_cmd, title, env, d) + class Mate(XTerminal): command = 'mate-terminal -t "{title}" -x {command}' priority = 2 @@ -63,17 +72,20 @@ class Xfce(XTerminal): command = 'xfce4-terminal -T "{title}" -e "{command}"' priority = 2 +class Terminology(XTerminal): + command = 'terminology -T="{title}" -e {command}' + priority = 2 + class Konsole(XTerminal): - command = 'konsole -T "{title}" -e {command}' + command = 'konsole --nofork -p tabtitle="{title}" -e {command}' priority = 2 def __init__(self, sh_cmd, title=None, env=None, d=None): # Check version - vernum = check_konsole_version("konsole") - if vernum: - if vernum.split('.')[0] == "2": - logger.debug(1, 'Konsole from KDE 4.x will not work as devshell, skipping') - raise UnsupportedTerminal(self.name) + vernum = check_terminal_version("konsole") + if vernum and LooseVersion(vernum) < '2.0.0': + # Konsole from KDE 3.x + self.command = 'konsole -T "{title}" -e {command}' XTerminal.__init__(self, sh_cmd, title, env, d) class XTerm(XTerminal): @@ -112,6 +124,24 @@ class TmuxRunning(Terminal): if not os.getenv('TMUX'): raise UnsupportedTerminal('tmux is not running') + if not check_tmux_pane_size('tmux'): + raise UnsupportedTerminal('tmux pane too small') + + Terminal.__init__(self, sh_cmd, title, env, d) + +class TmuxNewWindow(Terminal): + """Open a new window in the current running tmux session""" + name = 'tmux-new-window' + command = 'tmux new-window -n "{title}" "{command}"' + priority = 2.70 + + def __init__(self, sh_cmd, title=None, env=None, d=None): + if not bb.utils.which(os.getenv('PATH'), 'tmux'): + raise UnsupportedTerminal('tmux is not installed') + + if not os.getenv('TMUX'): + raise UnsupportedTerminal('tmux is not running') + Terminal.__init__(self, sh_cmd, title, env, d) class Tmux(Terminal): @@ -180,10 +210,27 @@ def spawn(name, sh_cmd, title=None, env=None, d=None): if pipe.returncode != 0: raise ExecutionError(sh_cmd, pipe.returncode, output) -def check_konsole_version(konsole): +def check_tmux_pane_size(tmux): + import subprocess as sub + try: + p = sub.Popen('%s list-panes -F "#{?pane_active,#{pane_height},}"' % tmux, + shell=True,stdout=sub.PIPE,stderr=sub.PIPE) + out, err = p.communicate() + size = int(out.strip()) + except OSError as exc: + import errno + if exc.errno == errno.ENOENT: + return None + else: + raise + if size/2 >= 19: + return True + return False + +def check_terminal_version(terminalName): import subprocess as sub try: - p = sub.Popen(['sh', '-c', '%s --version' % konsole],stdout=sub.PIPE,stderr=sub.PIPE) + p = sub.Popen(['sh', '-c', '%s --version' % terminalName],stdout=sub.PIPE,stderr=sub.PIPE) out, err = p.communicate() ver_info = out.rstrip().split('\n') except OSError as exc: @@ -196,6 +243,8 @@ def check_konsole_version(konsole): for ver in ver_info: if ver.startswith('Konsole'): vernum = ver.split(' ')[-1] + if ver.startswith('GNOME Terminal'): + vernum = ver.split(' ')[-1] return vernum def distro_name(): diff --git a/meta/lib/oe/utils.py b/meta/lib/oe/utils.py index 35442568e2..b8224de20b 100644 --- a/meta/lib/oe/utils.py +++ b/meta/lib/oe/utils.py @@ -42,8 +42,16 @@ def version_less_or_equal(variable, checkvalue, truevalue, falsevalue, d): return falsevalue def both_contain(variable1, variable2, checkvalue, d): - if d.getVar(variable1,1).find(checkvalue) != -1 and d.getVar(variable2,1).find(checkvalue) != -1: - return checkvalue + val1 = d.getVar(variable1, True) + val2 = d.getVar(variable2, True) + val1 = set(val1.split()) + val2 = set(val2.split()) + if isinstance(checkvalue, basestring): + checkvalue = set(checkvalue.split()) + else: + checkvalue = set(checkvalue) + if checkvalue.issubset(val1) and checkvalue.issubset(val2): + return " ".join(checkvalue) else: return "" @@ -180,3 +188,7 @@ def multiprocess_exec(commands, function): pool.terminate() pool.join() raise + +def squashspaces(string): + import re + return re.sub("\s+", " ", string).strip() |