summaryrefslogtreecommitdiffstats
path: root/scripts/devtool
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/devtool')
-rwxr-xr-xscripts/devtool357
1 files changed, 0 insertions, 357 deletions
diff --git a/scripts/devtool b/scripts/devtool
deleted file mode 100755
index 39cebec0d8..0000000000
--- a/scripts/devtool
+++ /dev/null
@@ -1,357 +0,0 @@
1#!/usr/bin/env python3
2
3# OpenEmbedded Development tool
4#
5# Copyright (C) 2014-2015 Intel Corporation
6#
7# SPDX-License-Identifier: GPL-2.0-only
8#
9
10import dataclasses
11import sys
12import os
13import argparse
14import glob
15import re
16import configparser
17import logging
18
19# This can be removed once our minimum is Python 3.9: https://docs.python.org/3/whatsnew/3.9.html#type-hinting-generics-in-standard-collections
20from typing import List
21
22
23scripts_path = os.path.dirname(os.path.realpath(__file__))
24lib_path = scripts_path + '/lib'
25sys.path = sys.path + [lib_path]
26from devtool import DevtoolError, setup_tinfoil
27import scriptutils
28import argparse_oe
29logger = scriptutils.logger_create('devtool')
30
31
32class ConfigHandler:
33 basepath = None
34 config_file = ''
35 config_obj = None
36 init_path = ''
37 workspace_path = ''
38
39 def __init__(self, basepath, filename):
40 self.basepath = basepath
41 self.config_file = filename
42 self.config_obj = configparser.ConfigParser()
43
44 def get(self, section, option, default=None):
45 try:
46 ret = self.config_obj.get(section, option)
47 except (configparser.NoOptionError, configparser.NoSectionError):
48 if default is not None:
49 ret = default
50 else:
51 raise
52 return ret
53
54 def read(self):
55 if os.path.exists(self.config_file):
56 self.config_obj.read(self.config_file)
57
58 if self.config_obj.has_option('General', 'init_path'):
59 pth = self.get('General', 'init_path')
60 self.init_path = os.path.join(self.basepath, pth)
61 if not os.path.exists(self.init_path):
62 logger.error('init_path %s specified in config file cannot be found' % pth)
63 return False
64 else:
65 self.config_obj.add_section('General')
66
67 self.workspace_path = self.get('General', 'workspace_path', os.path.join(self.basepath, 'workspace'))
68 return True
69
70
71 def write(self):
72 logger.debug('writing to config file %s' % self.config_file)
73 self.config_obj.set('General', 'workspace_path', self.workspace_path)
74 with open(self.config_file, 'w') as f:
75 self.config_obj.write(f)
76
77 def set(self, section, option, value):
78 if not self.config_obj.has_section(section):
79 self.config_obj.add_section(section)
80 self.config_obj.set(section, option, value)
81
82
83@dataclasses.dataclass
84class Context:
85 fixed_setup: bool
86 config: ConfigHandler
87 pluginpaths: List[str]
88
89
90def read_workspace(basepath, context):
91 workspace = {}
92 if not os.path.exists(os.path.join(context.config.workspace_path, 'conf', 'layer.conf')):
93 if context.fixed_setup:
94 logger.error("workspace layer not set up")
95 sys.exit(1)
96 else:
97 logger.info('Creating workspace layer in %s' % context.config.workspace_path)
98 _create_workspace(context.config.workspace_path, basepath)
99 if not context.fixed_setup:
100 _enable_workspace_layer(context.config.workspace_path, context.config, basepath)
101
102 logger.debug('Reading workspace in %s' % context.config.workspace_path)
103 externalsrc_re = re.compile(r'^EXTERNALSRC(:pn-([^ =]+))? *= *"([^"]*)"$')
104 for fn in glob.glob(os.path.join(context.config.workspace_path, 'appends', '*.bbappend')):
105 with open(fn, 'r') as f:
106 pnvalues = {}
107 pn = None
108 for line in f:
109 res = externalsrc_re.match(line.rstrip())
110 if res:
111 recipepn = os.path.splitext(os.path.basename(fn))[0].split('_')[0]
112 pn = res.group(2) or recipepn
113 # Find the recipe file within the workspace, if any
114 bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*')
115 recipefile = glob.glob(os.path.join(context.config.workspace_path,
116 'recipes',
117 recipepn,
118 bbfile))
119 if recipefile:
120 recipefile = recipefile[0]
121 pnvalues['srctree'] = res.group(3)
122 pnvalues['bbappend'] = fn
123 pnvalues['recipefile'] = recipefile
124 elif line.startswith('# srctreebase: '):
125 pnvalues['srctreebase'] = line.split(':', 1)[1].strip()
126 if pnvalues:
127 if not pn:
128 raise DevtoolError("Found *.bbappend in %s, but could not determine EXTERNALSRC:pn-*. "
129 "Maybe still using old syntax?" % context.config.workspace_path)
130 if not pnvalues.get('srctreebase', None):
131 pnvalues['srctreebase'] = pnvalues['srctree']
132 logger.debug('Found recipe %s' % pnvalues)
133 workspace[pn] = pnvalues
134
135 return workspace
136
137def create_workspace(args, config, basepath, _workspace):
138 if args.layerpath:
139 workspacedir = os.path.abspath(args.layerpath)
140 else:
141 workspacedir = os.path.abspath(os.path.join(basepath, 'workspace'))
142 layerseries = None
143 if args.layerseries:
144 layerseries = args.layerseries
145 _create_workspace(workspacedir, basepath, layerseries)
146 if not args.create_only:
147 _enable_workspace_layer(workspacedir, config, basepath)
148
149def _create_workspace(workspacedir, basepath, layerseries=None):
150 import bb.utils
151
152 confdir = os.path.join(workspacedir, 'conf')
153 if os.path.exists(os.path.join(confdir, 'layer.conf')):
154 logger.info('Specified workspace already set up, leaving as-is')
155 else:
156 if not layerseries:
157 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
158 try:
159 layerseries = tinfoil.config_data.getVar('LAYERSERIES_CORENAMES')
160 finally:
161 tinfoil.shutdown()
162
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 f.write('LAYERSERIES_COMPAT_workspacelayer = "%s"\n' % layerseries)
175 # Add a README file
176 with open(os.path.join(workspacedir, 'README'), 'w') as f:
177 f.write('This layer was created by the OpenEmbedded devtool utility in order to\n')
178 f.write('contain recipes and bbappends that are currently being worked on. The idea\n')
179 f.write('is that the contents is temporary - once you have finished working on a\n')
180 f.write('recipe you use the appropriate method to move the files you have been\n')
181 f.write('working on to a proper layer. In most instances you should use the\n')
182 f.write('devtool utility to manage files within it rather than modifying files\n')
183 f.write('directly (although recipes added with "devtool add" will often need\n')
184 f.write('direct modification.)\n')
185 f.write('\nIf you no longer need to use devtool or the workspace layer\'s contents\n')
186 f.write('you can remove the path to this workspace layer from your conf/bblayers.conf\n')
187 f.write('file (and then delete the layer, if you wish).\n')
188 f.write('\nNote that by default, if devtool fetches and unpacks source code, it\n')
189 f.write('will place it in a subdirectory of a "sources" subdirectory of the\n')
190 f.write('layer. If you prefer it to be elsewhere you can specify the source\n')
191 f.write('tree path on the command line.\n')
192
193def _enable_workspace_layer(workspacedir, config, basepath):
194 """Ensure the workspace layer is in bblayers.conf"""
195 import bb.utils
196 bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf')
197 if not os.path.exists(bblayers_conf):
198 logger.error('Unable to find bblayers.conf')
199 return
200 if os.path.abspath(workspacedir) != os.path.abspath(config.workspace_path):
201 removedir = config.workspace_path
202 else:
203 removedir = None
204 _, added = bb.utils.edit_bblayers_conf(bblayers_conf, workspacedir, removedir)
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 if sys.getfilesystemencoding() != "utf-8":
215 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.")
216
217 # Default basepath
218 basepath = os.path.dirname(os.path.abspath(__file__))
219
220 parser = argparse_oe.ArgumentParser(description="OpenEmbedded development tool",
221 add_help=False,
222 epilog="Use %(prog)s <subcommand> --help to get help on a specific command")
223 parser.add_argument('--basepath', help='Base directory of SDK / build directory')
224 parser.add_argument('--bbpath', help='Explicitly specify the BBPATH, rather than getting it from the metadata')
225 parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
226 parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true')
227 parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR')
228
229 global_args, unparsed_args = parser.parse_known_args()
230
231 # Help is added here rather than via add_help=True, as we don't want it to
232 # be handled by parse_known_args()
233 parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
234 help='show this help message and exit')
235
236 if global_args.debug:
237 logger.setLevel(logging.DEBUG)
238 elif global_args.quiet:
239 logger.setLevel(logging.ERROR)
240
241 is_fixed_setup = False
242
243 if global_args.basepath:
244 # Override
245 basepath = global_args.basepath
246 if os.path.exists(os.path.join(basepath, '.devtoolbase')):
247 is_fixed_setup = True
248 else:
249 pth = basepath
250 while pth != '' and pth != os.sep:
251 if os.path.exists(os.path.join(pth, '.devtoolbase')):
252 is_fixed_setup = True
253 basepath = pth
254 break
255 pth = os.path.dirname(pth)
256
257 if not is_fixed_setup:
258 basepath = os.environ.get('BUILDDIR')
259 if not basepath:
260 logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)")
261 sys.exit(1)
262
263 logger.debug('Using basepath %s' % basepath)
264
265 config = ConfigHandler(basepath, os.path.join(basepath, 'conf', 'devtool.conf'))
266 if not config.read():
267 return -1
268
269 bitbake_subdir = config.get('General', 'bitbake_subdir', '')
270 if bitbake_subdir:
271 # Normally set for use within the SDK
272 logger.debug('Using bitbake subdir %s' % bitbake_subdir)
273 sys.path.insert(0, os.path.join(basepath, bitbake_subdir, 'lib'))
274 core_meta_subdir = config.get('General', 'core_meta_subdir')
275 sys.path.insert(0, os.path.join(basepath, core_meta_subdir, 'lib'))
276 else:
277 # Standard location
278 import scriptpath
279 bitbakepath = scriptpath.add_bitbake_lib_path()
280 if not bitbakepath:
281 logger.error("Unable to find bitbake by searching parent directory of this script or PATH")
282 sys.exit(1)
283 logger.debug('Using standard bitbake path %s' % bitbakepath)
284 scriptpath.add_oe_lib_path()
285
286 scriptutils.logger_setup_color(logger, global_args.color)
287
288 if global_args.bbpath is None:
289 import bb
290 try:
291 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
292 try:
293 global_args.bbpath = tinfoil.config_data.getVar('BBPATH')
294 finally:
295 tinfoil.shutdown()
296 except bb.BBHandledException:
297 return 2
298
299 # Search BBPATH first to allow layers to override plugins in scripts_path
300 pluginpaths = [os.path.join(path, 'lib', 'devtool') for path in global_args.bbpath.split(':') + [scripts_path]]
301
302 context = Context(fixed_setup=is_fixed_setup, config=config, pluginpaths=pluginpaths)
303
304 plugins = []
305 for pluginpath in pluginpaths:
306 scriptutils.load_plugins(logger, plugins, pluginpath)
307
308 subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>')
309 subparsers.required = True
310
311 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
318 if not context.fixed_setup:
319 parser_create_workspace = subparsers.add_parser('create-workspace',
320 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')
323 parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created')
324 parser_create_workspace.add_argument('--layerseries', help='Layer series the workspace should be set to be compatible with')
325 parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace layer, do not alter configuration')
326 parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True)
327
328 for plugin in plugins:
329 if hasattr(plugin, 'register_commands'):
330 plugin.register_commands(subparsers, context)
331
332 args = parser.parse_args(unparsed_args, namespace=global_args)
333
334 try:
335 workspace = {}
336 if not getattr(args, 'no_workspace', False):
337 workspace = read_workspace(basepath, context)
338 ret = args.func(args, config, basepath, workspace)
339 except DevtoolError as err:
340 if str(err):
341 logger.error(str(err))
342 ret = err.exitcode
343 except argparse_oe.ArgumentUsageError as ae:
344 parser.error_subcommand(ae.message, ae.subcommand)
345 ret = 2
346
347 return ret
348
349
350if __name__ == "__main__":
351 try:
352 ret = main()
353 except Exception:
354 ret = 1
355 import traceback
356 traceback.print_exc()
357 sys.exit(ret)