blob: ea09bbff312857a2da298c085f17ab1fe3e8141f [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#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17"""Devtool standard plugins"""
18
19import os
20import sys
21import re
22import shutil
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050023import subprocess
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024import tempfile
25import logging
26import argparse
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050027import argparse_oe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050028import scriptutils
29import errno
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050030import glob
Patrick Williamsc0f7c042017-02-23 20:41:17 -060031import filecmp
Patrick Williamsf1e5d692016-03-30 15:21:19 -050032from collections import OrderedDict
Brad Bishop316dfdd2018-06-25 12:45:53 -040033from 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 -050034from devtool import parse_recipe
35
36logger = logging.getLogger('devtool')
37
Brad Bishop316dfdd2018-06-25 12:45:53 -040038override_branch_prefix = 'devtool-override-'
39
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040
41def add(args, config, basepath, workspace):
42 """Entry point for the devtool 'add' subcommand"""
43 import bb
44 import oe.recipeutils
45
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050046 if not args.recipename and not args.srctree and not args.fetch and not args.fetchuri:
47 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 -050048
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050049 # These are positional arguments, but because we're nice, allow
50 # specifying e.g. source tree without name, or fetch URI without name or
51 # source tree (if we can detect that that is what the user meant)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060052 if scriptutils.is_src_url(args.recipename):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050053 if not args.fetchuri:
54 if args.fetch:
55 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
56 args.fetchuri = args.recipename
57 args.recipename = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -060058 elif scriptutils.is_src_url(args.srctree):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050059 if not args.fetchuri:
60 if args.fetch:
61 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
62 args.fetchuri = args.srctree
63 args.srctree = ''
64 elif args.recipename and not args.srctree:
65 if os.sep in args.recipename:
66 args.srctree = args.recipename
67 args.recipename = None
68 elif os.path.isdir(args.recipename):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080069 logger.warning('Ambiguous argument "%s" - assuming you mean it to be the recipe name' % args.recipename)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060070
Brad Bishopd7bf8c12018-02-25 22:55:05 -050071 if not args.fetchuri:
72 if args.srcrev:
73 raise DevtoolError('The -S/--srcrev option is only valid when fetching from an SCM repository')
74 if args.srcbranch:
75 raise DevtoolError('The -B/--srcbranch option is only valid when fetching from an SCM repository')
76
Patrick Williamsc0f7c042017-02-23 20:41:17 -060077 if args.srctree and os.path.isfile(args.srctree):
78 args.fetchuri = 'file://' + os.path.abspath(args.srctree)
79 args.srctree = ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -050080
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050081 if args.fetch:
82 if args.fetchuri:
83 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
84 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080085 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 -050086 args.fetchuri = args.fetch
Patrick Williamsf1e5d692016-03-30 15:21:19 -050087
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050088 if args.recipename:
89 if args.recipename in workspace:
90 raise DevtoolError("recipe %s is already in your workspace" %
91 args.recipename)
92 reason = oe.recipeutils.validate_pn(args.recipename)
93 if reason:
94 raise DevtoolError(reason)
95
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050096 if args.srctree:
97 srctree = os.path.abspath(args.srctree)
98 srctreeparent = None
99 tmpsrcdir = None
100 else:
101 srctree = None
102 srctreeparent = get_default_srctree(config)
103 bb.utils.mkdirhier(srctreeparent)
104 tmpsrcdir = tempfile.mkdtemp(prefix='devtoolsrc', dir=srctreeparent)
105
106 if srctree and os.path.exists(srctree):
107 if args.fetchuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500108 if not os.path.isdir(srctree):
109 raise DevtoolError("Cannot fetch into source tree path %s as "
110 "it exists and is not a directory" %
111 srctree)
112 elif os.listdir(srctree):
113 raise DevtoolError("Cannot fetch into source tree path %s as "
114 "it already exists and is non-empty" %
115 srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500116 elif not args.fetchuri:
117 if args.srctree:
118 raise DevtoolError("Specified source tree %s could not be found" %
119 args.srctree)
120 elif srctree:
121 raise DevtoolError("No source tree exists at default path %s - "
122 "either create and populate this directory, "
123 "or specify a path to a source tree, or a "
124 "URI to fetch source from" % srctree)
125 else:
126 raise DevtoolError("You must either specify a source tree "
127 "or a URI to fetch source from")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500128
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500129 if args.version:
130 if '_' in args.version or ' ' in args.version:
131 raise DevtoolError('Invalid version string "%s"' % args.version)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500132
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500133 if args.color == 'auto' and sys.stdout.isatty():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500134 color = 'always'
135 else:
136 color = args.color
137 extracmdopts = ''
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500138 if args.fetchuri:
139 source = args.fetchuri
140 if srctree:
141 extracmdopts += ' -x %s' % srctree
142 else:
143 extracmdopts += ' -x %s' % tmpsrcdir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500144 else:
145 source = srctree
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500146 if args.recipename:
147 extracmdopts += ' -N %s' % args.recipename
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500148 if args.version:
149 extracmdopts += ' -V %s' % args.version
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500150 if args.binary:
151 extracmdopts += ' -b'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500152 if args.also_native:
153 extracmdopts += ' --also-native'
154 if args.src_subdir:
155 extracmdopts += ' --src-subdir "%s"' % args.src_subdir
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600156 if args.autorev:
157 extracmdopts += ' -a'
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500158 if args.fetch_dev:
159 extracmdopts += ' --fetch-dev'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500160 if args.mirrors:
161 extracmdopts += ' --mirrors'
162 if args.srcrev:
163 extracmdopts += ' --srcrev %s' % args.srcrev
164 if args.srcbranch:
165 extracmdopts += ' --srcbranch %s' % args.srcbranch
166 if args.provides:
167 extracmdopts += ' --provides %s' % args.provides
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500168
169 tempdir = tempfile.mkdtemp(prefix='devtool')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500171 try:
172 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create --devtool -o %s \'%s\' %s' % (color, tempdir, source, extracmdopts), watch=True)
173 except bb.process.ExecutionError as e:
174 if e.exitcode == 15:
175 raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line')
176 else:
177 raise DevtoolError('Command \'%s\' failed' % e.command)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500178
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500179 recipes = glob.glob(os.path.join(tempdir, '*.bb'))
180 if recipes:
181 recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0]
182 if recipename in workspace:
183 raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename)
184 recipedir = os.path.join(config.workspace_path, 'recipes', recipename)
185 bb.utils.mkdirhier(recipedir)
186 recipefile = os.path.join(recipedir, os.path.basename(recipes[0]))
187 appendfile = recipe_to_append(recipefile, config)
188 if os.path.exists(appendfile):
189 # This shouldn't be possible, but just in case
190 raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace')
191 if os.path.exists(recipefile):
192 raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile)
193 if tmpsrcdir:
194 srctree = os.path.join(srctreeparent, recipename)
195 if os.path.exists(tmpsrcdir):
196 if os.path.exists(srctree):
197 if os.path.isdir(srctree):
198 try:
199 os.rmdir(srctree)
200 except OSError as e:
201 if e.errno == errno.ENOTEMPTY:
202 raise DevtoolError('Source tree path %s already exists and is not empty' % srctree)
203 else:
204 raise
205 else:
206 raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree)
207 logger.info('Using default source tree path %s' % srctree)
208 shutil.move(tmpsrcdir, srctree)
209 else:
210 raise DevtoolError('Couldn\'t find source tree created by recipetool')
211 bb.utils.mkdirhier(recipedir)
212 shutil.move(recipes[0], recipefile)
213 # Move any additional files created by recipetool
214 for fn in os.listdir(tempdir):
215 shutil.move(os.path.join(tempdir, fn), recipedir)
216 else:
217 raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout))
218 attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile))
219 if os.path.exists(attic_recipe):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800220 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 -0500221 finally:
222 if tmpsrcdir and os.path.exists(tmpsrcdir):
223 shutil.rmtree(tmpsrcdir)
224 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500226 for fn in os.listdir(recipedir):
227 _add_md5(config, recipename, os.path.join(recipedir, fn))
228
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500229 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600230 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500231 try:
232 rd = tinfoil.parse_recipe_file(recipefile, False)
233 except Exception as e:
234 logger.error(str(e))
235 rd = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600236 if not rd:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500237 # Parsing failed. We just created this recipe and we shouldn't
238 # leave it in the workdir or it'll prevent bitbake from starting
239 movefn = '%s.parsefailed' % recipefile
240 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)
241 shutil.move(recipefile, movefn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600242 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500243
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600244 if args.fetchuri and not args.no_git:
245 setup_git_repo(srctree, args.version, 'devtool', d=tinfoil.config_data)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500246
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600247 initial_rev = None
248 if os.path.exists(os.path.join(srctree, '.git')):
249 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
250 initial_rev = stdout.rstrip()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500251
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600252 if args.src_subdir:
253 srctree = os.path.join(srctree, args.src_subdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600255 bb.utils.mkdirhier(os.path.dirname(appendfile))
256 with open(appendfile, 'w') as f:
257 f.write('inherit externalsrc\n')
258 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500259
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600260 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
261 if b_is_s:
262 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
263 if initial_rev:
264 f.write('\n# initial_rev: %s\n' % initial_rev)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500265
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600266 if args.binary:
267 f.write('do_install_append() {\n')
268 f.write(' rm -rf ${D}/.git\n')
269 f.write(' rm -f ${D}/singletask.lock\n')
270 f.write('}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500271
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600272 if bb.data.inherits_class('npm', rd):
273 f.write('do_install_append() {\n')
274 f.write(' # Remove files added to source dir by devtool/externalsrc\n')
275 f.write(' rm -f ${NPM_INSTALLDIR}/singletask.lock\n')
276 f.write(' rm -rf ${NPM_INSTALLDIR}/.git\n')
277 f.write(' rm -rf ${NPM_INSTALLDIR}/oe-local-files\n')
278 f.write(' for symlink in ${EXTERNALSRC_SYMLINKS} ; do\n')
279 f.write(' rm -f ${NPM_INSTALLDIR}/${symlink%%:*}\n')
280 f.write(' done\n')
281 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500282
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500283 # Check if the new layer provides recipes whose priorities have been
284 # overriden by PREFERRED_PROVIDER.
285 recipe_name = rd.getVar('PN')
286 provides = rd.getVar('PROVIDES')
287 # Search every item defined in PROVIDES
288 for recipe_provided in provides.split():
289 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
290 current_pprovider = rd.getVar(preferred_provider)
291 if current_pprovider and current_pprovider != recipe_name:
292 if args.fixed_setup:
293 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
294 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
295 with open(layerconf_file, 'a') as f:
296 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
297 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800298 logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500299 break
300
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600301 _add_md5(config, recipename, appendfile)
302
Brad Bishop316dfdd2018-06-25 12:45:53 -0400303 check_prerelease_version(rd.getVar('PV'), 'devtool add')
304
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600305 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
306
307 finally:
308 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500309
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500310 return 0
311
312
313def _check_compatible_recipe(pn, d):
314 """Check if the recipe is supported by devtool"""
315 if pn == 'perf':
316 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600317 "source and thus cannot be supported by this tool",
318 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319
320 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600321 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322
323 if bb.data.inherits_class('image', d):
324 raise DevtoolError("The %s recipe is an image, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600325 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326
327 if bb.data.inherits_class('populate_sdk', d):
328 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600329 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500330
331 if bb.data.inherits_class('packagegroup', d):
332 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600333 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500334
335 if bb.data.inherits_class('meta', d):
336 raise DevtoolError("The %s recipe is a meta-recipe, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600337 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500338
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500339 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600340 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341 raise DevtoolError("externalsrc is currently enabled for the %s "
342 "recipe. This prevents the normal do_patch task "
343 "from working. You will need to disable this "
344 "first." % pn)
345
Brad Bishop316dfdd2018-06-25 12:45:53 -0400346def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
347 """Common function for copying a file to the dry run output directory"""
348 relpath = os.path.relpath(dst, base_outdir)
349 if relpath.startswith('..'):
350 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
351 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500352 dst_d = os.path.dirname(dst)
353 if dst_d:
354 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400355 # Don't overwrite existing files, otherwise in the case of an upgrade
356 # the dry-run written out recipe will be overwritten with an unmodified
357 # version
358 if not os.path.exists(dst):
359 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500360
Brad Bishop316dfdd2018-06-25 12:45:53 -0400361def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
362 """Move a file. Creates all the directory components of destination path."""
363 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
364 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
365 if dry_run_outdir:
366 # We want to copy here, not move
367 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
368 else:
369 dst_d = os.path.dirname(dst)
370 if dst_d:
371 bb.utils.mkdirhier(dst_d)
372 shutil.move(src, dst)
373
374def _copy_file(src, dst, dry_run_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600375 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400376 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
377 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
378 if dry_run_outdir:
379 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
380 else:
381 dst_d = os.path.dirname(dst)
382 if dst_d:
383 bb.utils.mkdirhier(dst_d)
384 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600385
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500386def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
387 """List contents of a git treeish"""
388 import bb
389 cmd = ['git', 'ls-tree', '-z', treeish]
390 if recursive:
391 cmd.append('-r')
392 out, _ = bb.process.run(cmd, cwd=repodir)
393 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500394 if out:
395 for line in out.split('\0'):
396 if line:
397 split = line.split(None, 4)
398 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500399 return ret
400
401def _git_exclude_path(srctree, path):
402 """Return pathspec (list of paths) that excludes certain path"""
403 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
404 # we don't catch files that are deleted, for example. A more reliable way
405 # to implement this would be to use "negative pathspecs" which were
406 # introduced in Git v1.9.0. Revisit this when/if the required Git version
407 # becomes greater than that.
408 path = os.path.normpath(path)
409 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600410 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500411 if path in git_files:
412 git_files.remove(path)
413 return git_files
414 else:
415 return ['.']
416
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500417def _ls_tree(directory):
418 """Recursive listing of files in a directory"""
419 ret = []
420 for root, dirs, files in os.walk(directory):
421 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
422 fname in files])
423 return ret
424
425
426def extract(args, config, basepath, workspace):
427 """Entry point for the devtool 'extract' subcommand"""
428 import bb
429
Brad Bishop316dfdd2018-06-25 12:45:53 -0400430 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500431 if not tinfoil:
432 # Error already shown
433 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600434 try:
435 rd = parse_recipe(config, tinfoil, args.recipename, True)
436 if not rd:
437 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500438
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600439 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400440 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 -0600441 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500442
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600443 if initial_rev:
444 return 0
445 else:
446 return 1
447 finally:
448 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500449
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500450def sync(args, config, basepath, workspace):
451 """Entry point for the devtool 'sync' subcommand"""
452 import bb
453
Brad Bishop316dfdd2018-06-25 12:45:53 -0400454 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500455 if not tinfoil:
456 # Error already shown
457 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600458 try:
459 rd = parse_recipe(config, tinfoil, args.recipename, True)
460 if not rd:
461 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500462
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600463 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400464 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 -0600465 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500466
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600467 if initial_rev:
468 return 0
469 else:
470 return 1
471 finally:
472 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500473
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500474
Brad Bishop316dfdd2018-06-25 12:45:53 -0400475def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500476 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500477 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500478 import oe.patch
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500479
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500480 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500481
482 _check_compatible_recipe(pn, d)
483
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500484 if sync:
485 if not os.path.exists(srctree):
486 raise DevtoolError("output path %s does not exist" % srctree)
487 else:
488 if os.path.exists(srctree):
489 if not os.path.isdir(srctree):
490 raise DevtoolError("output path %s exists and is not a directory" %
491 srctree)
492 elif os.listdir(srctree):
493 raise DevtoolError("output path %s already exists and is "
494 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500495
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500496 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
497 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600498 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500499
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500500 if not sync:
501 # Prepare for shutil.move later on
502 bb.utils.mkdirhier(srctree)
503 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500504
Brad Bishop316dfdd2018-06-25 12:45:53 -0400505 extra_overrides = []
506 if not no_overrides:
507 history = d.varhistory.variable('SRC_URI')
508 for event in history:
509 if not 'flag' in event:
510 if event['op'].startswith(('_append[', '_prepend[')):
511 extra_overrides.append(event['op'].split('[')[1].split(']')[0])
Andrew Geissler99467da2019-02-25 18:54:23 -0600512 # We want to remove duplicate overrides. If a recipe had multiple
513 # SRC_URI_override += values it would cause mulitple instances of
514 # overrides. This doesn't play nicely with things like creating a
515 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
516 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400517 if extra_overrides:
518 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
519
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500521
522 appendexisted = False
523 recipefile = d.getVar('FILE')
524 appendfile = recipe_to_append(recipefile, config)
525 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
526
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500527 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
528 # directory so that:
529 # (a) we pick up all files that get unpacked to the WORKDIR, and
530 # (b) we don't disturb the existing build
531 # However, with recipe-specific sysroots the sysroots for the recipe
532 # will be prepared under WORKDIR, and if we used the system temporary
533 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
534 # our attempts to hardlink files into the recipe-specific sysroots
535 # will fail on systems where /tmp is a different filesystem, and it
536 # would have to fall back to copying the files which is a waste of
537 # time. Put the temp directory under the WORKDIR to prevent that from
538 # being a problem.
539 tempbasedir = d.getVar('WORKDIR')
540 bb.utils.mkdirhier(tempbasedir)
541 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500542 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500543 tinfoil.logger.setLevel(logging.WARNING)
544
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500545 # FIXME this results in a cache reload under control of tinfoil, which is fine
546 # except we don't get the knotty progress bar
547
548 if os.path.exists(appendfile):
549 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
550 shutil.copyfile(appendfile, appendbackup)
551 else:
552 appendbackup = None
553 bb.utils.mkdirhier(os.path.dirname(appendfile))
554 logger.debug('writing append file %s' % appendfile)
555 with open(appendfile, 'a') as f:
556 f.write('###--- _extract_source\n')
557 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
558 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
559 if not is_kernel_yocto:
560 f.write('PATCHTOOL = "git"\n')
561 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400562 if extra_overrides:
563 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500564 f.write('inherit devtool-source\n')
565 f.write('###--- _extract_source\n')
566
567 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
568
569 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
570 bb.utils.mkdirhier(sstate_manifests)
571 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
572 with open(preservestampfile, 'w') as f:
573 f.write(d.getVar('STAMP'))
574 try:
575 if bb.data.inherits_class('kernel-yocto', d):
576 # We need to generate the kernel config
577 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500578 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500579 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500580
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500581 # Run the fetch + unpack tasks
582 res = tinfoil.build_targets(pn,
583 task,
584 handle_events=True)
585 finally:
586 if os.path.exists(preservestampfile):
587 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500588
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500589 if not res:
590 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591
Brad Bishop316dfdd2018-06-25 12:45:53 -0400592 try:
593 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
594 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500595
Brad Bishop316dfdd2018-06-25 12:45:53 -0400596 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
597 srcsubdir = f.read()
598 except FileNotFoundError as e:
599 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
600 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500601
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500602 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
603 srctree_localdir = os.path.join(srctree, 'oe-local-files')
604
605 if sync:
606 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
607
608 # Move oe-local-files directory to srctree
609 # As the oe-local-files is not part of the constructed git tree,
610 # remove them directly during the synchrounizating might surprise
611 # the users. Instead, we move it to oe-local-files.bak and remind
612 # user in the log message.
613 if os.path.exists(srctree_localdir + '.bak'):
614 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
615
616 if os.path.exists(srctree_localdir):
617 logger.info('Backing up current local file directory %s' % srctree_localdir)
618 shutil.move(srctree_localdir, srctree_localdir + '.bak')
619
620 if os.path.exists(tempdir_localdir):
621 logger.info('Syncing local source files to srctree...')
622 shutil.copytree(tempdir_localdir, srctree_localdir)
623 else:
624 # Move oe-local-files directory to srctree
625 if os.path.exists(tempdir_localdir):
626 logger.info('Adding local source files to srctree...')
627 shutil.move(tempdir_localdir, srcsubdir)
628
629 shutil.move(srcsubdir, srctree)
630
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500631 if os.path.abspath(d.getVar('S')) == os.path.abspath(d.getVar('WORKDIR')):
632 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
633 # (otherwise the recipe won't build as expected)
634 local_files_dir = os.path.join(srctree, 'oe-local-files')
635 addfiles = []
636 for root, _, files in os.walk(local_files_dir):
637 relpth = os.path.relpath(root, local_files_dir)
638 if relpth != '.':
639 bb.utils.mkdirhier(os.path.join(srctree, relpth))
640 for fn in files:
641 if fn == '.gitignore':
642 continue
643 destpth = os.path.join(srctree, relpth, fn)
644 if os.path.exists(destpth):
645 os.unlink(destpth)
646 os.symlink('oe-local-files/%s' % fn, destpth)
647 addfiles.append(os.path.join(relpth, fn))
648 if addfiles:
649 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
650 useroptions = []
651 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=d)
652 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)
653
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500654 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500655 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500656 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500657
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500658 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500659 if appendbackup:
660 shutil.copyfile(appendbackup, appendfile)
661 elif os.path.exists(appendfile):
662 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500663 if keep_temp:
664 logger.info('Preserving temporary directory %s' % tempdir)
665 else:
666 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400667 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500668
669def _add_md5(config, recipename, filename):
670 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
671 import bb.utils
672
673 def addfile(fn):
674 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500675 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
676 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
677 f.seek(0, os.SEEK_SET)
678 if not md5_str in f.read():
679 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500680
681 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500682 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500683 for f in files:
684 addfile(os.path.join(root, f))
685 else:
686 addfile(filename)
687
688def _check_preserve(config, recipename):
689 """Check if a file was manually changed and needs to be saved in 'attic'
690 directory"""
691 import bb.utils
692 origfile = os.path.join(config.workspace_path, '.devtool_md5')
693 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500694 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500695 with open(origfile, 'r') as f:
696 with open(newfile, 'w') as tf:
697 for line in f.readlines():
698 splitline = line.rstrip().split('|')
699 if splitline[0] == recipename:
700 removefile = os.path.join(config.workspace_path, splitline[1])
701 try:
702 md5 = bb.utils.md5_file(removefile)
703 except IOError as err:
704 if err.errno == 2:
705 # File no longer exists, skip it
706 continue
707 else:
708 raise
709 if splitline[2] != md5:
710 bb.utils.mkdirhier(preservepath)
711 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800712 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500713 shutil.move(removefile, os.path.join(preservepath, preservefile))
714 else:
715 os.remove(removefile)
716 else:
717 tf.write(line)
718 os.rename(newfile, origfile)
719
720def modify(args, config, basepath, workspace):
721 """Entry point for the devtool 'modify' subcommand"""
722 import bb
723 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400724 import oe.patch
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500725
726 if args.recipename in workspace:
727 raise DevtoolError("recipe %s is already in your workspace" %
728 args.recipename)
729
Brad Bishop316dfdd2018-06-25 12:45:53 -0400730 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600731 try:
732 rd = parse_recipe(config, tinfoil, args.recipename, True)
733 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500734 return 1
735
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500736 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600737 if pn != args.recipename:
738 logger.info('Mapping %s to %s' % (args.recipename, pn))
739 if pn in workspace:
740 raise DevtoolError("recipe %s is already in your workspace" %
741 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600743 if args.srctree:
744 srctree = os.path.abspath(args.srctree)
745 else:
746 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500747
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600748 if args.no_extract and not os.path.isdir(srctree):
749 raise DevtoolError("--no-extract specified and source path %s does "
750 "not exist or is not a directory" %
751 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600752
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500753 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600754 appendfile = recipe_to_append(recipefile, config, args.wildcard)
755 if os.path.exists(appendfile):
756 raise DevtoolError("Another variant of recipe %s is already in your "
757 "workspace (only one variant of a recipe can "
758 "currently be worked on at once)"
759 % pn)
760
761 _check_compatible_recipe(pn, rd)
762
763 initial_rev = None
764 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400765 check_commits = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600766 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400767 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 -0500768 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600769 return 1
770 logger.info('Source tree extracted to %s' % srctree)
771 # Get list of commits since this revision
772 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
773 commits = stdout.split()
Brad Bishop316dfdd2018-06-25 12:45:53 -0400774 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600775 else:
776 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600777 # Check if it's a tree previously extracted by us. This is done
778 # by ensuring that devtool-base and args.branch (devtool) exist.
779 # The check_commits logic will cause an exception if either one
780 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600781 try:
782 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600783 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600784 except bb.process.ExecutionError:
785 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400786 if stdout:
787 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600788 for line in stdout.splitlines():
789 if line.startswith('*'):
790 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
791 initial_rev = stdout.rstrip()
792 if not initial_rev:
793 # Otherwise, just grab the head revision
794 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
795 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500796
Brad Bishop316dfdd2018-06-25 12:45:53 -0400797 branch_patches = {}
798 if check_commits:
799 # Check if there are override branches
800 (stdout, _) = bb.process.run('git branch', cwd=srctree)
801 branches = []
802 for line in stdout.rstrip().splitlines():
803 branchname = line[2:].rstrip()
804 if branchname.startswith(override_branch_prefix):
805 branches.append(branchname)
806 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800807 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 -0400808 branches.insert(0, args.branch)
809 seen_patches = []
810 for branch in branches:
811 branch_patches[branch] = []
812 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
813 for line in stdout.splitlines():
814 line = line.strip()
815 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
816 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
817 if not origpatch in seen_patches:
818 seen_patches.append(origpatch)
819 branch_patches[branch].append(origpatch)
820
821 # Need to grab this here in case the source is within a subdirectory
822 srctreebase = srctree
823
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600824 # Check that recipe isn't using a shared workdir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500825 s = os.path.abspath(rd.getVar('S'))
826 workdir = os.path.abspath(rd.getVar('WORKDIR'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600827 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
828 # Handle if S is set to a subdirectory of the source
829 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
830 srctree = os.path.join(srctree, srcsubdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500831
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600832 bb.utils.mkdirhier(os.path.dirname(appendfile))
833 with open(appendfile, 'w') as f:
834 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
835 # Local files can be modified/tracked in separate subdir under srctree
836 # Mostly useful for packages with S != WORKDIR
837 f.write('FILESPATH_prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400838 os.path.join(srctreebase, 'oe-local-files'))
839 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500840
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600841 f.write('\ninherit externalsrc\n')
842 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
843 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500844
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600845 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
846 if b_is_s:
847 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500848
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600849 if bb.data.inherits_class('kernel', rd):
850 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500851 'do_fetch do_unpack do_kernel_configme do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400852 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600853 f.write('\ndo_configure_append() {\n'
854 ' cp ${B}/.config ${S}/.config.baseline\n'
855 ' ln -sfT ${B}/.config ${S}/.config.new\n'
856 '}\n')
857 if initial_rev:
858 f.write('\n# initial_rev: %s\n' % initial_rev)
859 for commit in commits:
860 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400861 if branch_patches:
862 for branch in branch_patches:
863 if branch == args.branch:
864 continue
865 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500866
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500867 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
868
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600869 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500870
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600871 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500872
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600873 finally:
874 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500875
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500876 return 0
877
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500878
879def rename(args, config, basepath, workspace):
880 """Entry point for the devtool 'rename' subcommand"""
881 import bb
882 import oe.recipeutils
883
884 check_workspace_recipe(workspace, args.recipename)
885
886 if not (args.newname or args.version):
887 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
888
889 recipefile = workspace[args.recipename]['recipefile']
890 if not recipefile:
891 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
892
893 if args.newname and args.newname != args.recipename:
894 reason = oe.recipeutils.validate_pn(args.newname)
895 if reason:
896 raise DevtoolError(reason)
897 newname = args.newname
898 else:
899 newname = args.recipename
900
901 append = workspace[args.recipename]['bbappend']
902 appendfn = os.path.splitext(os.path.basename(append))[0]
903 splitfn = appendfn.split('_')
904 if len(splitfn) > 1:
905 origfnver = appendfn.split('_')[1]
906 else:
907 origfnver = ''
908
909 recipefilemd5 = None
910 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
911 try:
912 rd = parse_recipe(config, tinfoil, args.recipename, True)
913 if not rd:
914 return 1
915
916 bp = rd.getVar('BP')
917 bpn = rd.getVar('BPN')
918 if newname != args.recipename:
919 localdata = rd.createCopy()
920 localdata.setVar('PN', newname)
921 newbpn = localdata.getVar('BPN')
922 else:
923 newbpn = bpn
924 s = rd.getVar('S', False)
925 src_uri = rd.getVar('SRC_URI', False)
926 pv = rd.getVar('PV')
927
928 # Correct variable values that refer to the upstream source - these
929 # values must stay the same, so if the name/version are changing then
930 # we need to fix them up
931 new_s = s
932 new_src_uri = src_uri
933 if newbpn != bpn:
934 # ${PN} here is technically almost always incorrect, but people do use it
935 new_s = new_s.replace('${BPN}', bpn)
936 new_s = new_s.replace('${PN}', bpn)
937 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
938 new_src_uri = new_src_uri.replace('${BPN}', bpn)
939 new_src_uri = new_src_uri.replace('${PN}', bpn)
940 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
941 if args.version and origfnver == pv:
942 new_s = new_s.replace('${PV}', pv)
943 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
944 new_src_uri = new_src_uri.replace('${PV}', pv)
945 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
946 patchfields = {}
947 if new_s != s:
948 patchfields['S'] = new_s
949 if new_src_uri != src_uri:
950 patchfields['SRC_URI'] = new_src_uri
951 if patchfields:
952 recipefilemd5 = bb.utils.md5_file(recipefile)
953 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
954 newrecipefilemd5 = bb.utils.md5_file(recipefile)
955 finally:
956 tinfoil.shutdown()
957
958 if args.version:
959 newver = args.version
960 else:
961 newver = origfnver
962
963 if newver:
964 newappend = '%s_%s.bbappend' % (newname, newver)
965 newfile = '%s_%s.bb' % (newname, newver)
966 else:
967 newappend = '%s.bbappend' % newname
968 newfile = '%s.bb' % newname
969
970 oldrecipedir = os.path.dirname(recipefile)
971 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
972 if oldrecipedir != newrecipedir:
973 bb.utils.mkdirhier(newrecipedir)
974
975 newappend = os.path.join(os.path.dirname(append), newappend)
976 newfile = os.path.join(newrecipedir, newfile)
977
978 # Rename bbappend
979 logger.info('Renaming %s to %s' % (append, newappend))
980 os.rename(append, newappend)
981 # Rename recipe file
982 logger.info('Renaming %s to %s' % (recipefile, newfile))
983 os.rename(recipefile, newfile)
984
985 # Rename source tree if it's the default path
986 appendmd5 = None
987 if not args.no_srctree:
988 srctree = workspace[args.recipename]['srctree']
989 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
990 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
991 logger.info('Renaming %s to %s' % (srctree, newsrctree))
992 shutil.move(srctree, newsrctree)
993 # Correct any references (basically EXTERNALSRC*) in the .bbappend
994 appendmd5 = bb.utils.md5_file(newappend)
995 appendlines = []
996 with open(newappend, 'r') as f:
997 for line in f:
998 appendlines.append(line)
999 with open(newappend, 'w') as f:
1000 for line in appendlines:
1001 if srctree in line:
1002 line = line.replace(srctree, newsrctree)
1003 f.write(line)
1004 newappendmd5 = bb.utils.md5_file(newappend)
1005
1006 bpndir = None
1007 newbpndir = None
1008 if newbpn != bpn:
1009 bpndir = os.path.join(oldrecipedir, bpn)
1010 if os.path.exists(bpndir):
1011 newbpndir = os.path.join(newrecipedir, newbpn)
1012 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1013 shutil.move(bpndir, newbpndir)
1014
1015 bpdir = None
1016 newbpdir = None
1017 if newver != origfnver or newbpn != bpn:
1018 bpdir = os.path.join(oldrecipedir, bp)
1019 if os.path.exists(bpdir):
1020 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1021 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1022 shutil.move(bpdir, newbpdir)
1023
1024 if oldrecipedir != newrecipedir:
1025 # Move any stray files and delete the old recipe directory
1026 for entry in os.listdir(oldrecipedir):
1027 oldpath = os.path.join(oldrecipedir, entry)
1028 newpath = os.path.join(newrecipedir, entry)
1029 logger.info('Renaming %s to %s' % (oldpath, newpath))
1030 shutil.move(oldpath, newpath)
1031 os.rmdir(oldrecipedir)
1032
1033 # Now take care of entries in .devtool_md5
1034 md5entries = []
1035 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1036 for line in f:
1037 md5entries.append(line)
1038
1039 if bpndir and newbpndir:
1040 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1041 else:
1042 relbpndir = None
1043 if bpdir and newbpdir:
1044 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1045 else:
1046 relbpdir = None
1047
1048 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1049 for entry in md5entries:
1050 splitentry = entry.rstrip().split('|')
1051 if len(splitentry) > 2:
1052 if splitentry[0] == args.recipename:
1053 splitentry[0] = newname
1054 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1055 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1056 if appendmd5 and splitentry[2] == appendmd5:
1057 splitentry[2] = newappendmd5
1058 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1059 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1060 if recipefilemd5 and splitentry[2] == recipefilemd5:
1061 splitentry[2] = newrecipefilemd5
1062 elif relbpndir and splitentry[1].startswith(relbpndir):
1063 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1064 elif relbpdir and splitentry[1].startswith(relbpdir):
1065 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1066 entry = '|'.join(splitentry) + '\n'
1067 f.write(entry)
1068 return 0
1069
1070
Brad Bishop316dfdd2018-06-25 12:45:53 -04001071def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001072 """Get initial and update rev of a recipe. These are the start point of the
1073 whole patchset and start point for the patches to be re-generated/updated.
1074 """
1075 import bb
1076
Brad Bishop316dfdd2018-06-25 12:45:53 -04001077 # Get current branch
1078 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1079 cwd=srctree)
1080 branchname = stdout.rstrip()
1081
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001082 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001083 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001084 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001085 with open(recipe_path, 'r') as f:
1086 for line in f:
1087 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001088 if not initial_rev:
1089 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001090 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001091 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001092 elif line.startswith('# patches_%s:' % branchname):
1093 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001094
1095 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001096 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001097 if initial_rev:
1098 # Find first actually changed revision
1099 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1100 initial_rev, cwd=srctree)
1101 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001102 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001103 if newcommits[i] == commits[i]:
1104 update_rev = commits[i]
1105
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001106 try:
1107 stdout, _ = bb.process.run('git cherry devtool-patched',
1108 cwd=srctree)
1109 except bb.process.ExecutionError as err:
1110 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001111
Brad Bishop316dfdd2018-06-25 12:45:53 -04001112 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001113 changed_revs = []
1114 for line in stdout.splitlines():
1115 if line.startswith('+ '):
1116 rev = line.split()[1]
1117 if rev in newcommits:
1118 changed_revs.append(rev)
1119
Brad Bishop316dfdd2018-06-25 12:45:53 -04001120 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001121
1122def _remove_file_entries(srcuri, filelist):
1123 """Remove file:// entries from SRC_URI"""
1124 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001125 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001126 for fname in filelist:
1127 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001128 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001129 if (srcuri[i].startswith('file://') and
1130 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001131 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001132 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001133 srcuri.pop(i)
1134 break
1135 return entries, remaining
1136
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001137def _replace_srcuri_entry(srcuri, filename, newentry):
1138 """Replace entry corresponding to specified file with a new entry"""
1139 basename = os.path.basename(filename)
1140 for i in range(len(srcuri)):
1141 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1142 srcuri.pop(i)
1143 srcuri.insert(i, newentry)
1144 break
1145
Brad Bishop316dfdd2018-06-25 12:45:53 -04001146def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001147 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001148
1149 dry_run_suffix = ' (dry-run)' if dry_run else ''
1150
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001151 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001152 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001153 if not destpath:
1154 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001155 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001156
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001157 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001158 if not no_report_remove:
1159 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1160 if not dry_run:
1161 # FIXME "git rm" here would be nice if the file in question is
1162 # tracked
1163 # FIXME there's a chance that this file is referred to by
1164 # another recipe, in which case deleting wouldn't be the
1165 # right thing to do
1166 os.remove(path)
1167 # Remove directory if empty
1168 try:
1169 os.rmdir(os.path.dirname(path))
1170 except OSError as ose:
1171 if ose.errno != errno.ENOTEMPTY:
1172 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001173
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001174
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001175def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001176 """Export patches from srctree to given location.
1177 Returns three-tuple of dicts:
1178 1. updated - patches that already exist in SRCURI
1179 2. added - new patches that don't exist in SRCURI
1180 3 removed - patches that exist in SRCURI but not in exported patches
1181 In each dict the key is the 'basepath' of the URI and value is the
1182 absolute path to the existing file in recipe space (if any).
1183 """
1184 import oe.recipeutils
1185 from oe.patch import GitApplyTree
1186 updated = OrderedDict()
1187 added = OrderedDict()
1188 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1189
1190 existing_patches = dict((os.path.basename(path), path) for path in
1191 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001192 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001193
1194 # Generate patches from Git, exclude local files directory
1195 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1196 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1197
1198 new_patches = sorted(os.listdir(destdir))
1199 for new_patch in new_patches:
1200 # Strip numbering from patch names. If it's a git sequence named patch,
1201 # the numbers might not match up since we are starting from a different
1202 # revision This does assume that people are using unique shortlog
1203 # values, but they ought to be anyway...
1204 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001205 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001206 for old_patch in existing_patches:
1207 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001208 old_basename_splitext = os.path.splitext(old_basename)
1209 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1210 old_patch_noext = os.path.splitext(old_patch)[0]
1211 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001212 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001213 elif new_basename == old_basename:
1214 match_name = old_patch
1215 break
1216 if match_name:
1217 # Rename patch files
1218 if new_patch != match_name:
1219 os.rename(os.path.join(destdir, new_patch),
1220 os.path.join(destdir, match_name))
1221 # Need to pop it off the list now before checking changed_revs
1222 oldpath = existing_patches.pop(old_patch)
1223 if changed_revs is not None:
1224 # Avoid updating patches that have not actually changed
1225 with open(os.path.join(destdir, match_name), 'r') as f:
1226 firstlineitems = f.readline().split()
1227 # Looking for "From <hash>" line
1228 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1229 if not firstlineitems[1] in changed_revs:
1230 continue
1231 # Recompress if necessary
1232 if oldpath.endswith(('.gz', '.Z')):
1233 bb.process.run(['gzip', match_name], cwd=destdir)
1234 if oldpath.endswith('.gz'):
1235 match_name += '.gz'
1236 else:
1237 match_name += '.Z'
1238 elif oldpath.endswith('.bz2'):
1239 bb.process.run(['bzip2', match_name], cwd=destdir)
1240 match_name += '.bz2'
1241 updated[match_name] = oldpath
1242 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001243 added[new_patch] = None
1244 return (updated, added, existing_patches)
1245
1246
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001247def _create_kconfig_diff(srctree, rd, outfile):
1248 """Create a kconfig fragment"""
1249 # Only update config fragment if both config files exist
1250 orig_config = os.path.join(srctree, '.config.baseline')
1251 new_config = os.path.join(srctree, '.config.new')
1252 if os.path.exists(orig_config) and os.path.exists(new_config):
1253 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1254 '--unchanged-line-format=', orig_config, new_config]
1255 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1256 stderr=subprocess.PIPE)
1257 stdout, stderr = pipe.communicate()
1258 if pipe.returncode == 1:
1259 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001260 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001261 fobj.write(stdout)
1262 elif pipe.returncode == 0:
1263 logger.info("Would remove config fragment %s" % outfile)
1264 if os.path.exists(outfile):
1265 # Remove fragment file in case of empty diff
1266 logger.info("Removing config fragment %s" % outfile)
1267 os.unlink(outfile)
1268 else:
1269 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1270 return True
1271 return False
1272
1273
Brad Bishop316dfdd2018-06-25 12:45:53 -04001274def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001275 """Copy local files from srctree to given location.
1276 Returns three-tuple of dicts:
1277 1. updated - files that already exist in SRCURI
1278 2. added - new files files that don't exist in SRCURI
1279 3 removed - files that exist in SRCURI but not in exported files
1280 In each dict the key is the 'basepath' of the URI and value is the
1281 absolute path to the existing file in recipe space (if any).
1282 """
1283 import oe.recipeutils
1284
1285 # Find out local files (SRC_URI files that exist in the "recipe space").
1286 # Local files that reside in srctree are not included in patch generation.
1287 # Instead they are directly copied over the original source files (in
1288 # recipe space).
1289 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1290 new_set = None
1291 updated = OrderedDict()
1292 added = OrderedDict()
1293 removed = OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001294 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001295 git_files = _git_ls_tree(srctree)
1296 if 'oe-local-files' in git_files:
1297 # If tracked by Git, take the files from srctree HEAD. First get
1298 # the tree object of the directory
1299 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1300 tree = git_files['oe-local-files'][2]
1301 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1302 env=dict(os.environ, GIT_WORK_TREE=destdir,
1303 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001304 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001305 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001306 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001307 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001308 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001309 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001310 else:
1311 new_set = []
1312
1313 # Special handling for kernel config
1314 if bb.data.inherits_class('kernel-yocto', rd):
1315 fragment_fn = 'devtool-fragment.cfg'
1316 fragment_path = os.path.join(destdir, fragment_fn)
1317 if _create_kconfig_diff(srctree, rd, fragment_path):
1318 if os.path.exists(fragment_path):
1319 if fragment_fn not in new_set:
1320 new_set.append(fragment_fn)
1321 # Copy fragment to local-files
1322 if os.path.isdir(local_files_dir):
1323 shutil.copy2(fragment_path, local_files_dir)
1324 else:
1325 if fragment_fn in new_set:
1326 new_set.remove(fragment_fn)
1327 # Remove fragment from local-files
1328 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1329 os.unlink(os.path.join(local_files_dir, fragment_fn))
1330
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001331 if new_set is not None:
1332 for fname in new_set:
1333 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001334 origpath = existing_files.pop(fname)
1335 workpath = os.path.join(local_files_dir, fname)
1336 if not filecmp.cmp(origpath, workpath):
1337 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001338 elif fname != '.gitignore':
1339 added[fname] = None
1340
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001341 workdir = rd.getVar('WORKDIR')
1342 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001343 if not s.endswith(os.sep):
1344 s += os.sep
1345
1346 if workdir != s:
1347 # Handle files where subdir= was specified
1348 for fname in list(existing_files.keys()):
1349 # FIXME handle both subdir starting with BP and not?
1350 fworkpath = os.path.join(workdir, fname)
1351 if fworkpath.startswith(s):
1352 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1353 if os.path.exists(fpath):
1354 origpath = existing_files.pop(fname)
1355 if not filecmp.cmp(origpath, fpath):
1356 updated[fpath] = origpath
1357
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001358 removed = existing_files
1359 return (updated, added, removed)
1360
1361
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001362def _determine_files_dir(rd):
1363 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001364 recipedir = rd.getVar('FILE_DIRNAME')
1365 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001366 relpth = os.path.relpath(entry, recipedir)
1367 if not os.sep in relpth:
1368 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1369 if os.path.isdir(entry):
1370 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001371 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001372
1373
Brad Bishop316dfdd2018-06-25 12:45:53 -04001374def _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 -05001375 """Implement the 'srcrev' mode of update-recipe"""
1376 import bb
1377 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001378
Brad Bishop316dfdd2018-06-25 12:45:53 -04001379 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1380
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001381 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001382 recipedir = os.path.basename(recipefile)
1383 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001384
1385 # Get HEAD revision
1386 try:
1387 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1388 except bb.process.ExecutionError as err:
1389 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1390 (srctree, err))
1391 srcrev = stdout.strip()
1392 if len(srcrev) != 40:
1393 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1394
1395 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001396 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001397 patchfields = {}
1398 patchfields['SRCREV'] = srcrev
1399 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001400 srcuri = orig_src_uri.split()
1401 tempdir = tempfile.mkdtemp(prefix='devtool')
1402 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001403 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001404 try:
1405 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001406 srctreebase = workspace[recipename]['srctreebase']
1407 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001408 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001409 # Find list of existing patches in recipe file
1410 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001411 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001412 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1413 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001414 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 -05001415
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001416 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001417 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001418 if remove_files:
1419 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1420 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001421
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001422 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001423 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001424 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001425 removevalues = {}
1426 if update_srcuri:
1427 removevalues = {'SRC_URI': removedentries}
1428 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001429 if dry_run_outdir:
1430 logger.info('Creating bbappend (dry-run)')
1431 else:
1432 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1433 rd, appendlayerdir, files, wildcardver=wildcard_version,
1434 extralines=patchfields, removevalues=removevalues,
1435 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001436 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001437 files_dir = _determine_files_dir(rd)
1438 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001439 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001440 if os.path.isabs(basepath):
1441 # Original file (probably with subdir pointing inside source tree)
1442 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001443 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001444 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001445 _move_file(os.path.join(local_files_dir, basepath), path,
1446 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001447 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001448 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001449 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001450 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001451 os.path.join(files_dir, basepath),
1452 dry_run_outdir=dry_run_outdir,
1453 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001454 srcuri.append('file://%s' % basepath)
1455 update_srcuri = True
1456 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001457 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001458 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001459 finally:
1460 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001461 if not 'git://' in orig_src_uri:
1462 logger.info('You will need to update SRC_URI within the recipe to '
1463 'point to a git repository where you have pushed your '
1464 'changes')
1465
Brad Bishop316dfdd2018-06-25 12:45:53 -04001466 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1467 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001468
Brad Bishop316dfdd2018-06-25 12:45:53 -04001469def _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 -05001470 """Implement the 'patch' mode of update-recipe"""
1471 import bb
1472 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001473
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001474 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001475 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001476 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001477 if not os.path.exists(append):
1478 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001479 recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001480
Brad Bishop316dfdd2018-06-25 12:45:53 -04001481 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 -05001482 if not initial_rev:
1483 raise DevtoolError('Unable to find initial revision - please specify '
1484 'it with --initial-rev')
1485
Brad Bishop316dfdd2018-06-25 12:45:53 -04001486 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001487 dl_dir = rd.getVar('DL_DIR')
1488 if not dl_dir.endswith('/'):
1489 dl_dir += '/'
1490
Brad Bishop316dfdd2018-06-25 12:45:53 -04001491 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1492
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001493 tempdir = tempfile.mkdtemp(prefix='devtool')
1494 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001495 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001496 if filter_patches:
1497 upd_f = {}
1498 new_f = {}
1499 del_f = {}
1500 else:
1501 srctreebase = workspace[recipename]['srctreebase']
1502 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001503
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001504 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001505 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001506 # Get all patches from source tree and check if any should be removed
1507 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001508 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1509 all_patches_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001510 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001511 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001512
1513 # Get updated patches from source tree
1514 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001515 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1516 patches_dir, changed_revs)
1517 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1518 if filter_patches:
1519 new_p = {}
1520 upd_p = {k:v for k,v in upd_p.items() if k in filter_patches}
1521 remove_files = [f for f in remove_files if f in filter_patches]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001522 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001523 updaterecipe = False
1524 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001525 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001526 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001527 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001528 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001529 files.update(dict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001530 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001531 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001532 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001533 if remove_files:
1534 removedentries, remaining = _remove_file_entries(
1535 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001536 if removedentries or remaining:
1537 remaining = ['file://' + os.path.basename(item) for
1538 item in remaining]
1539 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001540 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001541 rd, appendlayerdir, files,
1542 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001543 removevalues=removevalues,
1544 redirect_output=dry_run_outdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001545 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001546 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001547 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001548 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001549 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001550 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001551 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001552 if os.path.isabs(basepath):
1553 # Original file (probably with subdir pointing inside source tree)
1554 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001555 _copy_file(basepath, path,
1556 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001557 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001558 _move_file(os.path.join(local_files_dir, basepath), path,
1559 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001560 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001561 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001562 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001563 if os.path.dirname(path) + '/' == dl_dir:
1564 # This is a a downloaded patch file - we now need to
1565 # replace the entry in SRC_URI with our local version
1566 logger.info('Replacing remote patch %s with updated local version' % basepath)
1567 path = os.path.join(files_dir, basepath)
1568 _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath)
1569 updaterecipe = True
1570 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001571 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1572 _move_file(patchfn, path,
1573 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001574 updatefiles = True
1575 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001576 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001577 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001578 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001579 os.path.join(files_dir, basepath),
1580 dry_run_outdir=dry_run_outdir,
1581 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001582 srcuri.append('file://%s' % basepath)
1583 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001584 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001585 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001586 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001587 os.path.join(files_dir, basepath),
1588 dry_run_outdir=dry_run_outdir,
1589 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001590 srcuri.append('file://%s' % basepath)
1591 updaterecipe = True
1592 # Update recipe, if needed
1593 if _remove_file_entries(srcuri, remove_files)[0]:
1594 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001595 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001596 if not dry_run_outdir:
1597 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1598 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1599 {'SRC_URI': ' '.join(srcuri)},
1600 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001601 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001602 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001603 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001604 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001605 finally:
1606 shutil.rmtree(tempdir)
1607
Brad Bishop316dfdd2018-06-25 12:45:53 -04001608 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1609 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001610
1611def _guess_recipe_update_mode(srctree, rdata):
1612 """Guess the recipe update mode to use"""
1613 src_uri = (rdata.getVar('SRC_URI', False) or '').split()
1614 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1615 if not git_uris:
1616 return 'patch'
1617 # Just use the first URI for now
1618 uri = git_uris[0]
1619 # Check remote branch
1620 params = bb.fetch.decodeurl(uri)[5]
1621 upstr_branch = params['branch'] if 'branch' in params else 'master'
1622 # Check if current branch HEAD is found in upstream branch
1623 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1624 head_rev = stdout.rstrip()
1625 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1626 cwd=srctree)
1627 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1628 if 'origin/' + upstr_branch in remote_brs:
1629 return 'srcrev'
1630
1631 return 'patch'
1632
Brad Bishop316dfdd2018-06-25 12:45:53 -04001633def _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 -06001634 srctree = workspace[recipename]['srctree']
1635 if mode == 'auto':
1636 mode = _guess_recipe_update_mode(srctree, rd)
1637
Brad Bishop316dfdd2018-06-25 12:45:53 -04001638 override_branches = []
1639 mainbranch = None
1640 startbranch = None
1641 if not no_overrides:
1642 stdout, _ = bb.process.run('git branch', cwd=srctree)
1643 other_branches = []
1644 for line in stdout.splitlines():
1645 branchname = line[2:]
1646 if line.startswith('* '):
1647 startbranch = branchname
1648 if branchname.startswith(override_branch_prefix):
1649 override_branches.append(branchname)
1650 else:
1651 other_branches.append(branchname)
1652
1653 if override_branches:
1654 logger.debug('_update_recipe: override branches: %s' % override_branches)
1655 logger.debug('_update_recipe: other branches: %s' % other_branches)
1656 if startbranch.startswith(override_branch_prefix):
1657 if len(other_branches) == 1:
1658 mainbranch = other_branches[1]
1659 else:
1660 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1661 else:
1662 mainbranch = startbranch
1663
1664 checkedout = None
1665 anyupdated = False
1666 appendfile = None
1667 allremoved = []
1668 if override_branches:
1669 logger.info('Handling main branch (%s)...' % mainbranch)
1670 if startbranch != mainbranch:
1671 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1672 checkedout = mainbranch
1673 try:
1674 branchlist = [mainbranch] + override_branches
1675 for branch in branchlist:
1676 crd = bb.data.createCopy(rd)
1677 if branch != mainbranch:
1678 logger.info('Handling branch %s...' % branch)
1679 override = branch[len(override_branch_prefix):]
1680 crd.appendVar('OVERRIDES', ':%s' % override)
1681 bb.process.run('git checkout %s' % branch, cwd=srctree)
1682 checkedout = branch
1683
1684 if mode == 'srcrev':
1685 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1686 elif mode == 'patch':
1687 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)
1688 else:
1689 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1690 if updated:
1691 anyupdated = True
1692 if appendf:
1693 appendfile = appendf
1694 allremoved.extend(removed)
1695 finally:
1696 if startbranch and checkedout != startbranch:
1697 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1698
1699 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001700
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001701def update_recipe(args, config, basepath, workspace):
1702 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001703 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001704
1705 if args.append:
1706 if not os.path.exists(args.append):
1707 raise DevtoolError('bbappend destination layer directory "%s" '
1708 'does not exist' % args.append)
1709 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1710 raise DevtoolError('conf/layer.conf not found in bbappend '
1711 'destination layer "%s"' % args.append)
1712
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001713 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001714 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001715
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001716 rd = parse_recipe(config, tinfoil, args.recipename, True)
1717 if not rd:
1718 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001719
Brad Bishop316dfdd2018-06-25 12:45:53 -04001720 dry_run_output = None
1721 dry_run_outdir = None
1722 if args.dry_run:
1723 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1724 dry_run_outdir = dry_run_output.name
1725 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 -05001726
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001727 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001728 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001729 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001730 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 -06001731 finally:
1732 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001733
1734 return 0
1735
1736
1737def status(args, config, basepath, workspace):
1738 """Entry point for the devtool 'status' subcommand"""
1739 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001740 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001741 recipefile = value['recipefile']
1742 if recipefile:
1743 recipestr = ' (%s)' % recipefile
1744 else:
1745 recipestr = ''
1746 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001747 else:
1748 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')
1749 return 0
1750
1751
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001752def _reset(recipes, no_clean, config, basepath, workspace):
1753 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001754 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001755
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001756 def clean_preferred_provider(pn, layerconf_path):
1757 """Remove PREFERRED_PROVIDER from layer.conf'"""
1758 import re
1759 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1760 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1761 pprovider_found = False
1762 with open(layerconf_file, 'r') as f:
1763 lines = f.readlines()
1764 with open(new_layerconf_file, 'a') as nf:
1765 for line in lines:
1766 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1767 if not re.match(pprovider_exp, line):
1768 nf.write(line)
1769 else:
1770 pprovider_found = True
1771 if pprovider_found:
1772 shutil.move(new_layerconf_file, layerconf_file)
1773 else:
1774 os.remove(new_layerconf_file)
1775
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001776 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001777 if len(recipes) == 1:
1778 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1779 else:
1780 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001781 # If the recipe file itself was created in the workspace, and
1782 # it uses BBCLASSEXTEND, then we need to also clean the other
1783 # variants
1784 targets = []
1785 for recipe in recipes:
1786 targets.append(recipe)
1787 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001788 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001789 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001790 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001791 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001792 except bb.process.ExecutionError as e:
1793 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1794 'wish, you may specify -n/--no-clean to '
1795 'skip running this command when resetting' %
1796 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001797
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001798 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001799 _check_preserve(config, pn)
1800
Brad Bishop316dfdd2018-06-25 12:45:53 -04001801 appendfile = workspace[pn]['bbappend']
1802 if os.path.exists(appendfile):
1803 # This shouldn't happen, but is possible if devtool errored out prior to
1804 # writing the md5 file. We need to delete this here or the recipe won't
1805 # actually be reset
1806 os.remove(appendfile)
1807
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001808 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001809 def preservedir(origdir):
1810 if os.path.exists(origdir):
1811 for root, dirs, files in os.walk(origdir):
1812 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001813 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001814 _move_file(os.path.join(origdir, fn),
1815 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001816 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001817 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001818 os.rmdir(origdir)
1819
Brad Bishop316dfdd2018-06-25 12:45:53 -04001820 recipefile = workspace[pn]['recipefile']
1821 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1822 # This should always be true if recipefile is set, but just in case
1823 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001824 # We don't automatically create this dir next to appends, but the user can
1825 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1826
Brad Bishop316dfdd2018-06-25 12:45:53 -04001827 srctreebase = workspace[pn]['srctreebase']
1828 if os.path.isdir(srctreebase):
1829 if os.listdir(srctreebase):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001830 # We don't want to risk wiping out any work in progress
1831 logger.info('Leaving source tree %s as-is; if you no '
1832 'longer need it then please delete it manually'
Brad Bishop316dfdd2018-06-25 12:45:53 -04001833 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001834 else:
1835 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04001836 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001837
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001838 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001839
1840def reset(args, config, basepath, workspace):
1841 """Entry point for the devtool 'reset' subcommand"""
1842 import bb
1843 if args.recipename:
1844 if args.all:
1845 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1846 else:
1847 for recipe in args.recipename:
1848 check_workspace_recipe(workspace, recipe, checksrc=False)
1849 elif not args.all:
1850 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1851 "reset all recipes")
1852 if args.all:
1853 recipes = list(workspace.keys())
1854 else:
1855 recipes = args.recipename
1856
1857 _reset(recipes, args.no_clean, config, basepath, workspace)
1858
1859 return 0
1860
1861
1862def _get_layer(layername, d):
1863 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001864 layerdirs = d.getVar('BBLAYERS').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001865 layers = {os.path.basename(p): p for p in layerdirs}
1866 # Provide some shortcuts
1867 if layername.lower() in ['oe-core', 'openembedded-core']:
1868 layerdir = layers.get('meta', None)
1869 else:
1870 layerdir = layers.get(layername, None)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001871 return os.path.abspath(layerdir or layername)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001872
1873def finish(args, config, basepath, workspace):
1874 """Entry point for the devtool 'finish' subcommand"""
1875 import bb
1876 import oe.recipeutils
1877
1878 check_workspace_recipe(workspace, args.recipename)
1879
Brad Bishop316dfdd2018-06-25 12:45:53 -04001880 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
1881
1882 # Grab the equivalent of COREBASE without having to initialise tinfoil
1883 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
1884
1885 srctree = workspace[args.recipename]['srctree']
1886 check_git_repo_op(srctree, [corebasedir])
1887 dirty = check_git_repo_dirty(srctree)
1888 if dirty:
1889 if args.force:
1890 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
1891 else:
1892 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)
1893
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001894 no_clean = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001895 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1896 try:
1897 rd = parse_recipe(config, tinfoil, args.recipename, True)
1898 if not rd:
1899 return 1
1900
1901 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001902 recipefile = rd.getVar('FILE')
1903 recipedir = os.path.dirname(recipefile)
1904 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001905
1906 if not os.path.isdir(destlayerdir):
1907 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
1908
1909 if os.path.abspath(destlayerdir) == config.workspace_path:
1910 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
1911
1912 # If it's an upgrade, grab the original path
1913 origpath = None
1914 origfilelist = None
1915 append = workspace[args.recipename]['bbappend']
1916 with open(append, 'r') as f:
1917 for line in f:
1918 if line.startswith('# original_path:'):
1919 origpath = line.split(':')[1].strip()
1920 elif line.startswith('# original_files:'):
1921 origfilelist = line.split(':')[1].split()
1922
Brad Bishop316dfdd2018-06-25 12:45:53 -04001923 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
1924
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001925 if origlayerdir == config.workspace_path:
1926 # Recipe file itself is in workspace, update it there first
1927 appendlayerdir = None
1928 origrelpath = None
1929 if origpath:
1930 origlayerpath = oe.recipeutils.find_layerdir(origpath)
1931 if origlayerpath:
1932 origrelpath = os.path.relpath(origpath, origlayerpath)
1933 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
1934 if not destpath:
1935 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 -05001936 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
1937 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04001938 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001939 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)
1940
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001941 elif destlayerdir == origlayerdir:
1942 # Same layer, update the original recipe
1943 appendlayerdir = None
1944 destpath = None
1945 else:
1946 # Create/update a bbappend in the specified layer
1947 appendlayerdir = destlayerdir
1948 destpath = None
1949
Brad Bishop316dfdd2018-06-25 12:45:53 -04001950 # Actually update the recipe / bbappend
1951 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
1952 dry_run_output = None
1953 dry_run_outdir = None
1954 if args.dry_run:
1955 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1956 dry_run_outdir = dry_run_output.name
1957 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)
1958 removed = [os.path.relpath(pth, recipedir) for pth in removed]
1959
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001960 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04001961 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001962 for fn in origfilelist:
1963 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001964 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
1965 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
1966 if not args.dry_run:
1967 try:
1968 os.remove(fnp)
1969 except FileNotFoundError:
1970 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001971
1972 if origlayerdir == config.workspace_path and destpath:
1973 # Recipe file itself is in the workspace - need to move it and any
1974 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001975 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04001976 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001977 for root, _, files in os.walk(recipedir):
1978 for fn in files:
1979 srcpath = os.path.join(root, fn)
1980 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
1981 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001982 destfp = os.path.join(destdir, fn)
1983 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001984
Brad Bishop316dfdd2018-06-25 12:45:53 -04001985 if dry_run_outdir:
1986 import difflib
1987 comparelist = []
1988 for root, _, files in os.walk(dry_run_outdir):
1989 for fn in files:
1990 outf = os.path.join(root, fn)
1991 relf = os.path.relpath(outf, dry_run_outdir)
1992 logger.debug('dry-run: output file %s' % relf)
1993 if fn.endswith('.bb'):
1994 if origfilelist and origpath and destpath:
1995 # Need to match this up with the pre-upgrade recipe file
1996 for origf in origfilelist:
1997 if origf.endswith('.bb'):
1998 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
1999 outf,
2000 os.path.abspath(os.path.join(destpath, relf))))
2001 break
2002 else:
2003 # Compare to the existing recipe
2004 comparelist.append((recipefile, outf, recipefile))
2005 elif fn.endswith('.bbappend'):
2006 if appendfile:
2007 if os.path.exists(appendfile):
2008 comparelist.append((appendfile, outf, appendfile))
2009 else:
2010 comparelist.append((None, outf, appendfile))
2011 else:
2012 if destpath:
2013 recipedest = destpath
2014 elif appendfile:
2015 recipedest = os.path.dirname(appendfile)
2016 else:
2017 recipedest = os.path.dirname(recipefile)
2018 destfp = os.path.join(recipedest, relf)
2019 if os.path.exists(destfp):
2020 comparelist.append((destfp, outf, destfp))
2021 output = ''
2022 for oldfile, newfile, newfileshow in comparelist:
2023 if oldfile:
2024 with open(oldfile, 'r') as f:
2025 oldlines = f.readlines()
2026 else:
2027 oldfile = '/dev/null'
2028 oldlines = []
2029 with open(newfile, 'r') as f:
2030 newlines = f.readlines()
2031 if not newfileshow:
2032 newfileshow = newfile
2033 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2034 difflines = list(diff)
2035 if difflines:
2036 output += ''.join(difflines)
2037 if output:
2038 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002039 finally:
2040 tinfoil.shutdown()
2041
2042 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002043 if args.dry_run:
2044 logger.info('Resetting recipe (dry-run)')
2045 else:
2046 _reset([args.recipename], no_clean=no_clean, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002047
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002048 return 0
2049
2050
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002051def get_default_srctree(config, recipename=''):
2052 """Get the default srctree path"""
2053 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2054 if recipename:
2055 return os.path.join(srctreeparent, 'sources', recipename)
2056 else:
2057 return os.path.join(srctreeparent, 'sources')
2058
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002059def register_commands(subparsers, context):
2060 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002061
2062 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002063 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002064 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.',
2065 group='starting', order=100)
2066 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.')
2067 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2068 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 -05002069 group = parser_add.add_mutually_exclusive_group()
2070 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2071 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002072 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 -05002073 parser_add.add_argument('--fetch-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002074 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002075 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 -05002076 group = parser_add.add_mutually_exclusive_group()
2077 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2078 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")
2079 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 -05002080 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')
2081 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')
2082 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002083 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2084 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 -06002085 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002086
2087 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002088 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.',
2089 group='starting', order=90)
2090 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2091 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 -05002092 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002093 group = parser_modify.add_mutually_exclusive_group()
2094 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2095 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 -05002096 group = parser_modify.add_mutually_exclusive_group()
2097 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2098 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002099 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 -04002100 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 -05002101 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002102 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002103
2104 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2105 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002106 group='advanced')
2107 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002108 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002109 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 -04002110 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 -05002111 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002112 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002113
2114 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2115 description='Synchronize the previously extracted source tree for an existing recipe',
2116 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2117 group='advanced')
2118 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2119 parser_sync.add_argument('srctree', help='Path to the source tree')
2120 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2121 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002122 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002123
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002124 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2125 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.',
2126 group='working', order=10)
2127 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2128 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2129 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)')
2130 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')
2131 parser_rename.set_defaults(func=rename)
2132
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002133 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002134 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.',
2135 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002136 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2137 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 -05002138 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002139 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2140 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')
2141 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 -04002142 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2143 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2144 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 -05002145 parser_update_recipe.set_defaults(func=update_recipe)
2146
2147 parser_status = subparsers.add_parser('status', help='Show workspace status',
2148 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002149 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002150 parser_status.set_defaults(func=status)
2151
2152 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002153 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 -05002154 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002155 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002156 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2157 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
2158 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002159
2160 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002161 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 -06002162 group='working', order=-100)
2163 parser_finish.add_argument('recipename', help='Recipe to finish')
2164 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.')
2165 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')
2166 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002167 parser_finish.add_argument('--force', '-f', action="store_true", help='Force continuing even if there are uncommitted changes in the source tree repository')
2168 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2169 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2170 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 -06002171 parser_finish.set_defaults(func=finish)