blob: b7d4d47dfc64979a35ee85b802257bc4790081e5 [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')
852 f.write('\ndo_patch() {\n'
853 ' :\n'
854 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600855 f.write('\ndo_configure_append() {\n'
856 ' cp ${B}/.config ${S}/.config.baseline\n'
857 ' ln -sfT ${B}/.config ${S}/.config.new\n'
858 '}\n')
859 if initial_rev:
860 f.write('\n# initial_rev: %s\n' % initial_rev)
861 for commit in commits:
862 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400863 if branch_patches:
864 for branch in branch_patches:
865 if branch == args.branch:
866 continue
867 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500868
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500869 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
870
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600871 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500872
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600873 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500874
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600875 finally:
876 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500877
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500878 return 0
879
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500880
881def rename(args, config, basepath, workspace):
882 """Entry point for the devtool 'rename' subcommand"""
883 import bb
884 import oe.recipeutils
885
886 check_workspace_recipe(workspace, args.recipename)
887
888 if not (args.newname or args.version):
889 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
890
891 recipefile = workspace[args.recipename]['recipefile']
892 if not recipefile:
893 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
894
895 if args.newname and args.newname != args.recipename:
896 reason = oe.recipeutils.validate_pn(args.newname)
897 if reason:
898 raise DevtoolError(reason)
899 newname = args.newname
900 else:
901 newname = args.recipename
902
903 append = workspace[args.recipename]['bbappend']
904 appendfn = os.path.splitext(os.path.basename(append))[0]
905 splitfn = appendfn.split('_')
906 if len(splitfn) > 1:
907 origfnver = appendfn.split('_')[1]
908 else:
909 origfnver = ''
910
911 recipefilemd5 = None
912 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
913 try:
914 rd = parse_recipe(config, tinfoil, args.recipename, True)
915 if not rd:
916 return 1
917
918 bp = rd.getVar('BP')
919 bpn = rd.getVar('BPN')
920 if newname != args.recipename:
921 localdata = rd.createCopy()
922 localdata.setVar('PN', newname)
923 newbpn = localdata.getVar('BPN')
924 else:
925 newbpn = bpn
926 s = rd.getVar('S', False)
927 src_uri = rd.getVar('SRC_URI', False)
928 pv = rd.getVar('PV')
929
930 # Correct variable values that refer to the upstream source - these
931 # values must stay the same, so if the name/version are changing then
932 # we need to fix them up
933 new_s = s
934 new_src_uri = src_uri
935 if newbpn != bpn:
936 # ${PN} here is technically almost always incorrect, but people do use it
937 new_s = new_s.replace('${BPN}', bpn)
938 new_s = new_s.replace('${PN}', bpn)
939 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
940 new_src_uri = new_src_uri.replace('${BPN}', bpn)
941 new_src_uri = new_src_uri.replace('${PN}', bpn)
942 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
943 if args.version and origfnver == pv:
944 new_s = new_s.replace('${PV}', pv)
945 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
946 new_src_uri = new_src_uri.replace('${PV}', pv)
947 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
948 patchfields = {}
949 if new_s != s:
950 patchfields['S'] = new_s
951 if new_src_uri != src_uri:
952 patchfields['SRC_URI'] = new_src_uri
953 if patchfields:
954 recipefilemd5 = bb.utils.md5_file(recipefile)
955 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
956 newrecipefilemd5 = bb.utils.md5_file(recipefile)
957 finally:
958 tinfoil.shutdown()
959
960 if args.version:
961 newver = args.version
962 else:
963 newver = origfnver
964
965 if newver:
966 newappend = '%s_%s.bbappend' % (newname, newver)
967 newfile = '%s_%s.bb' % (newname, newver)
968 else:
969 newappend = '%s.bbappend' % newname
970 newfile = '%s.bb' % newname
971
972 oldrecipedir = os.path.dirname(recipefile)
973 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
974 if oldrecipedir != newrecipedir:
975 bb.utils.mkdirhier(newrecipedir)
976
977 newappend = os.path.join(os.path.dirname(append), newappend)
978 newfile = os.path.join(newrecipedir, newfile)
979
980 # Rename bbappend
981 logger.info('Renaming %s to %s' % (append, newappend))
982 os.rename(append, newappend)
983 # Rename recipe file
984 logger.info('Renaming %s to %s' % (recipefile, newfile))
985 os.rename(recipefile, newfile)
986
987 # Rename source tree if it's the default path
988 appendmd5 = None
989 if not args.no_srctree:
990 srctree = workspace[args.recipename]['srctree']
991 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
992 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
993 logger.info('Renaming %s to %s' % (srctree, newsrctree))
994 shutil.move(srctree, newsrctree)
995 # Correct any references (basically EXTERNALSRC*) in the .bbappend
996 appendmd5 = bb.utils.md5_file(newappend)
997 appendlines = []
998 with open(newappend, 'r') as f:
999 for line in f:
1000 appendlines.append(line)
1001 with open(newappend, 'w') as f:
1002 for line in appendlines:
1003 if srctree in line:
1004 line = line.replace(srctree, newsrctree)
1005 f.write(line)
1006 newappendmd5 = bb.utils.md5_file(newappend)
1007
1008 bpndir = None
1009 newbpndir = None
1010 if newbpn != bpn:
1011 bpndir = os.path.join(oldrecipedir, bpn)
1012 if os.path.exists(bpndir):
1013 newbpndir = os.path.join(newrecipedir, newbpn)
1014 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1015 shutil.move(bpndir, newbpndir)
1016
1017 bpdir = None
1018 newbpdir = None
1019 if newver != origfnver or newbpn != bpn:
1020 bpdir = os.path.join(oldrecipedir, bp)
1021 if os.path.exists(bpdir):
1022 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1023 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1024 shutil.move(bpdir, newbpdir)
1025
1026 if oldrecipedir != newrecipedir:
1027 # Move any stray files and delete the old recipe directory
1028 for entry in os.listdir(oldrecipedir):
1029 oldpath = os.path.join(oldrecipedir, entry)
1030 newpath = os.path.join(newrecipedir, entry)
1031 logger.info('Renaming %s to %s' % (oldpath, newpath))
1032 shutil.move(oldpath, newpath)
1033 os.rmdir(oldrecipedir)
1034
1035 # Now take care of entries in .devtool_md5
1036 md5entries = []
1037 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1038 for line in f:
1039 md5entries.append(line)
1040
1041 if bpndir and newbpndir:
1042 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1043 else:
1044 relbpndir = None
1045 if bpdir and newbpdir:
1046 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1047 else:
1048 relbpdir = None
1049
1050 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1051 for entry in md5entries:
1052 splitentry = entry.rstrip().split('|')
1053 if len(splitentry) > 2:
1054 if splitentry[0] == args.recipename:
1055 splitentry[0] = newname
1056 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1057 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1058 if appendmd5 and splitentry[2] == appendmd5:
1059 splitentry[2] = newappendmd5
1060 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1061 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1062 if recipefilemd5 and splitentry[2] == recipefilemd5:
1063 splitentry[2] = newrecipefilemd5
1064 elif relbpndir and splitentry[1].startswith(relbpndir):
1065 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1066 elif relbpdir and splitentry[1].startswith(relbpdir):
1067 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1068 entry = '|'.join(splitentry) + '\n'
1069 f.write(entry)
1070 return 0
1071
1072
Brad Bishop316dfdd2018-06-25 12:45:53 -04001073def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001074 """Get initial and update rev of a recipe. These are the start point of the
1075 whole patchset and start point for the patches to be re-generated/updated.
1076 """
1077 import bb
1078
Brad Bishop316dfdd2018-06-25 12:45:53 -04001079 # Get current branch
1080 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1081 cwd=srctree)
1082 branchname = stdout.rstrip()
1083
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001084 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001085 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001086 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001087 with open(recipe_path, 'r') as f:
1088 for line in f:
1089 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001090 if not initial_rev:
1091 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001092 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001093 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001094 elif line.startswith('# patches_%s:' % branchname):
1095 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001096
1097 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001098 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001099 if initial_rev:
1100 # Find first actually changed revision
1101 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1102 initial_rev, cwd=srctree)
1103 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001104 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001105 if newcommits[i] == commits[i]:
1106 update_rev = commits[i]
1107
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001108 try:
1109 stdout, _ = bb.process.run('git cherry devtool-patched',
1110 cwd=srctree)
1111 except bb.process.ExecutionError as err:
1112 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001113
Brad Bishop316dfdd2018-06-25 12:45:53 -04001114 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001115 changed_revs = []
1116 for line in stdout.splitlines():
1117 if line.startswith('+ '):
1118 rev = line.split()[1]
1119 if rev in newcommits:
1120 changed_revs.append(rev)
1121
Brad Bishop316dfdd2018-06-25 12:45:53 -04001122 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001123
1124def _remove_file_entries(srcuri, filelist):
1125 """Remove file:// entries from SRC_URI"""
1126 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001127 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001128 for fname in filelist:
1129 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001130 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001131 if (srcuri[i].startswith('file://') and
1132 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001133 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001134 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001135 srcuri.pop(i)
1136 break
1137 return entries, remaining
1138
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001139def _replace_srcuri_entry(srcuri, filename, newentry):
1140 """Replace entry corresponding to specified file with a new entry"""
1141 basename = os.path.basename(filename)
1142 for i in range(len(srcuri)):
1143 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1144 srcuri.pop(i)
1145 srcuri.insert(i, newentry)
1146 break
1147
Brad Bishop316dfdd2018-06-25 12:45:53 -04001148def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001149 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001150
1151 dry_run_suffix = ' (dry-run)' if dry_run else ''
1152
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001153 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001154 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001155 if not destpath:
1156 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001157 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001158
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001159 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001160 if not no_report_remove:
1161 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1162 if not dry_run:
1163 # FIXME "git rm" here would be nice if the file in question is
1164 # tracked
1165 # FIXME there's a chance that this file is referred to by
1166 # another recipe, in which case deleting wouldn't be the
1167 # right thing to do
1168 os.remove(path)
1169 # Remove directory if empty
1170 try:
1171 os.rmdir(os.path.dirname(path))
1172 except OSError as ose:
1173 if ose.errno != errno.ENOTEMPTY:
1174 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001175
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001176
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001177def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001178 """Export patches from srctree to given location.
1179 Returns three-tuple of dicts:
1180 1. updated - patches that already exist in SRCURI
1181 2. added - new patches that don't exist in SRCURI
1182 3 removed - patches that exist in SRCURI but not in exported patches
1183 In each dict the key is the 'basepath' of the URI and value is the
1184 absolute path to the existing file in recipe space (if any).
1185 """
1186 import oe.recipeutils
1187 from oe.patch import GitApplyTree
1188 updated = OrderedDict()
1189 added = OrderedDict()
1190 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1191
1192 existing_patches = dict((os.path.basename(path), path) for path in
1193 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001194 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001195
1196 # Generate patches from Git, exclude local files directory
1197 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1198 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1199
1200 new_patches = sorted(os.listdir(destdir))
1201 for new_patch in new_patches:
1202 # Strip numbering from patch names. If it's a git sequence named patch,
1203 # the numbers might not match up since we are starting from a different
1204 # revision This does assume that people are using unique shortlog
1205 # values, but they ought to be anyway...
1206 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001207 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001208 for old_patch in existing_patches:
1209 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001210 old_basename_splitext = os.path.splitext(old_basename)
1211 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1212 old_patch_noext = os.path.splitext(old_patch)[0]
1213 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001214 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001215 elif new_basename == old_basename:
1216 match_name = old_patch
1217 break
1218 if match_name:
1219 # Rename patch files
1220 if new_patch != match_name:
1221 os.rename(os.path.join(destdir, new_patch),
1222 os.path.join(destdir, match_name))
1223 # Need to pop it off the list now before checking changed_revs
1224 oldpath = existing_patches.pop(old_patch)
1225 if changed_revs is not None:
1226 # Avoid updating patches that have not actually changed
1227 with open(os.path.join(destdir, match_name), 'r') as f:
1228 firstlineitems = f.readline().split()
1229 # Looking for "From <hash>" line
1230 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1231 if not firstlineitems[1] in changed_revs:
1232 continue
1233 # Recompress if necessary
1234 if oldpath.endswith(('.gz', '.Z')):
1235 bb.process.run(['gzip', match_name], cwd=destdir)
1236 if oldpath.endswith('.gz'):
1237 match_name += '.gz'
1238 else:
1239 match_name += '.Z'
1240 elif oldpath.endswith('.bz2'):
1241 bb.process.run(['bzip2', match_name], cwd=destdir)
1242 match_name += '.bz2'
1243 updated[match_name] = oldpath
1244 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001245 added[new_patch] = None
1246 return (updated, added, existing_patches)
1247
1248
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001249def _create_kconfig_diff(srctree, rd, outfile):
1250 """Create a kconfig fragment"""
1251 # Only update config fragment if both config files exist
1252 orig_config = os.path.join(srctree, '.config.baseline')
1253 new_config = os.path.join(srctree, '.config.new')
1254 if os.path.exists(orig_config) and os.path.exists(new_config):
1255 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1256 '--unchanged-line-format=', orig_config, new_config]
1257 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1258 stderr=subprocess.PIPE)
1259 stdout, stderr = pipe.communicate()
1260 if pipe.returncode == 1:
1261 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001262 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001263 fobj.write(stdout)
1264 elif pipe.returncode == 0:
1265 logger.info("Would remove config fragment %s" % outfile)
1266 if os.path.exists(outfile):
1267 # Remove fragment file in case of empty diff
1268 logger.info("Removing config fragment %s" % outfile)
1269 os.unlink(outfile)
1270 else:
1271 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1272 return True
1273 return False
1274
1275
Brad Bishop316dfdd2018-06-25 12:45:53 -04001276def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001277 """Copy local files from srctree to given location.
1278 Returns three-tuple of dicts:
1279 1. updated - files that already exist in SRCURI
1280 2. added - new files files that don't exist in SRCURI
1281 3 removed - files that exist in SRCURI but not in exported files
1282 In each dict the key is the 'basepath' of the URI and value is the
1283 absolute path to the existing file in recipe space (if any).
1284 """
1285 import oe.recipeutils
1286
1287 # Find out local files (SRC_URI files that exist in the "recipe space").
1288 # Local files that reside in srctree are not included in patch generation.
1289 # Instead they are directly copied over the original source files (in
1290 # recipe space).
1291 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1292 new_set = None
1293 updated = OrderedDict()
1294 added = OrderedDict()
1295 removed = OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001296 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001297 git_files = _git_ls_tree(srctree)
1298 if 'oe-local-files' in git_files:
1299 # If tracked by Git, take the files from srctree HEAD. First get
1300 # the tree object of the directory
1301 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1302 tree = git_files['oe-local-files'][2]
1303 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1304 env=dict(os.environ, GIT_WORK_TREE=destdir,
1305 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001306 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001307 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001308 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001309 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001310 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001311 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001312 else:
1313 new_set = []
1314
1315 # Special handling for kernel config
1316 if bb.data.inherits_class('kernel-yocto', rd):
1317 fragment_fn = 'devtool-fragment.cfg'
1318 fragment_path = os.path.join(destdir, fragment_fn)
1319 if _create_kconfig_diff(srctree, rd, fragment_path):
1320 if os.path.exists(fragment_path):
1321 if fragment_fn not in new_set:
1322 new_set.append(fragment_fn)
1323 # Copy fragment to local-files
1324 if os.path.isdir(local_files_dir):
1325 shutil.copy2(fragment_path, local_files_dir)
1326 else:
1327 if fragment_fn in new_set:
1328 new_set.remove(fragment_fn)
1329 # Remove fragment from local-files
1330 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1331 os.unlink(os.path.join(local_files_dir, fragment_fn))
1332
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001333 if new_set is not None:
1334 for fname in new_set:
1335 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001336 origpath = existing_files.pop(fname)
1337 workpath = os.path.join(local_files_dir, fname)
1338 if not filecmp.cmp(origpath, workpath):
1339 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001340 elif fname != '.gitignore':
1341 added[fname] = None
1342
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001343 workdir = rd.getVar('WORKDIR')
1344 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001345 if not s.endswith(os.sep):
1346 s += os.sep
1347
1348 if workdir != s:
1349 # Handle files where subdir= was specified
1350 for fname in list(existing_files.keys()):
1351 # FIXME handle both subdir starting with BP and not?
1352 fworkpath = os.path.join(workdir, fname)
1353 if fworkpath.startswith(s):
1354 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1355 if os.path.exists(fpath):
1356 origpath = existing_files.pop(fname)
1357 if not filecmp.cmp(origpath, fpath):
1358 updated[fpath] = origpath
1359
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001360 removed = existing_files
1361 return (updated, added, removed)
1362
1363
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001364def _determine_files_dir(rd):
1365 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001366 recipedir = rd.getVar('FILE_DIRNAME')
1367 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001368 relpth = os.path.relpath(entry, recipedir)
1369 if not os.sep in relpth:
1370 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1371 if os.path.isdir(entry):
1372 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001373 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001374
1375
Brad Bishop316dfdd2018-06-25 12:45:53 -04001376def _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 -05001377 """Implement the 'srcrev' mode of update-recipe"""
1378 import bb
1379 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001380
Brad Bishop316dfdd2018-06-25 12:45:53 -04001381 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1382
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001383 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001384 recipedir = os.path.basename(recipefile)
1385 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001386
1387 # Get HEAD revision
1388 try:
1389 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1390 except bb.process.ExecutionError as err:
1391 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1392 (srctree, err))
1393 srcrev = stdout.strip()
1394 if len(srcrev) != 40:
1395 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1396
1397 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001398 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001399 patchfields = {}
1400 patchfields['SRCREV'] = srcrev
1401 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001402 srcuri = orig_src_uri.split()
1403 tempdir = tempfile.mkdtemp(prefix='devtool')
1404 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001405 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001406 try:
1407 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001408 srctreebase = workspace[recipename]['srctreebase']
1409 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001410 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001411 # Find list of existing patches in recipe file
1412 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001413 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001414 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1415 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001416 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 -05001417
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001418 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001419 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001420 if remove_files:
1421 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1422 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001423
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001424 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001425 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001426 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001427 removevalues = {}
1428 if update_srcuri:
1429 removevalues = {'SRC_URI': removedentries}
1430 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001431 if dry_run_outdir:
1432 logger.info('Creating bbappend (dry-run)')
1433 else:
1434 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1435 rd, appendlayerdir, files, wildcardver=wildcard_version,
1436 extralines=patchfields, removevalues=removevalues,
1437 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001438 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001439 files_dir = _determine_files_dir(rd)
1440 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001441 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001442 if os.path.isabs(basepath):
1443 # Original file (probably with subdir pointing inside source tree)
1444 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001445 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001446 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001447 _move_file(os.path.join(local_files_dir, basepath), path,
1448 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001449 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001450 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001451 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001452 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001453 os.path.join(files_dir, basepath),
1454 dry_run_outdir=dry_run_outdir,
1455 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001456 srcuri.append('file://%s' % basepath)
1457 update_srcuri = True
1458 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001459 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001460 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001461 finally:
1462 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001463 if not 'git://' in orig_src_uri:
1464 logger.info('You will need to update SRC_URI within the recipe to '
1465 'point to a git repository where you have pushed your '
1466 'changes')
1467
Brad Bishop316dfdd2018-06-25 12:45:53 -04001468 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1469 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001470
Brad Bishop316dfdd2018-06-25 12:45:53 -04001471def _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 -05001472 """Implement the 'patch' mode of update-recipe"""
1473 import bb
1474 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001475
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001476 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001477 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001478 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001479 if not os.path.exists(append):
1480 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001481 recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001482
Brad Bishop316dfdd2018-06-25 12:45:53 -04001483 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 -05001484 if not initial_rev:
1485 raise DevtoolError('Unable to find initial revision - please specify '
1486 'it with --initial-rev')
1487
Brad Bishop316dfdd2018-06-25 12:45:53 -04001488 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001489 dl_dir = rd.getVar('DL_DIR')
1490 if not dl_dir.endswith('/'):
1491 dl_dir += '/'
1492
Brad Bishop316dfdd2018-06-25 12:45:53 -04001493 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1494
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001495 tempdir = tempfile.mkdtemp(prefix='devtool')
1496 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001497 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001498 if filter_patches:
1499 upd_f = {}
1500 new_f = {}
1501 del_f = {}
1502 else:
1503 srctreebase = workspace[recipename]['srctreebase']
1504 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001505
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001506 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001507 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001508 # Get all patches from source tree and check if any should be removed
1509 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001510 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1511 all_patches_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001512 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001513 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001514
1515 # Get updated patches from source tree
1516 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001517 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1518 patches_dir, changed_revs)
1519 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1520 if filter_patches:
1521 new_p = {}
1522 upd_p = {k:v for k,v in upd_p.items() if k in filter_patches}
1523 remove_files = [f for f in remove_files if f in filter_patches]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001524 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001525 updaterecipe = False
1526 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001527 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001528 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001529 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001530 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001531 files.update(dict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001532 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001533 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001534 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001535 if remove_files:
1536 removedentries, remaining = _remove_file_entries(
1537 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001538 if removedentries or remaining:
1539 remaining = ['file://' + os.path.basename(item) for
1540 item in remaining]
1541 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001542 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001543 rd, appendlayerdir, files,
1544 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001545 removevalues=removevalues,
1546 redirect_output=dry_run_outdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001547 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001548 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001549 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001550 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001551 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001552 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001553 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001554 if os.path.isabs(basepath):
1555 # Original file (probably with subdir pointing inside source tree)
1556 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001557 _copy_file(basepath, path,
1558 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001559 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001560 _move_file(os.path.join(local_files_dir, basepath), path,
1561 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001562 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001563 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001564 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001565 if os.path.dirname(path) + '/' == dl_dir:
1566 # This is a a downloaded patch file - we now need to
1567 # replace the entry in SRC_URI with our local version
1568 logger.info('Replacing remote patch %s with updated local version' % basepath)
1569 path = os.path.join(files_dir, basepath)
1570 _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath)
1571 updaterecipe = True
1572 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001573 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1574 _move_file(patchfn, path,
1575 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001576 updatefiles = True
1577 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001578 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001579 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001580 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001581 os.path.join(files_dir, basepath),
1582 dry_run_outdir=dry_run_outdir,
1583 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001584 srcuri.append('file://%s' % basepath)
1585 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001586 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001587 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001588 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001589 os.path.join(files_dir, basepath),
1590 dry_run_outdir=dry_run_outdir,
1591 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001592 srcuri.append('file://%s' % basepath)
1593 updaterecipe = True
1594 # Update recipe, if needed
1595 if _remove_file_entries(srcuri, remove_files)[0]:
1596 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001597 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001598 if not dry_run_outdir:
1599 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1600 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1601 {'SRC_URI': ' '.join(srcuri)},
1602 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001603 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001604 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001605 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001606 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001607 finally:
1608 shutil.rmtree(tempdir)
1609
Brad Bishop316dfdd2018-06-25 12:45:53 -04001610 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1611 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001612
1613def _guess_recipe_update_mode(srctree, rdata):
1614 """Guess the recipe update mode to use"""
1615 src_uri = (rdata.getVar('SRC_URI', False) or '').split()
1616 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1617 if not git_uris:
1618 return 'patch'
1619 # Just use the first URI for now
1620 uri = git_uris[0]
1621 # Check remote branch
1622 params = bb.fetch.decodeurl(uri)[5]
1623 upstr_branch = params['branch'] if 'branch' in params else 'master'
1624 # Check if current branch HEAD is found in upstream branch
1625 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1626 head_rev = stdout.rstrip()
1627 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1628 cwd=srctree)
1629 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1630 if 'origin/' + upstr_branch in remote_brs:
1631 return 'srcrev'
1632
1633 return 'patch'
1634
Brad Bishop316dfdd2018-06-25 12:45:53 -04001635def _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 -06001636 srctree = workspace[recipename]['srctree']
1637 if mode == 'auto':
1638 mode = _guess_recipe_update_mode(srctree, rd)
1639
Brad Bishop316dfdd2018-06-25 12:45:53 -04001640 override_branches = []
1641 mainbranch = None
1642 startbranch = None
1643 if not no_overrides:
1644 stdout, _ = bb.process.run('git branch', cwd=srctree)
1645 other_branches = []
1646 for line in stdout.splitlines():
1647 branchname = line[2:]
1648 if line.startswith('* '):
1649 startbranch = branchname
1650 if branchname.startswith(override_branch_prefix):
1651 override_branches.append(branchname)
1652 else:
1653 other_branches.append(branchname)
1654
1655 if override_branches:
1656 logger.debug('_update_recipe: override branches: %s' % override_branches)
1657 logger.debug('_update_recipe: other branches: %s' % other_branches)
1658 if startbranch.startswith(override_branch_prefix):
1659 if len(other_branches) == 1:
1660 mainbranch = other_branches[1]
1661 else:
1662 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1663 else:
1664 mainbranch = startbranch
1665
1666 checkedout = None
1667 anyupdated = False
1668 appendfile = None
1669 allremoved = []
1670 if override_branches:
1671 logger.info('Handling main branch (%s)...' % mainbranch)
1672 if startbranch != mainbranch:
1673 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1674 checkedout = mainbranch
1675 try:
1676 branchlist = [mainbranch] + override_branches
1677 for branch in branchlist:
1678 crd = bb.data.createCopy(rd)
1679 if branch != mainbranch:
1680 logger.info('Handling branch %s...' % branch)
1681 override = branch[len(override_branch_prefix):]
1682 crd.appendVar('OVERRIDES', ':%s' % override)
1683 bb.process.run('git checkout %s' % branch, cwd=srctree)
1684 checkedout = branch
1685
1686 if mode == 'srcrev':
1687 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1688 elif mode == 'patch':
1689 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)
1690 else:
1691 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1692 if updated:
1693 anyupdated = True
1694 if appendf:
1695 appendfile = appendf
1696 allremoved.extend(removed)
1697 finally:
1698 if startbranch and checkedout != startbranch:
1699 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1700
1701 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001702
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001703def update_recipe(args, config, basepath, workspace):
1704 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001705 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001706
1707 if args.append:
1708 if not os.path.exists(args.append):
1709 raise DevtoolError('bbappend destination layer directory "%s" '
1710 'does not exist' % args.append)
1711 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1712 raise DevtoolError('conf/layer.conf not found in bbappend '
1713 'destination layer "%s"' % args.append)
1714
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001715 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001716 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001717
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001718 rd = parse_recipe(config, tinfoil, args.recipename, True)
1719 if not rd:
1720 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001721
Brad Bishop316dfdd2018-06-25 12:45:53 -04001722 dry_run_output = None
1723 dry_run_outdir = None
1724 if args.dry_run:
1725 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1726 dry_run_outdir = dry_run_output.name
1727 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 -05001728
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001729 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001730 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001731 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001732 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 -06001733 finally:
1734 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001735
1736 return 0
1737
1738
1739def status(args, config, basepath, workspace):
1740 """Entry point for the devtool 'status' subcommand"""
1741 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001742 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001743 recipefile = value['recipefile']
1744 if recipefile:
1745 recipestr = ' (%s)' % recipefile
1746 else:
1747 recipestr = ''
1748 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001749 else:
1750 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')
1751 return 0
1752
1753
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001754def _reset(recipes, no_clean, config, basepath, workspace):
1755 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001756 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001757
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001758 def clean_preferred_provider(pn, layerconf_path):
1759 """Remove PREFERRED_PROVIDER from layer.conf'"""
1760 import re
1761 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1762 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1763 pprovider_found = False
1764 with open(layerconf_file, 'r') as f:
1765 lines = f.readlines()
1766 with open(new_layerconf_file, 'a') as nf:
1767 for line in lines:
1768 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1769 if not re.match(pprovider_exp, line):
1770 nf.write(line)
1771 else:
1772 pprovider_found = True
1773 if pprovider_found:
1774 shutil.move(new_layerconf_file, layerconf_file)
1775 else:
1776 os.remove(new_layerconf_file)
1777
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001778 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001779 if len(recipes) == 1:
1780 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1781 else:
1782 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001783 # If the recipe file itself was created in the workspace, and
1784 # it uses BBCLASSEXTEND, then we need to also clean the other
1785 # variants
1786 targets = []
1787 for recipe in recipes:
1788 targets.append(recipe)
1789 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001790 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001791 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001792 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001793 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001794 except bb.process.ExecutionError as e:
1795 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1796 'wish, you may specify -n/--no-clean to '
1797 'skip running this command when resetting' %
1798 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001799
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001800 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001801 _check_preserve(config, pn)
1802
Brad Bishop316dfdd2018-06-25 12:45:53 -04001803 appendfile = workspace[pn]['bbappend']
1804 if os.path.exists(appendfile):
1805 # This shouldn't happen, but is possible if devtool errored out prior to
1806 # writing the md5 file. We need to delete this here or the recipe won't
1807 # actually be reset
1808 os.remove(appendfile)
1809
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001810 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001811 def preservedir(origdir):
1812 if os.path.exists(origdir):
1813 for root, dirs, files in os.walk(origdir):
1814 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001815 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001816 _move_file(os.path.join(origdir, fn),
1817 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001818 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001819 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001820 os.rmdir(origdir)
1821
Brad Bishop316dfdd2018-06-25 12:45:53 -04001822 recipefile = workspace[pn]['recipefile']
1823 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1824 # This should always be true if recipefile is set, but just in case
1825 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001826 # We don't automatically create this dir next to appends, but the user can
1827 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1828
Brad Bishop316dfdd2018-06-25 12:45:53 -04001829 srctreebase = workspace[pn]['srctreebase']
1830 if os.path.isdir(srctreebase):
1831 if os.listdir(srctreebase):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001832 # We don't want to risk wiping out any work in progress
1833 logger.info('Leaving source tree %s as-is; if you no '
1834 'longer need it then please delete it manually'
Brad Bishop316dfdd2018-06-25 12:45:53 -04001835 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001836 else:
1837 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04001838 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001839
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001840 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001841
1842def reset(args, config, basepath, workspace):
1843 """Entry point for the devtool 'reset' subcommand"""
1844 import bb
1845 if args.recipename:
1846 if args.all:
1847 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1848 else:
1849 for recipe in args.recipename:
1850 check_workspace_recipe(workspace, recipe, checksrc=False)
1851 elif not args.all:
1852 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1853 "reset all recipes")
1854 if args.all:
1855 recipes = list(workspace.keys())
1856 else:
1857 recipes = args.recipename
1858
1859 _reset(recipes, args.no_clean, config, basepath, workspace)
1860
1861 return 0
1862
1863
1864def _get_layer(layername, d):
1865 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001866 layerdirs = d.getVar('BBLAYERS').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001867 layers = {os.path.basename(p): p for p in layerdirs}
1868 # Provide some shortcuts
1869 if layername.lower() in ['oe-core', 'openembedded-core']:
1870 layerdir = layers.get('meta', None)
1871 else:
1872 layerdir = layers.get(layername, None)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001873 return os.path.abspath(layerdir or layername)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001874
1875def finish(args, config, basepath, workspace):
1876 """Entry point for the devtool 'finish' subcommand"""
1877 import bb
1878 import oe.recipeutils
1879
1880 check_workspace_recipe(workspace, args.recipename)
1881
Brad Bishop316dfdd2018-06-25 12:45:53 -04001882 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
1883
1884 # Grab the equivalent of COREBASE without having to initialise tinfoil
1885 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
1886
1887 srctree = workspace[args.recipename]['srctree']
1888 check_git_repo_op(srctree, [corebasedir])
1889 dirty = check_git_repo_dirty(srctree)
1890 if dirty:
1891 if args.force:
1892 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
1893 else:
1894 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)
1895
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001896 no_clean = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001897 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1898 try:
1899 rd = parse_recipe(config, tinfoil, args.recipename, True)
1900 if not rd:
1901 return 1
1902
1903 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001904 recipefile = rd.getVar('FILE')
1905 recipedir = os.path.dirname(recipefile)
1906 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001907
1908 if not os.path.isdir(destlayerdir):
1909 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
1910
1911 if os.path.abspath(destlayerdir) == config.workspace_path:
1912 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
1913
1914 # If it's an upgrade, grab the original path
1915 origpath = None
1916 origfilelist = None
1917 append = workspace[args.recipename]['bbappend']
1918 with open(append, 'r') as f:
1919 for line in f:
1920 if line.startswith('# original_path:'):
1921 origpath = line.split(':')[1].strip()
1922 elif line.startswith('# original_files:'):
1923 origfilelist = line.split(':')[1].split()
1924
Brad Bishop316dfdd2018-06-25 12:45:53 -04001925 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
1926
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001927 if origlayerdir == config.workspace_path:
1928 # Recipe file itself is in workspace, update it there first
1929 appendlayerdir = None
1930 origrelpath = None
1931 if origpath:
1932 origlayerpath = oe.recipeutils.find_layerdir(origpath)
1933 if origlayerpath:
1934 origrelpath = os.path.relpath(origpath, origlayerpath)
1935 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
1936 if not destpath:
1937 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 -05001938 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
1939 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04001940 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001941 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)
1942
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001943 elif destlayerdir == origlayerdir:
1944 # Same layer, update the original recipe
1945 appendlayerdir = None
1946 destpath = None
1947 else:
1948 # Create/update a bbappend in the specified layer
1949 appendlayerdir = destlayerdir
1950 destpath = None
1951
Brad Bishop316dfdd2018-06-25 12:45:53 -04001952 # Actually update the recipe / bbappend
1953 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
1954 dry_run_output = None
1955 dry_run_outdir = None
1956 if args.dry_run:
1957 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1958 dry_run_outdir = dry_run_output.name
1959 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)
1960 removed = [os.path.relpath(pth, recipedir) for pth in removed]
1961
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001962 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04001963 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001964 for fn in origfilelist:
1965 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001966 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
1967 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
1968 if not args.dry_run:
1969 try:
1970 os.remove(fnp)
1971 except FileNotFoundError:
1972 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001973
1974 if origlayerdir == config.workspace_path and destpath:
1975 # Recipe file itself is in the workspace - need to move it and any
1976 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001977 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04001978 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001979 for root, _, files in os.walk(recipedir):
1980 for fn in files:
1981 srcpath = os.path.join(root, fn)
1982 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
1983 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001984 destfp = os.path.join(destdir, fn)
1985 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001986
Brad Bishop316dfdd2018-06-25 12:45:53 -04001987 if dry_run_outdir:
1988 import difflib
1989 comparelist = []
1990 for root, _, files in os.walk(dry_run_outdir):
1991 for fn in files:
1992 outf = os.path.join(root, fn)
1993 relf = os.path.relpath(outf, dry_run_outdir)
1994 logger.debug('dry-run: output file %s' % relf)
1995 if fn.endswith('.bb'):
1996 if origfilelist and origpath and destpath:
1997 # Need to match this up with the pre-upgrade recipe file
1998 for origf in origfilelist:
1999 if origf.endswith('.bb'):
2000 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2001 outf,
2002 os.path.abspath(os.path.join(destpath, relf))))
2003 break
2004 else:
2005 # Compare to the existing recipe
2006 comparelist.append((recipefile, outf, recipefile))
2007 elif fn.endswith('.bbappend'):
2008 if appendfile:
2009 if os.path.exists(appendfile):
2010 comparelist.append((appendfile, outf, appendfile))
2011 else:
2012 comparelist.append((None, outf, appendfile))
2013 else:
2014 if destpath:
2015 recipedest = destpath
2016 elif appendfile:
2017 recipedest = os.path.dirname(appendfile)
2018 else:
2019 recipedest = os.path.dirname(recipefile)
2020 destfp = os.path.join(recipedest, relf)
2021 if os.path.exists(destfp):
2022 comparelist.append((destfp, outf, destfp))
2023 output = ''
2024 for oldfile, newfile, newfileshow in comparelist:
2025 if oldfile:
2026 with open(oldfile, 'r') as f:
2027 oldlines = f.readlines()
2028 else:
2029 oldfile = '/dev/null'
2030 oldlines = []
2031 with open(newfile, 'r') as f:
2032 newlines = f.readlines()
2033 if not newfileshow:
2034 newfileshow = newfile
2035 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2036 difflines = list(diff)
2037 if difflines:
2038 output += ''.join(difflines)
2039 if output:
2040 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002041 finally:
2042 tinfoil.shutdown()
2043
2044 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002045 if args.dry_run:
2046 logger.info('Resetting recipe (dry-run)')
2047 else:
2048 _reset([args.recipename], no_clean=no_clean, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002049
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002050 return 0
2051
2052
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002053def get_default_srctree(config, recipename=''):
2054 """Get the default srctree path"""
2055 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2056 if recipename:
2057 return os.path.join(srctreeparent, 'sources', recipename)
2058 else:
2059 return os.path.join(srctreeparent, 'sources')
2060
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002061def register_commands(subparsers, context):
2062 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002063
2064 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002065 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002066 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.',
2067 group='starting', order=100)
2068 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.')
2069 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2070 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 -05002071 group = parser_add.add_mutually_exclusive_group()
2072 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2073 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002074 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 -05002075 parser_add.add_argument('--fetch-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002076 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002077 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 -05002078 group = parser_add.add_mutually_exclusive_group()
2079 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2080 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")
2081 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 -05002082 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')
2083 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')
2084 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002085 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2086 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 -06002087 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002088
2089 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002090 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.',
2091 group='starting', order=90)
2092 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2093 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 -05002094 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002095 group = parser_modify.add_mutually_exclusive_group()
2096 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2097 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 -05002098 group = parser_modify.add_mutually_exclusive_group()
2099 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2100 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002101 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 -04002102 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 -05002103 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002104 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002105
2106 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2107 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002108 group='advanced')
2109 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002110 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002111 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 -04002112 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 -05002113 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002114 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002115
2116 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2117 description='Synchronize the previously extracted source tree for an existing recipe',
2118 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2119 group='advanced')
2120 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2121 parser_sync.add_argument('srctree', help='Path to the source tree')
2122 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2123 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002124 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002125
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002126 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2127 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.',
2128 group='working', order=10)
2129 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2130 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2131 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)')
2132 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')
2133 parser_rename.set_defaults(func=rename)
2134
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002135 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002136 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.',
2137 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002138 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2139 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 -05002140 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002141 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2142 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')
2143 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 -04002144 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2145 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2146 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 -05002147 parser_update_recipe.set_defaults(func=update_recipe)
2148
2149 parser_status = subparsers.add_parser('status', help='Show workspace status',
2150 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002151 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002152 parser_status.set_defaults(func=status)
2153
2154 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002155 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 -05002156 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002157 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002158 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2159 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
2160 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002161
2162 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002163 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 -06002164 group='working', order=-100)
2165 parser_finish.add_argument('recipename', help='Recipe to finish')
2166 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.')
2167 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')
2168 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002169 parser_finish.add_argument('--force', '-f', action="store_true", help='Force continuing even if there are uncommitted changes in the source tree repository')
2170 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2171 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2172 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 -06002173 parser_finish.set_defaults(func=finish)