blob: 7b62b7e7b8835574cee4872d7d283ef47093bc86 [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'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500150 if args.mirrors:
151 extracmdopts += ' --mirrors'
152 if args.srcrev:
153 extracmdopts += ' --srcrev %s' % args.srcrev
154 if args.srcbranch:
155 extracmdopts += ' --srcbranch %s' % args.srcbranch
156 if args.provides:
157 extracmdopts += ' --provides %s' % args.provides
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500158
159 tempdir = tempfile.mkdtemp(prefix='devtool')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500161 try:
162 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create --devtool -o %s \'%s\' %s' % (color, tempdir, source, extracmdopts), watch=True)
163 except bb.process.ExecutionError as e:
164 if e.exitcode == 15:
165 raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line')
166 else:
167 raise DevtoolError('Command \'%s\' failed' % e.command)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500168
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500169 recipes = glob.glob(os.path.join(tempdir, '*.bb'))
170 if recipes:
171 recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0]
172 if recipename in workspace:
173 raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename)
174 recipedir = os.path.join(config.workspace_path, 'recipes', recipename)
175 bb.utils.mkdirhier(recipedir)
176 recipefile = os.path.join(recipedir, os.path.basename(recipes[0]))
177 appendfile = recipe_to_append(recipefile, config)
178 if os.path.exists(appendfile):
179 # This shouldn't be possible, but just in case
180 raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace')
181 if os.path.exists(recipefile):
182 raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile)
183 if tmpsrcdir:
184 srctree = os.path.join(srctreeparent, recipename)
185 if os.path.exists(tmpsrcdir):
186 if os.path.exists(srctree):
187 if os.path.isdir(srctree):
188 try:
189 os.rmdir(srctree)
190 except OSError as e:
191 if e.errno == errno.ENOTEMPTY:
192 raise DevtoolError('Source tree path %s already exists and is not empty' % srctree)
193 else:
194 raise
195 else:
196 raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree)
197 logger.info('Using default source tree path %s' % srctree)
198 shutil.move(tmpsrcdir, srctree)
199 else:
200 raise DevtoolError('Couldn\'t find source tree created by recipetool')
201 bb.utils.mkdirhier(recipedir)
202 shutil.move(recipes[0], recipefile)
203 # Move any additional files created by recipetool
204 for fn in os.listdir(tempdir):
205 shutil.move(os.path.join(tempdir, fn), recipedir)
206 else:
207 raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout))
208 attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile))
209 if os.path.exists(attic_recipe):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800210 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 -0500211 finally:
212 if tmpsrcdir and os.path.exists(tmpsrcdir):
213 shutil.rmtree(tmpsrcdir)
214 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500216 for fn in os.listdir(recipedir):
217 _add_md5(config, recipename, os.path.join(recipedir, fn))
218
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500219 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600220 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500221 try:
222 rd = tinfoil.parse_recipe_file(recipefile, False)
223 except Exception as e:
224 logger.error(str(e))
225 rd = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600226 if not rd:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500227 # Parsing failed. We just created this recipe and we shouldn't
228 # leave it in the workdir or it'll prevent bitbake from starting
229 movefn = '%s.parsefailed' % recipefile
230 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)
231 shutil.move(recipefile, movefn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600232 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500233
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600234 if args.fetchuri and not args.no_git:
235 setup_git_repo(srctree, args.version, 'devtool', d=tinfoil.config_data)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500236
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600237 initial_rev = None
238 if os.path.exists(os.path.join(srctree, '.git')):
239 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
240 initial_rev = stdout.rstrip()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500241
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600242 if args.src_subdir:
243 srctree = os.path.join(srctree, args.src_subdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500244
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600245 bb.utils.mkdirhier(os.path.dirname(appendfile))
246 with open(appendfile, 'w') as f:
247 f.write('inherit externalsrc\n')
248 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500249
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600250 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
251 if b_is_s:
252 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
253 if initial_rev:
254 f.write('\n# initial_rev: %s\n' % initial_rev)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500255
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600256 if args.binary:
257 f.write('do_install_append() {\n')
258 f.write(' rm -rf ${D}/.git\n')
259 f.write(' rm -f ${D}/singletask.lock\n')
260 f.write('}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500261
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600262 if bb.data.inherits_class('npm', rd):
Andrew Geissler82c905d2020-04-13 13:39:40 -0500263 f.write('python do_configure_append() {\n')
264 f.write(' pkgdir = d.getVar("NPM_PACKAGE")\n')
265 f.write(' lockfile = os.path.join(pkgdir, "singletask.lock")\n')
266 f.write(' bb.utils.remove(lockfile)\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600267 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500268
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500269 # Check if the new layer provides recipes whose priorities have been
270 # overriden by PREFERRED_PROVIDER.
271 recipe_name = rd.getVar('PN')
272 provides = rd.getVar('PROVIDES')
273 # Search every item defined in PROVIDES
274 for recipe_provided in provides.split():
275 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
276 current_pprovider = rd.getVar(preferred_provider)
277 if current_pprovider and current_pprovider != recipe_name:
278 if args.fixed_setup:
279 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
280 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
281 with open(layerconf_file, 'a') as f:
282 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
283 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800284 logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500285 break
286
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600287 _add_md5(config, recipename, appendfile)
288
Brad Bishop316dfdd2018-06-25 12:45:53 -0400289 check_prerelease_version(rd.getVar('PV'), 'devtool add')
290
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
292
293 finally:
294 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500295
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500296 return 0
297
298
299def _check_compatible_recipe(pn, d):
300 """Check if the recipe is supported by devtool"""
301 if pn == 'perf':
302 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600303 "source and thus cannot be supported by this tool",
304 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500305
306 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600307 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500308
309 if bb.data.inherits_class('image', d):
310 raise DevtoolError("The %s recipe is an image, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600311 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500312
313 if bb.data.inherits_class('populate_sdk', d):
314 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600315 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316
317 if bb.data.inherits_class('packagegroup', d):
318 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600319 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500320
321 if bb.data.inherits_class('meta', d):
322 raise DevtoolError("The %s recipe is a meta-recipe, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600323 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500324
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500325 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600326 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500327 raise DevtoolError("externalsrc is currently enabled for the %s "
328 "recipe. This prevents the normal do_patch task "
329 "from working. You will need to disable this "
330 "first." % pn)
331
Brad Bishop316dfdd2018-06-25 12:45:53 -0400332def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
333 """Common function for copying a file to the dry run output directory"""
334 relpath = os.path.relpath(dst, base_outdir)
335 if relpath.startswith('..'):
336 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
337 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500338 dst_d = os.path.dirname(dst)
339 if dst_d:
340 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400341 # Don't overwrite existing files, otherwise in the case of an upgrade
342 # the dry-run written out recipe will be overwritten with an unmodified
343 # version
344 if not os.path.exists(dst):
345 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500346
Brad Bishop316dfdd2018-06-25 12:45:53 -0400347def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
348 """Move a file. Creates all the directory components of destination path."""
349 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
350 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
351 if dry_run_outdir:
352 # We want to copy here, not move
353 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
354 else:
355 dst_d = os.path.dirname(dst)
356 if dst_d:
357 bb.utils.mkdirhier(dst_d)
358 shutil.move(src, dst)
359
360def _copy_file(src, dst, dry_run_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600361 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400362 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
363 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
364 if dry_run_outdir:
365 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
366 else:
367 dst_d = os.path.dirname(dst)
368 if dst_d:
369 bb.utils.mkdirhier(dst_d)
370 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600371
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500372def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
373 """List contents of a git treeish"""
374 import bb
375 cmd = ['git', 'ls-tree', '-z', treeish]
376 if recursive:
377 cmd.append('-r')
378 out, _ = bb.process.run(cmd, cwd=repodir)
379 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500380 if out:
381 for line in out.split('\0'):
382 if line:
383 split = line.split(None, 4)
384 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500385 return ret
386
387def _git_exclude_path(srctree, path):
388 """Return pathspec (list of paths) that excludes certain path"""
389 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
390 # we don't catch files that are deleted, for example. A more reliable way
391 # to implement this would be to use "negative pathspecs" which were
392 # introduced in Git v1.9.0. Revisit this when/if the required Git version
393 # becomes greater than that.
394 path = os.path.normpath(path)
395 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600396 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500397 if path in git_files:
398 git_files.remove(path)
399 return git_files
400 else:
401 return ['.']
402
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500403def _ls_tree(directory):
404 """Recursive listing of files in a directory"""
405 ret = []
406 for root, dirs, files in os.walk(directory):
407 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
408 fname in files])
409 return ret
410
411
412def extract(args, config, basepath, workspace):
413 """Entry point for the devtool 'extract' subcommand"""
414 import bb
415
Brad Bishop316dfdd2018-06-25 12:45:53 -0400416 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500417 if not tinfoil:
418 # Error already shown
419 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600420 try:
421 rd = parse_recipe(config, tinfoil, args.recipename, True)
422 if not rd:
423 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500424
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600425 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400426 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 -0600427 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500428
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600429 if initial_rev:
430 return 0
431 else:
432 return 1
433 finally:
434 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500435
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500436def sync(args, config, basepath, workspace):
437 """Entry point for the devtool 'sync' subcommand"""
438 import bb
439
Brad Bishop316dfdd2018-06-25 12:45:53 -0400440 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500441 if not tinfoil:
442 # Error already shown
443 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600444 try:
445 rd = parse_recipe(config, tinfoil, args.recipename, True)
446 if not rd:
447 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500448
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600449 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400450 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 -0600451 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500452
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600453 if initial_rev:
454 return 0
455 else:
456 return 1
457 finally:
458 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500459
Brad Bishop96ff1982019-08-19 13:50:42 -0400460def symlink_oelocal_files_srctree(rd,srctree):
461 import oe.patch
462 if os.path.abspath(rd.getVar('S')) == os.path.abspath(rd.getVar('WORKDIR')):
463 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
464 # (otherwise the recipe won't build as expected)
465 local_files_dir = os.path.join(srctree, 'oe-local-files')
466 addfiles = []
467 for root, _, files in os.walk(local_files_dir):
468 relpth = os.path.relpath(root, local_files_dir)
469 if relpth != '.':
470 bb.utils.mkdirhier(os.path.join(srctree, relpth))
471 for fn in files:
472 if fn == '.gitignore':
473 continue
474 destpth = os.path.join(srctree, relpth, fn)
475 if os.path.exists(destpth):
476 os.unlink(destpth)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600477 if relpth != '.':
478 back_relpth = os.path.relpath(local_files_dir, root)
479 os.symlink('%s/oe-local-files/%s/%s' % (back_relpth, relpth, fn), destpth)
480 else:
481 os.symlink('oe-local-files/%s' % fn, destpth)
Brad Bishop96ff1982019-08-19 13:50:42 -0400482 addfiles.append(os.path.join(relpth, fn))
483 if addfiles:
484 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
Brad Bishop79641f22019-09-10 07:20:22 -0400485 useroptions = []
486 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=rd)
487 bb.process.run('git %s commit -m "Committing local file symlinks\n\n%s"' % (' '.join(useroptions), oe.patch.GitApplyTree.ignore_commit_prefix), cwd=srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400488
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500489
Brad Bishop316dfdd2018-06-25 12:45:53 -0400490def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500491 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500492 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500493 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400494 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500495
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500496 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500497
498 _check_compatible_recipe(pn, d)
499
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500500 if sync:
501 if not os.path.exists(srctree):
502 raise DevtoolError("output path %s does not exist" % srctree)
503 else:
504 if os.path.exists(srctree):
505 if not os.path.isdir(srctree):
506 raise DevtoolError("output path %s exists and is not a directory" %
507 srctree)
508 elif os.listdir(srctree):
509 raise DevtoolError("output path %s already exists and is "
510 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500511
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500512 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
513 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600514 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500515
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500516 if not sync:
517 # Prepare for shutil.move later on
518 bb.utils.mkdirhier(srctree)
519 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520
Brad Bishop316dfdd2018-06-25 12:45:53 -0400521 extra_overrides = []
522 if not no_overrides:
523 history = d.varhistory.variable('SRC_URI')
524 for event in history:
525 if not 'flag' in event:
526 if event['op'].startswith(('_append[', '_prepend[')):
527 extra_overrides.append(event['op'].split('[')[1].split(']')[0])
Andrew Geissler99467da2019-02-25 18:54:23 -0600528 # We want to remove duplicate overrides. If a recipe had multiple
529 # SRC_URI_override += values it would cause mulitple instances of
530 # overrides. This doesn't play nicely with things like creating a
531 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
532 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400533 if extra_overrides:
534 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
535
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500536 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500537
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500538 recipefile = d.getVar('FILE')
539 appendfile = recipe_to_append(recipefile, config)
540 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
541
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500542 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
543 # directory so that:
544 # (a) we pick up all files that get unpacked to the WORKDIR, and
545 # (b) we don't disturb the existing build
546 # However, with recipe-specific sysroots the sysroots for the recipe
547 # will be prepared under WORKDIR, and if we used the system temporary
548 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
549 # our attempts to hardlink files into the recipe-specific sysroots
550 # will fail on systems where /tmp is a different filesystem, and it
551 # would have to fall back to copying the files which is a waste of
552 # time. Put the temp directory under the WORKDIR to prevent that from
553 # being a problem.
554 tempbasedir = d.getVar('WORKDIR')
555 bb.utils.mkdirhier(tempbasedir)
556 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500557 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500558 tinfoil.logger.setLevel(logging.WARNING)
559
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500560 # FIXME this results in a cache reload under control of tinfoil, which is fine
561 # except we don't get the knotty progress bar
562
563 if os.path.exists(appendfile):
564 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
565 shutil.copyfile(appendfile, appendbackup)
566 else:
567 appendbackup = None
568 bb.utils.mkdirhier(os.path.dirname(appendfile))
569 logger.debug('writing append file %s' % appendfile)
570 with open(appendfile, 'a') as f:
571 f.write('###--- _extract_source\n')
572 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
573 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
574 if not is_kernel_yocto:
575 f.write('PATCHTOOL = "git"\n')
576 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400577 if extra_overrides:
578 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500579 f.write('inherit devtool-source\n')
580 f.write('###--- _extract_source\n')
581
582 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
583
584 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
585 bb.utils.mkdirhier(sstate_manifests)
586 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
587 with open(preservestampfile, 'w') as f:
588 f.write(d.getVar('STAMP'))
589 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400590 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500591 # We need to generate the kernel config
592 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500593 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500594 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500595
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600596 if 'noexec' in (d.getVarFlags(task, False) or []) or 'task' not in (d.getVarFlags(task, False) or []):
597 logger.info('The %s recipe has %s disabled. Running only '
598 'do_configure task dependencies' % (pn, task))
599
600 if 'depends' in d.getVarFlags('do_configure', False):
601 pn = d.getVarFlags('do_configure', False)['depends']
602 pn = pn.replace('${PV}', d.getVar('PV'))
603 pn = pn.replace('${COMPILERDEP}', d.getVar('COMPILERDEP'))
604 task = None
605
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500606 # Run the fetch + unpack tasks
607 res = tinfoil.build_targets(pn,
608 task,
609 handle_events=True)
610 finally:
611 if os.path.exists(preservestampfile):
612 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500613
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500614 if not res:
615 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500616
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600617 if not is_kernel_yocto and ('noexec' in (d.getVarFlags('do_patch', False) or []) or 'task' not in (d.getVarFlags('do_patch', False) or [])):
618 workshareddir = d.getVar('S')
619 if os.path.islink(srctree):
620 os.unlink(srctree)
621
622 os.symlink(workshareddir, srctree)
623
624 # The initial_rev file is created in devtool_post_unpack function that will not be executed if
625 # do_unpack/do_patch tasks are disabled so we have to directly say that source extraction was successful
626 return True, True
627
Brad Bishop316dfdd2018-06-25 12:45:53 -0400628 try:
629 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
630 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500631
Brad Bishop316dfdd2018-06-25 12:45:53 -0400632 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
633 srcsubdir = f.read()
634 except FileNotFoundError as e:
635 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
636 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500637
Brad Bishop96ff1982019-08-19 13:50:42 -0400638 # Check if work-shared is empty, if yes
639 # find source and copy to work-shared
640 if is_kernel_yocto:
641 workshareddir = d.getVar('STAGING_KERNEL_DIR')
642 staging_kerVer = get_staging_kver(workshareddir)
643 kernelVersion = d.getVar('LINUX_VERSION')
644
645 # handle dangling symbolic link in work-shared:
646 if os.path.islink(workshareddir):
647 os.unlink(workshareddir)
648
649 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
650 shutil.rmtree(workshareddir)
651 oe.path.copyhardlinktree(srcsubdir,workshareddir)
652 elif not os.path.exists(workshareddir):
653 oe.path.copyhardlinktree(srcsubdir,workshareddir)
654
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500655 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
656 srctree_localdir = os.path.join(srctree, 'oe-local-files')
657
658 if sync:
659 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
660
661 # Move oe-local-files directory to srctree
662 # As the oe-local-files is not part of the constructed git tree,
663 # remove them directly during the synchrounizating might surprise
664 # the users. Instead, we move it to oe-local-files.bak and remind
665 # user in the log message.
666 if os.path.exists(srctree_localdir + '.bak'):
667 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
668
669 if os.path.exists(srctree_localdir):
670 logger.info('Backing up current local file directory %s' % srctree_localdir)
671 shutil.move(srctree_localdir, srctree_localdir + '.bak')
672
673 if os.path.exists(tempdir_localdir):
674 logger.info('Syncing local source files to srctree...')
675 shutil.copytree(tempdir_localdir, srctree_localdir)
676 else:
677 # Move oe-local-files directory to srctree
678 if os.path.exists(tempdir_localdir):
679 logger.info('Adding local source files to srctree...')
680 shutil.move(tempdir_localdir, srcsubdir)
681
682 shutil.move(srcsubdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400683 symlink_oelocal_files_srctree(d,srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500684
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500685 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500686 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500687 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500688
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500689 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500690 if appendbackup:
691 shutil.copyfile(appendbackup, appendfile)
692 elif os.path.exists(appendfile):
693 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500694 if keep_temp:
695 logger.info('Preserving temporary directory %s' % tempdir)
696 else:
697 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400698 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500699
700def _add_md5(config, recipename, filename):
701 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
702 import bb.utils
703
704 def addfile(fn):
705 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500706 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
707 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
708 f.seek(0, os.SEEK_SET)
709 if not md5_str in f.read():
710 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500711
712 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500713 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500714 for f in files:
715 addfile(os.path.join(root, f))
716 else:
717 addfile(filename)
718
719def _check_preserve(config, recipename):
720 """Check if a file was manually changed and needs to be saved in 'attic'
721 directory"""
722 import bb.utils
723 origfile = os.path.join(config.workspace_path, '.devtool_md5')
724 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500725 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500726 with open(origfile, 'r') as f:
727 with open(newfile, 'w') as tf:
728 for line in f.readlines():
729 splitline = line.rstrip().split('|')
730 if splitline[0] == recipename:
731 removefile = os.path.join(config.workspace_path, splitline[1])
732 try:
733 md5 = bb.utils.md5_file(removefile)
734 except IOError as err:
735 if err.errno == 2:
736 # File no longer exists, skip it
737 continue
738 else:
739 raise
740 if splitline[2] != md5:
741 bb.utils.mkdirhier(preservepath)
742 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800743 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500744 shutil.move(removefile, os.path.join(preservepath, preservefile))
745 else:
746 os.remove(removefile)
747 else:
748 tf.write(line)
749 os.rename(newfile, origfile)
750
Brad Bishop96ff1982019-08-19 13:50:42 -0400751def get_staging_kver(srcdir):
752 # Kernel version from work-shared
753 kerver = []
754 staging_kerVer=""
755 if os.path.exists(srcdir) and os.listdir(srcdir):
756 with open(os.path.join(srcdir,"Makefile")) as f:
757 version = [next(f) for x in range(5)][1:4]
758 for word in version:
759 kerver.append(word.split('= ')[1].split('\n')[0])
760 staging_kerVer = ".".join(kerver)
761 return staging_kerVer
762
763def get_staging_kbranch(srcdir):
764 staging_kbranch = ""
765 if os.path.exists(srcdir) and os.listdir(srcdir):
766 (branch, _) = bb.process.run('git branch | grep \* | cut -d \' \' -f2', cwd=srcdir)
767 staging_kbranch = "".join(branch.split('\n')[0])
768 return staging_kbranch
769
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500770def modify(args, config, basepath, workspace):
771 """Entry point for the devtool 'modify' subcommand"""
772 import bb
773 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400774 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400775 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500776
777 if args.recipename in workspace:
778 raise DevtoolError("recipe %s is already in your workspace" %
779 args.recipename)
780
Brad Bishop316dfdd2018-06-25 12:45:53 -0400781 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600782 try:
783 rd = parse_recipe(config, tinfoil, args.recipename, True)
784 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500785 return 1
786
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500787 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600788 if pn != args.recipename:
789 logger.info('Mapping %s to %s' % (args.recipename, pn))
790 if pn in workspace:
791 raise DevtoolError("recipe %s is already in your workspace" %
792 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500793
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600794 if args.srctree:
795 srctree = os.path.abspath(args.srctree)
796 else:
797 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500798
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600799 if args.no_extract and not os.path.isdir(srctree):
800 raise DevtoolError("--no-extract specified and source path %s does "
801 "not exist or is not a directory" %
802 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600803
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500804 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600805 appendfile = recipe_to_append(recipefile, config, args.wildcard)
806 if os.path.exists(appendfile):
807 raise DevtoolError("Another variant of recipe %s is already in your "
808 "workspace (only one variant of a recipe can "
809 "currently be worked on at once)"
810 % pn)
811
812 _check_compatible_recipe(pn, rd)
813
814 initial_rev = None
815 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400816 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400817
818 if bb.data.inherits_class('kernel-yocto', rd):
819 # Current set kernel version
820 kernelVersion = rd.getVar('LINUX_VERSION')
821 srcdir = rd.getVar('STAGING_KERNEL_DIR')
822 kbranch = rd.getVar('KBRANCH')
823
824 staging_kerVer = get_staging_kver(srcdir)
825 staging_kbranch = get_staging_kbranch(srcdir)
826 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
827 oe.path.copyhardlinktree(srcdir,srctree)
828 workdir = rd.getVar('WORKDIR')
829 srcsubdir = rd.getVar('S')
830 localfilesdir = os.path.join(srctree,'oe-local-files')
831 # Move local source files into separate subdir
832 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
833 local_files = oe.recipeutils.get_recipe_local_files(rd)
834
835 for key in local_files.copy():
836 if key.endswith('scc'):
837 sccfile = open(local_files[key], 'r')
838 for l in sccfile:
839 line = l.split()
840 if line and line[0] in ('kconf', 'patch'):
841 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
842 if not cfg in local_files.values():
843 local_files[line[-1]] = cfg
844 shutil.copy2(cfg, workdir)
845 sccfile.close()
846
847 # Ignore local files with subdir={BP}
848 srcabspath = os.path.abspath(srcsubdir)
849 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))]
850 if local_files:
851 for fname in local_files:
852 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
853 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
854 f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n')
855
856 symlink_oelocal_files_srctree(rd,srctree)
857
858 task = 'do_configure'
859 res = tinfoil.build_targets(pn, task, handle_events=True)
860
861 # Copy .config to workspace
862 kconfpath = rd.getVar('B')
863 logger.info('Copying kernel config to workspace')
864 shutil.copy2(os.path.join(kconfpath, '.config'),srctree)
865
866 # Set this to true, we still need to get initial_rev
867 # by parsing the git repo
868 args.no_extract = True
869
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600870 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400871 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 Williamsc124f4f2015-09-15 14:41:29 -0500872 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600873 return 1
874 logger.info('Source tree extracted to %s' % srctree)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600875 if os.path.exists(os.path.join(srctree, '.git')):
876 # Get list of commits since this revision
877 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
878 commits = stdout.split()
879 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600880 else:
881 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600882 # Check if it's a tree previously extracted by us. This is done
883 # by ensuring that devtool-base and args.branch (devtool) exist.
884 # The check_commits logic will cause an exception if either one
885 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600886 try:
887 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600888 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600889 except bb.process.ExecutionError:
890 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400891 if stdout:
892 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600893 for line in stdout.splitlines():
894 if line.startswith('*'):
895 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
896 initial_rev = stdout.rstrip()
897 if not initial_rev:
898 # Otherwise, just grab the head revision
899 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
900 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500901
Brad Bishop316dfdd2018-06-25 12:45:53 -0400902 branch_patches = {}
903 if check_commits:
904 # Check if there are override branches
905 (stdout, _) = bb.process.run('git branch', cwd=srctree)
906 branches = []
907 for line in stdout.rstrip().splitlines():
908 branchname = line[2:].rstrip()
909 if branchname.startswith(override_branch_prefix):
910 branches.append(branchname)
911 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800912 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 -0400913 branches.insert(0, args.branch)
914 seen_patches = []
915 for branch in branches:
916 branch_patches[branch] = []
917 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
918 for line in stdout.splitlines():
919 line = line.strip()
920 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
921 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
922 if not origpatch in seen_patches:
923 seen_patches.append(origpatch)
924 branch_patches[branch].append(origpatch)
925
926 # Need to grab this here in case the source is within a subdirectory
927 srctreebase = srctree
928
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600929 # Check that recipe isn't using a shared workdir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500930 s = os.path.abspath(rd.getVar('S'))
931 workdir = os.path.abspath(rd.getVar('WORKDIR'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600932 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
933 # Handle if S is set to a subdirectory of the source
934 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
935 srctree = os.path.join(srctree, srcsubdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500936
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600937 bb.utils.mkdirhier(os.path.dirname(appendfile))
938 with open(appendfile, 'w') as f:
939 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
940 # Local files can be modified/tracked in separate subdir under srctree
941 # Mostly useful for packages with S != WORKDIR
942 f.write('FILESPATH_prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400943 os.path.join(srctreebase, 'oe-local-files'))
944 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500945
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600946 f.write('\ninherit externalsrc\n')
947 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
948 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500949
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600950 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
951 if b_is_s:
952 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500953
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600954 if bb.data.inherits_class('kernel', rd):
955 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500956 'do_fetch do_unpack do_kernel_configme do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400957 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600958 f.write('\ndo_configure_append() {\n'
959 ' cp ${B}/.config ${S}/.config.baseline\n'
960 ' ln -sfT ${B}/.config ${S}/.config.new\n'
961 '}\n')
Brad Bishop96ff1982019-08-19 13:50:42 -0400962 if rd.getVarFlag('do_menuconfig','task'):
963 f.write('\ndo_configure_append() {\n'
Andrew Geissler82c905d2020-04-13 13:39:40 -0500964 ' if [ ! ${DEVTOOL_DISABLE_MENUCONFIG} ]; then\n'
965 ' cp ${B}/.config ${S}/.config.baseline\n'
966 ' ln -sfT ${B}/.config ${S}/.config.new\n'
967 ' fi\n'
Brad Bishop96ff1982019-08-19 13:50:42 -0400968 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600969 if initial_rev:
970 f.write('\n# initial_rev: %s\n' % initial_rev)
971 for commit in commits:
972 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400973 if branch_patches:
974 for branch in branch_patches:
975 if branch == args.branch:
976 continue
977 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500978
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500979 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
980
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600981 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500982
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600983 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500984
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600985 finally:
986 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500987
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500988 return 0
989
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500990
991def rename(args, config, basepath, workspace):
992 """Entry point for the devtool 'rename' subcommand"""
993 import bb
994 import oe.recipeutils
995
996 check_workspace_recipe(workspace, args.recipename)
997
998 if not (args.newname or args.version):
999 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
1000
1001 recipefile = workspace[args.recipename]['recipefile']
1002 if not recipefile:
1003 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
1004
1005 if args.newname and args.newname != args.recipename:
1006 reason = oe.recipeutils.validate_pn(args.newname)
1007 if reason:
1008 raise DevtoolError(reason)
1009 newname = args.newname
1010 else:
1011 newname = args.recipename
1012
1013 append = workspace[args.recipename]['bbappend']
1014 appendfn = os.path.splitext(os.path.basename(append))[0]
1015 splitfn = appendfn.split('_')
1016 if len(splitfn) > 1:
1017 origfnver = appendfn.split('_')[1]
1018 else:
1019 origfnver = ''
1020
1021 recipefilemd5 = None
1022 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1023 try:
1024 rd = parse_recipe(config, tinfoil, args.recipename, True)
1025 if not rd:
1026 return 1
1027
1028 bp = rd.getVar('BP')
1029 bpn = rd.getVar('BPN')
1030 if newname != args.recipename:
1031 localdata = rd.createCopy()
1032 localdata.setVar('PN', newname)
1033 newbpn = localdata.getVar('BPN')
1034 else:
1035 newbpn = bpn
1036 s = rd.getVar('S', False)
1037 src_uri = rd.getVar('SRC_URI', False)
1038 pv = rd.getVar('PV')
1039
1040 # Correct variable values that refer to the upstream source - these
1041 # values must stay the same, so if the name/version are changing then
1042 # we need to fix them up
1043 new_s = s
1044 new_src_uri = src_uri
1045 if newbpn != bpn:
1046 # ${PN} here is technically almost always incorrect, but people do use it
1047 new_s = new_s.replace('${BPN}', bpn)
1048 new_s = new_s.replace('${PN}', bpn)
1049 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1050 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1051 new_src_uri = new_src_uri.replace('${PN}', bpn)
1052 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1053 if args.version and origfnver == pv:
1054 new_s = new_s.replace('${PV}', pv)
1055 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1056 new_src_uri = new_src_uri.replace('${PV}', pv)
1057 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1058 patchfields = {}
1059 if new_s != s:
1060 patchfields['S'] = new_s
1061 if new_src_uri != src_uri:
1062 patchfields['SRC_URI'] = new_src_uri
1063 if patchfields:
1064 recipefilemd5 = bb.utils.md5_file(recipefile)
1065 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1066 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1067 finally:
1068 tinfoil.shutdown()
1069
1070 if args.version:
1071 newver = args.version
1072 else:
1073 newver = origfnver
1074
1075 if newver:
1076 newappend = '%s_%s.bbappend' % (newname, newver)
1077 newfile = '%s_%s.bb' % (newname, newver)
1078 else:
1079 newappend = '%s.bbappend' % newname
1080 newfile = '%s.bb' % newname
1081
1082 oldrecipedir = os.path.dirname(recipefile)
1083 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1084 if oldrecipedir != newrecipedir:
1085 bb.utils.mkdirhier(newrecipedir)
1086
1087 newappend = os.path.join(os.path.dirname(append), newappend)
1088 newfile = os.path.join(newrecipedir, newfile)
1089
1090 # Rename bbappend
1091 logger.info('Renaming %s to %s' % (append, newappend))
1092 os.rename(append, newappend)
1093 # Rename recipe file
1094 logger.info('Renaming %s to %s' % (recipefile, newfile))
1095 os.rename(recipefile, newfile)
1096
1097 # Rename source tree if it's the default path
1098 appendmd5 = None
1099 if not args.no_srctree:
1100 srctree = workspace[args.recipename]['srctree']
1101 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1102 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1103 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1104 shutil.move(srctree, newsrctree)
1105 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1106 appendmd5 = bb.utils.md5_file(newappend)
1107 appendlines = []
1108 with open(newappend, 'r') as f:
1109 for line in f:
1110 appendlines.append(line)
1111 with open(newappend, 'w') as f:
1112 for line in appendlines:
1113 if srctree in line:
1114 line = line.replace(srctree, newsrctree)
1115 f.write(line)
1116 newappendmd5 = bb.utils.md5_file(newappend)
1117
1118 bpndir = None
1119 newbpndir = None
1120 if newbpn != bpn:
1121 bpndir = os.path.join(oldrecipedir, bpn)
1122 if os.path.exists(bpndir):
1123 newbpndir = os.path.join(newrecipedir, newbpn)
1124 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1125 shutil.move(bpndir, newbpndir)
1126
1127 bpdir = None
1128 newbpdir = None
1129 if newver != origfnver or newbpn != bpn:
1130 bpdir = os.path.join(oldrecipedir, bp)
1131 if os.path.exists(bpdir):
1132 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1133 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1134 shutil.move(bpdir, newbpdir)
1135
1136 if oldrecipedir != newrecipedir:
1137 # Move any stray files and delete the old recipe directory
1138 for entry in os.listdir(oldrecipedir):
1139 oldpath = os.path.join(oldrecipedir, entry)
1140 newpath = os.path.join(newrecipedir, entry)
1141 logger.info('Renaming %s to %s' % (oldpath, newpath))
1142 shutil.move(oldpath, newpath)
1143 os.rmdir(oldrecipedir)
1144
1145 # Now take care of entries in .devtool_md5
1146 md5entries = []
1147 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1148 for line in f:
1149 md5entries.append(line)
1150
1151 if bpndir and newbpndir:
1152 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1153 else:
1154 relbpndir = None
1155 if bpdir and newbpdir:
1156 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1157 else:
1158 relbpdir = None
1159
1160 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1161 for entry in md5entries:
1162 splitentry = entry.rstrip().split('|')
1163 if len(splitentry) > 2:
1164 if splitentry[0] == args.recipename:
1165 splitentry[0] = newname
1166 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1167 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1168 if appendmd5 and splitentry[2] == appendmd5:
1169 splitentry[2] = newappendmd5
1170 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1171 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1172 if recipefilemd5 and splitentry[2] == recipefilemd5:
1173 splitentry[2] = newrecipefilemd5
1174 elif relbpndir and splitentry[1].startswith(relbpndir):
1175 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1176 elif relbpdir and splitentry[1].startswith(relbpdir):
1177 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1178 entry = '|'.join(splitentry) + '\n'
1179 f.write(entry)
1180 return 0
1181
1182
Brad Bishop316dfdd2018-06-25 12:45:53 -04001183def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001184 """Get initial and update rev of a recipe. These are the start point of the
1185 whole patchset and start point for the patches to be re-generated/updated.
1186 """
1187 import bb
1188
Brad Bishop316dfdd2018-06-25 12:45:53 -04001189 # Get current branch
1190 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1191 cwd=srctree)
1192 branchname = stdout.rstrip()
1193
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001194 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001195 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001196 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001197 with open(recipe_path, 'r') as f:
1198 for line in f:
1199 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001200 if not initial_rev:
1201 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001202 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001203 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001204 elif line.startswith('# patches_%s:' % branchname):
1205 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001206
1207 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001208 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001209 if initial_rev:
1210 # Find first actually changed revision
1211 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1212 initial_rev, cwd=srctree)
1213 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001214 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001215 if newcommits[i] == commits[i]:
1216 update_rev = commits[i]
1217
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001218 try:
1219 stdout, _ = bb.process.run('git cherry devtool-patched',
1220 cwd=srctree)
1221 except bb.process.ExecutionError as err:
1222 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001223
Brad Bishop316dfdd2018-06-25 12:45:53 -04001224 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001225 changed_revs = []
1226 for line in stdout.splitlines():
1227 if line.startswith('+ '):
1228 rev = line.split()[1]
1229 if rev in newcommits:
1230 changed_revs.append(rev)
1231
Brad Bishop316dfdd2018-06-25 12:45:53 -04001232 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001233
1234def _remove_file_entries(srcuri, filelist):
1235 """Remove file:// entries from SRC_URI"""
1236 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001237 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001238 for fname in filelist:
1239 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001240 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001241 if (srcuri[i].startswith('file://') and
1242 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001243 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001244 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001245 srcuri.pop(i)
1246 break
1247 return entries, remaining
1248
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001249def _replace_srcuri_entry(srcuri, filename, newentry):
1250 """Replace entry corresponding to specified file with a new entry"""
1251 basename = os.path.basename(filename)
1252 for i in range(len(srcuri)):
1253 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1254 srcuri.pop(i)
1255 srcuri.insert(i, newentry)
1256 break
1257
Brad Bishop316dfdd2018-06-25 12:45:53 -04001258def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001259 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001260
1261 dry_run_suffix = ' (dry-run)' if dry_run else ''
1262
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001263 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001264 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001265 if not destpath:
1266 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001267 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001268
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001269 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001270 if not no_report_remove:
1271 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1272 if not dry_run:
1273 # FIXME "git rm" here would be nice if the file in question is
1274 # tracked
1275 # FIXME there's a chance that this file is referred to by
1276 # another recipe, in which case deleting wouldn't be the
1277 # right thing to do
1278 os.remove(path)
1279 # Remove directory if empty
1280 try:
1281 os.rmdir(os.path.dirname(path))
1282 except OSError as ose:
1283 if ose.errno != errno.ENOTEMPTY:
1284 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001285
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001286
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001287def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001288 """Export patches from srctree to given location.
1289 Returns three-tuple of dicts:
1290 1. updated - patches that already exist in SRCURI
1291 2. added - new patches that don't exist in SRCURI
1292 3 removed - patches that exist in SRCURI but not in exported patches
1293 In each dict the key is the 'basepath' of the URI and value is the
1294 absolute path to the existing file in recipe space (if any).
1295 """
1296 import oe.recipeutils
1297 from oe.patch import GitApplyTree
1298 updated = OrderedDict()
1299 added = OrderedDict()
1300 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1301
1302 existing_patches = dict((os.path.basename(path), path) for path in
1303 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001304 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001305
1306 # Generate patches from Git, exclude local files directory
1307 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1308 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1309
1310 new_patches = sorted(os.listdir(destdir))
1311 for new_patch in new_patches:
1312 # Strip numbering from patch names. If it's a git sequence named patch,
1313 # the numbers might not match up since we are starting from a different
1314 # revision This does assume that people are using unique shortlog
1315 # values, but they ought to be anyway...
1316 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001317 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001318 for old_patch in existing_patches:
1319 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001320 old_basename_splitext = os.path.splitext(old_basename)
1321 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1322 old_patch_noext = os.path.splitext(old_patch)[0]
1323 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001324 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001325 elif new_basename == old_basename:
1326 match_name = old_patch
1327 break
1328 if match_name:
1329 # Rename patch files
1330 if new_patch != match_name:
1331 os.rename(os.path.join(destdir, new_patch),
1332 os.path.join(destdir, match_name))
1333 # Need to pop it off the list now before checking changed_revs
1334 oldpath = existing_patches.pop(old_patch)
1335 if changed_revs is not None:
1336 # Avoid updating patches that have not actually changed
1337 with open(os.path.join(destdir, match_name), 'r') as f:
1338 firstlineitems = f.readline().split()
1339 # Looking for "From <hash>" line
1340 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1341 if not firstlineitems[1] in changed_revs:
1342 continue
1343 # Recompress if necessary
1344 if oldpath.endswith(('.gz', '.Z')):
1345 bb.process.run(['gzip', match_name], cwd=destdir)
1346 if oldpath.endswith('.gz'):
1347 match_name += '.gz'
1348 else:
1349 match_name += '.Z'
1350 elif oldpath.endswith('.bz2'):
1351 bb.process.run(['bzip2', match_name], cwd=destdir)
1352 match_name += '.bz2'
1353 updated[match_name] = oldpath
1354 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001355 added[new_patch] = None
1356 return (updated, added, existing_patches)
1357
1358
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001359def _create_kconfig_diff(srctree, rd, outfile):
1360 """Create a kconfig fragment"""
1361 # Only update config fragment if both config files exist
1362 orig_config = os.path.join(srctree, '.config.baseline')
1363 new_config = os.path.join(srctree, '.config.new')
1364 if os.path.exists(orig_config) and os.path.exists(new_config):
1365 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1366 '--unchanged-line-format=', orig_config, new_config]
1367 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1368 stderr=subprocess.PIPE)
1369 stdout, stderr = pipe.communicate()
1370 if pipe.returncode == 1:
1371 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001372 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001373 fobj.write(stdout)
1374 elif pipe.returncode == 0:
1375 logger.info("Would remove config fragment %s" % outfile)
1376 if os.path.exists(outfile):
1377 # Remove fragment file in case of empty diff
1378 logger.info("Removing config fragment %s" % outfile)
1379 os.unlink(outfile)
1380 else:
1381 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1382 return True
1383 return False
1384
1385
Brad Bishop316dfdd2018-06-25 12:45:53 -04001386def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001387 """Copy local files from srctree to given location.
1388 Returns three-tuple of dicts:
1389 1. updated - files that already exist in SRCURI
1390 2. added - new files files that don't exist in SRCURI
1391 3 removed - files that exist in SRCURI but not in exported files
1392 In each dict the key is the 'basepath' of the URI and value is the
1393 absolute path to the existing file in recipe space (if any).
1394 """
1395 import oe.recipeutils
1396
1397 # Find out local files (SRC_URI files that exist in the "recipe space").
1398 # Local files that reside in srctree are not included in patch generation.
1399 # Instead they are directly copied over the original source files (in
1400 # recipe space).
1401 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1402 new_set = None
1403 updated = OrderedDict()
1404 added = OrderedDict()
1405 removed = OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001406 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001407 git_files = _git_ls_tree(srctree)
1408 if 'oe-local-files' in git_files:
1409 # If tracked by Git, take the files from srctree HEAD. First get
1410 # the tree object of the directory
1411 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1412 tree = git_files['oe-local-files'][2]
1413 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1414 env=dict(os.environ, GIT_WORK_TREE=destdir,
1415 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001416 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001417 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001418 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001419 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001420 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001421 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001422 else:
1423 new_set = []
1424
1425 # Special handling for kernel config
1426 if bb.data.inherits_class('kernel-yocto', rd):
1427 fragment_fn = 'devtool-fragment.cfg'
1428 fragment_path = os.path.join(destdir, fragment_fn)
1429 if _create_kconfig_diff(srctree, rd, fragment_path):
1430 if os.path.exists(fragment_path):
1431 if fragment_fn not in new_set:
1432 new_set.append(fragment_fn)
1433 # Copy fragment to local-files
1434 if os.path.isdir(local_files_dir):
1435 shutil.copy2(fragment_path, local_files_dir)
1436 else:
1437 if fragment_fn in new_set:
1438 new_set.remove(fragment_fn)
1439 # Remove fragment from local-files
1440 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1441 os.unlink(os.path.join(local_files_dir, fragment_fn))
1442
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001443 # Special handling for cml1, ccmake, etc bbclasses that generated
1444 # configuration fragment files that are consumed as source files
1445 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1446 if bb.data.inherits_class(frag_class, rd):
1447 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1448 if os.path.exists(srcpath):
1449 if frag_name not in new_set:
1450 new_set.append(frag_name)
1451 # copy fragment into destdir
1452 shutil.copy2(srcpath, destdir)
1453 # copy fragment into local files if exists
1454 if os.path.isdir(local_files_dir):
1455 shutil.copy2(srcpath, local_files_dir)
1456
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001457 if new_set is not None:
1458 for fname in new_set:
1459 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001460 origpath = existing_files.pop(fname)
1461 workpath = os.path.join(local_files_dir, fname)
1462 if not filecmp.cmp(origpath, workpath):
1463 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001464 elif fname != '.gitignore':
1465 added[fname] = None
1466
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001467 workdir = rd.getVar('WORKDIR')
1468 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001469 if not s.endswith(os.sep):
1470 s += os.sep
1471
1472 if workdir != s:
1473 # Handle files where subdir= was specified
1474 for fname in list(existing_files.keys()):
1475 # FIXME handle both subdir starting with BP and not?
1476 fworkpath = os.path.join(workdir, fname)
1477 if fworkpath.startswith(s):
1478 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1479 if os.path.exists(fpath):
1480 origpath = existing_files.pop(fname)
1481 if not filecmp.cmp(origpath, fpath):
1482 updated[fpath] = origpath
1483
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001484 removed = existing_files
1485 return (updated, added, removed)
1486
1487
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001488def _determine_files_dir(rd):
1489 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001490 recipedir = rd.getVar('FILE_DIRNAME')
1491 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001492 relpth = os.path.relpath(entry, recipedir)
1493 if not os.sep in relpth:
1494 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1495 if os.path.isdir(entry):
1496 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001497 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001498
1499
Brad Bishop316dfdd2018-06-25 12:45:53 -04001500def _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 -05001501 """Implement the 'srcrev' mode of update-recipe"""
1502 import bb
1503 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001504
Brad Bishop316dfdd2018-06-25 12:45:53 -04001505 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1506
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001507 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001508 recipedir = os.path.basename(recipefile)
1509 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001510
1511 # Get HEAD revision
1512 try:
1513 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1514 except bb.process.ExecutionError as err:
1515 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1516 (srctree, err))
1517 srcrev = stdout.strip()
1518 if len(srcrev) != 40:
1519 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1520
1521 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001522 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001523 patchfields = {}
1524 patchfields['SRCREV'] = srcrev
1525 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001526 srcuri = orig_src_uri.split()
1527 tempdir = tempfile.mkdtemp(prefix='devtool')
1528 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001529 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001530 try:
1531 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001532 srctreebase = workspace[recipename]['srctreebase']
1533 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001534 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001535 # Find list of existing patches in recipe file
1536 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001537 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001538 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1539 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001540 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 -05001541
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001542 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001543 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001544 if remove_files:
1545 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1546 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001547
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001548 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001549 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001550 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001551 removevalues = {}
1552 if update_srcuri:
1553 removevalues = {'SRC_URI': removedentries}
1554 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001555 if dry_run_outdir:
1556 logger.info('Creating bbappend (dry-run)')
1557 else:
1558 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1559 rd, appendlayerdir, files, wildcardver=wildcard_version,
1560 extralines=patchfields, removevalues=removevalues,
1561 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001562 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001563 files_dir = _determine_files_dir(rd)
1564 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001565 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001566 if os.path.isabs(basepath):
1567 # Original file (probably with subdir pointing inside source tree)
1568 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001569 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001570 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001571 _move_file(os.path.join(local_files_dir, basepath), path,
1572 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001573 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001574 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001575 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001576 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001577 os.path.join(files_dir, basepath),
1578 dry_run_outdir=dry_run_outdir,
1579 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001580 srcuri.append('file://%s' % basepath)
1581 update_srcuri = True
1582 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001583 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001584 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001585 finally:
1586 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001587 if not 'git://' in orig_src_uri:
1588 logger.info('You will need to update SRC_URI within the recipe to '
1589 'point to a git repository where you have pushed your '
1590 'changes')
1591
Brad Bishop316dfdd2018-06-25 12:45:53 -04001592 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1593 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001594
Brad Bishop316dfdd2018-06-25 12:45:53 -04001595def _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 -05001596 """Implement the 'patch' mode of update-recipe"""
1597 import bb
1598 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001599
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001600 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001601 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001602 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001603 if not os.path.exists(append):
1604 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001605 recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001606
Brad Bishop316dfdd2018-06-25 12:45:53 -04001607 initial_rev, update_rev, changed_revs, filter_patches = _get_patchset_revs(srctree, append, initial_rev, force_patch_refresh)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001608 if not initial_rev:
1609 raise DevtoolError('Unable to find initial revision - please specify '
1610 'it with --initial-rev')
1611
Brad Bishop316dfdd2018-06-25 12:45:53 -04001612 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001613 dl_dir = rd.getVar('DL_DIR')
1614 if not dl_dir.endswith('/'):
1615 dl_dir += '/'
1616
Brad Bishop316dfdd2018-06-25 12:45:53 -04001617 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1618
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001619 tempdir = tempfile.mkdtemp(prefix='devtool')
1620 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001621 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001622 if filter_patches:
1623 upd_f = {}
1624 new_f = {}
1625 del_f = {}
1626 else:
1627 srctreebase = workspace[recipename]['srctreebase']
1628 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001629
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001630 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001631 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001632 # Get all patches from source tree and check if any should be removed
1633 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001634 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1635 all_patches_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001636 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001637 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001638
1639 # Get updated patches from source tree
1640 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001641 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1642 patches_dir, changed_revs)
1643 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1644 if filter_patches:
Brad Bishop00e122a2019-10-05 11:10:57 -04001645 new_p = OrderedDict()
1646 upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001647 remove_files = [f for f in remove_files if f in filter_patches]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001648 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001649 updaterecipe = False
1650 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001651 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001652 if appendlayerdir:
Brad Bishop00e122a2019-10-05 11:10:57 -04001653 files = OrderedDict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001654 key, val in list(upd_f.items()) + list(new_f.items()))
Brad Bishop00e122a2019-10-05 11:10:57 -04001655 files.update(OrderedDict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001656 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001657 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001658 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001659 if remove_files:
1660 removedentries, remaining = _remove_file_entries(
1661 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001662 if removedentries or remaining:
1663 remaining = ['file://' + os.path.basename(item) for
1664 item in remaining]
1665 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001666 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001667 rd, appendlayerdir, files,
1668 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001669 removevalues=removevalues,
1670 redirect_output=dry_run_outdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001671 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001672 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001673 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001674 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001675 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001676 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001677 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001678 if os.path.isabs(basepath):
1679 # Original file (probably with subdir pointing inside source tree)
1680 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001681 _copy_file(basepath, path,
1682 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001683 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001684 _move_file(os.path.join(local_files_dir, basepath), path,
1685 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001686 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001687 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001688 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001689 if os.path.dirname(path) + '/' == dl_dir:
1690 # This is a a downloaded patch file - we now need to
1691 # replace the entry in SRC_URI with our local version
1692 logger.info('Replacing remote patch %s with updated local version' % basepath)
1693 path = os.path.join(files_dir, basepath)
1694 _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath)
1695 updaterecipe = True
1696 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001697 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1698 _move_file(patchfn, path,
1699 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001700 updatefiles = True
1701 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001702 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001703 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001704 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001705 os.path.join(files_dir, basepath),
1706 dry_run_outdir=dry_run_outdir,
1707 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001708 srcuri.append('file://%s' % basepath)
1709 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001710 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001711 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001712 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001713 os.path.join(files_dir, basepath),
1714 dry_run_outdir=dry_run_outdir,
1715 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001716 srcuri.append('file://%s' % basepath)
1717 updaterecipe = True
1718 # Update recipe, if needed
1719 if _remove_file_entries(srcuri, remove_files)[0]:
1720 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001721 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001722 if not dry_run_outdir:
1723 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1724 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1725 {'SRC_URI': ' '.join(srcuri)},
1726 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001727 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001728 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001729 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001730 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001731 finally:
1732 shutil.rmtree(tempdir)
1733
Brad Bishop316dfdd2018-06-25 12:45:53 -04001734 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1735 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001736
1737def _guess_recipe_update_mode(srctree, rdata):
1738 """Guess the recipe update mode to use"""
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001739 src_uri = (rdata.getVar('SRC_URI') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001740 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1741 if not git_uris:
1742 return 'patch'
1743 # Just use the first URI for now
1744 uri = git_uris[0]
1745 # Check remote branch
1746 params = bb.fetch.decodeurl(uri)[5]
1747 upstr_branch = params['branch'] if 'branch' in params else 'master'
1748 # Check if current branch HEAD is found in upstream branch
1749 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1750 head_rev = stdout.rstrip()
1751 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1752 cwd=srctree)
1753 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1754 if 'origin/' + upstr_branch in remote_brs:
1755 return 'srcrev'
1756
1757 return 'patch'
1758
Brad Bishop316dfdd2018-06-25 12:45:53 -04001759def _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 -06001760 srctree = workspace[recipename]['srctree']
1761 if mode == 'auto':
1762 mode = _guess_recipe_update_mode(srctree, rd)
1763
Brad Bishop316dfdd2018-06-25 12:45:53 -04001764 override_branches = []
1765 mainbranch = None
1766 startbranch = None
1767 if not no_overrides:
1768 stdout, _ = bb.process.run('git branch', cwd=srctree)
1769 other_branches = []
1770 for line in stdout.splitlines():
1771 branchname = line[2:]
1772 if line.startswith('* '):
1773 startbranch = branchname
1774 if branchname.startswith(override_branch_prefix):
1775 override_branches.append(branchname)
1776 else:
1777 other_branches.append(branchname)
1778
1779 if override_branches:
1780 logger.debug('_update_recipe: override branches: %s' % override_branches)
1781 logger.debug('_update_recipe: other branches: %s' % other_branches)
1782 if startbranch.startswith(override_branch_prefix):
1783 if len(other_branches) == 1:
1784 mainbranch = other_branches[1]
1785 else:
1786 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1787 else:
1788 mainbranch = startbranch
1789
1790 checkedout = None
1791 anyupdated = False
1792 appendfile = None
1793 allremoved = []
1794 if override_branches:
1795 logger.info('Handling main branch (%s)...' % mainbranch)
1796 if startbranch != mainbranch:
1797 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1798 checkedout = mainbranch
1799 try:
1800 branchlist = [mainbranch] + override_branches
1801 for branch in branchlist:
1802 crd = bb.data.createCopy(rd)
1803 if branch != mainbranch:
1804 logger.info('Handling branch %s...' % branch)
1805 override = branch[len(override_branch_prefix):]
1806 crd.appendVar('OVERRIDES', ':%s' % override)
1807 bb.process.run('git checkout %s' % branch, cwd=srctree)
1808 checkedout = branch
1809
1810 if mode == 'srcrev':
1811 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1812 elif mode == 'patch':
1813 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)
1814 else:
1815 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1816 if updated:
1817 anyupdated = True
1818 if appendf:
1819 appendfile = appendf
1820 allremoved.extend(removed)
1821 finally:
1822 if startbranch and checkedout != startbranch:
1823 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1824
1825 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001826
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001827def update_recipe(args, config, basepath, workspace):
1828 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001829 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001830
1831 if args.append:
1832 if not os.path.exists(args.append):
1833 raise DevtoolError('bbappend destination layer directory "%s" '
1834 'does not exist' % args.append)
1835 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1836 raise DevtoolError('conf/layer.conf not found in bbappend '
1837 'destination layer "%s"' % args.append)
1838
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001839 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001840 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001841
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001842 rd = parse_recipe(config, tinfoil, args.recipename, True)
1843 if not rd:
1844 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001845
Brad Bishop316dfdd2018-06-25 12:45:53 -04001846 dry_run_output = None
1847 dry_run_outdir = None
1848 if args.dry_run:
1849 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1850 dry_run_outdir = dry_run_output.name
1851 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 -05001852
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001853 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001854 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001855 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001856 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 -06001857 finally:
1858 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001859
1860 return 0
1861
1862
1863def status(args, config, basepath, workspace):
1864 """Entry point for the devtool 'status' subcommand"""
1865 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001866 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001867 recipefile = value['recipefile']
1868 if recipefile:
1869 recipestr = ' (%s)' % recipefile
1870 else:
1871 recipestr = ''
1872 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001873 else:
1874 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')
1875 return 0
1876
1877
Brad Bishop64c979e2019-11-04 13:55:29 -05001878def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001879 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001880 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001881
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001882 def clean_preferred_provider(pn, layerconf_path):
1883 """Remove PREFERRED_PROVIDER from layer.conf'"""
1884 import re
1885 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1886 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1887 pprovider_found = False
1888 with open(layerconf_file, 'r') as f:
1889 lines = f.readlines()
1890 with open(new_layerconf_file, 'a') as nf:
1891 for line in lines:
1892 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1893 if not re.match(pprovider_exp, line):
1894 nf.write(line)
1895 else:
1896 pprovider_found = True
1897 if pprovider_found:
1898 shutil.move(new_layerconf_file, layerconf_file)
1899 else:
1900 os.remove(new_layerconf_file)
1901
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001902 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001903 if len(recipes) == 1:
1904 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1905 else:
1906 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001907 # If the recipe file itself was created in the workspace, and
1908 # it uses BBCLASSEXTEND, then we need to also clean the other
1909 # variants
1910 targets = []
1911 for recipe in recipes:
1912 targets.append(recipe)
1913 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001914 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001915 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001916 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001917 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001918 except bb.process.ExecutionError as e:
1919 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1920 'wish, you may specify -n/--no-clean to '
1921 'skip running this command when resetting' %
1922 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001923
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001924 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001925 _check_preserve(config, pn)
1926
Brad Bishop316dfdd2018-06-25 12:45:53 -04001927 appendfile = workspace[pn]['bbappend']
1928 if os.path.exists(appendfile):
1929 # This shouldn't happen, but is possible if devtool errored out prior to
1930 # writing the md5 file. We need to delete this here or the recipe won't
1931 # actually be reset
1932 os.remove(appendfile)
1933
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001934 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001935 def preservedir(origdir):
1936 if os.path.exists(origdir):
1937 for root, dirs, files in os.walk(origdir):
1938 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001939 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001940 _move_file(os.path.join(origdir, fn),
1941 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001942 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001943 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001944 os.rmdir(origdir)
1945
Brad Bishop316dfdd2018-06-25 12:45:53 -04001946 recipefile = workspace[pn]['recipefile']
1947 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1948 # This should always be true if recipefile is set, but just in case
1949 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001950 # We don't automatically create this dir next to appends, but the user can
1951 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1952
Brad Bishop316dfdd2018-06-25 12:45:53 -04001953 srctreebase = workspace[pn]['srctreebase']
1954 if os.path.isdir(srctreebase):
1955 if os.listdir(srctreebase):
Brad Bishop64c979e2019-11-04 13:55:29 -05001956 if remove_work:
1957 logger.info('-r argument used on %s, removing source tree.'
1958 ' You will lose any unsaved work' %pn)
1959 shutil.rmtree(srctreebase)
1960 else:
1961 # We don't want to risk wiping out any work in progress
1962 logger.info('Leaving source tree %s as-is; if you no '
1963 'longer need it then please delete it manually'
1964 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001965 else:
1966 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04001967 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001968
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001969 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001970
1971def reset(args, config, basepath, workspace):
1972 """Entry point for the devtool 'reset' subcommand"""
1973 import bb
Brad Bishop64c979e2019-11-04 13:55:29 -05001974 import shutil
1975
1976 recipes = ""
1977
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001978 if args.recipename:
1979 if args.all:
1980 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1981 else:
1982 for recipe in args.recipename:
1983 check_workspace_recipe(workspace, recipe, checksrc=False)
1984 elif not args.all:
1985 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1986 "reset all recipes")
1987 if args.all:
1988 recipes = list(workspace.keys())
1989 else:
1990 recipes = args.recipename
1991
Brad Bishop64c979e2019-11-04 13:55:29 -05001992 _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001993
1994 return 0
1995
1996
1997def _get_layer(layername, d):
1998 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001999 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04002000 layers = {} # {basename: layer_paths}
2001 for p in layerdirs:
2002 bn = os.path.basename(p)
2003 if bn not in layers:
2004 layers[bn] = [p]
2005 else:
2006 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002007 # Provide some shortcuts
2008 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04002009 layername = 'meta'
2010 layer_paths = layers.get(layername, None)
2011 if not layer_paths:
2012 return os.path.abspath(layername)
2013 elif len(layer_paths) == 1:
2014 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002015 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04002016 # multiple layers having the same base name
2017 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
2018 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
2019 return os.path.abspath(layer_paths[0])
2020
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002021
2022def finish(args, config, basepath, workspace):
2023 """Entry point for the devtool 'finish' subcommand"""
2024 import bb
2025 import oe.recipeutils
2026
2027 check_workspace_recipe(workspace, args.recipename)
2028
Brad Bishop316dfdd2018-06-25 12:45:53 -04002029 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
2030
2031 # Grab the equivalent of COREBASE without having to initialise tinfoil
2032 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2033
2034 srctree = workspace[args.recipename]['srctree']
2035 check_git_repo_op(srctree, [corebasedir])
2036 dirty = check_git_repo_dirty(srctree)
2037 if dirty:
2038 if args.force:
2039 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2040 else:
2041 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)
2042
Brad Bishop00e122a2019-10-05 11:10:57 -04002043 no_clean = args.no_clean
Brad Bishop64c979e2019-11-04 13:55:29 -05002044 remove_work=args.remove_work
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002045 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2046 try:
Brad Bishop6dbb3162019-11-25 09:41:34 -05002047 rd = parse_recipe(config, tinfoil, args.recipename, True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002048 if not rd:
2049 return 1
2050
2051 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002052 recipefile = rd.getVar('FILE')
2053 recipedir = os.path.dirname(recipefile)
2054 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002055
2056 if not os.path.isdir(destlayerdir):
2057 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2058
2059 if os.path.abspath(destlayerdir) == config.workspace_path:
2060 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2061
2062 # If it's an upgrade, grab the original path
2063 origpath = None
2064 origfilelist = None
2065 append = workspace[args.recipename]['bbappend']
2066 with open(append, 'r') as f:
2067 for line in f:
2068 if line.startswith('# original_path:'):
2069 origpath = line.split(':')[1].strip()
2070 elif line.startswith('# original_files:'):
2071 origfilelist = line.split(':')[1].split()
2072
Brad Bishop316dfdd2018-06-25 12:45:53 -04002073 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2074
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002075 if origlayerdir == config.workspace_path:
2076 # Recipe file itself is in workspace, update it there first
2077 appendlayerdir = None
2078 origrelpath = None
2079 if origpath:
2080 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2081 if origlayerpath:
2082 origrelpath = os.path.relpath(origpath, origlayerpath)
2083 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2084 if not destpath:
2085 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 -05002086 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2087 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002088 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002089 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)
2090
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002091 elif destlayerdir == origlayerdir:
2092 # Same layer, update the original recipe
2093 appendlayerdir = None
2094 destpath = None
2095 else:
2096 # Create/update a bbappend in the specified layer
2097 appendlayerdir = destlayerdir
2098 destpath = None
2099
Brad Bishop316dfdd2018-06-25 12:45:53 -04002100 # Actually update the recipe / bbappend
2101 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2102 dry_run_output = None
2103 dry_run_outdir = None
2104 if args.dry_run:
2105 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2106 dry_run_outdir = dry_run_output.name
2107 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)
2108 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2109
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002110 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002111 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002112 for fn in origfilelist:
2113 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002114 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2115 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2116 if not args.dry_run:
2117 try:
2118 os.remove(fnp)
2119 except FileNotFoundError:
2120 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002121
2122 if origlayerdir == config.workspace_path and destpath:
2123 # Recipe file itself is in the workspace - need to move it and any
2124 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002125 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002126 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002127 for root, _, files in os.walk(recipedir):
2128 for fn in files:
2129 srcpath = os.path.join(root, fn)
2130 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2131 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002132 destfp = os.path.join(destdir, fn)
2133 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002134
Brad Bishop316dfdd2018-06-25 12:45:53 -04002135 if dry_run_outdir:
2136 import difflib
2137 comparelist = []
2138 for root, _, files in os.walk(dry_run_outdir):
2139 for fn in files:
2140 outf = os.path.join(root, fn)
2141 relf = os.path.relpath(outf, dry_run_outdir)
2142 logger.debug('dry-run: output file %s' % relf)
2143 if fn.endswith('.bb'):
2144 if origfilelist and origpath and destpath:
2145 # Need to match this up with the pre-upgrade recipe file
2146 for origf in origfilelist:
2147 if origf.endswith('.bb'):
2148 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2149 outf,
2150 os.path.abspath(os.path.join(destpath, relf))))
2151 break
2152 else:
2153 # Compare to the existing recipe
2154 comparelist.append((recipefile, outf, recipefile))
2155 elif fn.endswith('.bbappend'):
2156 if appendfile:
2157 if os.path.exists(appendfile):
2158 comparelist.append((appendfile, outf, appendfile))
2159 else:
2160 comparelist.append((None, outf, appendfile))
2161 else:
2162 if destpath:
2163 recipedest = destpath
2164 elif appendfile:
2165 recipedest = os.path.dirname(appendfile)
2166 else:
2167 recipedest = os.path.dirname(recipefile)
2168 destfp = os.path.join(recipedest, relf)
2169 if os.path.exists(destfp):
2170 comparelist.append((destfp, outf, destfp))
2171 output = ''
2172 for oldfile, newfile, newfileshow in comparelist:
2173 if oldfile:
2174 with open(oldfile, 'r') as f:
2175 oldlines = f.readlines()
2176 else:
2177 oldfile = '/dev/null'
2178 oldlines = []
2179 with open(newfile, 'r') as f:
2180 newlines = f.readlines()
2181 if not newfileshow:
2182 newfileshow = newfile
2183 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2184 difflines = list(diff)
2185 if difflines:
2186 output += ''.join(difflines)
2187 if output:
2188 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002189 finally:
2190 tinfoil.shutdown()
2191
2192 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002193 if args.dry_run:
2194 logger.info('Resetting recipe (dry-run)')
2195 else:
Brad Bishop64c979e2019-11-04 13:55:29 -05002196 _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002197
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002198 return 0
2199
2200
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002201def get_default_srctree(config, recipename=''):
2202 """Get the default srctree path"""
2203 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2204 if recipename:
2205 return os.path.join(srctreeparent, 'sources', recipename)
2206 else:
2207 return os.path.join(srctreeparent, 'sources')
2208
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002209def register_commands(subparsers, context):
2210 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002211
2212 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002213 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002214 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.',
2215 group='starting', order=100)
2216 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.')
2217 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2218 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 -05002219 group = parser_add.add_mutually_exclusive_group()
2220 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2221 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002222 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 -05002223 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002224 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002225 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 -05002226 group = parser_add.add_mutually_exclusive_group()
2227 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2228 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")
2229 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 -05002230 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')
2231 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')
2232 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002233 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2234 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 -06002235 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002236
2237 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002238 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.',
2239 group='starting', order=90)
2240 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2241 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 -05002242 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002243 group = parser_modify.add_mutually_exclusive_group()
2244 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2245 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 -05002246 group = parser_modify.add_mutually_exclusive_group()
2247 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2248 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002249 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 -04002250 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 -05002251 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002252 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002253
2254 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2255 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002256 group='advanced')
2257 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002258 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002259 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 -04002260 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 -05002261 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002262 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002263
2264 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2265 description='Synchronize the previously extracted source tree for an existing recipe',
2266 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2267 group='advanced')
2268 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2269 parser_sync.add_argument('srctree', help='Path to the source tree')
2270 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2271 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002272 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002273
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002274 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2275 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.',
2276 group='working', order=10)
2277 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2278 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2279 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)')
2280 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')
2281 parser_rename.set_defaults(func=rename)
2282
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002283 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002284 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.',
2285 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002286 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2287 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 -05002288 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002289 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2290 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')
2291 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 -04002292 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2293 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2294 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 -05002295 parser_update_recipe.set_defaults(func=update_recipe)
2296
2297 parser_status = subparsers.add_parser('status', help='Show workspace status',
2298 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002299 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002300 parser_status.set_defaults(func=status)
2301
2302 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002303 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 -05002304 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002305 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002306 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2307 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 -05002308 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 -05002309 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002310
2311 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002312 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 -06002313 group='working', order=-100)
2314 parser_finish.add_argument('recipename', help='Recipe to finish')
2315 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.')
2316 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')
2317 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002318 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 -05002319 parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace')
Brad Bishop00e122a2019-10-05 11:10:57 -04002320 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 -04002321 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2322 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2323 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 -06002324 parser_finish.set_defaults(func=finish)