blob: b761a80f8fdba6d5036094a479a792761f9e3a4a [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
33
34def 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
55def 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 = '''
102Arguments currently passed to the configure script:
103
104%s
105
106Some 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 = '''
128The current cmake command line:
129
130%s
131
132Arguments 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 = '''
137The current implementation of cmake_do_configure:
138
139cmake_do_configure() {
140%s
141}
142
143Arguments 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 = '''
149The current implementation of do_configure:
150
151do_configure() {
152%s
153}''' % do_configure.rstrip()
154 if '${EXTRA_OECONF}' in do_configure_noexpand:
155 configopttext += '''
156
157Arguments specified through EXTRA_OECONF (which you can change or add to easily):
158
159%s''' % extra_oeconf
160
161 if packageconfig:
162 configopttext += '''
163
164Some 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
180The 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
185The ./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
214def 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)