diff options
Diffstat (limited to 'scripts/devtool')
-rwxr-xr-x | scripts/devtool | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/scripts/devtool b/scripts/devtool new file mode 100755 index 0000000..9ac6e79 --- /dev/null +++ b/scripts/devtool @@ -0,0 +1,347 @@ +#!/usr/bin/env python + +# OpenEmbedded Development tool +# +# Copyright (C) 2014-2015 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 re +import ConfigParser +import subprocess +import logging + +basepath = '' +workspace = {} +config = None +context = None + + +scripts_path = os.path.dirname(os.path.realpath(__file__)) +lib_path = scripts_path + '/lib' +sys.path = sys.path + [lib_path] +from devtool import DevtoolError, setup_tinfoil +import scriptutils +import argparse_oe +logger = scriptutils.logger_create('devtool') + +plugins = [] + + +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 + else: + self.config_obj.add_section('General') + + self.workspace_path = self.get('General', 'workspace_path', os.path.join(basepath, 'workspace')) + return True + + + def write(self): + logger.debug('writing to config file %s' % self.config_file) + self.config_obj.set('General', 'workspace_path', self.workspace_path) + with open(self.config_file, 'w') as f: + self.config_obj.write(f) + + def set(self, section, option, value): + if not self.config_obj.has_section(section): + self.config_obj.add_section(section) + self.config_obj.set(section, option, value) + +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") + sys.exit(1) + else: + logger.info('Creating workspace layer in %s' % config.workspace_path) + _create_workspace(config.workspace_path, config, basepath) + if not context.fixed_setup: + _enable_workspace_layer(config.workspace_path, config, basepath) + + logger.debug('Reading workspace in %s' % config.workspace_path) + externalsrc_re = re.compile(r'^EXTERNALSRC(_pn-([^ =]+))? *= *"([^"]*)"$') + for fn in glob.glob(os.path.join(config.workspace_path, 'appends', '*.bbappend')): + with open(fn, 'r') as f: + for line in f: + res = externalsrc_re.match(line.rstrip()) + if res: + pn = res.group(2) or os.path.splitext(os.path.basename(fn))[0].split('_')[0] + # Find the recipe file within the workspace, if any + bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*') + recipefile = glob.glob(os.path.join(config.workspace_path, + 'recipes', + pn, + bbfile)) + if recipefile: + recipefile = recipefile[0] + workspace[pn] = {'srctree': res.group(3), + 'bbappend': fn, + 'recipefile': recipefile} + logger.debug('Found recipe %s' % workspace[pn]) + +def create_unlockedsigs(): + """ This function will make unlocked-sigs.inc match the recipes in the + workspace. This runs on every run of devtool, but it lets us ensure + the unlocked items are in sync with the workspace. """ + + confdir = os.path.join(basepath, 'conf') + unlockedsigs = os.path.join(confdir, 'unlocked-sigs.inc') + bb.utils.mkdirhier(confdir) + with open(os.path.join(confdir, 'unlocked-sigs.inc'), 'w') as f: + f.write("# DO NOT MODIFY! YOUR CHANGES WILL BE LOST.\n" + + "# This layer was created by the OpenEmbedded devtool" + + " utility in order to\n" + + "# contain recipes that are unlocked.\n") + + f.write('SIGGEN_UNLOCKED_RECIPES += "\\\n') + for pn in workspace: + f.write(' ' + pn) + f.write('"') + +def create_workspace(args, config, basepath, workspace): + if args.layerpath: + workspacedir = os.path.abspath(args.layerpath) + else: + workspacedir = os.path.abspath(os.path.join(basepath, 'workspace')) + _create_workspace(workspacedir, config, basepath) + if not args.create_only: + _enable_workspace_layer(workspacedir, config, basepath) + +def _create_workspace(workspacedir, config, basepath): + import bb + + 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: + # Add a config file + 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_PATTERN_IGNORE_EMPTY_workspacelayer = "1"\n') + f.write('BBFILE_PRIORITY_workspacelayer = "99"\n') + # Add a README file + with open(os.path.join(workspacedir, 'README'), 'w') as f: + f.write('This layer was created by the OpenEmbedded devtool utility in order to\n') + f.write('contain recipes and bbappends. In most instances you should use the\n') + f.write('devtool utility to manage files within it rather than modifying files\n') + f.write('directly (although recipes added with "devtool add" will often need\n') + f.write('direct modification.)\n') + f.write('\nIf you no longer need to use devtool you can remove the path to this\n') + f.write('workspace layer from your conf/bblayers.conf file (and then delete the\n') + f.write('layer, if you wish).\n') + f.write('\nNote that by default, if devtool fetches and unpacks source code, it\n') + f.write('will place it in a subdirectory of a "sources" subdirectory of the\n') + f.write('layer. If you prefer it to be elsewhere you can specify the source\n') + f.write('tree path on the command line.\n') + +def _enable_workspace_layer(workspacedir, config, basepath): + """Ensure the workspace layer is in bblayers.conf""" + import bb + bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf') + if not os.path.exists(bblayers_conf): + logger.error('Unable to find bblayers.conf') + return + _, added = bb.utils.edit_bblayers_conf(bblayers_conf, workspacedir, config.workspace_path) + if added: + logger.info('Enabling workspace layer in bblayers.conf') + if config.workspace_path != workspacedir: + # Update our config to point to the new location + config.workspace_path = workspacedir + config.write() + + +def main(): + global basepath + global config + global context + + context = Context(fixed_setup=False) + + # Default basepath + basepath = os.path.dirname(os.path.abspath(__file__)) + + parser = argparse_oe.ArgumentParser(description="OpenEmbedded development tool", + add_help=False, + epilog="Use %(prog)s <subcommand> --help to get help on a specific command") + parser.add_argument('--basepath', help='Base directory of SDK / build directory') + parser.add_argument('--bbpath', help='Explicitly specify the BBPATH, rather than getting it from the metadata') + parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true') + parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true') + parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR') + + global_args, unparsed_args = parser.parse_known_args() + + # Help is added here rather than via add_help=True, as we don't want it to + # be handled by parse_known_args() + parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS, + help='show this help message and exit') + + if global_args.debug: + logger.setLevel(logging.DEBUG) + elif global_args.quiet: + logger.setLevel(logging.ERROR) + + if global_args.basepath: + # Override + basepath = global_args.basepath + if os.path.exists(os.path.join(basepath, '.devtoolbase')): + context.fixed_setup = True + else: + 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) + + if 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, 'conf', 'devtool.conf')) + if not config.read(): + return -1 + context.config = config + + 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() + + scriptutils.logger_setup_color(logger, global_args.color) + + if global_args.bbpath is None: + tinfoil = setup_tinfoil(config_only=True, basepath=basepath) + global_args.bbpath = tinfoil.config_data.getVar('BBPATH', True) + else: + tinfoil = None + + for path in [scripts_path] + global_args.bbpath.split(':'): + pluginpath = os.path.join(path, 'lib', 'devtool') + scriptutils.load_plugins(logger, plugins, pluginpath) + + if tinfoil: + tinfoil.shutdown() + + subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>') + + subparsers.add_subparser_group('sdk', 'SDK maintenance', -2) + subparsers.add_subparser_group('advanced', 'Advanced', -1) + subparsers.add_subparser_group('starting', 'Beginning work on a recipe', 100) + subparsers.add_subparser_group('info', 'Getting information') + subparsers.add_subparser_group('working', 'Working on a recipe in the workspace') + subparsers.add_subparser_group('testbuild', 'Testing changes on target') + + if not context.fixed_setup: + parser_create_workspace = subparsers.add_parser('create-workspace', + help='Set up workspace in an alternative location', + description='Sets up a new workspace. NOTE: other devtool subcommands will create a workspace automatically as needed, so you only need to use %(prog)s if you want to specify where the workspace should be located.', + group='advanced') + parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created') + parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace layer, do not alter configuration') + parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True) + + for plugin in plugins: + if hasattr(plugin, 'register_commands'): + plugin.register_commands(subparsers, context) + + args = parser.parse_args(unparsed_args, namespace=global_args) + + if not getattr(args, 'no_workspace', False): + read_workspace() + create_unlockedsigs() + + try: + ret = args.func(args, config, basepath, workspace) + except DevtoolError as err: + if str(err): + logger.error(str(err)) + ret = 1 + except argparse_oe.ArgumentUsageError as ae: + parser.error_subcommand(ae.message, ae.subcommand) + + return ret + + +if __name__ == "__main__": + try: + ret = main() + except Exception: + ret = 1 + import traceback + traceback.print_exc() + sys.exit(ret) |