blob: 8fb4b934d438110eb1cde80885358a438b8e9679 [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'
Patrick Williams169d7bc2024-01-05 11:33:25 -0600150 if args.no_pypi:
151 extracmdopts += ' --no-pypi'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500152 if args.mirrors:
153 extracmdopts += ' --mirrors'
154 if args.srcrev:
155 extracmdopts += ' --srcrev %s' % args.srcrev
156 if args.srcbranch:
157 extracmdopts += ' --srcbranch %s' % args.srcbranch
158 if args.provides:
159 extracmdopts += ' --provides %s' % args.provides
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500160
161 tempdir = tempfile.mkdtemp(prefix='devtool')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500162 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500163 try:
164 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create --devtool -o %s \'%s\' %s' % (color, tempdir, source, extracmdopts), watch=True)
165 except bb.process.ExecutionError as e:
166 if e.exitcode == 15:
167 raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line')
168 else:
169 raise DevtoolError('Command \'%s\' failed' % e.command)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500171 recipes = glob.glob(os.path.join(tempdir, '*.bb'))
172 if recipes:
173 recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0]
174 if recipename in workspace:
175 raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename)
176 recipedir = os.path.join(config.workspace_path, 'recipes', recipename)
177 bb.utils.mkdirhier(recipedir)
178 recipefile = os.path.join(recipedir, os.path.basename(recipes[0]))
179 appendfile = recipe_to_append(recipefile, config)
180 if os.path.exists(appendfile):
181 # This shouldn't be possible, but just in case
182 raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace')
183 if os.path.exists(recipefile):
184 raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile)
185 if tmpsrcdir:
186 srctree = os.path.join(srctreeparent, recipename)
187 if os.path.exists(tmpsrcdir):
188 if os.path.exists(srctree):
189 if os.path.isdir(srctree):
190 try:
191 os.rmdir(srctree)
192 except OSError as e:
193 if e.errno == errno.ENOTEMPTY:
194 raise DevtoolError('Source tree path %s already exists and is not empty' % srctree)
195 else:
196 raise
197 else:
198 raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree)
199 logger.info('Using default source tree path %s' % srctree)
200 shutil.move(tmpsrcdir, srctree)
201 else:
202 raise DevtoolError('Couldn\'t find source tree created by recipetool')
203 bb.utils.mkdirhier(recipedir)
204 shutil.move(recipes[0], recipefile)
205 # Move any additional files created by recipetool
206 for fn in os.listdir(tempdir):
207 shutil.move(os.path.join(tempdir, fn), recipedir)
208 else:
209 raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout))
210 attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile))
211 if os.path.exists(attic_recipe):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800212 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 -0500213 finally:
214 if tmpsrcdir and os.path.exists(tmpsrcdir):
215 shutil.rmtree(tmpsrcdir)
216 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500217
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500218 for fn in os.listdir(recipedir):
219 _add_md5(config, recipename, os.path.join(recipedir, fn))
220
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500221 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600222 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500223 try:
224 rd = tinfoil.parse_recipe_file(recipefile, False)
225 except Exception as e:
226 logger.error(str(e))
227 rd = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600228 if not rd:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500229 # Parsing failed. We just created this recipe and we shouldn't
230 # leave it in the workdir or it'll prevent bitbake from starting
231 movefn = '%s.parsefailed' % recipefile
232 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)
233 shutil.move(recipefile, movefn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600234 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500235
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600236 if args.fetchuri and not args.no_git:
237 setup_git_repo(srctree, args.version, 'devtool', d=tinfoil.config_data)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500238
Patrick Williamsda295312023-12-05 16:48:56 -0600239 initial_rev = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600240 if os.path.exists(os.path.join(srctree, '.git')):
241 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600242 initial_rev["."] = stdout.rstrip()
243 (stdout, _) = bb.process.run('git submodule --quiet foreach --recursive \'echo `git rev-parse HEAD` $PWD\'', cwd=srctree)
244 for line in stdout.splitlines():
245 (rev, submodule) = line.split()
246 initial_rev[os.path.relpath(submodule, srctree)] = rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500247
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600248 if args.src_subdir:
249 srctree = os.path.join(srctree, args.src_subdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500250
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600251 bb.utils.mkdirhier(os.path.dirname(appendfile))
252 with open(appendfile, 'w') as f:
253 f.write('inherit externalsrc\n')
254 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500255
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600256 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
257 if b_is_s:
258 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
259 if initial_rev:
Patrick Williamsda295312023-12-05 16:48:56 -0600260 for key, value in initial_rev.items():
261 f.write('\n# initial_rev %s: %s\n' % (key, value))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500262
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600263 if args.binary:
Patrick Williams213cb262021-08-07 19:21:33 -0500264 f.write('do_install:append() {\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600265 f.write(' rm -rf ${D}/.git\n')
266 f.write(' rm -f ${D}/singletask.lock\n')
267 f.write('}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500268
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600269 if bb.data.inherits_class('npm', rd):
Patrick Williams213cb262021-08-07 19:21:33 -0500270 f.write('python do_configure:append() {\n')
Andrew Geissler82c905d2020-04-13 13:39:40 -0500271 f.write(' pkgdir = d.getVar("NPM_PACKAGE")\n')
272 f.write(' lockfile = os.path.join(pkgdir, "singletask.lock")\n')
273 f.write(' bb.utils.remove(lockfile)\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600274 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500275
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500276 # Check if the new layer provides recipes whose priorities have been
277 # overriden by PREFERRED_PROVIDER.
278 recipe_name = rd.getVar('PN')
279 provides = rd.getVar('PROVIDES')
280 # Search every item defined in PROVIDES
281 for recipe_provided in provides.split():
282 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
283 current_pprovider = rd.getVar(preferred_provider)
284 if current_pprovider and current_pprovider != recipe_name:
285 if args.fixed_setup:
286 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
287 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
288 with open(layerconf_file, 'a') as f:
289 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
290 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800291 logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500292 break
293
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600294 _add_md5(config, recipename, appendfile)
295
Brad Bishop316dfdd2018-06-25 12:45:53 -0400296 check_prerelease_version(rd.getVar('PV'), 'devtool add')
297
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600298 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
299
300 finally:
301 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500302
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500303 return 0
304
305
306def _check_compatible_recipe(pn, d):
307 """Check if the recipe is supported by devtool"""
308 if pn == 'perf':
309 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600310 "source and thus cannot be supported by this tool",
311 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500312
313 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600314 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500315
316 if bb.data.inherits_class('image', d):
317 raise DevtoolError("The %s recipe is an image, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600318 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319
320 if bb.data.inherits_class('populate_sdk', d):
321 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600322 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323
324 if bb.data.inherits_class('packagegroup', d):
325 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600326 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500327
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500328 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600329 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500330 raise DevtoolError("externalsrc is currently enabled for the %s "
331 "recipe. This prevents the normal do_patch task "
332 "from working. You will need to disable this "
333 "first." % pn)
334
Brad Bishop316dfdd2018-06-25 12:45:53 -0400335def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
336 """Common function for copying a file to the dry run output directory"""
337 relpath = os.path.relpath(dst, base_outdir)
338 if relpath.startswith('..'):
339 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
340 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500341 dst_d = os.path.dirname(dst)
342 if dst_d:
343 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400344 # Don't overwrite existing files, otherwise in the case of an upgrade
345 # the dry-run written out recipe will be overwritten with an unmodified
346 # version
347 if not os.path.exists(dst):
348 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500349
Brad Bishop316dfdd2018-06-25 12:45:53 -0400350def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
351 """Move a file. Creates all the directory components of destination path."""
352 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
353 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
354 if dry_run_outdir:
355 # We want to copy here, not move
356 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
357 else:
358 dst_d = os.path.dirname(dst)
359 if dst_d:
360 bb.utils.mkdirhier(dst_d)
361 shutil.move(src, dst)
362
Andrew Geissler78b72792022-06-14 06:47:25 -0500363def _copy_file(src, dst, dry_run_outdir=None, base_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600364 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400365 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
366 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
367 if dry_run_outdir:
368 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
369 else:
370 dst_d = os.path.dirname(dst)
371 if dst_d:
372 bb.utils.mkdirhier(dst_d)
373 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600374
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500375def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
376 """List contents of a git treeish"""
377 import bb
378 cmd = ['git', 'ls-tree', '-z', treeish]
379 if recursive:
380 cmd.append('-r')
381 out, _ = bb.process.run(cmd, cwd=repodir)
382 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500383 if out:
384 for line in out.split('\0'):
385 if line:
386 split = line.split(None, 4)
387 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500388 return ret
389
390def _git_exclude_path(srctree, path):
391 """Return pathspec (list of paths) that excludes certain path"""
392 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
393 # we don't catch files that are deleted, for example. A more reliable way
394 # to implement this would be to use "negative pathspecs" which were
395 # introduced in Git v1.9.0. Revisit this when/if the required Git version
396 # becomes greater than that.
397 path = os.path.normpath(path)
398 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600399 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500400 if path in git_files:
401 git_files.remove(path)
402 return git_files
403 else:
404 return ['.']
405
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500406def _ls_tree(directory):
407 """Recursive listing of files in a directory"""
408 ret = []
409 for root, dirs, files in os.walk(directory):
410 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
411 fname in files])
412 return ret
413
414
415def extract(args, config, basepath, workspace):
416 """Entry point for the devtool 'extract' subcommand"""
417 import bb
418
Brad Bishop316dfdd2018-06-25 12:45:53 -0400419 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500420 if not tinfoil:
421 # Error already shown
422 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600423 try:
424 rd = parse_recipe(config, tinfoil, args.recipename, True)
425 if not rd:
426 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500427
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600428 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400429 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 -0600430 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500431
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600432 if initial_rev:
433 return 0
434 else:
435 return 1
436 finally:
437 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500438
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500439def sync(args, config, basepath, workspace):
440 """Entry point for the devtool 'sync' subcommand"""
441 import bb
442
Brad Bishop316dfdd2018-06-25 12:45:53 -0400443 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500444 if not tinfoil:
445 # Error already shown
446 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600447 try:
448 rd = parse_recipe(config, tinfoil, args.recipename, True)
449 if not rd:
450 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500451
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600452 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400453 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 -0600454 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500455
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600456 if initial_rev:
457 return 0
458 else:
459 return 1
460 finally:
461 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500462
Brad Bishop96ff1982019-08-19 13:50:42 -0400463def symlink_oelocal_files_srctree(rd,srctree):
464 import oe.patch
465 if os.path.abspath(rd.getVar('S')) == os.path.abspath(rd.getVar('WORKDIR')):
466 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
467 # (otherwise the recipe won't build as expected)
468 local_files_dir = os.path.join(srctree, 'oe-local-files')
469 addfiles = []
470 for root, _, files in os.walk(local_files_dir):
471 relpth = os.path.relpath(root, local_files_dir)
472 if relpth != '.':
473 bb.utils.mkdirhier(os.path.join(srctree, relpth))
474 for fn in files:
475 if fn == '.gitignore':
476 continue
477 destpth = os.path.join(srctree, relpth, fn)
478 if os.path.exists(destpth):
479 os.unlink(destpth)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600480 if relpth != '.':
481 back_relpth = os.path.relpath(local_files_dir, root)
482 os.symlink('%s/oe-local-files/%s/%s' % (back_relpth, relpth, fn), destpth)
483 else:
484 os.symlink('oe-local-files/%s' % fn, destpth)
Brad Bishop96ff1982019-08-19 13:50:42 -0400485 addfiles.append(os.path.join(relpth, fn))
486 if addfiles:
487 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
Brad Bishop79641f22019-09-10 07:20:22 -0400488 useroptions = []
489 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=rd)
490 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 -0400491
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500492
Brad Bishop316dfdd2018-06-25 12:45:53 -0400493def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500494 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500495 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500496 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400497 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500498
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500499 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500500
501 _check_compatible_recipe(pn, d)
502
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500503 if sync:
504 if not os.path.exists(srctree):
505 raise DevtoolError("output path %s does not exist" % srctree)
506 else:
507 if os.path.exists(srctree):
508 if not os.path.isdir(srctree):
509 raise DevtoolError("output path %s exists and is not a directory" %
510 srctree)
511 elif os.listdir(srctree):
512 raise DevtoolError("output path %s already exists and is "
513 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500514
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500515 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
516 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600517 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500518
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500519 if not sync:
520 # Prepare for shutil.move later on
521 bb.utils.mkdirhier(srctree)
522 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500523
Brad Bishop316dfdd2018-06-25 12:45:53 -0400524 extra_overrides = []
525 if not no_overrides:
526 history = d.varhistory.variable('SRC_URI')
527 for event in history:
528 if not 'flag' in event:
Patrick Williams213cb262021-08-07 19:21:33 -0500529 if event['op'].startswith((':append[', ':prepend[')):
Andrew Geissler615f2f12022-07-15 14:00:58 -0500530 override = event['op'].split('[')[1].split(']')[0]
531 if not override.startswith('pn-'):
532 extra_overrides.append(override)
Andrew Geissler99467da2019-02-25 18:54:23 -0600533 # We want to remove duplicate overrides. If a recipe had multiple
534 # SRC_URI_override += values it would cause mulitple instances of
535 # overrides. This doesn't play nicely with things like creating a
536 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
537 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400538 if extra_overrides:
539 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
540
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500541 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500542
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500543 recipefile = d.getVar('FILE')
544 appendfile = recipe_to_append(recipefile, config)
545 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
546
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500547 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
548 # directory so that:
549 # (a) we pick up all files that get unpacked to the WORKDIR, and
550 # (b) we don't disturb the existing build
551 # However, with recipe-specific sysroots the sysroots for the recipe
552 # will be prepared under WORKDIR, and if we used the system temporary
553 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
554 # our attempts to hardlink files into the recipe-specific sysroots
555 # will fail on systems where /tmp is a different filesystem, and it
556 # would have to fall back to copying the files which is a waste of
557 # time. Put the temp directory under the WORKDIR to prevent that from
558 # being a problem.
559 tempbasedir = d.getVar('WORKDIR')
560 bb.utils.mkdirhier(tempbasedir)
561 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500562 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500563 tinfoil.logger.setLevel(logging.WARNING)
564
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500565 # FIXME this results in a cache reload under control of tinfoil, which is fine
566 # except we don't get the knotty progress bar
567
568 if os.path.exists(appendfile):
569 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
570 shutil.copyfile(appendfile, appendbackup)
571 else:
572 appendbackup = None
573 bb.utils.mkdirhier(os.path.dirname(appendfile))
574 logger.debug('writing append file %s' % appendfile)
575 with open(appendfile, 'a') as f:
576 f.write('###--- _extract_source\n')
Patrick Williams2a254922023-08-11 09:48:11 -0500577 f.write('deltask do_recipe_qa\n')
578 f.write('deltask do_recipe_qa_setscene\n')
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600579 f.write('ERROR_QA:remove = "patch-fuzz"\n')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500580 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
581 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
582 if not is_kernel_yocto:
583 f.write('PATCHTOOL = "git"\n')
584 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400585 if extra_overrides:
586 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500587 f.write('inherit devtool-source\n')
588 f.write('###--- _extract_source\n')
589
590 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
591
592 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
593 bb.utils.mkdirhier(sstate_manifests)
594 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
595 with open(preservestampfile, 'w') as f:
596 f.write(d.getVar('STAMP'))
Andrew Geissler220dafd2023-10-04 10:18:08 -0500597 tinfoil.modified_files()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500598 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400599 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500600 # We need to generate the kernel config
601 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500602 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500603 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500604
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600605 if 'noexec' in (d.getVarFlags(task, False) or []) or 'task' not in (d.getVarFlags(task, False) or []):
606 logger.info('The %s recipe has %s disabled. Running only '
607 'do_configure task dependencies' % (pn, task))
608
609 if 'depends' in d.getVarFlags('do_configure', False):
610 pn = d.getVarFlags('do_configure', False)['depends']
611 pn = pn.replace('${PV}', d.getVar('PV'))
612 pn = pn.replace('${COMPILERDEP}', d.getVar('COMPILERDEP'))
613 task = None
614
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500615 # Run the fetch + unpack tasks
616 res = tinfoil.build_targets(pn,
617 task,
618 handle_events=True)
619 finally:
620 if os.path.exists(preservestampfile):
621 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500622
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500623 if not res:
624 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500625
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600626 if not is_kernel_yocto and ('noexec' in (d.getVarFlags('do_patch', False) or []) or 'task' not in (d.getVarFlags('do_patch', False) or [])):
627 workshareddir = d.getVar('S')
628 if os.path.islink(srctree):
629 os.unlink(srctree)
630
631 os.symlink(workshareddir, srctree)
632
633 # The initial_rev file is created in devtool_post_unpack function that will not be executed if
634 # do_unpack/do_patch tasks are disabled so we have to directly say that source extraction was successful
635 return True, True
636
Brad Bishop316dfdd2018-06-25 12:45:53 -0400637 try:
638 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
639 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500640
Brad Bishop316dfdd2018-06-25 12:45:53 -0400641 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
642 srcsubdir = f.read()
643 except FileNotFoundError as e:
644 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
645 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500646
Brad Bishop96ff1982019-08-19 13:50:42 -0400647 # Check if work-shared is empty, if yes
648 # find source and copy to work-shared
649 if is_kernel_yocto:
650 workshareddir = d.getVar('STAGING_KERNEL_DIR')
651 staging_kerVer = get_staging_kver(workshareddir)
652 kernelVersion = d.getVar('LINUX_VERSION')
653
654 # handle dangling symbolic link in work-shared:
655 if os.path.islink(workshareddir):
656 os.unlink(workshareddir)
657
658 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
659 shutil.rmtree(workshareddir)
660 oe.path.copyhardlinktree(srcsubdir,workshareddir)
661 elif not os.path.exists(workshareddir):
662 oe.path.copyhardlinktree(srcsubdir,workshareddir)
663
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500664 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
665 srctree_localdir = os.path.join(srctree, 'oe-local-files')
666
667 if sync:
668 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
669
670 # Move oe-local-files directory to srctree
671 # As the oe-local-files is not part of the constructed git tree,
672 # remove them directly during the synchrounizating might surprise
673 # the users. Instead, we move it to oe-local-files.bak and remind
674 # user in the log message.
675 if os.path.exists(srctree_localdir + '.bak'):
676 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
677
678 if os.path.exists(srctree_localdir):
679 logger.info('Backing up current local file directory %s' % srctree_localdir)
680 shutil.move(srctree_localdir, srctree_localdir + '.bak')
681
682 if os.path.exists(tempdir_localdir):
683 logger.info('Syncing local source files to srctree...')
684 shutil.copytree(tempdir_localdir, srctree_localdir)
685 else:
686 # Move oe-local-files directory to srctree
687 if os.path.exists(tempdir_localdir):
688 logger.info('Adding local source files to srctree...')
689 shutil.move(tempdir_localdir, srcsubdir)
690
691 shutil.move(srcsubdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400692 symlink_oelocal_files_srctree(d,srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500693
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500694 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500695 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500696 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500697
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500698 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500699 if appendbackup:
700 shutil.copyfile(appendbackup, appendfile)
701 elif os.path.exists(appendfile):
702 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500703 if keep_temp:
704 logger.info('Preserving temporary directory %s' % tempdir)
705 else:
706 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400707 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500708
709def _add_md5(config, recipename, filename):
710 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
711 import bb.utils
712
713 def addfile(fn):
714 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500715 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
716 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
717 f.seek(0, os.SEEK_SET)
718 if not md5_str in f.read():
719 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500720
721 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500722 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500723 for f in files:
724 addfile(os.path.join(root, f))
725 else:
726 addfile(filename)
727
728def _check_preserve(config, recipename):
729 """Check if a file was manually changed and needs to be saved in 'attic'
730 directory"""
731 import bb.utils
732 origfile = os.path.join(config.workspace_path, '.devtool_md5')
733 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500734 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500735 with open(origfile, 'r') as f:
736 with open(newfile, 'w') as tf:
737 for line in f.readlines():
738 splitline = line.rstrip().split('|')
739 if splitline[0] == recipename:
740 removefile = os.path.join(config.workspace_path, splitline[1])
741 try:
742 md5 = bb.utils.md5_file(removefile)
743 except IOError as err:
744 if err.errno == 2:
745 # File no longer exists, skip it
746 continue
747 else:
748 raise
749 if splitline[2] != md5:
750 bb.utils.mkdirhier(preservepath)
751 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800752 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500753 shutil.move(removefile, os.path.join(preservepath, preservefile))
754 else:
755 os.remove(removefile)
756 else:
757 tf.write(line)
Andrew Geisslerc926e172021-05-07 16:11:35 -0500758 bb.utils.rename(newfile, origfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500759
Brad Bishop96ff1982019-08-19 13:50:42 -0400760def get_staging_kver(srcdir):
761 # Kernel version from work-shared
762 kerver = []
763 staging_kerVer=""
764 if os.path.exists(srcdir) and os.listdir(srcdir):
765 with open(os.path.join(srcdir,"Makefile")) as f:
766 version = [next(f) for x in range(5)][1:4]
767 for word in version:
768 kerver.append(word.split('= ')[1].split('\n')[0])
769 staging_kerVer = ".".join(kerver)
770 return staging_kerVer
771
772def get_staging_kbranch(srcdir):
773 staging_kbranch = ""
774 if os.path.exists(srcdir) and os.listdir(srcdir):
Patrick Williams705982a2024-01-12 09:51:57 -0600775 (branch, _) = bb.process.run('git branch | grep \\* | cut -d \' \' -f2', cwd=srcdir)
Brad Bishop96ff1982019-08-19 13:50:42 -0400776 staging_kbranch = "".join(branch.split('\n')[0])
777 return staging_kbranch
778
Andrew Geissler517393d2023-01-13 08:55:19 -0600779def get_real_srctree(srctree, s, workdir):
780 # Check that recipe isn't using a shared workdir
781 s = os.path.abspath(s)
782 workdir = os.path.abspath(workdir)
783 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
784 # Handle if S is set to a subdirectory of the source
785 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
786 srctree = os.path.join(srctree, srcsubdir)
787 return srctree
788
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500789def modify(args, config, basepath, workspace):
790 """Entry point for the devtool 'modify' subcommand"""
791 import bb
792 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400793 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400794 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500795
796 if args.recipename in workspace:
797 raise DevtoolError("recipe %s is already in your workspace" %
798 args.recipename)
799
Brad Bishop316dfdd2018-06-25 12:45:53 -0400800 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600801 try:
802 rd = parse_recipe(config, tinfoil, args.recipename, True)
803 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500804 return 1
805
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500806 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600807 if pn != args.recipename:
808 logger.info('Mapping %s to %s' % (args.recipename, pn))
809 if pn in workspace:
810 raise DevtoolError("recipe %s is already in your workspace" %
811 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500812
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600813 if args.srctree:
814 srctree = os.path.abspath(args.srctree)
815 else:
816 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500817
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600818 if args.no_extract and not os.path.isdir(srctree):
819 raise DevtoolError("--no-extract specified and source path %s does "
820 "not exist or is not a directory" %
821 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600822
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500823 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600824 appendfile = recipe_to_append(recipefile, config, args.wildcard)
825 if os.path.exists(appendfile):
826 raise DevtoolError("Another variant of recipe %s is already in your "
827 "workspace (only one variant of a recipe can "
828 "currently be worked on at once)"
829 % pn)
830
831 _check_compatible_recipe(pn, rd)
832
Patrick Williamsda295312023-12-05 16:48:56 -0600833 initial_revs = {}
834 commits = {}
Brad Bishop316dfdd2018-06-25 12:45:53 -0400835 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400836
837 if bb.data.inherits_class('kernel-yocto', rd):
838 # Current set kernel version
839 kernelVersion = rd.getVar('LINUX_VERSION')
840 srcdir = rd.getVar('STAGING_KERNEL_DIR')
841 kbranch = rd.getVar('KBRANCH')
842
843 staging_kerVer = get_staging_kver(srcdir)
844 staging_kbranch = get_staging_kbranch(srcdir)
845 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
846 oe.path.copyhardlinktree(srcdir,srctree)
847 workdir = rd.getVar('WORKDIR')
848 srcsubdir = rd.getVar('S')
849 localfilesdir = os.path.join(srctree,'oe-local-files')
850 # Move local source files into separate subdir
851 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
852 local_files = oe.recipeutils.get_recipe_local_files(rd)
853
854 for key in local_files.copy():
855 if key.endswith('scc'):
856 sccfile = open(local_files[key], 'r')
857 for l in sccfile:
858 line = l.split()
859 if line and line[0] in ('kconf', 'patch'):
860 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
861 if not cfg in local_files.values():
862 local_files[line[-1]] = cfg
863 shutil.copy2(cfg, workdir)
864 sccfile.close()
865
866 # Ignore local files with subdir={BP}
867 srcabspath = os.path.abspath(srcsubdir)
868 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))]
869 if local_files:
870 for fname in local_files:
871 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
872 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
873 f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n')
874
875 symlink_oelocal_files_srctree(rd,srctree)
876
877 task = 'do_configure'
878 res = tinfoil.build_targets(pn, task, handle_events=True)
879
880 # Copy .config to workspace
881 kconfpath = rd.getVar('B')
882 logger.info('Copying kernel config to workspace')
883 shutil.copy2(os.path.join(kconfpath, '.config'),srctree)
884
885 # Set this to true, we still need to get initial_rev
886 # by parsing the git repo
887 args.no_extract = True
888
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600889 if not args.no_extract:
Patrick Williamsda295312023-12-05 16:48:56 -0600890 initial_revs["."], _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides)
891 if not initial_revs["."]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600892 return 1
893 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600894
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600895 if os.path.exists(os.path.join(srctree, '.git')):
896 # Get list of commits since this revision
Patrick Williamsda295312023-12-05 16:48:56 -0600897 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_revs["."], cwd=srctree)
898 commits["."] = stdout.split()
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600899 check_commits = True
Patrick Williamsda295312023-12-05 16:48:56 -0600900 (stdout, _) = bb.process.run('git submodule --quiet foreach --recursive \'echo `git rev-parse devtool-base` $PWD\'', cwd=srctree)
901 for line in stdout.splitlines():
902 (rev, submodule_path) = line.split()
903 submodule = os.path.relpath(submodule_path, srctree)
904 initial_revs[submodule] = rev
905 (stdout, _) = bb.process.run('git rev-list --reverse devtool-base..HEAD', cwd=submodule_path)
906 commits[submodule] = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600907 else:
908 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600909 # Check if it's a tree previously extracted by us. This is done
910 # by ensuring that devtool-base and args.branch (devtool) exist.
911 # The check_commits logic will cause an exception if either one
912 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600913 try:
914 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600915 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600916 except bb.process.ExecutionError:
917 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400918 if stdout:
919 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600920 for line in stdout.splitlines():
921 if line.startswith('*'):
922 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600923 initial_revs["."] = stdout.rstrip()
Patrick Williams705982a2024-01-12 09:51:57 -0600924 if "." not in initial_revs:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600925 # Otherwise, just grab the head revision
926 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
Patrick Williamsda295312023-12-05 16:48:56 -0600927 initial_revs["."] = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500928
Brad Bishop316dfdd2018-06-25 12:45:53 -0400929 branch_patches = {}
930 if check_commits:
931 # Check if there are override branches
932 (stdout, _) = bb.process.run('git branch', cwd=srctree)
933 branches = []
934 for line in stdout.rstrip().splitlines():
935 branchname = line[2:].rstrip()
936 if branchname.startswith(override_branch_prefix):
937 branches.append(branchname)
938 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800939 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 -0400940 branches.insert(0, args.branch)
941 seen_patches = []
942 for branch in branches:
943 branch_patches[branch] = []
944 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
945 for line in stdout.splitlines():
946 line = line.strip()
947 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
948 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
949 if not origpatch in seen_patches:
950 seen_patches.append(origpatch)
951 branch_patches[branch].append(origpatch)
952
953 # Need to grab this here in case the source is within a subdirectory
954 srctreebase = srctree
Andrew Geissler517393d2023-01-13 08:55:19 -0600955 srctree = get_real_srctree(srctree, rd.getVar('S'), rd.getVar('WORKDIR'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500956
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600957 bb.utils.mkdirhier(os.path.dirname(appendfile))
958 with open(appendfile, 'w') as f:
Patrick Williamsf52e3dd2024-01-26 13:04:43 -0600959 # if not present, add type=git-dependency to the secondary sources
960 # (non local files) so they can be extracted correctly when building a recipe after
961 # doing a devtool modify on it
962 src_uri = rd.getVar('SRC_URI').split()
963 src_uri_append = []
964 src_uri_remove = []
965
966 # Assume first entry is main source extracted in ${S} so skip it
967 src_uri = src_uri[1::]
968
969 #Add "type=git-dependency" to all non local sources
970 for url in src_uri:
971 if not url.startswith('file://') and not 'type=' in url:
972 src_uri_remove.append(url)
973 src_uri_append.append('%s;type=git-dependency' % url)
974
975 if src_uri_remove:
976 f.write('SRC_URI:remove = "%s"\n' % ' '.join(src_uri_remove))
977 f.write('SRC_URI:append = "%s"\n\n' % ' '.join(src_uri_append))
978
Patrick Williams213cb262021-08-07 19:21:33 -0500979 f.write('FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600980 # Local files can be modified/tracked in separate subdir under srctree
981 # Mostly useful for packages with S != WORKDIR
Patrick Williams213cb262021-08-07 19:21:33 -0500982 f.write('FILESPATH:prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400983 os.path.join(srctreebase, 'oe-local-files'))
984 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500985
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600986 f.write('\ninherit externalsrc\n')
987 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 -0500988 f.write('EXTERNALSRC:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500989
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600990 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
991 if b_is_s:
Patrick Williams213cb262021-08-07 19:21:33 -0500992 f.write('EXTERNALSRC_BUILD:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500993
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600994 if bb.data.inherits_class('kernel', rd):
995 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500996 'do_fetch do_unpack do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400997 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500998 f.write('\ndo_configure:append() {\n'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600999 ' cp ${B}/.config ${S}/.config.baseline\n'
1000 ' ln -sfT ${B}/.config ${S}/.config.new\n'
1001 '}\n')
Patrick Williams213cb262021-08-07 19:21:33 -05001002 f.write('\ndo_kernel_configme:prepend() {\n'
Andrew Geissler95ac1b82021-03-31 14:34:31 -05001003 ' if [ -e ${S}/.config ]; then\n'
1004 ' mv ${S}/.config ${S}/.config.old\n'
1005 ' fi\n'
1006 '}\n')
Brad Bishop96ff1982019-08-19 13:50:42 -04001007 if rd.getVarFlag('do_menuconfig','task'):
Patrick Williams213cb262021-08-07 19:21:33 -05001008 f.write('\ndo_configure:append() {\n'
Patrick Williams169d7bc2024-01-05 11:33:25 -06001009 ' if [ ${@oe.types.boolean(d.getVar("KCONFIG_CONFIG_ENABLE_MENUCONFIG"))} = True ]; then\n'
Patrick Williams520786c2023-06-25 16:20:36 -05001010 ' cp ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.baseline\n'
1011 ' ln -sfT ${KCONFIG_CONFIG_ROOTDIR}/.config ${S}/.config.new\n'
Andrew Geissler82c905d2020-04-13 13:39:40 -05001012 ' fi\n'
Brad Bishop96ff1982019-08-19 13:50:42 -04001013 '}\n')
Patrick Williamsda295312023-12-05 16:48:56 -06001014 if initial_revs:
1015 for name, rev in initial_revs.items():
Patrick Williams169d7bc2024-01-05 11:33:25 -06001016 f.write('\n# initial_rev %s: %s\n' % (name, rev))
1017 if name in commits:
Patrick Williamsda295312023-12-05 16:48:56 -06001018 for commit in commits[name]:
1019 f.write('# commit %s: %s\n' % (name, commit))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001020 if branch_patches:
1021 for branch in branch_patches:
1022 if branch == args.branch:
1023 continue
1024 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001025
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001026 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
1027
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001028 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001029
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001030 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001031
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001032 finally:
1033 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001034
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001035 return 0
1036
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001037
1038def rename(args, config, basepath, workspace):
1039 """Entry point for the devtool 'rename' subcommand"""
1040 import bb
1041 import oe.recipeutils
1042
1043 check_workspace_recipe(workspace, args.recipename)
1044
1045 if not (args.newname or args.version):
1046 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
1047
1048 recipefile = workspace[args.recipename]['recipefile']
1049 if not recipefile:
1050 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
1051
1052 if args.newname and args.newname != args.recipename:
1053 reason = oe.recipeutils.validate_pn(args.newname)
1054 if reason:
1055 raise DevtoolError(reason)
1056 newname = args.newname
1057 else:
1058 newname = args.recipename
1059
1060 append = workspace[args.recipename]['bbappend']
1061 appendfn = os.path.splitext(os.path.basename(append))[0]
1062 splitfn = appendfn.split('_')
1063 if len(splitfn) > 1:
1064 origfnver = appendfn.split('_')[1]
1065 else:
1066 origfnver = ''
1067
1068 recipefilemd5 = None
1069 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1070 try:
1071 rd = parse_recipe(config, tinfoil, args.recipename, True)
1072 if not rd:
1073 return 1
1074
1075 bp = rd.getVar('BP')
1076 bpn = rd.getVar('BPN')
1077 if newname != args.recipename:
1078 localdata = rd.createCopy()
1079 localdata.setVar('PN', newname)
1080 newbpn = localdata.getVar('BPN')
1081 else:
1082 newbpn = bpn
1083 s = rd.getVar('S', False)
1084 src_uri = rd.getVar('SRC_URI', False)
1085 pv = rd.getVar('PV')
1086
1087 # Correct variable values that refer to the upstream source - these
1088 # values must stay the same, so if the name/version are changing then
1089 # we need to fix them up
1090 new_s = s
1091 new_src_uri = src_uri
1092 if newbpn != bpn:
1093 # ${PN} here is technically almost always incorrect, but people do use it
1094 new_s = new_s.replace('${BPN}', bpn)
1095 new_s = new_s.replace('${PN}', bpn)
1096 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1097 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1098 new_src_uri = new_src_uri.replace('${PN}', bpn)
1099 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1100 if args.version and origfnver == pv:
1101 new_s = new_s.replace('${PV}', pv)
1102 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1103 new_src_uri = new_src_uri.replace('${PV}', pv)
1104 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1105 patchfields = {}
1106 if new_s != s:
1107 patchfields['S'] = new_s
1108 if new_src_uri != src_uri:
1109 patchfields['SRC_URI'] = new_src_uri
1110 if patchfields:
1111 recipefilemd5 = bb.utils.md5_file(recipefile)
1112 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1113 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1114 finally:
1115 tinfoil.shutdown()
1116
1117 if args.version:
1118 newver = args.version
1119 else:
1120 newver = origfnver
1121
1122 if newver:
1123 newappend = '%s_%s.bbappend' % (newname, newver)
1124 newfile = '%s_%s.bb' % (newname, newver)
1125 else:
1126 newappend = '%s.bbappend' % newname
1127 newfile = '%s.bb' % newname
1128
1129 oldrecipedir = os.path.dirname(recipefile)
1130 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1131 if oldrecipedir != newrecipedir:
1132 bb.utils.mkdirhier(newrecipedir)
1133
1134 newappend = os.path.join(os.path.dirname(append), newappend)
1135 newfile = os.path.join(newrecipedir, newfile)
1136
1137 # Rename bbappend
1138 logger.info('Renaming %s to %s' % (append, newappend))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001139 bb.utils.rename(append, newappend)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001140 # Rename recipe file
1141 logger.info('Renaming %s to %s' % (recipefile, newfile))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001142 bb.utils.rename(recipefile, newfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001143
1144 # Rename source tree if it's the default path
1145 appendmd5 = None
1146 if not args.no_srctree:
1147 srctree = workspace[args.recipename]['srctree']
1148 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1149 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1150 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1151 shutil.move(srctree, newsrctree)
1152 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1153 appendmd5 = bb.utils.md5_file(newappend)
1154 appendlines = []
1155 with open(newappend, 'r') as f:
1156 for line in f:
1157 appendlines.append(line)
1158 with open(newappend, 'w') as f:
1159 for line in appendlines:
1160 if srctree in line:
1161 line = line.replace(srctree, newsrctree)
1162 f.write(line)
1163 newappendmd5 = bb.utils.md5_file(newappend)
1164
1165 bpndir = None
1166 newbpndir = None
1167 if newbpn != bpn:
1168 bpndir = os.path.join(oldrecipedir, bpn)
1169 if os.path.exists(bpndir):
1170 newbpndir = os.path.join(newrecipedir, newbpn)
1171 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1172 shutil.move(bpndir, newbpndir)
1173
1174 bpdir = None
1175 newbpdir = None
1176 if newver != origfnver or newbpn != bpn:
1177 bpdir = os.path.join(oldrecipedir, bp)
1178 if os.path.exists(bpdir):
1179 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1180 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1181 shutil.move(bpdir, newbpdir)
1182
1183 if oldrecipedir != newrecipedir:
1184 # Move any stray files and delete the old recipe directory
1185 for entry in os.listdir(oldrecipedir):
1186 oldpath = os.path.join(oldrecipedir, entry)
1187 newpath = os.path.join(newrecipedir, entry)
1188 logger.info('Renaming %s to %s' % (oldpath, newpath))
1189 shutil.move(oldpath, newpath)
1190 os.rmdir(oldrecipedir)
1191
1192 # Now take care of entries in .devtool_md5
1193 md5entries = []
1194 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1195 for line in f:
1196 md5entries.append(line)
1197
1198 if bpndir and newbpndir:
1199 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1200 else:
1201 relbpndir = None
1202 if bpdir and newbpdir:
1203 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1204 else:
1205 relbpdir = None
1206
1207 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1208 for entry in md5entries:
1209 splitentry = entry.rstrip().split('|')
1210 if len(splitentry) > 2:
1211 if splitentry[0] == args.recipename:
1212 splitentry[0] = newname
1213 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1214 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1215 if appendmd5 and splitentry[2] == appendmd5:
1216 splitentry[2] = newappendmd5
1217 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1218 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1219 if recipefilemd5 and splitentry[2] == recipefilemd5:
1220 splitentry[2] = newrecipefilemd5
1221 elif relbpndir and splitentry[1].startswith(relbpndir):
1222 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1223 elif relbpdir and splitentry[1].startswith(relbpdir):
1224 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1225 entry = '|'.join(splitentry) + '\n'
1226 f.write(entry)
1227 return 0
1228
1229
Brad Bishop316dfdd2018-06-25 12:45:53 -04001230def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001231 """Get initial and update rev of a recipe. These are the start point of the
1232 whole patchset and start point for the patches to be re-generated/updated.
1233 """
1234 import bb
1235
Brad Bishop316dfdd2018-06-25 12:45:53 -04001236 # Get current branch
1237 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1238 cwd=srctree)
1239 branchname = stdout.rstrip()
1240
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001241 # Parse initial rev from recipe if not specified
Patrick Williamsda295312023-12-05 16:48:56 -06001242 commits = {}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001243 patches = []
Patrick Williamsda295312023-12-05 16:48:56 -06001244 initial_revs = {}
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001245 with open(recipe_path, 'r') as f:
1246 for line in f:
Patrick Williamsda295312023-12-05 16:48:56 -06001247 pattern = r'^#\s.*\s(.*):\s([0-9a-fA-F]+)$'
1248 match = re.search(pattern, line)
1249 if match:
1250 name = match.group(1)
1251 rev = match.group(2)
1252 if line.startswith('# initial_rev'):
1253 if not (name == "." and initial_rev):
1254 initial_revs[name] = rev
1255 elif line.startswith('# commit') and not force_patch_refresh:
1256 if name not in commits:
1257 commits[name] = [rev]
1258 else:
1259 commits[name].append(rev)
1260 elif line.startswith('# patches_%s:' % branchname):
1261 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001262
Patrick Williamsda295312023-12-05 16:48:56 -06001263 update_revs = dict(initial_revs)
1264 changed_revs = {}
1265 for name, rev in initial_revs.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001266 # Find first actually changed revision
1267 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
Patrick Williamsda295312023-12-05 16:48:56 -06001268 rev, cwd=os.path.join(srctree, name))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001269 newcommits = stdout.split()
Patrick Williamsda295312023-12-05 16:48:56 -06001270 if name in commits:
1271 for i in range(min(len(commits[name]), len(newcommits))):
1272 if newcommits[i] == commits[name][i]:
1273 update_revs[name] = commits[name][i]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001274
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001275 try:
1276 stdout, _ = bb.process.run('git cherry devtool-patched',
Patrick Williamsda295312023-12-05 16:48:56 -06001277 cwd=os.path.join(srctree, name))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001278 except bb.process.ExecutionError as err:
1279 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001280
Brad Bishop316dfdd2018-06-25 12:45:53 -04001281 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001282 for line in stdout.splitlines():
1283 if line.startswith('+ '):
1284 rev = line.split()[1]
1285 if rev in newcommits:
Patrick Williamsda295312023-12-05 16:48:56 -06001286 if name not in changed_revs:
1287 changed_revs[name] = [rev]
1288 else:
1289 changed_revs[name].append(rev)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001290
Patrick Williamsda295312023-12-05 16:48:56 -06001291 return initial_revs, update_revs, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001292
1293def _remove_file_entries(srcuri, filelist):
1294 """Remove file:// entries from SRC_URI"""
1295 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001296 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001297 for fname in filelist:
1298 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001299 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001300 if (srcuri[i].startswith('file://') and
1301 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001302 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001303 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001304 srcuri.pop(i)
1305 break
1306 return entries, remaining
1307
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001308def _replace_srcuri_entry(srcuri, filename, newentry):
1309 """Replace entry corresponding to specified file with a new entry"""
1310 basename = os.path.basename(filename)
1311 for i in range(len(srcuri)):
1312 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1313 srcuri.pop(i)
1314 srcuri.insert(i, newentry)
1315 break
1316
Brad Bishop316dfdd2018-06-25 12:45:53 -04001317def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001318 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001319
1320 dry_run_suffix = ' (dry-run)' if dry_run else ''
1321
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001322 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001323 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001324 if not destpath:
1325 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001326 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001327
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001328 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001329 if not no_report_remove:
1330 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1331 if not dry_run:
1332 # FIXME "git rm" here would be nice if the file in question is
1333 # tracked
1334 # FIXME there's a chance that this file is referred to by
1335 # another recipe, in which case deleting wouldn't be the
1336 # right thing to do
1337 os.remove(path)
1338 # Remove directory if empty
1339 try:
1340 os.rmdir(os.path.dirname(path))
1341 except OSError as ose:
1342 if ose.errno != errno.ENOTEMPTY:
1343 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001344
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001345
Patrick Williamsda295312023-12-05 16:48:56 -06001346def _export_patches(srctree, rd, start_revs, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001347 """Export patches from srctree to given location.
1348 Returns three-tuple of dicts:
1349 1. updated - patches that already exist in SRCURI
1350 2. added - new patches that don't exist in SRCURI
1351 3 removed - patches that exist in SRCURI but not in exported patches
Patrick Williamsda295312023-12-05 16:48:56 -06001352 In each dict the key is the 'basepath' of the URI and value is:
1353 - for updated and added dicts, a dict with 2 optionnal keys:
1354 - 'path': the absolute path to the existing file in recipe space (if any)
1355 - 'patchdir': the directory in wich the patch should be applied (if any)
1356 - for removed dict, the absolute path to the existing file in recipe space
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001357 """
1358 import oe.recipeutils
1359 from oe.patch import GitApplyTree
1360 updated = OrderedDict()
1361 added = OrderedDict()
1362 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1363
1364 existing_patches = dict((os.path.basename(path), path) for path in
1365 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001366 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001367
1368 # Generate patches from Git, exclude local files directory
1369 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
Patrick Williamsda295312023-12-05 16:48:56 -06001370 GitApplyTree.extractPatches(srctree, start_revs, destdir, patch_pathspec)
1371 for dirpath, dirnames, filenames in os.walk(destdir):
1372 new_patches = filenames
1373 reldirpath = os.path.relpath(dirpath, destdir)
1374 for new_patch in new_patches:
1375 # Strip numbering from patch names. If it's a git sequence named patch,
1376 # the numbers might not match up since we are starting from a different
1377 # revision This does assume that people are using unique shortlog
1378 # values, but they ought to be anyway...
1379 new_basename = seqpatch_re.match(new_patch).group(2)
1380 match_name = None
1381 for old_patch in existing_patches:
1382 old_basename = seqpatch_re.match(old_patch).group(2)
1383 old_basename_splitext = os.path.splitext(old_basename)
1384 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1385 old_patch_noext = os.path.splitext(old_patch)[0]
1386 match_name = old_patch_noext
1387 break
1388 elif new_basename == old_basename:
1389 match_name = old_patch
1390 break
1391 if match_name:
1392 # Rename patch files
1393 if new_patch != match_name:
1394 bb.utils.rename(os.path.join(destdir, new_patch),
1395 os.path.join(destdir, match_name))
1396 # Need to pop it off the list now before checking changed_revs
1397 oldpath = existing_patches.pop(old_patch)
1398 if changed_revs is not None and dirpath in changed_revs:
1399 # Avoid updating patches that have not actually changed
1400 with open(os.path.join(dirpath, match_name), 'r') as f:
1401 firstlineitems = f.readline().split()
1402 # Looking for "From <hash>" line
1403 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1404 if not firstlineitems[1] in changed_revs[dirpath]:
1405 continue
1406 # Recompress if necessary
1407 if oldpath.endswith(('.gz', '.Z')):
1408 bb.process.run(['gzip', match_name], cwd=destdir)
1409 if oldpath.endswith('.gz'):
1410 match_name += '.gz'
1411 else:
1412 match_name += '.Z'
1413 elif oldpath.endswith('.bz2'):
1414 bb.process.run(['bzip2', match_name], cwd=destdir)
1415 match_name += '.bz2'
1416 updated[match_name] = {'path' : oldpath}
1417 if reldirpath != ".":
1418 updated[match_name]['patchdir'] = reldirpath
1419 else:
1420 added[new_patch] = {}
1421 if reldirpath != ".":
1422 added[new_patch]['patchdir'] = reldirpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001423
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001424 return (updated, added, existing_patches)
1425
1426
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001427def _create_kconfig_diff(srctree, rd, outfile):
1428 """Create a kconfig fragment"""
1429 # Only update config fragment if both config files exist
1430 orig_config = os.path.join(srctree, '.config.baseline')
1431 new_config = os.path.join(srctree, '.config.new')
1432 if os.path.exists(orig_config) and os.path.exists(new_config):
1433 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1434 '--unchanged-line-format=', orig_config, new_config]
1435 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1436 stderr=subprocess.PIPE)
1437 stdout, stderr = pipe.communicate()
1438 if pipe.returncode == 1:
1439 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001440 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001441 fobj.write(stdout)
1442 elif pipe.returncode == 0:
1443 logger.info("Would remove config fragment %s" % outfile)
1444 if os.path.exists(outfile):
1445 # Remove fragment file in case of empty diff
1446 logger.info("Removing config fragment %s" % outfile)
1447 os.unlink(outfile)
1448 else:
1449 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1450 return True
1451 return False
1452
1453
Brad Bishop316dfdd2018-06-25 12:45:53 -04001454def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001455 """Copy local files from srctree to given location.
1456 Returns three-tuple of dicts:
1457 1. updated - files that already exist in SRCURI
1458 2. added - new files files that don't exist in SRCURI
1459 3 removed - files that exist in SRCURI but not in exported files
1460 In each dict the key is the 'basepath' of the URI and value is the
1461 absolute path to the existing file in recipe space (if any).
1462 """
1463 import oe.recipeutils
1464
1465 # Find out local files (SRC_URI files that exist in the "recipe space").
1466 # Local files that reside in srctree are not included in patch generation.
1467 # Instead they are directly copied over the original source files (in
1468 # recipe space).
1469 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1470 new_set = None
1471 updated = OrderedDict()
1472 added = OrderedDict()
1473 removed = OrderedDict()
Andrew Geissler517393d2023-01-13 08:55:19 -06001474
1475 # Get current branch and return early with empty lists
1476 # if on one of the override branches
1477 # (local files are provided only for the main branch and processing
1478 # them against lists from recipe overrides will result in mismatches
1479 # and broken modifications to recipes).
1480 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1481 cwd=srctree)
1482 branchname = stdout.rstrip()
1483 if branchname.startswith(override_branch_prefix):
1484 return (updated, added, removed)
1485
Brad Bishop316dfdd2018-06-25 12:45:53 -04001486 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001487 git_files = _git_ls_tree(srctree)
1488 if 'oe-local-files' in git_files:
1489 # If tracked by Git, take the files from srctree HEAD. First get
1490 # the tree object of the directory
1491 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1492 tree = git_files['oe-local-files'][2]
1493 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1494 env=dict(os.environ, GIT_WORK_TREE=destdir,
1495 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001496 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001497 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001498 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001499 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001500 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001501 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001502 else:
1503 new_set = []
1504
1505 # Special handling for kernel config
1506 if bb.data.inherits_class('kernel-yocto', rd):
1507 fragment_fn = 'devtool-fragment.cfg'
1508 fragment_path = os.path.join(destdir, fragment_fn)
1509 if _create_kconfig_diff(srctree, rd, fragment_path):
1510 if os.path.exists(fragment_path):
1511 if fragment_fn not in new_set:
1512 new_set.append(fragment_fn)
1513 # Copy fragment to local-files
1514 if os.path.isdir(local_files_dir):
1515 shutil.copy2(fragment_path, local_files_dir)
1516 else:
1517 if fragment_fn in new_set:
1518 new_set.remove(fragment_fn)
1519 # Remove fragment from local-files
1520 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1521 os.unlink(os.path.join(local_files_dir, fragment_fn))
1522
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001523 # Special handling for cml1, ccmake, etc bbclasses that generated
1524 # configuration fragment files that are consumed as source files
1525 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1526 if bb.data.inherits_class(frag_class, rd):
1527 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1528 if os.path.exists(srcpath):
1529 if frag_name not in new_set:
1530 new_set.append(frag_name)
1531 # copy fragment into destdir
1532 shutil.copy2(srcpath, destdir)
1533 # copy fragment into local files if exists
1534 if os.path.isdir(local_files_dir):
1535 shutil.copy2(srcpath, local_files_dir)
1536
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001537 if new_set is not None:
1538 for fname in new_set:
1539 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001540 origpath = existing_files.pop(fname)
1541 workpath = os.path.join(local_files_dir, fname)
1542 if not filecmp.cmp(origpath, workpath):
1543 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001544 elif fname != '.gitignore':
1545 added[fname] = None
1546
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001547 workdir = rd.getVar('WORKDIR')
1548 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001549 if not s.endswith(os.sep):
1550 s += os.sep
1551
1552 if workdir != s:
1553 # Handle files where subdir= was specified
1554 for fname in list(existing_files.keys()):
1555 # FIXME handle both subdir starting with BP and not?
1556 fworkpath = os.path.join(workdir, fname)
1557 if fworkpath.startswith(s):
1558 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1559 if os.path.exists(fpath):
1560 origpath = existing_files.pop(fname)
1561 if not filecmp.cmp(origpath, fpath):
1562 updated[fpath] = origpath
1563
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001564 removed = existing_files
1565 return (updated, added, removed)
1566
1567
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001568def _determine_files_dir(rd):
1569 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001570 recipedir = rd.getVar('FILE_DIRNAME')
1571 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001572 relpth = os.path.relpath(entry, recipedir)
1573 if not os.sep in relpth:
1574 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1575 if os.path.isdir(entry):
1576 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001577 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001578
1579
Brad Bishop316dfdd2018-06-25 12:45:53 -04001580def _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 -05001581 """Implement the 'srcrev' mode of update-recipe"""
1582 import bb
1583 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001584
Brad Bishop316dfdd2018-06-25 12:45:53 -04001585 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1586
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001587 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001588 recipedir = os.path.basename(recipefile)
1589 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001590
Patrick Williamsda295312023-12-05 16:48:56 -06001591 # Get original SRCREV
1592 old_srcrev = rd.getVar('SRCREV') or ''
1593 if old_srcrev == "INVALID":
1594 raise DevtoolError('Update mode srcrev is only valid for recipe fetched from an SCM repository')
1595 old_srcrev = {'.': old_srcrev}
1596
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001597 # Get HEAD revision
1598 try:
1599 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1600 except bb.process.ExecutionError as err:
1601 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1602 (srctree, err))
1603 srcrev = stdout.strip()
1604 if len(srcrev) != 40:
1605 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1606
1607 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001608 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001609 patchfields = {}
1610 patchfields['SRCREV'] = srcrev
1611 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001612 srcuri = orig_src_uri.split()
1613 tempdir = tempfile.mkdtemp(prefix='devtool')
1614 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001615 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001616 try:
1617 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001618 srctreebase = workspace[recipename]['srctreebase']
1619 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001620 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001621 # Find list of existing patches in recipe file
1622 patches_dir = tempfile.mkdtemp(dir=tempdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001623 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1624 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001625 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 -05001626
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001627 # Remove deleted local files and "overlapping" patches
Patrick Williamsda295312023-12-05 16:48:56 -06001628 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 -05001629 if remove_files:
1630 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1631 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001632
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001633 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001634 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001635 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001636 removevalues = {}
1637 if update_srcuri:
1638 removevalues = {'SRC_URI': removedentries}
1639 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001640 if dry_run_outdir:
1641 logger.info('Creating bbappend (dry-run)')
Patrick Williamsda295312023-12-05 16:48:56 -06001642 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1643 rd, appendlayerdir, files, wildcardver=wildcard_version,
1644 extralines=patchfields, removevalues=removevalues,
1645 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001646 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001647 files_dir = _determine_files_dir(rd)
1648 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001649 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001650 if os.path.isabs(basepath):
1651 # Original file (probably with subdir pointing inside source tree)
1652 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001653 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001654 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001655 _move_file(os.path.join(local_files_dir, basepath), path,
1656 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001657 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001658 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001659 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001660 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001661 os.path.join(files_dir, basepath),
1662 dry_run_outdir=dry_run_outdir,
1663 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001664 srcuri.append('file://%s' % basepath)
1665 update_srcuri = True
1666 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001667 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001668 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001669 finally:
1670 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001671 if not 'git://' in orig_src_uri:
1672 logger.info('You will need to update SRC_URI within the recipe to '
1673 'point to a git repository where you have pushed your '
1674 'changes')
1675
Brad Bishop316dfdd2018-06-25 12:45:53 -04001676 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1677 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001678
Brad Bishop316dfdd2018-06-25 12:45:53 -04001679def _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 -05001680 """Implement the 'patch' mode of update-recipe"""
1681 import bb
1682 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001683
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001684 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001685 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001686 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001687 if not os.path.exists(append):
1688 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001689 recipename)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001690 srctreebase = workspace[recipename]['srctreebase']
1691 relpatchdir = os.path.relpath(srctreebase, srctree)
1692 if relpatchdir == '.':
1693 patchdir_params = {}
1694 else:
1695 patchdir_params = {'patchdir': relpatchdir}
1696
Patrick Williamsda295312023-12-05 16:48:56 -06001697 def srcuri_entry(basepath, patchdir_params):
Andrew Geissler615f2f12022-07-15 14:00:58 -05001698 if patchdir_params:
1699 paramstr = ';' + ';'.join('%s=%s' % (k,v) for k,v in patchdir_params.items())
1700 else:
1701 paramstr = ''
1702 return 'file://%s%s' % (basepath, paramstr)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001703
Patrick Williamsda295312023-12-05 16:48:56 -06001704 initial_revs, update_revs, changed_revs, filter_patches = _get_patchset_revs(srctree, append, initial_rev, force_patch_refresh)
1705 if not initial_revs:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001706 raise DevtoolError('Unable to find initial revision - please specify '
1707 'it with --initial-rev')
1708
Brad Bishop316dfdd2018-06-25 12:45:53 -04001709 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001710 dl_dir = rd.getVar('DL_DIR')
1711 if not dl_dir.endswith('/'):
1712 dl_dir += '/'
1713
Brad Bishop316dfdd2018-06-25 12:45:53 -04001714 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1715
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001716 tempdir = tempfile.mkdtemp(prefix='devtool')
1717 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001718 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Andrew Geissler517393d2023-01-13 08:55:19 -06001719 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001720
1721 # Get updated patches from source tree
1722 patches_dir = tempfile.mkdtemp(dir=tempdir)
Patrick Williamsda295312023-12-05 16:48:56 -06001723 upd_p, new_p, _ = _export_patches(srctree, rd, update_revs,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001724 patches_dir, changed_revs)
Andrew Geissler517393d2023-01-13 08:55:19 -06001725 # Get all patches from source tree and check if any should be removed
1726 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Patrick Williamsda295312023-12-05 16:48:56 -06001727 _, _, del_p = _export_patches(srctree, rd, initial_revs,
Andrew Geissler517393d2023-01-13 08:55:19 -06001728 all_patches_dir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001729 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1730 if filter_patches:
Brad Bishop00e122a2019-10-05 11:10:57 -04001731 new_p = OrderedDict()
1732 upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches)
Andrew Geissler517393d2023-01-13 08:55:19 -06001733 del_p = OrderedDict((k,v) for k,v in del_p.items() if k in filter_patches)
1734 remove_files = []
1735 if not no_remove:
1736 # Remove deleted local files and patches
1737 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001738 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001739 updaterecipe = False
1740 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001741 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsda295312023-12-05 16:48:56 -06001742
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001743 if appendlayerdir:
Brad Bishop00e122a2019-10-05 11:10:57 -04001744 files = OrderedDict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001745 key, val in list(upd_f.items()) + list(new_f.items()))
Brad Bishop00e122a2019-10-05 11:10:57 -04001746 files.update(OrderedDict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001747 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsda295312023-12-05 16:48:56 -06001748
1749 params = []
1750 for file, param in files.items():
1751 patchdir_param = dict(patchdir_params)
1752 patchdir = param.get('patchdir', ".")
1753 if patchdir != "." :
1754 if patchdir_param:
1755 patchdir_param['patchdir'] += patchdir
1756 else:
1757 patchdir_param['patchdir'] = patchdir
1758 params.append(patchdir_param)
1759
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001760 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001761 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001762 if remove_files:
1763 removedentries, remaining = _remove_file_entries(
1764 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001765 if removedentries or remaining:
Patrick Williamsda295312023-12-05 16:48:56 -06001766 remaining = [srcuri_entry(os.path.basename(item), patchdir_params) for
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001767 item in remaining]
1768 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001769 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001770 rd, appendlayerdir, files,
1771 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001772 removevalues=removevalues,
Andrew Geissler615f2f12022-07-15 14:00:58 -05001773 redirect_output=dry_run_outdir,
Patrick Williamsda295312023-12-05 16:48:56 -06001774 params=params)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001775 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001776 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001777 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001778 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001779 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001780 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001781 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001782 if os.path.isabs(basepath):
1783 # Original file (probably with subdir pointing inside source tree)
1784 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001785 _copy_file(basepath, path,
1786 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001787 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001788 _move_file(os.path.join(local_files_dir, basepath), path,
1789 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001790 updatefiles = True
Patrick Williamsda295312023-12-05 16:48:56 -06001791 for basepath, param in upd_p.items():
1792 path = param['path']
1793 patchdir = param.get('patchdir', ".")
1794 if patchdir != "." :
1795 patchdir_param = dict(patchdir_params)
1796 if patchdir_param:
1797 patchdir_param['patchdir'] += patchdir
1798 else:
1799 patchdir_param['patchdir'] = patchdir
1800 patchfn = os.path.join(patches_dir, patchdir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001801 if os.path.dirname(path) + '/' == dl_dir:
1802 # This is a a downloaded patch file - we now need to
1803 # replace the entry in SRC_URI with our local version
1804 logger.info('Replacing remote patch %s with updated local version' % basepath)
1805 path = os.path.join(files_dir, basepath)
Patrick Williamsda295312023-12-05 16:48:56 -06001806 _replace_srcuri_entry(srcuri, basepath, srcuri_entry(basepath, patchdir_param))
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001807 updaterecipe = True
1808 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001809 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1810 _move_file(patchfn, path,
1811 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001812 updatefiles = True
1813 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001814 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001815 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001816 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001817 os.path.join(files_dir, basepath),
1818 dry_run_outdir=dry_run_outdir,
1819 base_outdir=recipedir)
Patrick Williamsda295312023-12-05 16:48:56 -06001820 srcuri.append(srcuri_entry(basepath, patchdir_params))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001821 updaterecipe = True
Patrick Williamsda295312023-12-05 16:48:56 -06001822 for basepath, param in new_p.items():
1823 patchdir = param.get('patchdir', ".")
Brad Bishop316dfdd2018-06-25 12:45:53 -04001824 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsda295312023-12-05 16:48:56 -06001825 _move_file(os.path.join(patches_dir, patchdir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001826 os.path.join(files_dir, basepath),
1827 dry_run_outdir=dry_run_outdir,
1828 base_outdir=recipedir)
Patrick Williamsda295312023-12-05 16:48:56 -06001829 params = dict(patchdir_params)
1830 if patchdir != "." :
1831 if params:
1832 params['patchdir'] += patchdir
1833 else:
1834 params['patchdir'] = patchdir
1835
1836 srcuri.append(srcuri_entry(basepath, params))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001837 updaterecipe = True
1838 # Update recipe, if needed
1839 if _remove_file_entries(srcuri, remove_files)[0]:
1840 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001841 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001842 if not dry_run_outdir:
1843 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1844 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1845 {'SRC_URI': ' '.join(srcuri)},
1846 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001847 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001848 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001849 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001850 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001851 finally:
1852 shutil.rmtree(tempdir)
1853
Brad Bishop316dfdd2018-06-25 12:45:53 -04001854 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1855 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001856
1857def _guess_recipe_update_mode(srctree, rdata):
1858 """Guess the recipe update mode to use"""
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001859 src_uri = (rdata.getVar('SRC_URI') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001860 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1861 if not git_uris:
1862 return 'patch'
1863 # Just use the first URI for now
1864 uri = git_uris[0]
1865 # Check remote branch
1866 params = bb.fetch.decodeurl(uri)[5]
1867 upstr_branch = params['branch'] if 'branch' in params else 'master'
1868 # Check if current branch HEAD is found in upstream branch
1869 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1870 head_rev = stdout.rstrip()
1871 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1872 cwd=srctree)
1873 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1874 if 'origin/' + upstr_branch in remote_brs:
1875 return 'srcrev'
1876
1877 return 'patch'
1878
Brad Bishop316dfdd2018-06-25 12:45:53 -04001879def _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 -06001880 srctree = workspace[recipename]['srctree']
1881 if mode == 'auto':
1882 mode = _guess_recipe_update_mode(srctree, rd)
1883
Brad Bishop316dfdd2018-06-25 12:45:53 -04001884 override_branches = []
1885 mainbranch = None
1886 startbranch = None
1887 if not no_overrides:
1888 stdout, _ = bb.process.run('git branch', cwd=srctree)
1889 other_branches = []
1890 for line in stdout.splitlines():
1891 branchname = line[2:]
1892 if line.startswith('* '):
1893 startbranch = branchname
1894 if branchname.startswith(override_branch_prefix):
1895 override_branches.append(branchname)
1896 else:
1897 other_branches.append(branchname)
1898
1899 if override_branches:
1900 logger.debug('_update_recipe: override branches: %s' % override_branches)
1901 logger.debug('_update_recipe: other branches: %s' % other_branches)
1902 if startbranch.startswith(override_branch_prefix):
1903 if len(other_branches) == 1:
1904 mainbranch = other_branches[1]
1905 else:
1906 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1907 else:
1908 mainbranch = startbranch
1909
1910 checkedout = None
1911 anyupdated = False
1912 appendfile = None
1913 allremoved = []
1914 if override_branches:
1915 logger.info('Handling main branch (%s)...' % mainbranch)
1916 if startbranch != mainbranch:
1917 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1918 checkedout = mainbranch
1919 try:
1920 branchlist = [mainbranch] + override_branches
1921 for branch in branchlist:
1922 crd = bb.data.createCopy(rd)
1923 if branch != mainbranch:
1924 logger.info('Handling branch %s...' % branch)
1925 override = branch[len(override_branch_prefix):]
1926 crd.appendVar('OVERRIDES', ':%s' % override)
1927 bb.process.run('git checkout %s' % branch, cwd=srctree)
1928 checkedout = branch
1929
1930 if mode == 'srcrev':
1931 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1932 elif mode == 'patch':
1933 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)
1934 else:
1935 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1936 if updated:
1937 anyupdated = True
1938 if appendf:
1939 appendfile = appendf
1940 allremoved.extend(removed)
1941 finally:
1942 if startbranch and checkedout != startbranch:
1943 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1944
1945 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001946
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001947def update_recipe(args, config, basepath, workspace):
1948 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001949 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001950
1951 if args.append:
1952 if not os.path.exists(args.append):
1953 raise DevtoolError('bbappend destination layer directory "%s" '
1954 'does not exist' % args.append)
1955 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1956 raise DevtoolError('conf/layer.conf not found in bbappend '
1957 'destination layer "%s"' % args.append)
1958
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001959 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001960 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001961
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001962 rd = parse_recipe(config, tinfoil, args.recipename, True)
1963 if not rd:
1964 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001965
Brad Bishop316dfdd2018-06-25 12:45:53 -04001966 dry_run_output = None
1967 dry_run_outdir = None
1968 if args.dry_run:
1969 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1970 dry_run_outdir = dry_run_output.name
1971 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 -05001972
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001973 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001974 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001975 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001976 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 -06001977 finally:
1978 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001979
1980 return 0
1981
1982
1983def status(args, config, basepath, workspace):
1984 """Entry point for the devtool 'status' subcommand"""
1985 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001986 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001987 recipefile = value['recipefile']
1988 if recipefile:
1989 recipestr = ' (%s)' % recipefile
1990 else:
1991 recipestr = ''
1992 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001993 else:
1994 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')
1995 return 0
1996
1997
Brad Bishop64c979e2019-11-04 13:55:29 -05001998def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001999 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04002000 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002001
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002002 def clean_preferred_provider(pn, layerconf_path):
2003 """Remove PREFERRED_PROVIDER from layer.conf'"""
2004 import re
2005 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
2006 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
2007 pprovider_found = False
2008 with open(layerconf_file, 'r') as f:
2009 lines = f.readlines()
2010 with open(new_layerconf_file, 'a') as nf:
2011 for line in lines:
2012 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
2013 if not re.match(pprovider_exp, line):
2014 nf.write(line)
2015 else:
2016 pprovider_found = True
2017 if pprovider_found:
2018 shutil.move(new_layerconf_file, layerconf_file)
2019 else:
2020 os.remove(new_layerconf_file)
2021
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002022 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002023 if len(recipes) == 1:
2024 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
2025 else:
2026 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002027 # If the recipe file itself was created in the workspace, and
2028 # it uses BBCLASSEXTEND, then we need to also clean the other
2029 # variants
2030 targets = []
2031 for recipe in recipes:
2032 targets.append(recipe)
2033 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002034 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002035 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002036 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002037 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002038 except bb.process.ExecutionError as e:
2039 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
2040 'wish, you may specify -n/--no-clean to '
2041 'skip running this command when resetting' %
2042 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002043
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002044 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002045 _check_preserve(config, pn)
2046
Brad Bishop316dfdd2018-06-25 12:45:53 -04002047 appendfile = workspace[pn]['bbappend']
2048 if os.path.exists(appendfile):
2049 # This shouldn't happen, but is possible if devtool errored out prior to
2050 # writing the md5 file. We need to delete this here or the recipe won't
2051 # actually be reset
2052 os.remove(appendfile)
2053
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002054 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002055 def preservedir(origdir):
2056 if os.path.exists(origdir):
2057 for root, dirs, files in os.walk(origdir):
2058 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08002059 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05002060 _move_file(os.path.join(origdir, fn),
2061 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002062 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002063 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002064 os.rmdir(origdir)
2065
Brad Bishop316dfdd2018-06-25 12:45:53 -04002066 recipefile = workspace[pn]['recipefile']
2067 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
2068 # This should always be true if recipefile is set, but just in case
2069 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002070 # We don't automatically create this dir next to appends, but the user can
2071 preservedir(os.path.join(config.workspace_path, 'appends', pn))
2072
Brad Bishop316dfdd2018-06-25 12:45:53 -04002073 srctreebase = workspace[pn]['srctreebase']
2074 if os.path.isdir(srctreebase):
2075 if os.listdir(srctreebase):
Brad Bishop64c979e2019-11-04 13:55:29 -05002076 if remove_work:
2077 logger.info('-r argument used on %s, removing source tree.'
2078 ' You will lose any unsaved work' %pn)
2079 shutil.rmtree(srctreebase)
2080 else:
2081 # We don't want to risk wiping out any work in progress
Patrick Williams92b42cb2022-09-03 06:53:57 -05002082 if srctreebase.startswith(os.path.join(config.workspace_path, 'sources')):
2083 from datetime import datetime
2084 preservesrc = os.path.join(config.workspace_path, 'attic', 'sources', "{}.{}".format(pn,datetime.now().strftime("%Y%m%d%H%M%S")))
2085 logger.info('Preserving source tree in %s\nIf you no '
2086 'longer need it then please delete it manually.\n'
2087 'It is also possible to reuse it via devtool source tree argument.'
2088 % preservesrc)
2089 bb.utils.mkdirhier(os.path.dirname(preservesrc))
2090 shutil.move(srctreebase, preservesrc)
2091 else:
2092 logger.info('Leaving source tree %s as-is; if you no '
2093 'longer need it then please delete it manually'
2094 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002095 else:
2096 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04002097 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002098
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002099 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002100
2101def reset(args, config, basepath, workspace):
2102 """Entry point for the devtool 'reset' subcommand"""
2103 import bb
Brad Bishop64c979e2019-11-04 13:55:29 -05002104 import shutil
2105
2106 recipes = ""
2107
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002108 if args.recipename:
2109 if args.all:
2110 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
2111 else:
2112 for recipe in args.recipename:
2113 check_workspace_recipe(workspace, recipe, checksrc=False)
2114 elif not args.all:
2115 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
2116 "reset all recipes")
2117 if args.all:
2118 recipes = list(workspace.keys())
2119 else:
2120 recipes = args.recipename
2121
Brad Bishop64c979e2019-11-04 13:55:29 -05002122 _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002123
2124 return 0
2125
2126
2127def _get_layer(layername, d):
2128 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002129 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04002130 layers = {} # {basename: layer_paths}
2131 for p in layerdirs:
2132 bn = os.path.basename(p)
2133 if bn not in layers:
2134 layers[bn] = [p]
2135 else:
2136 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002137 # Provide some shortcuts
2138 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04002139 layername = 'meta'
2140 layer_paths = layers.get(layername, None)
2141 if not layer_paths:
2142 return os.path.abspath(layername)
2143 elif len(layer_paths) == 1:
2144 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002145 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04002146 # multiple layers having the same base name
2147 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
2148 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
2149 return os.path.abspath(layer_paths[0])
2150
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002151
2152def finish(args, config, basepath, workspace):
2153 """Entry point for the devtool 'finish' subcommand"""
2154 import bb
2155 import oe.recipeutils
2156
2157 check_workspace_recipe(workspace, args.recipename)
2158
Brad Bishop316dfdd2018-06-25 12:45:53 -04002159 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
2160
2161 # Grab the equivalent of COREBASE without having to initialise tinfoil
2162 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2163
2164 srctree = workspace[args.recipename]['srctree']
2165 check_git_repo_op(srctree, [corebasedir])
2166 dirty = check_git_repo_dirty(srctree)
2167 if dirty:
2168 if args.force:
2169 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2170 else:
2171 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)
2172
Brad Bishop00e122a2019-10-05 11:10:57 -04002173 no_clean = args.no_clean
Brad Bishop64c979e2019-11-04 13:55:29 -05002174 remove_work=args.remove_work
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002175 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2176 try:
Brad Bishop6dbb3162019-11-25 09:41:34 -05002177 rd = parse_recipe(config, tinfoil, args.recipename, True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002178 if not rd:
2179 return 1
2180
2181 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002182 recipefile = rd.getVar('FILE')
2183 recipedir = os.path.dirname(recipefile)
2184 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002185
2186 if not os.path.isdir(destlayerdir):
2187 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2188
2189 if os.path.abspath(destlayerdir) == config.workspace_path:
2190 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2191
2192 # If it's an upgrade, grab the original path
2193 origpath = None
2194 origfilelist = None
2195 append = workspace[args.recipename]['bbappend']
2196 with open(append, 'r') as f:
2197 for line in f:
2198 if line.startswith('# original_path:'):
2199 origpath = line.split(':')[1].strip()
2200 elif line.startswith('# original_files:'):
2201 origfilelist = line.split(':')[1].split()
2202
Brad Bishop316dfdd2018-06-25 12:45:53 -04002203 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2204
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002205 if origlayerdir == config.workspace_path:
2206 # Recipe file itself is in workspace, update it there first
2207 appendlayerdir = None
2208 origrelpath = None
2209 if origpath:
2210 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2211 if origlayerpath:
2212 origrelpath = os.path.relpath(origpath, origlayerpath)
2213 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2214 if not destpath:
2215 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 -05002216 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2217 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002218 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002219 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)
2220
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002221 elif destlayerdir == origlayerdir:
2222 # Same layer, update the original recipe
2223 appendlayerdir = None
2224 destpath = None
2225 else:
2226 # Create/update a bbappend in the specified layer
2227 appendlayerdir = destlayerdir
2228 destpath = None
2229
Brad Bishop316dfdd2018-06-25 12:45:53 -04002230 # Actually update the recipe / bbappend
2231 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2232 dry_run_output = None
2233 dry_run_outdir = None
2234 if args.dry_run:
2235 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2236 dry_run_outdir = dry_run_output.name
2237 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)
2238 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2239
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002240 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002241 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002242 for fn in origfilelist:
2243 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002244 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2245 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2246 if not args.dry_run:
2247 try:
2248 os.remove(fnp)
2249 except FileNotFoundError:
2250 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002251
2252 if origlayerdir == config.workspace_path and destpath:
2253 # Recipe file itself is in the workspace - need to move it and any
2254 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002255 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002256 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002257 for root, _, files in os.walk(recipedir):
2258 for fn in files:
2259 srcpath = os.path.join(root, fn)
2260 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2261 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002262 destfp = os.path.join(destdir, fn)
2263 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002264
Brad Bishop316dfdd2018-06-25 12:45:53 -04002265 if dry_run_outdir:
2266 import difflib
2267 comparelist = []
2268 for root, _, files in os.walk(dry_run_outdir):
2269 for fn in files:
2270 outf = os.path.join(root, fn)
2271 relf = os.path.relpath(outf, dry_run_outdir)
2272 logger.debug('dry-run: output file %s' % relf)
2273 if fn.endswith('.bb'):
2274 if origfilelist and origpath and destpath:
2275 # Need to match this up with the pre-upgrade recipe file
2276 for origf in origfilelist:
2277 if origf.endswith('.bb'):
2278 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2279 outf,
2280 os.path.abspath(os.path.join(destpath, relf))))
2281 break
2282 else:
2283 # Compare to the existing recipe
2284 comparelist.append((recipefile, outf, recipefile))
2285 elif fn.endswith('.bbappend'):
2286 if appendfile:
2287 if os.path.exists(appendfile):
2288 comparelist.append((appendfile, outf, appendfile))
2289 else:
2290 comparelist.append((None, outf, appendfile))
2291 else:
2292 if destpath:
2293 recipedest = destpath
2294 elif appendfile:
2295 recipedest = os.path.dirname(appendfile)
2296 else:
2297 recipedest = os.path.dirname(recipefile)
2298 destfp = os.path.join(recipedest, relf)
2299 if os.path.exists(destfp):
2300 comparelist.append((destfp, outf, destfp))
2301 output = ''
2302 for oldfile, newfile, newfileshow in comparelist:
2303 if oldfile:
2304 with open(oldfile, 'r') as f:
2305 oldlines = f.readlines()
2306 else:
2307 oldfile = '/dev/null'
2308 oldlines = []
2309 with open(newfile, 'r') as f:
2310 newlines = f.readlines()
2311 if not newfileshow:
2312 newfileshow = newfile
2313 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2314 difflines = list(diff)
2315 if difflines:
2316 output += ''.join(difflines)
2317 if output:
2318 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002319 finally:
2320 tinfoil.shutdown()
2321
2322 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002323 if args.dry_run:
2324 logger.info('Resetting recipe (dry-run)')
2325 else:
Brad Bishop64c979e2019-11-04 13:55:29 -05002326 _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002327
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002328 return 0
2329
2330
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002331def get_default_srctree(config, recipename=''):
2332 """Get the default srctree path"""
2333 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2334 if recipename:
2335 return os.path.join(srctreeparent, 'sources', recipename)
2336 else:
2337 return os.path.join(srctreeparent, 'sources')
2338
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002339def register_commands(subparsers, context):
2340 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002341
2342 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002343 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002344 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.',
2345 group='starting', order=100)
2346 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.')
2347 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2348 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 -05002349 group = parser_add.add_mutually_exclusive_group()
2350 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2351 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002352 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 -05002353 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williams169d7bc2024-01-05 11:33:25 -06002354 parser_add.add_argument('--no-pypi', help='Do not inherit pypi class', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002355 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002356 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 -05002357 group = parser_add.add_mutually_exclusive_group()
2358 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2359 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")
2360 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 -05002361 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')
2362 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')
2363 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002364 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2365 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 -06002366 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002367
2368 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002369 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.',
2370 group='starting', order=90)
2371 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2372 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 -05002373 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002374 group = parser_modify.add_mutually_exclusive_group()
2375 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2376 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 -05002377 group = parser_modify.add_mutually_exclusive_group()
2378 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2379 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002380 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 -04002381 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 -05002382 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002383 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002384
2385 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2386 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002387 group='advanced')
2388 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002389 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002390 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 -04002391 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 -05002392 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002393 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002394
2395 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2396 description='Synchronize the previously extracted source tree for an existing recipe',
2397 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2398 group='advanced')
2399 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2400 parser_sync.add_argument('srctree', help='Path to the source tree')
2401 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2402 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002403 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002404
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002405 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2406 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.',
2407 group='working', order=10)
2408 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2409 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2410 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)')
2411 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')
2412 parser_rename.set_defaults(func=rename)
2413
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002414 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002415 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.',
2416 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002417 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2418 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 -05002419 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002420 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2421 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')
2422 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 -04002423 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2424 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2425 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 -05002426 parser_update_recipe.set_defaults(func=update_recipe)
2427
2428 parser_status = subparsers.add_parser('status', help='Show workspace status',
2429 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002430 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002431 parser_status.set_defaults(func=status)
2432
2433 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002434 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 -05002435 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002436 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002437 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2438 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 -05002439 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 -05002440 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002441
2442 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002443 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 -06002444 group='working', order=-100)
2445 parser_finish.add_argument('recipename', help='Recipe to finish')
2446 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.')
2447 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')
2448 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002449 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 -05002450 parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace')
Brad Bishop00e122a2019-10-05 11:10:57 -04002451 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 -04002452 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2453 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2454 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 -06002455 parser_finish.set_defaults(func=finish)