Provides an easy means to work on developing applications and system components with the build system.
You can start by creating a workspace layer (only needs to be done once): $ devtool create-workspace Then you can use the other functions, for example to "modify" the source for an existing recipe: $ devtool modify -x pango /home/projects/pango Parsing recipes..done. INFO: Fetching pango... INFO: Unpacking... INFO: Patching... INFO: Source tree extracted to /tmp/woot2/ INFO: Recipe pango now set up to build from /home/projects/pango The pango source is now extracted to /home/projects/pango, managed in git, with each patch as a commit, and a bbappend is created in the workspace layer to use the source in /home/projects/pango when building. Additionally, you can add a new piece of software: $ devtool add pv /home/projects/pv INFO: Recipe /path/to/workspace/recipes/pv/pv.bb has been automatically created; further editing may be required to make it fully functional The latter uses recipetool to create a skeleton recipe and again sets up a bbappend to use the source in /home/projects/pv when building. [YOCTO #6561] [YOCTO #6653] [YOCTO #6656] Signed-off-by: Paul Eggleton <[email protected]> --- scripts/devtool | 223 +++++++++++++++++++++++ scripts/lib/devtool/__init__.py | 79 ++++++++ scripts/lib/devtool/standard.py | 390 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 692 insertions(+) create mode 100755 scripts/devtool create mode 100644 scripts/lib/devtool/__init__.py create mode 100644 scripts/lib/devtool/standard.py diff --git a/scripts/devtool b/scripts/devtool new file mode 100755 index 0000000..eea93e0 --- /dev/null +++ b/scripts/devtool @@ -0,0 +1,223 @@ +#!/usr/bin/env python + +# OpenEmbedded Development tool +# +# Copyright (C) 2014 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys +import os +import argparse +import glob +import ConfigParser +import subprocess +import logging + +basepath = '' +workspace = {} +config = None +context = None + +def logger_create(name): + logger = logging.getLogger(name) + loggerhandler = logging.StreamHandler() + loggerhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) + logger.addHandler(loggerhandler) + logger.setLevel(logging.INFO) + return logger +logger = logger_create('devtool') + +plugins = [] + +def load_plugins(pluginpath): + global plugins + + def load_plugin(name): + logger.debug('Loading plugin %s' % name) + fp, pathname, description = imp.find_module(name, [pluginpath]) + try: + return imp.load_module(name, fp, pathname, description) + finally: + if fp: + fp.close() + + logger.debug('Loading plugins from %s...' % pluginpath) + import imp + for fn in glob.glob(os.path.join(pluginpath, '*.py')): + name = os.path.splitext(os.path.basename(fn))[0] + if name != '__init__': + plugin = load_plugin(name) + if hasattr(plugin, 'plugin_init'): + plugin.plugin_init(plugins) + plugins.append(plugin) + + +class ConfigHandler(object): + config_file = '' + config_obj = None + init_path = '' + workspace_path = '' + + def __init__(self, filename): + self.config_file = filename + self.config_obj = ConfigParser.SafeConfigParser() + + def get(self, section, option, default=None): + try: + ret = self.config_obj.get(section, option) + except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): + if default != None: + ret = default + else: + raise + return ret + + def read(self): + if os.path.exists(self.config_file): + self.config_obj.read(self.config_file) + + if self.config_obj.has_option('General', 'init_path'): + pth = self.get('General', 'init_path') + self.init_path = os.path.join(basepath, pth) + if not os.path.exists(self.init_path): + logger.error('init_path %s specified in config file cannot be found' % pth) + return False + + self.workspace_path = self.get('General', 'workspace_path', os.path.join(basepath, 'workspace')) + + return True + else: + self.config_obj.add_section('General') + return True + + def write(self): + self.config_obj.set('General', 'workspace_path', self.workspace_path) + print self.config_file + with open(self.config_file, 'w') as f: + self.config_obj.write(f) + +class Context: + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + +def read_workspace(): + global workspace + workspace = {} + if not os.path.exists(os.path.join(config.workspace_path, 'conf', 'layer.conf')): + if context.fixed_setup: + logger.error("workspace layer not set up") + else: + logger.error("workspace layer not set up - you can create one by running %s create-workspace" % os.path.basename(sys.argv[0])) + sys.exit(1) + + logger.debug('Reading workspace in %s' % config.workspace_path) + for fn in glob.glob(os.path.join(config.workspace_path, 'appends', '*.bbappend')): + pn = os.path.splitext(os.path.basename(fn))[0].split('_')[0] + with open(fn, 'r') as f: + for line in f: + if line.startswith('EXTERNALSRC ='): + splitval = line.split('=', 2) + workspace[pn] = splitval[1].strip('" \n\r\t') + break + +def main(): + global basepath + global config + global context + + context = Context(fixed_setup=False) + + # Default basepath + basepath = os.path.dirname(os.path.abspath(__file__)) + pth = basepath + while pth != '' and pth != os.sep: + if os.path.exists(os.path.join(pth, '.devtoolbase')): + context.fixed_setup = True + basepath = pth + break + pth = os.path.dirname(pth) + + scripts_path = os.path.dirname(os.path.realpath(__file__)) + lib_path = scripts_path + '/lib' + sys.path = sys.path + [lib_path] + + parser = argparse.ArgumentParser(description="OpenEmbedded development tool") + parser.add_argument('--basepath', help='Base directory of SDK / build directory') + parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true') + parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true') + + subparsers = parser.add_subparsers(dest="subparser_name") + + load_plugins(os.path.join(scripts_path, 'lib', 'devtool')) + for plugin in plugins: + if hasattr(plugin, 'register_commands'): + plugin.register_commands(subparsers, context) + + args = parser.parse_args() + + if args.debug: + logger.setLevel(logging.DEBUG) + elif args.quiet: + logger.setLevel(logging.ERROR) + + if args.basepath: + # Override + basepath = args.basepath + elif not context.fixed_setup: + basepath = os.environ.get('BUILDDIR') + if not basepath: + logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)") + sys.exit(1) + + logger.debug('Using basepath %s' % basepath) + + config = ConfigHandler(os.path.join(basepath, 'devtool.conf')) + if not config.read(): + return -1 + + bitbake_subdir = config.get('General', 'bitbake_subdir', '') + if bitbake_subdir: + # Normally set for use within the SDK + logger.debug('Using bitbake subdir %s' % bitbake_subdir) + sys.path.insert(0, os.path.join(basepath, bitbake_subdir, 'lib')) + core_meta_subdir = config.get('General', 'core_meta_subdir') + sys.path.insert(0, os.path.join(basepath, core_meta_subdir, 'lib')) + else: + # Standard location + import scriptpath + bitbakepath = scriptpath.add_bitbake_lib_path() + if not bitbakepath: + logger.error("Unable to find bitbake by searching parent directory of this script or PATH") + sys.exit(1) + logger.debug('Using standard bitbake path %s' % bitbakepath) + scriptpath.add_oe_lib_path() + + if args.subparser_name != 'create-workspace': + read_workspace() + + ret = args.func(args, config, basepath, workspace) + + return ret + + +if __name__ == "__main__": + try: + ret = main() + except Exception: + ret = 1 + import traceback + traceback.print_exc(5) + sys.exit(ret) diff --git a/scripts/lib/devtool/__init__.py b/scripts/lib/devtool/__init__.py new file mode 100644 index 0000000..3cad708 --- /dev/null +++ b/scripts/lib/devtool/__init__.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python + +# Development tool - utility functions for plugins +# +# Copyright (C) 2014 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +import os +import sys +import subprocess +import logging + +logger = logging.getLogger('devtool') + +def exec_build_env_command(init_path, builddir, cmd, watch=False, **options): + import bb + if not 'cwd' in options: + options["cwd"] = builddir + if init_path: + logger.debug('Executing command: "%s" using init path %s' % (cmd, init_path)) + init_prefix = '. %s %s > /dev/null && ' % (init_path, builddir) + else: + logger.debug('Executing command "%s"' % cmd) + init_prefix = '' + if watch: + return exec_watch('%s%s' % (init_prefix, cmd), **options) + else: + return bb.process.run('%s%s' % (init_prefix, cmd), **options) + +def exec_watch(cmd, **options): + if isinstance(cmd, basestring) and not "shell" in options: + options["shell"] = True + + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **options + ) + + buf = '' + while True: + out = process.stdout.read(1) + if out: + sys.stdout.write(out) + sys.stdout.flush() + buf += out + elif out == '' and process.poll() != None: + break + return buf + +def setup_tinfoil(): + scripts_path = os.path.abspath(os.path.dirname(os.path.abspath(sys.argv[0]))) + lib_path = scripts_path + '/lib' + sys.path = sys.path + [lib_path] + + import scriptpath + bitbakepath = scriptpath.add_bitbake_lib_path() + if not bitbakepath: + logger.error("Unable to find bitbake by searching parent directory of this script or PATH") + sys.exit(1) + + import bb.tinfoil + import logging + tinfoil = bb.tinfoil.Tinfoil() + tinfoil.prepare(False) + tinfoil.logger.setLevel(logging.WARNING) + return tinfoil + diff --git a/scripts/lib/devtool/standard.py b/scripts/lib/devtool/standard.py new file mode 100644 index 0000000..82c5b53 --- /dev/null +++ b/scripts/lib/devtool/standard.py @@ -0,0 +1,390 @@ +# Development tool - standard commands plugin +# +# Copyright (C) 2014 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import os +import sys +import re +import shutil +import glob +import tempfile +import logging +from devtool import exec_build_env_command, setup_tinfoil + +logger = logging.getLogger('devtool') + +def plugin_init(pluginlist): + pass + + +def create_workspace(args, config, basepath, workspace): + import bb + + if args.directory: + workspacedir = os.path.abspath(args.directory) + else: + workspacedir = os.path.abspath(os.path.join(basepath, 'workspace')) + + confdir = os.path.join(workspacedir, 'conf') + if os.path.exists(os.path.join(confdir, 'layer.conf')): + logger.info('Specified workspace already set up, leaving as-is') + else: + bb.utils.mkdirhier(confdir) + with open(os.path.join(confdir, 'layer.conf'), 'w') as f: + f.write('# ### workspace layer auto-generated by devtool ###\n') + f.write('BBPATH =. "$' + '{LAYERDIR}:"\n') + f.write('BBFILES += "$' + '{LAYERDIR}/recipes/*/*.bb \\\n') + f.write(' $' + '{LAYERDIR}/appends/*.bbappend"\n') + f.write('BBFILE_COLLECTIONS += "workspacelayer"\n') + f.write('BBFILE_PATTERN_workspacelayer = "^$' + '{LAYERDIR}/"\n') + f.write('BBFILE_PRIORITY_workspacelayer = "99"\n') + if not args.create_only: + # Add the workspace layer to bblayers.conf + bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf') + if not os.path.exists(bblayers_conf): + logger.error('Unable to find bblayers.conf') + return -1 + newlines = [] + with open(bblayers_conf, 'r') as f: + bblayers_found = False + updated = False + in_bblayers = False + bblayers = '' + for line in f: + if in_bblayers: + value = line.rstrip() + bblayers += value[:-1] + if value.endswith('"'): + in_bblayers = False + bblayers = bblayers.split() + if workspacedir not in bblayers: + updated = True + bblayers.append(workspacedir) + if workspacedir != config.workspace_path and config.workspace_path in bblayers: + bblayers.remove(config.workspace_path) + newlines.append('BBLAYERS ?= " \\\n') + for layer in bblayers: + newlines.append(' %s \\\n' % layer) + newlines.append(' "\n') + else: + if line.startswith('BBLAYERS '): + bblayers_found = True + in_bblayers = True + value = line.split('"', 1)[1].rstrip() + if value.endswith('\\'): + value = value[:-1] + bblayers = value + else: + newlines.append(line) + if not bblayers_found: + logger.error('Found bblayers.conf but could not find BBLAYERS value to update') + return -1 + if updated: + # Write out the new bblayers.conf + with open(bblayers_conf, 'w') as f: + f.writelines(newlines) + if config.workspace_path != workspacedir: + # Update our config to point to the new location + config.workspace_path = workspacedir + config.write() + + +def add(args, config, basepath, workspace): + import bb + import oe.recipeutils + + if args.recipename in workspace: + logger.error("recipe %s is already in your workspace" % args.recipename) + return -1 + + reason = oe.recipeutils.validate_pn(args.recipename) + if reason: + logger.error(reason) + return -1 + + srctree = os.path.abspath(args.srctree) + appendpath = os.path.join(config.workspace_path, 'appends') + if not os.path.exists(appendpath): + os.makedirs(appendpath) + + recipedir = os.path.join(config.workspace_path, 'recipes', args.recipename) + bb.utils.mkdirhier(recipedir) + if args.version: + if '_' in args.version or ' ' in args.version: + logger.error('Invalid version string "%s"' % args.version) + return -1 + bp = "%s_%s" % (args.recipename, args.version) + else: + bp = args.recipename + recipefile = os.path.join(recipedir, "%s.bb" % bp) + stdout, stderr = exec_build_env_command(config.init_path, basepath, 'recipetool create -o %s %s' % (recipefile, srctree)) + logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile) + + _add_md5(config, args.recipename, recipefile) + + appendfile = os.path.join(appendpath, '%s.bbappend' % args.recipename) + with open(appendfile, 'w') as f: + f.write('inherit externalsrc\n') + f.write('EXTERNALSRC = "%s"\n' % srctree) + f.write('do_compile[nostamp] = "1"\n') + + _add_md5(config, args.recipename, appendfile) + + return 0 + + +def extract(args, config, basepath, workspace): + import bb + import oe.recipeutils + + tinfoil = setup_tinfoil() + + recipefile = oe.recipeutils.pn_to_recipe(tinfoil.cooker, args.recipename) + if not recipefile: + logger.error("Unable to find any recipe file matching %s" % args.recipename) + return -1 + rd = oe.recipeutils.parse_recipe(recipefile, tinfoil.config_data) + + srctree = os.path.abspath(args.srctree) + return _extract_source(srctree, rd) + + +def _extract_source(srctree, d): + pn = d.getVar('PN', True) + + if os.path.exists(srctree): + if not os.path.isdir(srctree): + logger.error("output path %s exists and is not a directory" % srctree) + return -1 + elif os.listdir(srctree): + logger.error("output path %s already exists and is non-empty" % srctree) + return -1 + + # Prepare for shutil.move later on + bb.utils.mkdirhier(srctree) + os.rmdir(srctree) + + tempdir = tempfile.mkdtemp(prefix='devtool') + try: + crd = d.createCopy() + # Make a subdir so we guard against WORKDIR==S + workdir = os.path.join(tempdir, 'workdir') + crd.setVar('WORKDIR', workdir) + crd.setVar('T', os.path.join(tempdir, 'temp')) + logger.info('Fetching %s...' % pn) + bb.build.exec_func('do_fetch', crd) + logger.info('Unpacking...') + bb.build.exec_func('do_unpack', crd) + srcsubdir = crd.getVar('S', True) + if srcsubdir != workdir and os.path.dirname(srcsubdir) != workdir: + # Handle if S is set to a subdirectory of the source + srcsubdir = os.path.join(workdir, os.path.relpath(srcsubdir, workdir).split(os.sep)[0]) + + patchdir = os.path.join(srcsubdir, 'patches') + haspatches = False + if os.path.exists(patchdir): + if os.listdir(patchdir): + haspatches = True + else: + os.rmdir(patchdir) + + if not os.listdir(srcsubdir): + logger.error("no source unpacked to S, perhaps the %s recipe doesn't use any source?" % pn) + return -1 + + if not os.path.exists(os.path.join(srcsubdir, '.git')): + bb.process.run('git init', cwd=srcsubdir) + bb.process.run('git add .', cwd=srcsubdir) + bb.process.run('git commit -q -m "Initial commit from upstream at version %s"' % crd.getVar('PV', True), cwd=srcsubdir) + logger.info('Patching...') + crd.setVar('PATCHTOOL', 'git') + bb.build.exec_func('do_patch', crd) + + if os.path.exists(patchdir): + shutil.rmtree(patchdir) + if haspatches: + bb.process.run('git checkout patches', cwd=srcsubdir) + + shutil.move(srcsubdir, srctree) + logger.info('Source tree extracted to %s' % srctree) + finally: + shutil.rmtree(tempdir) + return 0 + +def _add_md5(config, recipename, filename): + import bb.utils + md5 = bb.utils.md5_file(filename) + with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a') as f: + f.write('%s|%s|%s\n' % (recipename, os.path.relpath(filename, config.workspace_path), md5)) + +def _check_preserve(config, recipename): + import bb.utils + origfile = os.path.join(config.workspace_path, '.devtool_md5') + newfile = os.path.join(config.workspace_path, '.devtool_md5_new') + preservepath = os.path.join(config.workspace_path, 'attic') + with open(origfile, 'r') as f: + with open(newfile, 'w') as tf: + for line in f.readlines(): + splitline = line.rstrip().split('|') + if splitline[0] == recipename: + removefile = os.path.join(config.workspace_path, splitline[1]) + md5 = bb.utils.md5_file(removefile) + if splitline[2] != md5: + bb.utils.mkdirhier(preservepath) + preservefile = os.path.basename(removefile) + logger.warn('File %s modified since it was written, preserving in %s' % (preservefile, preservepath)) + shutil.move(removefile, os.path.join(preservepath, preservefile)) + else: + os.remove(removefile) + else: + tf.write(line) + os.rename(newfile, origfile) + + return False + + +def modify(args, config, basepath, workspace): + import bb + import oe.recipeutils + + if args.recipename in workspace: + logger.error("recipe %s is already in your workspace" % args.recipename) + return -1 + + if not args.extract: + if not os.path.isdir(args.srctree): + logger.error("directory %s does not exist or not a directory (specify -x to extract source from recipe)" % args.srctree) + return -1 + + tinfoil = setup_tinfoil() + + recipefile = oe.recipeutils.pn_to_recipe(tinfoil.cooker, args.recipename) + if not recipefile: + logger.error("Unable to find any recipe file matching %s" % args.recipename) + return -1 + rd = oe.recipeutils.parse_recipe(recipefile, tinfoil.config_data) + + srctree = os.path.abspath(args.srctree) + if args.extract: + ret = _extract_source(args.srctree, rd) + if ret: + return ret + + # Handle if S is set to a subdirectory of the source + s = rd.getVar('S', True) + workdir = rd.getVar('WORKDIR', True) + if s != workdir and os.path.dirname(s) != workdir: + srcsubdir = os.sep.join(os.path.relpath(s, workdir).split(os.sep)[1:]) + srctree = os.path.join(srctree, srcsubdir) + + appendpath = os.path.join(config.workspace_path, 'appends') + if not os.path.exists(appendpath): + os.makedirs(appendpath) + + appendname = os.path.splitext(os.path.basename(recipefile))[0] + if args.wildcard: + appendname = re.sub(r'_.*', '_%', appendname) + appendfile = os.path.join(appendpath, appendname + '.bbappend') + with open(appendfile, 'w') as f: + f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n\n') + f.write('inherit externalsrc\n') + f.write('EXTERNALSRC = "%s"\n' % srctree) + if bb.data.inherits_class('autotools-brokensep', rd): + logger.info('using source tree as build directory since original recipe inherits autotools-brokensep') + f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree) + f.write('do_compile[nostamp] = "1"\n') + + _add_md5(config, args.recipename, appendfile) + + logger.info('Recipe %s now set up to build from %s' % (args.recipename, srctree)) + + return 0 + + +def status(args, config, basepath, workspace): + for recipe, value in workspace.iteritems(): + print("%s: %s" % (recipe, value)) + return 0 + + +def reset(args, config, basepath, workspace): + import bb.utils + if not args.recipename in workspace: + logger.error("no recipe named %s in your workspace" % args.recipename) + return -1 + _check_preserve(config, args.recipename) + + preservepath = os.path.join(config.workspace_path, 'attic', args.recipename) + def preservedir(origdir): + if os.path.exists(origdir): + for fn in os.listdir(origdir): + logger.warn('Preserving %s in %s' % (fn, preservepath)) + bb.utils.mkdirhier(preservepath) + shutil.move(os.path.join(origdir, fn), os.path.join(preservepath, fn)) + os.rmdir(origdir) + + preservedir(os.path.join(config.workspace_path, 'recipes', args.recipename)) + # We don't automatically create this dir next to appends, but the user can + preservedir(os.path.join(config.workspace_path, 'appends', args.recipename)) + return 0 + + +def build(args, config, basepath, workspace): + import bb + if not args.recipename in workspace: + logger.error("no recipe named %s in your workspace" % args.recipename) + return -1 + exec_build_env_command(config.init_path, basepath, 'bitbake -c install %s' % args.recipename, watch=True) + + return 0 + + +def register_commands(subparsers, context): + if not context.fixed_setup: + parser_create_workspace = subparsers.add_parser('create-workspace', help='Set up a workspace') + parser_create_workspace.add_argument('directory', nargs='?', help='Directory for the workspace') + parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace, do not alter configuration') + parser_create_workspace.set_defaults(func=create_workspace) + + parser_add = subparsers.add_parser('add', help='Add a new recipe') + parser_add.add_argument('recipename', help='Name for new recipe to add') + parser_add.add_argument('srctree', help='Path to external source tree') + parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)') + parser_add.set_defaults(func=add) + + parser_add = subparsers.add_parser('modify', help='Modify the source for an existing recipe') + parser_add.add_argument('recipename', help='Name for recipe to edit') + parser_add.add_argument('srctree', help='Path to external source tree') + parser_add.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend') + parser_add.add_argument('--extract', '-x', action="store_true", help='Extract source as well') + parser_add.set_defaults(func=modify) + + parser_add = subparsers.add_parser('extract', help='Extract the source for an existing recipe') + parser_add.add_argument('recipename', help='Name for recipe to extract the source for') + parser_add.add_argument('srctree', help='Path to where to extract the source tree') + parser_add.set_defaults(func=extract) + + parser_status = subparsers.add_parser('status', help='Show status') + parser_status.set_defaults(func=status) + + parser_build = subparsers.add_parser('build', help='Build recipe') + parser_build.add_argument('recipename', help='Recipe to build') + parser_build.set_defaults(func=build) + + parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace') + parser_reset.add_argument('recipename', help='Recipe to reset') + parser_reset.set_defaults(func=reset) + -- 1.9.3 -- _______________________________________________ Openembedded-core mailing list [email protected] http://lists.openembedded.org/mailman/listinfo/openembedded-core
