blob: ad6e3462795803aa497ee1da15c47457d514dd5a [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 Williamsda295312023-12-05 16:48:56 -0600237 initial_rev = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600238 if os.path.exists(os.path.join(srctree, '.git')):
239 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600240 initial_rev["."] = stdout.rstrip()
241 (stdout, _) = bb.process.run('git submodule --quiet foreach --recursive \'echo `git rev-parse HEAD` $PWD\'', cwd=srctree)
242 for line in stdout.splitlines():
243 (rev, submodule) = line.split()
244 initial_rev[os.path.relpath(submodule, srctree)] = rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500245
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600246 if args.src_subdir:
247 srctree = os.path.join(srctree, args.src_subdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500248
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600249 bb.utils.mkdirhier(os.path.dirname(appendfile))
250 with open(appendfile, 'w') as f:
251 f.write('inherit externalsrc\n')
252 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500253
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600254 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
255 if b_is_s:
256 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
257 if initial_rev:
Patrick Williamsda295312023-12-05 16:48:56 -0600258 for key, value in initial_rev.items():
259 f.write('\n# initial_rev %s: %s\n' % (key, value))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500260
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600261 if args.binary:
Patrick Williams213cb262021-08-07 19:21:33 -0500262 f.write('do_install:append() {\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600263 f.write(' rm -rf ${D}/.git\n')
264 f.write(' rm -f ${D}/singletask.lock\n')
265 f.write('}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500266
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600267 if bb.data.inherits_class('npm', rd):
Patrick Williams213cb262021-08-07 19:21:33 -0500268 f.write('python do_configure:append() {\n')
Andrew Geissler82c905d2020-04-13 13:39:40 -0500269 f.write(' pkgdir = d.getVar("NPM_PACKAGE")\n')
270 f.write(' lockfile = os.path.join(pkgdir, "singletask.lock")\n')
271 f.write(' bb.utils.remove(lockfile)\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600272 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500273
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500274 # Check if the new layer provides recipes whose priorities have been
275 # overriden by PREFERRED_PROVIDER.
276 recipe_name = rd.getVar('PN')
277 provides = rd.getVar('PROVIDES')
278 # Search every item defined in PROVIDES
279 for recipe_provided in provides.split():
280 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
281 current_pprovider = rd.getVar(preferred_provider)
282 if current_pprovider and current_pprovider != recipe_name:
283 if args.fixed_setup:
284 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
285 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
286 with open(layerconf_file, 'a') as f:
287 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
288 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800289 logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500290 break
291
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600292 _add_md5(config, recipename, appendfile)
293
Brad Bishop316dfdd2018-06-25 12:45:53 -0400294 check_prerelease_version(rd.getVar('PV'), 'devtool add')
295
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600296 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
297
298 finally:
299 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500300
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500301 return 0
302
303
304def _check_compatible_recipe(pn, d):
305 """Check if the recipe is supported by devtool"""
306 if pn == 'perf':
307 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600308 "source and thus cannot be supported by this tool",
309 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500310
311 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600312 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500313
314 if bb.data.inherits_class('image', d):
315 raise DevtoolError("The %s recipe is an image, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600316 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500317
318 if bb.data.inherits_class('populate_sdk', d):
319 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600320 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500321
322 if bb.data.inherits_class('packagegroup', d):
323 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600324 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500325
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500326 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600327 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500328 raise DevtoolError("externalsrc is currently enabled for the %s "
329 "recipe. This prevents the normal do_patch task "
330 "from working. You will need to disable this "
331 "first." % pn)
332
Brad Bishop316dfdd2018-06-25 12:45:53 -0400333def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
334 """Common function for copying a file to the dry run output directory"""
335 relpath = os.path.relpath(dst, base_outdir)
336 if relpath.startswith('..'):
337 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
338 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500339 dst_d = os.path.dirname(dst)
340 if dst_d:
341 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400342 # Don't overwrite existing files, otherwise in the case of an upgrade
343 # the dry-run written out recipe will be overwritten with an unmodified
344 # version
345 if not os.path.exists(dst):
346 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500347
Brad Bishop316dfdd2018-06-25 12:45:53 -0400348def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
349 """Move a file. Creates all the directory components of destination path."""
350 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
351 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
352 if dry_run_outdir:
353 # We want to copy here, not move
354 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
355 else:
356 dst_d = os.path.dirname(dst)
357 if dst_d:
358 bb.utils.mkdirhier(dst_d)
359 shutil.move(src, dst)
360
Andrew Geissler78b72792022-06-14 06:47:25 -0500361def _copy_file(src, dst, dry_run_outdir=None, base_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600362 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400363 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
364 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
365 if dry_run_outdir:
366 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
367 else:
368 dst_d = os.path.dirname(dst)
369 if dst_d:
370 bb.utils.mkdirhier(dst_d)
371 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600372
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500373def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
374 """List contents of a git treeish"""
375 import bb
376 cmd = ['git', 'ls-tree', '-z', treeish]
377 if recursive:
378 cmd.append('-r')
379 out, _ = bb.process.run(cmd, cwd=repodir)
380 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500381 if out:
382 for line in out.split('\0'):
383 if line:
384 split = line.split(None, 4)
385 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500386 return ret
387
388def _git_exclude_path(srctree, path):
389 """Return pathspec (list of paths) that excludes certain path"""
390 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
391 # we don't catch files that are deleted, for example. A more reliable way
392 # to implement this would be to use "negative pathspecs" which were
393 # introduced in Git v1.9.0. Revisit this when/if the required Git version
394 # becomes greater than that.
395 path = os.path.normpath(path)
396 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600397 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500398 if path in git_files:
399 git_files.remove(path)
400 return git_files
401 else:
402 return ['.']
403
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500404def _ls_tree(directory):
405 """Recursive listing of files in a directory"""
406 ret = []
407 for root, dirs, files in os.walk(directory):
408 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
409 fname in files])
410 return ret
411
412
413def extract(args, config, basepath, workspace):
414 """Entry point for the devtool 'extract' subcommand"""
415 import bb
416
Brad Bishop316dfdd2018-06-25 12:45:53 -0400417 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500418 if not tinfoil:
419 # Error already shown
420 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600421 try:
422 rd = parse_recipe(config, tinfoil, args.recipename, True)
423 if not rd:
424 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600426 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400427 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 -0600428 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500429
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600430 if initial_rev:
431 return 0
432 else:
433 return 1
434 finally:
435 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500436
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500437def sync(args, config, basepath, workspace):
438 """Entry point for the devtool 'sync' subcommand"""
439 import bb
440
Brad Bishop316dfdd2018-06-25 12:45:53 -0400441 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500442 if not tinfoil:
443 # Error already shown
444 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600445 try:
446 rd = parse_recipe(config, tinfoil, args.recipename, True)
447 if not rd:
448 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500449
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600450 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400451 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 -0600452 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500453
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600454 if initial_rev:
455 return 0
456 else:
457 return 1
458 finally:
459 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500460
Brad Bishop96ff1982019-08-19 13:50:42 -0400461def symlink_oelocal_files_srctree(rd,srctree):
462 import oe.patch
463 if os.path.abspath(rd.getVar('S')) == os.path.abspath(rd.getVar('WORKDIR')):
464 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
465 # (otherwise the recipe won't build as expected)
466 local_files_dir = os.path.join(srctree, 'oe-local-files')
467 addfiles = []
468 for root, _, files in os.walk(local_files_dir):
469 relpth = os.path.relpath(root, local_files_dir)
470 if relpth != '.':
471 bb.utils.mkdirhier(os.path.join(srctree, relpth))
472 for fn in files:
473 if fn == '.gitignore':
474 continue
475 destpth = os.path.join(srctree, relpth, fn)
476 if os.path.exists(destpth):
477 os.unlink(destpth)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600478 if relpth != '.':
479 back_relpth = os.path.relpath(local_files_dir, root)
480 os.symlink('%s/oe-local-files/%s/%s' % (back_relpth, relpth, fn), destpth)
481 else:
482 os.symlink('oe-local-files/%s' % fn, destpth)
Brad Bishop96ff1982019-08-19 13:50:42 -0400483 addfiles.append(os.path.join(relpth, fn))
484 if addfiles:
485 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
Brad Bishop79641f22019-09-10 07:20:22 -0400486 useroptions = []
487 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=rd)
488 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 -0400489
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500490
Brad Bishop316dfdd2018-06-25 12:45:53 -0400491def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500492 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500493 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500494 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400495 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500496
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500497 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500498
499 _check_compatible_recipe(pn, d)
500
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500501 if sync:
502 if not os.path.exists(srctree):
503 raise DevtoolError("output path %s does not exist" % srctree)
504 else:
505 if os.path.exists(srctree):
506 if not os.path.isdir(srctree):
507 raise DevtoolError("output path %s exists and is not a directory" %
508 srctree)
509 elif os.listdir(srctree):
510 raise DevtoolError("output path %s already exists and is "
511 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500512
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500513 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
514 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600515 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500516
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500517 if not sync:
518 # Prepare for shutil.move later on
519 bb.utils.mkdirhier(srctree)
520 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500521
Brad Bishop316dfdd2018-06-25 12:45:53 -0400522 extra_overrides = []
523 if not no_overrides:
524 history = d.varhistory.variable('SRC_URI')
525 for event in history:
526 if not 'flag' in event:
Patrick Williams213cb262021-08-07 19:21:33 -0500527 if event['op'].startswith((':append[', ':prepend[')):
Andrew Geissler615f2f12022-07-15 14:00:58 -0500528 override = event['op'].split('[')[1].split(']')[0]
529 if not override.startswith('pn-'):
530 extra_overrides.append(override)
Andrew Geissler99467da2019-02-25 18:54:23 -0600531 # We want to remove duplicate overrides. If a recipe had multiple
532 # SRC_URI_override += values it would cause mulitple instances of
533 # overrides. This doesn't play nicely with things like creating a
534 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
535 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400536 if extra_overrides:
537 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
538
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500539 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500540
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500541 recipefile = d.getVar('FILE')
542 appendfile = recipe_to_append(recipefile, config)
543 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
544
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500545 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
546 # directory so that:
547 # (a) we pick up all files that get unpacked to the WORKDIR, and
548 # (b) we don't disturb the existing build
549 # However, with recipe-specific sysroots the sysroots for the recipe
550 # will be prepared under WORKDIR, and if we used the system temporary
551 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
552 # our attempts to hardlink files into the recipe-specific sysroots
553 # will fail on systems where /tmp is a different filesystem, and it
554 # would have to fall back to copying the files which is a waste of
555 # time. Put the temp directory under the WORKDIR to prevent that from
556 # being a problem.
557 tempbasedir = d.getVar('WORKDIR')
558 bb.utils.mkdirhier(tempbasedir)
559 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500560 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500561 tinfoil.logger.setLevel(logging.WARNING)
562
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500563 # FIXME this results in a cache reload under control of tinfoil, which is fine
564 # except we don't get the knotty progress bar
565
566 if os.path.exists(appendfile):
567 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
568 shutil.copyfile(appendfile, appendbackup)
569 else:
570 appendbackup = None
571 bb.utils.mkdirhier(os.path.dirname(appendfile))
572 logger.debug('writing append file %s' % appendfile)
573 with open(appendfile, 'a') as f:
574 f.write('###--- _extract_source\n')
Patrick Williams2a254922023-08-11 09:48:11 -0500575 f.write('deltask do_recipe_qa\n')
576 f.write('deltask do_recipe_qa_setscene\n')
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600577 f.write('ERROR_QA:remove = "patch-fuzz"\n')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500578 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
579 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
580 if not is_kernel_yocto:
581 f.write('PATCHTOOL = "git"\n')
582 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400583 if extra_overrides:
584 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500585 f.write('inherit devtool-source\n')
586 f.write('###--- _extract_source\n')
587
588 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
589
590 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
591 bb.utils.mkdirhier(sstate_manifests)
592 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
593 with open(preservestampfile, 'w') as f:
594 f.write(d.getVar('STAMP'))
Andrew Geissler220dafd2023-10-04 10:18:08 -0500595 tinfoil.modified_files()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500596 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400597 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500598 # We need to generate the kernel config
599 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500600 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500601 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500602
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600603 if 'noexec' in (d.getVarFlags(task, False) or []) or 'task' not in (d.getVarFlags(task, False) or []):
604 logger.info('The %s recipe has %s disabled. Running only '
605 'do_configure task dependencies' % (pn, task))
606
607 if 'depends' in d.getVarFlags('do_configure', False):
608 pn = d.getVarFlags('do_configure', False)['depends']
609 pn = pn.replace('${PV}', d.getVar('PV'))
610 pn = pn.replace('${COMPILERDEP}', d.getVar('COMPILERDEP'))
611 task = None
612
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500613 # Run the fetch + unpack tasks
614 res = tinfoil.build_targets(pn,
615 task,
616 handle_events=True)
617 finally:
618 if os.path.exists(preservestampfile):
619 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500620
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500621 if not res:
622 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500623
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600624 if not is_kernel_yocto and ('noexec' in (d.getVarFlags('do_patch', False) or []) or 'task' not in (d.getVarFlags('do_patch', False) or [])):
625 workshareddir = d.getVar('S')
626 if os.path.islink(srctree):
627 os.unlink(srctree)
628
629 os.symlink(workshareddir, srctree)
630
631 # The initial_rev file is created in devtool_post_unpack function that will not be executed if
632 # do_unpack/do_patch tasks are disabled so we have to directly say that source extraction was successful
633 return True, True
634
Brad Bishop316dfdd2018-06-25 12:45:53 -0400635 try:
636 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
637 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500638
Brad Bishop316dfdd2018-06-25 12:45:53 -0400639 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
640 srcsubdir = f.read()
641 except FileNotFoundError as e:
642 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
643 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500644
Brad Bishop96ff1982019-08-19 13:50:42 -0400645 # Check if work-shared is empty, if yes
646 # find source and copy to work-shared
647 if is_kernel_yocto:
648 workshareddir = d.getVar('STAGING_KERNEL_DIR')
649 staging_kerVer = get_staging_kver(workshareddir)
650 kernelVersion = d.getVar('LINUX_VERSION')
651
652 # handle dangling symbolic link in work-shared:
653 if os.path.islink(workshareddir):
654 os.unlink(workshareddir)
655
656 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
657 shutil.rmtree(workshareddir)
658 oe.path.copyhardlinktree(srcsubdir,workshareddir)
659 elif not os.path.exists(workshareddir):
660 oe.path.copyhardlinktree(srcsubdir,workshareddir)
661
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500662 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
663 srctree_localdir = os.path.join(srctree, 'oe-local-files')
664
665 if sync:
666 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
667
668 # Move oe-local-files directory to srctree
669 # As the oe-local-files is not part of the constructed git tree,
670 # remove them directly during the synchrounizating might surprise
671 # the users. Instead, we move it to oe-local-files.bak and remind
672 # user in the log message.
673 if os.path.exists(srctree_localdir + '.bak'):
674 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
675
676 if os.path.exists(srctree_localdir):
677 logger.info('Backing up current local file directory %s' % srctree_localdir)
678 shutil.move(srctree_localdir, srctree_localdir + '.bak')
679
680 if os.path.exists(tempdir_localdir):
681 logger.info('Syncing local source files to srctree...')
682 shutil.copytree(tempdir_localdir, srctree_localdir)
683 else:
684 # Move oe-local-files directory to srctree
685 if os.path.exists(tempdir_localdir):
686 logger.info('Adding local source files to srctree...')
687 shutil.move(tempdir_localdir, srcsubdir)
688
689 shutil.move(srcsubdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400690 symlink_oelocal_files_srctree(d,srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500691
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500692 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500693 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500694 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500695
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500696 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500697 if appendbackup:
698 shutil.copyfile(appendbackup, appendfile)
699 elif os.path.exists(appendfile):
700 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500701 if keep_temp:
702 logger.info('Preserving temporary directory %s' % tempdir)
703 else:
704 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400705 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500706
707def _add_md5(config, recipename, filename):
708 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
709 import bb.utils
710
711 def addfile(fn):
712 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500713 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
714 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
715 f.seek(0, os.SEEK_SET)
716 if not md5_str in f.read():
717 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500718
719 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500720 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500721 for f in files:
722 addfile(os.path.join(root, f))
723 else:
724 addfile(filename)
725
726def _check_preserve(config, recipename):
727 """Check if a file was manually changed and needs to be saved in 'attic'
728 directory"""
729 import bb.utils
730 origfile = os.path.join(config.workspace_path, '.devtool_md5')
731 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500732 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500733 with open(origfile, 'r') as f:
734 with open(newfile, 'w') as tf:
735 for line in f.readlines():
736 splitline = line.rstrip().split('|')
737 if splitline[0] == recipename:
738 removefile = os.path.join(config.workspace_path, splitline[1])
739 try:
740 md5 = bb.utils.md5_file(removefile)
741 except IOError as err:
742 if err.errno == 2:
743 # File no longer exists, skip it
744 continue
745 else:
746 raise
747 if splitline[2] != md5:
748 bb.utils.mkdirhier(preservepath)
749 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800750 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751 shutil.move(removefile, os.path.join(preservepath, preservefile))
752 else:
753 os.remove(removefile)
754 else:
755 tf.write(line)
Andrew Geisslerc926e172021-05-07 16:11:35 -0500756 bb.utils.rename(newfile, origfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500757
Brad Bishop96ff1982019-08-19 13:50:42 -0400758def get_staging_kver(srcdir):
759 # Kernel version from work-shared
760 kerver = []
761 staging_kerVer=""
762 if os.path.exists(srcdir) and os.listdir(srcdir):
763 with open(os.path.join(srcdir,"Makefile")) as f:
764 version = [next(f) for x in range(5)][1:4]
765 for word in version:
766 kerver.append(word.split('= ')[1].split('\n')[0])
767 staging_kerVer = ".".join(kerver)
768 return staging_kerVer
769
770def get_staging_kbranch(srcdir):
771 staging_kbranch = ""
772 if os.path.exists(srcdir) and os.listdir(srcdir):
773 (branch, _) = bb.process.run('git branch | grep \* | cut -d \' \' -f2', cwd=srcdir)
774 staging_kbranch = "".join(branch.split('\n')[0])
775 return staging_kbranch
776
Andrew Geissler517393d2023-01-13 08:55:19 -0600777def get_real_srctree(srctree, s, workdir):
778 # Check that recipe isn't using a shared workdir
779 s = os.path.abspath(s)
780 workdir = os.path.abspath(workdir)
781 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
782 # Handle if S is set to a subdirectory of the source
783 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
784 srctree = os.path.join(srctree, srcsubdir)
785 return srctree
786
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500787def modify(args, config, basepath, workspace):
788 """Entry point for the devtool 'modify' subcommand"""
789 import bb
790 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400791 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400792 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500793
794 if args.recipename in workspace:
795 raise DevtoolError("recipe %s is already in your workspace" %
796 args.recipename)
797
Brad Bishop316dfdd2018-06-25 12:45:53 -0400798 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600799 try:
800 rd = parse_recipe(config, tinfoil, args.recipename, True)
801 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500802 return 1
803
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500804 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600805 if pn != args.recipename:
806 logger.info('Mapping %s to %s' % (args.recipename, pn))
807 if pn in workspace:
808 raise DevtoolError("recipe %s is already in your workspace" %
809 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500810
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600811 if args.srctree:
812 srctree = os.path.abspath(args.srctree)
813 else:
814 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500815
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600816 if args.no_extract and not os.path.isdir(srctree):
817 raise DevtoolError("--no-extract specified and source path %s does "
818 "not exist or is not a directory" %
819 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600820
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500821 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600822 appendfile = recipe_to_append(recipefile, config, args.wildcard)
823 if os.path.exists(appendfile):
824 raise DevtoolError("Another variant of recipe %s is already in your "
825 "workspace (only one variant of a recipe can "
826 "currently be worked on at once)"
827 % pn)
828
829 _check_compatible_recipe(pn, rd)
830
Patrick Williamsda295312023-12-05 16:48:56 -0600831 initial_revs = {}
832 commits = {}
Brad Bishop316dfdd2018-06-25 12:45:53 -0400833 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400834
835 if bb.data.inherits_class('kernel-yocto', rd):
836 # Current set kernel version
837 kernelVersion = rd.getVar('LINUX_VERSION')
838 srcdir = rd.getVar('STAGING_KERNEL_DIR')
839 kbranch = rd.getVar('KBRANCH')
840
841 staging_kerVer = get_staging_kver(srcdir)
842 staging_kbranch = get_staging_kbranch(srcdir)
843 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
844 oe.path.copyhardlinktree(srcdir,srctree)
845 workdir = rd.getVar('WORKDIR')
846 srcsubdir = rd.getVar('S')
847 localfilesdir = os.path.join(srctree,'oe-local-files')
848 # Move local source files into separate subdir
849 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
850 local_files = oe.recipeutils.get_recipe_local_files(rd)
851
852 for key in local_files.copy():
853 if key.endswith('scc'):
854 sccfile = open(local_files[key], 'r')
855 for l in sccfile:
856 line = l.split()
857 if line and line[0] in ('kconf', 'patch'):
858 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
859 if not cfg in local_files.values():
860 local_files[line[-1]] = cfg
861 shutil.copy2(cfg, workdir)
862 sccfile.close()
863
864 # Ignore local files with subdir={BP}
865 srcabspath = os.path.abspath(srcsubdir)
866 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))]
867 if local_files:
868 for fname in local_files:
869 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
870 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
871 f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n')
872
873 symlink_oelocal_files_srctree(rd,srctree)
874
875 task = 'do_configure'
876 res = tinfoil.build_targets(pn, task, handle_events=True)
877
878 # Copy .config to workspace
879 kconfpath = rd.getVar('B')
880 logger.info('Copying kernel config to workspace')
881 shutil.copy2(os.path.join(kconfpath, '.config'),srctree)
882
883 # Set this to true, we still need to get initial_rev
884 # by parsing the git repo
885 args.no_extract = True
886
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600887 if not args.no_extract:
Patrick Williamsda295312023-12-05 16:48:56 -0600888 initial_revs["."], _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides)
889 if not initial_revs["."]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600890 return 1
891 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600892
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600893 if os.path.exists(os.path.join(srctree, '.git')):
894 # Get list of commits since this revision
Patrick Williamsda295312023-12-05 16:48:56 -0600895 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_revs["."], cwd=srctree)
896 commits["."] = stdout.split()
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600897 check_commits = True
Patrick Williamsda295312023-12-05 16:48:56 -0600898 (stdout, _) = bb.process.run('git submodule --quiet foreach --recursive \'echo `git rev-parse devtool-base` $PWD\'', cwd=srctree)
899 for line in stdout.splitlines():
900 (rev, submodule_path) = line.split()
901 submodule = os.path.relpath(submodule_path, srctree)
902 initial_revs[submodule] = rev
903 (stdout, _) = bb.process.run('git rev-list --reverse devtool-base..HEAD', cwd=submodule_path)
904 commits[submodule] = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600905 else:
906 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600907 # Check if it's a tree previously extracted by us. This is done
908 # by ensuring that devtool-base and args.branch (devtool) exist.
909 # The check_commits logic will cause an exception if either one
910 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600911 try:
912 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600913 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600914 except bb.process.ExecutionError:
915 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400916 if stdout:
917 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600918 for line in stdout.splitlines():
919 if line.startswith('*'):
920 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600921 initial_revs["."] = stdout.rstrip()
922 if not initial_revs["."]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600923 # Otherwise, just grab the head revision
924 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600925 initial_revs["."] = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500926
Brad Bishop316dfdd2018-06-25 12:45:53 -0400927 branch_patches = {}
928 if check_commits:
929 # Check if there are override branches
930 (stdout, _) = bb.process.run('git branch', cwd=srctree)
931 branches = []
932 for line in stdout.rstrip().splitlines():
933 branchname = line[2:].rstrip()
934 if branchname.startswith(override_branch_prefix):
935 branches.append(branchname)
936 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800937 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 -0400938 branches.insert(0, args.branch)
939 seen_patches = []
940 for branch in branches:
941 branch_patches[branch] = []
942 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
943 for line in stdout.splitlines():
944 line = line.strip()
945 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
946 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
947 if not origpatch in seen_patches:
948 seen_patches.append(origpatch)
949 branch_patches[branch].append(origpatch)
950
951 # Need to grab this here in case the source is within a subdirectory
952 srctreebase = srctree
Andrew Geissler517393d2023-01-13 08:55:19 -0600953 srctree = get_real_srctree(srctree, rd.getVar('S'), rd.getVar('WORKDIR'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500954
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600955 bb.utils.mkdirhier(os.path.dirname(appendfile))
956 with open(appendfile, 'w') as f:
Patrick Williams213cb262021-08-07 19:21:33 -0500957 f.write('FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600958 # Local files can be modified/tracked in separate subdir under srctree
959 # Mostly useful for packages with S != WORKDIR
Patrick Williams213cb262021-08-07 19:21:33 -0500960 f.write('FILESPATH:prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400961 os.path.join(srctreebase, 'oe-local-files'))
962 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500963
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600964 f.write('\ninherit externalsrc\n')
965 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 -0500966 f.write('EXTERNALSRC:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500967
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600968 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
969 if b_is_s:
Patrick Williams213cb262021-08-07 19:21:33 -0500970 f.write('EXTERNALSRC_BUILD:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500971
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600972 if bb.data.inherits_class('kernel', rd):
973 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500974 'do_fetch do_unpack do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400975 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500976 f.write('\ndo_configure:append() {\n'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600977 ' cp ${B}/.config ${S}/.config.baseline\n'
978 ' ln -sfT ${B}/.config ${S}/.config.new\n'
979 '}\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500980 f.write('\ndo_kernel_configme:prepend() {\n'
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500981 ' if [ -e ${S}/.config ]; then\n'
982 ' mv ${S}/.config ${S}/.config.old\n'
983 ' fi\n'
984 '}\n')
Brad Bishop96ff1982019-08-19 13:50:42 -0400985 if rd.getVarFlag('do_menuconfig','task'):
Patrick Williams213cb262021-08-07 19:21:33 -0500986 f.write('\ndo_configure:append() {\n'
Patrick Williams520786c2023-06-25 16:20:36 -0500987 ' if [ ${@ oe.types.boolean(\'${KCONFIG_CONFIG_ENABLE_MENUCONFIG}\') } = True ]; then\n'
988 ' cp ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.baseline\n'
989 ' ln -sfT ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.new\n'
Andrew Geissler82c905d2020-04-13 13:39:40 -0500990 ' fi\n'
Brad Bishop96ff1982019-08-19 13:50:42 -0400991 '}\n')
Patrick Williamsda295312023-12-05 16:48:56 -0600992 if initial_revs:
993 for name, rev in initial_revs.items():
994 f.write('\n# initial_rev %s: %s\n' % (name, rev))
995 for commit in commits[name]:
996 f.write('# commit %s: %s\n' % (name, commit))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400997 if branch_patches:
998 for branch in branch_patches:
999 if branch == args.branch:
1000 continue
1001 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001002
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001003 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
1004
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001005 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001006
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001007 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001008
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001009 finally:
1010 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001011
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001012 return 0
1013
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001014
1015def rename(args, config, basepath, workspace):
1016 """Entry point for the devtool 'rename' subcommand"""
1017 import bb
1018 import oe.recipeutils
1019
1020 check_workspace_recipe(workspace, args.recipename)
1021
1022 if not (args.newname or args.version):
1023 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
1024
1025 recipefile = workspace[args.recipename]['recipefile']
1026 if not recipefile:
1027 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
1028
1029 if args.newname and args.newname != args.recipename:
1030 reason = oe.recipeutils.validate_pn(args.newname)
1031 if reason:
1032 raise DevtoolError(reason)
1033 newname = args.newname
1034 else:
1035 newname = args.recipename
1036
1037 append = workspace[args.recipename]['bbappend']
1038 appendfn = os.path.splitext(os.path.basename(append))[0]
1039 splitfn = appendfn.split('_')
1040 if len(splitfn) > 1:
1041 origfnver = appendfn.split('_')[1]
1042 else:
1043 origfnver = ''
1044
1045 recipefilemd5 = None
1046 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1047 try:
1048 rd = parse_recipe(config, tinfoil, args.recipename, True)
1049 if not rd:
1050 return 1
1051
1052 bp = rd.getVar('BP')
1053 bpn = rd.getVar('BPN')
1054 if newname != args.recipename:
1055 localdata = rd.createCopy()
1056 localdata.setVar('PN', newname)
1057 newbpn = localdata.getVar('BPN')
1058 else:
1059 newbpn = bpn
1060 s = rd.getVar('S', False)
1061 src_uri = rd.getVar('SRC_URI', False)
1062 pv = rd.getVar('PV')
1063
1064 # Correct variable values that refer to the upstream source - these
1065 # values must stay the same, so if the name/version are changing then
1066 # we need to fix them up
1067 new_s = s
1068 new_src_uri = src_uri
1069 if newbpn != bpn:
1070 # ${PN} here is technically almost always incorrect, but people do use it
1071 new_s = new_s.replace('${BPN}', bpn)
1072 new_s = new_s.replace('${PN}', bpn)
1073 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1074 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1075 new_src_uri = new_src_uri.replace('${PN}', bpn)
1076 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1077 if args.version and origfnver == pv:
1078 new_s = new_s.replace('${PV}', pv)
1079 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1080 new_src_uri = new_src_uri.replace('${PV}', pv)
1081 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1082 patchfields = {}
1083 if new_s != s:
1084 patchfields['S'] = new_s
1085 if new_src_uri != src_uri:
1086 patchfields['SRC_URI'] = new_src_uri
1087 if patchfields:
1088 recipefilemd5 = bb.utils.md5_file(recipefile)
1089 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1090 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1091 finally:
1092 tinfoil.shutdown()
1093
1094 if args.version:
1095 newver = args.version
1096 else:
1097 newver = origfnver
1098
1099 if newver:
1100 newappend = '%s_%s.bbappend' % (newname, newver)
1101 newfile = '%s_%s.bb' % (newname, newver)
1102 else:
1103 newappend = '%s.bbappend' % newname
1104 newfile = '%s.bb' % newname
1105
1106 oldrecipedir = os.path.dirname(recipefile)
1107 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1108 if oldrecipedir != newrecipedir:
1109 bb.utils.mkdirhier(newrecipedir)
1110
1111 newappend = os.path.join(os.path.dirname(append), newappend)
1112 newfile = os.path.join(newrecipedir, newfile)
1113
1114 # Rename bbappend
1115 logger.info('Renaming %s to %s' % (append, newappend))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001116 bb.utils.rename(append, newappend)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001117 # Rename recipe file
1118 logger.info('Renaming %s to %s' % (recipefile, newfile))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001119 bb.utils.rename(recipefile, newfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001120
1121 # Rename source tree if it's the default path
1122 appendmd5 = None
1123 if not args.no_srctree:
1124 srctree = workspace[args.recipename]['srctree']
1125 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1126 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1127 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1128 shutil.move(srctree, newsrctree)
1129 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1130 appendmd5 = bb.utils.md5_file(newappend)
1131 appendlines = []
1132 with open(newappend, 'r') as f:
1133 for line in f:
1134 appendlines.append(line)
1135 with open(newappend, 'w') as f:
1136 for line in appendlines:
1137 if srctree in line:
1138 line = line.replace(srctree, newsrctree)
1139 f.write(line)
1140 newappendmd5 = bb.utils.md5_file(newappend)
1141
1142 bpndir = None
1143 newbpndir = None
1144 if newbpn != bpn:
1145 bpndir = os.path.join(oldrecipedir, bpn)
1146 if os.path.exists(bpndir):
1147 newbpndir = os.path.join(newrecipedir, newbpn)
1148 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1149 shutil.move(bpndir, newbpndir)
1150
1151 bpdir = None
1152 newbpdir = None
1153 if newver != origfnver or newbpn != bpn:
1154 bpdir = os.path.join(oldrecipedir, bp)
1155 if os.path.exists(bpdir):
1156 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1157 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1158 shutil.move(bpdir, newbpdir)
1159
1160 if oldrecipedir != newrecipedir:
1161 # Move any stray files and delete the old recipe directory
1162 for entry in os.listdir(oldrecipedir):
1163 oldpath = os.path.join(oldrecipedir, entry)
1164 newpath = os.path.join(newrecipedir, entry)
1165 logger.info('Renaming %s to %s' % (oldpath, newpath))
1166 shutil.move(oldpath, newpath)
1167 os.rmdir(oldrecipedir)
1168
1169 # Now take care of entries in .devtool_md5
1170 md5entries = []
1171 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1172 for line in f:
1173 md5entries.append(line)
1174
1175 if bpndir and newbpndir:
1176 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1177 else:
1178 relbpndir = None
1179 if bpdir and newbpdir:
1180 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1181 else:
1182 relbpdir = None
1183
1184 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1185 for entry in md5entries:
1186 splitentry = entry.rstrip().split('|')
1187 if len(splitentry) > 2:
1188 if splitentry[0] == args.recipename:
1189 splitentry[0] = newname
1190 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1191 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1192 if appendmd5 and splitentry[2] == appendmd5:
1193 splitentry[2] = newappendmd5
1194 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1195 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1196 if recipefilemd5 and splitentry[2] == recipefilemd5:
1197 splitentry[2] = newrecipefilemd5
1198 elif relbpndir and splitentry[1].startswith(relbpndir):
1199 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1200 elif relbpdir and splitentry[1].startswith(relbpdir):
1201 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1202 entry = '|'.join(splitentry) + '\n'
1203 f.write(entry)
1204 return 0
1205
1206
Brad Bishop316dfdd2018-06-25 12:45:53 -04001207def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001208 """Get initial and update rev of a recipe. These are the start point of the
1209 whole patchset and start point for the patches to be re-generated/updated.
1210 """
1211 import bb
1212
Brad Bishop316dfdd2018-06-25 12:45:53 -04001213 # Get current branch
1214 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1215 cwd=srctree)
1216 branchname = stdout.rstrip()
1217
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001218 # Parse initial rev from recipe if not specified
Patrick Williamsda295312023-12-05 16:48:56 -06001219 commits = {}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001220 patches = []
Patrick Williamsda295312023-12-05 16:48:56 -06001221 initial_revs = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001222 with open(recipe_path, 'r') as f:
1223 for line in f:
Patrick Williamsda295312023-12-05 16:48:56 -06001224 pattern = r'^#\s.*\s(.*):\s([0-9a-fA-F]+)$'
1225 match = re.search(pattern, line)
1226 if match:
1227 name = match.group(1)
1228 rev = match.group(2)
1229 if line.startswith('# initial_rev'):
1230 if not (name == "." and initial_rev):
1231 initial_revs[name] = rev
1232 elif line.startswith('# commit') and not force_patch_refresh:
1233 if name not in commits:
1234 commits[name] = [rev]
1235 else:
1236 commits[name].append(rev)
1237 elif line.startswith('# patches_%s:' % branchname):
1238 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001239
Patrick Williamsda295312023-12-05 16:48:56 -06001240 update_revs = dict(initial_revs)
1241 changed_revs = {}
1242 for name, rev in initial_revs.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001243 # Find first actually changed revision
1244 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
Patrick Williamsda295312023-12-05 16:48:56 -06001245 rev, cwd=os.path.join(srctree, name))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001246 newcommits = stdout.split()
Patrick Williamsda295312023-12-05 16:48:56 -06001247 if name in commits:
1248 for i in range(min(len(commits[name]), len(newcommits))):
1249 if newcommits[i] == commits[name][i]:
1250 update_revs[name] = commits[name][i]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001251
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001252 try:
1253 stdout, _ = bb.process.run('git cherry devtool-patched',
Patrick Williamsda295312023-12-05 16:48:56 -06001254 cwd=os.path.join(srctree, name))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001255 except bb.process.ExecutionError as err:
1256 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001257
Brad Bishop316dfdd2018-06-25 12:45:53 -04001258 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001259 for line in stdout.splitlines():
1260 if line.startswith('+ '):
1261 rev = line.split()[1]
1262 if rev in newcommits:
Patrick Williamsda295312023-12-05 16:48:56 -06001263 if name not in changed_revs:
1264 changed_revs[name] = [rev]
1265 else:
1266 changed_revs[name].append(rev)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001267
Patrick Williamsda295312023-12-05 16:48:56 -06001268 return initial_revs, update_revs, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001269
1270def _remove_file_entries(srcuri, filelist):
1271 """Remove file:// entries from SRC_URI"""
1272 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001273 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001274 for fname in filelist:
1275 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001276 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001277 if (srcuri[i].startswith('file://') and
1278 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001279 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001280 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001281 srcuri.pop(i)
1282 break
1283 return entries, remaining
1284
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001285def _replace_srcuri_entry(srcuri, filename, newentry):
1286 """Replace entry corresponding to specified file with a new entry"""
1287 basename = os.path.basename(filename)
1288 for i in range(len(srcuri)):
1289 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1290 srcuri.pop(i)
1291 srcuri.insert(i, newentry)
1292 break
1293
Brad Bishop316dfdd2018-06-25 12:45:53 -04001294def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001295 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001296
1297 dry_run_suffix = ' (dry-run)' if dry_run else ''
1298
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001299 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001300 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001301 if not destpath:
1302 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001303 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001304
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001305 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001306 if not no_report_remove:
1307 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1308 if not dry_run:
1309 # FIXME "git rm" here would be nice if the file in question is
1310 # tracked
1311 # FIXME there's a chance that this file is referred to by
1312 # another recipe, in which case deleting wouldn't be the
1313 # right thing to do
1314 os.remove(path)
1315 # Remove directory if empty
1316 try:
1317 os.rmdir(os.path.dirname(path))
1318 except OSError as ose:
1319 if ose.errno != errno.ENOTEMPTY:
1320 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001321
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001322
Patrick Williamsda295312023-12-05 16:48:56 -06001323def _export_patches(srctree, rd, start_revs, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001324 """Export patches from srctree to given location.
1325 Returns three-tuple of dicts:
1326 1. updated - patches that already exist in SRCURI
1327 2. added - new patches that don't exist in SRCURI
1328 3 removed - patches that exist in SRCURI but not in exported patches
Patrick Williamsda295312023-12-05 16:48:56 -06001329 In each dict the key is the 'basepath' of the URI and value is:
1330 - for updated and added dicts, a dict with 2 optionnal keys:
1331 - 'path': the absolute path to the existing file in recipe space (if any)
1332 - 'patchdir': the directory in wich the patch should be applied (if any)
1333 - for removed dict, the absolute path to the existing file in recipe space
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001334 """
1335 import oe.recipeutils
1336 from oe.patch import GitApplyTree
1337 updated = OrderedDict()
1338 added = OrderedDict()
1339 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1340
1341 existing_patches = dict((os.path.basename(path), path) for path in
1342 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001343 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001344
1345 # Generate patches from Git, exclude local files directory
1346 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
Patrick Williamsda295312023-12-05 16:48:56 -06001347 GitApplyTree.extractPatches(srctree, start_revs, destdir, patch_pathspec)
1348 for dirpath, dirnames, filenames in os.walk(destdir):
1349 new_patches = filenames
1350 reldirpath = os.path.relpath(dirpath, destdir)
1351 for new_patch in new_patches:
1352 # Strip numbering from patch names. If it's a git sequence named patch,
1353 # the numbers might not match up since we are starting from a different
1354 # revision This does assume that people are using unique shortlog
1355 # values, but they ought to be anyway...
1356 new_basename = seqpatch_re.match(new_patch).group(2)
1357 match_name = None
1358 for old_patch in existing_patches:
1359 old_basename = seqpatch_re.match(old_patch).group(2)
1360 old_basename_splitext = os.path.splitext(old_basename)
1361 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1362 old_patch_noext = os.path.splitext(old_patch)[0]
1363 match_name = old_patch_noext
1364 break
1365 elif new_basename == old_basename:
1366 match_name = old_patch
1367 break
1368 if match_name:
1369 # Rename patch files
1370 if new_patch != match_name:
1371 bb.utils.rename(os.path.join(destdir, new_patch),
1372 os.path.join(destdir, match_name))
1373 # Need to pop it off the list now before checking changed_revs
1374 oldpath = existing_patches.pop(old_patch)
1375 if changed_revs is not None and dirpath in changed_revs:
1376 # Avoid updating patches that have not actually changed
1377 with open(os.path.join(dirpath, match_name), 'r') as f:
1378 firstlineitems = f.readline().split()
1379 # Looking for "From <hash>" line
1380 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1381 if not firstlineitems[1] in changed_revs[dirpath]:
1382 continue
1383 # Recompress if necessary
1384 if oldpath.endswith(('.gz', '.Z')):
1385 bb.process.run(['gzip', match_name], cwd=destdir)
1386 if oldpath.endswith('.gz'):
1387 match_name += '.gz'
1388 else:
1389 match_name += '.Z'
1390 elif oldpath.endswith('.bz2'):
1391 bb.process.run(['bzip2', match_name], cwd=destdir)
1392 match_name += '.bz2'
1393 updated[match_name] = {'path' : oldpath}
1394 if reldirpath != ".":
1395 updated[match_name]['patchdir'] = reldirpath
1396 else:
1397 added[new_patch] = {}
1398 if reldirpath != ".":
1399 added[new_patch]['patchdir'] = reldirpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001400
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001401 return (updated, added, existing_patches)
1402
1403
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001404def _create_kconfig_diff(srctree, rd, outfile):
1405 """Create a kconfig fragment"""
1406 # Only update config fragment if both config files exist
1407 orig_config = os.path.join(srctree, '.config.baseline')
1408 new_config = os.path.join(srctree, '.config.new')
1409 if os.path.exists(orig_config) and os.path.exists(new_config):
1410 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1411 '--unchanged-line-format=', orig_config, new_config]
1412 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1413 stderr=subprocess.PIPE)
1414 stdout, stderr = pipe.communicate()
1415 if pipe.returncode == 1:
1416 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001417 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001418 fobj.write(stdout)
1419 elif pipe.returncode == 0:
1420 logger.info("Would remove config fragment %s" % outfile)
1421 if os.path.exists(outfile):
1422 # Remove fragment file in case of empty diff
1423 logger.info("Removing config fragment %s" % outfile)
1424 os.unlink(outfile)
1425 else:
1426 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1427 return True
1428 return False
1429
1430
Brad Bishop316dfdd2018-06-25 12:45:53 -04001431def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001432 """Copy local files from srctree to given location.
1433 Returns three-tuple of dicts:
1434 1. updated - files that already exist in SRCURI
1435 2. added - new files files that don't exist in SRCURI
1436 3 removed - files that exist in SRCURI but not in exported files
1437 In each dict the key is the 'basepath' of the URI and value is the
1438 absolute path to the existing file in recipe space (if any).
1439 """
1440 import oe.recipeutils
1441
1442 # Find out local files (SRC_URI files that exist in the "recipe space").
1443 # Local files that reside in srctree are not included in patch generation.
1444 # Instead they are directly copied over the original source files (in
1445 # recipe space).
1446 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1447 new_set = None
1448 updated = OrderedDict()
1449 added = OrderedDict()
1450 removed = OrderedDict()
Andrew Geissler517393d2023-01-13 08:55:19 -06001451
1452 # Get current branch and return early with empty lists
1453 # if on one of the override branches
1454 # (local files are provided only for the main branch and processing
1455 # them against lists from recipe overrides will result in mismatches
1456 # and broken modifications to recipes).
1457 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1458 cwd=srctree)
1459 branchname = stdout.rstrip()
1460 if branchname.startswith(override_branch_prefix):
1461 return (updated, added, removed)
1462
Brad Bishop316dfdd2018-06-25 12:45:53 -04001463 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001464 git_files = _git_ls_tree(srctree)
1465 if 'oe-local-files' in git_files:
1466 # If tracked by Git, take the files from srctree HEAD. First get
1467 # the tree object of the directory
1468 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1469 tree = git_files['oe-local-files'][2]
1470 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1471 env=dict(os.environ, GIT_WORK_TREE=destdir,
1472 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001473 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001474 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001475 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001476 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001477 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001478 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001479 else:
1480 new_set = []
1481
1482 # Special handling for kernel config
1483 if bb.data.inherits_class('kernel-yocto', rd):
1484 fragment_fn = 'devtool-fragment.cfg'
1485 fragment_path = os.path.join(destdir, fragment_fn)
1486 if _create_kconfig_diff(srctree, rd, fragment_path):
1487 if os.path.exists(fragment_path):
1488 if fragment_fn not in new_set:
1489 new_set.append(fragment_fn)
1490 # Copy fragment to local-files
1491 if os.path.isdir(local_files_dir):
1492 shutil.copy2(fragment_path, local_files_dir)
1493 else:
1494 if fragment_fn in new_set:
1495 new_set.remove(fragment_fn)
1496 # Remove fragment from local-files
1497 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1498 os.unlink(os.path.join(local_files_dir, fragment_fn))
1499
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001500 # Special handling for cml1, ccmake, etc bbclasses that generated
1501 # configuration fragment files that are consumed as source files
1502 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1503 if bb.data.inherits_class(frag_class, rd):
1504 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1505 if os.path.exists(srcpath):
1506 if frag_name not in new_set:
1507 new_set.append(frag_name)
1508 # copy fragment into destdir
1509 shutil.copy2(srcpath, destdir)
1510 # copy fragment into local files if exists
1511 if os.path.isdir(local_files_dir):
1512 shutil.copy2(srcpath, local_files_dir)
1513
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001514 if new_set is not None:
1515 for fname in new_set:
1516 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001517 origpath = existing_files.pop(fname)
1518 workpath = os.path.join(local_files_dir, fname)
1519 if not filecmp.cmp(origpath, workpath):
1520 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001521 elif fname != '.gitignore':
1522 added[fname] = None
1523
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001524 workdir = rd.getVar('WORKDIR')
1525 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001526 if not s.endswith(os.sep):
1527 s += os.sep
1528
1529 if workdir != s:
1530 # Handle files where subdir= was specified
1531 for fname in list(existing_files.keys()):
1532 # FIXME handle both subdir starting with BP and not?
1533 fworkpath = os.path.join(workdir, fname)
1534 if fworkpath.startswith(s):
1535 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1536 if os.path.exists(fpath):
1537 origpath = existing_files.pop(fname)
1538 if not filecmp.cmp(origpath, fpath):
1539 updated[fpath] = origpath
1540
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001541 removed = existing_files
1542 return (updated, added, removed)
1543
1544
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001545def _determine_files_dir(rd):
1546 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001547 recipedir = rd.getVar('FILE_DIRNAME')
1548 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001549 relpth = os.path.relpath(entry, recipedir)
1550 if not os.sep in relpth:
1551 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1552 if os.path.isdir(entry):
1553 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001554 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001555
1556
Brad Bishop316dfdd2018-06-25 12:45:53 -04001557def _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 -05001558 """Implement the 'srcrev' mode of update-recipe"""
1559 import bb
1560 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001561
Brad Bishop316dfdd2018-06-25 12:45:53 -04001562 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1563
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001564 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001565 recipedir = os.path.basename(recipefile)
1566 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001567
Patrick Williamsda295312023-12-05 16:48:56 -06001568 # Get original SRCREV
1569 old_srcrev = rd.getVar('SRCREV') or ''
1570 if old_srcrev == "INVALID":
1571 raise DevtoolError('Update mode srcrev is only valid for recipe fetched from an SCM repository')
1572 old_srcrev = {'.': old_srcrev}
1573
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001574 # Get HEAD revision
1575 try:
1576 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1577 except bb.process.ExecutionError as err:
1578 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1579 (srctree, err))
1580 srcrev = stdout.strip()
1581 if len(srcrev) != 40:
1582 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1583
1584 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001585 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001586 patchfields = {}
1587 patchfields['SRCREV'] = srcrev
1588 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001589 srcuri = orig_src_uri.split()
1590 tempdir = tempfile.mkdtemp(prefix='devtool')
1591 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001592 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001593 try:
1594 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001595 srctreebase = workspace[recipename]['srctreebase']
1596 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001597 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001598 # Find list of existing patches in recipe file
1599 patches_dir = tempfile.mkdtemp(dir=tempdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001600 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1601 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001602 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 -05001603
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001604 # Remove deleted local files and "overlapping" patches
Patrick Williamsda295312023-12-05 16:48:56 -06001605 remove_files = list(del_f.values()) + [value["path"] for value in upd_p.values() if "path" in value] + [value["path"] for value in del_p.values() if "path" in value]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001606 if remove_files:
1607 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1608 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001609
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001610 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001611 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001612 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001613 removevalues = {}
1614 if update_srcuri:
1615 removevalues = {'SRC_URI': removedentries}
1616 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001617 if dry_run_outdir:
1618 logger.info('Creating bbappend (dry-run)')
Patrick Williamsda295312023-12-05 16:48:56 -06001619 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1620 rd, appendlayerdir, files, wildcardver=wildcard_version,
1621 extralines=patchfields, removevalues=removevalues,
1622 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001623 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001624 files_dir = _determine_files_dir(rd)
1625 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001626 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001627 if os.path.isabs(basepath):
1628 # Original file (probably with subdir pointing inside source tree)
1629 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001630 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001631 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001632 _move_file(os.path.join(local_files_dir, basepath), path,
1633 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001634 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001635 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001636 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001637 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001638 os.path.join(files_dir, basepath),
1639 dry_run_outdir=dry_run_outdir,
1640 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001641 srcuri.append('file://%s' % basepath)
1642 update_srcuri = True
1643 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001644 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001645 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001646 finally:
1647 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001648 if not 'git://' in orig_src_uri:
1649 logger.info('You will need to update SRC_URI within the recipe to '
1650 'point to a git repository where you have pushed your '
1651 'changes')
1652
Brad Bishop316dfdd2018-06-25 12:45:53 -04001653 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1654 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001655
Brad Bishop316dfdd2018-06-25 12:45:53 -04001656def _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 -05001657 """Implement the 'patch' mode of update-recipe"""
1658 import bb
1659 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001660
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001661 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001662 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001663 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001664 if not os.path.exists(append):
1665 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001666 recipename)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001667 srctreebase = workspace[recipename]['srctreebase']
1668 relpatchdir = os.path.relpath(srctreebase, srctree)
1669 if relpatchdir == '.':
1670 patchdir_params = {}
1671 else:
1672 patchdir_params = {'patchdir': relpatchdir}
1673
Patrick Williamsda295312023-12-05 16:48:56 -06001674 def srcuri_entry(basepath, patchdir_params):
Andrew Geissler615f2f12022-07-15 14:00:58 -05001675 if patchdir_params:
1676 paramstr = ';' + ';'.join('%s=%s' % (k,v) for k,v in patchdir_params.items())
1677 else:
1678 paramstr = ''
1679 return 'file://%s%s' % (basepath, paramstr)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001680
Patrick Williamsda295312023-12-05 16:48:56 -06001681 initial_revs, update_revs, changed_revs, filter_patches = _get_patchset_revs(srctree, append, initial_rev, force_patch_refresh)
1682 if not initial_revs:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001683 raise DevtoolError('Unable to find initial revision - please specify '
1684 'it with --initial-rev')
1685
Brad Bishop316dfdd2018-06-25 12:45:53 -04001686 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001687 dl_dir = rd.getVar('DL_DIR')
1688 if not dl_dir.endswith('/'):
1689 dl_dir += '/'
1690
Brad Bishop316dfdd2018-06-25 12:45:53 -04001691 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1692
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001693 tempdir = tempfile.mkdtemp(prefix='devtool')
1694 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001695 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Andrew Geissler517393d2023-01-13 08:55:19 -06001696 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001697
1698 # Get updated patches from source tree
1699 patches_dir = tempfile.mkdtemp(dir=tempdir)
Patrick Williamsda295312023-12-05 16:48:56 -06001700 upd_p, new_p, _ = _export_patches(srctree, rd, update_revs,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001701 patches_dir, changed_revs)
Andrew Geissler517393d2023-01-13 08:55:19 -06001702 # Get all patches from source tree and check if any should be removed
1703 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Patrick Williamsda295312023-12-05 16:48:56 -06001704 _, _, del_p = _export_patches(srctree, rd, initial_revs,
Andrew Geissler517393d2023-01-13 08:55:19 -06001705 all_patches_dir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001706 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1707 if filter_patches:
Brad Bishop00e122a2019-10-05 11:10:57 -04001708 new_p = OrderedDict()
1709 upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches)
Andrew Geissler517393d2023-01-13 08:55:19 -06001710 del_p = OrderedDict((k,v) for k,v in del_p.items() if k in filter_patches)
1711 remove_files = []
1712 if not no_remove:
1713 # Remove deleted local files and patches
1714 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001715 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001716 updaterecipe = False
1717 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001718 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsda295312023-12-05 16:48:56 -06001719
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001720 if appendlayerdir:
Brad Bishop00e122a2019-10-05 11:10:57 -04001721 files = OrderedDict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001722 key, val in list(upd_f.items()) + list(new_f.items()))
Brad Bishop00e122a2019-10-05 11:10:57 -04001723 files.update(OrderedDict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001724 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsda295312023-12-05 16:48:56 -06001725
1726 params = []
1727 for file, param in files.items():
1728 patchdir_param = dict(patchdir_params)
1729 patchdir = param.get('patchdir', ".")
1730 if patchdir != "." :
1731 if patchdir_param:
1732 patchdir_param['patchdir'] += patchdir
1733 else:
1734 patchdir_param['patchdir'] = patchdir
1735 params.append(patchdir_param)
1736
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001737 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001738 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001739 if remove_files:
1740 removedentries, remaining = _remove_file_entries(
1741 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001742 if removedentries or remaining:
Patrick Williamsda295312023-12-05 16:48:56 -06001743 remaining = [srcuri_entry(os.path.basename(item), patchdir_params) for
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001744 item in remaining]
1745 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001746 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001747 rd, appendlayerdir, files,
1748 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001749 removevalues=removevalues,
Andrew Geissler615f2f12022-07-15 14:00:58 -05001750 redirect_output=dry_run_outdir,
Patrick Williamsda295312023-12-05 16:48:56 -06001751 params=params)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001752 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001753 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001754 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001755 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001756 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001757 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001758 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001759 if os.path.isabs(basepath):
1760 # Original file (probably with subdir pointing inside source tree)
1761 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001762 _copy_file(basepath, path,
1763 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001764 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001765 _move_file(os.path.join(local_files_dir, basepath), path,
1766 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001767 updatefiles = True
Patrick Williamsda295312023-12-05 16:48:56 -06001768 for basepath, param in upd_p.items():
1769 path = param['path']
1770 patchdir = param.get('patchdir', ".")
1771 if patchdir != "." :
1772 patchdir_param = dict(patchdir_params)
1773 if patchdir_param:
1774 patchdir_param['patchdir'] += patchdir
1775 else:
1776 patchdir_param['patchdir'] = patchdir
1777 patchfn = os.path.join(patches_dir, patchdir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001778 if os.path.dirname(path) + '/' == dl_dir:
1779 # This is a a downloaded patch file - we now need to
1780 # replace the entry in SRC_URI with our local version
1781 logger.info('Replacing remote patch %s with updated local version' % basepath)
1782 path = os.path.join(files_dir, basepath)
Patrick Williamsda295312023-12-05 16:48:56 -06001783 _replace_srcuri_entry(srcuri, basepath, srcuri_entry(basepath, patchdir_param))
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001784 updaterecipe = True
1785 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001786 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1787 _move_file(patchfn, path,
1788 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001789 updatefiles = True
1790 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001791 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001792 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001793 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001794 os.path.join(files_dir, basepath),
1795 dry_run_outdir=dry_run_outdir,
1796 base_outdir=recipedir)
Patrick Williamsda295312023-12-05 16:48:56 -06001797 srcuri.append(srcuri_entry(basepath, patchdir_params))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001798 updaterecipe = True
Patrick Williamsda295312023-12-05 16:48:56 -06001799 for basepath, param in new_p.items():
1800 patchdir = param.get('patchdir', ".")
Brad Bishop316dfdd2018-06-25 12:45:53 -04001801 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsda295312023-12-05 16:48:56 -06001802 _move_file(os.path.join(patches_dir, patchdir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001803 os.path.join(files_dir, basepath),
1804 dry_run_outdir=dry_run_outdir,
1805 base_outdir=recipedir)
Patrick Williamsda295312023-12-05 16:48:56 -06001806 params = dict(patchdir_params)
1807 if patchdir != "." :
1808 if params:
1809 params['patchdir'] += patchdir
1810 else:
1811 params['patchdir'] = patchdir
1812
1813 srcuri.append(srcuri_entry(basepath, params))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001814 updaterecipe = True
1815 # Update recipe, if needed
1816 if _remove_file_entries(srcuri, remove_files)[0]:
1817 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001818 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001819 if not dry_run_outdir:
1820 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1821 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1822 {'SRC_URI': ' '.join(srcuri)},
1823 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001824 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001825 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001826 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001827 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001828 finally:
1829 shutil.rmtree(tempdir)
1830
Brad Bishop316dfdd2018-06-25 12:45:53 -04001831 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1832 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001833
1834def _guess_recipe_update_mode(srctree, rdata):
1835 """Guess the recipe update mode to use"""
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001836 src_uri = (rdata.getVar('SRC_URI') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001837 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1838 if not git_uris:
1839 return 'patch'
1840 # Just use the first URI for now
1841 uri = git_uris[0]
1842 # Check remote branch
1843 params = bb.fetch.decodeurl(uri)[5]
1844 upstr_branch = params['branch'] if 'branch' in params else 'master'
1845 # Check if current branch HEAD is found in upstream branch
1846 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1847 head_rev = stdout.rstrip()
1848 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1849 cwd=srctree)
1850 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1851 if 'origin/' + upstr_branch in remote_brs:
1852 return 'srcrev'
1853
1854 return 'patch'
1855
Brad Bishop316dfdd2018-06-25 12:45:53 -04001856def _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 -06001857 srctree = workspace[recipename]['srctree']
1858 if mode == 'auto':
1859 mode = _guess_recipe_update_mode(srctree, rd)
1860
Brad Bishop316dfdd2018-06-25 12:45:53 -04001861 override_branches = []
1862 mainbranch = None
1863 startbranch = None
1864 if not no_overrides:
1865 stdout, _ = bb.process.run('git branch', cwd=srctree)
1866 other_branches = []
1867 for line in stdout.splitlines():
1868 branchname = line[2:]
1869 if line.startswith('* '):
1870 startbranch = branchname
1871 if branchname.startswith(override_branch_prefix):
1872 override_branches.append(branchname)
1873 else:
1874 other_branches.append(branchname)
1875
1876 if override_branches:
1877 logger.debug('_update_recipe: override branches: %s' % override_branches)
1878 logger.debug('_update_recipe: other branches: %s' % other_branches)
1879 if startbranch.startswith(override_branch_prefix):
1880 if len(other_branches) == 1:
1881 mainbranch = other_branches[1]
1882 else:
1883 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1884 else:
1885 mainbranch = startbranch
1886
1887 checkedout = None
1888 anyupdated = False
1889 appendfile = None
1890 allremoved = []
1891 if override_branches:
1892 logger.info('Handling main branch (%s)...' % mainbranch)
1893 if startbranch != mainbranch:
1894 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1895 checkedout = mainbranch
1896 try:
1897 branchlist = [mainbranch] + override_branches
1898 for branch in branchlist:
1899 crd = bb.data.createCopy(rd)
1900 if branch != mainbranch:
1901 logger.info('Handling branch %s...' % branch)
1902 override = branch[len(override_branch_prefix):]
1903 crd.appendVar('OVERRIDES', ':%s' % override)
1904 bb.process.run('git checkout %s' % branch, cwd=srctree)
1905 checkedout = branch
1906
1907 if mode == 'srcrev':
1908 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1909 elif mode == 'patch':
1910 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)
1911 else:
1912 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1913 if updated:
1914 anyupdated = True
1915 if appendf:
1916 appendfile = appendf
1917 allremoved.extend(removed)
1918 finally:
1919 if startbranch and checkedout != startbranch:
1920 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1921
1922 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001923
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001924def update_recipe(args, config, basepath, workspace):
1925 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001926 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001927
1928 if args.append:
1929 if not os.path.exists(args.append):
1930 raise DevtoolError('bbappend destination layer directory "%s" '
1931 'does not exist' % args.append)
1932 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1933 raise DevtoolError('conf/layer.conf not found in bbappend '
1934 'destination layer "%s"' % args.append)
1935
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001936 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001937 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001938
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001939 rd = parse_recipe(config, tinfoil, args.recipename, True)
1940 if not rd:
1941 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001942
Brad Bishop316dfdd2018-06-25 12:45:53 -04001943 dry_run_output = None
1944 dry_run_outdir = None
1945 if args.dry_run:
1946 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1947 dry_run_outdir = dry_run_output.name
1948 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 -05001949
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001950 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001951 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001952 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001953 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 -06001954 finally:
1955 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001956
1957 return 0
1958
1959
1960def status(args, config, basepath, workspace):
1961 """Entry point for the devtool 'status' subcommand"""
1962 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001963 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001964 recipefile = value['recipefile']
1965 if recipefile:
1966 recipestr = ' (%s)' % recipefile
1967 else:
1968 recipestr = ''
1969 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001970 else:
1971 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')
1972 return 0
1973
1974
Brad Bishop64c979e2019-11-04 13:55:29 -05001975def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001976 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001977 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001978
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001979 def clean_preferred_provider(pn, layerconf_path):
1980 """Remove PREFERRED_PROVIDER from layer.conf'"""
1981 import re
1982 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1983 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1984 pprovider_found = False
1985 with open(layerconf_file, 'r') as f:
1986 lines = f.readlines()
1987 with open(new_layerconf_file, 'a') as nf:
1988 for line in lines:
1989 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1990 if not re.match(pprovider_exp, line):
1991 nf.write(line)
1992 else:
1993 pprovider_found = True
1994 if pprovider_found:
1995 shutil.move(new_layerconf_file, layerconf_file)
1996 else:
1997 os.remove(new_layerconf_file)
1998
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001999 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002000 if len(recipes) == 1:
2001 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
2002 else:
2003 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002004 # If the recipe file itself was created in the workspace, and
2005 # it uses BBCLASSEXTEND, then we need to also clean the other
2006 # variants
2007 targets = []
2008 for recipe in recipes:
2009 targets.append(recipe)
2010 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002011 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002012 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002013 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002014 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002015 except bb.process.ExecutionError as e:
2016 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
2017 'wish, you may specify -n/--no-clean to '
2018 'skip running this command when resetting' %
2019 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002020
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002021 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002022 _check_preserve(config, pn)
2023
Brad Bishop316dfdd2018-06-25 12:45:53 -04002024 appendfile = workspace[pn]['bbappend']
2025 if os.path.exists(appendfile):
2026 # This shouldn't happen, but is possible if devtool errored out prior to
2027 # writing the md5 file. We need to delete this here or the recipe won't
2028 # actually be reset
2029 os.remove(appendfile)
2030
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002031 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002032 def preservedir(origdir):
2033 if os.path.exists(origdir):
2034 for root, dirs, files in os.walk(origdir):
2035 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08002036 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002037 _move_file(os.path.join(origdir, fn),
2038 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002039 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002040 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002041 os.rmdir(origdir)
2042
Brad Bishop316dfdd2018-06-25 12:45:53 -04002043 recipefile = workspace[pn]['recipefile']
2044 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
2045 # This should always be true if recipefile is set, but just in case
2046 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002047 # We don't automatically create this dir next to appends, but the user can
2048 preservedir(os.path.join(config.workspace_path, 'appends', pn))
2049
Brad Bishop316dfdd2018-06-25 12:45:53 -04002050 srctreebase = workspace[pn]['srctreebase']
2051 if os.path.isdir(srctreebase):
2052 if os.listdir(srctreebase):
Brad Bishop64c979e2019-11-04 13:55:29 -05002053 if remove_work:
2054 logger.info('-r argument used on %s, removing source tree.'
2055 ' You will lose any unsaved work' %pn)
2056 shutil.rmtree(srctreebase)
2057 else:
2058 # We don't want to risk wiping out any work in progress
Patrick Williams92b42cb2022-09-03 06:53:57 -05002059 if srctreebase.startswith(os.path.join(config.workspace_path, 'sources')):
2060 from datetime import datetime
2061 preservesrc = os.path.join(config.workspace_path, 'attic', 'sources', "{}.{}".format(pn,datetime.now().strftime("%Y%m%d%H%M%S")))
2062 logger.info('Preserving source tree in %s\nIf you no '
2063 'longer need it then please delete it manually.\n'
2064 'It is also possible to reuse it via devtool source tree argument.'
2065 % preservesrc)
2066 bb.utils.mkdirhier(os.path.dirname(preservesrc))
2067 shutil.move(srctreebase, preservesrc)
2068 else:
2069 logger.info('Leaving source tree %s as-is; if you no '
2070 'longer need it then please delete it manually'
2071 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002072 else:
2073 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04002074 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002075
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002076 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002077
2078def reset(args, config, basepath, workspace):
2079 """Entry point for the devtool 'reset' subcommand"""
2080 import bb
Brad Bishop64c979e2019-11-04 13:55:29 -05002081 import shutil
2082
2083 recipes = ""
2084
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002085 if args.recipename:
2086 if args.all:
2087 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
2088 else:
2089 for recipe in args.recipename:
2090 check_workspace_recipe(workspace, recipe, checksrc=False)
2091 elif not args.all:
2092 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
2093 "reset all recipes")
2094 if args.all:
2095 recipes = list(workspace.keys())
2096 else:
2097 recipes = args.recipename
2098
Brad Bishop64c979e2019-11-04 13:55:29 -05002099 _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002100
2101 return 0
2102
2103
2104def _get_layer(layername, d):
2105 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002106 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04002107 layers = {} # {basename: layer_paths}
2108 for p in layerdirs:
2109 bn = os.path.basename(p)
2110 if bn not in layers:
2111 layers[bn] = [p]
2112 else:
2113 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002114 # Provide some shortcuts
2115 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04002116 layername = 'meta'
2117 layer_paths = layers.get(layername, None)
2118 if not layer_paths:
2119 return os.path.abspath(layername)
2120 elif len(layer_paths) == 1:
2121 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002122 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04002123 # multiple layers having the same base name
2124 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
2125 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
2126 return os.path.abspath(layer_paths[0])
2127
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002128
2129def finish(args, config, basepath, workspace):
2130 """Entry point for the devtool 'finish' subcommand"""
2131 import bb
2132 import oe.recipeutils
2133
2134 check_workspace_recipe(workspace, args.recipename)
2135
Brad Bishop316dfdd2018-06-25 12:45:53 -04002136 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
2137
2138 # Grab the equivalent of COREBASE without having to initialise tinfoil
2139 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2140
2141 srctree = workspace[args.recipename]['srctree']
2142 check_git_repo_op(srctree, [corebasedir])
2143 dirty = check_git_repo_dirty(srctree)
2144 if dirty:
2145 if args.force:
2146 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2147 else:
2148 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)
2149
Brad Bishop00e122a2019-10-05 11:10:57 -04002150 no_clean = args.no_clean
Brad Bishop64c979e2019-11-04 13:55:29 -05002151 remove_work=args.remove_work
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002152 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2153 try:
Brad Bishop6dbb3162019-11-25 09:41:34 -05002154 rd = parse_recipe(config, tinfoil, args.recipename, True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002155 if not rd:
2156 return 1
2157
2158 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002159 recipefile = rd.getVar('FILE')
2160 recipedir = os.path.dirname(recipefile)
2161 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002162
2163 if not os.path.isdir(destlayerdir):
2164 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2165
2166 if os.path.abspath(destlayerdir) == config.workspace_path:
2167 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2168
2169 # If it's an upgrade, grab the original path
2170 origpath = None
2171 origfilelist = None
2172 append = workspace[args.recipename]['bbappend']
2173 with open(append, 'r') as f:
2174 for line in f:
2175 if line.startswith('# original_path:'):
2176 origpath = line.split(':')[1].strip()
2177 elif line.startswith('# original_files:'):
2178 origfilelist = line.split(':')[1].split()
2179
Brad Bishop316dfdd2018-06-25 12:45:53 -04002180 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2181
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002182 if origlayerdir == config.workspace_path:
2183 # Recipe file itself is in workspace, update it there first
2184 appendlayerdir = None
2185 origrelpath = None
2186 if origpath:
2187 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2188 if origlayerpath:
2189 origrelpath = os.path.relpath(origpath, origlayerpath)
2190 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2191 if not destpath:
2192 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 -05002193 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2194 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002195 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002196 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)
2197
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002198 elif destlayerdir == origlayerdir:
2199 # Same layer, update the original recipe
2200 appendlayerdir = None
2201 destpath = None
2202 else:
2203 # Create/update a bbappend in the specified layer
2204 appendlayerdir = destlayerdir
2205 destpath = None
2206
Brad Bishop316dfdd2018-06-25 12:45:53 -04002207 # Actually update the recipe / bbappend
2208 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2209 dry_run_output = None
2210 dry_run_outdir = None
2211 if args.dry_run:
2212 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2213 dry_run_outdir = dry_run_output.name
2214 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)
2215 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2216
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002217 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002218 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002219 for fn in origfilelist:
2220 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002221 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2222 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2223 if not args.dry_run:
2224 try:
2225 os.remove(fnp)
2226 except FileNotFoundError:
2227 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002228
2229 if origlayerdir == config.workspace_path and destpath:
2230 # Recipe file itself is in the workspace - need to move it and any
2231 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002232 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002233 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002234 for root, _, files in os.walk(recipedir):
2235 for fn in files:
2236 srcpath = os.path.join(root, fn)
2237 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2238 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002239 destfp = os.path.join(destdir, fn)
2240 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002241
Brad Bishop316dfdd2018-06-25 12:45:53 -04002242 if dry_run_outdir:
2243 import difflib
2244 comparelist = []
2245 for root, _, files in os.walk(dry_run_outdir):
2246 for fn in files:
2247 outf = os.path.join(root, fn)
2248 relf = os.path.relpath(outf, dry_run_outdir)
2249 logger.debug('dry-run: output file %s' % relf)
2250 if fn.endswith('.bb'):
2251 if origfilelist and origpath and destpath:
2252 # Need to match this up with the pre-upgrade recipe file
2253 for origf in origfilelist:
2254 if origf.endswith('.bb'):
2255 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2256 outf,
2257 os.path.abspath(os.path.join(destpath, relf))))
2258 break
2259 else:
2260 # Compare to the existing recipe
2261 comparelist.append((recipefile, outf, recipefile))
2262 elif fn.endswith('.bbappend'):
2263 if appendfile:
2264 if os.path.exists(appendfile):
2265 comparelist.append((appendfile, outf, appendfile))
2266 else:
2267 comparelist.append((None, outf, appendfile))
2268 else:
2269 if destpath:
2270 recipedest = destpath
2271 elif appendfile:
2272 recipedest = os.path.dirname(appendfile)
2273 else:
2274 recipedest = os.path.dirname(recipefile)
2275 destfp = os.path.join(recipedest, relf)
2276 if os.path.exists(destfp):
2277 comparelist.append((destfp, outf, destfp))
2278 output = ''
2279 for oldfile, newfile, newfileshow in comparelist:
2280 if oldfile:
2281 with open(oldfile, 'r') as f:
2282 oldlines = f.readlines()
2283 else:
2284 oldfile = '/dev/null'
2285 oldlines = []
2286 with open(newfile, 'r') as f:
2287 newlines = f.readlines()
2288 if not newfileshow:
2289 newfileshow = newfile
2290 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2291 difflines = list(diff)
2292 if difflines:
2293 output += ''.join(difflines)
2294 if output:
2295 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002296 finally:
2297 tinfoil.shutdown()
2298
2299 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002300 if args.dry_run:
2301 logger.info('Resetting recipe (dry-run)')
2302 else:
Brad Bishop64c979e2019-11-04 13:55:29 -05002303 _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002304
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002305 return 0
2306
2307
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002308def get_default_srctree(config, recipename=''):
2309 """Get the default srctree path"""
2310 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2311 if recipename:
2312 return os.path.join(srctreeparent, 'sources', recipename)
2313 else:
2314 return os.path.join(srctreeparent, 'sources')
2315
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002316def register_commands(subparsers, context):
2317 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002318
2319 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002320 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002321 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.',
2322 group='starting', order=100)
2323 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.')
2324 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2325 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 -05002326 group = parser_add.add_mutually_exclusive_group()
2327 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2328 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002329 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 -05002330 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002331 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002332 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 -05002333 group = parser_add.add_mutually_exclusive_group()
2334 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2335 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")
2336 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 -05002337 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')
2338 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')
2339 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002340 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2341 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 -06002342 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002343
2344 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002345 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.',
2346 group='starting', order=90)
2347 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2348 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 -05002349 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002350 group = parser_modify.add_mutually_exclusive_group()
2351 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2352 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 -05002353 group = parser_modify.add_mutually_exclusive_group()
2354 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2355 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002356 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 -04002357 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 -05002358 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002359 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002360
2361 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2362 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002363 group='advanced')
2364 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002365 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002366 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 -04002367 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 -05002368 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002369 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002370
2371 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2372 description='Synchronize the previously extracted source tree for an existing recipe',
2373 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2374 group='advanced')
2375 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2376 parser_sync.add_argument('srctree', help='Path to the source tree')
2377 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2378 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002379 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002380
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002381 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2382 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.',
2383 group='working', order=10)
2384 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2385 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2386 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)')
2387 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')
2388 parser_rename.set_defaults(func=rename)
2389
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002390 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002391 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.',
2392 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002393 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2394 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 -05002395 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002396 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2397 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')
2398 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 -04002399 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2400 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2401 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 -05002402 parser_update_recipe.set_defaults(func=update_recipe)
2403
2404 parser_status = subparsers.add_parser('status', help='Show workspace status',
2405 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002406 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002407 parser_status.set_defaults(func=status)
2408
2409 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002410 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 -05002411 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002412 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002413 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2414 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 -05002415 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 -05002416 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002417
2418 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002419 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 -06002420 group='working', order=-100)
2421 parser_finish.add_argument('recipename', help='Recipe to finish')
2422 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.')
2423 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')
2424 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002425 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 -05002426 parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace')
Brad Bishop00e122a2019-10-05 11:10:57 -04002427 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 -04002428 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2429 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2430 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 -06002431 parser_finish.set_defaults(func=finish)