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