diff options
Diffstat (limited to 'scripts/devtool')
-rwxr-xr-x | scripts/devtool | 110 |
1 files changed, 64 insertions, 46 deletions
diff --git a/scripts/devtool b/scripts/devtool index 8a4f41bc37..39cebec0d8 100755 --- a/scripts/devtool +++ b/scripts/devtool | |||
@@ -7,19 +7,17 @@ | |||
7 | # SPDX-License-Identifier: GPL-2.0-only | 7 | # SPDX-License-Identifier: GPL-2.0-only |
8 | # | 8 | # |
9 | 9 | ||
10 | import dataclasses | ||
10 | import sys | 11 | import sys |
11 | import os | 12 | import os |
12 | import argparse | 13 | import argparse |
13 | import glob | 14 | import glob |
14 | import re | 15 | import re |
15 | import configparser | 16 | import configparser |
16 | import subprocess | ||
17 | import logging | 17 | import logging |
18 | 18 | ||
19 | basepath = '' | 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 |
20 | workspace = {} | 20 | from typing import List |
21 | config = None | ||
22 | context = None | ||
23 | 21 | ||
24 | 22 | ||
25 | scripts_path = os.path.dirname(os.path.realpath(__file__)) | 23 | scripts_path = os.path.dirname(os.path.realpath(__file__)) |
@@ -30,16 +28,16 @@ import scriptutils | |||
30 | import argparse_oe | 28 | import argparse_oe |
31 | logger = scriptutils.logger_create('devtool') | 29 | logger = scriptutils.logger_create('devtool') |
32 | 30 | ||
33 | plugins = [] | ||
34 | 31 | ||
35 | 32 | class ConfigHandler: | |
36 | class ConfigHandler(object): | 33 | basepath = None |
37 | config_file = '' | 34 | config_file = '' |
38 | config_obj = None | 35 | config_obj = None |
39 | init_path = '' | 36 | init_path = '' |
40 | workspace_path = '' | 37 | workspace_path = '' |
41 | 38 | ||
42 | def __init__(self, filename): | 39 | def __init__(self, basepath, filename): |
40 | self.basepath = basepath | ||
43 | self.config_file = filename | 41 | self.config_file = filename |
44 | self.config_obj = configparser.ConfigParser() | 42 | self.config_obj = configparser.ConfigParser() |
45 | 43 | ||
@@ -47,7 +45,7 @@ class ConfigHandler(object): | |||
47 | try: | 45 | try: |
48 | ret = self.config_obj.get(section, option) | 46 | ret = self.config_obj.get(section, option) |
49 | except (configparser.NoOptionError, configparser.NoSectionError): | 47 | except (configparser.NoOptionError, configparser.NoSectionError): |
50 | if default != None: | 48 | if default is not None: |
51 | ret = default | 49 | ret = default |
52 | else: | 50 | else: |
53 | raise | 51 | raise |
@@ -59,14 +57,14 @@ class ConfigHandler(object): | |||
59 | 57 | ||
60 | if self.config_obj.has_option('General', 'init_path'): | 58 | if self.config_obj.has_option('General', 'init_path'): |
61 | pth = self.get('General', 'init_path') | 59 | pth = self.get('General', 'init_path') |
62 | self.init_path = os.path.join(basepath, pth) | 60 | self.init_path = os.path.join(self.basepath, pth) |
63 | if not os.path.exists(self.init_path): | 61 | if not os.path.exists(self.init_path): |
64 | logger.error('init_path %s specified in config file cannot be found' % pth) | 62 | logger.error('init_path %s specified in config file cannot be found' % pth) |
65 | return False | 63 | return False |
66 | else: | 64 | else: |
67 | self.config_obj.add_section('General') | 65 | self.config_obj.add_section('General') |
68 | 66 | ||
69 | self.workspace_path = self.get('General', 'workspace_path', os.path.join(basepath, 'workspace')) | 67 | self.workspace_path = self.get('General', 'workspace_path', os.path.join(self.basepath, 'workspace')) |
70 | return True | 68 | return True |
71 | 69 | ||
72 | 70 | ||
@@ -81,29 +79,32 @@ class ConfigHandler(object): | |||
81 | self.config_obj.add_section(section) | 79 | self.config_obj.add_section(section) |
82 | self.config_obj.set(section, option, value) | 80 | self.config_obj.set(section, option, value) |
83 | 81 | ||
82 | |||
83 | @dataclasses.dataclass | ||
84 | class Context: | 84 | class Context: |
85 | def __init__(self, **kwargs): | 85 | fixed_setup: bool |
86 | self.__dict__.update(kwargs) | 86 | config: ConfigHandler |
87 | pluginpaths: List[str] | ||
87 | 88 | ||
88 | 89 | ||
89 | def read_workspace(): | 90 | def read_workspace(basepath, context): |
90 | global workspace | ||
91 | workspace = {} | 91 | workspace = {} |
92 | if not os.path.exists(os.path.join(config.workspace_path, 'conf', 'layer.conf')): | 92 | if not os.path.exists(os.path.join(context.config.workspace_path, 'conf', 'layer.conf')): |
93 | if context.fixed_setup: | 93 | if context.fixed_setup: |
94 | logger.error("workspace layer not set up") | 94 | logger.error("workspace layer not set up") |
95 | sys.exit(1) | 95 | sys.exit(1) |
96 | else: | 96 | else: |
97 | logger.info('Creating workspace layer in %s' % config.workspace_path) | 97 | logger.info('Creating workspace layer in %s' % context.config.workspace_path) |
98 | _create_workspace(config.workspace_path, config, basepath) | 98 | _create_workspace(context.config.workspace_path, basepath) |
99 | if not context.fixed_setup: | 99 | if not context.fixed_setup: |
100 | _enable_workspace_layer(config.workspace_path, config, basepath) | 100 | _enable_workspace_layer(context.config.workspace_path, context.config, basepath) |
101 | 101 | ||
102 | logger.debug('Reading workspace in %s' % config.workspace_path) | 102 | logger.debug('Reading workspace in %s' % context.config.workspace_path) |
103 | externalsrc_re = re.compile(r'^EXTERNALSRC(_pn-([^ =]+))? *= *"([^"]*)"$') | 103 | externalsrc_re = re.compile(r'^EXTERNALSRC(:pn-([^ =]+))? *= *"([^"]*)"$') |
104 | for fn in glob.glob(os.path.join(config.workspace_path, 'appends', '*.bbappend')): | 104 | for fn in glob.glob(os.path.join(context.config.workspace_path, 'appends', '*.bbappend')): |
105 | with open(fn, 'r') as f: | 105 | with open(fn, 'r') as f: |
106 | pnvalues = {} | 106 | pnvalues = {} |
107 | pn = None | ||
107 | for line in f: | 108 | for line in f: |
108 | res = externalsrc_re.match(line.rstrip()) | 109 | res = externalsrc_re.match(line.rstrip()) |
109 | if res: | 110 | if res: |
@@ -111,7 +112,7 @@ def read_workspace(): | |||
111 | pn = res.group(2) or recipepn | 112 | pn = res.group(2) or recipepn |
112 | # Find the recipe file within the workspace, if any | 113 | # Find the recipe file within the workspace, if any |
113 | bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*') | 114 | bbfile = os.path.basename(fn).replace('.bbappend', '.bb').replace('%', '*') |
114 | recipefile = glob.glob(os.path.join(config.workspace_path, | 115 | recipefile = glob.glob(os.path.join(context.config.workspace_path, |
115 | 'recipes', | 116 | 'recipes', |
116 | recipepn, | 117 | recipepn, |
117 | bbfile)) | 118 | bbfile)) |
@@ -123,27 +124,42 @@ def read_workspace(): | |||
123 | elif line.startswith('# srctreebase: '): | 124 | elif line.startswith('# srctreebase: '): |
124 | pnvalues['srctreebase'] = line.split(':', 1)[1].strip() | 125 | pnvalues['srctreebase'] = line.split(':', 1)[1].strip() |
125 | if pnvalues: | 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) | ||
126 | if not pnvalues.get('srctreebase', None): | 130 | if not pnvalues.get('srctreebase', None): |
127 | pnvalues['srctreebase'] = pnvalues['srctree'] | 131 | pnvalues['srctreebase'] = pnvalues['srctree'] |
128 | logger.debug('Found recipe %s' % pnvalues) | 132 | logger.debug('Found recipe %s' % pnvalues) |
129 | workspace[pn] = pnvalues | 133 | workspace[pn] = pnvalues |
130 | 134 | ||
131 | def create_workspace(args, config, basepath, workspace): | 135 | return workspace |
136 | |||
137 | def create_workspace(args, config, basepath, _workspace): | ||
132 | if args.layerpath: | 138 | if args.layerpath: |
133 | workspacedir = os.path.abspath(args.layerpath) | 139 | workspacedir = os.path.abspath(args.layerpath) |
134 | else: | 140 | else: |
135 | workspacedir = os.path.abspath(os.path.join(basepath, 'workspace')) | 141 | workspacedir = os.path.abspath(os.path.join(basepath, 'workspace')) |
136 | _create_workspace(workspacedir, config, basepath) | 142 | layerseries = None |
143 | if args.layerseries: | ||
144 | layerseries = args.layerseries | ||
145 | _create_workspace(workspacedir, basepath, layerseries) | ||
137 | if not args.create_only: | 146 | if not args.create_only: |
138 | _enable_workspace_layer(workspacedir, config, basepath) | 147 | _enable_workspace_layer(workspacedir, config, basepath) |
139 | 148 | ||
140 | def _create_workspace(workspacedir, config, basepath): | 149 | def _create_workspace(workspacedir, basepath, layerseries=None): |
141 | import bb | 150 | import bb.utils |
142 | 151 | ||
143 | confdir = os.path.join(workspacedir, 'conf') | 152 | confdir = os.path.join(workspacedir, 'conf') |
144 | if os.path.exists(os.path.join(confdir, 'layer.conf')): | 153 | if os.path.exists(os.path.join(confdir, 'layer.conf')): |
145 | logger.info('Specified workspace already set up, leaving as-is') | 154 | logger.info('Specified workspace already set up, leaving as-is') |
146 | else: | 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 | |||
147 | # Add a config file | 163 | # Add a config file |
148 | bb.utils.mkdirhier(confdir) | 164 | bb.utils.mkdirhier(confdir) |
149 | with open(os.path.join(confdir, 'layer.conf'), 'w') as f: | 165 | with open(os.path.join(confdir, 'layer.conf'), 'w') as f: |
@@ -155,7 +171,7 @@ def _create_workspace(workspacedir, config, basepath): | |||
155 | f.write('BBFILE_PATTERN_workspacelayer = "^$' + '{LAYERDIR}/"\n') | 171 | f.write('BBFILE_PATTERN_workspacelayer = "^$' + '{LAYERDIR}/"\n') |
156 | f.write('BBFILE_PATTERN_IGNORE_EMPTY_workspacelayer = "1"\n') | 172 | f.write('BBFILE_PATTERN_IGNORE_EMPTY_workspacelayer = "1"\n') |
157 | f.write('BBFILE_PRIORITY_workspacelayer = "99"\n') | 173 | f.write('BBFILE_PRIORITY_workspacelayer = "99"\n') |
158 | f.write('LAYERSERIES_COMPAT_workspacelayer = "${LAYERSERIES_COMPAT_core}"\n') | 174 | f.write('LAYERSERIES_COMPAT_workspacelayer = "%s"\n' % layerseries) |
159 | # Add a README file | 175 | # Add a README file |
160 | with open(os.path.join(workspacedir, 'README'), 'w') as f: | 176 | with open(os.path.join(workspacedir, 'README'), 'w') as f: |
161 | f.write('This layer was created by the OpenEmbedded devtool utility in order to\n') | 177 | f.write('This layer was created by the OpenEmbedded devtool utility in order to\n') |
@@ -176,7 +192,7 @@ def _create_workspace(workspacedir, config, basepath): | |||
176 | 192 | ||
177 | def _enable_workspace_layer(workspacedir, config, basepath): | 193 | def _enable_workspace_layer(workspacedir, config, basepath): |
178 | """Ensure the workspace layer is in bblayers.conf""" | 194 | """Ensure the workspace layer is in bblayers.conf""" |
179 | import bb | 195 | import bb.utils |
180 | bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf') | 196 | bblayers_conf = os.path.join(basepath, 'conf', 'bblayers.conf') |
181 | if not os.path.exists(bblayers_conf): | 197 | if not os.path.exists(bblayers_conf): |
182 | logger.error('Unable to find bblayers.conf') | 198 | logger.error('Unable to find bblayers.conf') |
@@ -195,15 +211,9 @@ def _enable_workspace_layer(workspacedir, config, basepath): | |||
195 | 211 | ||
196 | 212 | ||
197 | def main(): | 213 | def main(): |
198 | global basepath | ||
199 | global config | ||
200 | global context | ||
201 | |||
202 | if sys.getfilesystemencoding() != "utf-8": | 214 | if sys.getfilesystemencoding() != "utf-8": |
203 | 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.") | 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.") |
204 | 216 | ||
205 | context = Context(fixed_setup=False) | ||
206 | |||
207 | # Default basepath | 217 | # Default basepath |
208 | basepath = os.path.dirname(os.path.abspath(__file__)) | 218 | basepath = os.path.dirname(os.path.abspath(__file__)) |
209 | 219 | ||
@@ -228,21 +238,23 @@ def main(): | |||
228 | elif global_args.quiet: | 238 | elif global_args.quiet: |
229 | logger.setLevel(logging.ERROR) | 239 | logger.setLevel(logging.ERROR) |
230 | 240 | ||
241 | is_fixed_setup = False | ||
242 | |||
231 | if global_args.basepath: | 243 | if global_args.basepath: |
232 | # Override | 244 | # Override |
233 | basepath = global_args.basepath | 245 | basepath = global_args.basepath |
234 | if os.path.exists(os.path.join(basepath, '.devtoolbase')): | 246 | if os.path.exists(os.path.join(basepath, '.devtoolbase')): |
235 | context.fixed_setup = True | 247 | is_fixed_setup = True |
236 | else: | 248 | else: |
237 | pth = basepath | 249 | pth = basepath |
238 | while pth != '' and pth != os.sep: | 250 | while pth != '' and pth != os.sep: |
239 | if os.path.exists(os.path.join(pth, '.devtoolbase')): | 251 | if os.path.exists(os.path.join(pth, '.devtoolbase')): |
240 | context.fixed_setup = True | 252 | is_fixed_setup = True |
241 | basepath = pth | 253 | basepath = pth |
242 | break | 254 | break |
243 | pth = os.path.dirname(pth) | 255 | pth = os.path.dirname(pth) |
244 | 256 | ||
245 | if not context.fixed_setup: | 257 | if not is_fixed_setup: |
246 | basepath = os.environ.get('BUILDDIR') | 258 | basepath = os.environ.get('BUILDDIR') |
247 | if not basepath: | 259 | if not basepath: |
248 | logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)") | 260 | logger.error("This script can only be run after initialising the build environment (e.g. by using oe-init-build-env)") |
@@ -250,10 +262,9 @@ def main(): | |||
250 | 262 | ||
251 | logger.debug('Using basepath %s' % basepath) | 263 | logger.debug('Using basepath %s' % basepath) |
252 | 264 | ||
253 | config = ConfigHandler(os.path.join(basepath, 'conf', 'devtool.conf')) | 265 | config = ConfigHandler(basepath, os.path.join(basepath, 'conf', 'devtool.conf')) |
254 | if not config.read(): | 266 | if not config.read(): |
255 | return -1 | 267 | return -1 |
256 | context.config = config | ||
257 | 268 | ||
258 | bitbake_subdir = config.get('General', 'bitbake_subdir', '') | 269 | bitbake_subdir = config.get('General', 'bitbake_subdir', '') |
259 | if bitbake_subdir: | 270 | if bitbake_subdir: |
@@ -275,6 +286,7 @@ def main(): | |||
275 | scriptutils.logger_setup_color(logger, global_args.color) | 286 | scriptutils.logger_setup_color(logger, global_args.color) |
276 | 287 | ||
277 | if global_args.bbpath is None: | 288 | if global_args.bbpath is None: |
289 | import bb | ||
278 | try: | 290 | try: |
279 | tinfoil = setup_tinfoil(config_only=True, basepath=basepath) | 291 | tinfoil = setup_tinfoil(config_only=True, basepath=basepath) |
280 | try: | 292 | try: |
@@ -285,8 +297,12 @@ def main(): | |||
285 | return 2 | 297 | return 2 |
286 | 298 | ||
287 | # Search BBPATH first to allow layers to override plugins in scripts_path | 299 | # Search BBPATH first to allow layers to override plugins in scripts_path |
288 | for path in global_args.bbpath.split(':') + [scripts_path]: | 300 | pluginpaths = [os.path.join(path, 'lib', 'devtool') for path in global_args.bbpath.split(':') + [scripts_path]] |
289 | pluginpath = os.path.join(path, 'lib', 'devtool') | 301 | |
302 | context = Context(fixed_setup=is_fixed_setup, config=config, pluginpaths=pluginpaths) | ||
303 | |||
304 | plugins = [] | ||
305 | for pluginpath in pluginpaths: | ||
290 | scriptutils.load_plugins(logger, plugins, pluginpath) | 306 | scriptutils.load_plugins(logger, plugins, pluginpath) |
291 | 307 | ||
292 | subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>') | 308 | subparsers = parser.add_subparsers(dest="subparser_name", title='subcommands', metavar='<subcommand>') |
@@ -305,6 +321,7 @@ def main(): | |||
305 | 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.', | 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.', |
306 | group='advanced') | 322 | group='advanced') |
307 | parser_create_workspace.add_argument('layerpath', nargs='?', help='Path in which the workspace layer should be created') | 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') | ||
308 | parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace layer, do not alter configuration') | 325 | parser_create_workspace.add_argument('--create-only', action="store_true", help='Only create the workspace layer, do not alter configuration') |
309 | parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True) | 326 | parser_create_workspace.set_defaults(func=create_workspace, no_workspace=True) |
310 | 327 | ||
@@ -314,10 +331,10 @@ def main(): | |||
314 | 331 | ||
315 | args = parser.parse_args(unparsed_args, namespace=global_args) | 332 | args = parser.parse_args(unparsed_args, namespace=global_args) |
316 | 333 | ||
317 | if not getattr(args, 'no_workspace', False): | ||
318 | read_workspace() | ||
319 | |||
320 | try: | 334 | try: |
335 | workspace = {} | ||
336 | if not getattr(args, 'no_workspace', False): | ||
337 | workspace = read_workspace(basepath, context) | ||
321 | ret = args.func(args, config, basepath, workspace) | 338 | ret = args.func(args, config, basepath, workspace) |
322 | except DevtoolError as err: | 339 | except DevtoolError as err: |
323 | if str(err): | 340 | if str(err): |
@@ -325,6 +342,7 @@ def main(): | |||
325 | ret = err.exitcode | 342 | ret = err.exitcode |
326 | except argparse_oe.ArgumentUsageError as ae: | 343 | except argparse_oe.ArgumentUsageError as ae: |
327 | parser.error_subcommand(ae.message, ae.subcommand) | 344 | parser.error_subcommand(ae.message, ae.subcommand) |
345 | ret = 2 | ||
328 | 346 | ||
329 | return ret | 347 | return ret |
330 | 348 | ||