blob: d53fb8100716bfdb1a43014e4a40a7aa1b88a300 [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:
Patrick Williams213cb262021-08-07 19:21:33 -0500257 f.write('do_install:append() {\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600258 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):
Patrick Williams213cb262021-08-07 19:21:33 -0500263 f.write('python do_configure:append() {\n')
Andrew Geissler82c905d2020-04-13 13:39:40 -0500264 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
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500321 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600322 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323 raise DevtoolError("externalsrc is currently enabled for the %s "
324 "recipe. This prevents the normal do_patch task "
325 "from working. You will need to disable this "
326 "first." % pn)
327
Brad Bishop316dfdd2018-06-25 12:45:53 -0400328def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
329 """Common function for copying a file to the dry run output directory"""
330 relpath = os.path.relpath(dst, base_outdir)
331 if relpath.startswith('..'):
332 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
333 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500334 dst_d = os.path.dirname(dst)
335 if dst_d:
336 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400337 # Don't overwrite existing files, otherwise in the case of an upgrade
338 # the dry-run written out recipe will be overwritten with an unmodified
339 # version
340 if not os.path.exists(dst):
341 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500342
Brad Bishop316dfdd2018-06-25 12:45:53 -0400343def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
344 """Move a file. Creates all the directory components of destination path."""
345 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
346 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
347 if dry_run_outdir:
348 # We want to copy here, not move
349 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
350 else:
351 dst_d = os.path.dirname(dst)
352 if dst_d:
353 bb.utils.mkdirhier(dst_d)
354 shutil.move(src, dst)
355
Andrew Geissler78b72792022-06-14 06:47:25 -0500356def _copy_file(src, dst, dry_run_outdir=None, base_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600357 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400358 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
359 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
360 if dry_run_outdir:
361 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
362 else:
363 dst_d = os.path.dirname(dst)
364 if dst_d:
365 bb.utils.mkdirhier(dst_d)
366 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600367
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500368def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
369 """List contents of a git treeish"""
370 import bb
371 cmd = ['git', 'ls-tree', '-z', treeish]
372 if recursive:
373 cmd.append('-r')
374 out, _ = bb.process.run(cmd, cwd=repodir)
375 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500376 if out:
377 for line in out.split('\0'):
378 if line:
379 split = line.split(None, 4)
380 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500381 return ret
382
383def _git_exclude_path(srctree, path):
384 """Return pathspec (list of paths) that excludes certain path"""
385 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
386 # we don't catch files that are deleted, for example. A more reliable way
387 # to implement this would be to use "negative pathspecs" which were
388 # introduced in Git v1.9.0. Revisit this when/if the required Git version
389 # becomes greater than that.
390 path = os.path.normpath(path)
391 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600392 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500393 if path in git_files:
394 git_files.remove(path)
395 return git_files
396 else:
397 return ['.']
398
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500399def _ls_tree(directory):
400 """Recursive listing of files in a directory"""
401 ret = []
402 for root, dirs, files in os.walk(directory):
403 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
404 fname in files])
405 return ret
406
407
408def extract(args, config, basepath, workspace):
409 """Entry point for the devtool 'extract' subcommand"""
410 import bb
411
Brad Bishop316dfdd2018-06-25 12:45:53 -0400412 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500413 if not tinfoil:
414 # Error already shown
415 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600416 try:
417 rd = parse_recipe(config, tinfoil, args.recipename, True)
418 if not rd:
419 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500420
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600421 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400422 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 -0600423 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500424
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600425 if initial_rev:
426 return 0
427 else:
428 return 1
429 finally:
430 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500431
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500432def sync(args, config, basepath, workspace):
433 """Entry point for the devtool 'sync' subcommand"""
434 import bb
435
Brad Bishop316dfdd2018-06-25 12:45:53 -0400436 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500437 if not tinfoil:
438 # Error already shown
439 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600440 try:
441 rd = parse_recipe(config, tinfoil, args.recipename, True)
442 if not rd:
443 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500444
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600445 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400446 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 -0600447 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500448
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600449 if initial_rev:
450 return 0
451 else:
452 return 1
453 finally:
454 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500455
Brad Bishop96ff1982019-08-19 13:50:42 -0400456def symlink_oelocal_files_srctree(rd,srctree):
457 import oe.patch
458 if os.path.abspath(rd.getVar('S')) == os.path.abspath(rd.getVar('WORKDIR')):
459 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
460 # (otherwise the recipe won't build as expected)
461 local_files_dir = os.path.join(srctree, 'oe-local-files')
462 addfiles = []
463 for root, _, files in os.walk(local_files_dir):
464 relpth = os.path.relpath(root, local_files_dir)
465 if relpth != '.':
466 bb.utils.mkdirhier(os.path.join(srctree, relpth))
467 for fn in files:
468 if fn == '.gitignore':
469 continue
470 destpth = os.path.join(srctree, relpth, fn)
471 if os.path.exists(destpth):
472 os.unlink(destpth)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600473 if relpth != '.':
474 back_relpth = os.path.relpath(local_files_dir, root)
475 os.symlink('%s/oe-local-files/%s/%s' % (back_relpth, relpth, fn), destpth)
476 else:
477 os.symlink('oe-local-files/%s' % fn, destpth)
Brad Bishop96ff1982019-08-19 13:50:42 -0400478 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:
Patrick Williams213cb262021-08-07 19:21:33 -0500522 if event['op'].startswith((':append[', ':prepend[')):
Andrew Geissler615f2f12022-07-15 14:00:58 -0500523 override = event['op'].split('[')[1].split(']')[0]
524 if not override.startswith('pn-'):
525 extra_overrides.append(override)
Andrew Geissler99467da2019-02-25 18:54:23 -0600526 # We want to remove duplicate overrides. If a recipe had multiple
527 # SRC_URI_override += values it would cause mulitple instances of
528 # overrides. This doesn't play nicely with things like creating a
529 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
530 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400531 if extra_overrides:
532 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
533
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500534 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500535
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500536 recipefile = d.getVar('FILE')
537 appendfile = recipe_to_append(recipefile, config)
538 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
539
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500540 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
541 # directory so that:
542 # (a) we pick up all files that get unpacked to the WORKDIR, and
543 # (b) we don't disturb the existing build
544 # However, with recipe-specific sysroots the sysroots for the recipe
545 # will be prepared under WORKDIR, and if we used the system temporary
546 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
547 # our attempts to hardlink files into the recipe-specific sysroots
548 # will fail on systems where /tmp is a different filesystem, and it
549 # would have to fall back to copying the files which is a waste of
550 # time. Put the temp directory under the WORKDIR to prevent that from
551 # being a problem.
552 tempbasedir = d.getVar('WORKDIR')
553 bb.utils.mkdirhier(tempbasedir)
554 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500555 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500556 tinfoil.logger.setLevel(logging.WARNING)
557
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500558 # FIXME this results in a cache reload under control of tinfoil, which is fine
559 # except we don't get the knotty progress bar
560
561 if os.path.exists(appendfile):
562 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
563 shutil.copyfile(appendfile, appendbackup)
564 else:
565 appendbackup = None
566 bb.utils.mkdirhier(os.path.dirname(appendfile))
567 logger.debug('writing append file %s' % appendfile)
568 with open(appendfile, 'a') as f:
569 f.write('###--- _extract_source\n')
Patrick Williams2a254922023-08-11 09:48:11 -0500570 f.write('deltask do_recipe_qa\n')
571 f.write('deltask do_recipe_qa_setscene\n')
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600572 f.write('ERROR_QA:remove = "patch-fuzz"\n')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500573 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
574 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
575 if not is_kernel_yocto:
576 f.write('PATCHTOOL = "git"\n')
577 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400578 if extra_overrides:
579 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500580 f.write('inherit devtool-source\n')
581 f.write('###--- _extract_source\n')
582
583 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
584
585 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
586 bb.utils.mkdirhier(sstate_manifests)
587 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
588 with open(preservestampfile, 'w') as f:
589 f.write(d.getVar('STAMP'))
Andrew Geissler220dafd2023-10-04 10:18:08 -0500590 tinfoil.modified_files()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500591 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400592 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500593 # We need to generate the kernel config
594 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500595 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500596 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500597
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600598 if 'noexec' in (d.getVarFlags(task, False) or []) or 'task' not in (d.getVarFlags(task, False) or []):
599 logger.info('The %s recipe has %s disabled. Running only '
600 'do_configure task dependencies' % (pn, task))
601
602 if 'depends' in d.getVarFlags('do_configure', False):
603 pn = d.getVarFlags('do_configure', False)['depends']
604 pn = pn.replace('${PV}', d.getVar('PV'))
605 pn = pn.replace('${COMPILERDEP}', d.getVar('COMPILERDEP'))
606 task = None
607
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500608 # Run the fetch + unpack tasks
609 res = tinfoil.build_targets(pn,
610 task,
611 handle_events=True)
612 finally:
613 if os.path.exists(preservestampfile):
614 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500615
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500616 if not res:
617 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500618
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600619 if not is_kernel_yocto and ('noexec' in (d.getVarFlags('do_patch', False) or []) or 'task' not in (d.getVarFlags('do_patch', False) or [])):
620 workshareddir = d.getVar('S')
621 if os.path.islink(srctree):
622 os.unlink(srctree)
623
624 os.symlink(workshareddir, srctree)
625
626 # The initial_rev file is created in devtool_post_unpack function that will not be executed if
627 # do_unpack/do_patch tasks are disabled so we have to directly say that source extraction was successful
628 return True, True
629
Brad Bishop316dfdd2018-06-25 12:45:53 -0400630 try:
631 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
632 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500633
Brad Bishop316dfdd2018-06-25 12:45:53 -0400634 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
635 srcsubdir = f.read()
636 except FileNotFoundError as e:
637 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
638 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500639
Brad Bishop96ff1982019-08-19 13:50:42 -0400640 # Check if work-shared is empty, if yes
641 # find source and copy to work-shared
642 if is_kernel_yocto:
643 workshareddir = d.getVar('STAGING_KERNEL_DIR')
644 staging_kerVer = get_staging_kver(workshareddir)
645 kernelVersion = d.getVar('LINUX_VERSION')
646
647 # handle dangling symbolic link in work-shared:
648 if os.path.islink(workshareddir):
649 os.unlink(workshareddir)
650
651 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
652 shutil.rmtree(workshareddir)
653 oe.path.copyhardlinktree(srcsubdir,workshareddir)
654 elif not os.path.exists(workshareddir):
655 oe.path.copyhardlinktree(srcsubdir,workshareddir)
656
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500657 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
658 srctree_localdir = os.path.join(srctree, 'oe-local-files')
659
660 if sync:
661 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
662
663 # Move oe-local-files directory to srctree
664 # As the oe-local-files is not part of the constructed git tree,
665 # remove them directly during the synchrounizating might surprise
666 # the users. Instead, we move it to oe-local-files.bak and remind
667 # user in the log message.
668 if os.path.exists(srctree_localdir + '.bak'):
669 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
670
671 if os.path.exists(srctree_localdir):
672 logger.info('Backing up current local file directory %s' % srctree_localdir)
673 shutil.move(srctree_localdir, srctree_localdir + '.bak')
674
675 if os.path.exists(tempdir_localdir):
676 logger.info('Syncing local source files to srctree...')
677 shutil.copytree(tempdir_localdir, srctree_localdir)
678 else:
679 # Move oe-local-files directory to srctree
680 if os.path.exists(tempdir_localdir):
681 logger.info('Adding local source files to srctree...')
682 shutil.move(tempdir_localdir, srcsubdir)
683
684 shutil.move(srcsubdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400685 symlink_oelocal_files_srctree(d,srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500686
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500687 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500688 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500689 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500690
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500691 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500692 if appendbackup:
693 shutil.copyfile(appendbackup, appendfile)
694 elif os.path.exists(appendfile):
695 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500696 if keep_temp:
697 logger.info('Preserving temporary directory %s' % tempdir)
698 else:
699 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400700 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500701
702def _add_md5(config, recipename, filename):
703 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
704 import bb.utils
705
706 def addfile(fn):
707 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500708 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
709 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
710 f.seek(0, os.SEEK_SET)
711 if not md5_str in f.read():
712 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500713
714 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500715 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500716 for f in files:
717 addfile(os.path.join(root, f))
718 else:
719 addfile(filename)
720
721def _check_preserve(config, recipename):
722 """Check if a file was manually changed and needs to be saved in 'attic'
723 directory"""
724 import bb.utils
725 origfile = os.path.join(config.workspace_path, '.devtool_md5')
726 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500727 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500728 with open(origfile, 'r') as f:
729 with open(newfile, 'w') as tf:
730 for line in f.readlines():
731 splitline = line.rstrip().split('|')
732 if splitline[0] == recipename:
733 removefile = os.path.join(config.workspace_path, splitline[1])
734 try:
735 md5 = bb.utils.md5_file(removefile)
736 except IOError as err:
737 if err.errno == 2:
738 # File no longer exists, skip it
739 continue
740 else:
741 raise
742 if splitline[2] != md5:
743 bb.utils.mkdirhier(preservepath)
744 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800745 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500746 shutil.move(removefile, os.path.join(preservepath, preservefile))
747 else:
748 os.remove(removefile)
749 else:
750 tf.write(line)
Andrew Geisslerc926e172021-05-07 16:11:35 -0500751 bb.utils.rename(newfile, origfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500752
Brad Bishop96ff1982019-08-19 13:50:42 -0400753def get_staging_kver(srcdir):
754 # Kernel version from work-shared
755 kerver = []
756 staging_kerVer=""
757 if os.path.exists(srcdir) and os.listdir(srcdir):
758 with open(os.path.join(srcdir,"Makefile")) as f:
759 version = [next(f) for x in range(5)][1:4]
760 for word in version:
761 kerver.append(word.split('= ')[1].split('\n')[0])
762 staging_kerVer = ".".join(kerver)
763 return staging_kerVer
764
765def get_staging_kbranch(srcdir):
766 staging_kbranch = ""
767 if os.path.exists(srcdir) and os.listdir(srcdir):
768 (branch, _) = bb.process.run('git branch | grep \* | cut -d \' \' -f2', cwd=srcdir)
769 staging_kbranch = "".join(branch.split('\n')[0])
770 return staging_kbranch
771
Andrew Geissler517393d2023-01-13 08:55:19 -0600772def get_real_srctree(srctree, s, workdir):
773 # Check that recipe isn't using a shared workdir
774 s = os.path.abspath(s)
775 workdir = os.path.abspath(workdir)
776 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
777 # Handle if S is set to a subdirectory of the source
778 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
779 srctree = os.path.join(srctree, srcsubdir)
780 return srctree
781
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500782def modify(args, config, basepath, workspace):
783 """Entry point for the devtool 'modify' subcommand"""
784 import bb
785 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400786 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400787 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500788
789 if args.recipename in workspace:
790 raise DevtoolError("recipe %s is already in your workspace" %
791 args.recipename)
792
Brad Bishop316dfdd2018-06-25 12:45:53 -0400793 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600794 try:
795 rd = parse_recipe(config, tinfoil, args.recipename, True)
796 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500797 return 1
798
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500799 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600800 if pn != args.recipename:
801 logger.info('Mapping %s to %s' % (args.recipename, pn))
802 if pn in workspace:
803 raise DevtoolError("recipe %s is already in your workspace" %
804 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500805
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600806 if args.srctree:
807 srctree = os.path.abspath(args.srctree)
808 else:
809 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500810
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600811 if args.no_extract and not os.path.isdir(srctree):
812 raise DevtoolError("--no-extract specified and source path %s does "
813 "not exist or is not a directory" %
814 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600815
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500816 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600817 appendfile = recipe_to_append(recipefile, config, args.wildcard)
818 if os.path.exists(appendfile):
819 raise DevtoolError("Another variant of recipe %s is already in your "
820 "workspace (only one variant of a recipe can "
821 "currently be worked on at once)"
822 % pn)
823
824 _check_compatible_recipe(pn, rd)
825
826 initial_rev = None
827 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400828 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400829
830 if bb.data.inherits_class('kernel-yocto', rd):
831 # Current set kernel version
832 kernelVersion = rd.getVar('LINUX_VERSION')
833 srcdir = rd.getVar('STAGING_KERNEL_DIR')
834 kbranch = rd.getVar('KBRANCH')
835
836 staging_kerVer = get_staging_kver(srcdir)
837 staging_kbranch = get_staging_kbranch(srcdir)
838 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
839 oe.path.copyhardlinktree(srcdir,srctree)
840 workdir = rd.getVar('WORKDIR')
841 srcsubdir = rd.getVar('S')
842 localfilesdir = os.path.join(srctree,'oe-local-files')
843 # Move local source files into separate subdir
844 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
845 local_files = oe.recipeutils.get_recipe_local_files(rd)
846
847 for key in local_files.copy():
848 if key.endswith('scc'):
849 sccfile = open(local_files[key], 'r')
850 for l in sccfile:
851 line = l.split()
852 if line and line[0] in ('kconf', 'patch'):
853 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
854 if not cfg in local_files.values():
855 local_files[line[-1]] = cfg
856 shutil.copy2(cfg, workdir)
857 sccfile.close()
858
859 # Ignore local files with subdir={BP}
860 srcabspath = os.path.abspath(srcsubdir)
861 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))]
862 if local_files:
863 for fname in local_files:
864 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
865 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
866 f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n')
867
868 symlink_oelocal_files_srctree(rd,srctree)
869
870 task = 'do_configure'
871 res = tinfoil.build_targets(pn, task, handle_events=True)
872
873 # Copy .config to workspace
874 kconfpath = rd.getVar('B')
875 logger.info('Copying kernel config to workspace')
876 shutil.copy2(os.path.join(kconfpath, '.config'),srctree)
877
878 # Set this to true, we still need to get initial_rev
879 # by parsing the git repo
880 args.no_extract = True
881
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600882 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400883 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 -0500884 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600885 return 1
886 logger.info('Source tree extracted to %s' % srctree)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600887 if os.path.exists(os.path.join(srctree, '.git')):
888 # Get list of commits since this revision
889 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
890 commits = stdout.split()
891 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600892 else:
893 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600894 # Check if it's a tree previously extracted by us. This is done
895 # by ensuring that devtool-base and args.branch (devtool) exist.
896 # The check_commits logic will cause an exception if either one
897 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600898 try:
899 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600900 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600901 except bb.process.ExecutionError:
902 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400903 if stdout:
904 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600905 for line in stdout.splitlines():
906 if line.startswith('*'):
907 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
908 initial_rev = stdout.rstrip()
909 if not initial_rev:
910 # Otherwise, just grab the head revision
911 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
912 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500913
Brad Bishop316dfdd2018-06-25 12:45:53 -0400914 branch_patches = {}
915 if check_commits:
916 # Check if there are override branches
917 (stdout, _) = bb.process.run('git branch', cwd=srctree)
918 branches = []
919 for line in stdout.rstrip().splitlines():
920 branchname = line[2:].rstrip()
921 if branchname.startswith(override_branch_prefix):
922 branches.append(branchname)
923 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800924 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 -0400925 branches.insert(0, args.branch)
926 seen_patches = []
927 for branch in branches:
928 branch_patches[branch] = []
929 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
930 for line in stdout.splitlines():
931 line = line.strip()
932 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
933 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
934 if not origpatch in seen_patches:
935 seen_patches.append(origpatch)
936 branch_patches[branch].append(origpatch)
937
938 # Need to grab this here in case the source is within a subdirectory
939 srctreebase = srctree
Andrew Geissler517393d2023-01-13 08:55:19 -0600940 srctree = get_real_srctree(srctree, rd.getVar('S'), rd.getVar('WORKDIR'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500941
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600942 bb.utils.mkdirhier(os.path.dirname(appendfile))
943 with open(appendfile, 'w') as f:
Patrick Williams213cb262021-08-07 19:21:33 -0500944 f.write('FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600945 # Local files can be modified/tracked in separate subdir under srctree
946 # Mostly useful for packages with S != WORKDIR
Patrick Williams213cb262021-08-07 19:21:33 -0500947 f.write('FILESPATH:prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400948 os.path.join(srctreebase, 'oe-local-files'))
949 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500950
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600951 f.write('\ninherit externalsrc\n')
952 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500953 f.write('EXTERNALSRC:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500954
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600955 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
956 if b_is_s:
Patrick Williams213cb262021-08-07 19:21:33 -0500957 f.write('EXTERNALSRC_BUILD:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500958
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600959 if bb.data.inherits_class('kernel', rd):
960 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500961 'do_fetch do_unpack do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400962 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500963 f.write('\ndo_configure:append() {\n'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600964 ' cp ${B}/.config ${S}/.config.baseline\n'
965 ' ln -sfT ${B}/.config ${S}/.config.new\n'
966 '}\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500967 f.write('\ndo_kernel_configme:prepend() {\n'
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500968 ' if [ -e ${S}/.config ]; then\n'
969 ' mv ${S}/.config ${S}/.config.old\n'
970 ' fi\n'
971 '}\n')
Brad Bishop96ff1982019-08-19 13:50:42 -0400972 if rd.getVarFlag('do_menuconfig','task'):
Patrick Williams213cb262021-08-07 19:21:33 -0500973 f.write('\ndo_configure:append() {\n'
Patrick Williams520786c2023-06-25 16:20:36 -0500974 ' if [ ${@ oe.types.boolean(\'${KCONFIG_CONFIG_ENABLE_MENUCONFIG}\') } = True ]; then\n'
975 ' cp ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.baseline\n'
976 ' ln -sfT ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.new\n'
Andrew Geissler82c905d2020-04-13 13:39:40 -0500977 ' fi\n'
Brad Bishop96ff1982019-08-19 13:50:42 -0400978 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600979 if initial_rev:
980 f.write('\n# initial_rev: %s\n' % initial_rev)
981 for commit in commits:
982 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400983 if branch_patches:
984 for branch in branch_patches:
985 if branch == args.branch:
986 continue
987 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500988
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500989 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
990
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600991 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500992
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600993 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500994
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600995 finally:
996 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500997
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500998 return 0
999
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001000
1001def rename(args, config, basepath, workspace):
1002 """Entry point for the devtool 'rename' subcommand"""
1003 import bb
1004 import oe.recipeutils
1005
1006 check_workspace_recipe(workspace, args.recipename)
1007
1008 if not (args.newname or args.version):
1009 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
1010
1011 recipefile = workspace[args.recipename]['recipefile']
1012 if not recipefile:
1013 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
1014
1015 if args.newname and args.newname != args.recipename:
1016 reason = oe.recipeutils.validate_pn(args.newname)
1017 if reason:
1018 raise DevtoolError(reason)
1019 newname = args.newname
1020 else:
1021 newname = args.recipename
1022
1023 append = workspace[args.recipename]['bbappend']
1024 appendfn = os.path.splitext(os.path.basename(append))[0]
1025 splitfn = appendfn.split('_')
1026 if len(splitfn) > 1:
1027 origfnver = appendfn.split('_')[1]
1028 else:
1029 origfnver = ''
1030
1031 recipefilemd5 = None
1032 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1033 try:
1034 rd = parse_recipe(config, tinfoil, args.recipename, True)
1035 if not rd:
1036 return 1
1037
1038 bp = rd.getVar('BP')
1039 bpn = rd.getVar('BPN')
1040 if newname != args.recipename:
1041 localdata = rd.createCopy()
1042 localdata.setVar('PN', newname)
1043 newbpn = localdata.getVar('BPN')
1044 else:
1045 newbpn = bpn
1046 s = rd.getVar('S', False)
1047 src_uri = rd.getVar('SRC_URI', False)
1048 pv = rd.getVar('PV')
1049
1050 # Correct variable values that refer to the upstream source - these
1051 # values must stay the same, so if the name/version are changing then
1052 # we need to fix them up
1053 new_s = s
1054 new_src_uri = src_uri
1055 if newbpn != bpn:
1056 # ${PN} here is technically almost always incorrect, but people do use it
1057 new_s = new_s.replace('${BPN}', bpn)
1058 new_s = new_s.replace('${PN}', bpn)
1059 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1060 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1061 new_src_uri = new_src_uri.replace('${PN}', bpn)
1062 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1063 if args.version and origfnver == pv:
1064 new_s = new_s.replace('${PV}', pv)
1065 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1066 new_src_uri = new_src_uri.replace('${PV}', pv)
1067 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1068 patchfields = {}
1069 if new_s != s:
1070 patchfields['S'] = new_s
1071 if new_src_uri != src_uri:
1072 patchfields['SRC_URI'] = new_src_uri
1073 if patchfields:
1074 recipefilemd5 = bb.utils.md5_file(recipefile)
1075 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1076 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1077 finally:
1078 tinfoil.shutdown()
1079
1080 if args.version:
1081 newver = args.version
1082 else:
1083 newver = origfnver
1084
1085 if newver:
1086 newappend = '%s_%s.bbappend' % (newname, newver)
1087 newfile = '%s_%s.bb' % (newname, newver)
1088 else:
1089 newappend = '%s.bbappend' % newname
1090 newfile = '%s.bb' % newname
1091
1092 oldrecipedir = os.path.dirname(recipefile)
1093 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1094 if oldrecipedir != newrecipedir:
1095 bb.utils.mkdirhier(newrecipedir)
1096
1097 newappend = os.path.join(os.path.dirname(append), newappend)
1098 newfile = os.path.join(newrecipedir, newfile)
1099
1100 # Rename bbappend
1101 logger.info('Renaming %s to %s' % (append, newappend))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001102 bb.utils.rename(append, newappend)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001103 # Rename recipe file
1104 logger.info('Renaming %s to %s' % (recipefile, newfile))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001105 bb.utils.rename(recipefile, newfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001106
1107 # Rename source tree if it's the default path
1108 appendmd5 = None
1109 if not args.no_srctree:
1110 srctree = workspace[args.recipename]['srctree']
1111 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1112 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1113 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1114 shutil.move(srctree, newsrctree)
1115 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1116 appendmd5 = bb.utils.md5_file(newappend)
1117 appendlines = []
1118 with open(newappend, 'r') as f:
1119 for line in f:
1120 appendlines.append(line)
1121 with open(newappend, 'w') as f:
1122 for line in appendlines:
1123 if srctree in line:
1124 line = line.replace(srctree, newsrctree)
1125 f.write(line)
1126 newappendmd5 = bb.utils.md5_file(newappend)
1127
1128 bpndir = None
1129 newbpndir = None
1130 if newbpn != bpn:
1131 bpndir = os.path.join(oldrecipedir, bpn)
1132 if os.path.exists(bpndir):
1133 newbpndir = os.path.join(newrecipedir, newbpn)
1134 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1135 shutil.move(bpndir, newbpndir)
1136
1137 bpdir = None
1138 newbpdir = None
1139 if newver != origfnver or newbpn != bpn:
1140 bpdir = os.path.join(oldrecipedir, bp)
1141 if os.path.exists(bpdir):
1142 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1143 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1144 shutil.move(bpdir, newbpdir)
1145
1146 if oldrecipedir != newrecipedir:
1147 # Move any stray files and delete the old recipe directory
1148 for entry in os.listdir(oldrecipedir):
1149 oldpath = os.path.join(oldrecipedir, entry)
1150 newpath = os.path.join(newrecipedir, entry)
1151 logger.info('Renaming %s to %s' % (oldpath, newpath))
1152 shutil.move(oldpath, newpath)
1153 os.rmdir(oldrecipedir)
1154
1155 # Now take care of entries in .devtool_md5
1156 md5entries = []
1157 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1158 for line in f:
1159 md5entries.append(line)
1160
1161 if bpndir and newbpndir:
1162 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1163 else:
1164 relbpndir = None
1165 if bpdir and newbpdir:
1166 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1167 else:
1168 relbpdir = None
1169
1170 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1171 for entry in md5entries:
1172 splitentry = entry.rstrip().split('|')
1173 if len(splitentry) > 2:
1174 if splitentry[0] == args.recipename:
1175 splitentry[0] = newname
1176 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1177 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1178 if appendmd5 and splitentry[2] == appendmd5:
1179 splitentry[2] = newappendmd5
1180 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1181 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1182 if recipefilemd5 and splitentry[2] == recipefilemd5:
1183 splitentry[2] = newrecipefilemd5
1184 elif relbpndir and splitentry[1].startswith(relbpndir):
1185 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1186 elif relbpdir and splitentry[1].startswith(relbpdir):
1187 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1188 entry = '|'.join(splitentry) + '\n'
1189 f.write(entry)
1190 return 0
1191
1192
Brad Bishop316dfdd2018-06-25 12:45:53 -04001193def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001194 """Get initial and update rev of a recipe. These are the start point of the
1195 whole patchset and start point for the patches to be re-generated/updated.
1196 """
1197 import bb
1198
Brad Bishop316dfdd2018-06-25 12:45:53 -04001199 # Get current branch
1200 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1201 cwd=srctree)
1202 branchname = stdout.rstrip()
1203
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001204 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001205 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001206 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001207 with open(recipe_path, 'r') as f:
1208 for line in f:
1209 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001210 if not initial_rev:
1211 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001212 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001213 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001214 elif line.startswith('# patches_%s:' % branchname):
1215 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001216
1217 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001218 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001219 if initial_rev:
1220 # Find first actually changed revision
1221 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1222 initial_rev, cwd=srctree)
1223 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001224 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001225 if newcommits[i] == commits[i]:
1226 update_rev = commits[i]
1227
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001228 try:
1229 stdout, _ = bb.process.run('git cherry devtool-patched',
1230 cwd=srctree)
1231 except bb.process.ExecutionError as err:
1232 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001233
Brad Bishop316dfdd2018-06-25 12:45:53 -04001234 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001235 changed_revs = []
1236 for line in stdout.splitlines():
1237 if line.startswith('+ '):
1238 rev = line.split()[1]
1239 if rev in newcommits:
1240 changed_revs.append(rev)
1241
Brad Bishop316dfdd2018-06-25 12:45:53 -04001242 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001243
1244def _remove_file_entries(srcuri, filelist):
1245 """Remove file:// entries from SRC_URI"""
1246 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001247 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001248 for fname in filelist:
1249 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001250 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001251 if (srcuri[i].startswith('file://') and
1252 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001253 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001254 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001255 srcuri.pop(i)
1256 break
1257 return entries, remaining
1258
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001259def _replace_srcuri_entry(srcuri, filename, newentry):
1260 """Replace entry corresponding to specified file with a new entry"""
1261 basename = os.path.basename(filename)
1262 for i in range(len(srcuri)):
1263 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1264 srcuri.pop(i)
1265 srcuri.insert(i, newentry)
1266 break
1267
Brad Bishop316dfdd2018-06-25 12:45:53 -04001268def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001269 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001270
1271 dry_run_suffix = ' (dry-run)' if dry_run else ''
1272
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001273 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001274 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001275 if not destpath:
1276 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001277 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001278
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001279 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001280 if not no_report_remove:
1281 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1282 if not dry_run:
1283 # FIXME "git rm" here would be nice if the file in question is
1284 # tracked
1285 # FIXME there's a chance that this file is referred to by
1286 # another recipe, in which case deleting wouldn't be the
1287 # right thing to do
1288 os.remove(path)
1289 # Remove directory if empty
1290 try:
1291 os.rmdir(os.path.dirname(path))
1292 except OSError as ose:
1293 if ose.errno != errno.ENOTEMPTY:
1294 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001295
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001296
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001297def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001298 """Export patches from srctree to given location.
1299 Returns three-tuple of dicts:
1300 1. updated - patches that already exist in SRCURI
1301 2. added - new patches that don't exist in SRCURI
1302 3 removed - patches that exist in SRCURI but not in exported patches
1303 In each dict the key is the 'basepath' of the URI and value is the
1304 absolute path to the existing file in recipe space (if any).
1305 """
1306 import oe.recipeutils
1307 from oe.patch import GitApplyTree
1308 updated = OrderedDict()
1309 added = OrderedDict()
1310 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1311
1312 existing_patches = dict((os.path.basename(path), path) for path in
1313 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001314 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001315
1316 # Generate patches from Git, exclude local files directory
1317 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1318 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1319
1320 new_patches = sorted(os.listdir(destdir))
1321 for new_patch in new_patches:
1322 # Strip numbering from patch names. If it's a git sequence named patch,
1323 # the numbers might not match up since we are starting from a different
1324 # revision This does assume that people are using unique shortlog
1325 # values, but they ought to be anyway...
1326 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001327 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001328 for old_patch in existing_patches:
1329 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001330 old_basename_splitext = os.path.splitext(old_basename)
1331 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1332 old_patch_noext = os.path.splitext(old_patch)[0]
1333 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001334 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001335 elif new_basename == old_basename:
1336 match_name = old_patch
1337 break
1338 if match_name:
1339 # Rename patch files
1340 if new_patch != match_name:
Andrew Geisslerc926e172021-05-07 16:11:35 -05001341 bb.utils.rename(os.path.join(destdir, new_patch),
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001342 os.path.join(destdir, match_name))
1343 # Need to pop it off the list now before checking changed_revs
1344 oldpath = existing_patches.pop(old_patch)
1345 if changed_revs is not None:
1346 # Avoid updating patches that have not actually changed
1347 with open(os.path.join(destdir, match_name), 'r') as f:
1348 firstlineitems = f.readline().split()
1349 # Looking for "From <hash>" line
1350 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1351 if not firstlineitems[1] in changed_revs:
1352 continue
1353 # Recompress if necessary
1354 if oldpath.endswith(('.gz', '.Z')):
1355 bb.process.run(['gzip', match_name], cwd=destdir)
1356 if oldpath.endswith('.gz'):
1357 match_name += '.gz'
1358 else:
1359 match_name += '.Z'
1360 elif oldpath.endswith('.bz2'):
1361 bb.process.run(['bzip2', match_name], cwd=destdir)
1362 match_name += '.bz2'
1363 updated[match_name] = oldpath
1364 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001365 added[new_patch] = None
1366 return (updated, added, existing_patches)
1367
1368
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001369def _create_kconfig_diff(srctree, rd, outfile):
1370 """Create a kconfig fragment"""
1371 # Only update config fragment if both config files exist
1372 orig_config = os.path.join(srctree, '.config.baseline')
1373 new_config = os.path.join(srctree, '.config.new')
1374 if os.path.exists(orig_config) and os.path.exists(new_config):
1375 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1376 '--unchanged-line-format=', orig_config, new_config]
1377 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1378 stderr=subprocess.PIPE)
1379 stdout, stderr = pipe.communicate()
1380 if pipe.returncode == 1:
1381 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001382 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001383 fobj.write(stdout)
1384 elif pipe.returncode == 0:
1385 logger.info("Would remove config fragment %s" % outfile)
1386 if os.path.exists(outfile):
1387 # Remove fragment file in case of empty diff
1388 logger.info("Removing config fragment %s" % outfile)
1389 os.unlink(outfile)
1390 else:
1391 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1392 return True
1393 return False
1394
1395
Brad Bishop316dfdd2018-06-25 12:45:53 -04001396def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001397 """Copy local files from srctree to given location.
1398 Returns three-tuple of dicts:
1399 1. updated - files that already exist in SRCURI
1400 2. added - new files files that don't exist in SRCURI
1401 3 removed - files that exist in SRCURI but not in exported files
1402 In each dict the key is the 'basepath' of the URI and value is the
1403 absolute path to the existing file in recipe space (if any).
1404 """
1405 import oe.recipeutils
1406
1407 # Find out local files (SRC_URI files that exist in the "recipe space").
1408 # Local files that reside in srctree are not included in patch generation.
1409 # Instead they are directly copied over the original source files (in
1410 # recipe space).
1411 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1412 new_set = None
1413 updated = OrderedDict()
1414 added = OrderedDict()
1415 removed = OrderedDict()
Andrew Geissler517393d2023-01-13 08:55:19 -06001416
1417 # Get current branch and return early with empty lists
1418 # if on one of the override branches
1419 # (local files are provided only for the main branch and processing
1420 # them against lists from recipe overrides will result in mismatches
1421 # and broken modifications to recipes).
1422 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1423 cwd=srctree)
1424 branchname = stdout.rstrip()
1425 if branchname.startswith(override_branch_prefix):
1426 return (updated, added, removed)
1427
Brad Bishop316dfdd2018-06-25 12:45:53 -04001428 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001429 git_files = _git_ls_tree(srctree)
1430 if 'oe-local-files' in git_files:
1431 # If tracked by Git, take the files from srctree HEAD. First get
1432 # the tree object of the directory
1433 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1434 tree = git_files['oe-local-files'][2]
1435 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1436 env=dict(os.environ, GIT_WORK_TREE=destdir,
1437 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001438 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001439 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001440 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001441 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001442 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001443 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001444 else:
1445 new_set = []
1446
1447 # Special handling for kernel config
1448 if bb.data.inherits_class('kernel-yocto', rd):
1449 fragment_fn = 'devtool-fragment.cfg'
1450 fragment_path = os.path.join(destdir, fragment_fn)
1451 if _create_kconfig_diff(srctree, rd, fragment_path):
1452 if os.path.exists(fragment_path):
1453 if fragment_fn not in new_set:
1454 new_set.append(fragment_fn)
1455 # Copy fragment to local-files
1456 if os.path.isdir(local_files_dir):
1457 shutil.copy2(fragment_path, local_files_dir)
1458 else:
1459 if fragment_fn in new_set:
1460 new_set.remove(fragment_fn)
1461 # Remove fragment from local-files
1462 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1463 os.unlink(os.path.join(local_files_dir, fragment_fn))
1464
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001465 # Special handling for cml1, ccmake, etc bbclasses that generated
1466 # configuration fragment files that are consumed as source files
1467 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1468 if bb.data.inherits_class(frag_class, rd):
1469 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1470 if os.path.exists(srcpath):
1471 if frag_name not in new_set:
1472 new_set.append(frag_name)
1473 # copy fragment into destdir
1474 shutil.copy2(srcpath, destdir)
1475 # copy fragment into local files if exists
1476 if os.path.isdir(local_files_dir):
1477 shutil.copy2(srcpath, local_files_dir)
1478
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001479 if new_set is not None:
1480 for fname in new_set:
1481 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001482 origpath = existing_files.pop(fname)
1483 workpath = os.path.join(local_files_dir, fname)
1484 if not filecmp.cmp(origpath, workpath):
1485 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001486 elif fname != '.gitignore':
1487 added[fname] = None
1488
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001489 workdir = rd.getVar('WORKDIR')
1490 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001491 if not s.endswith(os.sep):
1492 s += os.sep
1493
1494 if workdir != s:
1495 # Handle files where subdir= was specified
1496 for fname in list(existing_files.keys()):
1497 # FIXME handle both subdir starting with BP and not?
1498 fworkpath = os.path.join(workdir, fname)
1499 if fworkpath.startswith(s):
1500 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1501 if os.path.exists(fpath):
1502 origpath = existing_files.pop(fname)
1503 if not filecmp.cmp(origpath, fpath):
1504 updated[fpath] = origpath
1505
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001506 removed = existing_files
1507 return (updated, added, removed)
1508
1509
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001510def _determine_files_dir(rd):
1511 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001512 recipedir = rd.getVar('FILE_DIRNAME')
1513 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001514 relpth = os.path.relpath(entry, recipedir)
1515 if not os.sep in relpth:
1516 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1517 if os.path.isdir(entry):
1518 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001519 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001520
1521
Brad Bishop316dfdd2018-06-25 12:45:53 -04001522def _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 -05001523 """Implement the 'srcrev' mode of update-recipe"""
1524 import bb
1525 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001526
Brad Bishop316dfdd2018-06-25 12:45:53 -04001527 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1528
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001529 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001530 recipedir = os.path.basename(recipefile)
1531 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001532
1533 # Get HEAD revision
1534 try:
1535 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1536 except bb.process.ExecutionError as err:
1537 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1538 (srctree, err))
1539 srcrev = stdout.strip()
1540 if len(srcrev) != 40:
1541 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1542
1543 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001544 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001545 patchfields = {}
1546 patchfields['SRCREV'] = srcrev
1547 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001548 srcuri = orig_src_uri.split()
1549 tempdir = tempfile.mkdtemp(prefix='devtool')
1550 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001551 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001552 try:
1553 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001554 srctreebase = workspace[recipename]['srctreebase']
1555 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001556 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001557 # Find list of existing patches in recipe file
1558 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001559 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001560 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1561 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001562 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 -05001563
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001564 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001565 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001566 if remove_files:
1567 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1568 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001569
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001570 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001571 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001572 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001573 removevalues = {}
1574 if update_srcuri:
1575 removevalues = {'SRC_URI': removedentries}
1576 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001577 if dry_run_outdir:
1578 logger.info('Creating bbappend (dry-run)')
1579 else:
1580 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1581 rd, appendlayerdir, files, wildcardver=wildcard_version,
1582 extralines=patchfields, removevalues=removevalues,
1583 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001584 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001585 files_dir = _determine_files_dir(rd)
1586 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001587 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001588 if os.path.isabs(basepath):
1589 # Original file (probably with subdir pointing inside source tree)
1590 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001591 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001592 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001593 _move_file(os.path.join(local_files_dir, basepath), path,
1594 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001595 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001596 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001597 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001598 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001599 os.path.join(files_dir, basepath),
1600 dry_run_outdir=dry_run_outdir,
1601 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001602 srcuri.append('file://%s' % basepath)
1603 update_srcuri = True
1604 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001605 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001606 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001607 finally:
1608 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001609 if not 'git://' in orig_src_uri:
1610 logger.info('You will need to update SRC_URI within the recipe to '
1611 'point to a git repository where you have pushed your '
1612 'changes')
1613
Brad Bishop316dfdd2018-06-25 12:45:53 -04001614 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1615 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001616
Brad Bishop316dfdd2018-06-25 12:45:53 -04001617def _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 -05001618 """Implement the 'patch' mode of update-recipe"""
1619 import bb
1620 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001621
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001622 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001623 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001624 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001625 if not os.path.exists(append):
1626 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001627 recipename)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001628 srctreebase = workspace[recipename]['srctreebase']
1629 relpatchdir = os.path.relpath(srctreebase, srctree)
1630 if relpatchdir == '.':
1631 patchdir_params = {}
1632 else:
1633 patchdir_params = {'patchdir': relpatchdir}
1634
Patrick Williams520786c2023-06-25 16:20:36 -05001635 def srcuri_entry(basepath):
Andrew Geissler615f2f12022-07-15 14:00:58 -05001636 if patchdir_params:
1637 paramstr = ';' + ';'.join('%s=%s' % (k,v) for k,v in patchdir_params.items())
1638 else:
1639 paramstr = ''
1640 return 'file://%s%s' % (basepath, paramstr)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001641
Brad Bishop316dfdd2018-06-25 12:45:53 -04001642 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 -05001643 if not initial_rev:
1644 raise DevtoolError('Unable to find initial revision - please specify '
1645 'it with --initial-rev')
1646
Brad Bishop316dfdd2018-06-25 12:45:53 -04001647 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001648 dl_dir = rd.getVar('DL_DIR')
1649 if not dl_dir.endswith('/'):
1650 dl_dir += '/'
1651
Brad Bishop316dfdd2018-06-25 12:45:53 -04001652 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1653
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001654 tempdir = tempfile.mkdtemp(prefix='devtool')
1655 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001656 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Andrew Geissler517393d2023-01-13 08:55:19 -06001657 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001658
1659 # Get updated patches from source tree
1660 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001661 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1662 patches_dir, changed_revs)
Andrew Geissler517393d2023-01-13 08:55:19 -06001663 # Get all patches from source tree and check if any should be removed
1664 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
1665 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1666 all_patches_dir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001667 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1668 if filter_patches:
Brad Bishop00e122a2019-10-05 11:10:57 -04001669 new_p = OrderedDict()
1670 upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches)
Andrew Geissler517393d2023-01-13 08:55:19 -06001671 del_p = OrderedDict((k,v) for k,v in del_p.items() if k in filter_patches)
1672 remove_files = []
1673 if not no_remove:
1674 # Remove deleted local files and patches
1675 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001676 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001677 updaterecipe = False
1678 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001679 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001680 if appendlayerdir:
Brad Bishop00e122a2019-10-05 11:10:57 -04001681 files = OrderedDict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001682 key, val in list(upd_f.items()) + list(new_f.items()))
Brad Bishop00e122a2019-10-05 11:10:57 -04001683 files.update(OrderedDict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001684 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001685 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001686 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001687 if remove_files:
1688 removedentries, remaining = _remove_file_entries(
1689 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001690 if removedentries or remaining:
Andrew Geissler615f2f12022-07-15 14:00:58 -05001691 remaining = [srcuri_entry(os.path.basename(item)) for
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001692 item in remaining]
1693 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001694 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001695 rd, appendlayerdir, files,
1696 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001697 removevalues=removevalues,
Andrew Geissler615f2f12022-07-15 14:00:58 -05001698 redirect_output=dry_run_outdir,
1699 params=[patchdir_params] * len(files))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001700 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001701 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001702 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001703 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001704 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001705 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001706 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001707 if os.path.isabs(basepath):
1708 # Original file (probably with subdir pointing inside source tree)
1709 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001710 _copy_file(basepath, path,
1711 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001712 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001713 _move_file(os.path.join(local_files_dir, basepath), path,
1714 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001715 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001716 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001717 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001718 if os.path.dirname(path) + '/' == dl_dir:
1719 # This is a a downloaded patch file - we now need to
1720 # replace the entry in SRC_URI with our local version
1721 logger.info('Replacing remote patch %s with updated local version' % basepath)
1722 path = os.path.join(files_dir, basepath)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001723 _replace_srcuri_entry(srcuri, basepath, srcuri_entry(basepath))
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001724 updaterecipe = True
1725 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001726 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1727 _move_file(patchfn, path,
1728 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001729 updatefiles = True
1730 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001731 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001732 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001733 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001734 os.path.join(files_dir, basepath),
1735 dry_run_outdir=dry_run_outdir,
1736 base_outdir=recipedir)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001737 srcuri.append(srcuri_entry(basepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001738 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001739 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001740 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001741 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001742 os.path.join(files_dir, basepath),
1743 dry_run_outdir=dry_run_outdir,
1744 base_outdir=recipedir)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001745 srcuri.append(srcuri_entry(basepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001746 updaterecipe = True
1747 # Update recipe, if needed
1748 if _remove_file_entries(srcuri, remove_files)[0]:
1749 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001750 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001751 if not dry_run_outdir:
1752 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1753 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1754 {'SRC_URI': ' '.join(srcuri)},
1755 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001756 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001757 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001758 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001759 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001760 finally:
1761 shutil.rmtree(tempdir)
1762
Brad Bishop316dfdd2018-06-25 12:45:53 -04001763 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1764 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001765
1766def _guess_recipe_update_mode(srctree, rdata):
1767 """Guess the recipe update mode to use"""
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001768 src_uri = (rdata.getVar('SRC_URI') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001769 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1770 if not git_uris:
1771 return 'patch'
1772 # Just use the first URI for now
1773 uri = git_uris[0]
1774 # Check remote branch
1775 params = bb.fetch.decodeurl(uri)[5]
1776 upstr_branch = params['branch'] if 'branch' in params else 'master'
1777 # Check if current branch HEAD is found in upstream branch
1778 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1779 head_rev = stdout.rstrip()
1780 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1781 cwd=srctree)
1782 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1783 if 'origin/' + upstr_branch in remote_brs:
1784 return 'srcrev'
1785
1786 return 'patch'
1787
Brad Bishop316dfdd2018-06-25 12:45:53 -04001788def _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 -06001789 srctree = workspace[recipename]['srctree']
1790 if mode == 'auto':
1791 mode = _guess_recipe_update_mode(srctree, rd)
1792
Brad Bishop316dfdd2018-06-25 12:45:53 -04001793 override_branches = []
1794 mainbranch = None
1795 startbranch = None
1796 if not no_overrides:
1797 stdout, _ = bb.process.run('git branch', cwd=srctree)
1798 other_branches = []
1799 for line in stdout.splitlines():
1800 branchname = line[2:]
1801 if line.startswith('* '):
1802 startbranch = branchname
1803 if branchname.startswith(override_branch_prefix):
1804 override_branches.append(branchname)
1805 else:
1806 other_branches.append(branchname)
1807
1808 if override_branches:
1809 logger.debug('_update_recipe: override branches: %s' % override_branches)
1810 logger.debug('_update_recipe: other branches: %s' % other_branches)
1811 if startbranch.startswith(override_branch_prefix):
1812 if len(other_branches) == 1:
1813 mainbranch = other_branches[1]
1814 else:
1815 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1816 else:
1817 mainbranch = startbranch
1818
1819 checkedout = None
1820 anyupdated = False
1821 appendfile = None
1822 allremoved = []
1823 if override_branches:
1824 logger.info('Handling main branch (%s)...' % mainbranch)
1825 if startbranch != mainbranch:
1826 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1827 checkedout = mainbranch
1828 try:
1829 branchlist = [mainbranch] + override_branches
1830 for branch in branchlist:
1831 crd = bb.data.createCopy(rd)
1832 if branch != mainbranch:
1833 logger.info('Handling branch %s...' % branch)
1834 override = branch[len(override_branch_prefix):]
1835 crd.appendVar('OVERRIDES', ':%s' % override)
1836 bb.process.run('git checkout %s' % branch, cwd=srctree)
1837 checkedout = branch
1838
1839 if mode == 'srcrev':
1840 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1841 elif mode == 'patch':
1842 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)
1843 else:
1844 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1845 if updated:
1846 anyupdated = True
1847 if appendf:
1848 appendfile = appendf
1849 allremoved.extend(removed)
1850 finally:
1851 if startbranch and checkedout != startbranch:
1852 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1853
1854 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001855
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001856def update_recipe(args, config, basepath, workspace):
1857 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001858 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001859
1860 if args.append:
1861 if not os.path.exists(args.append):
1862 raise DevtoolError('bbappend destination layer directory "%s" '
1863 'does not exist' % args.append)
1864 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1865 raise DevtoolError('conf/layer.conf not found in bbappend '
1866 'destination layer "%s"' % args.append)
1867
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001868 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001869 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001870
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001871 rd = parse_recipe(config, tinfoil, args.recipename, True)
1872 if not rd:
1873 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001874
Brad Bishop316dfdd2018-06-25 12:45:53 -04001875 dry_run_output = None
1876 dry_run_outdir = None
1877 if args.dry_run:
1878 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1879 dry_run_outdir = dry_run_output.name
1880 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 -05001881
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001882 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001883 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001884 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001885 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 -06001886 finally:
1887 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001888
1889 return 0
1890
1891
1892def status(args, config, basepath, workspace):
1893 """Entry point for the devtool 'status' subcommand"""
1894 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001895 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001896 recipefile = value['recipefile']
1897 if recipefile:
1898 recipestr = ' (%s)' % recipefile
1899 else:
1900 recipestr = ''
1901 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001902 else:
1903 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')
1904 return 0
1905
1906
Brad Bishop64c979e2019-11-04 13:55:29 -05001907def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001908 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001909 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001910
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001911 def clean_preferred_provider(pn, layerconf_path):
1912 """Remove PREFERRED_PROVIDER from layer.conf'"""
1913 import re
1914 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1915 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1916 pprovider_found = False
1917 with open(layerconf_file, 'r') as f:
1918 lines = f.readlines()
1919 with open(new_layerconf_file, 'a') as nf:
1920 for line in lines:
1921 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1922 if not re.match(pprovider_exp, line):
1923 nf.write(line)
1924 else:
1925 pprovider_found = True
1926 if pprovider_found:
1927 shutil.move(new_layerconf_file, layerconf_file)
1928 else:
1929 os.remove(new_layerconf_file)
1930
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001931 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001932 if len(recipes) == 1:
1933 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1934 else:
1935 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001936 # If the recipe file itself was created in the workspace, and
1937 # it uses BBCLASSEXTEND, then we need to also clean the other
1938 # variants
1939 targets = []
1940 for recipe in recipes:
1941 targets.append(recipe)
1942 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001943 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001944 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001945 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001946 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001947 except bb.process.ExecutionError as e:
1948 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1949 'wish, you may specify -n/--no-clean to '
1950 'skip running this command when resetting' %
1951 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001952
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001953 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001954 _check_preserve(config, pn)
1955
Brad Bishop316dfdd2018-06-25 12:45:53 -04001956 appendfile = workspace[pn]['bbappend']
1957 if os.path.exists(appendfile):
1958 # This shouldn't happen, but is possible if devtool errored out prior to
1959 # writing the md5 file. We need to delete this here or the recipe won't
1960 # actually be reset
1961 os.remove(appendfile)
1962
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001963 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001964 def preservedir(origdir):
1965 if os.path.exists(origdir):
1966 for root, dirs, files in os.walk(origdir):
1967 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001968 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001969 _move_file(os.path.join(origdir, fn),
1970 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001971 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001972 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001973 os.rmdir(origdir)
1974
Brad Bishop316dfdd2018-06-25 12:45:53 -04001975 recipefile = workspace[pn]['recipefile']
1976 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1977 # This should always be true if recipefile is set, but just in case
1978 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001979 # We don't automatically create this dir next to appends, but the user can
1980 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1981
Brad Bishop316dfdd2018-06-25 12:45:53 -04001982 srctreebase = workspace[pn]['srctreebase']
1983 if os.path.isdir(srctreebase):
1984 if os.listdir(srctreebase):
Brad Bishop64c979e2019-11-04 13:55:29 -05001985 if remove_work:
1986 logger.info('-r argument used on %s, removing source tree.'
1987 ' You will lose any unsaved work' %pn)
1988 shutil.rmtree(srctreebase)
1989 else:
1990 # We don't want to risk wiping out any work in progress
Patrick Williams92b42cb2022-09-03 06:53:57 -05001991 if srctreebase.startswith(os.path.join(config.workspace_path, 'sources')):
1992 from datetime import datetime
1993 preservesrc = os.path.join(config.workspace_path, 'attic', 'sources', "{}.{}".format(pn,datetime.now().strftime("%Y%m%d%H%M%S")))
1994 logger.info('Preserving source tree in %s\nIf you no '
1995 'longer need it then please delete it manually.\n'
1996 'It is also possible to reuse it via devtool source tree argument.'
1997 % preservesrc)
1998 bb.utils.mkdirhier(os.path.dirname(preservesrc))
1999 shutil.move(srctreebase, preservesrc)
2000 else:
2001 logger.info('Leaving source tree %s as-is; if you no '
2002 'longer need it then please delete it manually'
2003 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002004 else:
2005 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04002006 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002007
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002008 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002009
2010def reset(args, config, basepath, workspace):
2011 """Entry point for the devtool 'reset' subcommand"""
2012 import bb
Brad Bishop64c979e2019-11-04 13:55:29 -05002013 import shutil
2014
2015 recipes = ""
2016
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002017 if args.recipename:
2018 if args.all:
2019 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
2020 else:
2021 for recipe in args.recipename:
2022 check_workspace_recipe(workspace, recipe, checksrc=False)
2023 elif not args.all:
2024 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
2025 "reset all recipes")
2026 if args.all:
2027 recipes = list(workspace.keys())
2028 else:
2029 recipes = args.recipename
2030
Brad Bishop64c979e2019-11-04 13:55:29 -05002031 _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002032
2033 return 0
2034
2035
2036def _get_layer(layername, d):
2037 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002038 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04002039 layers = {} # {basename: layer_paths}
2040 for p in layerdirs:
2041 bn = os.path.basename(p)
2042 if bn not in layers:
2043 layers[bn] = [p]
2044 else:
2045 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002046 # Provide some shortcuts
2047 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04002048 layername = 'meta'
2049 layer_paths = layers.get(layername, None)
2050 if not layer_paths:
2051 return os.path.abspath(layername)
2052 elif len(layer_paths) == 1:
2053 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002054 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04002055 # multiple layers having the same base name
2056 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
2057 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
2058 return os.path.abspath(layer_paths[0])
2059
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002060
2061def finish(args, config, basepath, workspace):
2062 """Entry point for the devtool 'finish' subcommand"""
2063 import bb
2064 import oe.recipeutils
2065
2066 check_workspace_recipe(workspace, args.recipename)
2067
Brad Bishop316dfdd2018-06-25 12:45:53 -04002068 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
2069
2070 # Grab the equivalent of COREBASE without having to initialise tinfoil
2071 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2072
2073 srctree = workspace[args.recipename]['srctree']
2074 check_git_repo_op(srctree, [corebasedir])
2075 dirty = check_git_repo_dirty(srctree)
2076 if dirty:
2077 if args.force:
2078 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2079 else:
2080 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)
2081
Brad Bishop00e122a2019-10-05 11:10:57 -04002082 no_clean = args.no_clean
Brad Bishop64c979e2019-11-04 13:55:29 -05002083 remove_work=args.remove_work
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002084 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2085 try:
Brad Bishop6dbb3162019-11-25 09:41:34 -05002086 rd = parse_recipe(config, tinfoil, args.recipename, True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002087 if not rd:
2088 return 1
2089
2090 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002091 recipefile = rd.getVar('FILE')
2092 recipedir = os.path.dirname(recipefile)
2093 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002094
2095 if not os.path.isdir(destlayerdir):
2096 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2097
2098 if os.path.abspath(destlayerdir) == config.workspace_path:
2099 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2100
2101 # If it's an upgrade, grab the original path
2102 origpath = None
2103 origfilelist = None
2104 append = workspace[args.recipename]['bbappend']
2105 with open(append, 'r') as f:
2106 for line in f:
2107 if line.startswith('# original_path:'):
2108 origpath = line.split(':')[1].strip()
2109 elif line.startswith('# original_files:'):
2110 origfilelist = line.split(':')[1].split()
2111
Brad Bishop316dfdd2018-06-25 12:45:53 -04002112 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2113
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002114 if origlayerdir == config.workspace_path:
2115 # Recipe file itself is in workspace, update it there first
2116 appendlayerdir = None
2117 origrelpath = None
2118 if origpath:
2119 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2120 if origlayerpath:
2121 origrelpath = os.path.relpath(origpath, origlayerpath)
2122 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2123 if not destpath:
2124 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 -05002125 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2126 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002127 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002128 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)
2129
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002130 elif destlayerdir == origlayerdir:
2131 # Same layer, update the original recipe
2132 appendlayerdir = None
2133 destpath = None
2134 else:
2135 # Create/update a bbappend in the specified layer
2136 appendlayerdir = destlayerdir
2137 destpath = None
2138
Brad Bishop316dfdd2018-06-25 12:45:53 -04002139 # Actually update the recipe / bbappend
2140 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2141 dry_run_output = None
2142 dry_run_outdir = None
2143 if args.dry_run:
2144 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2145 dry_run_outdir = dry_run_output.name
2146 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)
2147 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2148
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002149 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002150 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002151 for fn in origfilelist:
2152 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002153 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2154 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2155 if not args.dry_run:
2156 try:
2157 os.remove(fnp)
2158 except FileNotFoundError:
2159 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002160
2161 if origlayerdir == config.workspace_path and destpath:
2162 # Recipe file itself is in the workspace - need to move it and any
2163 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002164 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002165 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002166 for root, _, files in os.walk(recipedir):
2167 for fn in files:
2168 srcpath = os.path.join(root, fn)
2169 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2170 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002171 destfp = os.path.join(destdir, fn)
2172 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002173
Brad Bishop316dfdd2018-06-25 12:45:53 -04002174 if dry_run_outdir:
2175 import difflib
2176 comparelist = []
2177 for root, _, files in os.walk(dry_run_outdir):
2178 for fn in files:
2179 outf = os.path.join(root, fn)
2180 relf = os.path.relpath(outf, dry_run_outdir)
2181 logger.debug('dry-run: output file %s' % relf)
2182 if fn.endswith('.bb'):
2183 if origfilelist and origpath and destpath:
2184 # Need to match this up with the pre-upgrade recipe file
2185 for origf in origfilelist:
2186 if origf.endswith('.bb'):
2187 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2188 outf,
2189 os.path.abspath(os.path.join(destpath, relf))))
2190 break
2191 else:
2192 # Compare to the existing recipe
2193 comparelist.append((recipefile, outf, recipefile))
2194 elif fn.endswith('.bbappend'):
2195 if appendfile:
2196 if os.path.exists(appendfile):
2197 comparelist.append((appendfile, outf, appendfile))
2198 else:
2199 comparelist.append((None, outf, appendfile))
2200 else:
2201 if destpath:
2202 recipedest = destpath
2203 elif appendfile:
2204 recipedest = os.path.dirname(appendfile)
2205 else:
2206 recipedest = os.path.dirname(recipefile)
2207 destfp = os.path.join(recipedest, relf)
2208 if os.path.exists(destfp):
2209 comparelist.append((destfp, outf, destfp))
2210 output = ''
2211 for oldfile, newfile, newfileshow in comparelist:
2212 if oldfile:
2213 with open(oldfile, 'r') as f:
2214 oldlines = f.readlines()
2215 else:
2216 oldfile = '/dev/null'
2217 oldlines = []
2218 with open(newfile, 'r') as f:
2219 newlines = f.readlines()
2220 if not newfileshow:
2221 newfileshow = newfile
2222 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2223 difflines = list(diff)
2224 if difflines:
2225 output += ''.join(difflines)
2226 if output:
2227 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002228 finally:
2229 tinfoil.shutdown()
2230
2231 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002232 if args.dry_run:
2233 logger.info('Resetting recipe (dry-run)')
2234 else:
Brad Bishop64c979e2019-11-04 13:55:29 -05002235 _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002236
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002237 return 0
2238
2239
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002240def get_default_srctree(config, recipename=''):
2241 """Get the default srctree path"""
2242 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2243 if recipename:
2244 return os.path.join(srctreeparent, 'sources', recipename)
2245 else:
2246 return os.path.join(srctreeparent, 'sources')
2247
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002248def register_commands(subparsers, context):
2249 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002250
2251 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002252 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002253 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.',
2254 group='starting', order=100)
2255 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.')
2256 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2257 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 -05002258 group = parser_add.add_mutually_exclusive_group()
2259 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2260 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002261 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 -05002262 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002263 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002264 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 -05002265 group = parser_add.add_mutually_exclusive_group()
2266 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2267 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")
2268 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 -05002269 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')
2270 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')
2271 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002272 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2273 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 -06002274 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002275
2276 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002277 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.',
2278 group='starting', order=90)
2279 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2280 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 -05002281 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002282 group = parser_modify.add_mutually_exclusive_group()
2283 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2284 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 -05002285 group = parser_modify.add_mutually_exclusive_group()
2286 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2287 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002288 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 -04002289 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 -05002290 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002291 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002292
2293 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2294 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002295 group='advanced')
2296 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002297 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002298 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 -04002299 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 -05002300 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002301 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002302
2303 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2304 description='Synchronize the previously extracted source tree for an existing recipe',
2305 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2306 group='advanced')
2307 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2308 parser_sync.add_argument('srctree', help='Path to the source tree')
2309 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2310 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002311 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002312
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002313 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2314 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.',
2315 group='working', order=10)
2316 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2317 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2318 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)')
2319 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')
2320 parser_rename.set_defaults(func=rename)
2321
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002322 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002323 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.',
2324 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002325 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2326 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 -05002327 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002328 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2329 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')
2330 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 -04002331 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2332 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2333 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 -05002334 parser_update_recipe.set_defaults(func=update_recipe)
2335
2336 parser_status = subparsers.add_parser('status', help='Show workspace status',
2337 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002338 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002339 parser_status.set_defaults(func=status)
2340
2341 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002342 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 -05002343 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002344 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002345 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2346 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 -05002347 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 -05002348 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002349
2350 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002351 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 -06002352 group='working', order=-100)
2353 parser_finish.add_argument('recipename', help='Recipe to finish')
2354 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.')
2355 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')
2356 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002357 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 -05002358 parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace')
Brad Bishop00e122a2019-10-05 11:10:57 -04002359 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 -04002360 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2361 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2362 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 -06002363 parser_finish.set_defaults(func=finish)