blob: 261d642d4a5d8d9030c0c91756c027c60a8aedd6 [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)
477 os.symlink('oe-local-files/%s' % fn, destpth)
478 addfiles.append(os.path.join(relpth, fn))
479 if addfiles:
480 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
Brad Bishop79641f22019-09-10 07:20:22 -0400481 useroptions = []
482 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=rd)
483 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 -0400484
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500485
Brad Bishop316dfdd2018-06-25 12:45:53 -0400486def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500487 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500488 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500489 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400490 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500491
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500492 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500493
494 _check_compatible_recipe(pn, d)
495
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500496 if sync:
497 if not os.path.exists(srctree):
498 raise DevtoolError("output path %s does not exist" % srctree)
499 else:
500 if os.path.exists(srctree):
501 if not os.path.isdir(srctree):
502 raise DevtoolError("output path %s exists and is not a directory" %
503 srctree)
504 elif os.listdir(srctree):
505 raise DevtoolError("output path %s already exists and is "
506 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500507
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500508 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
509 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600510 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500511
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500512 if not sync:
513 # Prepare for shutil.move later on
514 bb.utils.mkdirhier(srctree)
515 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500516
Brad Bishop316dfdd2018-06-25 12:45:53 -0400517 extra_overrides = []
518 if not no_overrides:
519 history = d.varhistory.variable('SRC_URI')
520 for event in history:
521 if not 'flag' in event:
522 if event['op'].startswith(('_append[', '_prepend[')):
523 extra_overrides.append(event['op'].split('[')[1].split(']')[0])
Andrew Geissler99467da2019-02-25 18:54:23 -0600524 # We want to remove duplicate overrides. If a recipe had multiple
525 # SRC_URI_override += values it would cause mulitple instances of
526 # overrides. This doesn't play nicely with things like creating a
527 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
528 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400529 if extra_overrides:
530 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
531
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500532 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500533
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500534 recipefile = d.getVar('FILE')
535 appendfile = recipe_to_append(recipefile, config)
536 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
537
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500538 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
539 # directory so that:
540 # (a) we pick up all files that get unpacked to the WORKDIR, and
541 # (b) we don't disturb the existing build
542 # However, with recipe-specific sysroots the sysroots for the recipe
543 # will be prepared under WORKDIR, and if we used the system temporary
544 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
545 # our attempts to hardlink files into the recipe-specific sysroots
546 # will fail on systems where /tmp is a different filesystem, and it
547 # would have to fall back to copying the files which is a waste of
548 # time. Put the temp directory under the WORKDIR to prevent that from
549 # being a problem.
550 tempbasedir = d.getVar('WORKDIR')
551 bb.utils.mkdirhier(tempbasedir)
552 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500553 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500554 tinfoil.logger.setLevel(logging.WARNING)
555
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500556 # FIXME this results in a cache reload under control of tinfoil, which is fine
557 # except we don't get the knotty progress bar
558
559 if os.path.exists(appendfile):
560 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
561 shutil.copyfile(appendfile, appendbackup)
562 else:
563 appendbackup = None
564 bb.utils.mkdirhier(os.path.dirname(appendfile))
565 logger.debug('writing append file %s' % appendfile)
566 with open(appendfile, 'a') as f:
567 f.write('###--- _extract_source\n')
568 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
569 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
570 if not is_kernel_yocto:
571 f.write('PATCHTOOL = "git"\n')
572 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400573 if extra_overrides:
574 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500575 f.write('inherit devtool-source\n')
576 f.write('###--- _extract_source\n')
577
578 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
579
580 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
581 bb.utils.mkdirhier(sstate_manifests)
582 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
583 with open(preservestampfile, 'w') as f:
584 f.write(d.getVar('STAMP'))
585 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400586 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500587 # We need to generate the kernel config
588 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500589 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500590 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500592 # Run the fetch + unpack tasks
593 res = tinfoil.build_targets(pn,
594 task,
595 handle_events=True)
596 finally:
597 if os.path.exists(preservestampfile):
598 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500599
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500600 if not res:
601 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500602
Brad Bishop316dfdd2018-06-25 12:45:53 -0400603 try:
604 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
605 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500606
Brad Bishop316dfdd2018-06-25 12:45:53 -0400607 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
608 srcsubdir = f.read()
609 except FileNotFoundError as e:
610 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
611 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500612
Brad Bishop96ff1982019-08-19 13:50:42 -0400613 # Check if work-shared is empty, if yes
614 # find source and copy to work-shared
615 if is_kernel_yocto:
616 workshareddir = d.getVar('STAGING_KERNEL_DIR')
617 staging_kerVer = get_staging_kver(workshareddir)
618 kernelVersion = d.getVar('LINUX_VERSION')
619
620 # handle dangling symbolic link in work-shared:
621 if os.path.islink(workshareddir):
622 os.unlink(workshareddir)
623
624 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
625 shutil.rmtree(workshareddir)
626 oe.path.copyhardlinktree(srcsubdir,workshareddir)
627 elif not os.path.exists(workshareddir):
628 oe.path.copyhardlinktree(srcsubdir,workshareddir)
629
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500630 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
631 srctree_localdir = os.path.join(srctree, 'oe-local-files')
632
633 if sync:
634 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
635
636 # Move oe-local-files directory to srctree
637 # As the oe-local-files is not part of the constructed git tree,
638 # remove them directly during the synchrounizating might surprise
639 # the users. Instead, we move it to oe-local-files.bak and remind
640 # user in the log message.
641 if os.path.exists(srctree_localdir + '.bak'):
642 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
643
644 if os.path.exists(srctree_localdir):
645 logger.info('Backing up current local file directory %s' % srctree_localdir)
646 shutil.move(srctree_localdir, srctree_localdir + '.bak')
647
648 if os.path.exists(tempdir_localdir):
649 logger.info('Syncing local source files to srctree...')
650 shutil.copytree(tempdir_localdir, srctree_localdir)
651 else:
652 # Move oe-local-files directory to srctree
653 if os.path.exists(tempdir_localdir):
654 logger.info('Adding local source files to srctree...')
655 shutil.move(tempdir_localdir, srcsubdir)
656
657 shutil.move(srcsubdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400658 symlink_oelocal_files_srctree(d,srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500659
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500660 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500661 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500662 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500663
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500664 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500665 if appendbackup:
666 shutil.copyfile(appendbackup, appendfile)
667 elif os.path.exists(appendfile):
668 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500669 if keep_temp:
670 logger.info('Preserving temporary directory %s' % tempdir)
671 else:
672 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400673 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500674
675def _add_md5(config, recipename, filename):
676 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
677 import bb.utils
678
679 def addfile(fn):
680 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500681 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
682 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
683 f.seek(0, os.SEEK_SET)
684 if not md5_str in f.read():
685 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500686
687 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500688 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500689 for f in files:
690 addfile(os.path.join(root, f))
691 else:
692 addfile(filename)
693
694def _check_preserve(config, recipename):
695 """Check if a file was manually changed and needs to be saved in 'attic'
696 directory"""
697 import bb.utils
698 origfile = os.path.join(config.workspace_path, '.devtool_md5')
699 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500700 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500701 with open(origfile, 'r') as f:
702 with open(newfile, 'w') as tf:
703 for line in f.readlines():
704 splitline = line.rstrip().split('|')
705 if splitline[0] == recipename:
706 removefile = os.path.join(config.workspace_path, splitline[1])
707 try:
708 md5 = bb.utils.md5_file(removefile)
709 except IOError as err:
710 if err.errno == 2:
711 # File no longer exists, skip it
712 continue
713 else:
714 raise
715 if splitline[2] != md5:
716 bb.utils.mkdirhier(preservepath)
717 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800718 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500719 shutil.move(removefile, os.path.join(preservepath, preservefile))
720 else:
721 os.remove(removefile)
722 else:
723 tf.write(line)
724 os.rename(newfile, origfile)
725
Brad Bishop96ff1982019-08-19 13:50:42 -0400726def get_staging_kver(srcdir):
727 # Kernel version from work-shared
728 kerver = []
729 staging_kerVer=""
730 if os.path.exists(srcdir) and os.listdir(srcdir):
731 with open(os.path.join(srcdir,"Makefile")) as f:
732 version = [next(f) for x in range(5)][1:4]
733 for word in version:
734 kerver.append(word.split('= ')[1].split('\n')[0])
735 staging_kerVer = ".".join(kerver)
736 return staging_kerVer
737
738def get_staging_kbranch(srcdir):
739 staging_kbranch = ""
740 if os.path.exists(srcdir) and os.listdir(srcdir):
741 (branch, _) = bb.process.run('git branch | grep \* | cut -d \' \' -f2', cwd=srcdir)
742 staging_kbranch = "".join(branch.split('\n')[0])
743 return staging_kbranch
744
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500745def modify(args, config, basepath, workspace):
746 """Entry point for the devtool 'modify' subcommand"""
747 import bb
748 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400749 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400750 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751
752 if args.recipename in workspace:
753 raise DevtoolError("recipe %s is already in your workspace" %
754 args.recipename)
755
Brad Bishop316dfdd2018-06-25 12:45:53 -0400756 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600757 try:
758 rd = parse_recipe(config, tinfoil, args.recipename, True)
759 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500760 return 1
761
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500762 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600763 if pn != args.recipename:
764 logger.info('Mapping %s to %s' % (args.recipename, pn))
765 if pn in workspace:
766 raise DevtoolError("recipe %s is already in your workspace" %
767 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500768
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600769 if args.srctree:
770 srctree = os.path.abspath(args.srctree)
771 else:
772 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500773
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600774 if args.no_extract and not os.path.isdir(srctree):
775 raise DevtoolError("--no-extract specified and source path %s does "
776 "not exist or is not a directory" %
777 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600778
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500779 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600780 appendfile = recipe_to_append(recipefile, config, args.wildcard)
781 if os.path.exists(appendfile):
782 raise DevtoolError("Another variant of recipe %s is already in your "
783 "workspace (only one variant of a recipe can "
784 "currently be worked on at once)"
785 % pn)
786
787 _check_compatible_recipe(pn, rd)
788
789 initial_rev = None
790 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400791 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400792
793 if bb.data.inherits_class('kernel-yocto', rd):
794 # Current set kernel version
795 kernelVersion = rd.getVar('LINUX_VERSION')
796 srcdir = rd.getVar('STAGING_KERNEL_DIR')
797 kbranch = rd.getVar('KBRANCH')
798
799 staging_kerVer = get_staging_kver(srcdir)
800 staging_kbranch = get_staging_kbranch(srcdir)
801 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
802 oe.path.copyhardlinktree(srcdir,srctree)
803 workdir = rd.getVar('WORKDIR')
804 srcsubdir = rd.getVar('S')
805 localfilesdir = os.path.join(srctree,'oe-local-files')
806 # Move local source files into separate subdir
807 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
808 local_files = oe.recipeutils.get_recipe_local_files(rd)
809
810 for key in local_files.copy():
811 if key.endswith('scc'):
812 sccfile = open(local_files[key], 'r')
813 for l in sccfile:
814 line = l.split()
815 if line and line[0] in ('kconf', 'patch'):
816 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
817 if not cfg in local_files.values():
818 local_files[line[-1]] = cfg
819 shutil.copy2(cfg, workdir)
820 sccfile.close()
821
822 # Ignore local files with subdir={BP}
823 srcabspath = os.path.abspath(srcsubdir)
824 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))]
825 if local_files:
826 for fname in local_files:
827 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
828 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
829 f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n')
830
831 symlink_oelocal_files_srctree(rd,srctree)
832
833 task = 'do_configure'
834 res = tinfoil.build_targets(pn, task, handle_events=True)
835
836 # Copy .config to workspace
837 kconfpath = rd.getVar('B')
838 logger.info('Copying kernel config to workspace')
839 shutil.copy2(os.path.join(kconfpath, '.config'),srctree)
840
841 # Set this to true, we still need to get initial_rev
842 # by parsing the git repo
843 args.no_extract = True
844
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600845 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400846 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 -0500847 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600848 return 1
849 logger.info('Source tree extracted to %s' % srctree)
850 # Get list of commits since this revision
851 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
852 commits = stdout.split()
Brad Bishop316dfdd2018-06-25 12:45:53 -0400853 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600854 else:
855 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600856 # Check if it's a tree previously extracted by us. This is done
857 # by ensuring that devtool-base and args.branch (devtool) exist.
858 # The check_commits logic will cause an exception if either one
859 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600860 try:
861 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600862 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600863 except bb.process.ExecutionError:
864 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400865 if stdout:
866 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600867 for line in stdout.splitlines():
868 if line.startswith('*'):
869 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
870 initial_rev = stdout.rstrip()
871 if not initial_rev:
872 # Otherwise, just grab the head revision
873 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
874 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500875
Brad Bishop316dfdd2018-06-25 12:45:53 -0400876 branch_patches = {}
877 if check_commits:
878 # Check if there are override branches
879 (stdout, _) = bb.process.run('git branch', cwd=srctree)
880 branches = []
881 for line in stdout.rstrip().splitlines():
882 branchname = line[2:].rstrip()
883 if branchname.startswith(override_branch_prefix):
884 branches.append(branchname)
885 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800886 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 -0400887 branches.insert(0, args.branch)
888 seen_patches = []
889 for branch in branches:
890 branch_patches[branch] = []
891 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
892 for line in stdout.splitlines():
893 line = line.strip()
894 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
895 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
896 if not origpatch in seen_patches:
897 seen_patches.append(origpatch)
898 branch_patches[branch].append(origpatch)
899
900 # Need to grab this here in case the source is within a subdirectory
901 srctreebase = srctree
902
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600903 # Check that recipe isn't using a shared workdir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500904 s = os.path.abspath(rd.getVar('S'))
905 workdir = os.path.abspath(rd.getVar('WORKDIR'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600906 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
907 # Handle if S is set to a subdirectory of the source
908 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
909 srctree = os.path.join(srctree, srcsubdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500910
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600911 bb.utils.mkdirhier(os.path.dirname(appendfile))
912 with open(appendfile, 'w') as f:
913 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
914 # Local files can be modified/tracked in separate subdir under srctree
915 # Mostly useful for packages with S != WORKDIR
916 f.write('FILESPATH_prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400917 os.path.join(srctreebase, 'oe-local-files'))
918 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500919
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600920 f.write('\ninherit externalsrc\n')
921 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
922 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500923
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600924 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
925 if b_is_s:
926 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500927
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600928 if bb.data.inherits_class('kernel', rd):
929 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500930 'do_fetch do_unpack do_kernel_configme do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400931 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600932 f.write('\ndo_configure_append() {\n'
933 ' cp ${B}/.config ${S}/.config.baseline\n'
934 ' ln -sfT ${B}/.config ${S}/.config.new\n'
935 '}\n')
Brad Bishop96ff1982019-08-19 13:50:42 -0400936 if rd.getVarFlag('do_menuconfig','task'):
937 f.write('\ndo_configure_append() {\n'
Andrew Geissler82c905d2020-04-13 13:39:40 -0500938 ' if [ ! ${DEVTOOL_DISABLE_MENUCONFIG} ]; then\n'
939 ' cp ${B}/.config ${S}/.config.baseline\n'
940 ' ln -sfT ${B}/.config ${S}/.config.new\n'
941 ' fi\n'
Brad Bishop96ff1982019-08-19 13:50:42 -0400942 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600943 if initial_rev:
944 f.write('\n# initial_rev: %s\n' % initial_rev)
945 for commit in commits:
946 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400947 if branch_patches:
948 for branch in branch_patches:
949 if branch == args.branch:
950 continue
951 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500952
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500953 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
954
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600955 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500956
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600957 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500958
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600959 finally:
960 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500961
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500962 return 0
963
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500964
965def rename(args, config, basepath, workspace):
966 """Entry point for the devtool 'rename' subcommand"""
967 import bb
968 import oe.recipeutils
969
970 check_workspace_recipe(workspace, args.recipename)
971
972 if not (args.newname or args.version):
973 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
974
975 recipefile = workspace[args.recipename]['recipefile']
976 if not recipefile:
977 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
978
979 if args.newname and args.newname != args.recipename:
980 reason = oe.recipeutils.validate_pn(args.newname)
981 if reason:
982 raise DevtoolError(reason)
983 newname = args.newname
984 else:
985 newname = args.recipename
986
987 append = workspace[args.recipename]['bbappend']
988 appendfn = os.path.splitext(os.path.basename(append))[0]
989 splitfn = appendfn.split('_')
990 if len(splitfn) > 1:
991 origfnver = appendfn.split('_')[1]
992 else:
993 origfnver = ''
994
995 recipefilemd5 = None
996 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
997 try:
998 rd = parse_recipe(config, tinfoil, args.recipename, True)
999 if not rd:
1000 return 1
1001
1002 bp = rd.getVar('BP')
1003 bpn = rd.getVar('BPN')
1004 if newname != args.recipename:
1005 localdata = rd.createCopy()
1006 localdata.setVar('PN', newname)
1007 newbpn = localdata.getVar('BPN')
1008 else:
1009 newbpn = bpn
1010 s = rd.getVar('S', False)
1011 src_uri = rd.getVar('SRC_URI', False)
1012 pv = rd.getVar('PV')
1013
1014 # Correct variable values that refer to the upstream source - these
1015 # values must stay the same, so if the name/version are changing then
1016 # we need to fix them up
1017 new_s = s
1018 new_src_uri = src_uri
1019 if newbpn != bpn:
1020 # ${PN} here is technically almost always incorrect, but people do use it
1021 new_s = new_s.replace('${BPN}', bpn)
1022 new_s = new_s.replace('${PN}', bpn)
1023 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1024 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1025 new_src_uri = new_src_uri.replace('${PN}', bpn)
1026 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1027 if args.version and origfnver == pv:
1028 new_s = new_s.replace('${PV}', pv)
1029 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1030 new_src_uri = new_src_uri.replace('${PV}', pv)
1031 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1032 patchfields = {}
1033 if new_s != s:
1034 patchfields['S'] = new_s
1035 if new_src_uri != src_uri:
1036 patchfields['SRC_URI'] = new_src_uri
1037 if patchfields:
1038 recipefilemd5 = bb.utils.md5_file(recipefile)
1039 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1040 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1041 finally:
1042 tinfoil.shutdown()
1043
1044 if args.version:
1045 newver = args.version
1046 else:
1047 newver = origfnver
1048
1049 if newver:
1050 newappend = '%s_%s.bbappend' % (newname, newver)
1051 newfile = '%s_%s.bb' % (newname, newver)
1052 else:
1053 newappend = '%s.bbappend' % newname
1054 newfile = '%s.bb' % newname
1055
1056 oldrecipedir = os.path.dirname(recipefile)
1057 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1058 if oldrecipedir != newrecipedir:
1059 bb.utils.mkdirhier(newrecipedir)
1060
1061 newappend = os.path.join(os.path.dirname(append), newappend)
1062 newfile = os.path.join(newrecipedir, newfile)
1063
1064 # Rename bbappend
1065 logger.info('Renaming %s to %s' % (append, newappend))
1066 os.rename(append, newappend)
1067 # Rename recipe file
1068 logger.info('Renaming %s to %s' % (recipefile, newfile))
1069 os.rename(recipefile, newfile)
1070
1071 # Rename source tree if it's the default path
1072 appendmd5 = None
1073 if not args.no_srctree:
1074 srctree = workspace[args.recipename]['srctree']
1075 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1076 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1077 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1078 shutil.move(srctree, newsrctree)
1079 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1080 appendmd5 = bb.utils.md5_file(newappend)
1081 appendlines = []
1082 with open(newappend, 'r') as f:
1083 for line in f:
1084 appendlines.append(line)
1085 with open(newappend, 'w') as f:
1086 for line in appendlines:
1087 if srctree in line:
1088 line = line.replace(srctree, newsrctree)
1089 f.write(line)
1090 newappendmd5 = bb.utils.md5_file(newappend)
1091
1092 bpndir = None
1093 newbpndir = None
1094 if newbpn != bpn:
1095 bpndir = os.path.join(oldrecipedir, bpn)
1096 if os.path.exists(bpndir):
1097 newbpndir = os.path.join(newrecipedir, newbpn)
1098 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1099 shutil.move(bpndir, newbpndir)
1100
1101 bpdir = None
1102 newbpdir = None
1103 if newver != origfnver or newbpn != bpn:
1104 bpdir = os.path.join(oldrecipedir, bp)
1105 if os.path.exists(bpdir):
1106 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1107 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1108 shutil.move(bpdir, newbpdir)
1109
1110 if oldrecipedir != newrecipedir:
1111 # Move any stray files and delete the old recipe directory
1112 for entry in os.listdir(oldrecipedir):
1113 oldpath = os.path.join(oldrecipedir, entry)
1114 newpath = os.path.join(newrecipedir, entry)
1115 logger.info('Renaming %s to %s' % (oldpath, newpath))
1116 shutil.move(oldpath, newpath)
1117 os.rmdir(oldrecipedir)
1118
1119 # Now take care of entries in .devtool_md5
1120 md5entries = []
1121 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1122 for line in f:
1123 md5entries.append(line)
1124
1125 if bpndir and newbpndir:
1126 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1127 else:
1128 relbpndir = None
1129 if bpdir and newbpdir:
1130 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1131 else:
1132 relbpdir = None
1133
1134 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1135 for entry in md5entries:
1136 splitentry = entry.rstrip().split('|')
1137 if len(splitentry) > 2:
1138 if splitentry[0] == args.recipename:
1139 splitentry[0] = newname
1140 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1141 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1142 if appendmd5 and splitentry[2] == appendmd5:
1143 splitentry[2] = newappendmd5
1144 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1145 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1146 if recipefilemd5 and splitentry[2] == recipefilemd5:
1147 splitentry[2] = newrecipefilemd5
1148 elif relbpndir and splitentry[1].startswith(relbpndir):
1149 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1150 elif relbpdir and splitentry[1].startswith(relbpdir):
1151 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1152 entry = '|'.join(splitentry) + '\n'
1153 f.write(entry)
1154 return 0
1155
1156
Brad Bishop316dfdd2018-06-25 12:45:53 -04001157def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001158 """Get initial and update rev of a recipe. These are the start point of the
1159 whole patchset and start point for the patches to be re-generated/updated.
1160 """
1161 import bb
1162
Brad Bishop316dfdd2018-06-25 12:45:53 -04001163 # Get current branch
1164 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1165 cwd=srctree)
1166 branchname = stdout.rstrip()
1167
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001168 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001169 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001170 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001171 with open(recipe_path, 'r') as f:
1172 for line in f:
1173 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001174 if not initial_rev:
1175 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001176 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001177 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001178 elif line.startswith('# patches_%s:' % branchname):
1179 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001180
1181 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001182 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001183 if initial_rev:
1184 # Find first actually changed revision
1185 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1186 initial_rev, cwd=srctree)
1187 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001188 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001189 if newcommits[i] == commits[i]:
1190 update_rev = commits[i]
1191
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001192 try:
1193 stdout, _ = bb.process.run('git cherry devtool-patched',
1194 cwd=srctree)
1195 except bb.process.ExecutionError as err:
1196 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001197
Brad Bishop316dfdd2018-06-25 12:45:53 -04001198 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001199 changed_revs = []
1200 for line in stdout.splitlines():
1201 if line.startswith('+ '):
1202 rev = line.split()[1]
1203 if rev in newcommits:
1204 changed_revs.append(rev)
1205
Brad Bishop316dfdd2018-06-25 12:45:53 -04001206 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001207
1208def _remove_file_entries(srcuri, filelist):
1209 """Remove file:// entries from SRC_URI"""
1210 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001211 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001212 for fname in filelist:
1213 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001214 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001215 if (srcuri[i].startswith('file://') and
1216 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001217 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001218 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001219 srcuri.pop(i)
1220 break
1221 return entries, remaining
1222
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001223def _replace_srcuri_entry(srcuri, filename, newentry):
1224 """Replace entry corresponding to specified file with a new entry"""
1225 basename = os.path.basename(filename)
1226 for i in range(len(srcuri)):
1227 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1228 srcuri.pop(i)
1229 srcuri.insert(i, newentry)
1230 break
1231
Brad Bishop316dfdd2018-06-25 12:45:53 -04001232def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001233 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001234
1235 dry_run_suffix = ' (dry-run)' if dry_run else ''
1236
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001237 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001238 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001239 if not destpath:
1240 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001241 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001242
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001243 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001244 if not no_report_remove:
1245 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1246 if not dry_run:
1247 # FIXME "git rm" here would be nice if the file in question is
1248 # tracked
1249 # FIXME there's a chance that this file is referred to by
1250 # another recipe, in which case deleting wouldn't be the
1251 # right thing to do
1252 os.remove(path)
1253 # Remove directory if empty
1254 try:
1255 os.rmdir(os.path.dirname(path))
1256 except OSError as ose:
1257 if ose.errno != errno.ENOTEMPTY:
1258 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001259
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001260
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001261def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001262 """Export patches from srctree to given location.
1263 Returns three-tuple of dicts:
1264 1. updated - patches that already exist in SRCURI
1265 2. added - new patches that don't exist in SRCURI
1266 3 removed - patches that exist in SRCURI but not in exported patches
1267 In each dict the key is the 'basepath' of the URI and value is the
1268 absolute path to the existing file in recipe space (if any).
1269 """
1270 import oe.recipeutils
1271 from oe.patch import GitApplyTree
1272 updated = OrderedDict()
1273 added = OrderedDict()
1274 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1275
1276 existing_patches = dict((os.path.basename(path), path) for path in
1277 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001278 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001279
1280 # Generate patches from Git, exclude local files directory
1281 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1282 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1283
1284 new_patches = sorted(os.listdir(destdir))
1285 for new_patch in new_patches:
1286 # Strip numbering from patch names. If it's a git sequence named patch,
1287 # the numbers might not match up since we are starting from a different
1288 # revision This does assume that people are using unique shortlog
1289 # values, but they ought to be anyway...
1290 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001291 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001292 for old_patch in existing_patches:
1293 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001294 old_basename_splitext = os.path.splitext(old_basename)
1295 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1296 old_patch_noext = os.path.splitext(old_patch)[0]
1297 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001298 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001299 elif new_basename == old_basename:
1300 match_name = old_patch
1301 break
1302 if match_name:
1303 # Rename patch files
1304 if new_patch != match_name:
1305 os.rename(os.path.join(destdir, new_patch),
1306 os.path.join(destdir, match_name))
1307 # Need to pop it off the list now before checking changed_revs
1308 oldpath = existing_patches.pop(old_patch)
1309 if changed_revs is not None:
1310 # Avoid updating patches that have not actually changed
1311 with open(os.path.join(destdir, match_name), 'r') as f:
1312 firstlineitems = f.readline().split()
1313 # Looking for "From <hash>" line
1314 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1315 if not firstlineitems[1] in changed_revs:
1316 continue
1317 # Recompress if necessary
1318 if oldpath.endswith(('.gz', '.Z')):
1319 bb.process.run(['gzip', match_name], cwd=destdir)
1320 if oldpath.endswith('.gz'):
1321 match_name += '.gz'
1322 else:
1323 match_name += '.Z'
1324 elif oldpath.endswith('.bz2'):
1325 bb.process.run(['bzip2', match_name], cwd=destdir)
1326 match_name += '.bz2'
1327 updated[match_name] = oldpath
1328 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001329 added[new_patch] = None
1330 return (updated, added, existing_patches)
1331
1332
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001333def _create_kconfig_diff(srctree, rd, outfile):
1334 """Create a kconfig fragment"""
1335 # Only update config fragment if both config files exist
1336 orig_config = os.path.join(srctree, '.config.baseline')
1337 new_config = os.path.join(srctree, '.config.new')
1338 if os.path.exists(orig_config) and os.path.exists(new_config):
1339 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1340 '--unchanged-line-format=', orig_config, new_config]
1341 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1342 stderr=subprocess.PIPE)
1343 stdout, stderr = pipe.communicate()
1344 if pipe.returncode == 1:
1345 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001346 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001347 fobj.write(stdout)
1348 elif pipe.returncode == 0:
1349 logger.info("Would remove config fragment %s" % outfile)
1350 if os.path.exists(outfile):
1351 # Remove fragment file in case of empty diff
1352 logger.info("Removing config fragment %s" % outfile)
1353 os.unlink(outfile)
1354 else:
1355 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1356 return True
1357 return False
1358
1359
Brad Bishop316dfdd2018-06-25 12:45:53 -04001360def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001361 """Copy local files from srctree to given location.
1362 Returns three-tuple of dicts:
1363 1. updated - files that already exist in SRCURI
1364 2. added - new files files that don't exist in SRCURI
1365 3 removed - files that exist in SRCURI but not in exported files
1366 In each dict the key is the 'basepath' of the URI and value is the
1367 absolute path to the existing file in recipe space (if any).
1368 """
1369 import oe.recipeutils
1370
1371 # Find out local files (SRC_URI files that exist in the "recipe space").
1372 # Local files that reside in srctree are not included in patch generation.
1373 # Instead they are directly copied over the original source files (in
1374 # recipe space).
1375 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1376 new_set = None
1377 updated = OrderedDict()
1378 added = OrderedDict()
1379 removed = OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001380 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001381 git_files = _git_ls_tree(srctree)
1382 if 'oe-local-files' in git_files:
1383 # If tracked by Git, take the files from srctree HEAD. First get
1384 # the tree object of the directory
1385 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1386 tree = git_files['oe-local-files'][2]
1387 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1388 env=dict(os.environ, GIT_WORK_TREE=destdir,
1389 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001390 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001391 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001392 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001393 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001394 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001395 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001396 else:
1397 new_set = []
1398
1399 # Special handling for kernel config
1400 if bb.data.inherits_class('kernel-yocto', rd):
1401 fragment_fn = 'devtool-fragment.cfg'
1402 fragment_path = os.path.join(destdir, fragment_fn)
1403 if _create_kconfig_diff(srctree, rd, fragment_path):
1404 if os.path.exists(fragment_path):
1405 if fragment_fn not in new_set:
1406 new_set.append(fragment_fn)
1407 # Copy fragment to local-files
1408 if os.path.isdir(local_files_dir):
1409 shutil.copy2(fragment_path, local_files_dir)
1410 else:
1411 if fragment_fn in new_set:
1412 new_set.remove(fragment_fn)
1413 # Remove fragment from local-files
1414 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1415 os.unlink(os.path.join(local_files_dir, fragment_fn))
1416
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001417 # Special handling for cml1, ccmake, etc bbclasses that generated
1418 # configuration fragment files that are consumed as source files
1419 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1420 if bb.data.inherits_class(frag_class, rd):
1421 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1422 if os.path.exists(srcpath):
1423 if frag_name not in new_set:
1424 new_set.append(frag_name)
1425 # copy fragment into destdir
1426 shutil.copy2(srcpath, destdir)
1427 # copy fragment into local files if exists
1428 if os.path.isdir(local_files_dir):
1429 shutil.copy2(srcpath, local_files_dir)
1430
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001431 if new_set is not None:
1432 for fname in new_set:
1433 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001434 origpath = existing_files.pop(fname)
1435 workpath = os.path.join(local_files_dir, fname)
1436 if not filecmp.cmp(origpath, workpath):
1437 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001438 elif fname != '.gitignore':
1439 added[fname] = None
1440
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001441 workdir = rd.getVar('WORKDIR')
1442 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001443 if not s.endswith(os.sep):
1444 s += os.sep
1445
1446 if workdir != s:
1447 # Handle files where subdir= was specified
1448 for fname in list(existing_files.keys()):
1449 # FIXME handle both subdir starting with BP and not?
1450 fworkpath = os.path.join(workdir, fname)
1451 if fworkpath.startswith(s):
1452 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1453 if os.path.exists(fpath):
1454 origpath = existing_files.pop(fname)
1455 if not filecmp.cmp(origpath, fpath):
1456 updated[fpath] = origpath
1457
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001458 removed = existing_files
1459 return (updated, added, removed)
1460
1461
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001462def _determine_files_dir(rd):
1463 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001464 recipedir = rd.getVar('FILE_DIRNAME')
1465 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001466 relpth = os.path.relpath(entry, recipedir)
1467 if not os.sep in relpth:
1468 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1469 if os.path.isdir(entry):
1470 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001471 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001472
1473
Brad Bishop316dfdd2018-06-25 12:45:53 -04001474def _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 -05001475 """Implement the 'srcrev' mode of update-recipe"""
1476 import bb
1477 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001478
Brad Bishop316dfdd2018-06-25 12:45:53 -04001479 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1480
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001481 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001482 recipedir = os.path.basename(recipefile)
1483 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001484
1485 # Get HEAD revision
1486 try:
1487 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1488 except bb.process.ExecutionError as err:
1489 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1490 (srctree, err))
1491 srcrev = stdout.strip()
1492 if len(srcrev) != 40:
1493 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1494
1495 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001496 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001497 patchfields = {}
1498 patchfields['SRCREV'] = srcrev
1499 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001500 srcuri = orig_src_uri.split()
1501 tempdir = tempfile.mkdtemp(prefix='devtool')
1502 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001503 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001504 try:
1505 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001506 srctreebase = workspace[recipename]['srctreebase']
1507 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001508 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001509 # Find list of existing patches in recipe file
1510 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001511 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001512 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1513 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001514 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 -05001515
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001516 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001517 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001518 if remove_files:
1519 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1520 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001521
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001522 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001523 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001524 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001525 removevalues = {}
1526 if update_srcuri:
1527 removevalues = {'SRC_URI': removedentries}
1528 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001529 if dry_run_outdir:
1530 logger.info('Creating bbappend (dry-run)')
1531 else:
1532 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1533 rd, appendlayerdir, files, wildcardver=wildcard_version,
1534 extralines=patchfields, removevalues=removevalues,
1535 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001536 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001537 files_dir = _determine_files_dir(rd)
1538 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001539 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001540 if os.path.isabs(basepath):
1541 # Original file (probably with subdir pointing inside source tree)
1542 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001543 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001544 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001545 _move_file(os.path.join(local_files_dir, basepath), path,
1546 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001547 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001548 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001549 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001550 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001551 os.path.join(files_dir, basepath),
1552 dry_run_outdir=dry_run_outdir,
1553 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001554 srcuri.append('file://%s' % basepath)
1555 update_srcuri = True
1556 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001557 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001558 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001559 finally:
1560 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001561 if not 'git://' in orig_src_uri:
1562 logger.info('You will need to update SRC_URI within the recipe to '
1563 'point to a git repository where you have pushed your '
1564 'changes')
1565
Brad Bishop316dfdd2018-06-25 12:45:53 -04001566 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1567 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001568
Brad Bishop316dfdd2018-06-25 12:45:53 -04001569def _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 -05001570 """Implement the 'patch' mode of update-recipe"""
1571 import bb
1572 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001573
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001574 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001575 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001576 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001577 if not os.path.exists(append):
1578 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001579 recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001580
Brad Bishop316dfdd2018-06-25 12:45:53 -04001581 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 -05001582 if not initial_rev:
1583 raise DevtoolError('Unable to find initial revision - please specify '
1584 'it with --initial-rev')
1585
Brad Bishop316dfdd2018-06-25 12:45:53 -04001586 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001587 dl_dir = rd.getVar('DL_DIR')
1588 if not dl_dir.endswith('/'):
1589 dl_dir += '/'
1590
Brad Bishop316dfdd2018-06-25 12:45:53 -04001591 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1592
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001593 tempdir = tempfile.mkdtemp(prefix='devtool')
1594 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001595 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001596 if filter_patches:
1597 upd_f = {}
1598 new_f = {}
1599 del_f = {}
1600 else:
1601 srctreebase = workspace[recipename]['srctreebase']
1602 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001603
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001604 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001605 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001606 # Get all patches from source tree and check if any should be removed
1607 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001608 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1609 all_patches_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001610 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001611 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001612
1613 # Get updated patches from source tree
1614 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001615 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1616 patches_dir, changed_revs)
1617 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1618 if filter_patches:
Brad Bishop00e122a2019-10-05 11:10:57 -04001619 new_p = OrderedDict()
1620 upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001621 remove_files = [f for f in remove_files if f in filter_patches]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001622 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001623 updaterecipe = False
1624 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001625 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001626 if appendlayerdir:
Brad Bishop00e122a2019-10-05 11:10:57 -04001627 files = OrderedDict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001628 key, val in list(upd_f.items()) + list(new_f.items()))
Brad Bishop00e122a2019-10-05 11:10:57 -04001629 files.update(OrderedDict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001630 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001631 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001632 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001633 if remove_files:
1634 removedentries, remaining = _remove_file_entries(
1635 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001636 if removedentries or remaining:
1637 remaining = ['file://' + os.path.basename(item) for
1638 item in remaining]
1639 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001640 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001641 rd, appendlayerdir, files,
1642 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001643 removevalues=removevalues,
1644 redirect_output=dry_run_outdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001645 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001646 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001647 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001648 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001649 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001650 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001651 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001652 if os.path.isabs(basepath):
1653 # Original file (probably with subdir pointing inside source tree)
1654 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001655 _copy_file(basepath, path,
1656 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001657 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001658 _move_file(os.path.join(local_files_dir, basepath), path,
1659 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001660 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001661 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001662 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001663 if os.path.dirname(path) + '/' == dl_dir:
1664 # This is a a downloaded patch file - we now need to
1665 # replace the entry in SRC_URI with our local version
1666 logger.info('Replacing remote patch %s with updated local version' % basepath)
1667 path = os.path.join(files_dir, basepath)
1668 _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath)
1669 updaterecipe = True
1670 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001671 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1672 _move_file(patchfn, path,
1673 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001674 updatefiles = True
1675 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001676 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001677 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001678 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001679 os.path.join(files_dir, basepath),
1680 dry_run_outdir=dry_run_outdir,
1681 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001682 srcuri.append('file://%s' % basepath)
1683 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001684 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001685 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001686 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001687 os.path.join(files_dir, basepath),
1688 dry_run_outdir=dry_run_outdir,
1689 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001690 srcuri.append('file://%s' % basepath)
1691 updaterecipe = True
1692 # Update recipe, if needed
1693 if _remove_file_entries(srcuri, remove_files)[0]:
1694 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001695 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001696 if not dry_run_outdir:
1697 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1698 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1699 {'SRC_URI': ' '.join(srcuri)},
1700 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001701 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001702 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001703 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001704 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001705 finally:
1706 shutil.rmtree(tempdir)
1707
Brad Bishop316dfdd2018-06-25 12:45:53 -04001708 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1709 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001710
1711def _guess_recipe_update_mode(srctree, rdata):
1712 """Guess the recipe update mode to use"""
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001713 src_uri = (rdata.getVar('SRC_URI') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001714 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1715 if not git_uris:
1716 return 'patch'
1717 # Just use the first URI for now
1718 uri = git_uris[0]
1719 # Check remote branch
1720 params = bb.fetch.decodeurl(uri)[5]
1721 upstr_branch = params['branch'] if 'branch' in params else 'master'
1722 # Check if current branch HEAD is found in upstream branch
1723 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1724 head_rev = stdout.rstrip()
1725 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1726 cwd=srctree)
1727 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1728 if 'origin/' + upstr_branch in remote_brs:
1729 return 'srcrev'
1730
1731 return 'patch'
1732
Brad Bishop316dfdd2018-06-25 12:45:53 -04001733def _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 -06001734 srctree = workspace[recipename]['srctree']
1735 if mode == 'auto':
1736 mode = _guess_recipe_update_mode(srctree, rd)
1737
Brad Bishop316dfdd2018-06-25 12:45:53 -04001738 override_branches = []
1739 mainbranch = None
1740 startbranch = None
1741 if not no_overrides:
1742 stdout, _ = bb.process.run('git branch', cwd=srctree)
1743 other_branches = []
1744 for line in stdout.splitlines():
1745 branchname = line[2:]
1746 if line.startswith('* '):
1747 startbranch = branchname
1748 if branchname.startswith(override_branch_prefix):
1749 override_branches.append(branchname)
1750 else:
1751 other_branches.append(branchname)
1752
1753 if override_branches:
1754 logger.debug('_update_recipe: override branches: %s' % override_branches)
1755 logger.debug('_update_recipe: other branches: %s' % other_branches)
1756 if startbranch.startswith(override_branch_prefix):
1757 if len(other_branches) == 1:
1758 mainbranch = other_branches[1]
1759 else:
1760 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1761 else:
1762 mainbranch = startbranch
1763
1764 checkedout = None
1765 anyupdated = False
1766 appendfile = None
1767 allremoved = []
1768 if override_branches:
1769 logger.info('Handling main branch (%s)...' % mainbranch)
1770 if startbranch != mainbranch:
1771 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1772 checkedout = mainbranch
1773 try:
1774 branchlist = [mainbranch] + override_branches
1775 for branch in branchlist:
1776 crd = bb.data.createCopy(rd)
1777 if branch != mainbranch:
1778 logger.info('Handling branch %s...' % branch)
1779 override = branch[len(override_branch_prefix):]
1780 crd.appendVar('OVERRIDES', ':%s' % override)
1781 bb.process.run('git checkout %s' % branch, cwd=srctree)
1782 checkedout = branch
1783
1784 if mode == 'srcrev':
1785 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1786 elif mode == 'patch':
1787 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)
1788 else:
1789 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1790 if updated:
1791 anyupdated = True
1792 if appendf:
1793 appendfile = appendf
1794 allremoved.extend(removed)
1795 finally:
1796 if startbranch and checkedout != startbranch:
1797 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1798
1799 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001800
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001801def update_recipe(args, config, basepath, workspace):
1802 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001803 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001804
1805 if args.append:
1806 if not os.path.exists(args.append):
1807 raise DevtoolError('bbappend destination layer directory "%s" '
1808 'does not exist' % args.append)
1809 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1810 raise DevtoolError('conf/layer.conf not found in bbappend '
1811 'destination layer "%s"' % args.append)
1812
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001813 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001814 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001815
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001816 rd = parse_recipe(config, tinfoil, args.recipename, True)
1817 if not rd:
1818 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001819
Brad Bishop316dfdd2018-06-25 12:45:53 -04001820 dry_run_output = None
1821 dry_run_outdir = None
1822 if args.dry_run:
1823 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1824 dry_run_outdir = dry_run_output.name
1825 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 -05001826
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001827 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001828 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001829 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001830 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 -06001831 finally:
1832 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001833
1834 return 0
1835
1836
1837def status(args, config, basepath, workspace):
1838 """Entry point for the devtool 'status' subcommand"""
1839 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001840 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001841 recipefile = value['recipefile']
1842 if recipefile:
1843 recipestr = ' (%s)' % recipefile
1844 else:
1845 recipestr = ''
1846 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001847 else:
1848 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')
1849 return 0
1850
1851
Brad Bishop64c979e2019-11-04 13:55:29 -05001852def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001853 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001854 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001855
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001856 def clean_preferred_provider(pn, layerconf_path):
1857 """Remove PREFERRED_PROVIDER from layer.conf'"""
1858 import re
1859 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1860 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1861 pprovider_found = False
1862 with open(layerconf_file, 'r') as f:
1863 lines = f.readlines()
1864 with open(new_layerconf_file, 'a') as nf:
1865 for line in lines:
1866 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1867 if not re.match(pprovider_exp, line):
1868 nf.write(line)
1869 else:
1870 pprovider_found = True
1871 if pprovider_found:
1872 shutil.move(new_layerconf_file, layerconf_file)
1873 else:
1874 os.remove(new_layerconf_file)
1875
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001876 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001877 if len(recipes) == 1:
1878 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1879 else:
1880 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001881 # If the recipe file itself was created in the workspace, and
1882 # it uses BBCLASSEXTEND, then we need to also clean the other
1883 # variants
1884 targets = []
1885 for recipe in recipes:
1886 targets.append(recipe)
1887 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001888 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001889 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001890 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001891 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001892 except bb.process.ExecutionError as e:
1893 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1894 'wish, you may specify -n/--no-clean to '
1895 'skip running this command when resetting' %
1896 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001897
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001898 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001899 _check_preserve(config, pn)
1900
Brad Bishop316dfdd2018-06-25 12:45:53 -04001901 appendfile = workspace[pn]['bbappend']
1902 if os.path.exists(appendfile):
1903 # This shouldn't happen, but is possible if devtool errored out prior to
1904 # writing the md5 file. We need to delete this here or the recipe won't
1905 # actually be reset
1906 os.remove(appendfile)
1907
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001908 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001909 def preservedir(origdir):
1910 if os.path.exists(origdir):
1911 for root, dirs, files in os.walk(origdir):
1912 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001913 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001914 _move_file(os.path.join(origdir, fn),
1915 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001916 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001917 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001918 os.rmdir(origdir)
1919
Brad Bishop316dfdd2018-06-25 12:45:53 -04001920 recipefile = workspace[pn]['recipefile']
1921 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1922 # This should always be true if recipefile is set, but just in case
1923 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001924 # We don't automatically create this dir next to appends, but the user can
1925 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1926
Brad Bishop316dfdd2018-06-25 12:45:53 -04001927 srctreebase = workspace[pn]['srctreebase']
1928 if os.path.isdir(srctreebase):
1929 if os.listdir(srctreebase):
Brad Bishop64c979e2019-11-04 13:55:29 -05001930 if remove_work:
1931 logger.info('-r argument used on %s, removing source tree.'
1932 ' You will lose any unsaved work' %pn)
1933 shutil.rmtree(srctreebase)
1934 else:
1935 # We don't want to risk wiping out any work in progress
1936 logger.info('Leaving source tree %s as-is; if you no '
1937 'longer need it then please delete it manually'
1938 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001939 else:
1940 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04001941 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001942
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001943 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001944
1945def reset(args, config, basepath, workspace):
1946 """Entry point for the devtool 'reset' subcommand"""
1947 import bb
Brad Bishop64c979e2019-11-04 13:55:29 -05001948 import shutil
1949
1950 recipes = ""
1951
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001952 if args.recipename:
1953 if args.all:
1954 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1955 else:
1956 for recipe in args.recipename:
1957 check_workspace_recipe(workspace, recipe, checksrc=False)
1958 elif not args.all:
1959 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1960 "reset all recipes")
1961 if args.all:
1962 recipes = list(workspace.keys())
1963 else:
1964 recipes = args.recipename
1965
Brad Bishop64c979e2019-11-04 13:55:29 -05001966 _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001967
1968 return 0
1969
1970
1971def _get_layer(layername, d):
1972 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001973 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04001974 layers = {} # {basename: layer_paths}
1975 for p in layerdirs:
1976 bn = os.path.basename(p)
1977 if bn not in layers:
1978 layers[bn] = [p]
1979 else:
1980 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001981 # Provide some shortcuts
1982 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04001983 layername = 'meta'
1984 layer_paths = layers.get(layername, None)
1985 if not layer_paths:
1986 return os.path.abspath(layername)
1987 elif len(layer_paths) == 1:
1988 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001989 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04001990 # multiple layers having the same base name
1991 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
1992 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
1993 return os.path.abspath(layer_paths[0])
1994
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001995
1996def finish(args, config, basepath, workspace):
1997 """Entry point for the devtool 'finish' subcommand"""
1998 import bb
1999 import oe.recipeutils
2000
2001 check_workspace_recipe(workspace, args.recipename)
2002
Brad Bishop316dfdd2018-06-25 12:45:53 -04002003 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
2004
2005 # Grab the equivalent of COREBASE without having to initialise tinfoil
2006 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2007
2008 srctree = workspace[args.recipename]['srctree']
2009 check_git_repo_op(srctree, [corebasedir])
2010 dirty = check_git_repo_dirty(srctree)
2011 if dirty:
2012 if args.force:
2013 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2014 else:
2015 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)
2016
Brad Bishop00e122a2019-10-05 11:10:57 -04002017 no_clean = args.no_clean
Brad Bishop64c979e2019-11-04 13:55:29 -05002018 remove_work=args.remove_work
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002019 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2020 try:
Brad Bishop6dbb3162019-11-25 09:41:34 -05002021 rd = parse_recipe(config, tinfoil, args.recipename, True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002022 if not rd:
2023 return 1
2024
2025 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002026 recipefile = rd.getVar('FILE')
2027 recipedir = os.path.dirname(recipefile)
2028 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002029
2030 if not os.path.isdir(destlayerdir):
2031 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2032
2033 if os.path.abspath(destlayerdir) == config.workspace_path:
2034 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2035
2036 # If it's an upgrade, grab the original path
2037 origpath = None
2038 origfilelist = None
2039 append = workspace[args.recipename]['bbappend']
2040 with open(append, 'r') as f:
2041 for line in f:
2042 if line.startswith('# original_path:'):
2043 origpath = line.split(':')[1].strip()
2044 elif line.startswith('# original_files:'):
2045 origfilelist = line.split(':')[1].split()
2046
Brad Bishop316dfdd2018-06-25 12:45:53 -04002047 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2048
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002049 if origlayerdir == config.workspace_path:
2050 # Recipe file itself is in workspace, update it there first
2051 appendlayerdir = None
2052 origrelpath = None
2053 if origpath:
2054 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2055 if origlayerpath:
2056 origrelpath = os.path.relpath(origpath, origlayerpath)
2057 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2058 if not destpath:
2059 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 -05002060 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2061 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002062 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002063 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)
2064
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002065 elif destlayerdir == origlayerdir:
2066 # Same layer, update the original recipe
2067 appendlayerdir = None
2068 destpath = None
2069 else:
2070 # Create/update a bbappend in the specified layer
2071 appendlayerdir = destlayerdir
2072 destpath = None
2073
Brad Bishop316dfdd2018-06-25 12:45:53 -04002074 # Actually update the recipe / bbappend
2075 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2076 dry_run_output = None
2077 dry_run_outdir = None
2078 if args.dry_run:
2079 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2080 dry_run_outdir = dry_run_output.name
2081 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)
2082 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2083
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002084 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002085 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002086 for fn in origfilelist:
2087 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002088 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2089 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2090 if not args.dry_run:
2091 try:
2092 os.remove(fnp)
2093 except FileNotFoundError:
2094 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002095
2096 if origlayerdir == config.workspace_path and destpath:
2097 # Recipe file itself is in the workspace - need to move it and any
2098 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002099 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002100 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002101 for root, _, files in os.walk(recipedir):
2102 for fn in files:
2103 srcpath = os.path.join(root, fn)
2104 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2105 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002106 destfp = os.path.join(destdir, fn)
2107 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002108
Brad Bishop316dfdd2018-06-25 12:45:53 -04002109 if dry_run_outdir:
2110 import difflib
2111 comparelist = []
2112 for root, _, files in os.walk(dry_run_outdir):
2113 for fn in files:
2114 outf = os.path.join(root, fn)
2115 relf = os.path.relpath(outf, dry_run_outdir)
2116 logger.debug('dry-run: output file %s' % relf)
2117 if fn.endswith('.bb'):
2118 if origfilelist and origpath and destpath:
2119 # Need to match this up with the pre-upgrade recipe file
2120 for origf in origfilelist:
2121 if origf.endswith('.bb'):
2122 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2123 outf,
2124 os.path.abspath(os.path.join(destpath, relf))))
2125 break
2126 else:
2127 # Compare to the existing recipe
2128 comparelist.append((recipefile, outf, recipefile))
2129 elif fn.endswith('.bbappend'):
2130 if appendfile:
2131 if os.path.exists(appendfile):
2132 comparelist.append((appendfile, outf, appendfile))
2133 else:
2134 comparelist.append((None, outf, appendfile))
2135 else:
2136 if destpath:
2137 recipedest = destpath
2138 elif appendfile:
2139 recipedest = os.path.dirname(appendfile)
2140 else:
2141 recipedest = os.path.dirname(recipefile)
2142 destfp = os.path.join(recipedest, relf)
2143 if os.path.exists(destfp):
2144 comparelist.append((destfp, outf, destfp))
2145 output = ''
2146 for oldfile, newfile, newfileshow in comparelist:
2147 if oldfile:
2148 with open(oldfile, 'r') as f:
2149 oldlines = f.readlines()
2150 else:
2151 oldfile = '/dev/null'
2152 oldlines = []
2153 with open(newfile, 'r') as f:
2154 newlines = f.readlines()
2155 if not newfileshow:
2156 newfileshow = newfile
2157 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2158 difflines = list(diff)
2159 if difflines:
2160 output += ''.join(difflines)
2161 if output:
2162 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002163 finally:
2164 tinfoil.shutdown()
2165
2166 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002167 if args.dry_run:
2168 logger.info('Resetting recipe (dry-run)')
2169 else:
Brad Bishop64c979e2019-11-04 13:55:29 -05002170 _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002171
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002172 return 0
2173
2174
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002175def get_default_srctree(config, recipename=''):
2176 """Get the default srctree path"""
2177 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2178 if recipename:
2179 return os.path.join(srctreeparent, 'sources', recipename)
2180 else:
2181 return os.path.join(srctreeparent, 'sources')
2182
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002183def register_commands(subparsers, context):
2184 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002185
2186 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002187 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002188 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.',
2189 group='starting', order=100)
2190 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.')
2191 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2192 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 -05002193 group = parser_add.add_mutually_exclusive_group()
2194 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2195 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002196 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 -05002197 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002198 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002199 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 -05002200 group = parser_add.add_mutually_exclusive_group()
2201 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2202 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")
2203 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 -05002204 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')
2205 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')
2206 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002207 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2208 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 -06002209 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002210
2211 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002212 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.',
2213 group='starting', order=90)
2214 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2215 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 -05002216 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002217 group = parser_modify.add_mutually_exclusive_group()
2218 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2219 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 -05002220 group = parser_modify.add_mutually_exclusive_group()
2221 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2222 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002223 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 -04002224 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 -05002225 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002226 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002227
2228 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2229 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002230 group='advanced')
2231 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002232 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002233 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 -04002234 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 -05002235 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002236 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002237
2238 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2239 description='Synchronize the previously extracted source tree for an existing recipe',
2240 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2241 group='advanced')
2242 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2243 parser_sync.add_argument('srctree', help='Path to the source tree')
2244 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2245 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002246 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002247
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002248 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2249 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.',
2250 group='working', order=10)
2251 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2252 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2253 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)')
2254 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')
2255 parser_rename.set_defaults(func=rename)
2256
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002257 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002258 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.',
2259 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002260 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2261 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 -05002262 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002263 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2264 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')
2265 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 -04002266 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2267 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2268 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 -05002269 parser_update_recipe.set_defaults(func=update_recipe)
2270
2271 parser_status = subparsers.add_parser('status', help='Show workspace status',
2272 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002273 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002274 parser_status.set_defaults(func=status)
2275
2276 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002277 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 -05002278 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002279 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002280 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2281 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 -05002282 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 -05002283 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002284
2285 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002286 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 -06002287 group='working', order=-100)
2288 parser_finish.add_argument('recipename', help='Recipe to finish')
2289 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.')
2290 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')
2291 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002292 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 -05002293 parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace')
Brad Bishop00e122a2019-10-05 11:10:57 -04002294 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 -04002295 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2296 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2297 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 -06002298 parser_finish.set_defaults(func=finish)