blob: b74511643f1687652506aadf4e04c8d02f56603d [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:
35 tinfoil = setup_tinfoil(config_only=False, basepath=basepath)
36 try:
37 rd = parse_recipe(config, tinfoil, args.recipename, True)
38 if not rd:
Brad Bishopd7bf8c12018-02-25 22:55:05 -050039 raise DevtoolError("Failed to find specified recipe")
Brad Bishop6e60e8b2018-02-01 10:27:11 -050040 recipefile = rd.getVar('FILE')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050041 finally:
42 tinfoil.shutdown()
43 else:
44 check_workspace_recipe(workspace, args.recipename)
45 recipefile = workspace[args.recipename]['recipefile']
46 if not recipefile:
47 raise DevtoolError("Recipe file for %s is not under the workspace" %
48 args.recipename)
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"""
225 parser_edit_recipe = subparsers.add_parser('edit-recipe', help='Edit a recipe file in your workspace',
226 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.',
227 group='working')
228 parser_edit_recipe.add_argument('recipename', help='Recipe to edit')
229 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')
230 parser_edit_recipe.set_defaults(func=edit_recipe)
231
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500232 # Find-recipe
233 parser_find_recipe = subparsers.add_parser('find-recipe', help='Find a recipe file in your workspace',
234 description='By default, this will find a recipe file in your workspace; you can override this with the -a/--any-recipe option.',
235 group='working')
236 parser_find_recipe.add_argument('recipename', help='Recipe to find')
237 parser_find_recipe.add_argument('--any-recipe', '-a', action="store_true", help='Find any recipe, not just where the recipe file itself is in the workspace')
238 parser_find_recipe.set_defaults(func=find_recipe)
239
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500240 # NOTE: Needed to override the usage string here since the default
241 # gets the order wrong - recipename must come before --arg
242 parser_configure_help = subparsers.add_parser('configure-help', help='Get help on configure script options',
243 usage='devtool configure-help [options] recipename [--arg ...]',
244 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.',
245 group='working')
246 parser_configure_help.add_argument('recipename', help='Recipe to show configure help for')
247 parser_configure_help.add_argument('-p', '--no-pager', help='Disable paged output', action="store_true")
248 parser_configure_help.add_argument('-n', '--no-header', help='Disable explanatory header text', action="store_true")
249 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)
250 parser_configure_help.set_defaults(func=configure_help)