diff options
Diffstat (limited to 'scripts/devtool')
| -rwxr-xr-x | scripts/devtool | 108 |
1 files changed, 63 insertions, 45 deletions
diff --git a/scripts/devtool b/scripts/devtool index af4811b922..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 | ||
