blob: 5292f187e586b369963f65e25975d79344929896 [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
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500133def create_workspace(args, config, basepath, workspace):
134 if args.layerpath:
135 workspacedir = os.path.abspath(args.layerpath)
136 else:
137 workspacedir = os.path.abspath(os.path.join(basepath, 'workspace'))
138 _create_workspace(workspacedir, config, basepath)
139 if not args.create_only:
140 _enable_workspace_layer(workspacedir, config, basepath)
141
142def _create_workspace(workspacedir, config, basepath):
143 import bb
144
145 confdir = os.path.join(workspacedir, 'conf')
146 if os.path.exists(os.path.join(confdir, 'layer.conf')):
147 logger.info('Specified workspace already set up, leaving as-is')
148 else:
149 # Add a config file
150 bb.utils.mkdirhier(confdir)
151 with open(os.path.join(confdir, 'layer.conf'), 'w') as f:
152 f.write('# ### workspace layer auto-generated by devtool ###\n')
153 f.write('BBPATH =. "$' + '{LAYERDIR}:"\n')
154 f.write('BBFILES += "$' + '{LAYERDIR}/recipes/*/*.bb \\\n')
155 f.write(' $' + '{LAYERDIR}/appends/*.bbappend"\n')
156 f.write('BBFILE_COLLECTIONS += "workspacelayer"\n')
157 f.write('BBFILE_PATTERN_workspacelayer = "^$' + '{LAYERDIR}/"\n')
158 f.write('BBFILE_PATTERN_IGNORE_EMPTY_workspacelayer = "1"\n')
159 f.write('BBFILE_PRIORITY_workspacelayer = "99"\n')
160 # Add a README file
161 with open(os.path.join(workspacedir, 'README'), 'w') as f:
162 f.write('This layer was created by the OpenEmbedded devtool utility in order to\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600163 f.write('contain recipes and bbappends that are currently being worked on. The idea\n')
164 f.write('is that the contents is temporary - once you have finished working on a\n')
165 f.write('recipe you use the appropriate method to move the files you have been\n')
166 f.write('working on to a proper layer. In most instances you should use the\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500167 f.write('devtool utility to manage files within it rather than modifying files\n')
168 f.write('directly (although recipes added with "devtool add" will often need\n')
169 f.write('direct modification.)\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600170 f.write('\nIf you no longer need to use devtool or the workspace layer\'s contents\n')
171 f.write('you can remove the path to this workspace layer from your conf/bblayers.conf\n')
172 f.write('file (and then delete the layer, if you wish).\n')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500173 f.write('\nNote that by default, if devtool fetches and unpacks source code, it\n')
174 f.write('will place it in a subdirectory of a "sources" subdirectory of the\n')
175 f.write('layer. If you prefer it to be elsewhere you can specify the source\n')
176 f.write('tree path on the command line.\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500177
178def _enable_workspace_layer(workspacedir, config, basepath):
179 """Ensure the workspace layer is in bblayers.conf"""
180 import bb
181 bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf')
182 if not os.path.exists(bblayers_conf):
183 logger.error('Unable to find bblayers.conf')
184 return
185 _, added = bb.utils.edit_bblayers_conf(bblayers_conf, workspacedir, config.workspace_path)
186 if added:
187 logger.info('Enabling workspace layer in bblayers.conf')
188 if config.workspace_path != workspacedir:
189 # Update our config to point to the new location
190 config.workspace_path = workspacedir
191 config.write()
192
193
194def main():
195 global basepath
196 global config
197 global context
198
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500199 if sys.getfilesystemencoding() != "utf-8":
200 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.")
201
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500202 context = Context(fixed_setup=False)
203
204 # Default basepath
205 basepath = os.path.dirname(os.path.abspath(__file__))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500206
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500207 parser = argparse_oe.ArgumentParser(description="OpenEmbedded development tool",
208 add_help=False,
209 epilog="Use %(prog)s <subcommand> --help to get help on a specific command")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500210 parser.add_argument('--basepath', help='Base directory of SDK / build directory')
211 parser.add_argument('--bbpath', help='Explicitly specify the BBPATH, rather than getting it from the metadata')
212 parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
213 parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true')
214 parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR')
215
216 global_args, unparsed_args = parser.parse_known_args()
217
218 # Help is added here rather than via add_help=True, as we don't want it to
219 # be handled by parse_known_args()
220 parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
221 help='show this help message and exit')
222
223 if global_args.debug:
224 logger.setLevel(logging.DEBUG)
225 elif global_args.quiet:
226 logger.setLevel(logging.ERROR)
227
228 if global_args.basepath:
229 # Override
230 basepath = global_args.basepath
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500231 if os.path.exists(os.path.join(basepath, '.devtoolbase')):
232 context.fixed_setup = True
233 else:
234 pth = basepath
235 while pth != '' and pth != os.sep:
236 if os.path.exists(os.path.join(pth, '.devtoolbase')):
237 context.fixed_setup = True
238 basepath = pth
239 break
240 pth = os.path.dirname(pth)
241
242 if not context.fixed_setup:
243 basepath = os.environ.get('BUILDDIR')
244 if not basepath:
245 logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)")
246 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500247
248 logger.debug('Using basepath %s' % basepath)
249
250 config = ConfigHandler(os.path.join(basepath, 'conf', 'devtool.conf'))
251 if not config.read():
252 return -1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500253 context.config = config
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500255 bitbake_subdir = config.get('General', 'bitbake_subdir', '')
256 if bitbake_subdir:
257 # Normally set for use within the SDK
258 logger.debug('Using bitbake subdir %s' % bitbake_subdir)
259 sys.path.insert(0, os.path.join(basepath, bitbake_subdir, 'lib'))
260 core_meta_subdir = config.get('General', 'core_meta_subdir')
261 sys.path.insert(0, os.path.join(basepath, core_meta_subdir, 'lib'))
262 else:
263 # Standard location
264 import scriptpath
265 bitbakepath = scriptpath.add_bitbake_lib_path()
266 if not bitbakepath:
267 logger.error("Unable to find bitbake by searching parent directory of this script or PATH")
268 sys.exit(1)
269 logger.debug('Using standard bitbake path %s' % bitbakepath)
270 scriptpath.add_oe_lib_path()
271
272 scriptutils.logger_setup_color(logger, global_args.color)
273
274 if global_args.bbpath is None:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600275 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500276 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
277 try:
278 global_args.bbpath = tinfoil.config_data.getVar('BBPATH')
279 finally:
280 tinfoil.shutdown()
281 except bb.BBHandledException:
282 return 2
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500283
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500284 # Search BBPATH first to allow layers to override plugins in scripts_path
285 for path in global_args.bbpath.split(':') + [scripts_path]:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500286 pluginpath = os.path.join(path, 'lib', 'devtool')
287 scriptutils.load_plugins(logger, plugins, pluginpath)
288
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500289 subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600290 subparsers.required = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500291
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500292 subparsers.add_subparser_group('sdk', 'SDK maintenance', -2)
293 subparsers.add_subparser_group('advanced', 'Advanced', -1)
294 subparsers.add_subparser_group('starting', 'Beginning work on a recipe', 100)
295 subparsers.add_subparser_group('info', 'Getting information')
296 subparsers.add_subparser_group('working', 'Working on a recipe in the workspace')
297 subparsers.add_subparser_group('testbuild', 'Testing changes on target')
298
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500299 if not context.fixed_setup:
300 parser_create_workspace = subparsers.add_parser('create-workspace',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500301 help='Set up workspace in an alternative location',
302 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.',
303 group='advanced')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500304 parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created')
305 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 -0500306 parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500307
308 for plugin in plugins:
309 if hasattr(plugin, 'register_commands'):
310 plugin.register_commands(subparsers, context)
311
312 args = parser.parse_args(unparsed_args, namespace=global_args)
313
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500314 if not getattr(args, 'no_workspace', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500315 read_workspace()
316
317 try:
318 ret = args.func(args, config, basepath, workspace)
319 except DevtoolError as err:
320 if str(err):
321 logger.error(str(err))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600322 ret = err.exitcode
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500323 except argparse_oe.ArgumentUsageError as ae:
324 parser.error_subcommand(ae.message, ae.subcommand)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500325
326 return ret
327
328
329if __name__ == "__main__":
330 try:
331 ret = main()
332 except Exception:
333 ret = 1
334 import traceback
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500335 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500336 sys.exit(ret)