blob: 0339d1276354a81d77c2e7742d4d8e18942c1784 [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')
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600570 f.write('ERROR_QA:remove = "patch-fuzz"\n')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500571 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
572 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
573 if not is_kernel_yocto:
574 f.write('PATCHTOOL = "git"\n')
575 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400576 if extra_overrides:
577 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500578 f.write('inherit devtool-source\n')
579 f.write('###--- _extract_source\n')
580
581 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
582
583 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
584 bb.utils.mkdirhier(sstate_manifests)
585 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
586 with open(preservestampfile, 'w') as f:
587 f.write(d.getVar('STAMP'))
588 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400589 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500590 # We need to generate the kernel config
591 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500592 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500593 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600595 if 'noexec' in (d.getVarFlags(task, False) or []) or 'task' not in (d.getVarFlags(task, False) or []):
596 logger.info('The %s recipe has %s disabled. Running only '
597 'do_configure task dependencies' % (pn, task))
598
599 if 'depends' in d.getVarFlags('do_configure', False):
600 pn = d.getVarFlags('do_configure', False)['depends']
601 pn = pn.replace('${PV}', d.getVar('PV'))
602 pn = pn.replace('${COMPILERDEP}', d.getVar('COMPILERDEP'))
603 task = None
604
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500605 # Run the fetch + unpack tasks
606 res = tinfoil.build_targets(pn,
607 task,
608 handle_events=True)
609 finally:
610 if os.path.exists(preservestampfile):
611 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500612
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500613 if not res:
614 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500615
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600616 if not is_kernel_yocto and ('noexec' in (d.getVarFlags('do_patch', False) or []) or 'task' not in (d.getVarFlags('do_patch', False) or [])):
617 workshareddir = d.getVar('S')
618 if os.path.islink(srctree):
619 os.unlink(srctree)
620
621 os.symlink(workshareddir, srctree)
622
623 # The initial_rev file is created in devtool_post_unpack function that will not be executed if
624 # do_unpack/do_patch tasks are disabled so we have to directly say that source extraction was successful
625 return True, True
626
Brad Bishop316dfdd2018-06-25 12:45:53 -0400627 try:
628 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
629 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500630
Brad Bishop316dfdd2018-06-25 12:45:53 -0400631 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
632 srcsubdir = f.read()
633 except FileNotFoundError as e:
634 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
635 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500636
Brad Bishop96ff1982019-08-19 13:50:42 -0400637 # Check if work-shared is empty, if yes
638 # find source and copy to work-shared
639 if is_kernel_yocto:
640 workshareddir = d.getVar('STAGING_KERNEL_DIR')
641 staging_kerVer = get_staging_kver(workshareddir)
642 kernelVersion = d.getVar('LINUX_VERSION')
643
644 # handle dangling symbolic link in work-shared:
645 if os.path.islink(workshareddir):
646 os.unlink(workshareddir)
647
648 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
649 shutil.rmtree(workshareddir)
650 oe.path.copyhardlinktree(srcsubdir,workshareddir)
651 elif not os.path.exists(workshareddir):
652 oe.path.copyhardlinktree(srcsubdir,workshareddir)
653
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500654 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
655 srctree_localdir = os.path.join(srctree, 'oe-local-files')
656
657 if sync:
658 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
659
660 # Move oe-local-files directory to srctree
661 # As the oe-local-files is not part of the constructed git tree,
662 # remove them directly during the synchrounizating might surprise
663 # the users. Instead, we move it to oe-local-files.bak and remind
664 # user in the log message.
665 if os.path.exists(srctree_localdir + '.bak'):
666 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
667
668 if os.path.exists(srctree_localdir):
669 logger.info('Backing up current local file directory %s' % srctree_localdir)
670 shutil.move(srctree_localdir, srctree_localdir + '.bak')
671
672 if os.path.exists(tempdir_localdir):
673 logger.info('Syncing local source files to srctree...')
674 shutil.copytree(tempdir_localdir, srctree_localdir)
675 else:
676 # Move oe-local-files directory to srctree
677 if os.path.exists(tempdir_localdir):
678 logger.info('Adding local source files to srctree...')
679 shutil.move(tempdir_localdir, srcsubdir)
680
681 shutil.move(srcsubdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400682 symlink_oelocal_files_srctree(d,srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500683
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500684 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500685 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500686 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500687
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500688 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500689 if appendbackup:
690 shutil.copyfile(appendbackup, appendfile)
691 elif os.path.exists(appendfile):
692 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500693 if keep_temp:
694 logger.info('Preserving temporary directory %s' % tempdir)
695 else:
696 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400697 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500698
699def _add_md5(config, recipename, filename):
700 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
701 import bb.utils
702
703 def addfile(fn):
704 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500705 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
706 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
707 f.seek(0, os.SEEK_SET)
708 if not md5_str in f.read():
709 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500710
711 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500712 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500713 for f in files:
714 addfile(os.path.join(root, f))
715 else:
716 addfile(filename)
717
718def _check_preserve(config, recipename):
719 """Check if a file was manually changed and needs to be saved in 'attic'
720 directory"""
721 import bb.utils
722 origfile = os.path.join(config.workspace_path, '.devtool_md5')
723 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500724 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500725 with open(origfile, 'r') as f:
726 with open(newfile, 'w') as tf:
727 for line in f.readlines():
728 splitline = line.rstrip().split('|')
729 if splitline[0] == recipename:
730 removefile = os.path.join(config.workspace_path, splitline[1])
731 try:
732 md5 = bb.utils.md5_file(removefile)
733 except IOError as err:
734 if err.errno == 2:
735 # File no longer exists, skip it
736 continue
737 else:
738 raise
739 if splitline[2] != md5:
740 bb.utils.mkdirhier(preservepath)
741 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800742 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500743 shutil.move(removefile, os.path.join(preservepath, preservefile))
744 else:
745 os.remove(removefile)
746 else:
747 tf.write(line)
Andrew Geisslerc926e172021-05-07 16:11:35 -0500748 bb.utils.rename(newfile, origfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500749
Brad Bishop96ff1982019-08-19 13:50:42 -0400750def get_staging_kver(srcdir):
751 # Kernel version from work-shared
752 kerver = []
753 staging_kerVer=""
754 if os.path.exists(srcdir) and os.listdir(srcdir):
755 with open(os.path.join(srcdir,"Makefile")) as f:
756 version = [next(f) for x in range(5)][1:4]
757 for word in version:
758 kerver.append(word.split('= ')[1].split('\n')[0])
759 staging_kerVer = ".".join(kerver)
760 return staging_kerVer
761
762def get_staging_kbranch(srcdir):
763 staging_kbranch = ""
764 if os.path.exists(srcdir) and os.listdir(srcdir):
765 (branch, _) = bb.process.run('git branch | grep \* | cut -d \' \' -f2', cwd=srcdir)
766 staging_kbranch = "".join(branch.split('\n')[0])
767 return staging_kbranch
768
Andrew Geissler517393d2023-01-13 08:55:19 -0600769def get_real_srctree(srctree, s, workdir):
770 # Check that recipe isn't using a shared workdir
771 s = os.path.abspath(s)
772 workdir = os.path.abspath(workdir)
773 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
774 # Handle if S is set to a subdirectory of the source
775 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
776 srctree = os.path.join(srctree, srcsubdir)
777 return srctree
778
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500779def modify(args, config, basepath, workspace):
780 """Entry point for the devtool 'modify' subcommand"""
781 import bb
782 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400783 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400784 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500785
786 if args.recipename in workspace:
787 raise DevtoolError("recipe %s is already in your workspace" %
788 args.recipename)
789
Brad Bishop316dfdd2018-06-25 12:45:53 -0400790 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600791 try:
792 rd = parse_recipe(config, tinfoil, args.recipename, True)
793 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500794 return 1
795
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500796 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600797 if pn != args.recipename:
798 logger.info('Mapping %s to %s' % (args.recipename, pn))
799 if pn in workspace:
800 raise DevtoolError("recipe %s is already in your workspace" %
801 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500802
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600803 if args.srctree:
804 srctree = os.path.abspath(args.srctree)
805 else:
806 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500807
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600808 if args.no_extract and not os.path.isdir(srctree):
809 raise DevtoolError("--no-extract specified and source path %s does "
810 "not exist or is not a directory" %
811 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600812
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500813 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600814 appendfile = recipe_to_append(recipefile, config, args.wildcard)
815 if os.path.exists(appendfile):
816 raise DevtoolError("Another variant of recipe %s is already in your "
817 "workspace (only one variant of a recipe can "
818 "currently be worked on at once)"
819 % pn)
820
821 _check_compatible_recipe(pn, rd)
822
823 initial_rev = None
824 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400825 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400826
827 if bb.data.inherits_class('kernel-yocto', rd):
828 # Current set kernel version
829 kernelVersion = rd.getVar('LINUX_VERSION')
830 srcdir = rd.getVar('STAGING_KERNEL_DIR')
831 kbranch = rd.getVar('KBRANCH')
832
833 staging_kerVer = get_staging_kver(srcdir)
834 staging_kbranch = get_staging_kbranch(srcdir)
835 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
836 oe.path.copyhardlinktree(srcdir,srctree)
837 workdir = rd.getVar('WORKDIR')
838 srcsubdir = rd.getVar('S')
839 localfilesdir = os.path.join(srctree,'oe-local-files')
840 # Move local source files into separate subdir
841 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
842 local_files = oe.recipeutils.get_recipe_local_files(rd)
843
844 for key in local_files.copy():
845 if key.endswith('scc'):
846 sccfile = open(local_files[key], 'r')
847 for l in sccfile:
848 line = l.split()
849 if line and line[0] in ('kconf', 'patch'):
850 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
851 if not cfg in local_files.values():
852 local_files[line[-1]] = cfg
853 shutil.copy2(cfg, workdir)
854 sccfile.close()
855
856 # Ignore local files with subdir={BP}
857 srcabspath = os.path.abspath(srcsubdir)
858 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))]
859 if local_files:
860 for fname in local_files:
861 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
862 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
863 f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n')
864
865 symlink_oelocal_files_srctree(rd,srctree)
866
867 task = 'do_configure'
868 res = tinfoil.build_targets(pn, task, handle_events=True)
869
870 # Copy .config to workspace
871 kconfpath = rd.getVar('B')
872 logger.info('Copying kernel config to workspace')
873 shutil.copy2(os.path.join(kconfpath, '.config'),srctree)
874
875 # Set this to true, we still need to get initial_rev
876 # by parsing the git repo
877 args.no_extract = True
878
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600879 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400880 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 -0500881 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600882 return 1
883 logger.info('Source tree extracted to %s' % srctree)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600884 if os.path.exists(os.path.join(srctree, '.git')):
885 # Get list of commits since this revision
886 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
887 commits = stdout.split()
888 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600889 else:
890 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600891 # Check if it's a tree previously extracted by us. This is done
892 # by ensuring that devtool-base and args.branch (devtool) exist.
893 # The check_commits logic will cause an exception if either one
894 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600895 try:
896 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600897 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600898 except bb.process.ExecutionError:
899 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400900 if stdout:
901 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600902 for line in stdout.splitlines():
903 if line.startswith('*'):
904 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
905 initial_rev = stdout.rstrip()
906 if not initial_rev:
907 # Otherwise, just grab the head revision
908 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
909 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500910
Brad Bishop316dfdd2018-06-25 12:45:53 -0400911 branch_patches = {}
912 if check_commits:
913 # Check if there are override branches
914 (stdout, _) = bb.process.run('git branch', cwd=srctree)
915 branches = []
916 for line in stdout.rstrip().splitlines():
917 branchname = line[2:].rstrip()
918 if branchname.startswith(override_branch_prefix):
919 branches.append(branchname)
920 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800921 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 -0400922 branches.insert(0, args.branch)
923 seen_patches = []
924 for branch in branches:
925 branch_patches[branch] = []
926 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
927 for line in stdout.splitlines():
928 line = line.strip()
929 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
930 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
931 if not origpatch in seen_patches:
932 seen_patches.append(origpatch)
933 branch_patches[branch].append(origpatch)
934
935 # Need to grab this here in case the source is within a subdirectory
936 srctreebase = srctree
Andrew Geissler517393d2023-01-13 08:55:19 -0600937 srctree = get_real_srctree(srctree, rd.getVar('S'), rd.getVar('WORKDIR'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500938
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600939 bb.utils.mkdirhier(os.path.dirname(appendfile))
940 with open(appendfile, 'w') as f:
Patrick Williams213cb262021-08-07 19:21:33 -0500941 f.write('FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600942 # Local files can be modified/tracked in separate subdir under srctree
943 # Mostly useful for packages with S != WORKDIR
Patrick Williams213cb262021-08-07 19:21:33 -0500944 f.write('FILESPATH:prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400945 os.path.join(srctreebase, 'oe-local-files'))
946 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500947
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600948 f.write('\ninherit externalsrc\n')
949 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 -0500950 f.write('EXTERNALSRC:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500951
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600952 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
953 if b_is_s:
Patrick Williams213cb262021-08-07 19:21:33 -0500954 f.write('EXTERNALSRC_BUILD:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500955
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600956 if bb.data.inherits_class('kernel', rd):
957 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500958 'do_fetch do_unpack do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400959 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500960 f.write('\ndo_configure:append() {\n'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600961 ' cp ${B}/.config ${S}/.config.baseline\n'
962 ' ln -sfT ${B}/.config ${S}/.config.new\n'
963 '}\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500964 f.write('\ndo_kernel_configme:prepend() {\n'
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500965 ' if [ -e ${S}/.config ]; then\n'
966 ' mv ${S}/.config ${S}/.config.old\n'
967 ' fi\n'
968 '}\n')
Brad Bishop96ff1982019-08-19 13:50:42 -0400969 if rd.getVarFlag('do_menuconfig','task'):
Patrick Williams213cb262021-08-07 19:21:33 -0500970 f.write('\ndo_configure:append() {\n'
Andrew Geissler82c905d2020-04-13 13:39:40 -0500971 ' if [ ! ${DEVTOOL_DISABLE_MENUCONFIG} ]; then\n'
972 ' cp ${B}/.config ${S}/.config.baseline\n'
973 ' ln -sfT ${B}/.config ${S}/.config.new\n'
974 ' fi\n'
Brad Bishop96ff1982019-08-19 13:50:42 -0400975 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600976 if initial_rev:
977 f.write('\n# initial_rev: %s\n' % initial_rev)
978 for commit in commits:
979 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400980 if branch_patches:
981 for branch in branch_patches:
982 if branch == args.branch:
983 continue
984 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500985
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500986 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
987
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600988 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500989
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600990 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500991
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600992 finally:
993 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500994
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500995 return 0
996
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500997
998def rename(args, config, basepath, workspace):
999 """Entry point for the devtool 'rename' subcommand"""
1000 import bb
1001 import oe.recipeutils
1002
1003 check_workspace_recipe(workspace, args.recipename)
1004
1005 if not (args.newname or args.version):
1006 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
1007
1008 recipefile = workspace[args.recipename]['recipefile']
1009 if not recipefile:
1010 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
1011
1012 if args.newname and args.newname != args.recipename:
1013 reason = oe.recipeutils.validate_pn(args.newname)
1014 if reason:
1015 raise DevtoolError(reason)
1016 newname = args.newname
1017 else:
1018 newname = args.recipename
1019
1020 append = workspace[args.recipename]['bbappend']
1021 appendfn = os.path.splitext(os.path.basename(append))[0]
1022 splitfn = appendfn.split('_')
1023 if len(splitfn) > 1:
1024 origfnver = appendfn.split('_')[1]
1025 else:
1026 origfnver = ''
1027
1028 recipefilemd5 = None
1029 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1030 try:
1031 rd = parse_recipe(config, tinfoil, args.recipename, True)
1032 if not rd:
1033 return 1
1034
1035 bp = rd.getVar('BP')
1036 bpn = rd.getVar('BPN')
1037 if newname != args.recipename:
1038 localdata = rd.createCopy()
1039 localdata.setVar('PN', newname)
1040 newbpn = localdata.getVar('BPN')
1041 else:
1042 newbpn = bpn
1043 s = rd.getVar('S', False)
1044 src_uri = rd.getVar('SRC_URI', False)
1045 pv = rd.getVar('PV')
1046
1047 # Correct variable values that refer to the upstream source - these
1048 # values must stay the same, so if the name/version are changing then
1049 # we need to fix them up
1050 new_s = s
1051 new_src_uri = src_uri
1052 if newbpn != bpn:
1053 # ${PN} here is technically almost always incorrect, but people do use it
1054 new_s = new_s.replace('${BPN}', bpn)
1055 new_s = new_s.replace('${PN}', bpn)
1056 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1057 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1058 new_src_uri = new_src_uri.replace('${PN}', bpn)
1059 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1060 if args.version and origfnver == pv:
1061 new_s = new_s.replace('${PV}', pv)
1062 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1063 new_src_uri = new_src_uri.replace('${PV}', pv)
1064 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1065 patchfields = {}
1066 if new_s != s:
1067 patchfields['S'] = new_s
1068 if new_src_uri != src_uri:
1069 patchfields['SRC_URI'] = new_src_uri
1070 if patchfields:
1071 recipefilemd5 = bb.utils.md5_file(recipefile)
1072 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1073 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1074 finally:
1075 tinfoil.shutdown()
1076
1077 if args.version:
1078 newver = args.version
1079 else:
1080 newver = origfnver
1081
1082 if newver:
1083 newappend = '%s_%s.bbappend' % (newname, newver)
1084 newfile = '%s_%s.bb' % (newname, newver)
1085 else:
1086 newappend = '%s.bbappend' % newname
1087 newfile = '%s.bb' % newname
1088
1089 oldrecipedir = os.path.dirname(recipefile)
1090 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1091 if oldrecipedir != newrecipedir:
1092 bb.utils.mkdirhier(newrecipedir)
1093
1094 newappend = os.path.join(os.path.dirname(append), newappend)
1095 newfile = os.path.join(newrecipedir, newfile)
1096
1097 # Rename bbappend
1098 logger.info('Renaming %s to %s' % (append, newappend))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001099 bb.utils.rename(append, newappend)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001100 # Rename recipe file
1101 logger.info('Renaming %s to %s' % (recipefile, newfile))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001102 bb.utils.rename(recipefile, newfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001103
1104 # Rename source tree if it's the default path
1105 appendmd5 = None
1106 if not args.no_srctree:
1107 srctree = workspace[args.recipename]['srctree']
1108 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1109 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1110 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1111 shutil.move(srctree, newsrctree)
1112 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1113 appendmd5 = bb.utils.md5_file(newappend)
1114 appendlines = []
1115 with open(newappend, 'r') as f:
1116 for line in f:
1117 appendlines.append(line)
1118 with open(newappend, 'w') as f:
1119 for line in appendlines:
1120 if srctree in line:
1121 line = line.replace(srctree, newsrctree)
1122 f.write(line)
1123 newappendmd5 = bb.utils.md5_file(newappend)
1124
1125 bpndir = None
1126 newbpndir = None
1127 if newbpn != bpn:
1128 bpndir = os.path.join(oldrecipedir, bpn)
1129 if os.path.exists(bpndir):
1130 newbpndir = os.path.join(newrecipedir, newbpn)
1131 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1132 shutil.move(bpndir, newbpndir)
1133
1134 bpdir = None
1135 newbpdir = None
1136 if newver != origfnver or newbpn != bpn:
1137 bpdir = os.path.join(oldrecipedir, bp)
1138 if os.path.exists(bpdir):
1139 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1140 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1141 shutil.move(bpdir, newbpdir)
1142
1143 if oldrecipedir != newrecipedir:
1144 # Move any stray files and delete the old recipe directory
1145 for entry in os.listdir(oldrecipedir):
1146 oldpath = os.path.join(oldrecipedir, entry)
1147 newpath = os.path.join(newrecipedir, entry)
1148 logger.info('Renaming %s to %s' % (oldpath, newpath))
1149 shutil.move(oldpath, newpath)
1150 os.rmdir(oldrecipedir)
1151
1152 # Now take care of entries in .devtool_md5
1153 md5entries = []
1154 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1155 for line in f:
1156 md5entries.append(line)
1157
1158 if bpndir and newbpndir:
1159 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1160 else:
1161 relbpndir = None
1162 if bpdir and newbpdir:
1163 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1164 else:
1165 relbpdir = None
1166
1167 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1168 for entry in md5entries:
1169 splitentry = entry.rstrip().split('|')
1170 if len(splitentry) > 2:
1171 if splitentry[0] == args.recipename:
1172 splitentry[0] = newname
1173 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1174 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1175 if appendmd5 and splitentry[2] == appendmd5:
1176 splitentry[2] = newappendmd5
1177 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1178 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1179 if recipefilemd5 and splitentry[2] == recipefilemd5:
1180 splitentry[2] = newrecipefilemd5
1181 elif relbpndir and splitentry[1].startswith(relbpndir):
1182 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1183 elif relbpdir and splitentry[1].startswith(relbpdir):
1184 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1185 entry = '|'.join(splitentry) + '\n'
1186 f.write(entry)
1187 return 0
1188
1189
Brad Bishop316dfdd2018-06-25 12:45:53 -04001190def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001191 """Get initial and update rev of a recipe. These are the start point of the
1192 whole patchset and start point for the patches to be re-generated/updated.
1193 """
1194 import bb
1195
Brad Bishop316dfdd2018-06-25 12:45:53 -04001196 # Get current branch
1197 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1198 cwd=srctree)
1199 branchname = stdout.rstrip()
1200
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001201 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001202 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001203 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001204 with open(recipe_path, 'r') as f:
1205 for line in f:
1206 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001207 if not initial_rev:
1208 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001209 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001210 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001211 elif line.startswith('# patches_%s:' % branchname):
1212 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001213
1214 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001215 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001216 if initial_rev:
1217 # Find first actually changed revision
1218 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1219 initial_rev, cwd=srctree)
1220 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001221 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001222 if newcommits[i] == commits[i]:
1223 update_rev = commits[i]
1224
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001225 try:
1226 stdout, _ = bb.process.run('git cherry devtool-patched',
1227 cwd=srctree)
1228 except bb.process.ExecutionError as err:
1229 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001230
Brad Bishop316dfdd2018-06-25 12:45:53 -04001231 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001232 changed_revs = []
1233 for line in stdout.splitlines():
1234 if line.startswith('+ '):
1235 rev = line.split()[1]
1236 if rev in newcommits:
1237 changed_revs.append(rev)
1238
Brad Bishop316dfdd2018-06-25 12:45:53 -04001239 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001240
1241def _remove_file_entries(srcuri, filelist):
1242 """Remove file:// entries from SRC_URI"""
1243 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001244 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001245 for fname in filelist:
1246 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001247 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001248 if (srcuri[i].startswith('file://') and
1249 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001250 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001251 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001252 srcuri.pop(i)
1253 break
1254 return entries, remaining
1255
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001256def _replace_srcuri_entry(srcuri, filename, newentry):
1257 """Replace entry corresponding to specified file with a new entry"""
1258 basename = os.path.basename(filename)
1259 for i in range(len(srcuri)):
1260 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1261 srcuri.pop(i)
1262 srcuri.insert(i, newentry)
1263 break
1264
Brad Bishop316dfdd2018-06-25 12:45:53 -04001265def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001266 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001267
1268 dry_run_suffix = ' (dry-run)' if dry_run else ''
1269
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001270 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001271 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001272 if not destpath:
1273 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001274 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001275
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001276 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001277 if not no_report_remove:
1278 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1279 if not dry_run:
1280 # FIXME "git rm" here would be nice if the file in question is
1281 # tracked
1282 # FIXME there's a chance that this file is referred to by
1283 # another recipe, in which case deleting wouldn't be the
1284 # right thing to do
1285 os.remove(path)
1286 # Remove directory if empty
1287 try:
1288 os.rmdir(os.path.dirname(path))
1289 except OSError as ose:
1290 if ose.errno != errno.ENOTEMPTY:
1291 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001292
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001293
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001294def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001295 """Export patches from srctree to given location.
1296 Returns three-tuple of dicts:
1297 1. updated - patches that already exist in SRCURI
1298 2. added - new patches that don't exist in SRCURI
1299 3 removed - patches that exist in SRCURI but not in exported patches
1300 In each dict the key is the 'basepath' of the URI and value is the
1301 absolute path to the existing file in recipe space (if any).
1302 """
1303 import oe.recipeutils
1304 from oe.patch import GitApplyTree
1305 updated = OrderedDict()
1306 added = OrderedDict()
1307 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1308
1309 existing_patches = dict((os.path.basename(path), path) for path in
1310 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001311 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001312
1313 # Generate patches from Git, exclude local files directory
1314 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1315 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1316
1317 new_patches = sorted(os.listdir(destdir))
1318 for new_patch in new_patches:
1319 # Strip numbering from patch names. If it's a git sequence named patch,
1320 # the numbers might not match up since we are starting from a different
1321 # revision This does assume that people are using unique shortlog
1322 # values, but they ought to be anyway...
1323 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001324 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001325 for old_patch in existing_patches:
1326 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001327 old_basename_splitext = os.path.splitext(old_basename)
1328 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1329 old_patch_noext = os.path.splitext(old_patch)[0]
1330 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001331 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001332 elif new_basename == old_basename:
1333 match_name = old_patch
1334 break
1335 if match_name:
1336 # Rename patch files
1337 if new_patch != match_name:
Andrew Geisslerc926e172021-05-07 16:11:35 -05001338 bb.utils.rename(os.path.join(destdir, new_patch),
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001339 os.path.join(destdir, match_name))
1340 # Need to pop it off the list now before checking changed_revs
1341 oldpath = existing_patches.pop(old_patch)
1342 if changed_revs is not None:
1343 # Avoid updating patches that have not actually changed
1344 with open(os.path.join(destdir, match_name), 'r') as f:
1345 firstlineitems = f.readline().split()
1346 # Looking for "From <hash>" line
1347 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1348 if not firstlineitems[1] in changed_revs:
1349 continue
1350 # Recompress if necessary
1351 if oldpath.endswith(('.gz', '.Z')):
1352 bb.process.run(['gzip', match_name], cwd=destdir)
1353 if oldpath.endswith('.gz'):
1354 match_name += '.gz'
1355 else:
1356 match_name += '.Z'
1357 elif oldpath.endswith('.bz2'):
1358 bb.process.run(['bzip2', match_name], cwd=destdir)
1359 match_name += '.bz2'
1360 updated[match_name] = oldpath
1361 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001362 added[new_patch] = None
1363 return (updated, added, existing_patches)
1364
1365
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001366def _create_kconfig_diff(srctree, rd, outfile):
1367 """Create a kconfig fragment"""
1368 # Only update config fragment if both config files exist
1369 orig_config = os.path.join(srctree, '.config.baseline')
1370 new_config = os.path.join(srctree, '.config.new')
1371 if os.path.exists(orig_config) and os.path.exists(new_config):
1372 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1373 '--unchanged-line-format=', orig_config, new_config]
1374 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1375 stderr=subprocess.PIPE)
1376 stdout, stderr = pipe.communicate()
1377 if pipe.returncode == 1:
1378 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001379 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001380 fobj.write(stdout)
1381 elif pipe.returncode == 0:
1382 logger.info("Would remove config fragment %s" % outfile)
1383 if os.path.exists(outfile):
1384 # Remove fragment file in case of empty diff
1385 logger.info("Removing config fragment %s" % outfile)
1386 os.unlink(outfile)
1387 else:
1388 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1389 return True
1390 return False
1391
1392
Brad Bishop316dfdd2018-06-25 12:45:53 -04001393def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001394 """Copy local files from srctree to given location.
1395 Returns three-tuple of dicts:
1396 1. updated - files that already exist in SRCURI
1397 2. added - new files files that don't exist in SRCURI
1398 3 removed - files that exist in SRCURI but not in exported files
1399 In each dict the key is the 'basepath' of the URI and value is the
1400 absolute path to the existing file in recipe space (if any).
1401 """
1402 import oe.recipeutils
1403
1404 # Find out local files (SRC_URI files that exist in the "recipe space").
1405 # Local files that reside in srctree are not included in patch generation.
1406 # Instead they are directly copied over the original source files (in
1407 # recipe space).
1408 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1409 new_set = None
1410 updated = OrderedDict()
1411 added = OrderedDict()
1412 removed = OrderedDict()
Andrew Geissler517393d2023-01-13 08:55:19 -06001413
1414 # Get current branch and return early with empty lists
1415 # if on one of the override branches
1416 # (local files are provided only for the main branch and processing
1417 # them against lists from recipe overrides will result in mismatches
1418 # and broken modifications to recipes).
1419 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1420 cwd=srctree)
1421 branchname = stdout.rstrip()
1422 if branchname.startswith(override_branch_prefix):
1423 return (updated, added, removed)
1424
Brad Bishop316dfdd2018-06-25 12:45:53 -04001425 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001426 git_files = _git_ls_tree(srctree)
1427 if 'oe-local-files' in git_files:
1428 # If tracked by Git, take the files from srctree HEAD. First get
1429 # the tree object of the directory
1430 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1431 tree = git_files['oe-local-files'][2]
1432 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1433 env=dict(os.environ, GIT_WORK_TREE=destdir,
1434 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001435 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001436 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001437 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001438 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001439 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001440 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001441 else:
1442 new_set = []
1443
1444 # Special handling for kernel config
1445 if bb.data.inherits_class('kernel-yocto', rd):
1446 fragment_fn = 'devtool-fragment.cfg'
1447 fragment_path = os.path.join(destdir, fragment_fn)
1448 if _create_kconfig_diff(srctree, rd, fragment_path):
1449 if os.path.exists(fragment_path):
1450 if fragment_fn not in new_set:
1451 new_set.append(fragment_fn)
1452 # Copy fragment to local-files
1453 if os.path.isdir(local_files_dir):
1454 shutil.copy2(fragment_path, local_files_dir)
1455 else:
1456 if fragment_fn in new_set:
1457 new_set.remove(fragment_fn)
1458 # Remove fragment from local-files
1459 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1460 os.unlink(os.path.join(local_files_dir, fragment_fn))
1461
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001462 # Special handling for cml1, ccmake, etc bbclasses that generated
1463 # configuration fragment files that are consumed as source files
1464 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1465 if bb.data.inherits_class(frag_class, rd):
1466 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1467 if os.path.exists(srcpath):
1468 if frag_name not in new_set:
1469 new_set.append(frag_name)
1470 # copy fragment into destdir
1471 shutil.copy2(srcpath, destdir)
1472 # copy fragment into local files if exists
1473 if os.path.isdir(local_files_dir):
1474 shutil.copy2(srcpath, local_files_dir)
1475
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001476 if new_set is not None:
1477 for fname in new_set:
1478 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001479 origpath = existing_files.pop(fname)
1480 workpath = os.path.join(local_files_dir, fname)
1481 if not filecmp.cmp(origpath, workpath):
1482 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001483 elif fname != '.gitignore':
1484 added[fname] = None
1485
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001486 workdir = rd.getVar('WORKDIR')
1487 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001488 if not s.endswith(os.sep):
1489 s += os.sep
1490
1491 if workdir != s:
1492 # Handle files where subdir= was specified
1493 for fname in list(existing_files.keys()):
1494 # FIXME handle both subdir starting with BP and not?
1495 fworkpath = os.path.join(workdir, fname)
1496 if fworkpath.startswith(s):
1497 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1498 if os.path.exists(fpath):
1499 origpath = existing_files.pop(fname)
1500 if not filecmp.cmp(origpath, fpath):
1501 updated[fpath] = origpath
1502
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001503 removed = existing_files
1504 return (updated, added, removed)
1505
1506
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001507def _determine_files_dir(rd):
1508 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001509 recipedir = rd.getVar('FILE_DIRNAME')
1510 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001511 relpth = os.path.relpath(entry, recipedir)
1512 if not os.sep in relpth:
1513 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1514 if os.path.isdir(entry):
1515 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001516 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001517
1518
Brad Bishop316dfdd2018-06-25 12:45:53 -04001519def _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 -05001520 """Implement the 'srcrev' mode of update-recipe"""
1521 import bb
1522 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001523
Brad Bishop316dfdd2018-06-25 12:45:53 -04001524 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1525
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001526 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001527 recipedir = os.path.basename(recipefile)
1528 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001529
1530 # Get HEAD revision
1531 try:
1532 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1533 except bb.process.ExecutionError as err:
1534 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1535 (srctree, err))
1536 srcrev = stdout.strip()
1537 if len(srcrev) != 40:
1538 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1539
1540 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001541 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001542 patchfields = {}
1543 patchfields['SRCREV'] = srcrev
1544 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001545 srcuri = orig_src_uri.split()
1546 tempdir = tempfile.mkdtemp(prefix='devtool')
1547 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001548 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001549 try:
1550 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001551 srctreebase = workspace[recipename]['srctreebase']
1552 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001553 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001554 # Find list of existing patches in recipe file
1555 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001556 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001557 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1558 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001559 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 -05001560
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001561 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001562 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001563 if remove_files:
1564 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1565 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001566
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001567 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001568 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001569 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001570 removevalues = {}
1571 if update_srcuri:
1572 removevalues = {'SRC_URI': removedentries}
1573 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001574 if dry_run_outdir:
1575 logger.info('Creating bbappend (dry-run)')
1576 else:
1577 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1578 rd, appendlayerdir, files, wildcardver=wildcard_version,
1579 extralines=patchfields, removevalues=removevalues,
1580 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001581 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001582 files_dir = _determine_files_dir(rd)
1583 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001584 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001585 if os.path.isabs(basepath):
1586 # Original file (probably with subdir pointing inside source tree)
1587 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001588 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001589 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001590 _move_file(os.path.join(local_files_dir, basepath), path,
1591 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001592 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001593 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001594 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001595 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001596 os.path.join(files_dir, basepath),
1597 dry_run_outdir=dry_run_outdir,
1598 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001599 srcuri.append('file://%s' % basepath)
1600 update_srcuri = True
1601 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001602 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001603 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001604 finally:
1605 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001606 if not 'git://' in orig_src_uri:
1607 logger.info('You will need to update SRC_URI within the recipe to '
1608 'point to a git repository where you have pushed your '
1609 'changes')
1610
Brad Bishop316dfdd2018-06-25 12:45:53 -04001611 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1612 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001613
Brad Bishop316dfdd2018-06-25 12:45:53 -04001614def _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 -05001615 """Implement the 'patch' mode of update-recipe"""
1616 import bb
1617 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001618
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001619 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001620 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001621 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001622 if not os.path.exists(append):
1623 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001624 recipename)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001625 srctreebase = workspace[recipename]['srctreebase']
1626 relpatchdir = os.path.relpath(srctreebase, srctree)
1627 if relpatchdir == '.':
1628 patchdir_params = {}
1629 else:
1630 patchdir_params = {'patchdir': relpatchdir}
1631
1632 def srcuri_entry(fname):
1633 if patchdir_params:
1634 paramstr = ';' + ';'.join('%s=%s' % (k,v) for k,v in patchdir_params.items())
1635 else:
1636 paramstr = ''
1637 return 'file://%s%s' % (basepath, paramstr)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001638
Brad Bishop316dfdd2018-06-25 12:45:53 -04001639 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 -05001640 if not initial_rev:
1641 raise DevtoolError('Unable to find initial revision - please specify '
1642 'it with --initial-rev')
1643
Brad Bishop316dfdd2018-06-25 12:45:53 -04001644 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001645 dl_dir = rd.getVar('DL_DIR')
1646 if not dl_dir.endswith('/'):
1647 dl_dir += '/'
1648
Brad Bishop316dfdd2018-06-25 12:45:53 -04001649 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1650
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001651 tempdir = tempfile.mkdtemp(prefix='devtool')
1652 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001653 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Andrew Geissler517393d2023-01-13 08:55:19 -06001654 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001655
1656 # Get updated patches from source tree
1657 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001658 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1659 patches_dir, changed_revs)
Andrew Geissler517393d2023-01-13 08:55:19 -06001660 # Get all patches from source tree and check if any should be removed
1661 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
1662 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1663 all_patches_dir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001664 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1665 if filter_patches:
Brad Bishop00e122a2019-10-05 11:10:57 -04001666 new_p = OrderedDict()
1667 upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches)
Andrew Geissler517393d2023-01-13 08:55:19 -06001668 del_p = OrderedDict((k,v) for k,v in del_p.items() if k in filter_patches)
1669 remove_files = []
1670 if not no_remove:
1671 # Remove deleted local files and patches
1672 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001673 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001674 updaterecipe = False
1675 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001676 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001677 if appendlayerdir:
Brad Bishop00e122a2019-10-05 11:10:57 -04001678 files = OrderedDict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001679 key, val in list(upd_f.items()) + list(new_f.items()))
Brad Bishop00e122a2019-10-05 11:10:57 -04001680 files.update(OrderedDict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001681 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001682 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001683 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001684 if remove_files:
1685 removedentries, remaining = _remove_file_entries(
1686 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001687 if removedentries or remaining:
Andrew Geissler615f2f12022-07-15 14:00:58 -05001688 remaining = [srcuri_entry(os.path.basename(item)) for
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001689 item in remaining]
1690 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001691 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001692 rd, appendlayerdir, files,
1693 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001694 removevalues=removevalues,
Andrew Geissler615f2f12022-07-15 14:00:58 -05001695 redirect_output=dry_run_outdir,
1696 params=[patchdir_params] * len(files))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001697 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001698 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001699 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001700 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001701 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001702 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001703 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001704 if os.path.isabs(basepath):
1705 # Original file (probably with subdir pointing inside source tree)
1706 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001707 _copy_file(basepath, path,
1708 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001709 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001710 _move_file(os.path.join(local_files_dir, basepath), path,
1711 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001712 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001713 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001714 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001715 if os.path.dirname(path) + '/' == dl_dir:
1716 # This is a a downloaded patch file - we now need to
1717 # replace the entry in SRC_URI with our local version
1718 logger.info('Replacing remote patch %s with updated local version' % basepath)
1719 path = os.path.join(files_dir, basepath)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001720 _replace_srcuri_entry(srcuri, basepath, srcuri_entry(basepath))
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001721 updaterecipe = True
1722 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001723 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1724 _move_file(patchfn, path,
1725 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001726 updatefiles = True
1727 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001728 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001729 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001730 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001731 os.path.join(files_dir, basepath),
1732 dry_run_outdir=dry_run_outdir,
1733 base_outdir=recipedir)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001734 srcuri.append(srcuri_entry(basepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001735 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001736 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001737 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001738 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001739 os.path.join(files_dir, basepath),
1740 dry_run_outdir=dry_run_outdir,
1741 base_outdir=recipedir)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001742 srcuri.append(srcuri_entry(basepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001743 updaterecipe = True
1744 # Update recipe, if needed
1745 if _remove_file_entries(srcuri, remove_files)[0]:
1746 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001747 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001748 if not dry_run_outdir:
1749 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1750 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1751 {'SRC_URI': ' '.join(srcuri)},
1752 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001753 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001754 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001755 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001756 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001757 finally:
1758 shutil.rmtree(tempdir)
1759
Brad Bishop316dfdd2018-06-25 12:45:53 -04001760 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1761 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001762
1763def _guess_recipe_update_mode(srctree, rdata):
1764 """Guess the recipe update mode to use"""
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001765 src_uri = (rdata.getVar('SRC_URI') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001766 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1767 if not git_uris:
1768 return 'patch'
1769 # Just use the first URI for now
1770 uri = git_uris[0]
1771 # Check remote branch
1772 params = bb.fetch.decodeurl(uri)[5]
1773 upstr_branch = params['branch'] if 'branch' in params else 'master'
1774 # Check if current branch HEAD is found in upstream branch
1775 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1776 head_rev = stdout.rstrip()
1777 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1778 cwd=srctree)
1779 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1780 if 'origin/' + upstr_branch in remote_brs:
1781 return 'srcrev'
1782
1783 return 'patch'
1784
Brad Bishop316dfdd2018-06-25 12:45:53 -04001785def _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 -06001786 srctree = workspace[recipename]['srctree']
1787 if mode == 'auto':
1788 mode = _guess_recipe_update_mode(srctree, rd)
1789
Brad Bishop316dfdd2018-06-25 12:45:53 -04001790 override_branches = []
1791 mainbranch = None
1792 startbranch = None
1793 if not no_overrides:
1794 stdout, _ = bb.process.run('git branch', cwd=srctree)
1795 other_branches = []
1796 for line in stdout.splitlines():
1797 branchname = line[2:]
1798 if line.startswith('* '):
1799 startbranch = branchname
1800 if branchname.startswith(override_branch_prefix):
1801 override_branches.append(branchname)
1802 else:
1803 other_branches.append(branchname)
1804
1805 if override_branches:
1806 logger.debug('_update_recipe: override branches: %s' % override_branches)
1807 logger.debug('_update_recipe: other branches: %s' % other_branches)
1808 if startbranch.startswith(override_branch_prefix):
1809 if len(other_branches) == 1:
1810 mainbranch = other_branches[1]
1811 else:
1812 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1813 else:
1814 mainbranch = startbranch
1815
1816 checkedout = None
1817 anyupdated = False
1818 appendfile = None
1819 allremoved = []
1820 if override_branches:
1821 logger.info('Handling main branch (%s)...' % mainbranch)
1822 if startbranch != mainbranch:
1823 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1824 checkedout = mainbranch
1825 try:
1826 branchlist = [mainbranch] + override_branches
1827 for branch in branchlist:
1828 crd = bb.data.createCopy(rd)
1829 if branch != mainbranch:
1830 logger.info('Handling branch %s...' % branch)
1831 override = branch[len(override_branch_prefix):]
1832 crd.appendVar('OVERRIDES', ':%s' % override)
1833 bb.process.run('git checkout %s' % branch, cwd=srctree)
1834 checkedout = branch
1835
1836 if mode == 'srcrev':
1837 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1838 elif mode == 'patch':
1839 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)
1840 else:
1841 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1842 if updated:
1843 anyupdated = True
1844 if appendf:
1845 appendfile = appendf
1846 allremoved.extend(removed)
1847 finally:
1848 if startbranch and checkedout != startbranch:
1849 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1850
1851 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001852
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001853def update_recipe(args, config, basepath, workspace):
1854 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001855 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001856
1857 if args.append:
1858 if not os.path.exists(args.append):
1859 raise DevtoolError('bbappend destination layer directory "%s" '
1860 'does not exist' % args.append)
1861 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1862 raise DevtoolError('conf/layer.conf not found in bbappend '
1863 'destination layer "%s"' % args.append)
1864
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001865 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001866 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001867
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001868 rd = parse_recipe(config, tinfoil, args.recipename, True)
1869 if not rd:
1870 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001871
Brad Bishop316dfdd2018-06-25 12:45:53 -04001872 dry_run_output = None
1873 dry_run_outdir = None
1874 if args.dry_run:
1875 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1876 dry_run_outdir = dry_run_output.name
1877 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 -05001878
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001879 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001880 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001881 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001882 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 -06001883 finally:
1884 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001885
1886 return 0
1887
1888
1889def status(args, config, basepath, workspace):
1890 """Entry point for the devtool 'status' subcommand"""
1891 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001892 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001893 recipefile = value['recipefile']
1894 if recipefile:
1895 recipestr = ' (%s)' % recipefile
1896 else:
1897 recipestr = ''
1898 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001899 else:
1900 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')
1901 return 0
1902
1903
Brad Bishop64c979e2019-11-04 13:55:29 -05001904def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001905 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001906 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001907
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001908 def clean_preferred_provider(pn, layerconf_path):
1909 """Remove PREFERRED_PROVIDER from layer.conf'"""
1910 import re
1911 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1912 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1913 pprovider_found = False
1914 with open(layerconf_file, 'r') as f:
1915 lines = f.readlines()
1916 with open(new_layerconf_file, 'a') as nf:
1917 for line in lines:
1918 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1919 if not re.match(pprovider_exp, line):
1920 nf.write(line)
1921 else:
1922 pprovider_found = True
1923 if pprovider_found:
1924 shutil.move(new_layerconf_file, layerconf_file)
1925 else:
1926 os.remove(new_layerconf_file)
1927
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001928 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001929 if len(recipes) == 1:
1930 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1931 else:
1932 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001933 # If the recipe file itself was created in the workspace, and
1934 # it uses BBCLASSEXTEND, then we need to also clean the other
1935 # variants
1936 targets = []
1937 for recipe in recipes:
1938 targets.append(recipe)
1939 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001940 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001941 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001942 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001943 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001944 except bb.process.ExecutionError as e:
1945 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1946 'wish, you may specify -n/--no-clean to '
1947 'skip running this command when resetting' %
1948 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001949
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001950 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001951 _check_preserve(config, pn)
1952
Brad Bishop316dfdd2018-06-25 12:45:53 -04001953 appendfile = workspace[pn]['bbappend']
1954 if os.path.exists(appendfile):
1955 # This shouldn't happen, but is possible if devtool errored out prior to
1956 # writing the md5 file. We need to delete this here or the recipe won't
1957 # actually be reset
1958 os.remove(appendfile)
1959
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001960 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001961 def preservedir(origdir):
1962 if os.path.exists(origdir):
1963 for root, dirs, files in os.walk(origdir):
1964 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001965 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001966 _move_file(os.path.join(origdir, fn),
1967 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001968 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001969 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001970 os.rmdir(origdir)
1971
Brad Bishop316dfdd2018-06-25 12:45:53 -04001972 recipefile = workspace[pn]['recipefile']
1973 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1974 # This should always be true if recipefile is set, but just in case
1975 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001976 # We don't automatically create this dir next to appends, but the user can
1977 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1978
Brad Bishop316dfdd2018-06-25 12:45:53 -04001979 srctreebase = workspace[pn]['srctreebase']
1980 if os.path.isdir(srctreebase):
1981 if os.listdir(srctreebase):
Brad Bishop64c979e2019-11-04 13:55:29 -05001982 if remove_work:
1983 logger.info('-r argument used on %s, removing source tree.'
1984 ' You will lose any unsaved work' %pn)
1985 shutil.rmtree(srctreebase)
1986 else:
1987 # We don't want to risk wiping out any work in progress
Patrick Williams92b42cb2022-09-03 06:53:57 -05001988 if srctreebase.startswith(os.path.join(config.workspace_path, 'sources')):
1989 from datetime import datetime
1990 preservesrc = os.path.join(config.workspace_path, 'attic', 'sources', "{}.{}".format(pn,datetime.now().strftime("%Y%m%d%H%M%S")))
1991 logger.info('Preserving source tree in %s\nIf you no '
1992 'longer need it then please delete it manually.\n'
1993 'It is also possible to reuse it via devtool source tree argument.'
1994 % preservesrc)
1995 bb.utils.mkdirhier(os.path.dirname(preservesrc))
1996 shutil.move(srctreebase, preservesrc)
1997 else:
1998 logger.info('Leaving source tree %s as-is; if you no '
1999 'longer need it then please delete it manually'
2000 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002001 else:
2002 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04002003 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002004
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002005 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002006
2007def reset(args, config, basepath, workspace):
2008 """Entry point for the devtool 'reset' subcommand"""
2009 import bb
Brad Bishop64c979e2019-11-04 13:55:29 -05002010 import shutil
2011
2012 recipes = ""
2013
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002014 if args.recipename:
2015 if args.all:
2016 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
2017 else:
2018 for recipe in args.recipename:
2019 check_workspace_recipe(workspace, recipe, checksrc=False)
2020 elif not args.all:
2021 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
2022 "reset all recipes")
2023 if args.all:
2024 recipes = list(workspace.keys())
2025 else:
2026 recipes = args.recipename
2027
Brad Bishop64c979e2019-11-04 13:55:29 -05002028 _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002029
2030 return 0
2031
2032
2033def _get_layer(layername, d):
2034 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002035 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04002036 layers = {} # {basename: layer_paths}
2037 for p in layerdirs:
2038 bn = os.path.basename(p)
2039 if bn not in layers:
2040 layers[bn] = [p]
2041 else:
2042 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002043 # Provide some shortcuts
2044 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04002045 layername = 'meta'
2046 layer_paths = layers.get(layername, None)
2047 if not layer_paths:
2048 return os.path.abspath(layername)
2049 elif len(layer_paths) == 1:
2050 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002051 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04002052 # multiple layers having the same base name
2053 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
2054 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
2055 return os.path.abspath(layer_paths[0])
2056
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002057
2058def finish(args, config, basepath, workspace):
2059 """Entry point for the devtool 'finish' subcommand"""
2060 import bb
2061 import oe.recipeutils
2062
2063 check_workspace_recipe(workspace, args.recipename)
2064
Brad Bishop316dfdd2018-06-25 12:45:53 -04002065 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
2066
2067 # Grab the equivalent of COREBASE without having to initialise tinfoil
2068 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2069
2070 srctree = workspace[args.recipename]['srctree']
2071 check_git_repo_op(srctree, [corebasedir])
2072 dirty = check_git_repo_dirty(srctree)
2073 if dirty:
2074 if args.force:
2075 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2076 else:
2077 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)
2078
Brad Bishop00e122a2019-10-05 11:10:57 -04002079 no_clean = args.no_clean
Brad Bishop64c979e2019-11-04 13:55:29 -05002080 remove_work=args.remove_work
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002081 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2082 try:
Brad Bishop6dbb3162019-11-25 09:41:34 -05002083 rd = parse_recipe(config, tinfoil, args.recipename, True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002084 if not rd:
2085 return 1
2086
2087 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002088 recipefile = rd.getVar('FILE')
2089 recipedir = os.path.dirname(recipefile)
2090 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002091
2092 if not os.path.isdir(destlayerdir):
2093 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2094
2095 if os.path.abspath(destlayerdir) == config.workspace_path:
2096 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2097
2098 # If it's an upgrade, grab the original path
2099 origpath = None
2100 origfilelist = None
2101 append = workspace[args.recipename]['bbappend']
2102 with open(append, 'r') as f:
2103 for line in f:
2104 if line.startswith('# original_path:'):
2105 origpath = line.split(':')[1].strip()
2106 elif line.startswith('# original_files:'):
2107 origfilelist = line.split(':')[1].split()
2108
Brad Bishop316dfdd2018-06-25 12:45:53 -04002109 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2110
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002111 if origlayerdir == config.workspace_path:
2112 # Recipe file itself is in workspace, update it there first
2113 appendlayerdir = None
2114 origrelpath = None
2115 if origpath:
2116 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2117 if origlayerpath:
2118 origrelpath = os.path.relpath(origpath, origlayerpath)
2119 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2120 if not destpath:
2121 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 -05002122 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2123 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002124 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002125 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)
2126
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002127 elif destlayerdir == origlayerdir:
2128 # Same layer, update the original recipe
2129 appendlayerdir = None
2130 destpath = None
2131 else:
2132 # Create/update a bbappend in the specified layer
2133 appendlayerdir = destlayerdir
2134 destpath = None
2135
Brad Bishop316dfdd2018-06-25 12:45:53 -04002136 # Actually update the recipe / bbappend
2137 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2138 dry_run_output = None
2139 dry_run_outdir = None
2140 if args.dry_run:
2141 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2142 dry_run_outdir = dry_run_output.name
2143 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)
2144 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2145
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002146 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002147 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002148 for fn in origfilelist:
2149 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002150 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2151 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2152 if not args.dry_run:
2153 try:
2154 os.remove(fnp)
2155 except FileNotFoundError:
2156 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002157
2158 if origlayerdir == config.workspace_path and destpath:
2159 # Recipe file itself is in the workspace - need to move it and any
2160 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002161 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002162 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002163 for root, _, files in os.walk(recipedir):
2164 for fn in files:
2165 srcpath = os.path.join(root, fn)
2166 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2167 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002168 destfp = os.path.join(destdir, fn)
2169 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002170
Brad Bishop316dfdd2018-06-25 12:45:53 -04002171 if dry_run_outdir:
2172 import difflib
2173 comparelist = []
2174 for root, _, files in os.walk(dry_run_outdir):
2175 for fn in files:
2176 outf = os.path.join(root, fn)
2177 relf = os.path.relpath(outf, dry_run_outdir)
2178 logger.debug('dry-run: output file %s' % relf)
2179 if fn.endswith('.bb'):
2180 if origfilelist and origpath and destpath:
2181 # Need to match this up with the pre-upgrade recipe file
2182 for origf in origfilelist:
2183 if origf.endswith('.bb'):
2184 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2185 outf,
2186 os.path.abspath(os.path.join(destpath, relf))))
2187 break
2188 else:
2189 # Compare to the existing recipe
2190 comparelist.append((recipefile, outf, recipefile))
2191 elif fn.endswith('.bbappend'):
2192 if appendfile:
2193 if os.path.exists(appendfile):
2194 comparelist.append((appendfile, outf, appendfile))
2195 else:
2196 comparelist.append((None, outf, appendfile))
2197 else:
2198 if destpath:
2199 recipedest = destpath
2200 elif appendfile:
2201 recipedest = os.path.dirname(appendfile)
2202 else:
2203 recipedest = os.path.dirname(recipefile)
2204 destfp = os.path.join(recipedest, relf)
2205 if os.path.exists(destfp):
2206 comparelist.append((destfp, outf, destfp))
2207 output = ''
2208 for oldfile, newfile, newfileshow in comparelist:
2209 if oldfile:
2210 with open(oldfile, 'r') as f:
2211 oldlines = f.readlines()
2212 else:
2213 oldfile = '/dev/null'
2214 oldlines = []
2215 with open(newfile, 'r') as f:
2216 newlines = f.readlines()
2217 if not newfileshow:
2218 newfileshow = newfile
2219 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2220 difflines = list(diff)
2221 if difflines:
2222 output += ''.join(difflines)
2223 if output:
2224 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002225 finally:
2226 tinfoil.shutdown()
2227
2228 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002229 if args.dry_run:
2230 logger.info('Resetting recipe (dry-run)')
2231 else:
Brad Bishop64c979e2019-11-04 13:55:29 -05002232 _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002233
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002234 return 0
2235
2236
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002237def get_default_srctree(config, recipename=''):
2238 """Get the default srctree path"""
2239 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2240 if recipename:
2241 return os.path.join(srctreeparent, 'sources', recipename)
2242 else:
2243 return os.path.join(srctreeparent, 'sources')
2244
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002245def register_commands(subparsers, context):
2246 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002247
2248 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002249 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002250 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.',
2251 group='starting', order=100)
2252 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.')
2253 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2254 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 -05002255 group = parser_add.add_mutually_exclusive_group()
2256 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2257 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002258 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 -05002259 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002260 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002261 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 -05002262 group = parser_add.add_mutually_exclusive_group()
2263 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2264 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")
2265 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 -05002266 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')
2267 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')
2268 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002269 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2270 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 -06002271 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002272
2273 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002274 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.',
2275 group='starting', order=90)
2276 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2277 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 -05002278 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002279 group = parser_modify.add_mutually_exclusive_group()
2280 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2281 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 -05002282 group = parser_modify.add_mutually_exclusive_group()
2283 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2284 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002285 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 -04002286 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 -05002287 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002288 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002289
2290 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2291 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002292 group='advanced')
2293 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002294 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002295 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 -04002296 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 -05002297 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002298 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002299
2300 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2301 description='Synchronize the previously extracted source tree for an existing recipe',
2302 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2303 group='advanced')
2304 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2305 parser_sync.add_argument('srctree', help='Path to the source tree')
2306 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2307 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002308 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002309
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002310 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2311 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.',
2312 group='working', order=10)
2313 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2314 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2315 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)')
2316 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')
2317 parser_rename.set_defaults(func=rename)
2318
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002319 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002320 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.',
2321 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002322 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2323 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 -05002324 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002325 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2326 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')
2327 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 -04002328 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2329 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2330 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 -05002331 parser_update_recipe.set_defaults(func=update_recipe)
2332
2333 parser_status = subparsers.add_parser('status', help='Show workspace status',
2334 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002335 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002336 parser_status.set_defaults(func=status)
2337
2338 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002339 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 -05002340 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002341 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002342 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2343 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 -05002344 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 -05002345 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002346
2347 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002348 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 -06002349 group='working', order=-100)
2350 parser_finish.add_argument('recipename', help='Recipe to finish')
2351 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.')
2352 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')
2353 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002354 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 -05002355 parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace')
Brad Bishop00e122a2019-10-05 11:10:57 -04002356 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 -04002357 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2358 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2359 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 -06002360 parser_finish.set_defaults(func=finish)