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