blob: 6674e67267a71196e597c78ec8abfee9a4cd896a [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Development tool - standard commands plugin
2#
Brad Bishopd7bf8c12018-02-25 22:55:05 -05003# Copyright (C) 2014-2017 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004#
Brad Bishopc342db32019-05-15 21:57:59 -04005# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05006#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007"""Devtool standard plugins"""
8
9import os
10import sys
11import re
12import shutil
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050013import subprocess
Patrick Williamsc124f4f2015-09-15 14:41:29 -050014import tempfile
15import logging
16import argparse
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050017import argparse_oe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050018import scriptutils
19import errno
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050020import glob
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021import filecmp
Patrick Williamsf1e5d692016-03-30 15:21:19 -050022from collections import OrderedDict
Brad Bishop316dfdd2018-06-25 12:45:53 -040023from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, use_external_build, setup_git_repo, recipe_to_append, get_bbclassextend_targets, update_unlockedsigs, check_prerelease_version, check_git_repo_dirty, check_git_repo_op, DevtoolError
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024from devtool import parse_recipe
25
26logger = logging.getLogger('devtool')
27
Brad Bishop316dfdd2018-06-25 12:45:53 -040028override_branch_prefix = 'devtool-override-'
29
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030
31def add(args, config, basepath, workspace):
32 """Entry point for the devtool 'add' subcommand"""
33 import bb
34 import oe.recipeutils
35
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050036 if not args.recipename and not args.srctree and not args.fetch and not args.fetchuri:
37 raise argparse_oe.ArgumentUsageError('At least one of recipename, srctree, fetchuri or -f/--fetch must be specified', 'add')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050039 # These are positional arguments, but because we're nice, allow
40 # specifying e.g. source tree without name, or fetch URI without name or
41 # source tree (if we can detect that that is what the user meant)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060042 if scriptutils.is_src_url(args.recipename):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050043 if not args.fetchuri:
44 if args.fetch:
45 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
46 args.fetchuri = args.recipename
47 args.recipename = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -060048 elif scriptutils.is_src_url(args.srctree):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050049 if not args.fetchuri:
50 if args.fetch:
51 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
52 args.fetchuri = args.srctree
53 args.srctree = ''
54 elif args.recipename and not args.srctree:
55 if os.sep in args.recipename:
56 args.srctree = args.recipename
57 args.recipename = None
58 elif os.path.isdir(args.recipename):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080059 logger.warning('Ambiguous argument "%s" - assuming you mean it to be the recipe name' % args.recipename)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060060
Brad Bishopd7bf8c12018-02-25 22:55:05 -050061 if not args.fetchuri:
62 if args.srcrev:
63 raise DevtoolError('The -S/--srcrev option is only valid when fetching from an SCM repository')
64 if args.srcbranch:
65 raise DevtoolError('The -B/--srcbranch option is only valid when fetching from an SCM repository')
66
Patrick Williamsc0f7c042017-02-23 20:41:17 -060067 if args.srctree and os.path.isfile(args.srctree):
68 args.fetchuri = 'file://' + os.path.abspath(args.srctree)
69 args.srctree = ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -050070
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050071 if args.fetch:
72 if args.fetchuri:
73 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
74 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080075 logger.warning('-f/--fetch option is deprecated - you can now simply specify the URL to fetch as a positional argument instead')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050076 args.fetchuri = args.fetch
Patrick Williamsf1e5d692016-03-30 15:21:19 -050077
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050078 if args.recipename:
79 if args.recipename in workspace:
80 raise DevtoolError("recipe %s is already in your workspace" %
81 args.recipename)
82 reason = oe.recipeutils.validate_pn(args.recipename)
83 if reason:
84 raise DevtoolError(reason)
85
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050086 if args.srctree:
87 srctree = os.path.abspath(args.srctree)
88 srctreeparent = None
89 tmpsrcdir = None
90 else:
91 srctree = None
92 srctreeparent = get_default_srctree(config)
93 bb.utils.mkdirhier(srctreeparent)
94 tmpsrcdir = tempfile.mkdtemp(prefix='devtoolsrc', dir=srctreeparent)
95
96 if srctree and os.path.exists(srctree):
97 if args.fetchuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098 if not os.path.isdir(srctree):
99 raise DevtoolError("Cannot fetch into source tree path %s as "
100 "it exists and is not a directory" %
101 srctree)
102 elif os.listdir(srctree):
103 raise DevtoolError("Cannot fetch into source tree path %s as "
104 "it already exists and is non-empty" %
105 srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500106 elif not args.fetchuri:
107 if args.srctree:
108 raise DevtoolError("Specified source tree %s could not be found" %
109 args.srctree)
110 elif srctree:
111 raise DevtoolError("No source tree exists at default path %s - "
112 "either create and populate this directory, "
113 "or specify a path to a source tree, or a "
114 "URI to fetch source from" % srctree)
115 else:
116 raise DevtoolError("You must either specify a source tree "
117 "or a URI to fetch source from")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500118
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119 if args.version:
120 if '_' in args.version or ' ' in args.version:
121 raise DevtoolError('Invalid version string "%s"' % args.version)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500122
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500123 if args.color == 'auto' and sys.stdout.isatty():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500124 color = 'always'
125 else:
126 color = args.color
127 extracmdopts = ''
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500128 if args.fetchuri:
129 source = args.fetchuri
130 if srctree:
131 extracmdopts += ' -x %s' % srctree
132 else:
133 extracmdopts += ' -x %s' % tmpsrcdir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500134 else:
135 source = srctree
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500136 if args.recipename:
137 extracmdopts += ' -N %s' % args.recipename
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500138 if args.version:
139 extracmdopts += ' -V %s' % args.version
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500140 if args.binary:
141 extracmdopts += ' -b'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500142 if args.also_native:
143 extracmdopts += ' --also-native'
144 if args.src_subdir:
145 extracmdopts += ' --src-subdir "%s"' % args.src_subdir
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600146 if args.autorev:
147 extracmdopts += ' -a'
Andrew Geissler82c905d2020-04-13 13:39:40 -0500148 if args.npm_dev:
149 extracmdopts += ' --npm-dev'
Patrick Williams169d7bc2024-01-05 11:33:25 -0600150 if args.no_pypi:
151 extracmdopts += ' --no-pypi'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500152 if args.mirrors:
153 extracmdopts += ' --mirrors'
154 if args.srcrev:
155 extracmdopts += ' --srcrev %s' % args.srcrev
156 if args.srcbranch:
157 extracmdopts += ' --srcbranch %s' % args.srcbranch
158 if args.provides:
159 extracmdopts += ' --provides %s' % args.provides
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500160
161 tempdir = tempfile.mkdtemp(prefix='devtool')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500162 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500163 try:
164 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create --devtool -o %s \'%s\' %s' % (color, tempdir, source, extracmdopts), watch=True)
165 except bb.process.ExecutionError as e:
166 if e.exitcode == 15:
167 raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line')
168 else:
169 raise DevtoolError('Command \'%s\' failed' % e.command)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500171 recipes = glob.glob(os.path.join(tempdir, '*.bb'))
172 if recipes:
173 recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0]
174 if recipename in workspace:
175 raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename)
176 recipedir = os.path.join(config.workspace_path, 'recipes', recipename)
177 bb.utils.mkdirhier(recipedir)
178 recipefile = os.path.join(recipedir, os.path.basename(recipes[0]))
179 appendfile = recipe_to_append(recipefile, config)
180 if os.path.exists(appendfile):
181 # This shouldn't be possible, but just in case
182 raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace')
183 if os.path.exists(recipefile):
184 raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile)
185 if tmpsrcdir:
186 srctree = os.path.join(srctreeparent, recipename)
187 if os.path.exists(tmpsrcdir):
188 if os.path.exists(srctree):
189 if os.path.isdir(srctree):
190 try:
191 os.rmdir(srctree)
192 except OSError as e:
193 if e.errno == errno.ENOTEMPTY:
194 raise DevtoolError('Source tree path %s already exists and is not empty' % srctree)
195 else:
196 raise
197 else:
198 raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree)
199 logger.info('Using default source tree path %s' % srctree)
200 shutil.move(tmpsrcdir, srctree)
201 else:
202 raise DevtoolError('Couldn\'t find source tree created by recipetool')
203 bb.utils.mkdirhier(recipedir)
204 shutil.move(recipes[0], recipefile)
205 # Move any additional files created by recipetool
206 for fn in os.listdir(tempdir):
207 shutil.move(os.path.join(tempdir, fn), recipedir)
208 else:
209 raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout))
210 attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile))
211 if os.path.exists(attic_recipe):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800212 logger.warning('A modified recipe from a previous invocation exists in %s - you may wish to move this over the top of the new recipe if you had changes in it that you want to continue with' % attic_recipe)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500213 finally:
214 if tmpsrcdir and os.path.exists(tmpsrcdir):
215 shutil.rmtree(tmpsrcdir)
216 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500217
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500218 for fn in os.listdir(recipedir):
219 _add_md5(config, recipename, os.path.join(recipedir, fn))
220
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500221 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600222 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500223 try:
224 rd = tinfoil.parse_recipe_file(recipefile, False)
225 except Exception as e:
226 logger.error(str(e))
227 rd = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600228 if not rd:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500229 # Parsing failed. We just created this recipe and we shouldn't
230 # leave it in the workdir or it'll prevent bitbake from starting
231 movefn = '%s.parsefailed' % recipefile
232 logger.error('Parsing newly created recipe failed, moving recipe to %s for reference. If this looks to be caused by the recipe itself, please report this error.' % movefn)
233 shutil.move(recipefile, movefn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600234 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500235
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600236 if args.fetchuri and not args.no_git:
237 setup_git_repo(srctree, args.version, 'devtool', d=tinfoil.config_data)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500238
Patrick Williamsda295312023-12-05 16:48:56 -0600239 initial_rev = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600240 if os.path.exists(os.path.join(srctree, '.git')):
241 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600242 initial_rev["."] = stdout.rstrip()
243 (stdout, _) = bb.process.run('git submodule --quiet foreach --recursive \'echo `git rev-parse HEAD` $PWD\'', cwd=srctree)
244 for line in stdout.splitlines():
245 (rev, submodule) = line.split()
246 initial_rev[os.path.relpath(submodule, srctree)] = rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500247
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600248 if args.src_subdir:
249 srctree = os.path.join(srctree, args.src_subdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500250
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600251 bb.utils.mkdirhier(os.path.dirname(appendfile))
252 with open(appendfile, 'w') as f:
253 f.write('inherit externalsrc\n')
254 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500255
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600256 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
257 if b_is_s:
258 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
259 if initial_rev:
Patrick Williamsda295312023-12-05 16:48:56 -0600260 for key, value in initial_rev.items():
261 f.write('\n# initial_rev %s: %s\n' % (key, value))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500262
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600263 if args.binary:
Patrick Williams213cb262021-08-07 19:21:33 -0500264 f.write('do_install:append() {\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600265 f.write(' rm -rf ${D}/.git\n')
266 f.write(' rm -f ${D}/singletask.lock\n')
267 f.write('}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500268
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600269 if bb.data.inherits_class('npm', rd):
Patrick Williams213cb262021-08-07 19:21:33 -0500270 f.write('python do_configure:append() {\n')
Andrew Geissler82c905d2020-04-13 13:39:40 -0500271 f.write(' pkgdir = d.getVar("NPM_PACKAGE")\n')
272 f.write(' lockfile = os.path.join(pkgdir, "singletask.lock")\n')
273 f.write(' bb.utils.remove(lockfile)\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600274 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500275
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500276 # Check if the new layer provides recipes whose priorities have been
277 # overriden by PREFERRED_PROVIDER.
278 recipe_name = rd.getVar('PN')
279 provides = rd.getVar('PROVIDES')
280 # Search every item defined in PROVIDES
281 for recipe_provided in provides.split():
282 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
283 current_pprovider = rd.getVar(preferred_provider)
284 if current_pprovider and current_pprovider != recipe_name:
285 if args.fixed_setup:
286 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
287 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
288 with open(layerconf_file, 'a') as f:
289 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
290 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800291 logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500292 break
293
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600294 _add_md5(config, recipename, appendfile)
295
Brad Bishop316dfdd2018-06-25 12:45:53 -0400296 check_prerelease_version(rd.getVar('PV'), 'devtool add')
297
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600298 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
299
300 finally:
301 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500302
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500303 return 0
304
305
306def _check_compatible_recipe(pn, d):
307 """Check if the recipe is supported by devtool"""
308 if pn == 'perf':
309 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600310 "source and thus cannot be supported by this tool",
311 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500312
313 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600314 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500315
316 if bb.data.inherits_class('image', d):
317 raise DevtoolError("The %s recipe is an image, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600318 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319
320 if bb.data.inherits_class('populate_sdk', d):
321 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600322 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323
324 if bb.data.inherits_class('packagegroup', d):
325 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600326 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500327
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500328 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600329 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500330 raise DevtoolError("externalsrc is currently enabled for the %s "
331 "recipe. This prevents the normal do_patch task "
332 "from working. You will need to disable this "
333 "first." % pn)
334
Brad Bishop316dfdd2018-06-25 12:45:53 -0400335def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
336 """Common function for copying a file to the dry run output directory"""
337 relpath = os.path.relpath(dst, base_outdir)
338 if relpath.startswith('..'):
339 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
340 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500341 dst_d = os.path.dirname(dst)
342 if dst_d:
343 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400344 # Don't overwrite existing files, otherwise in the case of an upgrade
345 # the dry-run written out recipe will be overwritten with an unmodified
346 # version
347 if not os.path.exists(dst):
348 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500349
Brad Bishop316dfdd2018-06-25 12:45:53 -0400350def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
351 """Move a file. Creates all the directory components of destination path."""
352 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
353 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
354 if dry_run_outdir:
355 # We want to copy here, not move
356 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
357 else:
358 dst_d = os.path.dirname(dst)
359 if dst_d:
360 bb.utils.mkdirhier(dst_d)
361 shutil.move(src, dst)
362
Andrew Geissler78b72792022-06-14 06:47:25 -0500363def _copy_file(src, dst, dry_run_outdir=None, base_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600364 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400365 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
366 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
367 if dry_run_outdir:
368 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
369 else:
370 dst_d = os.path.dirname(dst)
371 if dst_d:
372 bb.utils.mkdirhier(dst_d)
373 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600374
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500375def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
376 """List contents of a git treeish"""
377 import bb
378 cmd = ['git', 'ls-tree', '-z', treeish]
379 if recursive:
380 cmd.append('-r')
381 out, _ = bb.process.run(cmd, cwd=repodir)
382 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500383 if out:
384 for line in out.split('\0'):
385 if line:
386 split = line.split(None, 4)
387 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500388 return ret
389
390def _git_exclude_path(srctree, path):
391 """Return pathspec (list of paths) that excludes certain path"""
392 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
393 # we don't catch files that are deleted, for example. A more reliable way
394 # to implement this would be to use "negative pathspecs" which were
395 # introduced in Git v1.9.0. Revisit this when/if the required Git version
396 # becomes greater than that.
397 path = os.path.normpath(path)
398 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600399 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500400 if path in git_files:
401 git_files.remove(path)
402 return git_files
403 else:
404 return ['.']
405
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500406def _ls_tree(directory):
407 """Recursive listing of files in a directory"""
408 ret = []
409 for root, dirs, files in os.walk(directory):
410 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
411 fname in files])
412 return ret
413
414
415def extract(args, config, basepath, workspace):
416 """Entry point for the devtool 'extract' subcommand"""
417 import bb
418
Brad Bishop316dfdd2018-06-25 12:45:53 -0400419 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500420 if not tinfoil:
421 # Error already shown
422 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600423 try:
424 rd = parse_recipe(config, tinfoil, args.recipename, True)
425 if not rd:
426 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500427
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600428 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400429 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600430 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500431
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600432 if initial_rev:
433 return 0
434 else:
435 return 1
436 finally:
437 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500438
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500439def sync(args, config, basepath, workspace):
440 """Entry point for the devtool 'sync' subcommand"""
441 import bb
442
Brad Bishop316dfdd2018-06-25 12:45:53 -0400443 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500444 if not tinfoil:
445 # Error already shown
446 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600447 try:
448 rd = parse_recipe(config, tinfoil, args.recipename, True)
449 if not rd:
450 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500451
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600452 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400453 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, True, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600454 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500455
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600456 if initial_rev:
457 return 0
458 else:
459 return 1
460 finally:
461 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500462
Patrick Williams73bd93f2024-02-20 08:07:48 -0600463def symlink_oelocal_files_srctree(rd, srctree):
Brad Bishop96ff1982019-08-19 13:50:42 -0400464 import oe.patch
465 if os.path.abspath(rd.getVar('S')) == os.path.abspath(rd.getVar('WORKDIR')):
466 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
467 # (otherwise the recipe won't build as expected)
468 local_files_dir = os.path.join(srctree, 'oe-local-files')
469 addfiles = []
470 for root, _, files in os.walk(local_files_dir):
471 relpth = os.path.relpath(root, local_files_dir)
472 if relpth != '.':
473 bb.utils.mkdirhier(os.path.join(srctree, relpth))
474 for fn in files:
475 if fn == '.gitignore':
476 continue
477 destpth = os.path.join(srctree, relpth, fn)
478 if os.path.exists(destpth):
479 os.unlink(destpth)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600480 if relpth != '.':
481 back_relpth = os.path.relpath(local_files_dir, root)
482 os.symlink('%s/oe-local-files/%s/%s' % (back_relpth, relpth, fn), destpth)
483 else:
484 os.symlink('oe-local-files/%s' % fn, destpth)
Brad Bishop96ff1982019-08-19 13:50:42 -0400485 addfiles.append(os.path.join(relpth, fn))
486 if addfiles:
Patrick Williams73bd93f2024-02-20 08:07:48 -0600487 oe.patch.GitApplyTree.commitIgnored("Add local file symlinks", dir=srctree, files=addfiles, d=rd)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500488
Brad Bishop316dfdd2018-06-25 12:45:53 -0400489def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500490 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500491 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500492 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400493 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500494
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500495 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500496
497 _check_compatible_recipe(pn, d)
498
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500499 if sync:
500 if not os.path.exists(srctree):
501 raise DevtoolError("output path %s does not exist" % srctree)
502 else:
503 if os.path.exists(srctree):
504 if not os.path.isdir(srctree):
505 raise DevtoolError("output path %s exists and is not a directory" %
506 srctree)
507 elif os.listdir(srctree):
508 raise DevtoolError("output path %s already exists and is "
509 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500510
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500511 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
512 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600513 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500514
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500515 if not sync:
516 # Prepare for shutil.move later on
517 bb.utils.mkdirhier(srctree)
518 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500519
Brad Bishop316dfdd2018-06-25 12:45:53 -0400520 extra_overrides = []
521 if not no_overrides:
522 history = d.varhistory.variable('SRC_URI')
523 for event in history:
524 if not 'flag' in event:
Patrick Williams213cb262021-08-07 19:21:33 -0500525 if event['op'].startswith((':append[', ':prepend[')):
Andrew Geissler615f2f12022-07-15 14:00:58 -0500526 override = event['op'].split('[')[1].split(']')[0]
527 if not override.startswith('pn-'):
528 extra_overrides.append(override)
Andrew Geissler99467da2019-02-25 18:54:23 -0600529 # We want to remove duplicate overrides. If a recipe had multiple
530 # SRC_URI_override += values it would cause mulitple instances of
531 # overrides. This doesn't play nicely with things like creating a
532 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
533 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400534 if extra_overrides:
535 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
536
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500537 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500538
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500539 recipefile = d.getVar('FILE')
540 appendfile = recipe_to_append(recipefile, config)
541 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
542
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500543 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
544 # directory so that:
545 # (a) we pick up all files that get unpacked to the WORKDIR, and
546 # (b) we don't disturb the existing build
547 # However, with recipe-specific sysroots the sysroots for the recipe
548 # will be prepared under WORKDIR, and if we used the system temporary
549 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
550 # our attempts to hardlink files into the recipe-specific sysroots
551 # will fail on systems where /tmp is a different filesystem, and it
552 # would have to fall back to copying the files which is a waste of
553 # time. Put the temp directory under the WORKDIR to prevent that from
554 # being a problem.
555 tempbasedir = d.getVar('WORKDIR')
556 bb.utils.mkdirhier(tempbasedir)
557 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500558 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500559 tinfoil.logger.setLevel(logging.WARNING)
560
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500561 # FIXME this results in a cache reload under control of tinfoil, which is fine
562 # except we don't get the knotty progress bar
563
564 if os.path.exists(appendfile):
565 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
566 shutil.copyfile(appendfile, appendbackup)
567 else:
568 appendbackup = None
569 bb.utils.mkdirhier(os.path.dirname(appendfile))
570 logger.debug('writing append file %s' % appendfile)
571 with open(appendfile, 'a') as f:
572 f.write('###--- _extract_source\n')
Patrick Williams2a254922023-08-11 09:48:11 -0500573 f.write('deltask do_recipe_qa\n')
574 f.write('deltask do_recipe_qa_setscene\n')
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600575 f.write('ERROR_QA:remove = "patch-fuzz"\n')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500576 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
577 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
578 if not is_kernel_yocto:
579 f.write('PATCHTOOL = "git"\n')
580 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400581 if extra_overrides:
582 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500583 f.write('inherit devtool-source\n')
584 f.write('###--- _extract_source\n')
585
586 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
587
588 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
589 bb.utils.mkdirhier(sstate_manifests)
590 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
591 with open(preservestampfile, 'w') as f:
592 f.write(d.getVar('STAMP'))
Andrew Geissler220dafd2023-10-04 10:18:08 -0500593 tinfoil.modified_files()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500594 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400595 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500596 # We need to generate the kernel config
597 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500598 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500599 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500600
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600601 if 'noexec' in (d.getVarFlags(task, False) or []) or 'task' not in (d.getVarFlags(task, False) or []):
602 logger.info('The %s recipe has %s disabled. Running only '
603 'do_configure task dependencies' % (pn, task))
604
605 if 'depends' in d.getVarFlags('do_configure', False):
606 pn = d.getVarFlags('do_configure', False)['depends']
607 pn = pn.replace('${PV}', d.getVar('PV'))
608 pn = pn.replace('${COMPILERDEP}', d.getVar('COMPILERDEP'))
609 task = None
610
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500611 # Run the fetch + unpack tasks
612 res = tinfoil.build_targets(pn,
613 task,
614 handle_events=True)
615 finally:
616 if os.path.exists(preservestampfile):
617 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500618
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500619 if not res:
620 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500621
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600622 if not is_kernel_yocto and ('noexec' in (d.getVarFlags('do_patch', False) or []) or 'task' not in (d.getVarFlags('do_patch', False) or [])):
623 workshareddir = d.getVar('S')
624 if os.path.islink(srctree):
625 os.unlink(srctree)
626
627 os.symlink(workshareddir, srctree)
628
629 # The initial_rev file is created in devtool_post_unpack function that will not be executed if
630 # do_unpack/do_patch tasks are disabled so we have to directly say that source extraction was successful
631 return True, True
632
Brad Bishop316dfdd2018-06-25 12:45:53 -0400633 try:
634 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
635 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500636
Brad Bishop316dfdd2018-06-25 12:45:53 -0400637 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
638 srcsubdir = f.read()
639 except FileNotFoundError as e:
640 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
641 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500642
Brad Bishop96ff1982019-08-19 13:50:42 -0400643 # Check if work-shared is empty, if yes
644 # find source and copy to work-shared
645 if is_kernel_yocto:
646 workshareddir = d.getVar('STAGING_KERNEL_DIR')
647 staging_kerVer = get_staging_kver(workshareddir)
648 kernelVersion = d.getVar('LINUX_VERSION')
649
650 # handle dangling symbolic link in work-shared:
651 if os.path.islink(workshareddir):
652 os.unlink(workshareddir)
653
654 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
655 shutil.rmtree(workshareddir)
Patrick Williams73bd93f2024-02-20 08:07:48 -0600656 oe.path.copyhardlinktree(srcsubdir, workshareddir)
Brad Bishop96ff1982019-08-19 13:50:42 -0400657 elif not os.path.exists(workshareddir):
Patrick Williams73bd93f2024-02-20 08:07:48 -0600658 oe.path.copyhardlinktree(srcsubdir, workshareddir)
Brad Bishop96ff1982019-08-19 13:50:42 -0400659
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500660 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
661 srctree_localdir = os.path.join(srctree, 'oe-local-files')
662
663 if sync:
664 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
665
Patrick Williams73bd93f2024-02-20 08:07:48 -0600666 # Move the oe-local-files directory to srctree.
667 # As oe-local-files is not part of the constructed git tree,
668 # removing it directly during the synchronization might surprise
669 # the user. Instead, we move it to oe-local-files.bak and remind
670 # the user in the log message.
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500671 if os.path.exists(srctree_localdir + '.bak'):
Patrick Williams73bd93f2024-02-20 08:07:48 -0600672 shutil.rmtree(srctree_localdir + '.bak')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500673
674 if os.path.exists(srctree_localdir):
675 logger.info('Backing up current local file directory %s' % srctree_localdir)
676 shutil.move(srctree_localdir, srctree_localdir + '.bak')
677
678 if os.path.exists(tempdir_localdir):
679 logger.info('Syncing local source files to srctree...')
680 shutil.copytree(tempdir_localdir, srctree_localdir)
681 else:
682 # Move oe-local-files directory to srctree
683 if os.path.exists(tempdir_localdir):
684 logger.info('Adding local source files to srctree...')
685 shutil.move(tempdir_localdir, srcsubdir)
686
687 shutil.move(srcsubdir, srctree)
Patrick Williams73bd93f2024-02-20 08:07:48 -0600688 symlink_oelocal_files_srctree(d, srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500689
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500690 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500691 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500692 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500693
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500694 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500695 if appendbackup:
696 shutil.copyfile(appendbackup, appendfile)
697 elif os.path.exists(appendfile):
698 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500699 if keep_temp:
700 logger.info('Preserving temporary directory %s' % tempdir)
701 else:
702 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400703 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500704
705def _add_md5(config, recipename, filename):
706 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
707 import bb.utils
708
709 def addfile(fn):
710 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500711 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
712 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
713 f.seek(0, os.SEEK_SET)
714 if not md5_str in f.read():
715 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500716
717 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500718 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500719 for f in files:
720 addfile(os.path.join(root, f))
721 else:
722 addfile(filename)
723
724def _check_preserve(config, recipename):
725 """Check if a file was manually changed and needs to be saved in 'attic'
726 directory"""
727 import bb.utils
728 origfile = os.path.join(config.workspace_path, '.devtool_md5')
729 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500730 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500731 with open(origfile, 'r') as f:
732 with open(newfile, 'w') as tf:
733 for line in f.readlines():
734 splitline = line.rstrip().split('|')
735 if splitline[0] == recipename:
736 removefile = os.path.join(config.workspace_path, splitline[1])
737 try:
738 md5 = bb.utils.md5_file(removefile)
739 except IOError as err:
740 if err.errno == 2:
741 # File no longer exists, skip it
742 continue
743 else:
744 raise
745 if splitline[2] != md5:
746 bb.utils.mkdirhier(preservepath)
747 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800748 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500749 shutil.move(removefile, os.path.join(preservepath, preservefile))
750 else:
751 os.remove(removefile)
752 else:
753 tf.write(line)
Andrew Geisslerc926e172021-05-07 16:11:35 -0500754 bb.utils.rename(newfile, origfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500755
Brad Bishop96ff1982019-08-19 13:50:42 -0400756def get_staging_kver(srcdir):
757 # Kernel version from work-shared
758 kerver = []
759 staging_kerVer=""
760 if os.path.exists(srcdir) and os.listdir(srcdir):
Patrick Williams73bd93f2024-02-20 08:07:48 -0600761 with open(os.path.join(srcdir, "Makefile")) as f:
Brad Bishop96ff1982019-08-19 13:50:42 -0400762 version = [next(f) for x in range(5)][1:4]
763 for word in version:
764 kerver.append(word.split('= ')[1].split('\n')[0])
765 staging_kerVer = ".".join(kerver)
766 return staging_kerVer
767
768def get_staging_kbranch(srcdir):
769 staging_kbranch = ""
770 if os.path.exists(srcdir) and os.listdir(srcdir):
Patrick Williams705982a2024-01-12 09:51:57 -0600771 (branch, _) = bb.process.run('git branch | grep \\* | cut -d \' \' -f2', cwd=srcdir)
Brad Bishop96ff1982019-08-19 13:50:42 -0400772 staging_kbranch = "".join(branch.split('\n')[0])
773 return staging_kbranch
774
Andrew Geissler517393d2023-01-13 08:55:19 -0600775def get_real_srctree(srctree, s, workdir):
776 # Check that recipe isn't using a shared workdir
777 s = os.path.abspath(s)
778 workdir = os.path.abspath(workdir)
779 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
780 # Handle if S is set to a subdirectory of the source
781 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
782 srctree = os.path.join(srctree, srcsubdir)
783 return srctree
784
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500785def modify(args, config, basepath, workspace):
786 """Entry point for the devtool 'modify' subcommand"""
787 import bb
788 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400789 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400790 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500791
792 if args.recipename in workspace:
793 raise DevtoolError("recipe %s is already in your workspace" %
794 args.recipename)
795
Brad Bishop316dfdd2018-06-25 12:45:53 -0400796 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600797 try:
798 rd = parse_recipe(config, tinfoil, args.recipename, True)
799 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500800 return 1
801
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500802 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600803 if pn != args.recipename:
804 logger.info('Mapping %s to %s' % (args.recipename, pn))
805 if pn in workspace:
806 raise DevtoolError("recipe %s is already in your workspace" %
807 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500808
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600809 if args.srctree:
810 srctree = os.path.abspath(args.srctree)
811 else:
812 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500813
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600814 if args.no_extract and not os.path.isdir(srctree):
815 raise DevtoolError("--no-extract specified and source path %s does "
816 "not exist or is not a directory" %
817 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600818
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500819 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600820 appendfile = recipe_to_append(recipefile, config, args.wildcard)
821 if os.path.exists(appendfile):
822 raise DevtoolError("Another variant of recipe %s is already in your "
823 "workspace (only one variant of a recipe can "
824 "currently be worked on at once)"
825 % pn)
826
827 _check_compatible_recipe(pn, rd)
828
Patrick Williamsda295312023-12-05 16:48:56 -0600829 initial_revs = {}
830 commits = {}
Brad Bishop316dfdd2018-06-25 12:45:53 -0400831 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400832
833 if bb.data.inherits_class('kernel-yocto', rd):
834 # Current set kernel version
835 kernelVersion = rd.getVar('LINUX_VERSION')
836 srcdir = rd.getVar('STAGING_KERNEL_DIR')
837 kbranch = rd.getVar('KBRANCH')
838
839 staging_kerVer = get_staging_kver(srcdir)
840 staging_kbranch = get_staging_kbranch(srcdir)
841 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
Patrick Williams73bd93f2024-02-20 08:07:48 -0600842 oe.path.copyhardlinktree(srcdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400843 workdir = rd.getVar('WORKDIR')
844 srcsubdir = rd.getVar('S')
Patrick Williams73bd93f2024-02-20 08:07:48 -0600845 localfilesdir = os.path.join(srctree, 'oe-local-files')
Brad Bishop96ff1982019-08-19 13:50:42 -0400846 # Move local source files into separate subdir
847 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
848 local_files = oe.recipeutils.get_recipe_local_files(rd)
849
850 for key in local_files.copy():
851 if key.endswith('scc'):
852 sccfile = open(local_files[key], 'r')
853 for l in sccfile:
854 line = l.split()
855 if line and line[0] in ('kconf', 'patch'):
856 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
857 if not cfg in local_files.values():
858 local_files[line[-1]] = cfg
859 shutil.copy2(cfg, workdir)
860 sccfile.close()
861
862 # Ignore local files with subdir={BP}
863 srcabspath = os.path.abspath(srcsubdir)
864 local_files = [fname for fname in local_files if os.path.exists(os.path.join(workdir, fname)) and (srcabspath == workdir or not os.path.join(workdir, fname).startswith(srcabspath + os.sep))]
865 if local_files:
866 for fname in local_files:
867 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
868 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
Patrick Williams73bd93f2024-02-20 08:07:48 -0600869 f.write('# Ignore local files, by default. Remove this file if you want to commit the directory to Git\n*\n')
Brad Bishop96ff1982019-08-19 13:50:42 -0400870
Patrick Williams73bd93f2024-02-20 08:07:48 -0600871 symlink_oelocal_files_srctree(rd, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400872
873 task = 'do_configure'
874 res = tinfoil.build_targets(pn, task, handle_events=True)
875
876 # Copy .config to workspace
877 kconfpath = rd.getVar('B')
878 logger.info('Copying kernel config to workspace')
Patrick Williams73bd93f2024-02-20 08:07:48 -0600879 shutil.copy2(os.path.join(kconfpath, '.config'), srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400880
881 # Set this to true, we still need to get initial_rev
882 # by parsing the git repo
883 args.no_extract = True
884
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600885 if not args.no_extract:
Patrick Williamsda295312023-12-05 16:48:56 -0600886 initial_revs["."], _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides)
887 if not initial_revs["."]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600888 return 1
889 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600890
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600891 if os.path.exists(os.path.join(srctree, '.git')):
892 # Get list of commits since this revision
Patrick Williamsda295312023-12-05 16:48:56 -0600893 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_revs["."], cwd=srctree)
894 commits["."] = stdout.split()
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600895 check_commits = True
Patrick Williamsda295312023-12-05 16:48:56 -0600896 (stdout, _) = bb.process.run('git submodule --quiet foreach --recursive \'echo `git rev-parse devtool-base` $PWD\'', cwd=srctree)
897 for line in stdout.splitlines():
898 (rev, submodule_path) = line.split()
899 submodule = os.path.relpath(submodule_path, srctree)
900 initial_revs[submodule] = rev
901 (stdout, _) = bb.process.run('git rev-list --reverse devtool-base..HEAD', cwd=submodule_path)
902 commits[submodule] = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600903 else:
904 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600905 # Check if it's a tree previously extracted by us. This is done
906 # by ensuring that devtool-base and args.branch (devtool) exist.
907 # The check_commits logic will cause an exception if either one
908 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600909 try:
910 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600911 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600912 except bb.process.ExecutionError:
913 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400914 if stdout:
915 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600916 for line in stdout.splitlines():
917 if line.startswith('*'):
918 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600919 initial_revs["."] = stdout.rstrip()
Patrick Williams705982a2024-01-12 09:51:57 -0600920 if "." not in initial_revs:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600921 # Otherwise, just grab the head revision
922 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600923 initial_revs["."] = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500924
Brad Bishop316dfdd2018-06-25 12:45:53 -0400925 branch_patches = {}
926 if check_commits:
927 # Check if there are override branches
928 (stdout, _) = bb.process.run('git branch', cwd=srctree)
929 branches = []
930 for line in stdout.rstrip().splitlines():
931 branchname = line[2:].rstrip()
932 if branchname.startswith(override_branch_prefix):
933 branches.append(branchname)
934 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800935 logger.warning('SRC_URI is conditionally overridden in this recipe, thus several %s* branches have been created, one for each override that makes changes to SRC_URI. It is recommended that you make changes to the %s branch first, then checkout and rebase each %s* branch and update any unique patches there (duplicates on those branches will be ignored by devtool finish/update-recipe)' % (override_branch_prefix, args.branch, override_branch_prefix))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400936 branches.insert(0, args.branch)
937 seen_patches = []
938 for branch in branches:
939 branch_patches[branch] = []
Patrick Williams73bd93f2024-02-20 08:07:48 -0600940 (stdout, _) = bb.process.run('git rev-list devtool-base..%s' % branch, cwd=srctree)
941 for sha1 in stdout.splitlines():
942 notes = oe.patch.GitApplyTree.getNotes(srctree, sha1.strip())
943 origpatch = notes.get(oe.patch.GitApplyTree.original_patch)
944 if origpatch and origpatch not in seen_patches:
945 seen_patches.append(origpatch)
946 branch_patches[branch].append(origpatch)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400947
948 # Need to grab this here in case the source is within a subdirectory
949 srctreebase = srctree
Andrew Geissler517393d2023-01-13 08:55:19 -0600950 srctree = get_real_srctree(srctree, rd.getVar('S'), rd.getVar('WORKDIR'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500951
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600952 bb.utils.mkdirhier(os.path.dirname(appendfile))
953 with open(appendfile, 'w') as f:
Patrick Williamsf52e3dd2024-01-26 13:04:43 -0600954 # if not present, add type=git-dependency to the secondary sources
955 # (non local files) so they can be extracted correctly when building a recipe after
956 # doing a devtool modify on it
957 src_uri = rd.getVar('SRC_URI').split()
958 src_uri_append = []
959 src_uri_remove = []
960
961 # Assume first entry is main source extracted in ${S} so skip it
962 src_uri = src_uri[1::]
963
Patrick Williams73bd93f2024-02-20 08:07:48 -0600964 # Add "type=git-dependency" to all non local sources
Patrick Williamsf52e3dd2024-01-26 13:04:43 -0600965 for url in src_uri:
966 if not url.startswith('file://') and not 'type=' in url:
967 src_uri_remove.append(url)
968 src_uri_append.append('%s;type=git-dependency' % url)
969
970 if src_uri_remove:
971 f.write('SRC_URI:remove = "%s"\n' % ' '.join(src_uri_remove))
Patrick Williams73bd93f2024-02-20 08:07:48 -0600972 f.write('SRC_URI:append = " %s"\n\n' % ' '.join(src_uri_append))
Patrick Williamsf52e3dd2024-01-26 13:04:43 -0600973
Patrick Williams213cb262021-08-07 19:21:33 -0500974 f.write('FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600975 # Local files can be modified/tracked in separate subdir under srctree
976 # Mostly useful for packages with S != WORKDIR
Patrick Williams213cb262021-08-07 19:21:33 -0500977 f.write('FILESPATH:prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400978 os.path.join(srctreebase, 'oe-local-files'))
979 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500980
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600981 f.write('\ninherit externalsrc\n')
982 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500983 f.write('EXTERNALSRC:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500984
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600985 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
986 if b_is_s:
Patrick Williams213cb262021-08-07 19:21:33 -0500987 f.write('EXTERNALSRC_BUILD:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500988
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600989 if bb.data.inherits_class('kernel', rd):
990 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500991 'do_fetch do_unpack do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400992 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500993 f.write('\ndo_configure:append() {\n'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600994 ' cp ${B}/.config ${S}/.config.baseline\n'
995 ' ln -sfT ${B}/.config ${S}/.config.new\n'
996 '}\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500997 f.write('\ndo_kernel_configme:prepend() {\n'
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500998 ' if [ -e ${S}/.config ]; then\n'
999 ' mv ${S}/.config ${S}/.config.old\n'
1000 ' fi\n'
1001 '}\n')
Patrick Williams73bd93f2024-02-20 08:07:48 -06001002 if rd.getVarFlag('do_menuconfig', 'task'):
Patrick Williams213cb262021-08-07 19:21:33 -05001003 f.write('\ndo_configure:append() {\n'
Patrick Williams169d7bc2024-01-05 11:33:25 -06001004 ' if [ ${@oe.types.boolean(d.getVar("KCONFIG_CONFIG_ENABLE_MENUCONFIG"))} = True ]; then\n'
Patrick Williams520786c2023-06-25 16:20:36 -05001005 ' cp ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.baseline\n'
1006 ' ln -sfT ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.new\n'
Andrew Geissler82c905d2020-04-13 13:39:40 -05001007 ' fi\n'
Brad Bishop96ff1982019-08-19 13:50:42 -04001008 '}\n')
Patrick Williamsda295312023-12-05 16:48:56 -06001009 if initial_revs:
1010 for name, rev in initial_revs.items():
Patrick Williams169d7bc2024-01-05 11:33:25 -06001011 f.write('\n# initial_rev %s: %s\n' % (name, rev))
1012 if name in commits:
Patrick Williamsda295312023-12-05 16:48:56 -06001013 for commit in commits[name]:
1014 f.write('# commit %s: %s\n' % (name, commit))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001015 if branch_patches:
1016 for branch in branch_patches:
1017 if branch == args.branch:
1018 continue
1019 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001020
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001021 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
1022
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001023 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001024
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001025 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001026
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001027 finally:
1028 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001029
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001030 return 0
1031
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001032
1033def rename(args, config, basepath, workspace):
1034 """Entry point for the devtool 'rename' subcommand"""
1035 import bb
1036 import oe.recipeutils
1037
1038 check_workspace_recipe(workspace, args.recipename)
1039
1040 if not (args.newname or args.version):
1041 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
1042
1043 recipefile = workspace[args.recipename]['recipefile']
1044 if not recipefile:
1045 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
1046
1047 if args.newname and args.newname != args.recipename:
1048 reason = oe.recipeutils.validate_pn(args.newname)
1049 if reason:
1050 raise DevtoolError(reason)
1051 newname = args.newname
1052 else:
1053 newname = args.recipename
1054
1055 append = workspace[args.recipename]['bbappend']
1056 appendfn = os.path.splitext(os.path.basename(append))[0]
1057 splitfn = appendfn.split('_')
1058 if len(splitfn) > 1:
1059 origfnver = appendfn.split('_')[1]
1060 else:
1061 origfnver = ''
1062
1063 recipefilemd5 = None
1064 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1065 try:
1066 rd = parse_recipe(config, tinfoil, args.recipename, True)
1067 if not rd:
1068 return 1
1069
1070 bp = rd.getVar('BP')
1071 bpn = rd.getVar('BPN')
1072 if newname != args.recipename:
1073 localdata = rd.createCopy()
1074 localdata.setVar('PN', newname)
1075 newbpn = localdata.getVar('BPN')
1076 else:
1077 newbpn = bpn
1078 s = rd.getVar('S', False)
1079 src_uri = rd.getVar('SRC_URI', False)
1080 pv = rd.getVar('PV')
1081
1082 # Correct variable values that refer to the upstream source - these
1083 # values must stay the same, so if the name/version are changing then
1084 # we need to fix them up
1085 new_s = s
1086 new_src_uri = src_uri
1087 if newbpn != bpn:
1088 # ${PN} here is technically almost always incorrect, but people do use it
1089 new_s = new_s.replace('${BPN}', bpn)
1090 new_s = new_s.replace('${PN}', bpn)
1091 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1092 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1093 new_src_uri = new_src_uri.replace('${PN}', bpn)
1094 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1095 if args.version and origfnver == pv:
1096 new_s = new_s.replace('${PV}', pv)
1097 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1098 new_src_uri = new_src_uri.replace('${PV}', pv)
1099 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1100 patchfields = {}
1101 if new_s != s:
1102 patchfields['S'] = new_s
1103 if new_src_uri != src_uri:
1104 patchfields['SRC_URI'] = new_src_uri
1105 if patchfields:
1106 recipefilemd5 = bb.utils.md5_file(recipefile)
1107 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1108 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1109 finally:
1110 tinfoil.shutdown()
1111
1112 if args.version:
1113 newver = args.version
1114 else:
1115 newver = origfnver
1116
1117 if newver:
1118 newappend = '%s_%s.bbappend' % (newname, newver)
1119 newfile = '%s_%s.bb' % (newname, newver)
1120 else:
1121 newappend = '%s.bbappend' % newname
1122 newfile = '%s.bb' % newname
1123
1124 oldrecipedir = os.path.dirname(recipefile)
1125 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1126 if oldrecipedir != newrecipedir:
1127 bb.utils.mkdirhier(newrecipedir)
1128
1129 newappend = os.path.join(os.path.dirname(append), newappend)
1130 newfile = os.path.join(newrecipedir, newfile)
1131
1132 # Rename bbappend
1133 logger.info('Renaming %s to %s' % (append, newappend))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001134 bb.utils.rename(append, newappend)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001135 # Rename recipe file
1136 logger.info('Renaming %s to %s' % (recipefile, newfile))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001137 bb.utils.rename(recipefile, newfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001138
1139 # Rename source tree if it's the default path
1140 appendmd5 = None
1141 if not args.no_srctree:
1142 srctree = workspace[args.recipename]['srctree']
1143 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1144 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1145 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1146 shutil.move(srctree, newsrctree)
1147 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1148 appendmd5 = bb.utils.md5_file(newappend)
1149 appendlines = []
1150 with open(newappend, 'r') as f:
1151 for line in f:
1152 appendlines.append(line)
1153 with open(newappend, 'w') as f:
1154 for line in appendlines:
1155 if srctree in line:
1156 line = line.replace(srctree, newsrctree)
1157 f.write(line)
1158 newappendmd5 = bb.utils.md5_file(newappend)
1159
1160 bpndir = None
1161 newbpndir = None
1162 if newbpn != bpn:
1163 bpndir = os.path.join(oldrecipedir, bpn)
1164 if os.path.exists(bpndir):
1165 newbpndir = os.path.join(newrecipedir, newbpn)
1166 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1167 shutil.move(bpndir, newbpndir)
1168
1169 bpdir = None
1170 newbpdir = None
1171 if newver != origfnver or newbpn != bpn:
1172 bpdir = os.path.join(oldrecipedir, bp)
1173 if os.path.exists(bpdir):
1174 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1175 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1176 shutil.move(bpdir, newbpdir)
1177
1178 if oldrecipedir != newrecipedir:
1179 # Move any stray files and delete the old recipe directory
1180 for entry in os.listdir(oldrecipedir):
1181 oldpath = os.path.join(oldrecipedir, entry)
1182 newpath = os.path.join(newrecipedir, entry)
1183 logger.info('Renaming %s to %s' % (oldpath, newpath))
1184 shutil.move(oldpath, newpath)
1185 os.rmdir(oldrecipedir)
1186
1187 # Now take care of entries in .devtool_md5
1188 md5entries = []
1189 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1190 for line in f:
1191 md5entries.append(line)
1192
1193 if bpndir and newbpndir:
1194 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1195 else:
1196 relbpndir = None
1197 if bpdir and newbpdir:
1198 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1199 else:
1200 relbpdir = None
1201
1202 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1203 for entry in md5entries:
1204 splitentry = entry.rstrip().split('|')
1205 if len(splitentry) > 2:
1206 if splitentry[0] == args.recipename:
1207 splitentry[0] = newname
1208 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1209 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1210 if appendmd5 and splitentry[2] == appendmd5:
1211 splitentry[2] = newappendmd5
1212 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1213 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1214 if recipefilemd5 and splitentry[2] == recipefilemd5:
1215 splitentry[2] = newrecipefilemd5
1216 elif relbpndir and splitentry[1].startswith(relbpndir):
1217 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1218 elif relbpdir and splitentry[1].startswith(relbpdir):
1219 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1220 entry = '|'.join(splitentry) + '\n'
1221 f.write(entry)
1222 return 0
1223
1224
Brad Bishop316dfdd2018-06-25 12:45:53 -04001225def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001226 """Get initial and update rev of a recipe. These are the start point of the
1227 whole patchset and start point for the patches to be re-generated/updated.
1228 """
1229 import bb
1230
Brad Bishop316dfdd2018-06-25 12:45:53 -04001231 # Get current branch
1232 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1233 cwd=srctree)
1234 branchname = stdout.rstrip()
1235
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001236 # Parse initial rev from recipe if not specified
Patrick Williamsda295312023-12-05 16:48:56 -06001237 commits = {}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001238 patches = []
Patrick Williamsda295312023-12-05 16:48:56 -06001239 initial_revs = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001240 with open(recipe_path, 'r') as f:
1241 for line in f:
Patrick Williamsda295312023-12-05 16:48:56 -06001242 pattern = r'^#\s.*\s(.*):\s([0-9a-fA-F]+)$'
1243 match = re.search(pattern, line)
1244 if match:
1245 name = match.group(1)
1246 rev = match.group(2)
1247 if line.startswith('# initial_rev'):
1248 if not (name == "." and initial_rev):
1249 initial_revs[name] = rev
1250 elif line.startswith('# commit') and not force_patch_refresh:
1251 if name not in commits:
1252 commits[name] = [rev]
1253 else:
1254 commits[name].append(rev)
1255 elif line.startswith('# patches_%s:' % branchname):
1256 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001257
Patrick Williamsda295312023-12-05 16:48:56 -06001258 update_revs = dict(initial_revs)
1259 changed_revs = {}
1260 for name, rev in initial_revs.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001261 # Find first actually changed revision
1262 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
Patrick Williamsda295312023-12-05 16:48:56 -06001263 rev, cwd=os.path.join(srctree, name))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001264 newcommits = stdout.split()
Patrick Williamsda295312023-12-05 16:48:56 -06001265 if name in commits:
1266 for i in range(min(len(commits[name]), len(newcommits))):
1267 if newcommits[i] == commits[name][i]:
1268 update_revs[name] = commits[name][i]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001269
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001270 try:
1271 stdout, _ = bb.process.run('git cherry devtool-patched',
Patrick Williamsda295312023-12-05 16:48:56 -06001272 cwd=os.path.join(srctree, name))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001273 except bb.process.ExecutionError as err:
1274 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001275
Brad Bishop316dfdd2018-06-25 12:45:53 -04001276 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001277 for line in stdout.splitlines():
1278 if line.startswith('+ '):
1279 rev = line.split()[1]
1280 if rev in newcommits:
Patrick Williamsda295312023-12-05 16:48:56 -06001281 if name not in changed_revs:
1282 changed_revs[name] = [rev]
1283 else:
1284 changed_revs[name].append(rev)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001285
Patrick Williamsda295312023-12-05 16:48:56 -06001286 return initial_revs, update_revs, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001287
1288def _remove_file_entries(srcuri, filelist):
1289 """Remove file:// entries from SRC_URI"""
1290 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001291 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001292 for fname in filelist:
1293 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001294 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001295 if (srcuri[i].startswith('file://') and
1296 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001297 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001298 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001299 srcuri.pop(i)
1300 break
1301 return entries, remaining
1302
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001303def _replace_srcuri_entry(srcuri, filename, newentry):
1304 """Replace entry corresponding to specified file with a new entry"""
1305 basename = os.path.basename(filename)
1306 for i in range(len(srcuri)):
1307 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1308 srcuri.pop(i)
1309 srcuri.insert(i, newentry)
1310 break
1311
Brad Bishop316dfdd2018-06-25 12:45:53 -04001312def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001313 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001314
1315 dry_run_suffix = ' (dry-run)' if dry_run else ''
1316
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001317 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001318 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001319 if not destpath:
1320 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001321 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001322
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001323 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001324 if not no_report_remove:
1325 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1326 if not dry_run:
1327 # FIXME "git rm" here would be nice if the file in question is
1328 # tracked
1329 # FIXME there's a chance that this file is referred to by
1330 # another recipe, in which case deleting wouldn't be the
1331 # right thing to do
1332 os.remove(path)
1333 # Remove directory if empty
1334 try:
1335 os.rmdir(os.path.dirname(path))
1336 except OSError as ose:
1337 if ose.errno != errno.ENOTEMPTY:
1338 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001339
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001340
Patrick Williamsda295312023-12-05 16:48:56 -06001341def _export_patches(srctree, rd, start_revs, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001342 """Export patches from srctree to given location.
1343 Returns three-tuple of dicts:
1344 1. updated - patches that already exist in SRCURI
1345 2. added - new patches that don't exist in SRCURI
1346 3 removed - patches that exist in SRCURI but not in exported patches
Patrick Williamsda295312023-12-05 16:48:56 -06001347 In each dict the key is the 'basepath' of the URI and value is:
1348 - for updated and added dicts, a dict with 2 optionnal keys:
1349 - 'path': the absolute path to the existing file in recipe space (if any)
1350 - 'patchdir': the directory in wich the patch should be applied (if any)
1351 - for removed dict, the absolute path to the existing file in recipe space
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001352 """
1353 import oe.recipeutils
1354 from oe.patch import GitApplyTree
1355 updated = OrderedDict()
1356 added = OrderedDict()
1357 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1358
1359 existing_patches = dict((os.path.basename(path), path) for path in
1360 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001361 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001362
1363 # Generate patches from Git, exclude local files directory
1364 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
Patrick Williamsda295312023-12-05 16:48:56 -06001365 GitApplyTree.extractPatches(srctree, start_revs, destdir, patch_pathspec)
1366 for dirpath, dirnames, filenames in os.walk(destdir):
1367 new_patches = filenames
1368 reldirpath = os.path.relpath(dirpath, destdir)
1369 for new_patch in new_patches:
1370 # Strip numbering from patch names. If it's a git sequence named patch,
1371 # the numbers might not match up since we are starting from a different
1372 # revision This does assume that people are using unique shortlog
1373 # values, but they ought to be anyway...
1374 new_basename = seqpatch_re.match(new_patch).group(2)
1375 match_name = None
1376 for old_patch in existing_patches:
1377 old_basename = seqpatch_re.match(old_patch).group(2)
1378 old_basename_splitext = os.path.splitext(old_basename)
1379 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1380 old_patch_noext = os.path.splitext(old_patch)[0]
1381 match_name = old_patch_noext
1382 break
1383 elif new_basename == old_basename:
1384 match_name = old_patch
1385 break
1386 if match_name:
1387 # Rename patch files
1388 if new_patch != match_name:
1389 bb.utils.rename(os.path.join(destdir, new_patch),
1390 os.path.join(destdir, match_name))
1391 # Need to pop it off the list now before checking changed_revs
1392 oldpath = existing_patches.pop(old_patch)
1393 if changed_revs is not None and dirpath in changed_revs:
1394 # Avoid updating patches that have not actually changed
1395 with open(os.path.join(dirpath, match_name), 'r') as f:
1396 firstlineitems = f.readline().split()
1397 # Looking for "From <hash>" line
1398 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1399 if not firstlineitems[1] in changed_revs[dirpath]:
1400 continue
1401 # Recompress if necessary
1402 if oldpath.endswith(('.gz', '.Z')):
1403 bb.process.run(['gzip', match_name], cwd=destdir)
1404 if oldpath.endswith('.gz'):
1405 match_name += '.gz'
1406 else:
1407 match_name += '.Z'
1408 elif oldpath.endswith('.bz2'):
1409 bb.process.run(['bzip2', match_name], cwd=destdir)
1410 match_name += '.bz2'
1411 updated[match_name] = {'path' : oldpath}
1412 if reldirpath != ".":
1413 updated[match_name]['patchdir'] = reldirpath
1414 else:
1415 added[new_patch] = {}
1416 if reldirpath != ".":
1417 added[new_patch]['patchdir'] = reldirpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001418
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001419 return (updated, added, existing_patches)
1420
1421
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001422def _create_kconfig_diff(srctree, rd, outfile):
1423 """Create a kconfig fragment"""
1424 # Only update config fragment if both config files exist
1425 orig_config = os.path.join(srctree, '.config.baseline')
1426 new_config = os.path.join(srctree, '.config.new')
1427 if os.path.exists(orig_config) and os.path.exists(new_config):
1428 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1429 '--unchanged-line-format=', orig_config, new_config]
1430 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1431 stderr=subprocess.PIPE)
1432 stdout, stderr = pipe.communicate()
1433 if pipe.returncode == 1:
1434 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001435 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001436 fobj.write(stdout)
1437 elif pipe.returncode == 0:
1438 logger.info("Would remove config fragment %s" % outfile)
1439 if os.path.exists(outfile):
1440 # Remove fragment file in case of empty diff
1441 logger.info("Removing config fragment %s" % outfile)
1442 os.unlink(outfile)
1443 else:
1444 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1445 return True
1446 return False
1447
1448
Brad Bishop316dfdd2018-06-25 12:45:53 -04001449def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001450 """Copy local files from srctree to given location.
1451 Returns three-tuple of dicts:
1452 1. updated - files that already exist in SRCURI
1453 2. added - new files files that don't exist in SRCURI
1454 3 removed - files that exist in SRCURI but not in exported files
1455 In each dict the key is the 'basepath' of the URI and value is the
1456 absolute path to the existing file in recipe space (if any).
1457 """
1458 import oe.recipeutils
1459
1460 # Find out local files (SRC_URI files that exist in the "recipe space").
1461 # Local files that reside in srctree are not included in patch generation.
1462 # Instead they are directly copied over the original source files (in
1463 # recipe space).
1464 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1465 new_set = None
1466 updated = OrderedDict()
1467 added = OrderedDict()
1468 removed = OrderedDict()
Andrew Geissler517393d2023-01-13 08:55:19 -06001469
1470 # Get current branch and return early with empty lists
1471 # if on one of the override branches
1472 # (local files are provided only for the main branch and processing
1473 # them against lists from recipe overrides will result in mismatches
1474 # and broken modifications to recipes).
1475 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1476 cwd=srctree)
1477 branchname = stdout.rstrip()
1478 if branchname.startswith(override_branch_prefix):
1479 return (updated, added, removed)
1480
Brad Bishop316dfdd2018-06-25 12:45:53 -04001481 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001482 git_files = _git_ls_tree(srctree)
1483 if 'oe-local-files' in git_files:
1484 # If tracked by Git, take the files from srctree HEAD. First get
1485 # the tree object of the directory
1486 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1487 tree = git_files['oe-local-files'][2]
1488 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1489 env=dict(os.environ, GIT_WORK_TREE=destdir,
1490 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001491 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001492 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001493 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001494 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001495 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001496 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001497 else:
1498 new_set = []
1499
1500 # Special handling for kernel config
1501 if bb.data.inherits_class('kernel-yocto', rd):
1502 fragment_fn = 'devtool-fragment.cfg'
1503 fragment_path = os.path.join(destdir, fragment_fn)
1504 if _create_kconfig_diff(srctree, rd, fragment_path):
1505 if os.path.exists(fragment_path):
1506 if fragment_fn not in new_set:
1507 new_set.append(fragment_fn)
1508 # Copy fragment to local-files
1509 if os.path.isdir(local_files_dir):
1510 shutil.copy2(fragment_path, local_files_dir)
1511 else:
1512 if fragment_fn in new_set:
1513 new_set.remove(fragment_fn)
1514 # Remove fragment from local-files
1515 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1516 os.unlink(os.path.join(local_files_dir, fragment_fn))
1517
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001518 # Special handling for cml1, ccmake, etc bbclasses that generated
1519 # configuration fragment files that are consumed as source files
1520 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1521 if bb.data.inherits_class(frag_class, rd):
1522 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1523 if os.path.exists(srcpath):
1524 if frag_name not in new_set:
1525 new_set.append(frag_name)
1526 # copy fragment into destdir
1527 shutil.copy2(srcpath, destdir)
1528 # copy fragment into local files if exists
1529 if os.path.isdir(local_files_dir):
1530 shutil.copy2(srcpath, local_files_dir)
1531
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001532 if new_set is not None:
1533 for fname in new_set:
1534 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001535 origpath = existing_files.pop(fname)
1536 workpath = os.path.join(local_files_dir, fname)
1537 if not filecmp.cmp(origpath, workpath):
1538 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001539 elif fname != '.gitignore':
1540 added[fname] = None
1541
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001542 workdir = rd.getVar('WORKDIR')
1543 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001544 if not s.endswith(os.sep):
1545 s += os.sep
1546
1547 if workdir != s:
1548 # Handle files where subdir= was specified
1549 for fname in list(existing_files.keys()):
1550 # FIXME handle both subdir starting with BP and not?
1551 fworkpath = os.path.join(workdir, fname)
1552 if fworkpath.startswith(s):
1553 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1554 if os.path.exists(fpath):
1555 origpath = existing_files.pop(fname)
1556 if not filecmp.cmp(origpath, fpath):
1557 updated[fpath] = origpath
1558
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001559 removed = existing_files
1560 return (updated, added, removed)
1561
1562
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001563def _determine_files_dir(rd):
1564 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001565 recipedir = rd.getVar('FILE_DIRNAME')
1566 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001567 relpth = os.path.relpath(entry, recipedir)
1568 if not os.sep in relpth:
1569 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1570 if os.path.isdir(entry):
1571 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001572 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001573
1574
Brad Bishop316dfdd2018-06-25 12:45:53 -04001575def _update_recipe_srcrev(recipename, workspace, srctree, rd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001576 """Implement the 'srcrev' mode of update-recipe"""
1577 import bb
1578 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001579
Brad Bishop316dfdd2018-06-25 12:45:53 -04001580 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1581
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001582 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001583 recipedir = os.path.basename(recipefile)
1584 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001585
Patrick Williamsda295312023-12-05 16:48:56 -06001586 # Get original SRCREV
1587 old_srcrev = rd.getVar('SRCREV') or ''
1588 if old_srcrev == "INVALID":
1589 raise DevtoolError('Update mode srcrev is only valid for recipe fetched from an SCM repository')
1590 old_srcrev = {'.': old_srcrev}
1591
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001592 # Get HEAD revision
1593 try:
1594 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1595 except bb.process.ExecutionError as err:
1596 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1597 (srctree, err))
1598 srcrev = stdout.strip()
1599 if len(srcrev) != 40:
1600 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1601
1602 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001603 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001604 patchfields = {}
1605 patchfields['SRCREV'] = srcrev
1606 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001607 srcuri = orig_src_uri.split()
1608 tempdir = tempfile.mkdtemp(prefix='devtool')
1609 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001610 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001611 try:
1612 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001613 srctreebase = workspace[recipename]['srctreebase']
1614 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001615 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001616 # Find list of existing patches in recipe file
1617 patches_dir = tempfile.mkdtemp(dir=tempdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001618 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1619 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001620 logger.debug('Patches: update %s, new %s, delete %s' % (dict(upd_p), dict(new_p), dict(del_p)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001621
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001622 # Remove deleted local files and "overlapping" patches
Patrick Williamsda295312023-12-05 16:48:56 -06001623 remove_files = list(del_f.values()) + [value["path"] for value in upd_p.values() if "path" in value] + [value["path"] for value in del_p.values() if "path" in value]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001624 if remove_files:
1625 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1626 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001627
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001628 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001629 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001630 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001631 removevalues = {}
1632 if update_srcuri:
1633 removevalues = {'SRC_URI': removedentries}
1634 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001635 if dry_run_outdir:
1636 logger.info('Creating bbappend (dry-run)')
Patrick Williamsda295312023-12-05 16:48:56 -06001637 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1638 rd, appendlayerdir, files, wildcardver=wildcard_version,
1639 extralines=patchfields, removevalues=removevalues,
1640 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001641 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001642 files_dir = _determine_files_dir(rd)
1643 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001644 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001645 if os.path.isabs(basepath):
1646 # Original file (probably with subdir pointing inside source tree)
1647 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001648 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001649 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001650 _move_file(os.path.join(local_files_dir, basepath), path,
1651 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001652 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001653 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001654 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001655 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001656 os.path.join(files_dir, basepath),
1657 dry_run_outdir=dry_run_outdir,
1658 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001659 srcuri.append('file://%s' % basepath)
1660 update_srcuri = True
1661 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001662 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001663 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001664 finally:
1665 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001666 if not 'git://' in orig_src_uri:
1667 logger.info('You will need to update SRC_URI within the recipe to '
1668 'point to a git repository where you have pushed your '
1669 'changes')
1670
Brad Bishop316dfdd2018-06-25 12:45:53 -04001671 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1672 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001673
Brad Bishop316dfdd2018-06-25 12:45:53 -04001674def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wildcard_version, no_remove, no_report_remove, initial_rev, dry_run_outdir=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001675 """Implement the 'patch' mode of update-recipe"""
1676 import bb
1677 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001678
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001679 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001680 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001681 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001682 if not os.path.exists(append):
1683 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001684 recipename)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001685 srctreebase = workspace[recipename]['srctreebase']
1686 relpatchdir = os.path.relpath(srctreebase, srctree)
1687 if relpatchdir == '.':
1688 patchdir_params = {}
1689 else:
1690 patchdir_params = {'patchdir': relpatchdir}
1691
Patrick Williamsda295312023-12-05 16:48:56 -06001692 def srcuri_entry(basepath, patchdir_params):
Andrew Geissler615f2f12022-07-15 14:00:58 -05001693 if patchdir_params:
1694 paramstr = ';' + ';'.join('%s=%s' % (k,v) for k,v in patchdir_params.items())
1695 else:
1696 paramstr = ''
1697 return 'file://%s%s' % (basepath, paramstr)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001698
Patrick Williamsda295312023-12-05 16:48:56 -06001699 initial_revs, update_revs, changed_revs, filter_patches = _get_patchset_revs(srctree, append, initial_rev, force_patch_refresh)
1700 if not initial_revs:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001701 raise DevtoolError('Unable to find initial revision - please specify '
1702 'it with --initial-rev')
1703
Brad Bishop316dfdd2018-06-25 12:45:53 -04001704 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001705 dl_dir = rd.getVar('DL_DIR')
1706 if not dl_dir.endswith('/'):
1707 dl_dir += '/'
1708
Brad Bishop316dfdd2018-06-25 12:45:53 -04001709 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1710
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001711 tempdir = tempfile.mkdtemp(prefix='devtool')
1712 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001713 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Andrew Geissler517393d2023-01-13 08:55:19 -06001714 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001715
1716 # Get updated patches from source tree
1717 patches_dir = tempfile.mkdtemp(dir=tempdir)
Patrick Williamsda295312023-12-05 16:48:56 -06001718 upd_p, new_p, _ = _export_patches(srctree, rd, update_revs,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001719 patches_dir, changed_revs)
Andrew Geissler517393d2023-01-13 08:55:19 -06001720 # Get all patches from source tree and check if any should be removed
1721 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Patrick Williamsda295312023-12-05 16:48:56 -06001722 _, _, del_p = _export_patches(srctree, rd, initial_revs,
Andrew Geissler517393d2023-01-13 08:55:19 -06001723 all_patches_dir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001724 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1725 if filter_patches:
Brad Bishop00e122a2019-10-05 11:10:57 -04001726 new_p = OrderedDict()
1727 upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches)
Andrew Geissler517393d2023-01-13 08:55:19 -06001728 del_p = OrderedDict((k,v) for k,v in del_p.items() if k in filter_patches)
1729 remove_files = []
1730 if not no_remove:
1731 # Remove deleted local files and patches
1732 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001733 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001734 updaterecipe = False
1735 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001736 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsda295312023-12-05 16:48:56 -06001737
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001738 if appendlayerdir:
Brad Bishop00e122a2019-10-05 11:10:57 -04001739 files = OrderedDict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001740 key, val in list(upd_f.items()) + list(new_f.items()))
Brad Bishop00e122a2019-10-05 11:10:57 -04001741 files.update(OrderedDict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001742 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsda295312023-12-05 16:48:56 -06001743
1744 params = []
1745 for file, param in files.items():
1746 patchdir_param = dict(patchdir_params)
1747 patchdir = param.get('patchdir', ".")
1748 if patchdir != "." :
1749 if patchdir_param:
1750 patchdir_param['patchdir'] += patchdir
1751 else:
1752 patchdir_param['patchdir'] = patchdir
1753 params.append(patchdir_param)
1754
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001755 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001756 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001757 if remove_files:
1758 removedentries, remaining = _remove_file_entries(
1759 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001760 if removedentries or remaining:
Patrick Williamsda295312023-12-05 16:48:56 -06001761 remaining = [srcuri_entry(os.path.basename(item), patchdir_params) for
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001762 item in remaining]
1763 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001764 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001765 rd, appendlayerdir, files,
1766 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001767 removevalues=removevalues,
Andrew Geissler615f2f12022-07-15 14:00:58 -05001768 redirect_output=dry_run_outdir,
Patrick Williamsda295312023-12-05 16:48:56 -06001769 params=params)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001770 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001771 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001772 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001773 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001774 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001775 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001776 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001777 if os.path.isabs(basepath):
1778 # Original file (probably with subdir pointing inside source tree)
1779 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001780 _copy_file(basepath, path,
1781 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001782 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001783 _move_file(os.path.join(local_files_dir, basepath), path,
1784 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001785 updatefiles = True
Patrick Williamsda295312023-12-05 16:48:56 -06001786 for basepath, param in upd_p.items():
1787 path = param['path']
1788 patchdir = param.get('patchdir', ".")
1789 if patchdir != "." :
1790 patchdir_param = dict(patchdir_params)
1791 if patchdir_param:
1792 patchdir_param['patchdir'] += patchdir
1793 else:
1794 patchdir_param['patchdir'] = patchdir
1795 patchfn = os.path.join(patches_dir, patchdir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001796 if os.path.dirname(path) + '/' == dl_dir:
1797 # This is a a downloaded patch file - we now need to
1798 # replace the entry in SRC_URI with our local version
1799 logger.info('Replacing remote patch %s with updated local version' % basepath)
1800 path = os.path.join(files_dir, basepath)
Patrick Williamsda295312023-12-05 16:48:56 -06001801 _replace_srcuri_entry(srcuri, basepath, srcuri_entry(basepath, patchdir_param))
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001802 updaterecipe = True
1803 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001804 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1805 _move_file(patchfn, path,
1806 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001807 updatefiles = True
1808 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001809 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001810 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001811 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001812 os.path.join(files_dir, basepath),
1813 dry_run_outdir=dry_run_outdir,
1814 base_outdir=recipedir)
Patrick Williamsda295312023-12-05 16:48:56 -06001815 srcuri.append(srcuri_entry(basepath, patchdir_params))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001816 updaterecipe = True
Patrick Williamsda295312023-12-05 16:48:56 -06001817 for basepath, param in new_p.items():
1818 patchdir = param.get('patchdir', ".")
Brad Bishop316dfdd2018-06-25 12:45:53 -04001819 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsda295312023-12-05 16:48:56 -06001820 _move_file(os.path.join(patches_dir, patchdir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001821 os.path.join(files_dir, basepath),
1822 dry_run_outdir=dry_run_outdir,
1823 base_outdir=recipedir)
Patrick Williamsda295312023-12-05 16:48:56 -06001824 params = dict(patchdir_params)
1825 if patchdir != "." :
1826 if params:
1827 params['patchdir'] += patchdir
1828 else:
1829 params['patchdir'] = patchdir
1830
1831 srcuri.append(srcuri_entry(basepath, params))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001832 updaterecipe = True
1833 # Update recipe, if needed
1834 if _remove_file_entries(srcuri, remove_files)[0]:
1835 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001836 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001837 if not dry_run_outdir:
1838 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1839 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1840 {'SRC_URI': ' '.join(srcuri)},
1841 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001842 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001843 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001844 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001845 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001846 finally:
1847 shutil.rmtree(tempdir)
1848
Brad Bishop316dfdd2018-06-25 12:45:53 -04001849 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1850 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001851
1852def _guess_recipe_update_mode(srctree, rdata):
1853 """Guess the recipe update mode to use"""
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001854 src_uri = (rdata.getVar('SRC_URI') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001855 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1856 if not git_uris:
1857 return 'patch'
1858 # Just use the first URI for now
1859 uri = git_uris[0]
1860 # Check remote branch
1861 params = bb.fetch.decodeurl(uri)[5]
1862 upstr_branch = params['branch'] if 'branch' in params else 'master'
1863 # Check if current branch HEAD is found in upstream branch
1864 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1865 head_rev = stdout.rstrip()
1866 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1867 cwd=srctree)
1868 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1869 if 'origin/' + upstr_branch in remote_brs:
1870 return 'srcrev'
1871
1872 return 'patch'
1873
Brad Bishop316dfdd2018-06-25 12:45:53 -04001874def _update_recipe(recipename, workspace, rd, mode, appendlayerdir, wildcard_version, no_remove, initial_rev, no_report_remove=False, dry_run_outdir=None, no_overrides=False, force_patch_refresh=False):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001875 srctree = workspace[recipename]['srctree']
1876 if mode == 'auto':
1877 mode = _guess_recipe_update_mode(srctree, rd)
1878
Brad Bishop316dfdd2018-06-25 12:45:53 -04001879 override_branches = []
1880 mainbranch = None
1881 startbranch = None
1882 if not no_overrides:
1883 stdout, _ = bb.process.run('git branch', cwd=srctree)
1884 other_branches = []
1885 for line in stdout.splitlines():
1886 branchname = line[2:]
1887 if line.startswith('* '):
Patrick Williams44b3caf2024-04-12 16:51:14 -05001888 if 'HEAD' in line:
1889 raise DevtoolError('Detached HEAD - please check out a branch, e.g., "devtool"')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001890 startbranch = branchname
1891 if branchname.startswith(override_branch_prefix):
1892 override_branches.append(branchname)
1893 else:
1894 other_branches.append(branchname)
1895
1896 if override_branches:
1897 logger.debug('_update_recipe: override branches: %s' % override_branches)
1898 logger.debug('_update_recipe: other branches: %s' % other_branches)
1899 if startbranch.startswith(override_branch_prefix):
1900 if len(other_branches) == 1:
1901 mainbranch = other_branches[1]
1902 else:
1903 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1904 else:
1905 mainbranch = startbranch
1906
1907 checkedout = None
1908 anyupdated = False
1909 appendfile = None
1910 allremoved = []
1911 if override_branches:
1912 logger.info('Handling main branch (%s)...' % mainbranch)
1913 if startbranch != mainbranch:
1914 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1915 checkedout = mainbranch
1916 try:
1917 branchlist = [mainbranch] + override_branches
1918 for branch in branchlist:
1919 crd = bb.data.createCopy(rd)
1920 if branch != mainbranch:
1921 logger.info('Handling branch %s...' % branch)
1922 override = branch[len(override_branch_prefix):]
1923 crd.appendVar('OVERRIDES', ':%s' % override)
1924 bb.process.run('git checkout %s' % branch, cwd=srctree)
1925 checkedout = branch
1926
1927 if mode == 'srcrev':
1928 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1929 elif mode == 'patch':
1930 updated, appendf, removed = _update_recipe_patch(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, initial_rev, dry_run_outdir, force_patch_refresh)
1931 else:
1932 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1933 if updated:
1934 anyupdated = True
1935 if appendf:
1936 appendfile = appendf
1937 allremoved.extend(removed)
1938 finally:
1939 if startbranch and checkedout != startbranch:
1940 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1941
1942 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001943
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001944def update_recipe(args, config, basepath, workspace):
1945 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001946 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001947
1948 if args.append:
1949 if not os.path.exists(args.append):
1950 raise DevtoolError('bbappend destination layer directory "%s" '
1951 'does not exist' % args.append)
1952 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1953 raise DevtoolError('conf/layer.conf not found in bbappend '
1954 'destination layer "%s"' % args.append)
1955
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001956 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001957 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001958
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001959 rd = parse_recipe(config, tinfoil, args.recipename, True)
1960 if not rd:
1961 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001962
Brad Bishop316dfdd2018-06-25 12:45:53 -04001963 dry_run_output = None
1964 dry_run_outdir = None
1965 if args.dry_run:
1966 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1967 dry_run_outdir = dry_run_output.name
1968 updated, _, _ = _update_recipe(args.recipename, workspace, rd, args.mode, args.append, args.wildcard_version, args.no_remove, args.initial_rev, dry_run_outdir=dry_run_outdir, no_overrides=args.no_overrides, force_patch_refresh=args.force_patch_refresh)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001969
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001970 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001971 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001972 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001973 logger.warning('Recipe file %s has been updated but is inside the workspace - you will need to move it (and any associated files next to it) out to the desired layer before using "devtool reset" in order to keep any changes' % rf)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001974 finally:
1975 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001976
1977 return 0
1978
1979
1980def status(args, config, basepath, workspace):
1981 """Entry point for the devtool 'status' subcommand"""
1982 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001983 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001984 recipefile = value['recipefile']
1985 if recipefile:
1986 recipestr = ' (%s)' % recipefile
1987 else:
1988 recipestr = ''
1989 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001990 else:
1991 logger.info('No recipes currently in your workspace - you can use "devtool modify" to work on an existing recipe or "devtool add" to add a new one')
1992 return 0
1993
1994
Brad Bishop64c979e2019-11-04 13:55:29 -05001995def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001996 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001997 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001998
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001999 def clean_preferred_provider(pn, layerconf_path):
2000 """Remove PREFERRED_PROVIDER from layer.conf'"""
2001 import re
2002 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
2003 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
2004 pprovider_found = False
2005 with open(layerconf_file, 'r') as f:
2006 lines = f.readlines()
2007 with open(new_layerconf_file, 'a') as nf:
2008 for line in lines:
2009 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
2010 if not re.match(pprovider_exp, line):
2011 nf.write(line)
2012 else:
2013 pprovider_found = True
2014 if pprovider_found:
2015 shutil.move(new_layerconf_file, layerconf_file)
2016 else:
2017 os.remove(new_layerconf_file)
2018
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002019 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002020 if len(recipes) == 1:
2021 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
2022 else:
2023 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002024 # If the recipe file itself was created in the workspace, and
2025 # it uses BBCLASSEXTEND, then we need to also clean the other
2026 # variants
2027 targets = []
2028 for recipe in recipes:
2029 targets.append(recipe)
2030 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002031 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002032 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002033 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002034 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002035 except bb.process.ExecutionError as e:
2036 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
2037 'wish, you may specify -n/--no-clean to '
2038 'skip running this command when resetting' %
2039 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002040
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002041 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002042 _check_preserve(config, pn)
2043
Brad Bishop316dfdd2018-06-25 12:45:53 -04002044 appendfile = workspace[pn]['bbappend']
2045 if os.path.exists(appendfile):
2046 # This shouldn't happen, but is possible if devtool errored out prior to
2047 # writing the md5 file. We need to delete this here or the recipe won't
2048 # actually be reset
2049 os.remove(appendfile)
2050
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002051 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002052 def preservedir(origdir):
2053 if os.path.exists(origdir):
2054 for root, dirs, files in os.walk(origdir):
2055 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08002056 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002057 _move_file(os.path.join(origdir, fn),
2058 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002059 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002060 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002061 os.rmdir(origdir)
2062
Brad Bishop316dfdd2018-06-25 12:45:53 -04002063 recipefile = workspace[pn]['recipefile']
2064 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
2065 # This should always be true if recipefile is set, but just in case
2066 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002067 # We don't automatically create this dir next to appends, but the user can
2068 preservedir(os.path.join(config.workspace_path, 'appends', pn))
2069
Brad Bishop316dfdd2018-06-25 12:45:53 -04002070 srctreebase = workspace[pn]['srctreebase']
2071 if os.path.isdir(srctreebase):
2072 if os.listdir(srctreebase):
Brad Bishop64c979e2019-11-04 13:55:29 -05002073 if remove_work:
2074 logger.info('-r argument used on %s, removing source tree.'
2075 ' You will lose any unsaved work' %pn)
2076 shutil.rmtree(srctreebase)
2077 else:
2078 # We don't want to risk wiping out any work in progress
Patrick Williams92b42cb2022-09-03 06:53:57 -05002079 if srctreebase.startswith(os.path.join(config.workspace_path, 'sources')):
2080 from datetime import datetime
Patrick Williams73bd93f2024-02-20 08:07:48 -06002081 preservesrc = os.path.join(config.workspace_path, 'attic', 'sources', "{}.{}".format(pn, datetime.now().strftime("%Y%m%d%H%M%S")))
Patrick Williams92b42cb2022-09-03 06:53:57 -05002082 logger.info('Preserving source tree in %s\nIf you no '
2083 'longer need it then please delete it manually.\n'
2084 'It is also possible to reuse it via devtool source tree argument.'
2085 % preservesrc)
2086 bb.utils.mkdirhier(os.path.dirname(preservesrc))
2087 shutil.move(srctreebase, preservesrc)
2088 else:
2089 logger.info('Leaving source tree %s as-is; if you no '
2090 'longer need it then please delete it manually'
2091 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002092 else:
2093 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04002094 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002095
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002096 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002097
2098def reset(args, config, basepath, workspace):
2099 """Entry point for the devtool 'reset' subcommand"""
2100 import bb
Brad Bishop64c979e2019-11-04 13:55:29 -05002101 import shutil
2102
2103 recipes = ""
2104
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002105 if args.recipename:
2106 if args.all:
2107 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
2108 else:
2109 for recipe in args.recipename:
2110 check_workspace_recipe(workspace, recipe, checksrc=False)
2111 elif not args.all:
2112 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
2113 "reset all recipes")
2114 if args.all:
2115 recipes = list(workspace.keys())
2116 else:
2117 recipes = args.recipename
2118
Brad Bishop64c979e2019-11-04 13:55:29 -05002119 _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002120
2121 return 0
2122
2123
2124def _get_layer(layername, d):
2125 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002126 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04002127 layers = {} # {basename: layer_paths}
2128 for p in layerdirs:
2129 bn = os.path.basename(p)
2130 if bn not in layers:
2131 layers[bn] = [p]
2132 else:
2133 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002134 # Provide some shortcuts
2135 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04002136 layername = 'meta'
2137 layer_paths = layers.get(layername, None)
2138 if not layer_paths:
2139 return os.path.abspath(layername)
2140 elif len(layer_paths) == 1:
2141 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002142 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04002143 # multiple layers having the same base name
2144 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
2145 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
2146 return os.path.abspath(layer_paths[0])
2147
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002148
2149def finish(args, config, basepath, workspace):
2150 """Entry point for the devtool 'finish' subcommand"""
2151 import bb
2152 import oe.recipeutils
2153
2154 check_workspace_recipe(workspace, args.recipename)
2155
Brad Bishop316dfdd2018-06-25 12:45:53 -04002156 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
2157
2158 # Grab the equivalent of COREBASE without having to initialise tinfoil
2159 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2160
2161 srctree = workspace[args.recipename]['srctree']
2162 check_git_repo_op(srctree, [corebasedir])
2163 dirty = check_git_repo_dirty(srctree)
2164 if dirty:
2165 if args.force:
2166 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2167 else:
2168 raise DevtoolError('Source tree is not clean:\n\n%s\nEnsure you have committed your changes or use -f/--force if you are sure there\'s nothing that needs to be committed' % dirty)
2169
Brad Bishop00e122a2019-10-05 11:10:57 -04002170 no_clean = args.no_clean
Brad Bishop64c979e2019-11-04 13:55:29 -05002171 remove_work=args.remove_work
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002172 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2173 try:
Brad Bishop6dbb3162019-11-25 09:41:34 -05002174 rd = parse_recipe(config, tinfoil, args.recipename, True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002175 if not rd:
2176 return 1
2177
2178 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002179 recipefile = rd.getVar('FILE')
2180 recipedir = os.path.dirname(recipefile)
2181 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002182
2183 if not os.path.isdir(destlayerdir):
2184 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2185
2186 if os.path.abspath(destlayerdir) == config.workspace_path:
2187 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2188
2189 # If it's an upgrade, grab the original path
2190 origpath = None
2191 origfilelist = None
2192 append = workspace[args.recipename]['bbappend']
2193 with open(append, 'r') as f:
2194 for line in f:
2195 if line.startswith('# original_path:'):
2196 origpath = line.split(':')[1].strip()
2197 elif line.startswith('# original_files:'):
2198 origfilelist = line.split(':')[1].split()
2199
Brad Bishop316dfdd2018-06-25 12:45:53 -04002200 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2201
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002202 if origlayerdir == config.workspace_path:
2203 # Recipe file itself is in workspace, update it there first
2204 appendlayerdir = None
2205 origrelpath = None
2206 if origpath:
2207 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2208 if origlayerpath:
2209 origrelpath = os.path.relpath(origpath, origlayerpath)
2210 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2211 if not destpath:
2212 raise DevtoolError("Unable to determine destination layer path - check that %s specifies an actual layer and %s/conf/layer.conf specifies BBFILES. You may also need to specify a more complete path." % (args.destination, destlayerdir))
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002213 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2214 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002215 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002216 bb.warn('Specified destination layer is not currently enabled in bblayers.conf, so the %s recipe will now be unavailable in your current configuration until you add the layer there' % args.recipename)
2217
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002218 elif destlayerdir == origlayerdir:
2219 # Same layer, update the original recipe
2220 appendlayerdir = None
2221 destpath = None
2222 else:
2223 # Create/update a bbappend in the specified layer
2224 appendlayerdir = destlayerdir
2225 destpath = None
2226
Brad Bishop316dfdd2018-06-25 12:45:53 -04002227 # Actually update the recipe / bbappend
2228 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2229 dry_run_output = None
2230 dry_run_outdir = None
2231 if args.dry_run:
2232 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2233 dry_run_outdir = dry_run_output.name
2234 updated, appendfile, removed = _update_recipe(args.recipename, workspace, rd, args.mode, appendlayerdir, wildcard_version=True, no_remove=False, no_report_remove=removing_original, initial_rev=args.initial_rev, dry_run_outdir=dry_run_outdir, no_overrides=args.no_overrides, force_patch_refresh=args.force_patch_refresh)
2235 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2236
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002237 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002238 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002239 for fn in origfilelist:
2240 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002241 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2242 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2243 if not args.dry_run:
2244 try:
2245 os.remove(fnp)
2246 except FileNotFoundError:
2247 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002248
2249 if origlayerdir == config.workspace_path and destpath:
2250 # Recipe file itself is in the workspace - need to move it and any
2251 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002252 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002253 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002254 for root, _, files in os.walk(recipedir):
2255 for fn in files:
2256 srcpath = os.path.join(root, fn)
2257 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2258 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002259 destfp = os.path.join(destdir, fn)
2260 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002261
Brad Bishop316dfdd2018-06-25 12:45:53 -04002262 if dry_run_outdir:
2263 import difflib
2264 comparelist = []
2265 for root, _, files in os.walk(dry_run_outdir):
2266 for fn in files:
2267 outf = os.path.join(root, fn)
2268 relf = os.path.relpath(outf, dry_run_outdir)
2269 logger.debug('dry-run: output file %s' % relf)
2270 if fn.endswith('.bb'):
2271 if origfilelist and origpath and destpath:
2272 # Need to match this up with the pre-upgrade recipe file
2273 for origf in origfilelist:
2274 if origf.endswith('.bb'):
2275 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2276 outf,
2277 os.path.abspath(os.path.join(destpath, relf))))
2278 break
2279 else:
2280 # Compare to the existing recipe
2281 comparelist.append((recipefile, outf, recipefile))
2282 elif fn.endswith('.bbappend'):
2283 if appendfile:
2284 if os.path.exists(appendfile):
2285 comparelist.append((appendfile, outf, appendfile))
2286 else:
2287 comparelist.append((None, outf, appendfile))
2288 else:
2289 if destpath:
2290 recipedest = destpath
2291 elif appendfile:
2292 recipedest = os.path.dirname(appendfile)
2293 else:
2294 recipedest = os.path.dirname(recipefile)
2295 destfp = os.path.join(recipedest, relf)
2296 if os.path.exists(destfp):
2297 comparelist.append((destfp, outf, destfp))
2298 output = ''
2299 for oldfile, newfile, newfileshow in comparelist:
2300 if oldfile:
2301 with open(oldfile, 'r') as f:
2302 oldlines = f.readlines()
2303 else:
2304 oldfile = '/dev/null'
2305 oldlines = []
2306 with open(newfile, 'r') as f:
2307 newlines = f.readlines()
2308 if not newfileshow:
2309 newfileshow = newfile
2310 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2311 difflines = list(diff)
2312 if difflines:
2313 output += ''.join(difflines)
2314 if output:
2315 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002316 finally:
2317 tinfoil.shutdown()
2318
2319 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002320 if args.dry_run:
2321 logger.info('Resetting recipe (dry-run)')
2322 else:
Brad Bishop64c979e2019-11-04 13:55:29 -05002323 _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002324
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002325 return 0
2326
2327
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002328def get_default_srctree(config, recipename=''):
2329 """Get the default srctree path"""
2330 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2331 if recipename:
2332 return os.path.join(srctreeparent, 'sources', recipename)
2333 else:
2334 return os.path.join(srctreeparent, 'sources')
2335
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002336def register_commands(subparsers, context):
2337 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002338
2339 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002340 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002341 description='Adds a new recipe to the workspace to build a specified source tree. Can optionally fetch a remote URI and unpack it to create the source tree.',
2342 group='starting', order=100)
2343 parser_add.add_argument('recipename', nargs='?', help='Name for new recipe to add (just name - no version, path or extension). If not specified, will attempt to auto-detect it.')
2344 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2345 parser_add.add_argument('fetchuri', nargs='?', help='Fetch the specified URI and extract it to create the source tree')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002346 group = parser_add.add_mutually_exclusive_group()
2347 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2348 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002349 parser_add.add_argument('--fetch', '-f', help='Fetch the specified URI and extract it to create the source tree (deprecated - pass as positional argument instead)', metavar='URI')
Andrew Geissler82c905d2020-04-13 13:39:40 -05002350 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williams169d7bc2024-01-05 11:33:25 -06002351 parser_add.add_argument('--no-pypi', help='Do not inherit pypi class', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002352 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002353 parser_add.add_argument('--no-git', '-g', help='If fetching source, do not set up source tree as a git repository', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002354 group = parser_add.add_mutually_exclusive_group()
2355 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2356 group.add_argument('--autorev', '-a', help='When fetching from a git repository, set SRCREV in the recipe to a floating revision instead of fixed', action="store_true")
2357 parser_add.add_argument('--srcbranch', '-B', help='Branch in source repository if fetching from an SCM such as git (default master)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002358 parser_add.add_argument('--binary', '-b', help='Treat the source tree as something that should be installed verbatim (no compilation, same directory structure). Useful with binary packages e.g. RPMs.', action='store_true')
2359 parser_add.add_argument('--also-native', help='Also add native variant (i.e. support building recipe for the build host as well as the target machine)', action='store_true')
2360 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002361 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2362 parser_add.add_argument('--provides', '-p', help='Specify an alias for the item provided by the recipe. E.g. virtual/libgl')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002363 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002364
2365 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002366 description='Sets up the build environment to modify the source for an existing recipe. The default behaviour is to extract the source being fetched by the recipe into a git tree so you can work on it; alternatively if you already have your own pre-prepared source tree you can specify -n/--no-extract.',
2367 group='starting', order=90)
2368 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2369 parser_modify.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002370 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002371 group = parser_modify.add_mutually_exclusive_group()
2372 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2373 group.add_argument('--no-extract', '-n', action="store_true", help='Do not extract source, expect it to exist')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002374 group = parser_modify.add_mutually_exclusive_group()
2375 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2376 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002377 parser_modify.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (when not using -n/--no-extract) (default "%(default)s")')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002378 parser_modify.add_argument('--no-overrides', '-O', action="store_true", help='Do not create branches for other override configurations')
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002379 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002380 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002381
2382 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2383 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002384 group='advanced')
2385 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002386 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002387 parser_extract.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (default "%(default)s")')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002388 parser_extract.add_argument('--no-overrides', '-O', action="store_true", help='Do not create branches for other override configurations')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002389 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002390 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002391
2392 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2393 description='Synchronize the previously extracted source tree for an existing recipe',
2394 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2395 group='advanced')
2396 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2397 parser_sync.add_argument('srctree', help='Path to the source tree')
2398 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2399 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002400 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002401
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002402 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2403 description='Renames the recipe file for a recipe in the workspace, changing the name or version part or both, ensuring that all references within the workspace are updated at the same time. Only works when the recipe file itself is in the workspace, e.g. after devtool add. Particularly useful when devtool add did not automatically determine the correct name.',
2404 group='working', order=10)
2405 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2406 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2407 parser_rename.add_argument('--version', '-V', help='Change the version (NOTE: this does not change the version fetched by the recipe, just the version in the recipe file name)')
2408 parser_rename.add_argument('--no-srctree', '-s', action='store_true', help='Do not rename the source tree directory (if the default source tree path has been used) - keeping the old name may be desirable if there are internal/other external references to this path')
2409 parser_rename.set_defaults(func=rename)
2410
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002411 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002412 description='Applies changes from external source tree to a recipe (updating/adding/removing patches as necessary, or by updating SRCREV). Note that these changes need to have been committed to the git repository in order to be recognised.',
2413 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002414 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2415 parser_update_recipe.add_argument('--mode', '-m', choices=['patch', 'srcrev', 'auto'], default='auto', help='Update mode (where %(metavar)s is %(choices)s; default is %(default)s)', metavar='MODE')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002416 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002417 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2418 parser_update_recipe.add_argument('--wildcard-version', '-w', help='In conjunction with -a/--append, use a wildcard to make the bbappend apply to any recipe version', action='store_true')
2419 parser_update_recipe.add_argument('--no-remove', '-n', action="store_true", help='Don\'t remove patches, only add or update')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002420 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2421 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2422 parser_update_recipe.add_argument('--force-patch-refresh', action="store_true", help='Update patches in the layer even if they have not been modified (useful for refreshing patch context)')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002423 parser_update_recipe.set_defaults(func=update_recipe)
2424
2425 parser_status = subparsers.add_parser('status', help='Show workspace status',
2426 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002427 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002428 parser_status.set_defaults(func=status)
2429
2430 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002431 description='Removes the specified recipe(s) from your workspace (resetting its state back to that defined by the metadata).',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002432 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002433 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002434 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2435 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
Brad Bishop64c979e2019-11-04 13:55:29 -05002436 parser_reset.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory along with append')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002437 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002438
2439 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002440 description='Pushes any committed changes to the specified recipe to the specified layer and removes it from your workspace. Roughly equivalent to an update-recipe followed by reset, except the update-recipe step will do the "right thing" depending on the recipe and the destination layer specified. Note that your changes must have been committed to the git repository in order to be recognised.',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002441 group='working', order=-100)
2442 parser_finish.add_argument('recipename', help='Recipe to finish')
2443 parser_finish.add_argument('destination', help='Layer/path to put recipe into. Can be the name of a layer configured in your bblayers.conf, the path to the base of a layer, or a partial path inside a layer. %(prog)s will attempt to complete the path based on the layer\'s structure.')
2444 parser_finish.add_argument('--mode', '-m', choices=['patch', 'srcrev', 'auto'], default='auto', help='Update mode (where %(metavar)s is %(choices)s; default is %(default)s)', metavar='MODE')
2445 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002446 parser_finish.add_argument('--force', '-f', action="store_true", help='Force continuing even if there are uncommitted changes in the source tree repository')
Brad Bishop64c979e2019-11-04 13:55:29 -05002447 parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace')
Brad Bishop00e122a2019-10-05 11:10:57 -04002448 parser_finish.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002449 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2450 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2451 parser_finish.add_argument('--force-patch-refresh', action="store_true", help='Update patches in the layer even if they have not been modified (useful for refreshing patch context)')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002452 parser_finish.set_defaults(func=finish)