1*4882a593Smuzhiyun# Development tool - build-image plugin 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Copyright (C) 2015 Intel Corporation 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun 8*4882a593Smuzhiyun"""Devtool plugin containing the build-image subcommand.""" 9*4882a593Smuzhiyun 10*4882a593Smuzhiyunimport os 11*4882a593Smuzhiyunimport errno 12*4882a593Smuzhiyunimport logging 13*4882a593Smuzhiyun 14*4882a593Smuzhiyunfrom bb.process import ExecutionError 15*4882a593Smuzhiyunfrom devtool import exec_build_env_command, setup_tinfoil, parse_recipe, DevtoolError 16*4882a593Smuzhiyun 17*4882a593Smuzhiyunlogger = logging.getLogger('devtool') 18*4882a593Smuzhiyun 19*4882a593Smuzhiyunclass TargetNotImageError(Exception): 20*4882a593Smuzhiyun pass 21*4882a593Smuzhiyun 22*4882a593Smuzhiyundef _get_packages(tinfoil, workspace, config): 23*4882a593Smuzhiyun """Get list of packages from recipes in the workspace.""" 24*4882a593Smuzhiyun result = [] 25*4882a593Smuzhiyun for recipe in workspace: 26*4882a593Smuzhiyun data = parse_recipe(config, tinfoil, recipe, True) 27*4882a593Smuzhiyun if 'class-target' in data.getVar('OVERRIDES').split(':'): 28*4882a593Smuzhiyun if recipe in data.getVar('PACKAGES').split(): 29*4882a593Smuzhiyun result.append(recipe) 30*4882a593Smuzhiyun else: 31*4882a593Smuzhiyun logger.warning("Skipping recipe %s as it doesn't produce a " 32*4882a593Smuzhiyun "package with the same name", recipe) 33*4882a593Smuzhiyun return result 34*4882a593Smuzhiyun 35*4882a593Smuzhiyundef build_image(args, config, basepath, workspace): 36*4882a593Smuzhiyun """Entry point for the devtool 'build-image' subcommand.""" 37*4882a593Smuzhiyun 38*4882a593Smuzhiyun image = args.imagename 39*4882a593Smuzhiyun auto_image = False 40*4882a593Smuzhiyun if not image: 41*4882a593Smuzhiyun sdk_targets = config.get('SDK', 'sdk_targets', '').split() 42*4882a593Smuzhiyun if sdk_targets: 43*4882a593Smuzhiyun image = sdk_targets[0] 44*4882a593Smuzhiyun auto_image = True 45*4882a593Smuzhiyun if not image: 46*4882a593Smuzhiyun raise DevtoolError('Unable to determine image to build, please specify one') 47*4882a593Smuzhiyun 48*4882a593Smuzhiyun try: 49*4882a593Smuzhiyun if args.add_packages: 50*4882a593Smuzhiyun add_packages = args.add_packages.split(',') 51*4882a593Smuzhiyun else: 52*4882a593Smuzhiyun add_packages = None 53*4882a593Smuzhiyun result, outputdir = build_image_task(config, basepath, workspace, image, add_packages) 54*4882a593Smuzhiyun except TargetNotImageError: 55*4882a593Smuzhiyun if auto_image: 56*4882a593Smuzhiyun raise DevtoolError('Unable to determine image to build, please specify one') 57*4882a593Smuzhiyun else: 58*4882a593Smuzhiyun raise DevtoolError('Specified recipe %s is not an image recipe' % image) 59*4882a593Smuzhiyun 60*4882a593Smuzhiyun if result == 0: 61*4882a593Smuzhiyun logger.info('Successfully built %s. You can find output files in %s' 62*4882a593Smuzhiyun % (image, outputdir)) 63*4882a593Smuzhiyun return result 64*4882a593Smuzhiyun 65*4882a593Smuzhiyundef build_image_task(config, basepath, workspace, image, add_packages=None, task=None, extra_append=None): 66*4882a593Smuzhiyun # remove <image>.bbappend to make sure setup_tinfoil doesn't 67*4882a593Smuzhiyun # break because of it 68*4882a593Smuzhiyun target_basename = config.get('SDK', 'target_basename', '') 69*4882a593Smuzhiyun if target_basename: 70*4882a593Smuzhiyun appendfile = os.path.join(config.workspace_path, 'appends', 71*4882a593Smuzhiyun '%s.bbappend' % target_basename) 72*4882a593Smuzhiyun try: 73*4882a593Smuzhiyun os.unlink(appendfile) 74*4882a593Smuzhiyun except OSError as exc: 75*4882a593Smuzhiyun if exc.errno != errno.ENOENT: 76*4882a593Smuzhiyun raise 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun tinfoil = setup_tinfoil(basepath=basepath) 79*4882a593Smuzhiyun try: 80*4882a593Smuzhiyun rd = parse_recipe(config, tinfoil, image, True) 81*4882a593Smuzhiyun if not rd: 82*4882a593Smuzhiyun # Error already shown 83*4882a593Smuzhiyun return (1, None) 84*4882a593Smuzhiyun if not bb.data.inherits_class('image', rd): 85*4882a593Smuzhiyun raise TargetNotImageError() 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun # Get the actual filename used and strip the .bb and full path 88*4882a593Smuzhiyun target_basename = rd.getVar('FILE') 89*4882a593Smuzhiyun target_basename = os.path.splitext(os.path.basename(target_basename))[0] 90*4882a593Smuzhiyun config.set('SDK', 'target_basename', target_basename) 91*4882a593Smuzhiyun config.write() 92*4882a593Smuzhiyun 93*4882a593Smuzhiyun appendfile = os.path.join(config.workspace_path, 'appends', 94*4882a593Smuzhiyun '%s.bbappend' % target_basename) 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun outputdir = None 97*4882a593Smuzhiyun try: 98*4882a593Smuzhiyun if workspace or add_packages: 99*4882a593Smuzhiyun if add_packages: 100*4882a593Smuzhiyun packages = add_packages 101*4882a593Smuzhiyun else: 102*4882a593Smuzhiyun packages = _get_packages(tinfoil, workspace, config) 103*4882a593Smuzhiyun else: 104*4882a593Smuzhiyun packages = None 105*4882a593Smuzhiyun if not task: 106*4882a593Smuzhiyun if not packages and not add_packages and workspace: 107*4882a593Smuzhiyun logger.warning('No recipes in workspace, building image %s unmodified', image) 108*4882a593Smuzhiyun elif not packages: 109*4882a593Smuzhiyun logger.warning('No packages to add, building image %s unmodified', image) 110*4882a593Smuzhiyun 111*4882a593Smuzhiyun if packages or extra_append: 112*4882a593Smuzhiyun bb.utils.mkdirhier(os.path.dirname(appendfile)) 113*4882a593Smuzhiyun with open(appendfile, 'w') as afile: 114*4882a593Smuzhiyun if packages: 115*4882a593Smuzhiyun # include packages from workspace recipes into the image 116*4882a593Smuzhiyun afile.write('IMAGE_INSTALL:append = " %s"\n' % ' '.join(packages)) 117*4882a593Smuzhiyun if not task: 118*4882a593Smuzhiyun logger.info('Building image %s with the following ' 119*4882a593Smuzhiyun 'additional packages: %s', image, ' '.join(packages)) 120*4882a593Smuzhiyun if extra_append: 121*4882a593Smuzhiyun for line in extra_append: 122*4882a593Smuzhiyun afile.write('%s\n' % line) 123*4882a593Smuzhiyun 124*4882a593Smuzhiyun if task in ['populate_sdk', 'populate_sdk_ext']: 125*4882a593Smuzhiyun outputdir = rd.getVar('SDK_DEPLOY') 126*4882a593Smuzhiyun else: 127*4882a593Smuzhiyun outputdir = rd.getVar('DEPLOY_DIR_IMAGE') 128*4882a593Smuzhiyun 129*4882a593Smuzhiyun tmp_tinfoil = tinfoil 130*4882a593Smuzhiyun tinfoil = None 131*4882a593Smuzhiyun tmp_tinfoil.shutdown() 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun options = '' 134*4882a593Smuzhiyun if task: 135*4882a593Smuzhiyun options += '-c %s' % task 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun # run bitbake to build image (or specified task) 138*4882a593Smuzhiyun try: 139*4882a593Smuzhiyun exec_build_env_command(config.init_path, basepath, 140*4882a593Smuzhiyun 'bitbake %s %s' % (options, image), watch=True) 141*4882a593Smuzhiyun except ExecutionError as err: 142*4882a593Smuzhiyun return (err.exitcode, None) 143*4882a593Smuzhiyun finally: 144*4882a593Smuzhiyun if os.path.isfile(appendfile): 145*4882a593Smuzhiyun os.unlink(appendfile) 146*4882a593Smuzhiyun finally: 147*4882a593Smuzhiyun if tinfoil: 148*4882a593Smuzhiyun tinfoil.shutdown() 149*4882a593Smuzhiyun return (0, outputdir) 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun 152*4882a593Smuzhiyundef register_commands(subparsers, context): 153*4882a593Smuzhiyun """Register devtool subcommands from the build-image plugin""" 154*4882a593Smuzhiyun parser = subparsers.add_parser('build-image', 155*4882a593Smuzhiyun help='Build image including workspace recipe packages', 156*4882a593Smuzhiyun description='Builds an image, extending it to include ' 157*4882a593Smuzhiyun 'packages from recipes in the workspace', 158*4882a593Smuzhiyun group='testbuild', order=-10) 159*4882a593Smuzhiyun parser.add_argument('imagename', help='Image recipe to build', nargs='?') 160*4882a593Smuzhiyun parser.add_argument('-p', '--add-packages', help='Instead of adding packages for the ' 161*4882a593Smuzhiyun 'entire workspace, specify packages to be added to the image ' 162*4882a593Smuzhiyun '(separate multiple packages by commas)', 163*4882a593Smuzhiyun metavar='PACKAGES') 164*4882a593Smuzhiyun parser.set_defaults(func=build_image) 165