| #!/usr/bin/env python3 |
| |
| # 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 that are currently being worked on. The idea\n') |
| f.write('is that the contents is temporary - once you have finished working on a\n') |
| f.write('recipe you use the appropriate method to move the files you have been\n') |
| f.write('working on to a proper layer. 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 or the workspace layer\'s contents\n') |
| f.write('you can remove the path to this workspace layer from your conf/bblayers.conf\n') |
| f.write('file (and then delete the 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 |
| |
| if sys.getfilesystemencoding() != "utf-8": |
| sys.exit("Please use a locale setting which supports utf-8.\nPython can't change the filesystem locale after loading so we need a utf-8 when python starts or things won't work.") |
| |
| 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: |
| try: |
| tinfoil = setup_tinfoil(config_only=True, basepath=basepath) |
| try: |
| global_args.bbpath = tinfoil.config_data.getVar('BBPATH') |
| finally: |
| tinfoil.shutdown() |
| except bb.BBHandledException: |
| return 2 |
| |
| # Search BBPATH first to allow layers to override plugins in scripts_path |
| for path in global_args.bbpath.split(':') + [scripts_path]: |
| pluginpath = os.path.join(path, 'lib', 'devtool') |
| scriptutils.load_plugins(logger, plugins, pluginpath) |
| |
| subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>') |
| subparsers.required = True |
| |
| 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 = err.exitcode |
| 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) |