blob: c98bfe8195a426b0d8fd077db24cbd499ac613d2 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Development tool - standard commands plugin
2#
Brad Bishopd7bf8c12018-02-25 22:55:05 -05003# Copyright (C) 2014-2017 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004#
Brad Bishopc342db32019-05-15 21:57:59 -04005# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05006#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007"""Devtool standard plugins"""
8
9import os
10import sys
11import re
12import shutil
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050013import subprocess
Patrick Williamsc124f4f2015-09-15 14:41:29 -050014import tempfile
15import logging
16import argparse
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050017import argparse_oe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050018import scriptutils
19import errno
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050020import glob
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021import filecmp
Patrick Williamsf1e5d692016-03-30 15:21:19 -050022from collections import OrderedDict
Brad Bishop316dfdd2018-06-25 12:45:53 -040023from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, use_external_build, setup_git_repo, recipe_to_append, get_bbclassextend_targets, update_unlockedsigs, check_prerelease_version, check_git_repo_dirty, check_git_repo_op, DevtoolError
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024from devtool import parse_recipe
25
26logger = logging.getLogger('devtool')
27
Brad Bishop316dfdd2018-06-25 12:45:53 -040028override_branch_prefix = 'devtool-override-'
29
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030
31def add(args, config, basepath, workspace):
32 """Entry point for the devtool 'add' subcommand"""
33 import bb
34 import oe.recipeutils
35
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050036 if not args.recipename and not args.srctree and not args.fetch and not args.fetchuri:
37 raise argparse_oe.ArgumentUsageError('At least one of recipename, srctree, fetchuri or -f/--fetch must be specified', 'add')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050039 # These are positional arguments, but because we're nice, allow
40 # specifying e.g. source tree without name, or fetch URI without name or
41 # source tree (if we can detect that that is what the user meant)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060042 if scriptutils.is_src_url(args.recipename):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050043 if not args.fetchuri:
44 if args.fetch:
45 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
46 args.fetchuri = args.recipename
47 args.recipename = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -060048 elif scriptutils.is_src_url(args.srctree):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050049 if not args.fetchuri:
50 if args.fetch:
51 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
52 args.fetchuri = args.srctree
53 args.srctree = ''
54 elif args.recipename and not args.srctree:
55 if os.sep in args.recipename:
56 args.srctree = args.recipename
57 args.recipename = None
58 elif os.path.isdir(args.recipename):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080059 logger.warning('Ambiguous argument "%s" - assuming you mean it to be the recipe name' % args.recipename)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060060
Brad Bishopd7bf8c12018-02-25 22:55:05 -050061 if not args.fetchuri:
62 if args.srcrev:
63 raise DevtoolError('The -S/--srcrev option is only valid when fetching from an SCM repository')
64 if args.srcbranch:
65 raise DevtoolError('The -B/--srcbranch option is only valid when fetching from an SCM repository')
66
Patrick Williamsc0f7c042017-02-23 20:41:17 -060067 if args.srctree and os.path.isfile(args.srctree):
68 args.fetchuri = 'file://' + os.path.abspath(args.srctree)
69 args.srctree = ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -050070
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050071 if args.fetch:
72 if args.fetchuri:
73 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
74 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080075 logger.warning('-f/--fetch option is deprecated - you can now simply specify the URL to fetch as a positional argument instead')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050076 args.fetchuri = args.fetch
Patrick Williamsf1e5d692016-03-30 15:21:19 -050077
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050078 if args.recipename:
79 if args.recipename in workspace:
80 raise DevtoolError("recipe %s is already in your workspace" %
81 args.recipename)
82 reason = oe.recipeutils.validate_pn(args.recipename)
83 if reason:
84 raise DevtoolError(reason)
85
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050086 if args.srctree:
87 srctree = os.path.abspath(args.srctree)
88 srctreeparent = None
89 tmpsrcdir = None
90 else:
91 srctree = None
92 srctreeparent = get_default_srctree(config)
93 bb.utils.mkdirhier(srctreeparent)
94 tmpsrcdir = tempfile.mkdtemp(prefix='devtoolsrc', dir=srctreeparent)
95
96 if srctree and os.path.exists(srctree):
97 if args.fetchuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098 if not os.path.isdir(srctree):
99 raise DevtoolError("Cannot fetch into source tree path %s as "
100 "it exists and is not a directory" %
101 srctree)
102 elif os.listdir(srctree):
103 raise DevtoolError("Cannot fetch into source tree path %s as "
104 "it already exists and is non-empty" %
105 srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500106 elif not args.fetchuri:
107 if args.srctree:
108 raise DevtoolError("Specified source tree %s could not be found" %
109 args.srctree)
110 elif srctree:
111 raise DevtoolError("No source tree exists at default path %s - "
112 "either create and populate this directory, "
113 "or specify a path to a source tree, or a "
114 "URI to fetch source from" % srctree)
115 else:
116 raise DevtoolError("You must either specify a source tree "
117 "or a URI to fetch source from")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500118
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119 if args.version:
120 if '_' in args.version or ' ' in args.version:
121 raise DevtoolError('Invalid version string "%s"' % args.version)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500122
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500123 if args.color == 'auto' and sys.stdout.isatty():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500124 color = 'always'
125 else:
126 color = args.color
127 extracmdopts = ''
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500128 if args.fetchuri:
129 source = args.fetchuri
130 if srctree:
131 extracmdopts += ' -x %s' % srctree
132 else:
133 extracmdopts += ' -x %s' % tmpsrcdir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500134 else:
135 source = srctree
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500136 if args.recipename:
137 extracmdopts += ' -N %s' % args.recipename
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500138 if args.version:
139 extracmdopts += ' -V %s' % args.version
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500140 if args.binary:
141 extracmdopts += ' -b'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500142 if args.also_native:
143 extracmdopts += ' --also-native'
144 if args.src_subdir:
145 extracmdopts += ' --src-subdir "%s"' % args.src_subdir
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600146 if args.autorev:
147 extracmdopts += ' -a'
Andrew Geissler82c905d2020-04-13 13:39:40 -0500148 if args.npm_dev:
149 extracmdopts += ' --npm-dev'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500150 if args.mirrors:
151 extracmdopts += ' --mirrors'
152 if args.srcrev:
153 extracmdopts += ' --srcrev %s' % args.srcrev
154 if args.srcbranch:
155 extracmdopts += ' --srcbranch %s' % args.srcbranch
156 if args.provides:
157 extracmdopts += ' --provides %s' % args.provides
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500158
159 tempdir = tempfile.mkdtemp(prefix='devtool')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500161 try:
162 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create --devtool -o %s \'%s\' %s' % (color, tempdir, source, extracmdopts), watch=True)
163 except bb.process.ExecutionError as e:
164 if e.exitcode == 15:
165 raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line')
166 else:
167 raise DevtoolError('Command \'%s\' failed' % e.command)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500168
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500169 recipes = glob.glob(os.path.join(tempdir, '*.bb'))
170 if recipes:
171 recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0]
172 if recipename in workspace:
173 raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename)
174 recipedir = os.path.join(config.workspace_path, 'recipes', recipename)
175 bb.utils.mkdirhier(recipedir)
176 recipefile = os.path.join(recipedir, os.path.basename(recipes[0]))
177 appendfile = recipe_to_append(recipefile, config)
178 if os.path.exists(appendfile):
179 # This shouldn't be possible, but just in case
180 raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace')
181 if os.path.exists(recipefile):
182 raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile)
183 if tmpsrcdir:
184 srctree = os.path.join(srctreeparent, recipename)
185 if os.path.exists(tmpsrcdir):
186 if os.path.exists(srctree):
187 if os.path.isdir(srctree):
188 try:
189 os.rmdir(srctree)
190 except OSError as e:
191 if e.errno == errno.ENOTEMPTY:
192 raise DevtoolError('Source tree path %s already exists and is not empty' % srctree)
193 else:
194 raise
195 else:
196 raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree)
197 logger.info('Using default source tree path %s' % srctree)
198 shutil.move(tmpsrcdir, srctree)
199 else:
200 raise DevtoolError('Couldn\'t find source tree created by recipetool')
201 bb.utils.mkdirhier(recipedir)
202 shutil.move(recipes[0], recipefile)
203 # Move any additional files created by recipetool
204 for fn in os.listdir(tempdir):
205 shutil.move(os.path.join(tempdir, fn), recipedir)
206 else:
207 raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout))
208 attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile))
209 if os.path.exists(attic_recipe):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800210 logger.warning('A modified recipe from a previous invocation exists in %s - you may wish to move this over the top of the new recipe if you had changes in it that you want to continue with' % attic_recipe)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500211 finally:
212 if tmpsrcdir and os.path.exists(tmpsrcdir):
213 shutil.rmtree(tmpsrcdir)
214 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500216 for fn in os.listdir(recipedir):
217 _add_md5(config, recipename, os.path.join(recipedir, fn))
218
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500219 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600220 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500221 try:
222 rd = tinfoil.parse_recipe_file(recipefile, False)
223 except Exception as e:
224 logger.error(str(e))
225 rd = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600226 if not rd:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500227 # Parsing failed. We just created this recipe and we shouldn't
228 # leave it in the workdir or it'll prevent bitbake from starting
229 movefn = '%s.parsefailed' % recipefile
230 logger.error('Parsing newly created recipe failed, moving recipe to %s for reference. If this looks to be caused by the recipe itself, please report this error.' % movefn)
231 shutil.move(recipefile, movefn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600232 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500233
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600234 if args.fetchuri and not args.no_git:
235 setup_git_repo(srctree, args.version, 'devtool', d=tinfoil.config_data)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500236
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600237 initial_rev = None
238 if os.path.exists(os.path.join(srctree, '.git')):
239 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
240 initial_rev = stdout.rstrip()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500241
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600242 if args.src_subdir:
243 srctree = os.path.join(srctree, args.src_subdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500244
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600245 bb.utils.mkdirhier(os.path.dirname(appendfile))
246 with open(appendfile, 'w') as f:
247 f.write('inherit externalsrc\n')
248 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500249
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600250 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
251 if b_is_s:
252 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
253 if initial_rev:
254 f.write('\n# initial_rev: %s\n' % initial_rev)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500255
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600256 if args.binary:
Patrick Williams213cb262021-08-07 19:21:33 -0500257 f.write('do_install:append() {\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600258 f.write(' rm -rf ${D}/.git\n')
259 f.write(' rm -f ${D}/singletask.lock\n')
260 f.write('}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500261
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600262 if bb.data.inherits_class('npm', rd):
Patrick Williams213cb262021-08-07 19:21:33 -0500263 f.write('python do_configure:append() {\n')
Andrew Geissler82c905d2020-04-13 13:39:40 -0500264 f.write(' pkgdir = d.getVar("NPM_PACKAGE")\n')
265 f.write(' lockfile = os.path.join(pkgdir, "singletask.lock")\n')
266 f.write(' bb.utils.remove(lockfile)\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600267 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500268
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500269 # Check if the new layer provides recipes whose priorities have been
270 # overriden by PREFERRED_PROVIDER.
271 recipe_name = rd.getVar('PN')
272 provides = rd.getVar('PROVIDES')
273 # Search every item defined in PROVIDES
274 for recipe_provided in provides.split():
275 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
276 current_pprovider = rd.getVar(preferred_provider)
277 if current_pprovider and current_pprovider != recipe_name:
278 if args.fixed_setup:
279 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
280 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
281 with open(layerconf_file, 'a') as f:
282 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
283 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800284 logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500285 break
286
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600287 _add_md5(config, recipename, appendfile)
288
Brad Bishop316dfdd2018-06-25 12:45:53 -0400289 check_prerelease_version(rd.getVar('PV'), 'devtool add')
290
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
292
293 finally:
294 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500295
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500296 return 0
297
298
299def _check_compatible_recipe(pn, d):
300 """Check if the recipe is supported by devtool"""
301 if pn == 'perf':
302 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600303 "source and thus cannot be supported by this tool",
304 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500305
306 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600307 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500308
309 if bb.data.inherits_class('image', d):
310 raise DevtoolError("The %s recipe is an image, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600311 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500312
313 if bb.data.inherits_class('populate_sdk', d):
314 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600315 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316
317 if bb.data.inherits_class('packagegroup', d):
318 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600319 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500320
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500321 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600322 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323 raise DevtoolError("externalsrc is currently enabled for the %s "
324 "recipe. This prevents the normal do_patch task "
325 "from working. You will need to disable this "
326 "first." % pn)
327
Brad Bishop316dfdd2018-06-25 12:45:53 -0400328def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
329 """Common function for copying a file to the dry run output directory"""
330 relpath = os.path.relpath(dst, base_outdir)
331 if relpath.startswith('..'):
332 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
333 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500334 dst_d = os.path.dirname(dst)
335 if dst_d:
336 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400337 # Don't overwrite existing files, otherwise in the case of an upgrade
338 # the dry-run written out recipe will be overwritten with an unmodified
339 # version
340 if not os.path.exists(dst):
341 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500342
Brad Bishop316dfdd2018-06-25 12:45:53 -0400343def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
344 """Move a file. Creates all the directory components of destination path."""
345 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
346 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
347 if dry_run_outdir:
348 # We want to copy here, not move
349 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
350 else:
351 dst_d = os.path.dirname(dst)
352 if dst_d:
353 bb.utils.mkdirhier(dst_d)
354 shutil.move(src, dst)
355
Andrew Geissler78b72792022-06-14 06:47:25 -0500356def _copy_file(src, dst, dry_run_outdir=None, base_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600357 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400358 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
359 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
360 if dry_run_outdir:
361 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
362 else:
363 dst_d = os.path.dirname(dst)
364 if dst_d:
365 bb.utils.mkdirhier(dst_d)
366 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600367
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500368def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
369 """List contents of a git treeish"""
370 import bb
371 cmd = ['git', 'ls-tree', '-z', treeish]
372 if recursive:
373 cmd.append('-r')
374 out, _ = bb.process.run(cmd, cwd=repodir)
375 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500376 if out:
377 for line in out.split('\0'):
378 if line:
379 split = line.split(None, 4)
380 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500381 return ret
382
383def _git_exclude_path(srctree, path):
384 """Return pathspec (list of paths) that excludes certain path"""
385 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
386 # we don't catch files that are deleted, for example. A more reliable way
387 # to implement this would be to use "negative pathspecs" which were
388 # introduced in Git v1.9.0. Revisit this when/if the required Git version
389 # becomes greater than that.
390 path = os.path.normpath(path)
391 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600392 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500393 if path in git_files:
394 git_files.remove(path)
395 return git_files
396 else:
397 return ['.']
398
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500399def _ls_tree(directory):
400 """Recursive listing of files in a directory"""
401 ret = []
402 for root, dirs, files in os.walk(directory):
403 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
404 fname in files])
405 return ret
406
407
408def extract(args, config, basepath, workspace):
409 """Entry point for the devtool 'extract' subcommand"""
410 import bb
411
Brad Bishop316dfdd2018-06-25 12:45:53 -0400412 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500413 if not tinfoil:
414 # Error already shown
415 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600416 try:
417 rd = parse_recipe(config, tinfoil, args.recipename, True)
418 if not rd:
419 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500420
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600421 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400422 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600423 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500424
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600425 if initial_rev:
426 return 0
427 else:
428 return 1
429 finally:
430 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500431
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500432def sync(args, config, basepath, workspace):
433 """Entry point for the devtool 'sync' subcommand"""
434 import bb
435
Brad Bishop316dfdd2018-06-25 12:45:53 -0400436 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500437 if not tinfoil:
438 # Error already shown
439 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600440 try:
441 rd = parse_recipe(config, tinfoil, args.recipename, True)
442 if not rd:
443 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500444
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600445 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400446 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, True, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600447 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500448
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600449 if initial_rev:
450 return 0
451 else:
452 return 1
453 finally:
454 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500455
Brad Bishop96ff1982019-08-19 13:50:42 -0400456def symlink_oelocal_files_srctree(rd,srctree):
457 import oe.patch
458 if os.path.abspath(rd.getVar('S')) == os.path.abspath(rd.getVar('WORKDIR')):
459 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
460 # (otherwise the recipe won't build as expected)
461 local_files_dir = os.path.join(srctree, 'oe-local-files')
462 addfiles = []
463 for root, _, files in os.walk(local_files_dir):
464 relpth = os.path.relpath(root, local_files_dir)
465 if relpth != '.':
466 bb.utils.mkdirhier(os.path.join(srctree, relpth))
467 for fn in files:
468 if fn == '.gitignore':
469 continue
470 destpth = os.path.join(srctree, relpth, fn)
471 if os.path.exists(destpth):
472 os.unlink(destpth)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600473 if relpth != '.':
474 back_relpth = os.path.relpath(local_files_dir, root)
475 os.symlink('%s/oe-local-files/%s/%s' % (back_relpth, relpth, fn), destpth)
476 else:
477 os.symlink('oe-local-files/%s' % fn, destpth)
Brad Bishop96ff1982019-08-19 13:50:42 -0400478 addfiles.append(os.path.join(relpth, fn))
479 if addfiles:
480 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
Brad Bishop79641f22019-09-10 07:20:22 -0400481 useroptions = []
482 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=rd)
483 bb.process.run('git %s commit -m "Committing local file symlinks\n\n%s"' % (' '.join(useroptions), oe.patch.GitApplyTree.ignore_commit_prefix), cwd=srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400484
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500485
Brad Bishop316dfdd2018-06-25 12:45:53 -0400486def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500487 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500488 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500489 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400490 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500491
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500492 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500493
494 _check_compatible_recipe(pn, d)
495
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500496 if sync:
497 if not os.path.exists(srctree):
498 raise DevtoolError("output path %s does not exist" % srctree)
499 else:
500 if os.path.exists(srctree):
501 if not os.path.isdir(srctree):
502 raise DevtoolError("output path %s exists and is not a directory" %
503 srctree)
504 elif os.listdir(srctree):
505 raise DevtoolError("output path %s already exists and is "
506 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500507
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500508 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
509 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600510 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500511
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500512 if not sync:
513 # Prepare for shutil.move later on
514 bb.utils.mkdirhier(srctree)
515 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500516
Brad Bishop316dfdd2018-06-25 12:45:53 -0400517 extra_overrides = []
518 if not no_overrides:
519 history = d.varhistory.variable('SRC_URI')
520 for event in history:
521 if not 'flag' in event:
Patrick Williams213cb262021-08-07 19:21:33 -0500522 if event['op'].startswith((':append[', ':prepend[')):
Andrew Geissler615f2f12022-07-15 14:00:58 -0500523 override = event['op'].split('[')[1].split(']')[0]
524 if not override.startswith('pn-'):
525 extra_overrides.append(override)
Andrew Geissler99467da2019-02-25 18:54:23 -0600526 # We want to remove duplicate overrides. If a recipe had multiple
527 # SRC_URI_override += values it would cause mulitple instances of
528 # overrides. This doesn't play nicely with things like creating a
529 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
530 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400531 if extra_overrides:
532 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
533
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500534 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500535
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500536 recipefile = d.getVar('FILE')
537 appendfile = recipe_to_append(recipefile, config)
538 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
539
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500540 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
541 # directory so that:
542 # (a) we pick up all files that get unpacked to the WORKDIR, and
543 # (b) we don't disturb the existing build
544 # However, with recipe-specific sysroots the sysroots for the recipe
545 # will be prepared under WORKDIR, and if we used the system temporary
546 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
547 # our attempts to hardlink files into the recipe-specific sysroots
548 # will fail on systems where /tmp is a different filesystem, and it
549 # would have to fall back to copying the files which is a waste of
550 # time. Put the temp directory under the WORKDIR to prevent that from
551 # being a problem.
552 tempbasedir = d.getVar('WORKDIR')
553 bb.utils.mkdirhier(tempbasedir)
554 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500555 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500556 tinfoil.logger.setLevel(logging.WARNING)
557
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500558 # FIXME this results in a cache reload under control of tinfoil, which is fine
559 # except we don't get the knotty progress bar
560
561 if os.path.exists(appendfile):
562 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
563 shutil.copyfile(appendfile, appendbackup)
564 else:
565 appendbackup = None
566 bb.utils.mkdirhier(os.path.dirname(appendfile))
567 logger.debug('writing append file %s' % appendfile)
568 with open(appendfile, 'a') as f:
569 f.write('###--- _extract_source\n')
570 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
571 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
572 if not is_kernel_yocto:
573 f.write('PATCHTOOL = "git"\n')
574 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400575 if extra_overrides:
576 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500577 f.write('inherit devtool-source\n')
578 f.write('###--- _extract_source\n')
579
580 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
581
582 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
583 bb.utils.mkdirhier(sstate_manifests)
584 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
585 with open(preservestampfile, 'w') as f:
586 f.write(d.getVar('STAMP'))
587 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400588 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500589 # We need to generate the kernel config
590 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500592 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500593
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600594 if 'noexec' in (d.getVarFlags(task, False) or []) or 'task' not in (d.getVarFlags(task, False) or []):
595 logger.info('The %s recipe has %s disabled. Running only '
596 'do_configure task dependencies' % (pn, task))
597
598 if 'depends' in d.getVarFlags('do_configure', False):
599 pn = d.getVarFlags('do_configure', False)['depends']
600 pn = pn.replace('${PV}', d.getVar('PV'))
601 pn = pn.replace('${COMPILERDEP}', d.getVar('COMPILERDEP'))
602 task = None
603
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500604 # Run the fetch + unpack tasks
605 res = tinfoil.build_targets(pn,
606 task,
607 handle_events=True)
608 finally:
609 if os.path.exists(preservestampfile):
610 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500611
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500612 if not res:
613 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500614
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600615 if not is_kernel_yocto and ('noexec' in (d.getVarFlags('do_patch', False) or []) or 'task' not in (d.getVarFlags('do_patch', False) or [])):
616 workshareddir = d.getVar('S')
617 if os.path.islink(srctree):
618 os.unlink(srctree)
619
620 os.symlink(workshareddir, srctree)
621
622 # The initial_rev file is created in devtool_post_unpack function that will not be executed if
623 # do_unpack/do_patch tasks are disabled so we have to directly say that source extraction was successful
624 return True, True
625
Brad Bishop316dfdd2018-06-25 12:45:53 -0400626 try:
627 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
628 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500629
Brad Bishop316dfdd2018-06-25 12:45:53 -0400630 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
631 srcsubdir = f.read()
632 except FileNotFoundError as e:
633 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
634 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500635
Brad Bishop96ff1982019-08-19 13:50:42 -0400636 # Check if work-shared is empty, if yes
637 # find source and copy to work-shared
638 if is_kernel_yocto:
639 workshareddir = d.getVar('STAGING_KERNEL_DIR')
640 staging_kerVer = get_staging_kver(workshareddir)
641 kernelVersion = d.getVar('LINUX_VERSION')
642
643 # handle dangling symbolic link in work-shared:
644 if os.path.islink(workshareddir):
645 os.unlink(workshareddir)
646
647 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
648 shutil.rmtree(workshareddir)
649 oe.path.copyhardlinktree(srcsubdir,workshareddir)
650 elif not os.path.exists(workshareddir):
651 oe.path.copyhardlinktree(srcsubdir,workshareddir)
652
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500653 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
654 srctree_localdir = os.path.join(srctree, 'oe-local-files')
655
656 if sync:
657 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
658
659 # Move oe-local-files directory to srctree
660 # As the oe-local-files is not part of the constructed git tree,
661 # remove them directly during the synchrounizating might surprise
662 # the users. Instead, we move it to oe-local-files.bak and remind
663 # user in the log message.
664 if os.path.exists(srctree_localdir + '.bak'):
665 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
666
667 if os.path.exists(srctree_localdir):
668 logger.info('Backing up current local file directory %s' % srctree_localdir)
669 shutil.move(srctree_localdir, srctree_localdir + '.bak')
670
671 if os.path.exists(tempdir_localdir):
672 logger.info('Syncing local source files to srctree...')
673 shutil.copytree(tempdir_localdir, srctree_localdir)
674 else:
675 # Move oe-local-files directory to srctree
676 if os.path.exists(tempdir_localdir):
677 logger.info('Adding local source files to srctree...')
678 shutil.move(tempdir_localdir, srcsubdir)
679
680 shutil.move(srcsubdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400681 symlink_oelocal_files_srctree(d,srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500682
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500683 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500684 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500685 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500686
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500687 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500688 if appendbackup:
689 shutil.copyfile(appendbackup, appendfile)
690 elif os.path.exists(appendfile):
691 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500692 if keep_temp:
693 logger.info('Preserving temporary directory %s' % tempdir)
694 else:
695 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400696 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500697
698def _add_md5(config, recipename, filename):
699 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
700 import bb.utils
701
702 def addfile(fn):
703 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500704 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
705 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
706 f.seek(0, os.SEEK_SET)
707 if not md5_str in f.read():
708 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500709
710 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500711 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500712 for f in files:
713 addfile(os.path.join(root, f))
714 else:
715 addfile(filename)
716
717def _check_preserve(config, recipename):
718 """Check if a file was manually changed and needs to be saved in 'attic'
719 directory"""
720 import bb.utils
721 origfile = os.path.join(config.workspace_path, '.devtool_md5')
722 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500723 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500724 with open(origfile, 'r') as f:
725 with open(newfile, 'w') as tf:
726 for line in f.readlines():
727 splitline = line.rstrip().split('|')
728 if splitline[0] == recipename:
729 removefile = os.path.join(config.workspace_path, splitline[1])
730 try:
731 md5 = bb.utils.md5_file(removefile)
732 except IOError as err:
733 if err.errno == 2:
734 # File no longer exists, skip it
735 continue
736 else:
737 raise
738 if splitline[2] != md5:
739 bb.utils.mkdirhier(preservepath)
740 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800741 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742 shutil.move(removefile, os.path.join(preservepath, preservefile))
743 else:
744 os.remove(removefile)
745 else:
746 tf.write(line)
Andrew Geisslerc926e172021-05-07 16:11:35 -0500747 bb.utils.rename(newfile, origfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500748
Brad Bishop96ff1982019-08-19 13:50:42 -0400749def get_staging_kver(srcdir):
750 # Kernel version from work-shared
751 kerver = []
752 staging_kerVer=""
753 if os.path.exists(srcdir) and os.listdir(srcdir):
754 with open(os.path.join(srcdir,"Makefile")) as f:
755 version = [next(f) for x in range(5)][1:4]
756 for word in version:
757 kerver.append(word.split('= ')[1].split('\n')[0])
758 staging_kerVer = ".".join(kerver)
759 return staging_kerVer
760
761def get_staging_kbranch(srcdir):
762 staging_kbranch = ""
763 if os.path.exists(srcdir) and os.listdir(srcdir):
764 (branch, _) = bb.process.run('git branch | grep \* | cut -d \' \' -f2', cwd=srcdir)
765 staging_kbranch = "".join(branch.split('\n')[0])
766 return staging_kbranch
767
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500768def modify(args, config, basepath, workspace):
769 """Entry point for the devtool 'modify' subcommand"""
770 import bb
771 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400772 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400773 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500774
775 if args.recipename in workspace:
776 raise DevtoolError("recipe %s is already in your workspace" %
777 args.recipename)
778
Brad Bishop316dfdd2018-06-25 12:45:53 -0400779 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600780 try:
781 rd = parse_recipe(config, tinfoil, args.recipename, True)
782 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500783 return 1
784
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500785 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600786 if pn != args.recipename:
787 logger.info('Mapping %s to %s' % (args.recipename, pn))
788 if pn in workspace:
789 raise DevtoolError("recipe %s is already in your workspace" %
790 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500791
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600792 if args.srctree:
793 srctree = os.path.abspath(args.srctree)
794 else:
795 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500796
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600797 if args.no_extract and not os.path.isdir(srctree):
798 raise DevtoolError("--no-extract specified and source path %s does "
799 "not exist or is not a directory" %
800 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600801
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500802 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600803 appendfile = recipe_to_append(recipefile, config, args.wildcard)
804 if os.path.exists(appendfile):
805 raise DevtoolError("Another variant of recipe %s is already in your "
806 "workspace (only one variant of a recipe can "
807 "currently be worked on at once)"
808 % pn)
809
810 _check_compatible_recipe(pn, rd)
811
812 initial_rev = None
813 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400814 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400815
816 if bb.data.inherits_class('kernel-yocto', rd):
817 # Current set kernel version
818 kernelVersion = rd.getVar('LINUX_VERSION')
819 srcdir = rd.getVar('STAGING_KERNEL_DIR')
820 kbranch = rd.getVar('KBRANCH')
821
822 staging_kerVer = get_staging_kver(srcdir)
823 staging_kbranch = get_staging_kbranch(srcdir)
824 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
825 oe.path.copyhardlinktree(srcdir,srctree)
826 workdir = rd.getVar('WORKDIR')
827 srcsubdir = rd.getVar('S')
828 localfilesdir = os.path.join(srctree,'oe-local-files')
829 # Move local source files into separate subdir
830 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
831 local_files = oe.recipeutils.get_recipe_local_files(rd)
832
833 for key in local_files.copy():
834 if key.endswith('scc'):
835 sccfile = open(local_files[key], 'r')
836 for l in sccfile:
837 line = l.split()
838 if line and line[0] in ('kconf', 'patch'):
839 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
840 if not cfg in local_files.values():
841 local_files[line[-1]] = cfg
842 shutil.copy2(cfg, workdir)
843 sccfile.close()
844
845 # Ignore local files with subdir={BP}
846 srcabspath = os.path.abspath(srcsubdir)
847 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))]
848 if local_files:
849 for fname in local_files:
850 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
851 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
852 f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n')
853
854 symlink_oelocal_files_srctree(rd,srctree)
855
856 task = 'do_configure'
857 res = tinfoil.build_targets(pn, task, handle_events=True)
858
859 # Copy .config to workspace
860 kconfpath = rd.getVar('B')
861 logger.info('Copying kernel config to workspace')
862 shutil.copy2(os.path.join(kconfpath, '.config'),srctree)
863
864 # Set this to true, we still need to get initial_rev
865 # by parsing the git repo
866 args.no_extract = True
867
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600868 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400869 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500870 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600871 return 1
872 logger.info('Source tree extracted to %s' % srctree)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600873 if os.path.exists(os.path.join(srctree, '.git')):
874 # Get list of commits since this revision
875 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
876 commits = stdout.split()
877 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600878 else:
879 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600880 # Check if it's a tree previously extracted by us. This is done
881 # by ensuring that devtool-base and args.branch (devtool) exist.
882 # The check_commits logic will cause an exception if either one
883 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600884 try:
885 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600886 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600887 except bb.process.ExecutionError:
888 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400889 if stdout:
890 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600891 for line in stdout.splitlines():
892 if line.startswith('*'):
893 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
894 initial_rev = stdout.rstrip()
895 if not initial_rev:
896 # Otherwise, just grab the head revision
897 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
898 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500899
Brad Bishop316dfdd2018-06-25 12:45:53 -0400900 branch_patches = {}
901 if check_commits:
902 # Check if there are override branches
903 (stdout, _) = bb.process.run('git branch', cwd=srctree)
904 branches = []
905 for line in stdout.rstrip().splitlines():
906 branchname = line[2:].rstrip()
907 if branchname.startswith(override_branch_prefix):
908 branches.append(branchname)
909 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800910 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 -0400911 branches.insert(0, args.branch)
912 seen_patches = []
913 for branch in branches:
914 branch_patches[branch] = []
915 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
916 for line in stdout.splitlines():
917 line = line.strip()
918 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
919 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
920 if not origpatch in seen_patches:
921 seen_patches.append(origpatch)
922 branch_patches[branch].append(origpatch)
923
924 # Need to grab this here in case the source is within a subdirectory
925 srctreebase = srctree
926
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600927 # Check that recipe isn't using a shared workdir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500928 s = os.path.abspath(rd.getVar('S'))
929 workdir = os.path.abspath(rd.getVar('WORKDIR'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600930 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
931 # Handle if S is set to a subdirectory of the source
932 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
933 srctree = os.path.join(srctree, srcsubdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500934
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600935 bb.utils.mkdirhier(os.path.dirname(appendfile))
936 with open(appendfile, 'w') as f:
Patrick Williams213cb262021-08-07 19:21:33 -0500937 f.write('FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600938 # Local files can be modified/tracked in separate subdir under srctree
939 # Mostly useful for packages with S != WORKDIR
Patrick Williams213cb262021-08-07 19:21:33 -0500940 f.write('FILESPATH:prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400941 os.path.join(srctreebase, 'oe-local-files'))
942 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500943
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600944 f.write('\ninherit externalsrc\n')
945 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 -0500946 f.write('EXTERNALSRC:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500947
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600948 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
949 if b_is_s:
Patrick Williams213cb262021-08-07 19:21:33 -0500950 f.write('EXTERNALSRC_BUILD:pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500951
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600952 if bb.data.inherits_class('kernel', rd):
953 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500954 'do_fetch do_unpack do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400955 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500956 f.write('\ndo_configure:append() {\n'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600957 ' cp ${B}/.config ${S}/.config.baseline\n'
958 ' ln -sfT ${B}/.config ${S}/.config.new\n'
959 '}\n')
Patrick Williams213cb262021-08-07 19:21:33 -0500960 f.write('\ndo_kernel_configme:prepend() {\n'
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500961 ' if [ -e ${S}/.config ]; then\n'
962 ' mv ${S}/.config ${S}/.config.old\n'
963 ' fi\n'
964 '}\n')
Brad Bishop96ff1982019-08-19 13:50:42 -0400965 if rd.getVarFlag('do_menuconfig','task'):
Patrick Williams213cb262021-08-07 19:21:33 -0500966 f.write('\ndo_configure:append() {\n'
Andrew Geissler82c905d2020-04-13 13:39:40 -0500967 ' if [ ! ${DEVTOOL_DISABLE_MENUCONFIG} ]; then\n'
968 ' cp ${B}/.config ${S}/.config.baseline\n'
969 ' ln -sfT ${B}/.config ${S}/.config.new\n'
970 ' fi\n'
Brad Bishop96ff1982019-08-19 13:50:42 -0400971 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600972 if initial_rev:
973 f.write('\n# initial_rev: %s\n' % initial_rev)
974 for commit in commits:
975 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400976 if branch_patches:
977 for branch in branch_patches:
978 if branch == args.branch:
979 continue
980 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500981
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500982 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
983
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600984 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500985
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600986 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500987
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600988 finally:
989 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500990
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500991 return 0
992
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500993
994def rename(args, config, basepath, workspace):
995 """Entry point for the devtool 'rename' subcommand"""
996 import bb
997 import oe.recipeutils
998
999 check_workspace_recipe(workspace, args.recipename)
1000
1001 if not (args.newname or args.version):
1002 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
1003
1004 recipefile = workspace[args.recipename]['recipefile']
1005 if not recipefile:
1006 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
1007
1008 if args.newname and args.newname != args.recipename:
1009 reason = oe.recipeutils.validate_pn(args.newname)
1010 if reason:
1011 raise DevtoolError(reason)
1012 newname = args.newname
1013 else:
1014 newname = args.recipename
1015
1016 append = workspace[args.recipename]['bbappend']
1017 appendfn = os.path.splitext(os.path.basename(append))[0]
1018 splitfn = appendfn.split('_')
1019 if len(splitfn) > 1:
1020 origfnver = appendfn.split('_')[1]
1021 else:
1022 origfnver = ''
1023
1024 recipefilemd5 = None
1025 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1026 try:
1027 rd = parse_recipe(config, tinfoil, args.recipename, True)
1028 if not rd:
1029 return 1
1030
1031 bp = rd.getVar('BP')
1032 bpn = rd.getVar('BPN')
1033 if newname != args.recipename:
1034 localdata = rd.createCopy()
1035 localdata.setVar('PN', newname)
1036 newbpn = localdata.getVar('BPN')
1037 else:
1038 newbpn = bpn
1039 s = rd.getVar('S', False)
1040 src_uri = rd.getVar('SRC_URI', False)
1041 pv = rd.getVar('PV')
1042
1043 # Correct variable values that refer to the upstream source - these
1044 # values must stay the same, so if the name/version are changing then
1045 # we need to fix them up
1046 new_s = s
1047 new_src_uri = src_uri
1048 if newbpn != bpn:
1049 # ${PN} here is technically almost always incorrect, but people do use it
1050 new_s = new_s.replace('${BPN}', bpn)
1051 new_s = new_s.replace('${PN}', bpn)
1052 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1053 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1054 new_src_uri = new_src_uri.replace('${PN}', bpn)
1055 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1056 if args.version and origfnver == pv:
1057 new_s = new_s.replace('${PV}', pv)
1058 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1059 new_src_uri = new_src_uri.replace('${PV}', pv)
1060 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1061 patchfields = {}
1062 if new_s != s:
1063 patchfields['S'] = new_s
1064 if new_src_uri != src_uri:
1065 patchfields['SRC_URI'] = new_src_uri
1066 if patchfields:
1067 recipefilemd5 = bb.utils.md5_file(recipefile)
1068 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1069 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1070 finally:
1071 tinfoil.shutdown()
1072
1073 if args.version:
1074 newver = args.version
1075 else:
1076 newver = origfnver
1077
1078 if newver:
1079 newappend = '%s_%s.bbappend' % (newname, newver)
1080 newfile = '%s_%s.bb' % (newname, newver)
1081 else:
1082 newappend = '%s.bbappend' % newname
1083 newfile = '%s.bb' % newname
1084
1085 oldrecipedir = os.path.dirname(recipefile)
1086 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1087 if oldrecipedir != newrecipedir:
1088 bb.utils.mkdirhier(newrecipedir)
1089
1090 newappend = os.path.join(os.path.dirname(append), newappend)
1091 newfile = os.path.join(newrecipedir, newfile)
1092
1093 # Rename bbappend
1094 logger.info('Renaming %s to %s' % (append, newappend))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001095 bb.utils.rename(append, newappend)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001096 # Rename recipe file
1097 logger.info('Renaming %s to %s' % (recipefile, newfile))
Andrew Geisslerc926e172021-05-07 16:11:35 -05001098 bb.utils.rename(recipefile, newfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001099
1100 # Rename source tree if it's the default path
1101 appendmd5 = None
1102 if not args.no_srctree:
1103 srctree = workspace[args.recipename]['srctree']
1104 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1105 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1106 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1107 shutil.move(srctree, newsrctree)
1108 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1109 appendmd5 = bb.utils.md5_file(newappend)
1110 appendlines = []
1111 with open(newappend, 'r') as f:
1112 for line in f:
1113 appendlines.append(line)
1114 with open(newappend, 'w') as f:
1115 for line in appendlines:
1116 if srctree in line:
1117 line = line.replace(srctree, newsrctree)
1118 f.write(line)
1119 newappendmd5 = bb.utils.md5_file(newappend)
1120
1121 bpndir = None
1122 newbpndir = None
1123 if newbpn != bpn:
1124 bpndir = os.path.join(oldrecipedir, bpn)
1125 if os.path.exists(bpndir):
1126 newbpndir = os.path.join(newrecipedir, newbpn)
1127 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1128 shutil.move(bpndir, newbpndir)
1129
1130 bpdir = None
1131 newbpdir = None
1132 if newver != origfnver or newbpn != bpn:
1133 bpdir = os.path.join(oldrecipedir, bp)
1134 if os.path.exists(bpdir):
1135 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1136 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1137 shutil.move(bpdir, newbpdir)
1138
1139 if oldrecipedir != newrecipedir:
1140 # Move any stray files and delete the old recipe directory
1141 for entry in os.listdir(oldrecipedir):
1142 oldpath = os.path.join(oldrecipedir, entry)
1143 newpath = os.path.join(newrecipedir, entry)
1144 logger.info('Renaming %s to %s' % (oldpath, newpath))
1145 shutil.move(oldpath, newpath)
1146 os.rmdir(oldrecipedir)
1147
1148 # Now take care of entries in .devtool_md5
1149 md5entries = []
1150 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1151 for line in f:
1152 md5entries.append(line)
1153
1154 if bpndir and newbpndir:
1155 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1156 else:
1157 relbpndir = None
1158 if bpdir and newbpdir:
1159 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1160 else:
1161 relbpdir = None
1162
1163 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1164 for entry in md5entries:
1165 splitentry = entry.rstrip().split('|')
1166 if len(splitentry) > 2:
1167 if splitentry[0] == args.recipename:
1168 splitentry[0] = newname
1169 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1170 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1171 if appendmd5 and splitentry[2] == appendmd5:
1172 splitentry[2] = newappendmd5
1173 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1174 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1175 if recipefilemd5 and splitentry[2] == recipefilemd5:
1176 splitentry[2] = newrecipefilemd5
1177 elif relbpndir and splitentry[1].startswith(relbpndir):
1178 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1179 elif relbpdir and splitentry[1].startswith(relbpdir):
1180 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1181 entry = '|'.join(splitentry) + '\n'
1182 f.write(entry)
1183 return 0
1184
1185
Brad Bishop316dfdd2018-06-25 12:45:53 -04001186def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001187 """Get initial and update rev of a recipe. These are the start point of the
1188 whole patchset and start point for the patches to be re-generated/updated.
1189 """
1190 import bb
1191
Brad Bishop316dfdd2018-06-25 12:45:53 -04001192 # Get current branch
1193 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1194 cwd=srctree)
1195 branchname = stdout.rstrip()
1196
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001197 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001198 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001199 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001200 with open(recipe_path, 'r') as f:
1201 for line in f:
1202 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001203 if not initial_rev:
1204 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001205 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001206 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001207 elif line.startswith('# patches_%s:' % branchname):
1208 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001209
1210 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001211 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001212 if initial_rev:
1213 # Find first actually changed revision
1214 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1215 initial_rev, cwd=srctree)
1216 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001217 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001218 if newcommits[i] == commits[i]:
1219 update_rev = commits[i]
1220
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001221 try:
1222 stdout, _ = bb.process.run('git cherry devtool-patched',
1223 cwd=srctree)
1224 except bb.process.ExecutionError as err:
1225 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001226
Brad Bishop316dfdd2018-06-25 12:45:53 -04001227 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001228 changed_revs = []
1229 for line in stdout.splitlines():
1230 if line.startswith('+ '):
1231 rev = line.split()[1]
1232 if rev in newcommits:
1233 changed_revs.append(rev)
1234
Brad Bishop316dfdd2018-06-25 12:45:53 -04001235 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001236
1237def _remove_file_entries(srcuri, filelist):
1238 """Remove file:// entries from SRC_URI"""
1239 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001240 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001241 for fname in filelist:
1242 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001243 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001244 if (srcuri[i].startswith('file://') and
1245 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001246 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001247 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001248 srcuri.pop(i)
1249 break
1250 return entries, remaining
1251
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001252def _replace_srcuri_entry(srcuri, filename, newentry):
1253 """Replace entry corresponding to specified file with a new entry"""
1254 basename = os.path.basename(filename)
1255 for i in range(len(srcuri)):
1256 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1257 srcuri.pop(i)
1258 srcuri.insert(i, newentry)
1259 break
1260
Brad Bishop316dfdd2018-06-25 12:45:53 -04001261def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001262 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001263
1264 dry_run_suffix = ' (dry-run)' if dry_run else ''
1265
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001266 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001267 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001268 if not destpath:
1269 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001270 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001271
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001272 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001273 if not no_report_remove:
1274 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1275 if not dry_run:
1276 # FIXME "git rm" here would be nice if the file in question is
1277 # tracked
1278 # FIXME there's a chance that this file is referred to by
1279 # another recipe, in which case deleting wouldn't be the
1280 # right thing to do
1281 os.remove(path)
1282 # Remove directory if empty
1283 try:
1284 os.rmdir(os.path.dirname(path))
1285 except OSError as ose:
1286 if ose.errno != errno.ENOTEMPTY:
1287 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001288
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001289
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001290def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001291 """Export patches from srctree to given location.
1292 Returns three-tuple of dicts:
1293 1. updated - patches that already exist in SRCURI
1294 2. added - new patches that don't exist in SRCURI
1295 3 removed - patches that exist in SRCURI but not in exported patches
1296 In each dict the key is the 'basepath' of the URI and value is the
1297 absolute path to the existing file in recipe space (if any).
1298 """
1299 import oe.recipeutils
1300 from oe.patch import GitApplyTree
1301 updated = OrderedDict()
1302 added = OrderedDict()
1303 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1304
1305 existing_patches = dict((os.path.basename(path), path) for path in
1306 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001307 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001308
1309 # Generate patches from Git, exclude local files directory
1310 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1311 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1312
1313 new_patches = sorted(os.listdir(destdir))
1314 for new_patch in new_patches:
1315 # Strip numbering from patch names. If it's a git sequence named patch,
1316 # the numbers might not match up since we are starting from a different
1317 # revision This does assume that people are using unique shortlog
1318 # values, but they ought to be anyway...
1319 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001320 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001321 for old_patch in existing_patches:
1322 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001323 old_basename_splitext = os.path.splitext(old_basename)
1324 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1325 old_patch_noext = os.path.splitext(old_patch)[0]
1326 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001327 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001328 elif new_basename == old_basename:
1329 match_name = old_patch
1330 break
1331 if match_name:
1332 # Rename patch files
1333 if new_patch != match_name:
Andrew Geisslerc926e172021-05-07 16:11:35 -05001334 bb.utils.rename(os.path.join(destdir, new_patch),
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001335 os.path.join(destdir, match_name))
1336 # Need to pop it off the list now before checking changed_revs
1337 oldpath = existing_patches.pop(old_patch)
1338 if changed_revs is not None:
1339 # Avoid updating patches that have not actually changed
1340 with open(os.path.join(destdir, match_name), 'r') as f:
1341 firstlineitems = f.readline().split()
1342 # Looking for "From <hash>" line
1343 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1344 if not firstlineitems[1] in changed_revs:
1345 continue
1346 # Recompress if necessary
1347 if oldpath.endswith(('.gz', '.Z')):
1348 bb.process.run(['gzip', match_name], cwd=destdir)
1349 if oldpath.endswith('.gz'):
1350 match_name += '.gz'
1351 else:
1352 match_name += '.Z'
1353 elif oldpath.endswith('.bz2'):
1354 bb.process.run(['bzip2', match_name], cwd=destdir)
1355 match_name += '.bz2'
1356 updated[match_name] = oldpath
1357 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001358 added[new_patch] = None
1359 return (updated, added, existing_patches)
1360
1361
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001362def _create_kconfig_diff(srctree, rd, outfile):
1363 """Create a kconfig fragment"""
1364 # Only update config fragment if both config files exist
1365 orig_config = os.path.join(srctree, '.config.baseline')
1366 new_config = os.path.join(srctree, '.config.new')
1367 if os.path.exists(orig_config) and os.path.exists(new_config):
1368 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1369 '--unchanged-line-format=', orig_config, new_config]
1370 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1371 stderr=subprocess.PIPE)
1372 stdout, stderr = pipe.communicate()
1373 if pipe.returncode == 1:
1374 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001375 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001376 fobj.write(stdout)
1377 elif pipe.returncode == 0:
1378 logger.info("Would remove config fragment %s" % outfile)
1379 if os.path.exists(outfile):
1380 # Remove fragment file in case of empty diff
1381 logger.info("Removing config fragment %s" % outfile)
1382 os.unlink(outfile)
1383 else:
1384 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1385 return True
1386 return False
1387
1388
Brad Bishop316dfdd2018-06-25 12:45:53 -04001389def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001390 """Copy local files from srctree to given location.
1391 Returns three-tuple of dicts:
1392 1. updated - files that already exist in SRCURI
1393 2. added - new files files that don't exist in SRCURI
1394 3 removed - files that exist in SRCURI but not in exported files
1395 In each dict the key is the 'basepath' of the URI and value is the
1396 absolute path to the existing file in recipe space (if any).
1397 """
1398 import oe.recipeutils
1399
1400 # Find out local files (SRC_URI files that exist in the "recipe space").
1401 # Local files that reside in srctree are not included in patch generation.
1402 # Instead they are directly copied over the original source files (in
1403 # recipe space).
1404 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1405 new_set = None
1406 updated = OrderedDict()
1407 added = OrderedDict()
1408 removed = OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001409 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001410 git_files = _git_ls_tree(srctree)
1411 if 'oe-local-files' in git_files:
1412 # If tracked by Git, take the files from srctree HEAD. First get
1413 # the tree object of the directory
1414 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1415 tree = git_files['oe-local-files'][2]
1416 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1417 env=dict(os.environ, GIT_WORK_TREE=destdir,
1418 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001419 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001420 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001421 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001422 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001423 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001424 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001425 else:
1426 new_set = []
1427
1428 # Special handling for kernel config
1429 if bb.data.inherits_class('kernel-yocto', rd):
1430 fragment_fn = 'devtool-fragment.cfg'
1431 fragment_path = os.path.join(destdir, fragment_fn)
1432 if _create_kconfig_diff(srctree, rd, fragment_path):
1433 if os.path.exists(fragment_path):
1434 if fragment_fn not in new_set:
1435 new_set.append(fragment_fn)
1436 # Copy fragment to local-files
1437 if os.path.isdir(local_files_dir):
1438 shutil.copy2(fragment_path, local_files_dir)
1439 else:
1440 if fragment_fn in new_set:
1441 new_set.remove(fragment_fn)
1442 # Remove fragment from local-files
1443 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1444 os.unlink(os.path.join(local_files_dir, fragment_fn))
1445
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001446 # Special handling for cml1, ccmake, etc bbclasses that generated
1447 # configuration fragment files that are consumed as source files
1448 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1449 if bb.data.inherits_class(frag_class, rd):
1450 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1451 if os.path.exists(srcpath):
1452 if frag_name not in new_set:
1453 new_set.append(frag_name)
1454 # copy fragment into destdir
1455 shutil.copy2(srcpath, destdir)
1456 # copy fragment into local files if exists
1457 if os.path.isdir(local_files_dir):
1458 shutil.copy2(srcpath, local_files_dir)
1459
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001460 if new_set is not None:
1461 for fname in new_set:
1462 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001463 origpath = existing_files.pop(fname)
1464 workpath = os.path.join(local_files_dir, fname)
1465 if not filecmp.cmp(origpath, workpath):
1466 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001467 elif fname != '.gitignore':
1468 added[fname] = None
1469
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001470 workdir = rd.getVar('WORKDIR')
1471 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001472 if not s.endswith(os.sep):
1473 s += os.sep
1474
1475 if workdir != s:
1476 # Handle files where subdir= was specified
1477 for fname in list(existing_files.keys()):
1478 # FIXME handle both subdir starting with BP and not?
1479 fworkpath = os.path.join(workdir, fname)
1480 if fworkpath.startswith(s):
1481 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1482 if os.path.exists(fpath):
1483 origpath = existing_files.pop(fname)
1484 if not filecmp.cmp(origpath, fpath):
1485 updated[fpath] = origpath
1486
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001487 removed = existing_files
1488 return (updated, added, removed)
1489
1490
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001491def _determine_files_dir(rd):
1492 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001493 recipedir = rd.getVar('FILE_DIRNAME')
1494 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001495 relpth = os.path.relpath(entry, recipedir)
1496 if not os.sep in relpth:
1497 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1498 if os.path.isdir(entry):
1499 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001500 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001501
1502
Brad Bishop316dfdd2018-06-25 12:45:53 -04001503def _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 -05001504 """Implement the 'srcrev' mode of update-recipe"""
1505 import bb
1506 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001507
Brad Bishop316dfdd2018-06-25 12:45:53 -04001508 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1509
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001510 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001511 recipedir = os.path.basename(recipefile)
1512 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001513
1514 # Get HEAD revision
1515 try:
1516 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1517 except bb.process.ExecutionError as err:
1518 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1519 (srctree, err))
1520 srcrev = stdout.strip()
1521 if len(srcrev) != 40:
1522 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1523
1524 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001525 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001526 patchfields = {}
1527 patchfields['SRCREV'] = srcrev
1528 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001529 srcuri = orig_src_uri.split()
1530 tempdir = tempfile.mkdtemp(prefix='devtool')
1531 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001532 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001533 try:
1534 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001535 srctreebase = workspace[recipename]['srctreebase']
1536 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001537 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001538 # Find list of existing patches in recipe file
1539 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001540 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001541 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1542 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001543 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 -05001544
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001545 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001546 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001547 if remove_files:
1548 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1549 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001550
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001551 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001552 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001553 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001554 removevalues = {}
1555 if update_srcuri:
1556 removevalues = {'SRC_URI': removedentries}
1557 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001558 if dry_run_outdir:
1559 logger.info('Creating bbappend (dry-run)')
1560 else:
1561 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1562 rd, appendlayerdir, files, wildcardver=wildcard_version,
1563 extralines=patchfields, removevalues=removevalues,
1564 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001565 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001566 files_dir = _determine_files_dir(rd)
1567 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001568 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001569 if os.path.isabs(basepath):
1570 # Original file (probably with subdir pointing inside source tree)
1571 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001572 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001573 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001574 _move_file(os.path.join(local_files_dir, basepath), path,
1575 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001576 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001577 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001578 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001579 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001580 os.path.join(files_dir, basepath),
1581 dry_run_outdir=dry_run_outdir,
1582 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001583 srcuri.append('file://%s' % basepath)
1584 update_srcuri = True
1585 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001586 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001587 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001588 finally:
1589 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001590 if not 'git://' in orig_src_uri:
1591 logger.info('You will need to update SRC_URI within the recipe to '
1592 'point to a git repository where you have pushed your '
1593 'changes')
1594
Brad Bishop316dfdd2018-06-25 12:45:53 -04001595 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1596 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001597
Brad Bishop316dfdd2018-06-25 12:45:53 -04001598def _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 -05001599 """Implement the 'patch' mode of update-recipe"""
1600 import bb
1601 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001602
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001603 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001604 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001605 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001606 if not os.path.exists(append):
1607 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001608 recipename)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001609 srctreebase = workspace[recipename]['srctreebase']
1610 relpatchdir = os.path.relpath(srctreebase, srctree)
1611 if relpatchdir == '.':
1612 patchdir_params = {}
1613 else:
1614 patchdir_params = {'patchdir': relpatchdir}
1615
1616 def srcuri_entry(fname):
1617 if patchdir_params:
1618 paramstr = ';' + ';'.join('%s=%s' % (k,v) for k,v in patchdir_params.items())
1619 else:
1620 paramstr = ''
1621 return 'file://%s%s' % (basepath, paramstr)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001622
Brad Bishop316dfdd2018-06-25 12:45:53 -04001623 initial_rev, update_rev, changed_revs, filter_patches = _get_patchset_revs(srctree, append, initial_rev, force_patch_refresh)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001624 if not initial_rev:
1625 raise DevtoolError('Unable to find initial revision - please specify '
1626 'it with --initial-rev')
1627
Brad Bishop316dfdd2018-06-25 12:45:53 -04001628 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001629 dl_dir = rd.getVar('DL_DIR')
1630 if not dl_dir.endswith('/'):
1631 dl_dir += '/'
1632
Brad Bishop316dfdd2018-06-25 12:45:53 -04001633 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1634
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001635 tempdir = tempfile.mkdtemp(prefix='devtool')
1636 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001637 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001638 if filter_patches:
1639 upd_f = {}
1640 new_f = {}
1641 del_f = {}
1642 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001643 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001644
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001645 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001646 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001647 # Get all patches from source tree and check if any should be removed
1648 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001649 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1650 all_patches_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001651 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001652 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001653
1654 # Get updated patches from source tree
1655 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001656 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1657 patches_dir, changed_revs)
1658 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1659 if filter_patches:
Brad Bishop00e122a2019-10-05 11:10:57 -04001660 new_p = OrderedDict()
1661 upd_p = OrderedDict((k,v) for k,v in upd_p.items() if k in filter_patches)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001662 remove_files = [f for f in remove_files if f in filter_patches]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001663 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001664 updaterecipe = False
1665 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001666 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001667 if appendlayerdir:
Brad Bishop00e122a2019-10-05 11:10:57 -04001668 files = OrderedDict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001669 key, val in list(upd_f.items()) + list(new_f.items()))
Brad Bishop00e122a2019-10-05 11:10:57 -04001670 files.update(OrderedDict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001671 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001672 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001673 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001674 if remove_files:
1675 removedentries, remaining = _remove_file_entries(
1676 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001677 if removedentries or remaining:
Andrew Geissler615f2f12022-07-15 14:00:58 -05001678 remaining = [srcuri_entry(os.path.basename(item)) for
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001679 item in remaining]
1680 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001681 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001682 rd, appendlayerdir, files,
1683 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001684 removevalues=removevalues,
Andrew Geissler615f2f12022-07-15 14:00:58 -05001685 redirect_output=dry_run_outdir,
1686 params=[patchdir_params] * len(files))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001687 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001688 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001689 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001690 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001691 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001692 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001693 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001694 if os.path.isabs(basepath):
1695 # Original file (probably with subdir pointing inside source tree)
1696 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001697 _copy_file(basepath, path,
1698 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001699 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001700 _move_file(os.path.join(local_files_dir, basepath), path,
1701 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001702 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001703 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001704 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001705 if os.path.dirname(path) + '/' == dl_dir:
1706 # This is a a downloaded patch file - we now need to
1707 # replace the entry in SRC_URI with our local version
1708 logger.info('Replacing remote patch %s with updated local version' % basepath)
1709 path = os.path.join(files_dir, basepath)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001710 _replace_srcuri_entry(srcuri, basepath, srcuri_entry(basepath))
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001711 updaterecipe = True
1712 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001713 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1714 _move_file(patchfn, path,
1715 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001716 updatefiles = True
1717 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001718 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001719 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001720 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001721 os.path.join(files_dir, basepath),
1722 dry_run_outdir=dry_run_outdir,
1723 base_outdir=recipedir)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001724 srcuri.append(srcuri_entry(basepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001725 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001726 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001727 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001728 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001729 os.path.join(files_dir, basepath),
1730 dry_run_outdir=dry_run_outdir,
1731 base_outdir=recipedir)
Andrew Geissler615f2f12022-07-15 14:00:58 -05001732 srcuri.append(srcuri_entry(basepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001733 updaterecipe = True
1734 # Update recipe, if needed
1735 if _remove_file_entries(srcuri, remove_files)[0]:
1736 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001737 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001738 if not dry_run_outdir:
1739 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1740 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1741 {'SRC_URI': ' '.join(srcuri)},
1742 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001743 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001744 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001745 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001746 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001747 finally:
1748 shutil.rmtree(tempdir)
1749
Brad Bishop316dfdd2018-06-25 12:45:53 -04001750 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1751 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001752
1753def _guess_recipe_update_mode(srctree, rdata):
1754 """Guess the recipe update mode to use"""
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001755 src_uri = (rdata.getVar('SRC_URI') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001756 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1757 if not git_uris:
1758 return 'patch'
1759 # Just use the first URI for now
1760 uri = git_uris[0]
1761 # Check remote branch
1762 params = bb.fetch.decodeurl(uri)[5]
1763 upstr_branch = params['branch'] if 'branch' in params else 'master'
1764 # Check if current branch HEAD is found in upstream branch
1765 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1766 head_rev = stdout.rstrip()
1767 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1768 cwd=srctree)
1769 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1770 if 'origin/' + upstr_branch in remote_brs:
1771 return 'srcrev'
1772
1773 return 'patch'
1774
Brad Bishop316dfdd2018-06-25 12:45:53 -04001775def _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 -06001776 srctree = workspace[recipename]['srctree']
1777 if mode == 'auto':
1778 mode = _guess_recipe_update_mode(srctree, rd)
1779
Brad Bishop316dfdd2018-06-25 12:45:53 -04001780 override_branches = []
1781 mainbranch = None
1782 startbranch = None
1783 if not no_overrides:
1784 stdout, _ = bb.process.run('git branch', cwd=srctree)
1785 other_branches = []
1786 for line in stdout.splitlines():
1787 branchname = line[2:]
1788 if line.startswith('* '):
1789 startbranch = branchname
1790 if branchname.startswith(override_branch_prefix):
1791 override_branches.append(branchname)
1792 else:
1793 other_branches.append(branchname)
1794
1795 if override_branches:
1796 logger.debug('_update_recipe: override branches: %s' % override_branches)
1797 logger.debug('_update_recipe: other branches: %s' % other_branches)
1798 if startbranch.startswith(override_branch_prefix):
1799 if len(other_branches) == 1:
1800 mainbranch = other_branches[1]
1801 else:
1802 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1803 else:
1804 mainbranch = startbranch
1805
1806 checkedout = None
1807 anyupdated = False
1808 appendfile = None
1809 allremoved = []
1810 if override_branches:
1811 logger.info('Handling main branch (%s)...' % mainbranch)
1812 if startbranch != mainbranch:
1813 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1814 checkedout = mainbranch
1815 try:
1816 branchlist = [mainbranch] + override_branches
1817 for branch in branchlist:
1818 crd = bb.data.createCopy(rd)
1819 if branch != mainbranch:
1820 logger.info('Handling branch %s...' % branch)
1821 override = branch[len(override_branch_prefix):]
1822 crd.appendVar('OVERRIDES', ':%s' % override)
1823 bb.process.run('git checkout %s' % branch, cwd=srctree)
1824 checkedout = branch
1825
1826 if mode == 'srcrev':
1827 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1828 elif mode == 'patch':
1829 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)
1830 else:
1831 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1832 if updated:
1833 anyupdated = True
1834 if appendf:
1835 appendfile = appendf
1836 allremoved.extend(removed)
1837 finally:
1838 if startbranch and checkedout != startbranch:
1839 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1840
1841 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001842
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001843def update_recipe(args, config, basepath, workspace):
1844 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001845 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001846
1847 if args.append:
1848 if not os.path.exists(args.append):
1849 raise DevtoolError('bbappend destination layer directory "%s" '
1850 'does not exist' % args.append)
1851 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1852 raise DevtoolError('conf/layer.conf not found in bbappend '
1853 'destination layer "%s"' % args.append)
1854
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001855 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001856 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001857
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001858 rd = parse_recipe(config, tinfoil, args.recipename, True)
1859 if not rd:
1860 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001861
Brad Bishop316dfdd2018-06-25 12:45:53 -04001862 dry_run_output = None
1863 dry_run_outdir = None
1864 if args.dry_run:
1865 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1866 dry_run_outdir = dry_run_output.name
1867 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 -05001868
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001869 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001870 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001871 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001872 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 -06001873 finally:
1874 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001875
1876 return 0
1877
1878
1879def status(args, config, basepath, workspace):
1880 """Entry point for the devtool 'status' subcommand"""
1881 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001882 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001883 recipefile = value['recipefile']
1884 if recipefile:
1885 recipestr = ' (%s)' % recipefile
1886 else:
1887 recipestr = ''
1888 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001889 else:
1890 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')
1891 return 0
1892
1893
Brad Bishop64c979e2019-11-04 13:55:29 -05001894def _reset(recipes, no_clean, remove_work, config, basepath, workspace):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001895 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001896 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001897
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001898 def clean_preferred_provider(pn, layerconf_path):
1899 """Remove PREFERRED_PROVIDER from layer.conf'"""
1900 import re
1901 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1902 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1903 pprovider_found = False
1904 with open(layerconf_file, 'r') as f:
1905 lines = f.readlines()
1906 with open(new_layerconf_file, 'a') as nf:
1907 for line in lines:
1908 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1909 if not re.match(pprovider_exp, line):
1910 nf.write(line)
1911 else:
1912 pprovider_found = True
1913 if pprovider_found:
1914 shutil.move(new_layerconf_file, layerconf_file)
1915 else:
1916 os.remove(new_layerconf_file)
1917
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001918 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001919 if len(recipes) == 1:
1920 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1921 else:
1922 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001923 # If the recipe file itself was created in the workspace, and
1924 # it uses BBCLASSEXTEND, then we need to also clean the other
1925 # variants
1926 targets = []
1927 for recipe in recipes:
1928 targets.append(recipe)
1929 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001930 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001931 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001932 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001933 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001934 except bb.process.ExecutionError as e:
1935 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1936 'wish, you may specify -n/--no-clean to '
1937 'skip running this command when resetting' %
1938 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001939
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001940 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001941 _check_preserve(config, pn)
1942
Brad Bishop316dfdd2018-06-25 12:45:53 -04001943 appendfile = workspace[pn]['bbappend']
1944 if os.path.exists(appendfile):
1945 # This shouldn't happen, but is possible if devtool errored out prior to
1946 # writing the md5 file. We need to delete this here or the recipe won't
1947 # actually be reset
1948 os.remove(appendfile)
1949
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001950 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001951 def preservedir(origdir):
1952 if os.path.exists(origdir):
1953 for root, dirs, files in os.walk(origdir):
1954 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001955 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001956 _move_file(os.path.join(origdir, fn),
1957 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001958 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001959 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001960 os.rmdir(origdir)
1961
Brad Bishop316dfdd2018-06-25 12:45:53 -04001962 recipefile = workspace[pn]['recipefile']
1963 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1964 # This should always be true if recipefile is set, but just in case
1965 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001966 # We don't automatically create this dir next to appends, but the user can
1967 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1968
Brad Bishop316dfdd2018-06-25 12:45:53 -04001969 srctreebase = workspace[pn]['srctreebase']
1970 if os.path.isdir(srctreebase):
1971 if os.listdir(srctreebase):
Brad Bishop64c979e2019-11-04 13:55:29 -05001972 if remove_work:
1973 logger.info('-r argument used on %s, removing source tree.'
1974 ' You will lose any unsaved work' %pn)
1975 shutil.rmtree(srctreebase)
1976 else:
1977 # We don't want to risk wiping out any work in progress
1978 logger.info('Leaving source tree %s as-is; if you no '
1979 'longer need it then please delete it manually'
1980 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001981 else:
1982 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04001983 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001984
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001985 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001986
1987def reset(args, config, basepath, workspace):
1988 """Entry point for the devtool 'reset' subcommand"""
1989 import bb
Brad Bishop64c979e2019-11-04 13:55:29 -05001990 import shutil
1991
1992 recipes = ""
1993
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001994 if args.recipename:
1995 if args.all:
1996 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1997 else:
1998 for recipe in args.recipename:
1999 check_workspace_recipe(workspace, recipe, checksrc=False)
2000 elif not args.all:
2001 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
2002 "reset all recipes")
2003 if args.all:
2004 recipes = list(workspace.keys())
2005 else:
2006 recipes = args.recipename
2007
Brad Bishop64c979e2019-11-04 13:55:29 -05002008 _reset(recipes, args.no_clean, args.remove_work, config, basepath, workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002009
2010 return 0
2011
2012
2013def _get_layer(layername, d):
2014 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002015 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04002016 layers = {} # {basename: layer_paths}
2017 for p in layerdirs:
2018 bn = os.path.basename(p)
2019 if bn not in layers:
2020 layers[bn] = [p]
2021 else:
2022 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002023 # Provide some shortcuts
2024 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04002025 layername = 'meta'
2026 layer_paths = layers.get(layername, None)
2027 if not layer_paths:
2028 return os.path.abspath(layername)
2029 elif len(layer_paths) == 1:
2030 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002031 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04002032 # multiple layers having the same base name
2033 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
2034 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
2035 return os.path.abspath(layer_paths[0])
2036
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002037
2038def finish(args, config, basepath, workspace):
2039 """Entry point for the devtool 'finish' subcommand"""
2040 import bb
2041 import oe.recipeutils
2042
2043 check_workspace_recipe(workspace, args.recipename)
2044
Brad Bishop316dfdd2018-06-25 12:45:53 -04002045 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
2046
2047 # Grab the equivalent of COREBASE without having to initialise tinfoil
2048 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2049
2050 srctree = workspace[args.recipename]['srctree']
2051 check_git_repo_op(srctree, [corebasedir])
2052 dirty = check_git_repo_dirty(srctree)
2053 if dirty:
2054 if args.force:
2055 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2056 else:
2057 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)
2058
Brad Bishop00e122a2019-10-05 11:10:57 -04002059 no_clean = args.no_clean
Brad Bishop64c979e2019-11-04 13:55:29 -05002060 remove_work=args.remove_work
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002061 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2062 try:
Brad Bishop6dbb3162019-11-25 09:41:34 -05002063 rd = parse_recipe(config, tinfoil, args.recipename, True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002064 if not rd:
2065 return 1
2066
2067 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002068 recipefile = rd.getVar('FILE')
2069 recipedir = os.path.dirname(recipefile)
2070 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002071
2072 if not os.path.isdir(destlayerdir):
2073 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2074
2075 if os.path.abspath(destlayerdir) == config.workspace_path:
2076 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2077
2078 # If it's an upgrade, grab the original path
2079 origpath = None
2080 origfilelist = None
2081 append = workspace[args.recipename]['bbappend']
2082 with open(append, 'r') as f:
2083 for line in f:
2084 if line.startswith('# original_path:'):
2085 origpath = line.split(':')[1].strip()
2086 elif line.startswith('# original_files:'):
2087 origfilelist = line.split(':')[1].split()
2088
Brad Bishop316dfdd2018-06-25 12:45:53 -04002089 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2090
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002091 if origlayerdir == config.workspace_path:
2092 # Recipe file itself is in workspace, update it there first
2093 appendlayerdir = None
2094 origrelpath = None
2095 if origpath:
2096 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2097 if origlayerpath:
2098 origrelpath = os.path.relpath(origpath, origlayerpath)
2099 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2100 if not destpath:
2101 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 -05002102 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2103 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002104 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002105 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)
2106
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002107 elif destlayerdir == origlayerdir:
2108 # Same layer, update the original recipe
2109 appendlayerdir = None
2110 destpath = None
2111 else:
2112 # Create/update a bbappend in the specified layer
2113 appendlayerdir = destlayerdir
2114 destpath = None
2115
Brad Bishop316dfdd2018-06-25 12:45:53 -04002116 # Actually update the recipe / bbappend
2117 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2118 dry_run_output = None
2119 dry_run_outdir = None
2120 if args.dry_run:
2121 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2122 dry_run_outdir = dry_run_output.name
2123 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)
2124 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2125
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002126 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002127 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002128 for fn in origfilelist:
2129 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002130 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2131 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2132 if not args.dry_run:
2133 try:
2134 os.remove(fnp)
2135 except FileNotFoundError:
2136 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002137
2138 if origlayerdir == config.workspace_path and destpath:
2139 # Recipe file itself is in the workspace - need to move it and any
2140 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002141 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002142 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002143 for root, _, files in os.walk(recipedir):
2144 for fn in files:
2145 srcpath = os.path.join(root, fn)
2146 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2147 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002148 destfp = os.path.join(destdir, fn)
2149 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002150
Brad Bishop316dfdd2018-06-25 12:45:53 -04002151 if dry_run_outdir:
2152 import difflib
2153 comparelist = []
2154 for root, _, files in os.walk(dry_run_outdir):
2155 for fn in files:
2156 outf = os.path.join(root, fn)
2157 relf = os.path.relpath(outf, dry_run_outdir)
2158 logger.debug('dry-run: output file %s' % relf)
2159 if fn.endswith('.bb'):
2160 if origfilelist and origpath and destpath:
2161 # Need to match this up with the pre-upgrade recipe file
2162 for origf in origfilelist:
2163 if origf.endswith('.bb'):
2164 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2165 outf,
2166 os.path.abspath(os.path.join(destpath, relf))))
2167 break
2168 else:
2169 # Compare to the existing recipe
2170 comparelist.append((recipefile, outf, recipefile))
2171 elif fn.endswith('.bbappend'):
2172 if appendfile:
2173 if os.path.exists(appendfile):
2174 comparelist.append((appendfile, outf, appendfile))
2175 else:
2176 comparelist.append((None, outf, appendfile))
2177 else:
2178 if destpath:
2179 recipedest = destpath
2180 elif appendfile:
2181 recipedest = os.path.dirname(appendfile)
2182 else:
2183 recipedest = os.path.dirname(recipefile)
2184 destfp = os.path.join(recipedest, relf)
2185 if os.path.exists(destfp):
2186 comparelist.append((destfp, outf, destfp))
2187 output = ''
2188 for oldfile, newfile, newfileshow in comparelist:
2189 if oldfile:
2190 with open(oldfile, 'r') as f:
2191 oldlines = f.readlines()
2192 else:
2193 oldfile = '/dev/null'
2194 oldlines = []
2195 with open(newfile, 'r') as f:
2196 newlines = f.readlines()
2197 if not newfileshow:
2198 newfileshow = newfile
2199 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2200 difflines = list(diff)
2201 if difflines:
2202 output += ''.join(difflines)
2203 if output:
2204 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002205 finally:
2206 tinfoil.shutdown()
2207
2208 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002209 if args.dry_run:
2210 logger.info('Resetting recipe (dry-run)')
2211 else:
Brad Bishop64c979e2019-11-04 13:55:29 -05002212 _reset([args.recipename], no_clean=no_clean, remove_work=remove_work, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002213
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002214 return 0
2215
2216
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002217def get_default_srctree(config, recipename=''):
2218 """Get the default srctree path"""
2219 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2220 if recipename:
2221 return os.path.join(srctreeparent, 'sources', recipename)
2222 else:
2223 return os.path.join(srctreeparent, 'sources')
2224
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002225def register_commands(subparsers, context):
2226 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002227
2228 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002229 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002230 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.',
2231 group='starting', order=100)
2232 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.')
2233 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2234 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 -05002235 group = parser_add.add_mutually_exclusive_group()
2236 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2237 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002238 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 -05002239 parser_add.add_argument('--npm-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002240 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002241 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 -05002242 group = parser_add.add_mutually_exclusive_group()
2243 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2244 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")
2245 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 -05002246 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')
2247 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')
2248 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002249 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2250 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 -06002251 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002252
2253 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002254 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.',
2255 group='starting', order=90)
2256 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2257 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 -05002258 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002259 group = parser_modify.add_mutually_exclusive_group()
2260 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2261 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 -05002262 group = parser_modify.add_mutually_exclusive_group()
2263 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2264 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002265 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 -04002266 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 -05002267 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002268 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002269
2270 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2271 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002272 group='advanced')
2273 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002274 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002275 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 -04002276 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 -05002277 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002278 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002279
2280 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2281 description='Synchronize the previously extracted source tree for an existing recipe',
2282 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2283 group='advanced')
2284 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2285 parser_sync.add_argument('srctree', help='Path to the source tree')
2286 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2287 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002288 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002289
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002290 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2291 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.',
2292 group='working', order=10)
2293 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2294 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2295 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)')
2296 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')
2297 parser_rename.set_defaults(func=rename)
2298
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002299 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002300 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.',
2301 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002302 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2303 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 -05002304 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002305 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2306 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')
2307 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 -04002308 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2309 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2310 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 -05002311 parser_update_recipe.set_defaults(func=update_recipe)
2312
2313 parser_status = subparsers.add_parser('status', help='Show workspace status',
2314 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002315 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002316 parser_status.set_defaults(func=status)
2317
2318 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002319 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 -05002320 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002321 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002322 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2323 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 -05002324 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 -05002325 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002326
2327 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002328 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 -06002329 group='working', order=-100)
2330 parser_finish.add_argument('recipename', help='Recipe to finish')
2331 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.')
2332 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')
2333 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002334 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 -05002335 parser_finish.add_argument('--remove-work', '-r', action="store_true", help='Clean the sources directory under workspace')
Brad Bishop00e122a2019-10-05 11:10:57 -04002336 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 -04002337 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2338 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2339 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 -06002340 parser_finish.set_defaults(func=finish)