1# Development tool - utility commands plugin 2# 3# Copyright (C) 2015-2016 Intel Corporation 4# 5# SPDX-License-Identifier: GPL-2.0-only 6# 7 8"""Devtool utility plugins""" 9 10import os 11import sys 12import shutil 13import tempfile 14import logging 15import argparse 16import subprocess 17import scriptutils 18from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, DevtoolError 19from devtool import parse_recipe 20 21logger = logging.getLogger('devtool') 22 23def _find_recipe_path(args, config, basepath, workspace): 24 if args.any_recipe: 25 logger.warning('-a/--any-recipe option is now always active, and thus the option will be removed in a future release') 26 if args.recipename in workspace: 27 recipefile = workspace[args.recipename]['recipefile'] 28 else: 29 recipefile = None 30 if not recipefile: 31 tinfoil = setup_tinfoil(config_only=False, basepath=basepath) 32 try: 33 rd = parse_recipe(config, tinfoil, args.recipename, True) 34 if not rd: 35 raise DevtoolError("Failed to find specified recipe") 36 recipefile = rd.getVar('FILE') 37 finally: 38 tinfoil.shutdown() 39 return recipefile 40 41 42def find_recipe(args, config, basepath, workspace): 43 """Entry point for the devtool 'find-recipe' subcommand""" 44 recipefile = _find_recipe_path(args, config, basepath, workspace) 45 print(recipefile) 46 return 0 47 48 49def edit_recipe(args, config, basepath, workspace): 50 """Entry point for the devtool 'edit-recipe' subcommand""" 51 return scriptutils.run_editor(_find_recipe_path(args, config, basepath, workspace), logger) 52 53 54def configure_help(args, config, basepath, workspace): 55 """Entry point for the devtool 'configure-help' subcommand""" 56 import oe.utils 57 58 check_workspace_recipe(workspace, args.recipename) 59 tinfoil = setup_tinfoil(config_only=False, basepath=basepath) 60 try: 61 rd = parse_recipe(config, tinfoil, args.recipename, appends=True, filter_workspace=False) 62 if not rd: 63 return 1 64 b = rd.getVar('B') 65 s = rd.getVar('S') 66 configurescript = os.path.join(s, 'configure') 67 confdisabled = 'noexec' in rd.getVarFlags('do_configure') or 'do_configure' not in (rd.getVar('__BBTASKS', False) or []) 68 configureopts = oe.utils.squashspaces(rd.getVar('CONFIGUREOPTS') or '') 69 extra_oeconf = oe.utils.squashspaces(rd.getVar('EXTRA_OECONF') or '') 70 extra_oecmake = oe.utils.squashspaces(rd.getVar('EXTRA_OECMAKE') or '') 71 do_configure = rd.getVar('do_configure') or '' 72 do_configure_noexpand = rd.getVar('do_configure', False) or '' 73 packageconfig = rd.getVarFlags('PACKAGECONFIG') or [] 74 autotools = bb.data.inherits_class('autotools', rd) and ('oe_runconf' in do_configure or 'autotools_do_configure' in do_configure) 75 cmake = bb.data.inherits_class('cmake', rd) and ('cmake_do_configure' in do_configure) 76 cmake_do_configure = rd.getVar('cmake_do_configure') 77 pn = rd.getVar('PN') 78 finally: 79 tinfoil.shutdown() 80 81 if 'doc' in packageconfig: 82 del packageconfig['doc'] 83 84 if autotools and not os.path.exists(configurescript): 85 logger.info('Running do_configure to generate configure script') 86 try: 87 stdout, _ = exec_build_env_command(config.init_path, basepath, 88 'bitbake -c configure %s' % args.recipename, 89 stderr=subprocess.STDOUT) 90 except bb.process.ExecutionError: 91 pass 92 93 if confdisabled or do_configure.strip() in ('', ':'): 94 raise DevtoolError("do_configure task has been disabled for this recipe") 95 elif args.no_pager and not os.path.exists(configurescript): 96 raise DevtoolError("No configure script found and no other information to display") 97 else: 98 configopttext = '' 99 if autotools and configureopts: 100 configopttext = ''' 101Arguments currently passed to the configure script: 102 103%s 104 105Some of those are fixed.''' % (configureopts + ' ' + extra_oeconf) 106 if extra_oeconf: 107 configopttext += ''' The ones that are specified through EXTRA_OECONF (which you can change or add to easily): 108 109%s''' % extra_oeconf 110 111 elif cmake: 112 in_cmake = False 113 cmake_cmd = '' 114 for line in cmake_do_configure.splitlines(): 115 if in_cmake: 116 cmake_cmd = cmake_cmd + ' ' + line.strip().rstrip('\\') 117 if not line.endswith('\\'): 118 break 119 if line.lstrip().startswith('cmake '): 120 cmake_cmd = line.strip().rstrip('\\') 121 if line.endswith('\\'): 122 in_cmake = True 123 else: 124 break 125 if cmake_cmd: 126 configopttext = ''' 127The current cmake command line: 128 129%s 130 131Arguments specified through EXTRA_OECMAKE (which you can change or add to easily) 132 133%s''' % (oe.utils.squashspaces(cmake_cmd), extra_oecmake) 134 else: 135 configopttext = ''' 136The current implementation of cmake_do_configure: 137 138cmake_do_configure() { 139%s 140} 141 142Arguments specified through EXTRA_OECMAKE (which you can change or add to easily) 143 144%s''' % (cmake_do_configure.rstrip(), extra_oecmake) 145 146 elif do_configure: 147 configopttext = ''' 148The current implementation of do_configure: 149 150do_configure() { 151%s 152}''' % do_configure.rstrip() 153 if '${EXTRA_OECONF}' in do_configure_noexpand: 154 configopttext += ''' 155 156Arguments specified through EXTRA_OECONF (which you can change or add to easily): 157 158%s''' % extra_oeconf 159 160 if packageconfig: 161 configopttext += ''' 162 163Some of these options may be controlled through PACKAGECONFIG; for more details please see the recipe.''' 164 165 if args.arg: 166 helpargs = ' '.join(args.arg) 167 elif cmake: 168 helpargs = '-LH' 169 else: 170 helpargs = '--help' 171 172 msg = '''configure information for %s 173------------------------------------------ 174%s''' % (pn, configopttext) 175 176 if cmake: 177 msg += ''' 178 179The cmake %s output for %s follows. After "-- Cache values" you should see a list of variables you can add to EXTRA_OECMAKE (prefixed with -D and suffixed with = followed by the desired value, without any spaces). 180------------------------------------------''' % (helpargs, pn) 181 elif os.path.exists(configurescript): 182 msg += ''' 183 184The ./configure %s output for %s follows. 185------------------------------------------''' % (helpargs, pn) 186 187 olddir = os.getcwd() 188 tmppath = tempfile.mkdtemp() 189 with tempfile.NamedTemporaryFile('w', delete=False) as tf: 190 if not args.no_header: 191 tf.write(msg + '\n') 192 tf.close() 193 try: 194 try: 195 cmd = 'cat %s' % tf.name 196 if cmake: 197 cmd += '; cmake %s %s 2>&1' % (helpargs, s) 198 os.chdir(b) 199 elif os.path.exists(configurescript): 200 cmd += '; %s %s' % (configurescript, helpargs) 201 if sys.stdout.isatty() and not args.no_pager: 202 pager = os.environ.get('PAGER', 'less') 203 cmd = '(%s) | %s' % (cmd, pager) 204 subprocess.check_call(cmd, shell=True) 205 except subprocess.CalledProcessError as e: 206 return e.returncode 207 finally: 208 os.chdir(olddir) 209 shutil.rmtree(tmppath) 210 os.remove(tf.name) 211 212 213def register_commands(subparsers, context): 214 """Register devtool subcommands from this plugin""" 215 parser_edit_recipe = subparsers.add_parser('edit-recipe', help='Edit a recipe file', 216 description='Runs the default editor (as specified by the EDITOR variable) on the specified recipe. Note that this will be quicker for recipes in the workspace as the cache does not need to be loaded in that case.', 217 group='working') 218 parser_edit_recipe.add_argument('recipename', help='Recipe to edit') 219 # FIXME drop -a at some point in future 220 parser_edit_recipe.add_argument('--any-recipe', '-a', action="store_true", help='Does nothing (exists for backwards-compatibility)') 221 parser_edit_recipe.set_defaults(func=edit_recipe) 222 223 # Find-recipe 224 parser_find_recipe = subparsers.add_parser('find-recipe', help='Find a recipe file', 225 description='Finds a recipe file. Note that this will be quicker for recipes in the workspace as the cache does not need to be loaded in that case.', 226 group='working') 227 parser_find_recipe.add_argument('recipename', help='Recipe to find') 228 # FIXME drop -a at some point in future 229 parser_find_recipe.add_argument('--any-recipe', '-a', action="store_true", help='Does nothing (exists for backwards-compatibility)') 230 parser_find_recipe.set_defaults(func=find_recipe) 231 232 # NOTE: Needed to override the usage string here since the default 233 # gets the order wrong - recipename must come before --arg 234 parser_configure_help = subparsers.add_parser('configure-help', help='Get help on configure script options', 235 usage='devtool configure-help [options] recipename [--arg ...]', 236 description='Displays the help for the configure script for the specified recipe (i.e. runs ./configure --help) prefaced by a header describing the current options being specified. Output is piped through less (or whatever PAGER is set to, if set) for easy browsing.', 237 group='working') 238 parser_configure_help.add_argument('recipename', help='Recipe to show configure help for') 239 parser_configure_help.add_argument('-p', '--no-pager', help='Disable paged output', action="store_true") 240 parser_configure_help.add_argument('-n', '--no-header', help='Disable explanatory header text', action="store_true") 241 parser_configure_help.add_argument('--arg', help='Pass remaining arguments to the configure script instead of --help (useful if the script has additional help options)', nargs=argparse.REMAINDER) 242 parser_configure_help.set_defaults(func=configure_help) 243