blob: 7cd139fb8bcc01c9d263b2eb3bd38aac4e9b904f [file] [log] [blame]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001# 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
20import os
21import sys
22import shutil
23import tempfile
24import logging
25import argparse
26import subprocess
27import scriptutils
28from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, DevtoolError
29from devtool import parse_recipe
30
31logger = logging.getLogger('devtool')
32
Brad Bishopd7bf8c12018-02-25 22:55:05 -050033def _find_recipe_path(args, config, basepath, workspace):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050034 if args.any_recipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -040035 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 Williamsd8c66bc2016-06-20 12:57:21 -050041 tinfoil = setup_tinfoil(config_only=False, basepath=basepath)
42 try:
43 rd = parse_recipe(config, tinfoil, args.recipename, True)
44 if not rd:
Brad Bishopd7bf8c12018-02-25 22:55:05 -050045 raise DevtoolError("Failed to find specified recipe")
Brad Bishop6e60e8b2018-02-01 10:27:11 -050046 recipefile = rd.getVar('FILE')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050047 finally:
48 tinfoil.shutdown()
Brad Bishopd7bf8c12018-02-25 22:55:05 -050049 return recipefile
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050050
Brad Bishopd7bf8c12018-02-25 22:55:05 -050051
52def 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
59def 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 Williamsd8c66bc2016-06-20 12:57:21 -050062
63
64def 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 Bishop6e60e8b2018-02-01 10:27:11 -050074 b = rd.getVar('B')
75 s = rd.getVar('S')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050076 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 Bishop6e60e8b2018-02-01 10:27:11 -050078 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 Williamsd8c66bc2016-06-20 12:57:21 -050082 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 Bishop6e60e8b2018-02-01 10:27:11 -050086 cmake_do_configure = rd.getVar('cmake_do_configure')
87 pn = rd.getVar('PN')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050088 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 = '''
111Arguments currently passed to the configure script:
112
113%s
114
115Some 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 = '''
137The current cmake command line:
138
139%s
140
141Arguments 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 = '''
146The current implementation of cmake_do_configure:
147
148cmake_do_configure() {
149%s
150}
151
152Arguments 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 = '''
158The current implementation of do_configure:
159
160do_configure() {
161%s
162}''' % do_configure.rstrip()
163 if '${EXTRA_OECONF}' in do_configure_noexpand:
164 configopttext += '''
165
166Arguments specified through EXTRA_OECONF (which you can change or add to easily):
167
168%s''' % extra_oeconf
169
170 if packageconfig:
171 configopttext += '''
172
173Some 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
189The 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
194The ./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
223def register_commands(subparsers, context):
224 """Register devtool subcommands from this plugin"""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400225 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 Williamsd8c66bc2016-06-20 12:57:21 -0500227 group='working')
228 parser_edit_recipe.add_argument('recipename', help='Recipe to edit')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400229 # 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 Williamsd8c66bc2016-06-20 12:57:21 -0500231 parser_edit_recipe.set_defaults(func=edit_recipe)
232
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500233 # Find-recipe
Brad Bishop316dfdd2018-06-25 12:45:53 -0400234 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 Bishopd7bf8c12018-02-25 22:55:05 -0500236 group='working')
237 parser_find_recipe.add_argument('recipename', help='Recipe to find')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400238 # 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 Bishopd7bf8c12018-02-25 22:55:05 -0500240 parser_find_recipe.set_defaults(func=find_recipe)
241
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500242 # 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)