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