blob: 7cd139fb8bcc01c9d263b2eb3bd38aac4e9b904f [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001# 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
33def _find_recipe_path(args, config, basepath, workspace):
34 if args.any_recipe:
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:
41 tinfoil = setup_tinfoil(config_only=False, basepath=basepath)
42 try:
43 rd = parse_recipe(config, tinfoil, args.recipename, True)
44 if not rd:
45 raise DevtoolError("Failed to find specified recipe")
46 recipefile = rd.getVar('FILE')
47 finally:
48 tinfoil.shutdown()
49 return recipefile
50
51
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)
62
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
74 b = rd.getVar('B')
75 s = rd.getVar('S')
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 [])
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 ''
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)
86 cmake_do_configure = rd.getVar('cmake_do_configure')
87 pn = rd.getVar('PN')
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 = '''
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',
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.',
227 group='working')
228 parser_edit_recipe.add_argument('recipename', help='Recipe to edit')
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)')
231 parser_edit_recipe.set_defaults(func=edit_recipe)
232
233 # Find-recipe
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.',
236 group='working')
237 parser_find_recipe.add_argument('recipename', help='Recipe to find')
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)')
240 parser_find_recipe.set_defaults(func=find_recipe)
241
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)