1*4882a593Smuzhiyun#!/usr/bin/env python3 2*4882a593Smuzhiyun 3*4882a593Smuzhiyun# OpenEmbedded Development tool 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# Copyright (C) 2014-2015 Intel Corporation 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 8*4882a593Smuzhiyun# 9*4882a593Smuzhiyun 10*4882a593Smuzhiyunimport sys 11*4882a593Smuzhiyunimport os 12*4882a593Smuzhiyunimport argparse 13*4882a593Smuzhiyunimport glob 14*4882a593Smuzhiyunimport re 15*4882a593Smuzhiyunimport configparser 16*4882a593Smuzhiyunimport subprocess 17*4882a593Smuzhiyunimport logging 18*4882a593Smuzhiyun 19*4882a593Smuzhiyunbasepath = '' 20*4882a593Smuzhiyunworkspace = {} 21*4882a593Smuzhiyunconfig = None 22*4882a593Smuzhiyuncontext = None 23*4882a593Smuzhiyun 24*4882a593Smuzhiyun 25*4882a593Smuzhiyunscripts_path = os.path.dirname(os.path.realpath(__file__)) 26*4882a593Smuzhiyunlib_path = scripts_path + '/lib' 27*4882a593Smuzhiyunsys.path = sys.path + [lib_path] 28*4882a593Smuzhiyunfrom devtool import DevtoolError, setup_tinfoil 29*4882a593Smuzhiyunimport scriptutils 30*4882a593Smuzhiyunimport argparse_oe 31*4882a593Smuzhiyunlogger = scriptutils.logger_create('devtool') 32*4882a593Smuzhiyun 33*4882a593Smuzhiyunplugins = [] 34*4882a593Smuzhiyun 35*4882a593Smuzhiyun 36*4882a593Smuzhiyunclass ConfigHandler(object): 37*4882a593Smuzhiyun config_file = '' 38*4882a593Smuzhiyun config_obj = None 39*4882a593Smuzhiyun init_path = '' 40*4882a593Smuzhiyun workspace_path = '' 41*4882a593Smuzhiyun 42*4882a593Smuzhiyun def __init__(self, filename): 43*4882a593Smuzhiyun self.config_file = filename 44*4882a593Smuzhiyun self.config_obj = configparser.ConfigParser() 45*4882a593Smuzhiyun 46*4882a593Smuzhiyun def get(self, section, option, default=None): 47*4882a593Smuzhiyun try: 48*4882a593Smuzhiyun ret = self.config_obj.get(section, option) 49*4882a593Smuzhiyun except (configparser.NoOptionError, configparser.NoSectionError): 50*4882a593Smuzhiyun if default != None: 51*4882a593Smuzhiyun ret = default 52*4882a593Smuzhiyun else: 53*4882a593Smuzhiyun raise 54*4882a593Smuzhiyun return ret 55*4882a593Smuzhiyun 56*4882a593Smuzhiyun def read(self): 57*4882a593Smuzhiyun if os.path.exists(self.config_file): 58*4882a593Smuzhiyun self.config_obj.read(self.config_file) 59*4882a593Smuzhiyun 60*4882a593Smuzhiyun if self.config_obj.has_option('General', 'init_path'): 61*4882a593Smuzhiyun pth = self.get('General', 'init_path') 62*4882a593Smuzhiyun self.init_path = os.path.join(basepath, pth) 63*4882a593Smuzhiyun if not os.path.exists(self.init_path): 64*4882a593Smuzhiyun logger.error('init_path %s specified in config file cannot be found' % pth) 65*4882a593Smuzhiyun return False 66*4882a593Smuzhiyun else: 67*4882a593Smuzhiyun self.config_obj.add_section('General') 68*4882a593Smuzhiyun 69*4882a593Smuzhiyun self.workspace_path = self.get('General', 'workspace_path', os.path.join(basepath, 'workspace')) 70*4882a593Smuzhiyun return True 71*4882a593Smuzhiyun 72*4882a593Smuzhiyun 73*4882a593Smuzhiyun def write(self): 74*4882a593Smuzhiyun logger.debug('writing to config file %s' % self.config_file) 75*4882a593Smuzhiyun self.config_obj.set('General', 'workspace_path', self.workspace_path) 76*4882a593Smuzhiyun with open(self.config_file, 'w') as f: 77*4882a593Smuzhiyun self.config_obj.write(f) 78*4882a593Smuzhiyun 79*4882a593Smuzhiyun def set(self, section, option, value): 80*4882a593Smuzhiyun if not self.config_obj.has_section(section): 81*4882a593Smuzhiyun self.config_obj.add_section(section) 82*4882a593Smuzhiyun self.config_obj.set(section, option, value) 83*4882a593Smuzhiyun 84*4882a593Smuzhiyunclass Context: 85*4882a593Smuzhiyun def __init__(self, **kwargs): 86*4882a593Smuzhiyun self.__dict__.update(kwargs) 87*4882a593Smuzhiyun 88*4882a593Smuzhiyun 89*4882a593Smuzhiyundef read_workspace(): 90*4882a593Smuzhiyun global workspace 91*4882a593Smuzhiyun workspace = {} 92*4882a593Smuzhiyun if not os.path.exists(os.path.join(config.workspace_path, 'conf', 'layer.conf')): 93*4882a593Smuzhiyun if context.fixed_setup: 94*4882a593Smuzhiyun logger.error("workspace layer not set up") 95*4882a593Smuzhiyun sys.exit(1) 96*4882a593Smuzhiyun else: 97*4882a593Smuzhiyun logger.info('Creating workspace layer in %s' % config.workspace_path) 98*4882a593Smuzhiyun _create_workspace(config.workspace_path, config, basepath) 99*4882a593Smuzhiyun if not context.fixed_setup: 100*4882a593Smuzhiyun _enable_workspace_layer(config.workspace_path, config, basepath) 101*4882a593Smuzhiyun 102*4882a593Smuzhiyun logger.debug('Reading workspace in %s' % config.workspace_path) 103*4882a593Smuzhiyun externalsrc_re = re.compile(r'^EXTERNALSRC(:pn-([^ =]+))? *= *"([^"]*)"$') 104*4882a593Smuzhiyun for fn in glob.glob(os.path.join(config.workspace_path, 'appends', '*.bbappend')): 105*4882a593Smuzhiyun with open(fn, 'r') as f: 106*4882a593Smuzhiyun pnvalues = {} 107*4882a593Smuzhiyun pn = None 108*4882a593Smuzhiyun for line in f: 109*4882a593Smuzhiyun res = externalsrc_re.match(line.rstrip()) 110*4882a593Smuzhiyun if res: 111*4882a593Smuzhiyun recipepn = os.path.splitext(os.path.basename(fn))[0].split('_')[0] 112*4882a593Smuzhiyun pn = res.group(2) or recipepn 113*4882a593Smuzhiyun # Find the recipe file within the workspace, if any 114*4882a593Smuzhiyun bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*') 115*4882a593Smuzhiyun recipefile = glob.glob(os.path.join(config.workspace_path, 116*4882a593Smuzhiyun 'recipes', 117*4882a593Smuzhiyun recipepn, 118*4882a593Smuzhiyun bbfile)) 119*4882a593Smuzhiyun if recipefile: 120*4882a593Smuzhiyun recipefile = recipefile[0] 121*4882a593Smuzhiyun pnvalues['srctree'] = res.group(3) 122*4882a593Smuzhiyun pnvalues['bbappend'] = fn 123*4882a593Smuzhiyun pnvalues['recipefile'] = recipefile 124*4882a593Smuzhiyun elif line.startswith('# srctreebase: '): 125*4882a593Smuzhiyun pnvalues['srctreebase'] = line.split(':', 1)[1].strip() 126*4882a593Smuzhiyun if pnvalues: 127*4882a593Smuzhiyun if not pn: 128*4882a593Smuzhiyun raise DevtoolError("Found *.bbappend in %s, but could not determine EXTERNALSRC:pn-*. " 129*4882a593Smuzhiyun "Maybe still using old syntax?" % config.workspace_path) 130*4882a593Smuzhiyun if not pnvalues.get('srctreebase', None): 131*4882a593Smuzhiyun pnvalues['srctreebase'] = pnvalues['srctree'] 132*4882a593Smuzhiyun logger.debug('Found recipe %s' % pnvalues) 133*4882a593Smuzhiyun workspace[pn] = pnvalues 134*4882a593Smuzhiyun 135*4882a593Smuzhiyundef create_workspace(args, config, basepath, workspace): 136*4882a593Smuzhiyun if args.layerpath: 137*4882a593Smuzhiyun workspacedir = os.path.abspath(args.layerpath) 138*4882a593Smuzhiyun else: 139*4882a593Smuzhiyun workspacedir = os.path.abspath(os.path.join(basepath, 'workspace')) 140*4882a593Smuzhiyun _create_workspace(workspacedir, config, basepath) 141*4882a593Smuzhiyun if not args.create_only: 142*4882a593Smuzhiyun _enable_workspace_layer(workspacedir, config, basepath) 143*4882a593Smuzhiyun 144*4882a593Smuzhiyundef _create_workspace(workspacedir, config, basepath): 145*4882a593Smuzhiyun import bb 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun confdir = os.path.join(workspacedir, 'conf') 148*4882a593Smuzhiyun if os.path.exists(os.path.join(confdir, 'layer.conf')): 149*4882a593Smuzhiyun logger.info('Specified workspace already set up, leaving as-is') 150*4882a593Smuzhiyun else: 151*4882a593Smuzhiyun # Add a config file 152*4882a593Smuzhiyun bb.utils.mkdirhier(confdir) 153*4882a593Smuzhiyun with open(os.path.join(confdir, 'layer.conf'), 'w') as f: 154*4882a593Smuzhiyun f.write('# ### workspace layer auto-generated by devtool ###\n') 155*4882a593Smuzhiyun f.write('BBPATH =. "$' + '{LAYERDIR}:"\n') 156*4882a593Smuzhiyun f.write('BBFILES += "$' + '{LAYERDIR}/recipes/*/*.bb \\\n') 157*4882a593Smuzhiyun f.write(' $' + '{LAYERDIR}/appends/*.bbappend"\n') 158*4882a593Smuzhiyun f.write('BBFILE_COLLECTIONS += "workspacelayer"\n') 159*4882a593Smuzhiyun f.write('BBFILE_PATTERN_workspacelayer = "^$' + '{LAYERDIR}/"\n') 160*4882a593Smuzhiyun f.write('BBFILE_PATTERN_IGNORE_EMPTY_workspacelayer = "1"\n') 161*4882a593Smuzhiyun f.write('BBFILE_PRIORITY_workspacelayer = "99"\n') 162*4882a593Smuzhiyun f.write('LAYERSERIES_COMPAT_workspacelayer = "${LAYERSERIES_COMPAT_core}"\n') 163*4882a593Smuzhiyun # Add a README file 164*4882a593Smuzhiyun with open(os.path.join(workspacedir, 'README'), 'w') as f: 165*4882a593Smuzhiyun f.write('This layer was created by the OpenEmbedded devtool utility in order to\n') 166*4882a593Smuzhiyun f.write('contain recipes and bbappends that are currently being worked on. The idea\n') 167*4882a593Smuzhiyun f.write('is that the contents is temporary - once you have finished working on a\n') 168*4882a593Smuzhiyun f.write('recipe you use the appropriate method to move the files you have been\n') 169*4882a593Smuzhiyun f.write('working on to a proper layer. In most instances you should use the\n') 170*4882a593Smuzhiyun f.write('devtool utility to manage files within it rather than modifying files\n') 171*4882a593Smuzhiyun f.write('directly (although recipes added with "devtool add" will often need\n') 172*4882a593Smuzhiyun f.write('direct modification.)\n') 173*4882a593Smuzhiyun f.write('\nIf you no longer need to use devtool or the workspace layer\'s contents\n') 174*4882a593Smuzhiyun f.write('you can remove the path to this workspace layer from your conf/bblayers.conf\n') 175*4882a593Smuzhiyun f.write('file (and then delete the layer, if you wish).\n') 176*4882a593Smuzhiyun f.write('\nNote that by default, if devtool fetches and unpacks source code, it\n') 177*4882a593Smuzhiyun f.write('will place it in a subdirectory of a "sources" subdirectory of the\n') 178*4882a593Smuzhiyun f.write('layer. If you prefer it to be elsewhere you can specify the source\n') 179*4882a593Smuzhiyun f.write('tree path on the command line.\n') 180*4882a593Smuzhiyun 181*4882a593Smuzhiyundef _enable_workspace_layer(workspacedir, config, basepath): 182*4882a593Smuzhiyun """Ensure the workspace layer is in bblayers.conf""" 183*4882a593Smuzhiyun import bb 184*4882a593Smuzhiyun bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf') 185*4882a593Smuzhiyun if not os.path.exists(bblayers_conf): 186*4882a593Smuzhiyun logger.error('Unable to find bblayers.conf') 187*4882a593Smuzhiyun return 188*4882a593Smuzhiyun if os.path.abspath(workspacedir) != os.path.abspath(config.workspace_path): 189*4882a593Smuzhiyun removedir = config.workspace_path 190*4882a593Smuzhiyun else: 191*4882a593Smuzhiyun removedir = None 192*4882a593Smuzhiyun _, added = bb.utils.edit_bblayers_conf(bblayers_conf, workspacedir, removedir) 193*4882a593Smuzhiyun if added: 194*4882a593Smuzhiyun logger.info('Enabling workspace layer in bblayers.conf') 195*4882a593Smuzhiyun if config.workspace_path != workspacedir: 196*4882a593Smuzhiyun # Update our config to point to the new location 197*4882a593Smuzhiyun config.workspace_path = workspacedir 198*4882a593Smuzhiyun config.write() 199*4882a593Smuzhiyun 200*4882a593Smuzhiyun 201*4882a593Smuzhiyundef main(): 202*4882a593Smuzhiyun global basepath 203*4882a593Smuzhiyun global config 204*4882a593Smuzhiyun global context 205*4882a593Smuzhiyun 206*4882a593Smuzhiyun if sys.getfilesystemencoding() != "utf-8": 207*4882a593Smuzhiyun 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.") 208*4882a593Smuzhiyun 209*4882a593Smuzhiyun context = Context(fixed_setup=False) 210*4882a593Smuzhiyun 211*4882a593Smuzhiyun # Default basepath 212*4882a593Smuzhiyun basepath = os.path.dirname(os.path.abspath(__file__)) 213*4882a593Smuzhiyun 214*4882a593Smuzhiyun parser = argparse_oe.ArgumentParser(description="OpenEmbedded development tool", 215*4882a593Smuzhiyun add_help=False, 216*4882a593Smuzhiyun epilog="Use %(prog)s <subcommand> --help to get help on a specific command") 217*4882a593Smuzhiyun parser.add_argument('--basepath', help='Base directory of SDK / build directory') 218*4882a593Smuzhiyun parser.add_argument('--bbpath', help='Explicitly specify the BBPATH, rather than getting it from the metadata') 219*4882a593Smuzhiyun parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true') 220*4882a593Smuzhiyun parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true') 221*4882a593Smuzhiyun parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR') 222*4882a593Smuzhiyun 223*4882a593Smuzhiyun global_args, unparsed_args = parser.parse_known_args() 224*4882a593Smuzhiyun 225*4882a593Smuzhiyun # Help is added here rather than via add_help=True, as we don't want it to 226*4882a593Smuzhiyun # be handled by parse_known_args() 227*4882a593Smuzhiyun parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS, 228*4882a593Smuzhiyun help='show this help message and exit') 229*4882a593Smuzhiyun 230*4882a593Smuzhiyun if global_args.debug: 231*4882a593Smuzhiyun logger.setLevel(logging.DEBUG) 232*4882a593Smuzhiyun elif global_args.quiet: 233*4882a593Smuzhiyun logger.setLevel(logging.ERROR) 234*4882a593Smuzhiyun 235*4882a593Smuzhiyun if global_args.basepath: 236*4882a593Smuzhiyun # Override 237*4882a593Smuzhiyun basepath = global_args.basepath 238*4882a593Smuzhiyun if os.path.exists(os.path.join(basepath, '.devtoolbase')): 239*4882a593Smuzhiyun context.fixed_setup = True 240*4882a593Smuzhiyun else: 241*4882a593Smuzhiyun pth = basepath 242*4882a593Smuzhiyun while pth != '' and pth != os.sep: 243*4882a593Smuzhiyun if os.path.exists(os.path.join(pth, '.devtoolbase')): 244*4882a593Smuzhiyun context.fixed_setup = True 245*4882a593Smuzhiyun basepath = pth 246*4882a593Smuzhiyun break 247*4882a593Smuzhiyun pth = os.path.dirname(pth) 248*4882a593Smuzhiyun 249*4882a593Smuzhiyun if not context.fixed_setup: 250*4882a593Smuzhiyun basepath = os.environ.get('BUILDDIR') 251*4882a593Smuzhiyun if not basepath: 252*4882a593Smuzhiyun logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)") 253*4882a593Smuzhiyun sys.exit(1) 254*4882a593Smuzhiyun 255*4882a593Smuzhiyun logger.debug('Using basepath %s' % basepath) 256*4882a593Smuzhiyun 257*4882a593Smuzhiyun config = ConfigHandler(os.path.join(basepath, 'conf', 'devtool.conf')) 258*4882a593Smuzhiyun if not config.read(): 259*4882a593Smuzhiyun return -1 260*4882a593Smuzhiyun context.config = config 261*4882a593Smuzhiyun 262*4882a593Smuzhiyun bitbake_subdir = config.get('General', 'bitbake_subdir', '') 263*4882a593Smuzhiyun if bitbake_subdir: 264*4882a593Smuzhiyun # Normally set for use within the SDK 265*4882a593Smuzhiyun logger.debug('Using bitbake subdir %s' % bitbake_subdir) 266*4882a593Smuzhiyun sys.path.insert(0, os.path.join(basepath, bitbake_subdir, 'lib')) 267*4882a593Smuzhiyun core_meta_subdir = config.get('General', 'core_meta_subdir') 268*4882a593Smuzhiyun sys.path.insert(0, os.path.join(basepath, core_meta_subdir, 'lib')) 269*4882a593Smuzhiyun else: 270*4882a593Smuzhiyun # Standard location 271*4882a593Smuzhiyun import scriptpath 272*4882a593Smuzhiyun bitbakepath = scriptpath.add_bitbake_lib_path() 273*4882a593Smuzhiyun if not bitbakepath: 274*4882a593Smuzhiyun logger.error("Unable to find bitbake by searching parent directory of this script or PATH") 275*4882a593Smuzhiyun sys.exit(1) 276*4882a593Smuzhiyun logger.debug('Using standard bitbake path %s' % bitbakepath) 277*4882a593Smuzhiyun scriptpath.add_oe_lib_path() 278*4882a593Smuzhiyun 279*4882a593Smuzhiyun scriptutils.logger_setup_color(logger, global_args.color) 280*4882a593Smuzhiyun 281*4882a593Smuzhiyun if global_args.bbpath is None: 282*4882a593Smuzhiyun try: 283*4882a593Smuzhiyun tinfoil = setup_tinfoil(config_only=True, basepath=basepath) 284*4882a593Smuzhiyun try: 285*4882a593Smuzhiyun global_args.bbpath = tinfoil.config_data.getVar('BBPATH') 286*4882a593Smuzhiyun finally: 287*4882a593Smuzhiyun tinfoil.shutdown() 288*4882a593Smuzhiyun except bb.BBHandledException: 289*4882a593Smuzhiyun return 2 290*4882a593Smuzhiyun 291*4882a593Smuzhiyun # Search BBPATH first to allow layers to override plugins in scripts_path 292*4882a593Smuzhiyun for path in global_args.bbpath.split(':') + [scripts_path]: 293*4882a593Smuzhiyun pluginpath = os.path.join(path, 'lib', 'devtool') 294*4882a593Smuzhiyun scriptutils.load_plugins(logger, plugins, pluginpath) 295*4882a593Smuzhiyun 296*4882a593Smuzhiyun subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>') 297*4882a593Smuzhiyun subparsers.required = True 298*4882a593Smuzhiyun 299*4882a593Smuzhiyun subparsers.add_subparser_group('sdk', 'SDK maintenance', -2) 300*4882a593Smuzhiyun subparsers.add_subparser_group('advanced', 'Advanced', -1) 301*4882a593Smuzhiyun subparsers.add_subparser_group('starting', 'Beginning work on a recipe', 100) 302*4882a593Smuzhiyun subparsers.add_subparser_group('info', 'Getting information') 303*4882a593Smuzhiyun subparsers.add_subparser_group('working', 'Working on a recipe in the workspace') 304*4882a593Smuzhiyun subparsers.add_subparser_group('testbuild', 'Testing changes on target') 305*4882a593Smuzhiyun 306*4882a593Smuzhiyun if not context.fixed_setup: 307*4882a593Smuzhiyun parser_create_workspace = subparsers.add_parser('create-workspace', 308*4882a593Smuzhiyun help='Set up workspace in an alternative location', 309*4882a593Smuzhiyun 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.', 310*4882a593Smuzhiyun group='advanced') 311*4882a593Smuzhiyun parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created') 312*4882a593Smuzhiyun parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace layer, do not alter configuration') 313*4882a593Smuzhiyun parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True) 314*4882a593Smuzhiyun 315*4882a593Smuzhiyun for plugin in plugins: 316*4882a593Smuzhiyun if hasattr(plugin, 'register_commands'): 317*4882a593Smuzhiyun plugin.register_commands(subparsers, context) 318*4882a593Smuzhiyun 319*4882a593Smuzhiyun args = parser.parse_args(unparsed_args, namespace=global_args) 320*4882a593Smuzhiyun 321*4882a593Smuzhiyun try: 322*4882a593Smuzhiyun if not getattr(args, 'no_workspace', False): 323*4882a593Smuzhiyun read_workspace() 324*4882a593Smuzhiyun 325*4882a593Smuzhiyun ret = args.func(args, config, basepath, workspace) 326*4882a593Smuzhiyun except DevtoolError as err: 327*4882a593Smuzhiyun if str(err): 328*4882a593Smuzhiyun logger.error(str(err)) 329*4882a593Smuzhiyun ret = err.exitcode 330*4882a593Smuzhiyun except argparse_oe.ArgumentUsageError as ae: 331*4882a593Smuzhiyun parser.error_subcommand(ae.message, ae.subcommand) 332*4882a593Smuzhiyun 333*4882a593Smuzhiyun return ret 334*4882a593Smuzhiyun 335*4882a593Smuzhiyun 336*4882a593Smuzhiyunif __name__ == "__main__": 337*4882a593Smuzhiyun try: 338*4882a593Smuzhiyun ret = main() 339*4882a593Smuzhiyun except Exception: 340*4882a593Smuzhiyun ret = 1 341*4882a593Smuzhiyun import traceback 342*4882a593Smuzhiyun traceback.print_exc() 343*4882a593Smuzhiyun sys.exit(ret) 344