blob: aca74b1fc66118a7b6beaca8ebbd0a6f5812b689 [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'
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500148 if args.fetch_dev:
149 extracmdopts += ' --fetch-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:
257 f.write('do_install_append() {\n')
258 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):
263 f.write('do_install_append() {\n')
264 f.write(' # Remove files added to source dir by devtool/externalsrc\n')
265 f.write(' rm -f ${NPM_INSTALLDIR}/singletask.lock\n')
266 f.write(' rm -rf ${NPM_INSTALLDIR}/.git\n')
267 f.write(' rm -rf ${NPM_INSTALLDIR}/oe-local-files\n')
268 f.write(' for symlink in ${EXTERNALSRC_SYMLINKS} ; do\n')
269 f.write(' rm -f ${NPM_INSTALLDIR}/${symlink%%:*}\n')
270 f.write(' done\n')
271 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500272
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500273 # Check if the new layer provides recipes whose priorities have been
274 # overriden by PREFERRED_PROVIDER.
275 recipe_name = rd.getVar('PN')
276 provides = rd.getVar('PROVIDES')
277 # Search every item defined in PROVIDES
278 for recipe_provided in provides.split():
279 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
280 current_pprovider = rd.getVar(preferred_provider)
281 if current_pprovider and current_pprovider != recipe_name:
282 if args.fixed_setup:
283 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
284 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
285 with open(layerconf_file, 'a') as f:
286 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
287 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800288 logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500289 break
290
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291 _add_md5(config, recipename, appendfile)
292
Brad Bishop316dfdd2018-06-25 12:45:53 -0400293 check_prerelease_version(rd.getVar('PV'), 'devtool add')
294
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600295 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
296
297 finally:
298 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500299
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500300 return 0
301
302
303def _check_compatible_recipe(pn, d):
304 """Check if the recipe is supported by devtool"""
305 if pn == 'perf':
306 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600307 "source and thus cannot be supported by this tool",
308 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500309
310 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600311 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500312
313 if bb.data.inherits_class('image', d):
314 raise DevtoolError("The %s recipe is an image, 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('populate_sdk', d):
318 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600319 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500320
321 if bb.data.inherits_class('packagegroup', d):
322 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600323 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500324
325 if bb.data.inherits_class('meta', d):
326 raise DevtoolError("The %s recipe is a meta-recipe, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600327 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500328
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500329 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600330 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500331 raise DevtoolError("externalsrc is currently enabled for the %s "
332 "recipe. This prevents the normal do_patch task "
333 "from working. You will need to disable this "
334 "first." % pn)
335
Brad Bishop316dfdd2018-06-25 12:45:53 -0400336def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
337 """Common function for copying a file to the dry run output directory"""
338 relpath = os.path.relpath(dst, base_outdir)
339 if relpath.startswith('..'):
340 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
341 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500342 dst_d = os.path.dirname(dst)
343 if dst_d:
344 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400345 # Don't overwrite existing files, otherwise in the case of an upgrade
346 # the dry-run written out recipe will be overwritten with an unmodified
347 # version
348 if not os.path.exists(dst):
349 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500350
Brad Bishop316dfdd2018-06-25 12:45:53 -0400351def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
352 """Move a file. Creates all the directory components of destination path."""
353 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
354 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
355 if dry_run_outdir:
356 # We want to copy here, not move
357 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
358 else:
359 dst_d = os.path.dirname(dst)
360 if dst_d:
361 bb.utils.mkdirhier(dst_d)
362 shutil.move(src, dst)
363
364def _copy_file(src, dst, dry_run_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600365 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400366 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
367 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
368 if dry_run_outdir:
369 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
370 else:
371 dst_d = os.path.dirname(dst)
372 if dst_d:
373 bb.utils.mkdirhier(dst_d)
374 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600375
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500376def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
377 """List contents of a git treeish"""
378 import bb
379 cmd = ['git', 'ls-tree', '-z', treeish]
380 if recursive:
381 cmd.append('-r')
382 out, _ = bb.process.run(cmd, cwd=repodir)
383 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500384 if out:
385 for line in out.split('\0'):
386 if line:
387 split = line.split(None, 4)
388 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500389 return ret
390
391def _git_exclude_path(srctree, path):
392 """Return pathspec (list of paths) that excludes certain path"""
393 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
394 # we don't catch files that are deleted, for example. A more reliable way
395 # to implement this would be to use "negative pathspecs" which were
396 # introduced in Git v1.9.0. Revisit this when/if the required Git version
397 # becomes greater than that.
398 path = os.path.normpath(path)
399 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600400 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500401 if path in git_files:
402 git_files.remove(path)
403 return git_files
404 else:
405 return ['.']
406
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500407def _ls_tree(directory):
408 """Recursive listing of files in a directory"""
409 ret = []
410 for root, dirs, files in os.walk(directory):
411 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
412 fname in files])
413 return ret
414
415
416def extract(args, config, basepath, workspace):
417 """Entry point for the devtool 'extract' subcommand"""
418 import bb
419
Brad Bishop316dfdd2018-06-25 12:45:53 -0400420 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500421 if not tinfoil:
422 # Error already shown
423 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600424 try:
425 rd = parse_recipe(config, tinfoil, args.recipename, True)
426 if not rd:
427 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500428
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600429 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400430 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 -0600431 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500432
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600433 if initial_rev:
434 return 0
435 else:
436 return 1
437 finally:
438 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500439
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500440def sync(args, config, basepath, workspace):
441 """Entry point for the devtool 'sync' subcommand"""
442 import bb
443
Brad Bishop316dfdd2018-06-25 12:45:53 -0400444 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500445 if not tinfoil:
446 # Error already shown
447 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600448 try:
449 rd = parse_recipe(config, tinfoil, args.recipename, True)
450 if not rd:
451 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500452
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600453 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400454 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 -0600455 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500456
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600457 if initial_rev:
458 return 0
459 else:
460 return 1
461 finally:
462 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500463
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500464
Brad Bishop316dfdd2018-06-25 12:45:53 -0400465def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500466 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500467 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500468 import oe.patch
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500469
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500470 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500471
472 _check_compatible_recipe(pn, d)
473
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500474 if sync:
475 if not os.path.exists(srctree):
476 raise DevtoolError("output path %s does not exist" % srctree)
477 else:
478 if os.path.exists(srctree):
479 if not os.path.isdir(srctree):
480 raise DevtoolError("output path %s exists and is not a directory" %
481 srctree)
482 elif os.listdir(srctree):
483 raise DevtoolError("output path %s already exists and is "
484 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500485
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500486 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
487 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600488 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500489
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500490 if not sync:
491 # Prepare for shutil.move later on
492 bb.utils.mkdirhier(srctree)
493 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500494
Brad Bishop316dfdd2018-06-25 12:45:53 -0400495 extra_overrides = []
496 if not no_overrides:
497 history = d.varhistory.variable('SRC_URI')
498 for event in history:
499 if not 'flag' in event:
500 if event['op'].startswith(('_append[', '_prepend[')):
501 extra_overrides.append(event['op'].split('[')[1].split(']')[0])
Andrew Geissler99467da2019-02-25 18:54:23 -0600502 # We want to remove duplicate overrides. If a recipe had multiple
503 # SRC_URI_override += values it would cause mulitple instances of
504 # overrides. This doesn't play nicely with things like creating a
505 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
506 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400507 if extra_overrides:
508 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
509
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500510 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500511
512 appendexisted = False
513 recipefile = d.getVar('FILE')
514 appendfile = recipe_to_append(recipefile, config)
515 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
516
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500517 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
518 # directory so that:
519 # (a) we pick up all files that get unpacked to the WORKDIR, and
520 # (b) we don't disturb the existing build
521 # However, with recipe-specific sysroots the sysroots for the recipe
522 # will be prepared under WORKDIR, and if we used the system temporary
523 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
524 # our attempts to hardlink files into the recipe-specific sysroots
525 # will fail on systems where /tmp is a different filesystem, and it
526 # would have to fall back to copying the files which is a waste of
527 # time. Put the temp directory under the WORKDIR to prevent that from
528 # being a problem.
529 tempbasedir = d.getVar('WORKDIR')
530 bb.utils.mkdirhier(tempbasedir)
531 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500532 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500533 tinfoil.logger.setLevel(logging.WARNING)
534
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500535 # FIXME this results in a cache reload under control of tinfoil, which is fine
536 # except we don't get the knotty progress bar
537
538 if os.path.exists(appendfile):
539 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
540 shutil.copyfile(appendfile, appendbackup)
541 else:
542 appendbackup = None
543 bb.utils.mkdirhier(os.path.dirname(appendfile))
544 logger.debug('writing append file %s' % appendfile)
545 with open(appendfile, 'a') as f:
546 f.write('###--- _extract_source\n')
547 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
548 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
549 if not is_kernel_yocto:
550 f.write('PATCHTOOL = "git"\n')
551 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400552 if extra_overrides:
553 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500554 f.write('inherit devtool-source\n')
555 f.write('###--- _extract_source\n')
556
557 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
558
559 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
560 bb.utils.mkdirhier(sstate_manifests)
561 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
562 with open(preservestampfile, 'w') as f:
563 f.write(d.getVar('STAMP'))
564 try:
565 if bb.data.inherits_class('kernel-yocto', d):
566 # We need to generate the kernel config
567 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500568 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500569 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500570
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500571 # Run the fetch + unpack tasks
572 res = tinfoil.build_targets(pn,
573 task,
574 handle_events=True)
575 finally:
576 if os.path.exists(preservestampfile):
577 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500578
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500579 if not res:
580 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500581
Brad Bishop316dfdd2018-06-25 12:45:53 -0400582 try:
583 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
584 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500585
Brad Bishop316dfdd2018-06-25 12:45:53 -0400586 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
587 srcsubdir = f.read()
588 except FileNotFoundError as e:
589 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
590 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500591
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500592 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
593 srctree_localdir = os.path.join(srctree, 'oe-local-files')
594
595 if sync:
596 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
597
598 # Move oe-local-files directory to srctree
599 # As the oe-local-files is not part of the constructed git tree,
600 # remove them directly during the synchrounizating might surprise
601 # the users. Instead, we move it to oe-local-files.bak and remind
602 # user in the log message.
603 if os.path.exists(srctree_localdir + '.bak'):
604 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
605
606 if os.path.exists(srctree_localdir):
607 logger.info('Backing up current local file directory %s' % srctree_localdir)
608 shutil.move(srctree_localdir, srctree_localdir + '.bak')
609
610 if os.path.exists(tempdir_localdir):
611 logger.info('Syncing local source files to srctree...')
612 shutil.copytree(tempdir_localdir, srctree_localdir)
613 else:
614 # Move oe-local-files directory to srctree
615 if os.path.exists(tempdir_localdir):
616 logger.info('Adding local source files to srctree...')
617 shutil.move(tempdir_localdir, srcsubdir)
618
619 shutil.move(srcsubdir, srctree)
620
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500621 if os.path.abspath(d.getVar('S')) == os.path.abspath(d.getVar('WORKDIR')):
622 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
623 # (otherwise the recipe won't build as expected)
624 local_files_dir = os.path.join(srctree, 'oe-local-files')
625 addfiles = []
626 for root, _, files in os.walk(local_files_dir):
627 relpth = os.path.relpath(root, local_files_dir)
628 if relpth != '.':
629 bb.utils.mkdirhier(os.path.join(srctree, relpth))
630 for fn in files:
631 if fn == '.gitignore':
632 continue
633 destpth = os.path.join(srctree, relpth, fn)
634 if os.path.exists(destpth):
635 os.unlink(destpth)
636 os.symlink('oe-local-files/%s' % fn, destpth)
637 addfiles.append(os.path.join(relpth, fn))
638 if addfiles:
639 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
640 useroptions = []
641 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=d)
642 bb.process.run('git %s commit -a -m "Committing local file symlinks\n\n%s"' % (' '.join(useroptions), oe.patch.GitApplyTree.ignore_commit_prefix), cwd=srctree)
643
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500644 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500645 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500646 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500647
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500648 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500649 if appendbackup:
650 shutil.copyfile(appendbackup, appendfile)
651 elif os.path.exists(appendfile):
652 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500653 if keep_temp:
654 logger.info('Preserving temporary directory %s' % tempdir)
655 else:
656 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400657 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500658
659def _add_md5(config, recipename, filename):
660 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
661 import bb.utils
662
663 def addfile(fn):
664 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500665 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
666 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
667 f.seek(0, os.SEEK_SET)
668 if not md5_str in f.read():
669 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500670
671 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500672 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500673 for f in files:
674 addfile(os.path.join(root, f))
675 else:
676 addfile(filename)
677
678def _check_preserve(config, recipename):
679 """Check if a file was manually changed and needs to be saved in 'attic'
680 directory"""
681 import bb.utils
682 origfile = os.path.join(config.workspace_path, '.devtool_md5')
683 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500684 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500685 with open(origfile, 'r') as f:
686 with open(newfile, 'w') as tf:
687 for line in f.readlines():
688 splitline = line.rstrip().split('|')
689 if splitline[0] == recipename:
690 removefile = os.path.join(config.workspace_path, splitline[1])
691 try:
692 md5 = bb.utils.md5_file(removefile)
693 except IOError as err:
694 if err.errno == 2:
695 # File no longer exists, skip it
696 continue
697 else:
698 raise
699 if splitline[2] != md5:
700 bb.utils.mkdirhier(preservepath)
701 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800702 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500703 shutil.move(removefile, os.path.join(preservepath, preservefile))
704 else:
705 os.remove(removefile)
706 else:
707 tf.write(line)
708 os.rename(newfile, origfile)
709
710def modify(args, config, basepath, workspace):
711 """Entry point for the devtool 'modify' subcommand"""
712 import bb
713 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400714 import oe.patch
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500715
716 if args.recipename in workspace:
717 raise DevtoolError("recipe %s is already in your workspace" %
718 args.recipename)
719
Brad Bishop316dfdd2018-06-25 12:45:53 -0400720 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600721 try:
722 rd = parse_recipe(config, tinfoil, args.recipename, True)
723 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500724 return 1
725
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500726 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600727 if pn != args.recipename:
728 logger.info('Mapping %s to %s' % (args.recipename, pn))
729 if pn in workspace:
730 raise DevtoolError("recipe %s is already in your workspace" %
731 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500732
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600733 if args.srctree:
734 srctree = os.path.abspath(args.srctree)
735 else:
736 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500737
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600738 if args.no_extract and not os.path.isdir(srctree):
739 raise DevtoolError("--no-extract specified and source path %s does "
740 "not exist or is not a directory" %
741 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600742
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500743 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600744 appendfile = recipe_to_append(recipefile, config, args.wildcard)
745 if os.path.exists(appendfile):
746 raise DevtoolError("Another variant of recipe %s is already in your "
747 "workspace (only one variant of a recipe can "
748 "currently be worked on at once)"
749 % pn)
750
751 _check_compatible_recipe(pn, rd)
752
753 initial_rev = None
754 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400755 check_commits = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600756 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400757 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 -0500758 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600759 return 1
760 logger.info('Source tree extracted to %s' % srctree)
761 # Get list of commits since this revision
762 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
763 commits = stdout.split()
Brad Bishop316dfdd2018-06-25 12:45:53 -0400764 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600765 else:
766 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600767 # Check if it's a tree previously extracted by us. This is done
768 # by ensuring that devtool-base and args.branch (devtool) exist.
769 # The check_commits logic will cause an exception if either one
770 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600771 try:
772 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600773 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600774 except bb.process.ExecutionError:
775 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400776 if stdout:
777 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600778 for line in stdout.splitlines():
779 if line.startswith('*'):
780 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
781 initial_rev = stdout.rstrip()
782 if not initial_rev:
783 # Otherwise, just grab the head revision
784 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
785 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500786
Brad Bishop316dfdd2018-06-25 12:45:53 -0400787 branch_patches = {}
788 if check_commits:
789 # Check if there are override branches
790 (stdout, _) = bb.process.run('git branch', cwd=srctree)
791 branches = []
792 for line in stdout.rstrip().splitlines():
793 branchname = line[2:].rstrip()
794 if branchname.startswith(override_branch_prefix):
795 branches.append(branchname)
796 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800797 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 -0400798 branches.insert(0, args.branch)
799 seen_patches = []
800 for branch in branches:
801 branch_patches[branch] = []
802 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
803 for line in stdout.splitlines():
804 line = line.strip()
805 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
806 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
807 if not origpatch in seen_patches:
808 seen_patches.append(origpatch)
809 branch_patches[branch].append(origpatch)
810
811 # Need to grab this here in case the source is within a subdirectory
812 srctreebase = srctree
813
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600814 # Check that recipe isn't using a shared workdir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500815 s = os.path.abspath(rd.getVar('S'))
816 workdir = os.path.abspath(rd.getVar('WORKDIR'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600817 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
818 # Handle if S is set to a subdirectory of the source
819 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
820 srctree = os.path.join(srctree, srcsubdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500821
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600822 bb.utils.mkdirhier(os.path.dirname(appendfile))
823 with open(appendfile, 'w') as f:
824 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
825 # Local files can be modified/tracked in separate subdir under srctree
826 # Mostly useful for packages with S != WORKDIR
827 f.write('FILESPATH_prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400828 os.path.join(srctreebase, 'oe-local-files'))
829 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500830
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600831 f.write('\ninherit externalsrc\n')
832 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
833 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500834
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600835 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
836 if b_is_s:
837 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500838
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600839 if bb.data.inherits_class('kernel', rd):
840 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500841 'do_fetch do_unpack do_kernel_configme do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400842 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600843 f.write('\ndo_configure_append() {\n'
844 ' cp ${B}/.config ${S}/.config.baseline\n'
845 ' ln -sfT ${B}/.config ${S}/.config.new\n'
846 '}\n')
847 if initial_rev:
848 f.write('\n# initial_rev: %s\n' % initial_rev)
849 for commit in commits:
850 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400851 if branch_patches:
852 for branch in branch_patches:
853 if branch == args.branch:
854 continue
855 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500856
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500857 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
858
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600859 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500860
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600861 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500862
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600863 finally:
864 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500865
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500866 return 0
867
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500868
869def rename(args, config, basepath, workspace):
870 """Entry point for the devtool 'rename' subcommand"""
871 import bb
872 import oe.recipeutils
873
874 check_workspace_recipe(workspace, args.recipename)
875
876 if not (args.newname or args.version):
877 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
878
879 recipefile = workspace[args.recipename]['recipefile']
880 if not recipefile:
881 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
882
883 if args.newname and args.newname != args.recipename:
884 reason = oe.recipeutils.validate_pn(args.newname)
885 if reason:
886 raise DevtoolError(reason)
887 newname = args.newname
888 else:
889 newname = args.recipename
890
891 append = workspace[args.recipename]['bbappend']
892 appendfn = os.path.splitext(os.path.basename(append))[0]
893 splitfn = appendfn.split('_')
894 if len(splitfn) > 1:
895 origfnver = appendfn.split('_')[1]
896 else:
897 origfnver = ''
898
899 recipefilemd5 = None
900 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
901 try:
902 rd = parse_recipe(config, tinfoil, args.recipename, True)
903 if not rd:
904 return 1
905
906 bp = rd.getVar('BP')
907 bpn = rd.getVar('BPN')
908 if newname != args.recipename:
909 localdata = rd.createCopy()
910 localdata.setVar('PN', newname)
911 newbpn = localdata.getVar('BPN')
912 else:
913 newbpn = bpn
914 s = rd.getVar('S', False)
915 src_uri = rd.getVar('SRC_URI', False)
916 pv = rd.getVar('PV')
917
918 # Correct variable values that refer to the upstream source - these
919 # values must stay the same, so if the name/version are changing then
920 # we need to fix them up
921 new_s = s
922 new_src_uri = src_uri
923 if newbpn != bpn:
924 # ${PN} here is technically almost always incorrect, but people do use it
925 new_s = new_s.replace('${BPN}', bpn)
926 new_s = new_s.replace('${PN}', bpn)
927 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
928 new_src_uri = new_src_uri.replace('${BPN}', bpn)
929 new_src_uri = new_src_uri.replace('${PN}', bpn)
930 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
931 if args.version and origfnver == pv:
932 new_s = new_s.replace('${PV}', pv)
933 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
934 new_src_uri = new_src_uri.replace('${PV}', pv)
935 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
936 patchfields = {}
937 if new_s != s:
938 patchfields['S'] = new_s
939 if new_src_uri != src_uri:
940 patchfields['SRC_URI'] = new_src_uri
941 if patchfields:
942 recipefilemd5 = bb.utils.md5_file(recipefile)
943 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
944 newrecipefilemd5 = bb.utils.md5_file(recipefile)
945 finally:
946 tinfoil.shutdown()
947
948 if args.version:
949 newver = args.version
950 else:
951 newver = origfnver
952
953 if newver:
954 newappend = '%s_%s.bbappend' % (newname, newver)
955 newfile = '%s_%s.bb' % (newname, newver)
956 else:
957 newappend = '%s.bbappend' % newname
958 newfile = '%s.bb' % newname
959
960 oldrecipedir = os.path.dirname(recipefile)
961 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
962 if oldrecipedir != newrecipedir:
963 bb.utils.mkdirhier(newrecipedir)
964
965 newappend = os.path.join(os.path.dirname(append), newappend)
966 newfile = os.path.join(newrecipedir, newfile)
967
968 # Rename bbappend
969 logger.info('Renaming %s to %s' % (append, newappend))
970 os.rename(append, newappend)
971 # Rename recipe file
972 logger.info('Renaming %s to %s' % (recipefile, newfile))
973 os.rename(recipefile, newfile)
974
975 # Rename source tree if it's the default path
976 appendmd5 = None
977 if not args.no_srctree:
978 srctree = workspace[args.recipename]['srctree']
979 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
980 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
981 logger.info('Renaming %s to %s' % (srctree, newsrctree))
982 shutil.move(srctree, newsrctree)
983 # Correct any references (basically EXTERNALSRC*) in the .bbappend
984 appendmd5 = bb.utils.md5_file(newappend)
985 appendlines = []
986 with open(newappend, 'r') as f:
987 for line in f:
988 appendlines.append(line)
989 with open(newappend, 'w') as f:
990 for line in appendlines:
991 if srctree in line:
992 line = line.replace(srctree, newsrctree)
993 f.write(line)
994 newappendmd5 = bb.utils.md5_file(newappend)
995
996 bpndir = None
997 newbpndir = None
998 if newbpn != bpn:
999 bpndir = os.path.join(oldrecipedir, bpn)
1000 if os.path.exists(bpndir):
1001 newbpndir = os.path.join(newrecipedir, newbpn)
1002 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1003 shutil.move(bpndir, newbpndir)
1004
1005 bpdir = None
1006 newbpdir = None
1007 if newver != origfnver or newbpn != bpn:
1008 bpdir = os.path.join(oldrecipedir, bp)
1009 if os.path.exists(bpdir):
1010 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1011 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1012 shutil.move(bpdir, newbpdir)
1013
1014 if oldrecipedir != newrecipedir:
1015 # Move any stray files and delete the old recipe directory
1016 for entry in os.listdir(oldrecipedir):
1017 oldpath = os.path.join(oldrecipedir, entry)
1018 newpath = os.path.join(newrecipedir, entry)
1019 logger.info('Renaming %s to %s' % (oldpath, newpath))
1020 shutil.move(oldpath, newpath)
1021 os.rmdir(oldrecipedir)
1022
1023 # Now take care of entries in .devtool_md5
1024 md5entries = []
1025 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1026 for line in f:
1027 md5entries.append(line)
1028
1029 if bpndir and newbpndir:
1030 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1031 else:
1032 relbpndir = None
1033 if bpdir and newbpdir:
1034 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1035 else:
1036 relbpdir = None
1037
1038 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1039 for entry in md5entries:
1040 splitentry = entry.rstrip().split('|')
1041 if len(splitentry) > 2:
1042 if splitentry[0] == args.recipename:
1043 splitentry[0] = newname
1044 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1045 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1046 if appendmd5 and splitentry[2] == appendmd5:
1047 splitentry[2] = newappendmd5
1048 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1049 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1050 if recipefilemd5 and splitentry[2] == recipefilemd5:
1051 splitentry[2] = newrecipefilemd5
1052 elif relbpndir and splitentry[1].startswith(relbpndir):
1053 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1054 elif relbpdir and splitentry[1].startswith(relbpdir):
1055 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1056 entry = '|'.join(splitentry) + '\n'
1057 f.write(entry)
1058 return 0
1059
1060
Brad Bishop316dfdd2018-06-25 12:45:53 -04001061def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001062 """Get initial and update rev of a recipe. These are the start point of the
1063 whole patchset and start point for the patches to be re-generated/updated.
1064 """
1065 import bb
1066
Brad Bishop316dfdd2018-06-25 12:45:53 -04001067 # Get current branch
1068 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1069 cwd=srctree)
1070 branchname = stdout.rstrip()
1071
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001072 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001073 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001074 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001075 with open(recipe_path, 'r') as f:
1076 for line in f:
1077 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001078 if not initial_rev:
1079 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001080 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001081 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001082 elif line.startswith('# patches_%s:' % branchname):
1083 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001084
1085 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001086 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001087 if initial_rev:
1088 # Find first actually changed revision
1089 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1090 initial_rev, cwd=srctree)
1091 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001092 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001093 if newcommits[i] == commits[i]:
1094 update_rev = commits[i]
1095
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001096 try:
1097 stdout, _ = bb.process.run('git cherry devtool-patched',
1098 cwd=srctree)
1099 except bb.process.ExecutionError as err:
1100 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001101
Brad Bishop316dfdd2018-06-25 12:45:53 -04001102 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001103 changed_revs = []
1104 for line in stdout.splitlines():
1105 if line.startswith('+ '):
1106 rev = line.split()[1]
1107 if rev in newcommits:
1108 changed_revs.append(rev)
1109
Brad Bishop316dfdd2018-06-25 12:45:53 -04001110 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001111
1112def _remove_file_entries(srcuri, filelist):
1113 """Remove file:// entries from SRC_URI"""
1114 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001115 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001116 for fname in filelist:
1117 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001118 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001119 if (srcuri[i].startswith('file://') and
1120 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001121 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001122 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001123 srcuri.pop(i)
1124 break
1125 return entries, remaining
1126
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001127def _replace_srcuri_entry(srcuri, filename, newentry):
1128 """Replace entry corresponding to specified file with a new entry"""
1129 basename = os.path.basename(filename)
1130 for i in range(len(srcuri)):
1131 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1132 srcuri.pop(i)
1133 srcuri.insert(i, newentry)
1134 break
1135
Brad Bishop316dfdd2018-06-25 12:45:53 -04001136def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001137 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001138
1139 dry_run_suffix = ' (dry-run)' if dry_run else ''
1140
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001141 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001142 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001143 if not destpath:
1144 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001145 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001146
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001147 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001148 if not no_report_remove:
1149 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1150 if not dry_run:
1151 # FIXME "git rm" here would be nice if the file in question is
1152 # tracked
1153 # FIXME there's a chance that this file is referred to by
1154 # another recipe, in which case deleting wouldn't be the
1155 # right thing to do
1156 os.remove(path)
1157 # Remove directory if empty
1158 try:
1159 os.rmdir(os.path.dirname(path))
1160 except OSError as ose:
1161 if ose.errno != errno.ENOTEMPTY:
1162 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001163
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001164
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001165def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001166 """Export patches from srctree to given location.
1167 Returns three-tuple of dicts:
1168 1. updated - patches that already exist in SRCURI
1169 2. added - new patches that don't exist in SRCURI
1170 3 removed - patches that exist in SRCURI but not in exported patches
1171 In each dict the key is the 'basepath' of the URI and value is the
1172 absolute path to the existing file in recipe space (if any).
1173 """
1174 import oe.recipeutils
1175 from oe.patch import GitApplyTree
1176 updated = OrderedDict()
1177 added = OrderedDict()
1178 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1179
1180 existing_patches = dict((os.path.basename(path), path) for path in
1181 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001182 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001183
1184 # Generate patches from Git, exclude local files directory
1185 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1186 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1187
1188 new_patches = sorted(os.listdir(destdir))
1189 for new_patch in new_patches:
1190 # Strip numbering from patch names. If it's a git sequence named patch,
1191 # the numbers might not match up since we are starting from a different
1192 # revision This does assume that people are using unique shortlog
1193 # values, but they ought to be anyway...
1194 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001195 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001196 for old_patch in existing_patches:
1197 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001198 old_basename_splitext = os.path.splitext(old_basename)
1199 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1200 old_patch_noext = os.path.splitext(old_patch)[0]
1201 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001202 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001203 elif new_basename == old_basename:
1204 match_name = old_patch
1205 break
1206 if match_name:
1207 # Rename patch files
1208 if new_patch != match_name:
1209 os.rename(os.path.join(destdir, new_patch),
1210 os.path.join(destdir, match_name))
1211 # Need to pop it off the list now before checking changed_revs
1212 oldpath = existing_patches.pop(old_patch)
1213 if changed_revs is not None:
1214 # Avoid updating patches that have not actually changed
1215 with open(os.path.join(destdir, match_name), 'r') as f:
1216 firstlineitems = f.readline().split()
1217 # Looking for "From <hash>" line
1218 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1219 if not firstlineitems[1] in changed_revs:
1220 continue
1221 # Recompress if necessary
1222 if oldpath.endswith(('.gz', '.Z')):
1223 bb.process.run(['gzip', match_name], cwd=destdir)
1224 if oldpath.endswith('.gz'):
1225 match_name += '.gz'
1226 else:
1227 match_name += '.Z'
1228 elif oldpath.endswith('.bz2'):
1229 bb.process.run(['bzip2', match_name], cwd=destdir)
1230 match_name += '.bz2'
1231 updated[match_name] = oldpath
1232 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001233 added[new_patch] = None
1234 return (updated, added, existing_patches)
1235
1236
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001237def _create_kconfig_diff(srctree, rd, outfile):
1238 """Create a kconfig fragment"""
1239 # Only update config fragment if both config files exist
1240 orig_config = os.path.join(srctree, '.config.baseline')
1241 new_config = os.path.join(srctree, '.config.new')
1242 if os.path.exists(orig_config) and os.path.exists(new_config):
1243 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1244 '--unchanged-line-format=', orig_config, new_config]
1245 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1246 stderr=subprocess.PIPE)
1247 stdout, stderr = pipe.communicate()
1248 if pipe.returncode == 1:
1249 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001250 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001251 fobj.write(stdout)
1252 elif pipe.returncode == 0:
1253 logger.info("Would remove config fragment %s" % outfile)
1254 if os.path.exists(outfile):
1255 # Remove fragment file in case of empty diff
1256 logger.info("Removing config fragment %s" % outfile)
1257 os.unlink(outfile)
1258 else:
1259 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1260 return True
1261 return False
1262
1263
Brad Bishop316dfdd2018-06-25 12:45:53 -04001264def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001265 """Copy local files from srctree to given location.
1266 Returns three-tuple of dicts:
1267 1. updated - files that already exist in SRCURI
1268 2. added - new files files that don't exist in SRCURI
1269 3 removed - files that exist in SRCURI but not in exported files
1270 In each dict the key is the 'basepath' of the URI and value is the
1271 absolute path to the existing file in recipe space (if any).
1272 """
1273 import oe.recipeutils
1274
1275 # Find out local files (SRC_URI files that exist in the "recipe space").
1276 # Local files that reside in srctree are not included in patch generation.
1277 # Instead they are directly copied over the original source files (in
1278 # recipe space).
1279 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1280 new_set = None
1281 updated = OrderedDict()
1282 added = OrderedDict()
1283 removed = OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001284 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001285 git_files = _git_ls_tree(srctree)
1286 if 'oe-local-files' in git_files:
1287 # If tracked by Git, take the files from srctree HEAD. First get
1288 # the tree object of the directory
1289 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1290 tree = git_files['oe-local-files'][2]
1291 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1292 env=dict(os.environ, GIT_WORK_TREE=destdir,
1293 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001294 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001295 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001296 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001297 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001298 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001299 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001300 else:
1301 new_set = []
1302
1303 # Special handling for kernel config
1304 if bb.data.inherits_class('kernel-yocto', rd):
1305 fragment_fn = 'devtool-fragment.cfg'
1306 fragment_path = os.path.join(destdir, fragment_fn)
1307 if _create_kconfig_diff(srctree, rd, fragment_path):
1308 if os.path.exists(fragment_path):
1309 if fragment_fn not in new_set:
1310 new_set.append(fragment_fn)
1311 # Copy fragment to local-files
1312 if os.path.isdir(local_files_dir):
1313 shutil.copy2(fragment_path, local_files_dir)
1314 else:
1315 if fragment_fn in new_set:
1316 new_set.remove(fragment_fn)
1317 # Remove fragment from local-files
1318 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1319 os.unlink(os.path.join(local_files_dir, fragment_fn))
1320
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001321 # Special handling for cml1, ccmake, etc bbclasses that generated
1322 # configuration fragment files that are consumed as source files
1323 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1324 if bb.data.inherits_class(frag_class, rd):
1325 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1326 if os.path.exists(srcpath):
1327 if frag_name not in new_set:
1328 new_set.append(frag_name)
1329 # copy fragment into destdir
1330 shutil.copy2(srcpath, destdir)
1331 # copy fragment into local files if exists
1332 if os.path.isdir(local_files_dir):
1333 shutil.copy2(srcpath, local_files_dir)
1334
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001335 if new_set is not None:
1336 for fname in new_set:
1337 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001338 origpath = existing_files.pop(fname)
1339 workpath = os.path.join(local_files_dir, fname)
1340 if not filecmp.cmp(origpath, workpath):
1341 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001342 elif fname != '.gitignore':
1343 added[fname] = None
1344
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001345 workdir = rd.getVar('WORKDIR')
1346 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001347 if not s.endswith(os.sep):
1348 s += os.sep
1349
1350 if workdir != s:
1351 # Handle files where subdir= was specified
1352 for fname in list(existing_files.keys()):
1353 # FIXME handle both subdir starting with BP and not?
1354 fworkpath = os.path.join(workdir, fname)
1355 if fworkpath.startswith(s):
1356 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1357 if os.path.exists(fpath):
1358 origpath = existing_files.pop(fname)
1359 if not filecmp.cmp(origpath, fpath):
1360 updated[fpath] = origpath
1361
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001362 removed = existing_files
1363 return (updated, added, removed)
1364
1365
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001366def _determine_files_dir(rd):
1367 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001368 recipedir = rd.getVar('FILE_DIRNAME')
1369 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001370 relpth = os.path.relpath(entry, recipedir)
1371 if not os.sep in relpth:
1372 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1373 if os.path.isdir(entry):
1374 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001375 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001376
1377
Brad Bishop316dfdd2018-06-25 12:45:53 -04001378def _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 -05001379 """Implement the 'srcrev' mode of update-recipe"""
1380 import bb
1381 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001382
Brad Bishop316dfdd2018-06-25 12:45:53 -04001383 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1384
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001385 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001386 recipedir = os.path.basename(recipefile)
1387 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001388
1389 # Get HEAD revision
1390 try:
1391 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1392 except bb.process.ExecutionError as err:
1393 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1394 (srctree, err))
1395 srcrev = stdout.strip()
1396 if len(srcrev) != 40:
1397 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1398
1399 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001400 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001401 patchfields = {}
1402 patchfields['SRCREV'] = srcrev
1403 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001404 srcuri = orig_src_uri.split()
1405 tempdir = tempfile.mkdtemp(prefix='devtool')
1406 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001407 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001408 try:
1409 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001410 srctreebase = workspace[recipename]['srctreebase']
1411 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001412 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001413 # Find list of existing patches in recipe file
1414 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001415 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001416 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1417 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001418 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 -05001419
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001420 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001421 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001422 if remove_files:
1423 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1424 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001425
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001426 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001427 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001428 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001429 removevalues = {}
1430 if update_srcuri:
1431 removevalues = {'SRC_URI': removedentries}
1432 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001433 if dry_run_outdir:
1434 logger.info('Creating bbappend (dry-run)')
1435 else:
1436 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1437 rd, appendlayerdir, files, wildcardver=wildcard_version,
1438 extralines=patchfields, removevalues=removevalues,
1439 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001440 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001441 files_dir = _determine_files_dir(rd)
1442 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001443 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001444 if os.path.isabs(basepath):
1445 # Original file (probably with subdir pointing inside source tree)
1446 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001447 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001448 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001449 _move_file(os.path.join(local_files_dir, basepath), path,
1450 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001451 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001452 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001453 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001454 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001455 os.path.join(files_dir, basepath),
1456 dry_run_outdir=dry_run_outdir,
1457 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001458 srcuri.append('file://%s' % basepath)
1459 update_srcuri = True
1460 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001461 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001462 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001463 finally:
1464 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001465 if not 'git://' in orig_src_uri:
1466 logger.info('You will need to update SRC_URI within the recipe to '
1467 'point to a git repository where you have pushed your '
1468 'changes')
1469
Brad Bishop316dfdd2018-06-25 12:45:53 -04001470 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1471 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001472
Brad Bishop316dfdd2018-06-25 12:45:53 -04001473def _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 -05001474 """Implement the 'patch' mode of update-recipe"""
1475 import bb
1476 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001477
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001478 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001479 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001480 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001481 if not os.path.exists(append):
1482 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001483 recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001484
Brad Bishop316dfdd2018-06-25 12:45:53 -04001485 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 -05001486 if not initial_rev:
1487 raise DevtoolError('Unable to find initial revision - please specify '
1488 'it with --initial-rev')
1489
Brad Bishop316dfdd2018-06-25 12:45:53 -04001490 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001491 dl_dir = rd.getVar('DL_DIR')
1492 if not dl_dir.endswith('/'):
1493 dl_dir += '/'
1494
Brad Bishop316dfdd2018-06-25 12:45:53 -04001495 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1496
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001497 tempdir = tempfile.mkdtemp(prefix='devtool')
1498 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001499 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001500 if filter_patches:
1501 upd_f = {}
1502 new_f = {}
1503 del_f = {}
1504 else:
1505 srctreebase = workspace[recipename]['srctreebase']
1506 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001507
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001508 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001509 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001510 # Get all patches from source tree and check if any should be removed
1511 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001512 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1513 all_patches_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001514 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001515 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001516
1517 # Get updated patches from source tree
1518 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001519 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1520 patches_dir, changed_revs)
1521 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1522 if filter_patches:
1523 new_p = {}
1524 upd_p = {k:v for k,v in upd_p.items() if k in filter_patches}
1525 remove_files = [f for f in remove_files if f in filter_patches]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001526 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001527 updaterecipe = False
1528 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001529 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001530 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001531 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001532 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001533 files.update(dict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001534 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001535 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001536 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001537 if remove_files:
1538 removedentries, remaining = _remove_file_entries(
1539 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001540 if removedentries or remaining:
1541 remaining = ['file://' + os.path.basename(item) for
1542 item in remaining]
1543 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001544 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001545 rd, appendlayerdir, files,
1546 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001547 removevalues=removevalues,
1548 redirect_output=dry_run_outdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001549 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001550 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001551 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001552 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001553 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001554 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001555 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001556 if os.path.isabs(basepath):
1557 # Original file (probably with subdir pointing inside source tree)
1558 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001559 _copy_file(basepath, path,
1560 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001561 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001562 _move_file(os.path.join(local_files_dir, basepath), path,
1563 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001564 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001565 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001566 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001567 if os.path.dirname(path) + '/' == dl_dir:
1568 # This is a a downloaded patch file - we now need to
1569 # replace the entry in SRC_URI with our local version
1570 logger.info('Replacing remote patch %s with updated local version' % basepath)
1571 path = os.path.join(files_dir, basepath)
1572 _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath)
1573 updaterecipe = True
1574 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001575 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1576 _move_file(patchfn, path,
1577 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001578 updatefiles = True
1579 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001580 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001581 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001582 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001583 os.path.join(files_dir, basepath),
1584 dry_run_outdir=dry_run_outdir,
1585 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001586 srcuri.append('file://%s' % basepath)
1587 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001588 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001589 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001590 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001591 os.path.join(files_dir, basepath),
1592 dry_run_outdir=dry_run_outdir,
1593 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001594 srcuri.append('file://%s' % basepath)
1595 updaterecipe = True
1596 # Update recipe, if needed
1597 if _remove_file_entries(srcuri, remove_files)[0]:
1598 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001599 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001600 if not dry_run_outdir:
1601 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1602 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1603 {'SRC_URI': ' '.join(srcuri)},
1604 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001605 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001606 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001607 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001608 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001609 finally:
1610 shutil.rmtree(tempdir)
1611
Brad Bishop316dfdd2018-06-25 12:45:53 -04001612 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1613 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001614
1615def _guess_recipe_update_mode(srctree, rdata):
1616 """Guess the recipe update mode to use"""
1617 src_uri = (rdata.getVar('SRC_URI', False) or '').split()
1618 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1619 if not git_uris:
1620 return 'patch'
1621 # Just use the first URI for now
1622 uri = git_uris[0]
1623 # Check remote branch
1624 params = bb.fetch.decodeurl(uri)[5]
1625 upstr_branch = params['branch'] if 'branch' in params else 'master'
1626 # Check if current branch HEAD is found in upstream branch
1627 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1628 head_rev = stdout.rstrip()
1629 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1630 cwd=srctree)
1631 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1632 if 'origin/' + upstr_branch in remote_brs:
1633 return 'srcrev'
1634
1635 return 'patch'
1636
Brad Bishop316dfdd2018-06-25 12:45:53 -04001637def _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 -06001638 srctree = workspace[recipename]['srctree']
1639 if mode == 'auto':
1640 mode = _guess_recipe_update_mode(srctree, rd)
1641
Brad Bishop316dfdd2018-06-25 12:45:53 -04001642 override_branches = []
1643 mainbranch = None
1644 startbranch = None
1645 if not no_overrides:
1646 stdout, _ = bb.process.run('git branch', cwd=srctree)
1647 other_branches = []
1648 for line in stdout.splitlines():
1649 branchname = line[2:]
1650 if line.startswith('* '):
1651 startbranch = branchname
1652 if branchname.startswith(override_branch_prefix):
1653 override_branches.append(branchname)
1654 else:
1655 other_branches.append(branchname)
1656
1657 if override_branches:
1658 logger.debug('_update_recipe: override branches: %s' % override_branches)
1659 logger.debug('_update_recipe: other branches: %s' % other_branches)
1660 if startbranch.startswith(override_branch_prefix):
1661 if len(other_branches) == 1:
1662 mainbranch = other_branches[1]
1663 else:
1664 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1665 else:
1666 mainbranch = startbranch
1667
1668 checkedout = None
1669 anyupdated = False
1670 appendfile = None
1671 allremoved = []
1672 if override_branches:
1673 logger.info('Handling main branch (%s)...' % mainbranch)
1674 if startbranch != mainbranch:
1675 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1676 checkedout = mainbranch
1677 try:
1678 branchlist = [mainbranch] + override_branches
1679 for branch in branchlist:
1680 crd = bb.data.createCopy(rd)
1681 if branch != mainbranch:
1682 logger.info('Handling branch %s...' % branch)
1683 override = branch[len(override_branch_prefix):]
1684 crd.appendVar('OVERRIDES', ':%s' % override)
1685 bb.process.run('git checkout %s' % branch, cwd=srctree)
1686 checkedout = branch
1687
1688 if mode == 'srcrev':
1689 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1690 elif mode == 'patch':
1691 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)
1692 else:
1693 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1694 if updated:
1695 anyupdated = True
1696 if appendf:
1697 appendfile = appendf
1698 allremoved.extend(removed)
1699 finally:
1700 if startbranch and checkedout != startbranch:
1701 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1702
1703 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001704
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001705def update_recipe(args, config, basepath, workspace):
1706 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001707 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001708
1709 if args.append:
1710 if not os.path.exists(args.append):
1711 raise DevtoolError('bbappend destination layer directory "%s" '
1712 'does not exist' % args.append)
1713 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1714 raise DevtoolError('conf/layer.conf not found in bbappend '
1715 'destination layer "%s"' % args.append)
1716
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001717 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001718 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001719
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001720 rd = parse_recipe(config, tinfoil, args.recipename, True)
1721 if not rd:
1722 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001723
Brad Bishop316dfdd2018-06-25 12:45:53 -04001724 dry_run_output = None
1725 dry_run_outdir = None
1726 if args.dry_run:
1727 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1728 dry_run_outdir = dry_run_output.name
1729 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 -05001730
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001731 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001732 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001733 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001734 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 -06001735 finally:
1736 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001737
1738 return 0
1739
1740
1741def status(args, config, basepath, workspace):
1742 """Entry point for the devtool 'status' subcommand"""
1743 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001744 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001745 recipefile = value['recipefile']
1746 if recipefile:
1747 recipestr = ' (%s)' % recipefile
1748 else:
1749 recipestr = ''
1750 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001751 else:
1752 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')
1753 return 0
1754
1755
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001756def _reset(recipes, no_clean, config, basepath, workspace):
1757 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001758 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001759
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001760 def clean_preferred_provider(pn, layerconf_path):
1761 """Remove PREFERRED_PROVIDER from layer.conf'"""
1762 import re
1763 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1764 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1765 pprovider_found = False
1766 with open(layerconf_file, 'r') as f:
1767 lines = f.readlines()
1768 with open(new_layerconf_file, 'a') as nf:
1769 for line in lines:
1770 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1771 if not re.match(pprovider_exp, line):
1772 nf.write(line)
1773 else:
1774 pprovider_found = True
1775 if pprovider_found:
1776 shutil.move(new_layerconf_file, layerconf_file)
1777 else:
1778 os.remove(new_layerconf_file)
1779
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001780 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001781 if len(recipes) == 1:
1782 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1783 else:
1784 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001785 # If the recipe file itself was created in the workspace, and
1786 # it uses BBCLASSEXTEND, then we need to also clean the other
1787 # variants
1788 targets = []
1789 for recipe in recipes:
1790 targets.append(recipe)
1791 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001792 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001793 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001794 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001795 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001796 except bb.process.ExecutionError as e:
1797 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1798 'wish, you may specify -n/--no-clean to '
1799 'skip running this command when resetting' %
1800 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001801
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001802 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001803 _check_preserve(config, pn)
1804
Brad Bishop316dfdd2018-06-25 12:45:53 -04001805 appendfile = workspace[pn]['bbappend']
1806 if os.path.exists(appendfile):
1807 # This shouldn't happen, but is possible if devtool errored out prior to
1808 # writing the md5 file. We need to delete this here or the recipe won't
1809 # actually be reset
1810 os.remove(appendfile)
1811
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001812 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001813 def preservedir(origdir):
1814 if os.path.exists(origdir):
1815 for root, dirs, files in os.walk(origdir):
1816 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001817 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001818 _move_file(os.path.join(origdir, fn),
1819 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001820 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001821 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001822 os.rmdir(origdir)
1823
Brad Bishop316dfdd2018-06-25 12:45:53 -04001824 recipefile = workspace[pn]['recipefile']
1825 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1826 # This should always be true if recipefile is set, but just in case
1827 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001828 # We don't automatically create this dir next to appends, but the user can
1829 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1830
Brad Bishop316dfdd2018-06-25 12:45:53 -04001831 srctreebase = workspace[pn]['srctreebase']
1832 if os.path.isdir(srctreebase):
1833 if os.listdir(srctreebase):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001834 # We don't want to risk wiping out any work in progress
1835 logger.info('Leaving source tree %s as-is; if you no '
1836 'longer need it then please delete it manually'
Brad Bishop316dfdd2018-06-25 12:45:53 -04001837 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001838 else:
1839 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04001840 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001841
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001842 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001843
1844def reset(args, config, basepath, workspace):
1845 """Entry point for the devtool 'reset' subcommand"""
1846 import bb
1847 if args.recipename:
1848 if args.all:
1849 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1850 else:
1851 for recipe in args.recipename:
1852 check_workspace_recipe(workspace, recipe, checksrc=False)
1853 elif not args.all:
1854 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1855 "reset all recipes")
1856 if args.all:
1857 recipes = list(workspace.keys())
1858 else:
1859 recipes = args.recipename
1860
1861 _reset(recipes, args.no_clean, config, basepath, workspace)
1862
1863 return 0
1864
1865
1866def _get_layer(layername, d):
1867 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001868 layerdirs = d.getVar('BBLAYERS').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001869 layers = {os.path.basename(p): p for p in layerdirs}
1870 # Provide some shortcuts
1871 if layername.lower() in ['oe-core', 'openembedded-core']:
1872 layerdir = layers.get('meta', None)
1873 else:
1874 layerdir = layers.get(layername, None)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001875 return os.path.abspath(layerdir or layername)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001876
1877def finish(args, config, basepath, workspace):
1878 """Entry point for the devtool 'finish' subcommand"""
1879 import bb
1880 import oe.recipeutils
1881
1882 check_workspace_recipe(workspace, args.recipename)
1883
Brad Bishop316dfdd2018-06-25 12:45:53 -04001884 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
1885
1886 # Grab the equivalent of COREBASE without having to initialise tinfoil
1887 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
1888
1889 srctree = workspace[args.recipename]['srctree']
1890 check_git_repo_op(srctree, [corebasedir])
1891 dirty = check_git_repo_dirty(srctree)
1892 if dirty:
1893 if args.force:
1894 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
1895 else:
1896 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)
1897
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001898 no_clean = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001899 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1900 try:
1901 rd = parse_recipe(config, tinfoil, args.recipename, True)
1902 if not rd:
1903 return 1
1904
1905 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001906 recipefile = rd.getVar('FILE')
1907 recipedir = os.path.dirname(recipefile)
1908 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001909
1910 if not os.path.isdir(destlayerdir):
1911 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
1912
1913 if os.path.abspath(destlayerdir) == config.workspace_path:
1914 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
1915
1916 # If it's an upgrade, grab the original path
1917 origpath = None
1918 origfilelist = None
1919 append = workspace[args.recipename]['bbappend']
1920 with open(append, 'r') as f:
1921 for line in f:
1922 if line.startswith('# original_path:'):
1923 origpath = line.split(':')[1].strip()
1924 elif line.startswith('# original_files:'):
1925 origfilelist = line.split(':')[1].split()
1926
Brad Bishop316dfdd2018-06-25 12:45:53 -04001927 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
1928
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001929 if origlayerdir == config.workspace_path:
1930 # Recipe file itself is in workspace, update it there first
1931 appendlayerdir = None
1932 origrelpath = None
1933 if origpath:
1934 origlayerpath = oe.recipeutils.find_layerdir(origpath)
1935 if origlayerpath:
1936 origrelpath = os.path.relpath(origpath, origlayerpath)
1937 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
1938 if not destpath:
1939 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 -05001940 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
1941 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04001942 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001943 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)
1944
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001945 elif destlayerdir == origlayerdir:
1946 # Same layer, update the original recipe
1947 appendlayerdir = None
1948 destpath = None
1949 else:
1950 # Create/update a bbappend in the specified layer
1951 appendlayerdir = destlayerdir
1952 destpath = None
1953
Brad Bishop316dfdd2018-06-25 12:45:53 -04001954 # Actually update the recipe / bbappend
1955 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
1956 dry_run_output = None
1957 dry_run_outdir = None
1958 if args.dry_run:
1959 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1960 dry_run_outdir = dry_run_output.name
1961 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)
1962 removed = [os.path.relpath(pth, recipedir) for pth in removed]
1963
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001964 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04001965 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001966 for fn in origfilelist:
1967 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001968 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
1969 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
1970 if not args.dry_run:
1971 try:
1972 os.remove(fnp)
1973 except FileNotFoundError:
1974 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001975
1976 if origlayerdir == config.workspace_path and destpath:
1977 # Recipe file itself is in the workspace - need to move it and any
1978 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001979 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04001980 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001981 for root, _, files in os.walk(recipedir):
1982 for fn in files:
1983 srcpath = os.path.join(root, fn)
1984 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
1985 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001986 destfp = os.path.join(destdir, fn)
1987 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001988
Brad Bishop316dfdd2018-06-25 12:45:53 -04001989 if dry_run_outdir:
1990 import difflib
1991 comparelist = []
1992 for root, _, files in os.walk(dry_run_outdir):
1993 for fn in files:
1994 outf = os.path.join(root, fn)
1995 relf = os.path.relpath(outf, dry_run_outdir)
1996 logger.debug('dry-run: output file %s' % relf)
1997 if fn.endswith('.bb'):
1998 if origfilelist and origpath and destpath:
1999 # Need to match this up with the pre-upgrade recipe file
2000 for origf in origfilelist:
2001 if origf.endswith('.bb'):
2002 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2003 outf,
2004 os.path.abspath(os.path.join(destpath, relf))))
2005 break
2006 else:
2007 # Compare to the existing recipe
2008 comparelist.append((recipefile, outf, recipefile))
2009 elif fn.endswith('.bbappend'):
2010 if appendfile:
2011 if os.path.exists(appendfile):
2012 comparelist.append((appendfile, outf, appendfile))
2013 else:
2014 comparelist.append((None, outf, appendfile))
2015 else:
2016 if destpath:
2017 recipedest = destpath
2018 elif appendfile:
2019 recipedest = os.path.dirname(appendfile)
2020 else:
2021 recipedest = os.path.dirname(recipefile)
2022 destfp = os.path.join(recipedest, relf)
2023 if os.path.exists(destfp):
2024 comparelist.append((destfp, outf, destfp))
2025 output = ''
2026 for oldfile, newfile, newfileshow in comparelist:
2027 if oldfile:
2028 with open(oldfile, 'r') as f:
2029 oldlines = f.readlines()
2030 else:
2031 oldfile = '/dev/null'
2032 oldlines = []
2033 with open(newfile, 'r') as f:
2034 newlines = f.readlines()
2035 if not newfileshow:
2036 newfileshow = newfile
2037 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2038 difflines = list(diff)
2039 if difflines:
2040 output += ''.join(difflines)
2041 if output:
2042 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002043 finally:
2044 tinfoil.shutdown()
2045
2046 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002047 if args.dry_run:
2048 logger.info('Resetting recipe (dry-run)')
2049 else:
2050 _reset([args.recipename], no_clean=no_clean, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002051
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002052 return 0
2053
2054
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002055def get_default_srctree(config, recipename=''):
2056 """Get the default srctree path"""
2057 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2058 if recipename:
2059 return os.path.join(srctreeparent, 'sources', recipename)
2060 else:
2061 return os.path.join(srctreeparent, 'sources')
2062
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002063def register_commands(subparsers, context):
2064 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002065
2066 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002067 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002068 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.',
2069 group='starting', order=100)
2070 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.')
2071 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2072 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 -05002073 group = parser_add.add_mutually_exclusive_group()
2074 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2075 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002076 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')
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002077 parser_add.add_argument('--fetch-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002078 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002079 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 -05002080 group = parser_add.add_mutually_exclusive_group()
2081 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2082 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")
2083 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 -05002084 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')
2085 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')
2086 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002087 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2088 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 -06002089 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002090
2091 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002092 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.',
2093 group='starting', order=90)
2094 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2095 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 -05002096 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002097 group = parser_modify.add_mutually_exclusive_group()
2098 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2099 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 -05002100 group = parser_modify.add_mutually_exclusive_group()
2101 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2102 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002103 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 -04002104 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 -05002105 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002106 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002107
2108 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2109 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002110 group='advanced')
2111 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002112 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002113 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 -04002114 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 -05002115 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002116 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002117
2118 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2119 description='Synchronize the previously extracted source tree for an existing recipe',
2120 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2121 group='advanced')
2122 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2123 parser_sync.add_argument('srctree', help='Path to the source tree')
2124 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2125 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002126 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002127
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002128 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2129 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.',
2130 group='working', order=10)
2131 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2132 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2133 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)')
2134 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')
2135 parser_rename.set_defaults(func=rename)
2136
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002137 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002138 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.',
2139 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002140 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2141 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 -05002142 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002143 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2144 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')
2145 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 -04002146 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2147 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2148 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 -05002149 parser_update_recipe.set_defaults(func=update_recipe)
2150
2151 parser_status = subparsers.add_parser('status', help='Show workspace status',
2152 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002153 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002154 parser_status.set_defaults(func=status)
2155
2156 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002157 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 -05002158 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002159 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002160 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2161 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
2162 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002163
2164 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002165 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 -06002166 group='working', order=-100)
2167 parser_finish.add_argument('recipename', help='Recipe to finish')
2168 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.')
2169 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')
2170 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002171 parser_finish.add_argument('--force', '-f', action="store_true", help='Force continuing even if there are uncommitted changes in the source tree repository')
2172 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2173 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2174 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 -06002175 parser_finish.set_defaults(func=finish)