blob: c9ad9ddb9550d83a352c60432bbb96200344f92b [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
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500218 if sys.getfilesystemencoding() != "utf-8":
219 sys.exit("Please use a locale setting which supports utf-8.\nPython can't change the filesystem locale after loading so we need a utf-8 when python starts or things won't work.")
220
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500221 context = Context(fixed_setup=False)
222
223 # Default basepath
224 basepath = os.path.dirname(os.path.abspath(__file__))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500226 parser = argparse_oe.ArgumentParser(description="OpenEmbedded development tool",
227 add_help=False,
228 epilog="Use %(prog)s <subcommand> --help to get help on a specific command")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500229 parser.add_argument('--basepath', help='Base directory of SDK / build directory')
230 parser.add_argument('--bbpath', help='Explicitly specify the BBPATH, rather than getting it from the metadata')
231 parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
232 parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true')
233 parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR')
234
235 global_args, unparsed_args = parser.parse_known_args()
236
237 # Help is added here rather than via add_help=True, as we don't want it to
238 # be handled by parse_known_args()
239 parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
240 help='show this help message and exit')
241
242 if global_args.debug:
243 logger.setLevel(logging.DEBUG)
244 elif global_args.quiet:
245 logger.setLevel(logging.ERROR)
246
247 if global_args.basepath:
248 # Override
249 basepath = global_args.basepath
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500250 if os.path.exists(os.path.join(basepath, '.devtoolbase')):
251 context.fixed_setup = True
252 else:
253 pth = basepath
254 while pth != '' and pth != os.sep:
255 if os.path.exists(os.path.join(pth, '.devtoolbase')):
256 context.fixed_setup = True
257 basepath = pth
258 break
259 pth = os.path.dirname(pth)
260
261 if not context.fixed_setup:
262 basepath = os.environ.get('BUILDDIR')
263 if not basepath:
264 logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)")
265 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500266
267 logger.debug('Using basepath %s' % basepath)
268
269 config = ConfigHandler(os.path.join(basepath, 'conf', 'devtool.conf'))
270 if not config.read():
271 return -1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500272 context.config = config
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500273
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500274 bitbake_subdir = config.get('General', 'bitbake_subdir', '')
275 if bitbake_subdir:
276 # Normally set for use within the SDK
277 logger.debug('Using bitbake subdir %s' % bitbake_subdir)
278 sys.path.insert(0, os.path.join(basepath, bitbake_subdir, 'lib'))
279 core_meta_subdir = config.get('General', 'core_meta_subdir')
280 sys.path.insert(0, os.path.join(basepath, core_meta_subdir, 'lib'))
281 else:
282 # Standard location
283 import scriptpath
284 bitbakepath = scriptpath.add_bitbake_lib_path()
285 if not bitbakepath:
286 logger.error("Unable to find bitbake by searching parent directory of this script or PATH")
287 sys.exit(1)
288 logger.debug('Using standard bitbake path %s' % bitbakepath)
289 scriptpath.add_oe_lib_path()
290
291 scriptutils.logger_setup_color(logger, global_args.color)
292
293 if global_args.bbpath is None:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600294 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500295 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
296 try:
297 global_args.bbpath = tinfoil.config_data.getVar('BBPATH')
298 finally:
299 tinfoil.shutdown()
300 except bb.BBHandledException:
301 return 2
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500302
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500303 # Search BBPATH first to allow layers to override plugins in scripts_path
304 for path in global_args.bbpath.split(':') + [scripts_path]:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500305 pluginpath = os.path.join(path, 'lib', 'devtool')
306 scriptutils.load_plugins(logger, plugins, pluginpath)
307
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500308 subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600309 subparsers.required = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500310
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500311 subparsers.add_subparser_group('sdk', 'SDK maintenance', -2)
312 subparsers.add_subparser_group('advanced', 'Advanced', -1)
313 subparsers.add_subparser_group('starting', 'Beginning work on a recipe', 100)
314 subparsers.add_subparser_group('info', 'Getting information')
315 subparsers.add_subparser_group('working', 'Working on a recipe in the workspace')
316 subparsers.add_subparser_group('testbuild', 'Testing changes on target')
317
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500318 if not context.fixed_setup:
319 parser_create_workspace = subparsers.add_parser('create-workspace',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500320 help='Set up workspace in an alternative location',
321 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.',
322 group='advanced')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323 parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created')
324 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 -0500325 parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326
327 for plugin in plugins:
328 if hasattr(plugin, 'register_commands'):
329 plugin.register_commands(subparsers, context)
330
331 args = parser.parse_args(unparsed_args, namespace=global_args)
332
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500333 if not getattr(args, 'no_workspace', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500334 read_workspace()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500335 create_unlockedsigs()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500336
337 try:
338 ret = args.func(args, config, basepath, workspace)
339 except DevtoolError as err:
340 if str(err):
341 logger.error(str(err))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600342 ret = err.exitcode
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500343 except argparse_oe.ArgumentUsageError as ae:
344 parser.error_subcommand(ae.message, ae.subcommand)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500345
346 return ret
347
348
349if __name__ == "__main__":
350 try:
351 ret = main()
352 except Exception:
353 ret = 1
354 import traceback
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500355 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500356 sys.exit(ret)