blob: 47803906549e5d5e4b4f5fd74baef4b7977ee6ec [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#!/usr/bin/env python
2
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
25import ConfigParser
26import 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
54 self.config_obj = ConfigParser.SafeConfigParser()
55
56 def get(self, section, option, default=None):
57 try:
58 ret = self.config_obj.get(section, option)
59 except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
60 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
89class Context:
90 def __init__(self, **kwargs):
91 self.__dict__.update(kwargs)
92
93
94def read_workspace():
95 global workspace
96 workspace = {}
97 if not os.path.exists(os.path.join(config.workspace_path, 'conf', 'layer.conf')):
98 if context.fixed_setup:
99 logger.error("workspace layer not set up")
100 sys.exit(1)
101 else:
102 logger.info('Creating workspace layer in %s' % config.workspace_path)
103 _create_workspace(config.workspace_path, config, basepath)
104 if not context.fixed_setup:
105 _enable_workspace_layer(config.workspace_path, config, basepath)
106
107 logger.debug('Reading workspace in %s' % config.workspace_path)
108 externalsrc_re = re.compile(r'^EXTERNALSRC(_pn-([^ =]+))? *= *"([^"]*)"$')
109 for fn in glob.glob(os.path.join(config.workspace_path, 'appends', '*.bbappend')):
110 with open(fn, 'r') as f:
111 for line in f:
112 res = externalsrc_re.match(line.rstrip())
113 if res:
114 pn = res.group(2) or os.path.splitext(os.path.basename(fn))[0].split('_')[0]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500115 # Find the recipe file within the workspace, if any
116 bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*')
117 recipefile = glob.glob(os.path.join(config.workspace_path,
118 'recipes',
119 pn,
120 bbfile))
121 if recipefile:
122 recipefile = recipefile[0]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500123 workspace[pn] = {'srctree': res.group(3),
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500124 'bbappend': fn,
125 'recipefile': recipefile}
126 logger.debug('Found recipe %s' % workspace[pn])
127
128def create_unlockedsigs():
129 """ This function will make unlocked-sigs.inc match the recipes in the
130 workspace. This runs on every run of devtool, but it lets us ensure
131 the unlocked items are in sync with the workspace. """
132
133 confdir = os.path.join(basepath, 'conf')
134 unlockedsigs = os.path.join(confdir, 'unlocked-sigs.inc')
135 bb.utils.mkdirhier(confdir)
136 with open(os.path.join(confdir, 'unlocked-sigs.inc'), 'w') as f:
137 f.write("# DO NOT MODIFY! YOUR CHANGES WILL BE LOST.\n" +
138 "# This layer was created by the OpenEmbedded devtool" +
139 " utility in order to\n" +
140 "# contain recipes that are unlocked.\n")
141
142 f.write('SIGGEN_UNLOCKED_RECIPES += "\\\n')
143 for pn in workspace:
144 f.write(' ' + pn)
145 f.write('"')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500146
147def create_workspace(args, config, basepath, workspace):
148 if args.layerpath:
149 workspacedir = os.path.abspath(args.layerpath)
150 else:
151 workspacedir = os.path.abspath(os.path.join(basepath, 'workspace'))
152 _create_workspace(workspacedir, config, basepath)
153 if not args.create_only:
154 _enable_workspace_layer(workspacedir, config, basepath)
155
156def _create_workspace(workspacedir, config, basepath):
157 import bb
158
159 confdir = os.path.join(workspacedir, 'conf')
160 if os.path.exists(os.path.join(confdir, 'layer.conf')):
161 logger.info('Specified workspace already set up, leaving as-is')
162 else:
163 # Add a config file
164 bb.utils.mkdirhier(confdir)
165 with open(os.path.join(confdir, 'layer.conf'), 'w') as f:
166 f.write('# ### workspace layer auto-generated by devtool ###\n')
167 f.write('BBPATH =. "$' + '{LAYERDIR}:"\n')
168 f.write('BBFILES += "$' + '{LAYERDIR}/recipes/*/*.bb \\\n')
169 f.write(' $' + '{LAYERDIR}/appends/*.bbappend"\n')
170 f.write('BBFILE_COLLECTIONS += "workspacelayer"\n')
171 f.write('BBFILE_PATTERN_workspacelayer = "^$' + '{LAYERDIR}/"\n')
172 f.write('BBFILE_PATTERN_IGNORE_EMPTY_workspacelayer = "1"\n')
173 f.write('BBFILE_PRIORITY_workspacelayer = "99"\n')
174 # Add a README file
175 with open(os.path.join(workspacedir, 'README'), 'w') as f:
176 f.write('This layer was created by the OpenEmbedded devtool utility in order to\n')
177 f.write('contain recipes and bbappends. In most instances you should use the\n')
178 f.write('devtool utility to manage files within it rather than modifying files\n')
179 f.write('directly (although recipes added with "devtool add" will often need\n')
180 f.write('direct modification.)\n')
181 f.write('\nIf you no longer need to use devtool you can remove the path to this\n')
182 f.write('workspace layer from your conf/bblayers.conf file (and then delete the\n')
183 f.write('layer, if you wish).\n')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500184 f.write('\nNote that by default, if devtool fetches and unpacks source code, it\n')
185 f.write('will place it in a subdirectory of a "sources" subdirectory of the\n')
186 f.write('layer. If you prefer it to be elsewhere you can specify the source\n')
187 f.write('tree path on the command line.\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500188
189def _enable_workspace_layer(workspacedir, config, basepath):
190 """Ensure the workspace layer is in bblayers.conf"""
191 import bb
192 bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf')
193 if not os.path.exists(bblayers_conf):
194 logger.error('Unable to find bblayers.conf')
195 return
196 _, added = bb.utils.edit_bblayers_conf(bblayers_conf, workspacedir, config.workspace_path)
197 if added:
198 logger.info('Enabling workspace layer in bblayers.conf')
199 if config.workspace_path != workspacedir:
200 # Update our config to point to the new location
201 config.workspace_path = workspacedir
202 config.write()
203
204
205def main():
206 global basepath
207 global config
208 global context
209
210 context = Context(fixed_setup=False)
211
212 # Default basepath
213 basepath = os.path.dirname(os.path.abspath(__file__))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500214
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500215 parser = argparse_oe.ArgumentParser(description="OpenEmbedded development tool",
216 add_help=False,
217 epilog="Use %(prog)s <subcommand> --help to get help on a specific command")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500218 parser.add_argument('--basepath', help='Base directory of SDK / build directory')
219 parser.add_argument('--bbpath', help='Explicitly specify the BBPATH, rather than getting it from the metadata')
220 parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
221 parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true')
222 parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR')
223
224 global_args, unparsed_args = parser.parse_known_args()
225
226 # Help is added here rather than via add_help=True, as we don't want it to
227 # be handled by parse_known_args()
228 parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
229 help='show this help message and exit')
230
231 if global_args.debug:
232 logger.setLevel(logging.DEBUG)
233 elif global_args.quiet:
234 logger.setLevel(logging.ERROR)
235
236 if global_args.basepath:
237 # Override
238 basepath = global_args.basepath
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500239 if os.path.exists(os.path.join(basepath, '.devtoolbase')):
240 context.fixed_setup = True
241 else:
242 pth = basepath
243 while pth != '' and pth != os.sep:
244 if os.path.exists(os.path.join(pth, '.devtoolbase')):
245 context.fixed_setup = True
246 basepath = pth
247 break
248 pth = os.path.dirname(pth)
249
250 if not context.fixed_setup:
251 basepath = os.environ.get('BUILDDIR')
252 if not basepath:
253 logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)")
254 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500255
256 logger.debug('Using basepath %s' % basepath)
257
258 config = ConfigHandler(os.path.join(basepath, 'conf', 'devtool.conf'))
259 if not config.read():
260 return -1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500261 context.config = config
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500262
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500263 bitbake_subdir = config.get('General', 'bitbake_subdir', '')
264 if bitbake_subdir:
265 # Normally set for use within the SDK
266 logger.debug('Using bitbake subdir %s' % bitbake_subdir)
267 sys.path.insert(0, os.path.join(basepath, bitbake_subdir, 'lib'))
268 core_meta_subdir = config.get('General', 'core_meta_subdir')
269 sys.path.insert(0, os.path.join(basepath, core_meta_subdir, 'lib'))
270 else:
271 # Standard location
272 import scriptpath
273 bitbakepath = scriptpath.add_bitbake_lib_path()
274 if not bitbakepath:
275 logger.error("Unable to find bitbake by searching parent directory of this script or PATH")
276 sys.exit(1)
277 logger.debug('Using standard bitbake path %s' % bitbakepath)
278 scriptpath.add_oe_lib_path()
279
280 scriptutils.logger_setup_color(logger, global_args.color)
281
282 if global_args.bbpath is None:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500283 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500284 global_args.bbpath = tinfoil.config_data.getVar('BBPATH', True)
285 else:
286 tinfoil = None
287
288 for path in [scripts_path] + global_args.bbpath.split(':'):
289 pluginpath = os.path.join(path, 'lib', 'devtool')
290 scriptutils.load_plugins(logger, plugins, pluginpath)
291
292 if tinfoil:
293 tinfoil.shutdown()
294
295 subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>')
296
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500297 subparsers.add_subparser_group('sdk', 'SDK maintenance', -2)
298 subparsers.add_subparser_group('advanced', 'Advanced', -1)
299 subparsers.add_subparser_group('starting', 'Beginning work on a recipe', 100)
300 subparsers.add_subparser_group('info', 'Getting information')
301 subparsers.add_subparser_group('working', 'Working on a recipe in the workspace')
302 subparsers.add_subparser_group('testbuild', 'Testing changes on target')
303
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500304 if not context.fixed_setup:
305 parser_create_workspace = subparsers.add_parser('create-workspace',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500306 help='Set up workspace in an alternative location',
307 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.',
308 group='advanced')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500309 parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created')
310 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 -0500311 parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500312
313 for plugin in plugins:
314 if hasattr(plugin, 'register_commands'):
315 plugin.register_commands(subparsers, context)
316
317 args = parser.parse_args(unparsed_args, namespace=global_args)
318
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500319 if not getattr(args, 'no_workspace', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500320 read_workspace()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500321 create_unlockedsigs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322
323 try:
324 ret = args.func(args, config, basepath, workspace)
325 except DevtoolError as err:
326 if str(err):
327 logger.error(str(err))
328 ret = 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500329 except argparse_oe.ArgumentUsageError as ae:
330 parser.error_subcommand(ae.message, ae.subcommand)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500331
332 return ret
333
334
335if __name__ == "__main__":
336 try:
337 ret = main()
338 except Exception:
339 ret = 1
340 import traceback
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500341 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500342 sys.exit(ret)