#!/usr/bin/env python3 
 | 
  
 | 
# OpenEmbedded Development tool 
 | 
# 
 | 
# Copyright (C) 2014-2015 Intel Corporation 
 | 
# 
 | 
# SPDX-License-Identifier: GPL-2.0-only 
 | 
# 
 | 
  
 | 
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.ConfigParser() 
 | 
  
 | 
    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: 
 | 
            pnvalues = {} 
 | 
            for line in f: 
 | 
                res = externalsrc_re.match(line.rstrip()) 
 | 
                if res: 
 | 
                    recipepn = os.path.splitext(os.path.basename(fn))[0].split('_')[0] 
 | 
                    pn = res.group(2) or recipepn 
 | 
                    # 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', 
 | 
                                                        recipepn, 
 | 
                                                        bbfile)) 
 | 
                    if recipefile: 
 | 
                        recipefile = recipefile[0] 
 | 
                    pnvalues['srctree'] = res.group(3) 
 | 
                    pnvalues['bbappend'] = fn 
 | 
                    pnvalues['recipefile'] = recipefile 
 | 
                elif line.startswith('# srctreebase: '): 
 | 
                    pnvalues['srctreebase'] = line.split(':', 1)[1].strip() 
 | 
            if pnvalues: 
 | 
                if not pnvalues.get('srctreebase', None): 
 | 
                    pnvalues['srctreebase'] = pnvalues['srctree'] 
 | 
                logger.debug('Found recipe %s' % pnvalues) 
 | 
                workspace[pn] = pnvalues 
 | 
  
 | 
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') 
 | 
            f.write('LAYERSERIES_COMPAT_workspacelayer = "${LAYERSERIES_COMPAT_core}"\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 
 | 
    if os.path.abspath(workspacedir) != os.path.abspath(config.workspace_path): 
 | 
        removedir = config.workspace_path 
 | 
    else: 
 | 
        removedir = None 
 | 
    _, added = bb.utils.edit_bblayers_conf(bblayers_conf, workspacedir, removedir) 
 | 
    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() 
 | 
  
 | 
    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) 
 |