blob: 0c32c502a3f16cdeff35424815a01e0ad2660533 [file] [log] [blame]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001#!/usr/bin/env python3
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002
3# OpenEmbedded Development tool
4#
5# Copyright (C) 2014-2015 Intel Corporation
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20import sys
21import os
22import argparse
23import glob
24import re
Patrick Williamsc0f7c042017-02-23 20:41:17 -060025import configparser
Patrick Williamsc124f4f2015-09-15 14:41:29 -050026import subprocess
27import logging
28
29basepath = ''
30workspace = {}
31config = None
32context = None
33
34
35scripts_path = os.path.dirname(os.path.realpath(__file__))
36lib_path = scripts_path + '/lib'
37sys.path = sys.path + [lib_path]
38from devtool import DevtoolError, setup_tinfoil
39import scriptutils
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050040import argparse_oe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050041logger = scriptutils.logger_create('devtool')
42
43plugins = []
44
45
46class ConfigHandler(object):
47 config_file = ''
48 config_obj = None
49 init_path = ''
50 workspace_path = ''
51
52 def __init__(self, filename):
53 self.config_file = filename
Patrick Williamsc0f7c042017-02-23 20:41:17 -060054 self.config_obj = configparser.SafeConfigParser()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055
56 def get(self, section, option, default=None):
57 try:
58 ret = self.config_obj.get(section, option)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060059 except (configparser.NoOptionError, configparser.NoSectionError):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050060 if default != None:
61 ret = default
62 else:
63 raise
64 return ret
65
66 def read(self):
67 if os.path.exists(self.config_file):
68 self.config_obj.read(self.config_file)
69
70 if self.config_obj.has_option('General', 'init_path'):
71 pth = self.get('General', 'init_path')
72 self.init_path = os.path.join(basepath, pth)
73 if not os.path.exists(self.init_path):
74 logger.error('init_path %s specified in config file cannot be found' % pth)
75 return False
76 else:
77 self.config_obj.add_section('General')
78
79 self.workspace_path = self.get('General', 'workspace_path', os.path.join(basepath, 'workspace'))
80 return True
81
82
83 def write(self):
84 logger.debug('writing to config file %s' % self.config_file)
85 self.config_obj.set('General', 'workspace_path', self.workspace_path)
86 with open(self.config_file, 'w') as f:
87 self.config_obj.write(f)
88
Patrick Williamsc0f7c042017-02-23 20:41:17 -060089 def set(self, section, option, value):
90 if not self.config_obj.has_section(section):
91 self.config_obj.add_section(section)
92 self.config_obj.set(section, option, value)
93
Patrick Williamsc124f4f2015-09-15 14:41:29 -050094class Context:
95 def __init__(self, **kwargs):
96 self.__dict__.update(kwargs)
97
98
99def read_workspace():
100 global workspace
101 workspace = {}
102 if not os.path.exists(os.path.join(config.workspace_path, 'conf', 'layer.conf')):
103 if context.fixed_setup:
104 logger.error("workspace layer not set up")
105 sys.exit(1)
106 else:
107 logger.info('Creating workspace layer in %s' % config.workspace_path)
108 _create_workspace(config.workspace_path, config, basepath)
109 if not context.fixed_setup:
110 _enable_workspace_layer(config.workspace_path, config, basepath)
111
112 logger.debug('Reading workspace in %s' % config.workspace_path)
113 externalsrc_re = re.compile(r'^EXTERNALSRC(_pn-([^ =]+))? *= *"([^"]*)"$')
114 for fn in glob.glob(os.path.join(config.workspace_path, 'appends', '*.bbappend')):
115 with open(fn, 'r') as f:
116 for line in f:
117 res = externalsrc_re.match(line.rstrip())
118 if res:
119 pn = res.group(2) or os.path.splitext(os.path.basename(fn))[0].split('_')[0]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500120 # Find the recipe file within the workspace, if any
121 bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*')
122 recipefile = glob.glob(os.path.join(config.workspace_path,
123 'recipes',
124 pn,
125 bbfile))
126 if recipefile:
127 recipefile = recipefile[0]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500128 workspace[pn] = {'srctree': res.group(3),
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500129 'bbappend': fn,
130 'recipefile': recipefile}
131 logger.debug('Found recipe %s' % workspace[pn])
132
133def create_unlockedsigs():
134 """ This function will make unlocked-sigs.inc match the recipes in the
135 workspace. This runs on every run of devtool, but it lets us ensure
136 the unlocked items are in sync with the workspace. """
137
138 confdir = os.path.join(basepath, 'conf')
139 unlockedsigs = os.path.join(confdir, 'unlocked-sigs.inc')
140 bb.utils.mkdirhier(confdir)
141 with open(os.path.join(confdir, 'unlocked-sigs.inc'), 'w') as f:
142 f.write("# DO NOT MODIFY! YOUR CHANGES WILL BE LOST.\n" +
143 "# This layer was created by the OpenEmbedded devtool" +
144 " utility in order to\n" +
145 "# contain recipes that are unlocked.\n")
146
147 f.write('SIGGEN_UNLOCKED_RECIPES += "\\\n')
148 for pn in workspace:
149 f.write(' ' + pn)
150 f.write('"')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500151
152def create_workspace(args, config, basepath, workspace):
153 if args.layerpath:
154 workspacedir = os.path.abspath(args.layerpath)
155 else:
156 workspacedir = os.path.abspath(os.path.join(basepath, 'workspace'))
157 _create_workspace(workspacedir, config, basepath)
158 if not args.create_only:
159 _enable_workspace_layer(workspacedir, config, basepath)
160
161def _create_workspace(workspacedir, config, basepath):
162 import bb
163
164 confdir = os.path.join(workspacedir, 'conf')
165 if os.path.exists(os.path.join(confdir, 'layer.conf')):
166 logger.info('Specified workspace already set up, leaving as-is')
167 else:
168 # Add a config file
169 bb.utils.mkdirhier(confdir)
170 with open(os.path.join(confdir, 'layer.conf'), 'w') as f:
171 f.write('# ### workspace layer auto-generated by devtool ###\n')
172 f.write('BBPATH =. "$' + '{LAYERDIR}:"\n')
173 f.write('BBFILES += "$' + '{LAYERDIR}/recipes/*/*.bb \\\n')
174 f.write(' $' + '{LAYERDIR}/appends/*.bbappend"\n')
175 f.write('BBFILE_COLLECTIONS += "workspacelayer"\n')
176 f.write('BBFILE_PATTERN_workspacelayer = "^$' + '{LAYERDIR}/"\n')
177 f.write('BBFILE_PATTERN_IGNORE_EMPTY_workspacelayer = "1"\n')
178 f.write('BBFILE_PRIORITY_workspacelayer = "99"\n')
179 # Add a README file
180 with open(os.path.join(workspacedir, 'README'), 'w') as f:
181 f.write('This layer was created by the OpenEmbedded devtool utility in order to\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600182 f.write('contain recipes and bbappends that are currently being worked on. The idea\n')
183 f.write('is that the contents is temporary - once you have finished working on a\n')
184 f.write('recipe you use the appropriate method to move the files you have been\n')
185 f.write('working on to a proper layer. In most instances you should use the\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500186 f.write('devtool utility to manage files within it rather than modifying files\n')
187 f.write('directly (although recipes added with "devtool add" will often need\n')
188 f.write('direct modification.)\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600189 f.write('\nIf you no longer need to use devtool or the workspace layer\'s contents\n')
190 f.write('you can remove the path to this workspace layer from your conf/bblayers.conf\n')
191 f.write('file (and then delete the layer, if you wish).\n')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500192 f.write('\nNote that by default, if devtool fetches and unpacks source code, it\n')
193 f.write('will place it in a subdirectory of a "sources" subdirectory of the\n')
194 f.write('layer. If you prefer it to be elsewhere you can specify the source\n')
195 f.write('tree path on the command line.\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500196
197def _enable_workspace_layer(workspacedir, config, basepath):
198 """Ensure the workspace layer is in bblayers.conf"""
199 import bb
200 bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf')
201 if not os.path.exists(bblayers_conf):
202 logger.error('Unable to find bblayers.conf')
203 return
204 _, added = bb.utils.edit_bblayers_conf(bblayers_conf, workspacedir, config.workspace_path)
205 if added:
206 logger.info('Enabling workspace layer in bblayers.conf')
207 if config.workspace_path != workspacedir:
208 # Update our config to point to the new location
209 config.workspace_path = workspacedir
210 config.write()
211
212
213def main():
214 global basepath
215 global config
216 global context
217
218 context = Context(fixed_setup=False)
219
220 # Default basepath
221 basepath = os.path.dirname(os.path.abspath(__file__))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500222
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500223 parser = argparse_oe.ArgumentParser(description="OpenEmbedded development tool",
224 add_help=False,
225 epilog="Use %(prog)s <subcommand> --help to get help on a specific command")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500226 parser.add_argument('--basepath', help='Base directory of SDK / build directory')
227 parser.add_argument('--bbpath', help='Explicitly specify the BBPATH, rather than getting it from the metadata')
228 parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
229 parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true')
230 parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR')
231
232 global_args, unparsed_args = parser.parse_known_args()
233
234 # Help is added here rather than via add_help=True, as we don't want it to
235 # be handled by parse_known_args()
236 parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
237 help='show this help message and exit')
238
239 if global_args.debug:
240 logger.setLevel(logging.DEBUG)
241 elif global_args.quiet:
242 logger.setLevel(logging.ERROR)
243
244 if global_args.basepath:
245 # Override
246 basepath = global_args.basepath
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500247 if os.path.exists(os.path.join(basepath, '.devtoolbase')):
248 context.fixed_setup = True
249 else:
250 pth = basepath
251 while pth != '' and pth != os.sep:
252 if os.path.exists(os.path.join(pth, '.devtoolbase')):
253 context.fixed_setup = True
254 basepath = pth
255 break
256 pth = os.path.dirname(pth)
257
258 if not context.fixed_setup:
259 basepath = os.environ.get('BUILDDIR')
260 if not basepath:
261 logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)")
262 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500263
264 logger.debug('Using basepath %s' % basepath)
265
266 config = ConfigHandler(os.path.join(basepath, 'conf', 'devtool.conf'))
267 if not config.read():
268 return -1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500269 context.config = config
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500270
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500271 bitbake_subdir = config.get('General', 'bitbake_subdir', '')
272 if bitbake_subdir:
273 # Normally set for use within the SDK
274 logger.debug('Using bitbake subdir %s' % bitbake_subdir)
275 sys.path.insert(0, os.path.join(basepath, bitbake_subdir, 'lib'))
276 core_meta_subdir = config.get('General', 'core_meta_subdir')
277 sys.path.insert(0, os.path.join(basepath, core_meta_subdir, 'lib'))
278 else:
279 # Standard location
280 import scriptpath
281 bitbakepath = scriptpath.add_bitbake_lib_path()
282 if not bitbakepath:
283 logger.error("Unable to find bitbake by searching parent directory of this script or PATH")
284 sys.exit(1)
285 logger.debug('Using standard bitbake path %s' % bitbakepath)
286 scriptpath.add_oe_lib_path()
287
288 scriptutils.logger_setup_color(logger, global_args.color)
289
290 if global_args.bbpath is None:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500291 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600292 try:
293 global_args.bbpath = tinfoil.config_data.getVar('BBPATH', True)
294 finally:
295 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500296
297 for path in [scripts_path] + global_args.bbpath.split(':'):
298 pluginpath = os.path.join(path, 'lib', 'devtool')
299 scriptutils.load_plugins(logger, plugins, pluginpath)
300
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500301 subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600302 subparsers.required = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500303
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500304 subparsers.add_subparser_group('sdk', 'SDK maintenance', -2)
305 subparsers.add_subparser_group('advanced', 'Advanced', -1)
306 subparsers.add_subparser_group('starting', 'Beginning work on a recipe', 100)
307 subparsers.add_subparser_group('info', 'Getting information')
308 subparsers.add_subparser_group('working', 'Working on a recipe in the workspace')
309 subparsers.add_subparser_group('testbuild', 'Testing changes on target')
310
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500311 if not context.fixed_setup:
312 parser_create_workspace = subparsers.add_parser('create-workspace',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500313 help='Set up workspace in an alternative location',
314 description='Sets up a new workspace. NOTE: other devtool subcommands will create a workspace automatically as needed, so you only need to use %(prog)s if you want to specify where the workspace should be located.',
315 group='advanced')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316 parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created')
317 parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace layer, do not alter configuration')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500318 parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319
320 for plugin in plugins:
321 if hasattr(plugin, 'register_commands'):
322 plugin.register_commands(subparsers, context)
323
324 args = parser.parse_args(unparsed_args, namespace=global_args)
325
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500326 if not getattr(args, 'no_workspace', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500327 read_workspace()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500328 create_unlockedsigs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500329
330 try:
331 ret = args.func(args, config, basepath, workspace)
332 except DevtoolError as err:
333 if str(err):
334 logger.error(str(err))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600335 ret = err.exitcode
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500336 except argparse_oe.ArgumentUsageError as ae:
337 parser.error_subcommand(ae.message, ae.subcommand)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500338
339 return ret
340
341
342if __name__ == "__main__":
343 try:
344 ret = main()
345 except Exception:
346 ret = 1
347 import traceback
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500348 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500349 sys.exit(ret)