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