blob: 88a6ecd4219083ed95c7f1a08e0ff894b949a71f [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'))
590 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400591 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500592 # We need to generate the kernel config
593 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500595 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500596
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600597 if 'noexec' in (d.getVarFlags(task, False) or []) or 'task' not in (d.getVarFlags(task, False) or []):
598 logger.info('The %s recipe has %s disabled. Running only '
599 'do_configure task dependencies' % (pn, task))
600
601 if 'depends' in d.getVarFlags('do_configure', False):
602 pn = d.getVarFlags('do_configure', False)['depends']
603 pn = pn.replace('${PV}', d.getVar('PV'))
604 pn = pn.replace('${COMPILERDEP}', d.getVar('COMPILERDEP'))
605 task = None
606
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500607 # Run the fetch + unpack tasks
608 res = tinfoil.build_targets(pn,
609 task,
610 handle_events=True)
611 finally:
612 if os.path.exists(preservestampfile):
613 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500614
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500615 if not res:
616 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500617
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600618 if not is_kernel_yocto and ('noexec' in (d.getVarFlags('do_patch', False) or []) or 'task' not in (d.getVarFlags('do_patch', False) or [])):
619 workshareddir = d.getVar('S')
620 if os.path.islink(srctree):
621 os.unlink(srctree)
622
623 os.symlink(workshareddir, srctree)
624
625 # The initial_rev file is created in devtool_post_unpack function that will not be executed if
626 # do_unpack/do_patch tasks are disabled so we have to directly say that source extraction was successful
627 return True, True
628
Brad Bishop316dfdd2018-06-25 12:45:53 -0400629 try:
630 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
631 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500632
Brad Bishop316dfdd2018-06-25 12:45:53 -0400633 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
634 srcsubdir = f.read()
635 except FileNotFoundError as e:
636 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
637 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500638
Brad Bishop96ff1982019-08-19 13:50:42 -0400639 # Check if work-shared is empty, if yes
640 # find source and copy to work-shared
641 if is_kernel_yocto:
642 workshareddir = d.getVar('STAGING_KERNEL_DIR')
643 staging_kerVer = get_staging_kver(workshareddir)
644 kernelVersion = d.getVar('LINUX_VERSION')
645
646 # handle dangling symbolic link in work-shared:
647 if os.path.islink(workshareddir):
648 os.unlink(workshareddir)
649
650 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
651 shutil.rmtree(workshareddir)
652 oe.path.copyhardlinktree(srcsubdir,workshareddir)
653 elif not os.path.exists(workshareddir):
654 oe.path.copyhardlinktree(srcsubdir,workshareddir)
655
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500656 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
657 srctree_localdir = os.path.join(srctree, 'oe-local-files')
658
659 if sync:
660 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
661
662 # Move oe-local-files directory to srctree
663 # As the oe-local-files is not part of the constructed git tree,
664 # remove them directly during the synchrounizating might surprise
665 # the users. Instead, we move it to oe-local-files.bak and remind
666 # user in the log message.
667 if os.path.exists(srctree_localdir + '.bak'):
668 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
669
670 if os.path.exists(srctree_localdir):
671 logger.info('Backing up current local file directory %s' % srctree_localdir)
672 shutil.move(srctree_localdir, srctree_localdir + '.bak')
673
674 if os.path.exists(tempdir_localdir):
675 logger.info('Syncing local source files to srctree...')
676 shutil.copytree(tempdir_localdir, srctree_localdir)
677 else:
678 # Move oe-local-files directory to srctree
679 if os.path.exists(tempdir_localdir):
680 logger.info('Adding local source files to srctree...')
681 shutil.move(tempdir_localdir, srcsubdir)
682
683 shutil.move(srcsubdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400684 symlink_oelocal_files_srctree(d,srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500685
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500686 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500687 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500688 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500689
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500690 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500691 if appendbackup:
692 shutil.copyfile(appendbackup, appendfile)
693 elif os.path.exists(appendfile):
694 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500695 if keep_temp:
696 logger.info('Preserving temporary directory %s' % tempdir)
697 else:
698 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400699 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500700
701def _add_md5(config, recipename, filename):
702 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
703 import bb.utils
704
705 def addfile(fn):
706 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500707 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
708 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
709 f.seek(0, os.SEEK_SET)
710 if not md5_str in f.read():
711 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500712
713 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500714 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500715 for f in files:
716 addfile(os.path.join(root, f))
717 else:
718 addfile(filename)
719
720def _check_preserve(config, recipename):
721 """Check if a file was manually changed and needs to be saved in 'attic'
722 directory"""
723 import bb.utils
724 origfile = os.path.join(config.workspace_path, '.devtool_md5')
725 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500726 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500727 with open(origfile, 'r') as f:
728 with open(newfile, 'w') as tf:
729 for line in f.readlines():
730 splitline = line.rstrip().split('|')
731 if splitline[0] == recipename:
732 removefile = os.path.join(config.workspace_path, splitline[1])
733 try:
734 md5 = bb.utils.md5_file(removefile)
735 except IOError as err:
736 if err.errno == 2:
737 # File no longer exists, skip it
738 continue
739 else:
740 raise
741 if splitline[2] != md5:
742 bb.utils.mkdirhier(preservepath)
743 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800744 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500745 shutil.move(removefile, os.path.join(preservepath, preservefile))
746 else:
747 os.remove(removefile)
748 else:
749 tf.write(line)
Andrew Geisslerc926e172021-05-07 16:11:35 -0500750 bb.utils.rename(newfile, origfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751
Brad Bishop96ff1982019-08-19 13:50:42 -0400752def get_staging_kver(srcdir):
753 # Kernel version from work-shared
754 kerver = []
755 staging_kerVer=""
756 if os.path.exists(srcdir) and os.listdir(srcdir):
757 with open(os.path.join(srcdir,"Makefile")) as f:
758 version = [next(f) for x in range(5)][1:4]
759 for word in version:
760 kerver.append(word.split('= ')[1].split('\n')[0])
761 staging_kerVer = ".".join(kerver)
762 return staging_kerVer
763
764def get_staging_kbranch(srcdir):
765 staging_kbranch = ""
766 if os.path.exists(srcdir) and os.listdir(srcdir):
767 (branch, _) = bb.process.run('git branch | grep \* | cut -d \' \' -f2', cwd=srcdir)
768 staging_kbranch = "".join(branch.split('\n')[0])
769 return staging_kbranch
770
Andrew Geissler517393d2023-01-13 08:55:19 -0600771def get_real_srctree(srctree, s, workdir):
772 # Check that recipe isn't using a shared workdir
773 s = os.path.abspath(s)
774 workdir = os.path.abspath(workdir)
775 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
776 # Handle if S is set to a subdirectory of the source
777 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
778 srctree = os.path.join(srctree, srcsubdir)
779 return srctree
780
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500781def modify(args, config, basepath, workspace):
782 """Entry point for the devtool 'modify' subcommand"""
783 import bb
784 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400785 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400786 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500787
788 if args.recipename in workspace:
789 raise DevtoolError("recipe %s is already in your workspace" %
790 args.recipename)
791
Brad Bishop316dfdd2018-06-25 12:45:53 -0400792 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600793 try:
794 rd = parse_recipe(config, tinfoil, args.recipename, True)
795 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500796 return 1
797
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500798 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600799 if pn != args.recipename:
800 logger.info('Mapping %s to %s' % (args.recipename, pn))
801 if pn in workspace:
802 raise DevtoolError("recipe %s is already in your workspace" %
803 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500804
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600805 if args.srctree:
806 srctree = os.path.abspath(args.srctree)
807 else:
808 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500809
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600810 if args.no_extract and not os.path.isdir(srctree):
811 raise DevtoolError("--no-extract specified and source path %s does "
812 "not exist or is not a directory" %
813 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600814
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500815 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600816 appendfile = recipe_to_append(recipefile, config, args.wildcard)
817 if os.path.exists(appendfile):
818 raise DevtoolError("Another variant of recipe %s is already in your "
819 "workspace (only one variant of a recipe can "
820 "currently be worked on at once)"
821 % pn)
822
823 _check_compatible_recipe(pn, rd)
824
825 initial_rev = None
826 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400827 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400828
829 if bb.data.inherits_class('kernel-yocto', rd):
830 # Current set kernel version
831 kernelVersion = rd.getVar('LINUX_VERSION')
832 srcdir = rd.getVar('STAGING_KERNEL_DIR')
833 kbranch = rd.getVar('KBRANCH')
834
835 staging_kerVer = get_staging_kver(srcdir)
836 staging_kbranch = get_staging_kbranch(srcdir)
837 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
838 oe.path.copyhardlinktree(srcdir,srctree)
839 workdir = rd.getVar('WORKDIR')
840 srcsubdir = rd.getVar('S')
841 localfilesdir = os.path.join(srctree,'oe-local-files')
842 # Move local source files into separate subdir
843 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
844 local_files = oe.recipeutils.get_recipe_local_files(rd)
845
846 for key in local_files.copy():
847 if key.endswith('scc'):
848 sccfile = open(local_files[key], 'r')
849 for l in sccfile:
850 line = l.split()
851 if line and line[0] in ('kconf', 'patch'):
852 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
853 if not cfg in local_files.values():
854 local_files[line[-1]] = cfg
855 shutil.copy2(cfg, workdir)
856 sccfile.close()
857
858 # Ignore local files with subdir={BP}
859 srcabspath = os.path.abspath(srcsubdir)
860 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))]
861 if local_files:
862 for fname in local_files:
863 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
864 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
865 f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n')
866
867 symlink_oelocal_files_srctree(rd,srctree)
868
869 task = 'do_configure'
870 res = tinfoil.build_targets(pn, task, handle_events=True)
871
872 # Copy .config to workspace
873 kconfpath = rd.getVar('B')
874 logger.info('Copying kernel config to workspace')
875 shutil.copy2(os.path.join(kconfpath, '.config'),srctree)
876
877 # Set this to true, we still need to get initial_rev
878 # by parsing the git repo
879 args.no_extract = True
880
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600881 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400882 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 -0500883 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600884 return 1
885 logger.info('Source tree extracted to %s' % srctree)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600886 if os.path.exists(os.path.join(srctree, '.git')):
887 # Get list of commits since this revision
888 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
889 commits = stdout.split()
890 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600891 else:
892 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600893 # Check if it's a tree previously extracted by us. This is done
894 # by ensuring that devtool-base and args.branch (devtool) exist.
895 # The check_commits logic will cause an exception if either one
896 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600897 try:
898 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600899 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600900 except bb.process.ExecutionError:
901 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400902 if stdout:
903 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600904 for line in stdout.splitlines():
905 if line.startswith('*'):
906 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
907 initial_rev = stdout.rstrip()
908 if not initial_rev:
909 # Otherwise, just grab the head revision
910 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
911 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500912
Brad Bishop316dfdd2018-06-25 12:45:53 -0400913 branch_patches = {}
914 if check_commits:
915 # Check if there are override branches
916 (stdout, _) = bb.process.run('git branch', cwd=srctree)
917 branches = []
918 for line in stdout.rstrip().splitlines():
919 branchname = line[2:].rstrip()
920 if branchname.startswith(override_branch_prefix):
921 branches.append(branchname)
922 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800923 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 -0400924 branches.insert(0, args.branch)
925 seen_patches = []
926 for branch in branches:
927 branch_patches[branch] = []
928 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
929 for line in stdout.splitlines():
930 line = line.strip()
931 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
932 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
933 if not origpatch in seen_patches:
934 seen_patches.append(origpatch)
935 branch_patches[branch].append(origpatch)
936
937 # Need to grab this here in case the source is within a subdirectory
938 srctreebase = srctree
Andrew Geissler517393d2023-01-13 08:55:19 -0600939 srctree = get_real_srctree(srctree, rd.getVar('S'), rd.getVar('WORKDIR'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500940
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600941 bb.utils.mkdirhier(os.path.dirname(appendfile))
942 with open(appendfile, 'w') as f:
Patrick Williams213cb262021-08-07 19:21:33 -0500943 f.write('FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600944 # Local files can be modified/tracked in separate subdir under srctree
945 # Mostly useful for packages with S != WORKDIR
Patrick Williams213cb262021-08-07 19:21:33 -0500946 f.write('FILESPATH:prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400947 os.path.join(srctreebase, 'oe-local-files'))
948 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500949
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600950 f.write('\ninherit externalsrc\n')
951 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 -0500952 f.write('EXTERNALSRC:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500953
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600954 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
955 if b_is_s:
Patrick Williams213cb262021-08-07 19:21:33 -0500956 f.write('EXTERNALSRC_BUILD:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500957
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600958 if bb.data.inherits_class('kernel', rd):
959 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500960 'do_fetch do_unpack do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400961 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500962 f.write('\ndo_configure:append() {\n'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600963 ' cp ${B}/.config ${S}/.config.baseline\n'
964 ' ln -sfT ${B}/.config ${S}/.config.new\n'
965 '}\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500966 f.write('\ndo_kernel_configme:prepend() {\n'
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500967 ' if [ -e ${S}/.config ]; then\n'
968 ' mv ${S}/.config ${S}/.config.old\n'
969 ' fi\n'
970 '}\n')
Brad Bishop96ff1982019-08-19 13:50:42 -0400971 if rd.getVarFlag('do_menuconfig','task'):
Patrick Williams213cb262021-08-07 19:21:33 -0500972 f.write('\ndo_configure:append() {\n'
Patrick Williams520786c2023-06-25 16:20:36 -0500973 ' if [ ${@ oe.types.boolean(\'${KCONFIG_CONFIG_ENABLE_MENUCONFIG}\') } = True ]; then\n'
974 ' cp ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.baseline\n'
975 ' ln -sfT ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.new\n'
Andrew Geissler82c905d2020-04-13 13:39:40 -0500976 ' fi\n'
Brad Bishop96ff1982019-08-19 13:50:42 -0400977 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600978 if initial_rev:
979 f.write('\n# initial_rev: %s\n' % initial_rev)
980 for commit in commits:
981 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400982 if branch_patches:
983 for branch in branch_patches:
984 if branch == args.branch:
985 continue
986 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500987
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500988 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
989
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600990 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500991
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600992 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500993
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600994 finally:
995 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500996
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500997 return 0
998
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500999
1000def rename(args, config, basepath, workspace):
1001 """Entry point for the devtool 'rename' subcommand"""
1002 import bb
1003 import oe.recipeutils
1004
1005 check_workspace_recipe(workspace, args.recipename)
1006
1007 if not (args.newname or args.version):
1008 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
1009
1010 recipefile = workspace[args.recipename]['recipefile']
1011 if not recipefile:
1012 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
1013
1014 if args.newname and args.newname != args.recipename:
1015 reason = oe.recipeutils.validate_pn(args.newname)
1016 if reason:
1017 raise DevtoolError(reason)
1018 newname = args.newname
1019 else:
1020 newname = args.recipename
1021
1022 append = workspace[args.recipename]['bbappend']
1023 appendfn = os.path.splitext(os.path.basename(append))[0]
1024 splitfn = appendfn.split('_')
1025 if len(splitfn) > 1:
1026 origfnver = appendfn.split('_')[1]
1027 else:
1028 origfnver = ''
1029
1030 recipefilemd5 = None
1031 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1032 try:
1033 rd = parse_recipe(config, tinfoil, args.recipename, True)
1034 if not rd:
1035 return 1
1036
1037 bp = rd.getVar('BP')
1038 bpn = rd.getVar('BPN')
1039 if newname != args.recipename:
1040 localdata = rd.createCopy()
1041 localdata.setVar('PN', newname)
1042 newbpn = localdata.getVar('BPN')
1043 else:
1044 newbpn = bpn
1045 s = rd.getVar('S', False)
1046 src_uri = rd.getVar('SRC_URI', False)
1047 pv = rd.getVar('PV')
1048
1049 # Correct variable values that refer to the upstream source - these
1050 # values must stay the same, so if the name/version are changing then
1051 # we need to fix them up
1052 new_s = s
1053 new_src_uri = src_uri
1054 if newbpn != bpn:
1055 # ${PN} here is technically almost always incorrect, but people do use it
1056 new_s = new_s.replace('${BPN}', bpn)
1057 new_s = new_s.replace('${PN}', bpn)
1058 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1059 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1060 new_src_uri = new_src_uri.replace('${PN}', bpn)
1061 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1062 if args.version and origfnver == pv:
1063 new_s = new_s.replace('${PV}', pv)
1064 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1065 new_src_uri = new_src_uri.replace('${PV}', pv)
1066 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1067 patchfields = {}
1068 if new_s != s:
1069 patchfields['S'] = new_s
1070 if new_src_uri != src_uri:
1071 patchfields['SRC_URI'] = new_src_uri
1072 if patchfields:
1073 recipefilemd5 = bb.utils.md5_file(recipefile)
1074 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1075 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1076 finally:
1077 tinfoil.shutdown()
1078
1079 if args.version:
1080 newver = args.version
1081 else:
1082 newver = origfnver
1083
1084 if newver:
1085 newappend = '%s_%s.bbappend' % (newname, newver)
1086 newfile = '%s_%s.bb' % (newname, newver)
1087 else:
1088 newappend = '%s.bbappend' % newname
1089 newfile = '%s.bb' % newname
1090
1091 oldrecipedir = os.path.dirname(recipefile)
1092 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1093 if oldrecipedir != newrecipedir:
1094 bb.utils.mkdirhier(newrecipedir)
1095
1096 newappend = os.path.join(os.path.dirname(append), newappend)
1097 newfile = os.path.join(newrecipedir, newfile)
1098
1099 # Rename bbappend
1100 logger.info('Renaming %s to %s' % (append, newappend))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001101 bb.utils.rename(append, newappend)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001102 # Rename recipe file
1103 logger.info('Renaming %s to %s' % (recipefile, newfile))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001104 bb.utils.rename(recipefile, newfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001105
1106 # Rename source tree if it's the default path
1107 appendmd5 = None
1108 if not args.no_srctree:
1109 srctree = workspace[args.recipename]['srctree']
1110 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1111 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1112 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1113 shutil.move(srctree, newsrctree)
1114 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1115 appendmd5 = bb.utils.md5_file(newappend)
1116 appendlines = []
1117 with open(newappend, 'r') as f:
1118 for line in f:
1119 appendlines.append(line)
1120 with open(newappend, 'w') as f:
1121 for line in appendlines:
1122 if srctree in line:
1123 line = line.replace(srctree, newsrctree)
1124 f.write(line)
1125 newappendmd5 = bb.utils.md5_file(newappend)
1126
1127 bpndir = None
1128 newbpndir = None
1129 if newbpn != bpn:
1130 bpndir = os.path.join(oldrecipedir, bpn)
1131 if os.path.exists(bpndir):
1132 newbpndir = os.path.join(newrecipedir, newbpn)
1133 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1134 shutil.move(bpndir, newbpndir)
1135
1136 bpdir = None
1137 newbpdir = None
1138 if newver != origfnver or newbpn != bpn:
1139 bpdir = os.path.join(oldrecipedir, bp)
1140 if os.path.exists(bpdir):
1141 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1142 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1143 shutil.move(bpdir, newbpdir)
1144
1145 if oldrecipedir != newrecipedir:
1146 # Move any stray files and delete the old recipe directory
1147 for entry in os.listdir(oldrecipedir):
1148 oldpath = os.path.join(oldrecipedir, entry)
1149 newpath = os.path.join(newrecipedir, entry)
1150 logger.info('Renaming %s to %s' % (oldpath, newpath))
1151 shutil.move(oldpath, newpath)
1152 os.rmdir(oldrecipedir)
1153
1154 # Now take care of entries in .devtool_md5
1155 md5entries = []
1156 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1157 for line in f:
1158 md5entries.append(line)
1159
1160 if bpndir and newbpndir:
1161 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1162 else:
1163 relbpndir = None
1164 if bpdir and newbpdir:
1165 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1166 else:
1167 relbpdir = None
1168
1169 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1170 for entry in md5entries:
1171 splitentry = entry.rstrip().split('|')
1172 if len(splitentry) > 2:
1173 if splitentry[0] == args.recipename:
1174 splitentry[0] = newname
1175 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1176 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1177 if appendmd5 and splitentry[2] == appendmd5:
1178 splitentry[2] = newappendmd5
1179 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1180 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1181 if recipefilemd5 and splitentry[2] == recipefilemd5:
1182 splitentry[2] = newrecipefilemd5
1183 elif relbpndir and splitentry[1].startswith(relbpndir):
1184 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1185 elif relbpdir and splitentry[1].startswith(relbpdir):
1186 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1187 entry = '|'.join(splitentry) + '\n'
1188 f.write(entry)
1189 return 0
1190
1191
Brad Bishop316dfdd2018-06-25 12:45:53 -04001192def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001193 """Get initial and update rev of a recipe. These are the start point of the
1194 whole patchset and start point for the patches to be re-generated/updated.
1195 """
1196 import bb
1197
Brad Bishop316dfdd2018-06-25 12:45:53 -04001198 # Get current branch
1199 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1200 cwd=srctree)
1201 branchname = stdout.rstrip()
1202
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001203 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001204 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001205 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001206 with open(recipe_path, 'r') as f:
1207 for line in f:
1208 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001209 if not initial_rev:
1210 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001211 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001212 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001213 elif line.startswith('# patches_%s:' % branchname):
1214 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001215
1216 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001217 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001218 if initial_rev:
1219 # Find first actually changed revision
1220 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1221 initial_rev, cwd=srctree)
1222 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001223 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001224 if newcommits[i] == commits[i]:
1225 update_rev = commits[i]
1226
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001227 try:
1228 stdout, _ = bb.process.run('git cherry devtool-patched',
1229 cwd=srctree)
1230 except bb.process.ExecutionError as err:
1231 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001232
Brad Bishop316dfdd2018-06-25 12:45:53 -04001233 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001234 changed_revs = []
1235 for line in stdout.splitlines():
1236 if line.startswith('+ '):
1237 rev = line.split()[1]
1238 if rev in newcommits:
1239 changed_revs.append(rev)
1240
Brad Bishop316dfdd2018-06-25 12:45:53 -04001241 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001242
1243def _remove_file_entries(srcuri, filelist):
1244 """Remove file:// entries from SRC_URI"""
1245 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001246 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001247 for fname in filelist:
1248 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001249 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001250 if (srcuri[i].startswith('file://') and
1251 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001252 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001253 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001254 srcuri.pop(i)
1255 break
1256 return entries, remaining
1257
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001258def _replace_srcuri_entry(srcuri, filename, newentry):
1259 """Replace entry corresponding to specified file with a new entry"""
1260 basename = os.path.basename(filename)
1261 for i in range(len(srcuri)):
1262 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1263 srcuri.pop(i)
1264 srcuri.insert(i, newentry)
1265 break
1266
Brad Bishop316dfdd2018-06-25 12:45:53 -04001267def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001268 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001269
1270 dry_run_suffix = ' (dry-run)' if dry_run else ''
1271
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001272 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001273 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001274 if not destpath:
1275 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001276 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001277
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001278 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001279 if not no_report_remove:
1280 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1281 if not dry_run:
1282 # FIXME "git rm" here would be nice if the file in question is
1283 # tracked
1284 # FIXME there's a chance that this file is referred to by
1285 # another recipe, in which case deleting wouldn't be the
1286 # right thing to do
1287 os.remove(path)
1288 # Remove directory if empty
1289 try:
1290 os.rmdir(os.path.dirname(path))
1291 except OSError as ose:
1292 if ose.errno != errno.ENOTEMPTY:
1293 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001294
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001295
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001296def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001297 """Export patches from srctree to given location.
1298 Returns three-tuple of dicts:
1299 1. updated - patches that already exist in SRCURI
1300 2. added - new patches that don't exist in SRCURI
1301 3 removed - patches that exist in SRCURI but not in exported patches
1302 In each dict the key is the 'basepath' of the URI and value is the
1303 absolute path to the existing file in recipe space (if any).
1304 """
1305 import oe.recipeutils
1306 from oe.patch import GitApplyTree
1307 updated = OrderedDict()
1308 added = OrderedDict()
1309 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1310
1311 existing_patches = dict((os.path.basename(path), path) for path in
1312 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001313 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001314
1315 # Generate patches from Git, exclude local files directory
1316 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1317 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1318
1319 new_patches = sorted(os.listdir(destdir))
1320 for new_patch in new_patches:
1321 # Strip numbering from patch names. If it's a git sequence named patch,
1322 # the numbers might not match up since we are starting from a different
1323 # revision This does assume that people are using unique shortlog
1324 # values, but they ought to be anyway...
1325 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001326 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001327 for old_patch in existing_patches:
1328 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001329 old_basename_splitext = os.path.splitext(old_basename)
1330 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1331 old_patch_noext = os.path.splitext(old_patch)[0]
1332 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001333 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001334 elif new_basename == old_basename:
1335 match_name = old_patch
1336 break
1337 if match_name:
1338 # Rename patch files
1339 if new_patch != match_name:
Andrew Geisslerc926e172021-05-07 16:11:35 -05001340 bb.utils.rename(os.path.join(destdir, new_patch),
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001341 os.path.join(destdir, match_name))
1342 # Need to pop it off the list now before checking changed_revs
1343 oldpath = existing_patches.pop(old_patch)
1344 if changed_revs is not None:
1345 # Avoid updating patches that have not actually changed
1346 with open(os.path.join(destdir, match_name), 'r') as f:
1347 firstlineitems = f.readline().split()
1348 # Looking for "From <hash>" line
1349 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1350 if not firstlineitems[1] in changed_revs:
1351 continue
1352 # Recompress if necessary
1353 if oldpath.endswith(('.gz', '.Z')):
1354 bb.process.run(['gzip', match_name], cwd=destdir)
1355 if oldpath.endswith('.gz'):
1356 match_name += '.gz'
1357 else:
1358 match_name += '.Z'
1359 elif oldpath.endswith('.bz2'):
1360 bb.process.run(['bzip2', match_name], cwd=destdir)
1361 match_name += '.bz2'
1362 updated[match_name] = oldpath
1363 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001364 added[new_patch] = None
1365 return (updated, added, existing_patches)
1366
1367
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001368def _create_kconfig_diff(srctree, rd, outfile):
1369 """Create a kconfig fragment"""
1370 # Only update config fragment if both config files exist
1371 orig_config = os.path.join(srctree, '.config.baseline')
1372 new_config = os.path.join(srctree, '.config.new')
1373 if os.path.exists(orig_config) and os.path.exists(new_config):
1374 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1375 '--unchanged-line-format=', orig_config, new_config]
1376 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1377 stderr=subprocess.PIPE)
1378 stdout, stderr = pipe.communicate()
1379 if pipe.returncode == 1:
1380 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001381 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001382 fobj.write(stdout)
1383 elif pipe.returncode == 0:
1384 logger.info("Would remove config fragment %s" % outfile)
1385 if os.path.exists(outfile):
1386 # Remove fragment file in case of empty diff
1387 logger.info("Removing config fragment %s" % outfile)
1388 os.unlink(outfile)
1389 else:
1390 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1391 return True
1392 return False
1393
1394
Brad Bishop316dfdd2018-06-25 12:45:53 -04001395def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001396 """Copy local files from srctree to given location.
1397 Returns three-tuple of dicts:
1398 1. updated - files that already exist in SRCURI
1399 2. added - new files files that don't exist in SRCURI
1400 3 removed - files that exist in SRCURI but not in exported files
1401 In each dict the key is the 'basepath' of the URI and value is the
1402 absolute path to the existing file in recipe space (if any).
1403 """
1404 import oe.recipeutils
1405
1406 # Find out local files (SRC_URI files that exist in the "recipe space").
1407 # Local files that reside in srctree are not included in patch generation.
1408 # Instead they are directly copied over the original source files (in
1409 # recipe space).
1410 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1411 new_set = None
1412 updated = OrderedDict()
1413 added = OrderedDict()
1414 removed = OrderedDict()
Andrew Geissler517393d2023-01-13 08:55:19 -06001415
1416 # Get current branch and return early with empty lists
1417 # if on one of the override branches
1418 # (local files are provided only for the main branch and processing
1419 # them against lists from recipe overrides will result in mismatches
1420 # and broken modifications to recipes).
1421 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1422 cwd=srctree)
1423 branchname = stdout.rstrip()
1424 if branchname.startswith(override_branch_prefix):
1425 return (updated, added, removed)
1426
Brad Bishop316dfdd2018-06-25 12:45:53 -04001427 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001428 git_files = _git_ls_tree(srctree)
1429 if 'oe-local-files' in git_files:
1430 # If tracked by Git, take the files from srctree HEAD. First get
1431 # the tree object of the directory
1432 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1433 tree = git_files['oe-local-files'][2]
1434 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1435 env=dict(os.environ, GIT_WORK_TREE=destdir,
1436 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001437 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001438 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001439 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001440 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001441 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001442 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001443 else:
1444 new_set = []
1445
1446 # Special handling for kernel config
1447 if bb.data.inherits_class('kernel-yocto', rd):
1448 fragment_fn = 'devtool-fragment.cfg'
1449 fragment_path = os.path.join(destdir, fragment_fn)
1450 if _create_kconfig_diff(srctree, rd, fragment_path):
1451 if os.path.exists(fragment_path):
1452 if fragment_fn not in new_set:
1453 new_set.append(fragment_fn)
1454 # Copy fragment to local-files
1455 if os.path.isdir(local_files_dir):
1456 shutil.copy2(fragment_path, local_files_dir)
1457 else:
1458 if fragment_fn in new_set:
1459 new_set.remove(fragment_fn)
1460 # Remove fragment from local-files
1461 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1462 os.unlink(os.path.join(local_files_dir, fragment_fn))
1463
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001464 # Special handling for cml1, ccmake, etc bbclasses that generated
1465 # configuration fragment files that are consumed as source files
1466 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1467 if bb.data.inherits_class(frag_class, rd):
1468 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1469 if os.path.exists(srcpath):
1470 if frag_name not in new_set:
1471 new_set.append(frag_name)
1472 # copy fragment into destdir
1473 shutil.copy2(srcpath, destdir)
1474 # copy fragment into local files if exists
1475 if os.path.isdir(local_files_dir):
1476 shutil.copy2(srcpath, local_files_dir)
1477
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001478 if new_set is not None:
1479 for fname in new_set:
1480 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001481 origpath = existing_files.pop(fname)
1482 workpath = os.path.join(local_files_dir, fname)
1483 if not filecmp.cmp(origpath, workpath):
1484 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001485 elif fname != '.gitignore':
1486 added[fname] = None
1487
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001488 workdir = rd.getVar('WORKDIR')
1489 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001490 if not s.endswith(os.sep):
1491 s += os.sep
1492
1493 if workdir != s:
1494 # Handle files where subdir= was specified
1495 for fname in list(existing_files.keys()):
1496 # FIXME handle both subdir starting with BP and not?
1497 fworkpath = os.path.join(workdir, fname)
1498 if fworkpath.startswith(s):
1499 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1500 if os.path.exists(fpath):
1501 origpath = existing_files.pop(fname)
1502 if not filecmp.cmp(origpath, fpath):
1503 updated[fpath] = origpath
1504
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001505 removed = existing_files
1506 return (updated, added, removed)
1507
1508
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001509def _determine_files_dir(rd):
1510 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001511 recipedir = rd.getVar('FILE_DIRNAME')
1512 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001513 relpth = os.path.relpath(entry, recipedir)
1514 if not os.sep in relpth:
1515 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1516 if os.path.isdir(entry):
1517 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001518 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001519
1520
Brad Bishop316dfdd2018-06-25 12:45:53 -04001521def _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 -05001522 """Implement the 'srcrev' mode of update-recipe"""
1523 import bb
1524 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001525
Brad Bishop316dfdd2018-06-25 12:45:53 -04001526 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1527
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001528 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001529 recipedir = os.path.basename(recipefile)
1530 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001531
1532 # Get HEAD revision
1533 try:
1534 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1535 except bb.process.ExecutionError as err:
1536 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1537 (srctree, err))
1538 srcrev = stdout.strip()
1539 if len(srcrev) != 40:
1540 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1541
1542 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001543 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001544 patchfields = {}
1545 patchfields['SRCREV'] = srcrev
1546 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001547 srcuri = orig_src_uri.split()
1548 tempdir = tempfile.mkdtemp(prefix='devtool')
1549 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001550 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001551 try:
1552 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001553 srctreebase = workspace[recipename]['srctreebase']
1554 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001555 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001556 # Find list of existing patches in recipe file
1557 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001558 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001559 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1560 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001561 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 -05001562
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001563 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001564 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001565 if remove_files:
1566 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1567 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001568
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001569 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001570 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001571 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001572 removevalues = {}
1573 if update_srcuri:
1574 removevalues = {'SRC_URI': removedentries}
1575 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001576 if dry_run_outdir:
1577 logger.info('Creating bbappend (dry-run)')
1578 else:
1579 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1580 rd, appendlayerdir, files, wildcardver=wildcard_version,
1581 extralines=patchfields, removevalues=removevalues,
1582 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001583 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001584 files_dir = _determine_files_dir(rd)
1585 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001586 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001587 if os.path.isabs(basepath):
1588 # Original file (probably with subdir pointing inside source tree)
1589 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001590 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001591 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001592 _move_file(os.path.join(local_files_dir, basepath), path,
1593 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001594 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001595 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001596 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001597 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001598 os.path.join(files_dir, basepath),
1599 dry_run_outdir=dry_run_outdir,
1600 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001601 srcuri.append('file://%s' % basepath)
1602 update_srcuri = True
1603 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001604 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001605 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001606 finally:
1607 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001608 if not 'git://' in orig_src_uri:
1609 logger.info('You will need to update SRC_URI within the recipe to '
1610 'point to a git repository where you have pushed your '
1611 'changes')
1612
Brad Bishop316dfdd2018-06-25 12:45:53 -04001613 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1614 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001615
Brad Bishop316dfdd2018-06-25 12:45:53 -04001616def _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 -05001617 """Implement the 'patch' mode of update-recipe"""
1618 import bb
1619 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001620
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001621 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001622 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001623 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001624 if not os.path.exists(append):
1625 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001626 recipename)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001627 srctreebase = workspace[recipename]['srctreebase']
1628 relpatchdir = os.path.relpath(srctreebase, srctree)
1629 if relpatchdir == '.':
1630 patchdir_params = {}
1631 else:
1632 patchdir_params = {'patchdir': relpatchdir}
1633
Patrick Williams520786c2023-06-25 16:20:36 -05001634 def srcuri_entry(basepath):
Andrew Geissler615f2f12022-07-15 14:00:58 -05001635 if patchdir_params:
1636 paramstr = ';' + ';'.join('%s=%s' % (k,v) for k,v in patchdir_params.items())
1637 else:
1638 paramstr = ''
1639 return 'file://%s%s' % (basepath, paramstr)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001640
Brad Bishop316dfdd2018-06-25 12:45:53 -04001641 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 -05001642 if not initial_rev:
1643 raise DevtoolError('Unable to find initial revision - please specify '
1644 'it with --initial-rev')
1645
Brad Bishop316dfdd2018-06-25 12:45:53 -04001646 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001647 dl_dir = rd.getVar('DL_DIR')
1648 if not dl_dir.endswith('/'):
1649 dl_dir += '/'
1650
Brad Bishop316dfdd2018-06-25 12:45:53 -04001651 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1652
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001653 tempdir = tempfile.mkdtemp(prefix='devtool')
1654 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001655 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Andrew Geissler517393d2023-01-13 08:55:19 -06001656 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001657
1658 # Get updated patches from source tree
1659 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001660 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1661 patches_dir, changed_revs)
Andrew Geissler517393d2023-01-13 08:55:19 -06001662 # Get all patches from source tree and check if any should be removed
1663 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
1664 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1665 all_patches_dir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001666 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1667 if filter_patches:
Brad Bishop00e122a2019-10-05 11:10:57 -04001668 new_p = OrderedDict()
1669 upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches)
Andrew Geissler517393d2023-01-13 08:55:19 -06001670 del_p = OrderedDict((k,v) for k,v in del_p.items() if k in filter_patches)
1671 remove_files = []
1672 if not no_remove:
1673 # Remove deleted local files and patches
1674 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001675 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001676 updaterecipe = False
1677 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001678 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001679 if appendlayerdir:
Brad Bishop00e122a2019-10-05 11:10:57 -04001680 files = OrderedDict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001681 key, val in list(upd_f.items()) + list(new_f.items()))
Brad Bishop00e122a2019-10-05 11:10:57 -04001682 files.update(OrderedDict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001683 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001684 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001685 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001686 if remove_files:
1687 removedentries, remaining = _remove_file_entries(
1688 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001689 if removedentries or remaining:
Andrew Geissler615f2f12022-07-15 14:00:58 -05001690 remaining = [srcuri_entry(os.path.basename(item)) for
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001691 item in remaining]
1692 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001693 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001694 rd, appendlayerdir, files,
1695 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001696 removevalues=removevalues,
Andrew Geissler615f2f12022-07-15 14:00:58 -05001697 redirect_output=dry_run_outdir,
1698 params=[patchdir_params] * len(files))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001699 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001700 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001701 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001702 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001703 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001704 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001705 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001706 if os.path.isabs(basepath):
1707 # Original file (probably with subdir pointing inside source tree)
1708 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001709 _copy_file(basepath, path,
1710 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001711 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001712 _move_file(os.path.join(local_files_dir, basepath), path,
1713 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001714 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001715 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001716 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001717 if os.path.dirname(path) + '/' == dl_dir:
1718 # This is a a downloaded patch file - we now need to
1719 # replace the entry in SRC_URI with our local version
1720 logger.info('Replacing remote patch %s with updated local version' % basepath)
1721 path = os.path.join(files_dir, basepath)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001722 _replace_srcuri_entry(srcuri, basepath, srcuri_entry(basepath))
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001723 updaterecipe = True
1724 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001725 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1726 _move_file(patchfn, path,
1727 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001728 updatefiles = True
1729 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001730 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001731 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001732 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001733 os.path.join(files_dir, basepath),
1734 dry_run_outdir=dry_run_outdir,
1735 base_outdir=recipedir)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001736 srcuri.append(srcuri_entry(basepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001737 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001738 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001739 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001740 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001741 os.path.join(files_dir, basepath),
1742 dry_run_outdir=dry_run_outdir,
1743 base_outdir=recipedir)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001744 srcuri.append(srcuri_entry(basepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001745 updaterecipe = True
1746 # Update recipe, if needed
1747 if _remove_file_entries(srcuri, remove_files)[0]:
1748 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001749 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001750 if not dry_run_outdir:
1751 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1752 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1753 {'SRC_URI': ' '.join(srcuri)},
1754 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001755 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001756 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001757 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001758 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001759 finally:
1760 shutil.rmtree(tempdir)
1761
Brad Bishop316dfdd2018-06-25 12:45:53 -04001762 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1763 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001764
1765def _guess_recipe_update_mode(srctree, rdata):
1766 """Guess the recipe update mode to use"""
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001767 src_uri = (rdata.getVar('SRC_URI') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001768 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1769 if not git_uris:
1770 return 'patch'
1771 # Just use the first URI for now
1772 uri = git_uris[0]
1773 # Check remote branch
1774 params = bb.fetch.decodeurl(uri)[5]
1775 upstr_branch = params['branch'] if 'branch' in params else 'master'
1776 # Check if current branch HEAD is found in upstream branch
1777 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1778 head_rev = stdout.rstrip()
1779 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1780 cwd=srctree)
1781 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1782 if 'origin/' + upstr_branch in remote_brs:
1783 return 'srcrev'
1784
1785 return 'patch'
1786
Brad Bishop316dfdd2018-06-25 12:45:53 -04001787def _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 -06001788 srctree = workspace[recipename]['srctree']
1789 if mode == 'auto':
1790 mode = _guess_recipe_update_mode(srctree, rd)
1791
Brad Bishop316dfdd2018-06-25 12:45:53 -04001792 override_branches = []
1793 mainbranch = None
1794 startbranch = None
1795 if not no_overrides:
1796 stdout, _ = bb.process.run('git branch', cwd=srctree)
1797 other_branches = []
1798 for line in stdout.splitlines():
1799 branchname = line[2:]
1800 if line.startswith('* '):
1801 startbranch = branchname
1802 if branchname.startswith(override_branch_prefix):
1803 override_branches.append(branchname)
1804 else:
1805 other_branches.append(branchname)
1806
1807 if override_branches:
1808 logger.debug('_update_recipe: override branches: %s' % override_branches)
1809 logger.debug('_update_recipe: other branches: %s' % other_branches)
1810 if startbranch.startswith(override_branch_prefix):
1811 if len(other_branches) == 1:
1812 mainbranch = other_branches[1]
1813 else:
1814 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1815 else:
1816 mainbranch = startbranch
1817
1818 checkedout = None
1819 anyupdated = False
1820 appendfile = None
1821 allremoved = []
1822 if override_branches:
1823 logger.info('Handling main branch (%s)...' % mainbranch)
1824 if startbranch != mainbranch:
1825 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1826 checkedout = mainbranch
1827 try:
1828 branchlist = [mainbranch] + override_branches
1829 for branch in branchlist:
1830 crd = bb.data.createCopy(rd)
1831 if branch != mainbranch:
1832 logger.info('Handling branch %s...' % branch)
1833 override = branch[len(override_branch_prefix):]
1834 crd.appendVar('OVERRIDES', ':%s' % override)
1835 bb.process.run('git checkout %s' % branch, cwd=srctree)
1836 checkedout = branch
1837
1838 if mode == 'srcrev':
1839 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1840 elif mode == 'patch':
1841 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)
1842 else:
1843 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1844 if updated:
1845 anyupdated = True
1846 if appendf:
1847 appendfile = appendf
1848 allremoved.extend(removed)
1849 finally:
1850 if startbranch and checkedout != startbranch:
1851 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1852
1853 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001854
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001855def update_recipe(args, config, basepath, workspace):
1856 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001857 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001858
1859 if args.append:
1860 if not os.path.exists(args.append):
1861 raise DevtoolError('bbappend destination layer directory "%s" '
1862 'does not exist' % args.append)
1863 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1864 raise DevtoolError('conf/layer.conf not found in bbappend '
1865 'destination layer "%s"' % args.append)
1866
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001867 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001868 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001869
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001870 rd = parse_recipe(config, tinfoil, args.recipename, True)
1871 if not rd:
1872 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001873
Brad Bishop316dfdd2018-06-25 12:45:53 -04001874 dry_run_output = None
1875 dry_run_outdir = None
1876 if args.dry_run:
1877 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1878 dry_run_outdir = dry_run_output.name
1879 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 -05001880
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001881 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001882 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001883 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001884 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 -06001885 finally:
1886 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001887
1888 return 0
1889
1890
1891def status(args, config, basepath, workspace):
1892 """Entry point for the devtool 'status' subcommand"""
1893 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001894 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001895 recipefile = value['recipefile']
1896 if recipefile:
1897 recipestr = ' (%s)' % recipefile
1898 else:
1899 recipestr = ''
1900 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001901 else:
1902 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')
1903 return 0
1904
1905
Brad Bishop64c979e2019-11-04 13:55:29 -05001906def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001907 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001908 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001909
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001910 def clean_preferred_provider(pn, layerconf_path):
1911 """Remove PREFERRED_PROVIDER from layer.conf'"""
1912 import re
1913 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1914 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1915 pprovider_found = False
1916 with open(layerconf_file, 'r') as f:
1917 lines = f.readlines()
1918 with open(new_layerconf_file, 'a') as nf:
1919 for line in lines:
1920 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1921 if not re.match(pprovider_exp, line):
1922 nf.write(line)
1923 else:
1924 pprovider_found = True
1925 if pprovider_found:
1926 shutil.move(new_layerconf_file, layerconf_file)
1927 else:
1928 os.remove(new_layerconf_file)
1929
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001930 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001931 if len(recipes) == 1:
1932 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1933 else:
1934 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001935 # If the recipe file itself was created in the workspace, and
1936 # it uses BBCLASSEXTEND, then we need to also clean the other
1937 # variants
1938 targets = []
1939 for recipe in recipes:
1940 targets.append(recipe)
1941 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001942 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001943 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001944 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001945 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001946 except bb.process.ExecutionError as e:
1947 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1948 'wish, you may specify -n/--no-clean to '
1949 'skip running this command when resetting' %
1950 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001951
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001952 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001953 _check_preserve(config, pn)
1954
Brad Bishop316dfdd2018-06-25 12:45:53 -04001955 appendfile = workspace[pn]['bbappend']
1956 if os.path.exists(appendfile):
1957 # This shouldn't happen, but is possible if devtool errored out prior to
1958 # writing the md5 file. We need to delete this here or the recipe won't
1959 # actually be reset
1960 os.remove(appendfile)
1961
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001962 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001963 def preservedir(origdir):
1964 if os.path.exists(origdir):
1965 for root, dirs, files in os.walk(origdir):
1966 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001967 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001968 _move_file(os.path.join(origdir, fn),
1969 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001970 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001971 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001972 os.rmdir(origdir)
1973
Brad Bishop316dfdd2018-06-25 12:45:53 -04001974 recipefile = workspace[pn]['recipefile']
1975 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1976 # This should always be true if recipefile is set, but just in case
1977 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001978 # We don't automatically create this dir next to appends, but the user can
1979 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1980
Brad Bishop316dfdd2018-06-25 12:45:53 -04001981 srctreebase = workspace[pn]['srctreebase']
1982 if os.path.isdir(srctreebase):
1983 if os.listdir(srctreebase):
Brad Bishop64c979e2019-11-04 13:55:29 -05001984 if remove_work:
1985 logger.info('-r argument used on %s, removing source tree.'
1986 ' You will lose any unsaved work' %pn)
1987 shutil.rmtree(srctreebase)
1988 else:
1989 # We don't want to risk wiping out any work in progress
Patrick Williams92b42cb2022-09-03 06:53:57 -05001990 if srctreebase.startswith(os.path.join(config.workspace_path, 'sources')):
1991 from datetime import datetime
1992 preservesrc = os.path.join(config.workspace_path, 'attic', 'sources', "{}.{}".format(pn,datetime.now().strftime("%Y%m%d%H%M%S")))
1993 logger.info('Preserving source tree in %s\nIf you no '
1994 'longer need it then please delete it manually.\n'
1995 'It is also possible to reuse it via devtool source tree argument.'
1996 % preservesrc)
1997 bb.utils.mkdirhier(os.path.dirname(preservesrc))
1998 shutil.move(srctreebase, preservesrc)
1999 else:
2000 logger.info('Leaving source tree %s as-is; if you no '
2001 'longer need it then please delete it manually'
2002 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002003 else:
2004 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04002005 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002006
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002007 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002008
2009def reset(args, config, basepath, workspace):
2010 """Entry point for the devtool 'reset' subcommand"""
2011 import bb
Brad Bishop64c979e2019-11-04 13:55:29 -05002012 import shutil
2013
2014 recipes = ""
2015
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002016 if args.recipename:
2017 if args.all:
2018 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
2019 else:
2020 for recipe in args.recipename:
2021 check_workspace_recipe(workspace, recipe, checksrc=False)
2022 elif not args.all:
2023 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
2024 "reset all recipes")
2025 if args.all:
2026 recipes = list(workspace.keys())
2027 else:
2028 recipes = args.recipename
2029
Brad Bishop64c979e2019-11-04 13:55:29 -05002030 _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002031
2032 return 0
2033
2034
2035def _get_layer(layername, d):
2036 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002037 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04002038 layers = {} # {basename: layer_paths}
2039 for p in layerdirs:
2040 bn = os.path.basename(p)
2041 if bn not in layers:
2042 layers[bn] = [p]
2043 else:
2044 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002045 # Provide some shortcuts
2046 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04002047 layername = 'meta'
2048 layer_paths = layers.get(layername, None)
2049 if not layer_paths:
2050 return os.path.abspath(layername)
2051 elif len(layer_paths) == 1:
2052 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002053 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04002054 # multiple layers having the same base name
2055 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
2056 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
2057 return os.path.abspath(layer_paths[0])
2058
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002059
2060def finish(args, config, basepath, workspace):
2061 """Entry point for the devtool 'finish' subcommand"""
2062 import bb
2063 import oe.recipeutils
2064
2065 check_workspace_recipe(workspace, args.recipename)
2066
Brad Bishop316dfdd2018-06-25 12:45:53 -04002067 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
2068
2069 # Grab the equivalent of COREBASE without having to initialise tinfoil
2070 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2071
2072 srctree = workspace[args.recipename]['srctree']
2073 check_git_repo_op(srctree, [corebasedir])
2074 dirty = check_git_repo_dirty(srctree)
2075 if dirty:
2076 if args.force:
2077 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2078 else:
2079 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)
2080
Brad Bishop00e122a2019-10-05 11:10:57 -04002081 no_clean = args.no_clean
Brad Bishop64c979e2019-11-04 13:55:29 -05002082 remove_work=args.remove_work
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002083 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2084 try:
Brad Bishop6dbb3162019-11-25 09:41:34 -05002085 rd = parse_recipe(config, tinfoil, args.recipename, True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002086 if not rd:
2087 return 1
2088
2089 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002090 recipefile = rd.getVar('FILE')
2091 recipedir = os.path.dirname(recipefile)
2092 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002093
2094 if not os.path.isdir(destlayerdir):
2095 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2096
2097 if os.path.abspath(destlayerdir) == config.workspace_path:
2098 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2099
2100 # If it's an upgrade, grab the original path
2101 origpath = None
2102 origfilelist = None
2103 append = workspace[args.recipename]['bbappend']
2104 with open(append, 'r') as f:
2105 for line in f:
2106 if line.startswith('# original_path:'):
2107 origpath = line.split(':')[1].strip()
2108 elif line.startswith('# original_files:'):
2109 origfilelist = line.split(':')[1].split()
2110
Brad Bishop316dfdd2018-06-25 12:45:53 -04002111 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2112
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002113 if origlayerdir == config.workspace_path:
2114 # Recipe file itself is in workspace, update it there first
2115 appendlayerdir = None
2116 origrelpath = None
2117 if origpath:
2118 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2119 if origlayerpath:
2120 origrelpath = os.path.relpath(origpath, origlayerpath)
2121 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2122 if not destpath:
2123 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 -05002124 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2125 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002126 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002127 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)
2128
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002129 elif destlayerdir == origlayerdir:
2130 # Same layer, update the original recipe
2131 appendlayerdir = None
2132 destpath = None
2133 else:
2134 # Create/update a bbappend in the specified layer
2135 appendlayerdir = destlayerdir
2136 destpath = None
2137
Brad Bishop316dfdd2018-06-25 12:45:53 -04002138 # Actually update the recipe / bbappend
2139 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2140 dry_run_output = None
2141 dry_run_outdir = None
2142 if args.dry_run:
2143 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2144 dry_run_outdir = dry_run_output.name
2145 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)
2146 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2147
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002148 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002149 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002150 for fn in origfilelist:
2151 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002152 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2153 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2154 if not args.dry_run:
2155 try:
2156 os.remove(fnp)
2157 except FileNotFoundError:
2158 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002159
2160 if origlayerdir == config.workspace_path and destpath:
2161 # Recipe file itself is in the workspace - need to move it and any
2162 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002163 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002164 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002165 for root, _, files in os.walk(recipedir):
2166 for fn in files:
2167 srcpath = os.path.join(root, fn)
2168 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2169 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002170 destfp = os.path.join(destdir, fn)
2171 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002172
Brad Bishop316dfdd2018-06-25 12:45:53 -04002173 if dry_run_outdir:
2174 import difflib
2175 comparelist = []
2176 for root, _, files in os.walk(dry_run_outdir):
2177 for fn in files:
2178 outf = os.path.join(root, fn)
2179 relf = os.path.relpath(outf, dry_run_outdir)
2180 logger.debug('dry-run: output file %s' % relf)
2181 if fn.endswith('.bb'):
2182 if origfilelist and origpath and destpath:
2183 # Need to match this up with the pre-upgrade recipe file
2184 for origf in origfilelist:
2185 if origf.endswith('.bb'):
2186 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2187 outf,
2188 os.path.abspath(os.path.join(destpath, relf))))
2189 break
2190 else:
2191 # Compare to the existing recipe
2192 comparelist.append((recipefile, outf, recipefile))
2193 elif fn.endswith('.bbappend'):
2194 if appendfile:
2195 if os.path.exists(appendfile):
2196 comparelist.append((appendfile, outf, appendfile))
2197 else:
2198 comparelist.append((None, outf, appendfile))
2199 else:
2200 if destpath:
2201 recipedest = destpath
2202 elif appendfile:
2203 recipedest = os.path.dirname(appendfile)
2204 else:
2205 recipedest = os.path.dirname(recipefile)
2206 destfp = os.path.join(recipedest, relf)
2207 if os.path.exists(destfp):
2208 comparelist.append((destfp, outf, destfp))
2209 output = ''
2210 for oldfile, newfile, newfileshow in comparelist:
2211 if oldfile:
2212 with open(oldfile, 'r') as f:
2213 oldlines = f.readlines()
2214 else:
2215 oldfile = '/dev/null'
2216 oldlines = []
2217 with open(newfile, 'r') as f:
2218 newlines = f.readlines()
2219 if not newfileshow:
2220 newfileshow = newfile
2221 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2222 difflines = list(diff)
2223 if difflines:
2224 output += ''.join(difflines)
2225 if output:
2226 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002227 finally:
2228 tinfoil.shutdown()
2229
2230 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002231 if args.dry_run:
2232 logger.info('Resetting recipe (dry-run)')
2233 else:
Brad Bishop64c979e2019-11-04 13:55:29 -05002234 _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002235
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002236 return 0
2237
2238
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002239def get_default_srctree(config, recipename=''):
2240 """Get the default srctree path"""
2241 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2242 if recipename:
2243 return os.path.join(srctreeparent, 'sources', recipename)
2244 else:
2245 return os.path.join(srctreeparent, 'sources')
2246
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002247def register_commands(subparsers, context):
2248 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002249
2250 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002251 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002252 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.',
2253 group='starting', order=100)
2254 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.')
2255 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2256 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 -05002257 group = parser_add.add_mutually_exclusive_group()
2258 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2259 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002260 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 -05002261 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002262 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002263 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 -05002264 group = parser_add.add_mutually_exclusive_group()
2265 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2266 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")
2267 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 -05002268 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')
2269 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')
2270 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002271 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2272 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 -06002273 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002274
2275 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002276 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.',
2277 group='starting', order=90)
2278 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2279 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 -05002280 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002281 group = parser_modify.add_mutually_exclusive_group()
2282 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2283 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 -05002284 group = parser_modify.add_mutually_exclusive_group()
2285 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2286 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002287 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 -04002288 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 -05002289 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002290 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002291
2292 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2293 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002294 group='advanced')
2295 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002296 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002297 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 -04002298 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 -05002299 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002300 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002301
2302 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2303 description='Synchronize the previously extracted source tree for an existing recipe',
2304 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2305 group='advanced')
2306 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2307 parser_sync.add_argument('srctree', help='Path to the source tree')
2308 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2309 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002310 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002311
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002312 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2313 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.',
2314 group='working', order=10)
2315 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2316 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2317 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)')
2318 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')
2319 parser_rename.set_defaults(func=rename)
2320
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002321 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002322 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.',
2323 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002324 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2325 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 -05002326 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002327 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2328 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')
2329 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 -04002330 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2331 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2332 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 -05002333 parser_update_recipe.set_defaults(func=update_recipe)
2334
2335 parser_status = subparsers.add_parser('status', help='Show workspace status',
2336 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002337 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002338 parser_status.set_defaults(func=status)
2339
2340 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002341 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 -05002342 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002343 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002344 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2345 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 -05002346 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 -05002347 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002348
2349 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002350 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 -06002351 group='working', order=-100)
2352 parser_finish.add_argument('recipename', help='Recipe to finish')
2353 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.')
2354 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')
2355 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002356 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 -05002357 parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace')
Brad Bishop00e122a2019-10-05 11:10:57 -04002358 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 -04002359 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2360 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2361 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 -06002362 parser_finish.set_defaults(func=finish)