blob: 0a1e329e61edd3ff3807d89cceeec92226cff722 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Development tool - standard commands plugin
2#
Brad Bishopd7bf8c12018-02-25 22:55:05 -05003# Copyright (C) 2014-2017 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17"""Devtool standard plugins"""
18
19import os
20import sys
21import re
22import shutil
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050023import subprocess
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024import tempfile
25import logging
26import argparse
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050027import argparse_oe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050028import scriptutils
29import errno
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050030import glob
Patrick Williamsc0f7c042017-02-23 20:41:17 -060031import filecmp
Patrick Williamsf1e5d692016-03-30 15:21:19 -050032from collections import OrderedDict
Brad Bishop316dfdd2018-06-25 12:45:53 -040033from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, use_external_build, setup_git_repo, recipe_to_append, get_bbclassextend_targets, update_unlockedsigs, check_prerelease_version, check_git_repo_dirty, check_git_repo_op, DevtoolError
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034from devtool import parse_recipe
35
36logger = logging.getLogger('devtool')
37
Brad Bishop316dfdd2018-06-25 12:45:53 -040038override_branch_prefix = 'devtool-override-'
39
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040
41def add(args, config, basepath, workspace):
42 """Entry point for the devtool 'add' subcommand"""
43 import bb
44 import oe.recipeutils
45
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050046 if not args.recipename and not args.srctree and not args.fetch and not args.fetchuri:
47 raise argparse_oe.ArgumentUsageError('At least one of recipename, srctree, fetchuri or -f/--fetch must be specified', 'add')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050048
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050049 # These are positional arguments, but because we're nice, allow
50 # specifying e.g. source tree without name, or fetch URI without name or
51 # source tree (if we can detect that that is what the user meant)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060052 if scriptutils.is_src_url(args.recipename):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050053 if not args.fetchuri:
54 if args.fetch:
55 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
56 args.fetchuri = args.recipename
57 args.recipename = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -060058 elif scriptutils.is_src_url(args.srctree):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050059 if not args.fetchuri:
60 if args.fetch:
61 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
62 args.fetchuri = args.srctree
63 args.srctree = ''
64 elif args.recipename and not args.srctree:
65 if os.sep in args.recipename:
66 args.srctree = args.recipename
67 args.recipename = None
68 elif os.path.isdir(args.recipename):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080069 logger.warning('Ambiguous argument "%s" - assuming you mean it to be the recipe name' % args.recipename)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060070
Brad Bishopd7bf8c12018-02-25 22:55:05 -050071 if not args.fetchuri:
72 if args.srcrev:
73 raise DevtoolError('The -S/--srcrev option is only valid when fetching from an SCM repository')
74 if args.srcbranch:
75 raise DevtoolError('The -B/--srcbranch option is only valid when fetching from an SCM repository')
76
Patrick Williamsc0f7c042017-02-23 20:41:17 -060077 if args.srctree and os.path.isfile(args.srctree):
78 args.fetchuri = 'file://' + os.path.abspath(args.srctree)
79 args.srctree = ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -050080
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050081 if args.fetch:
82 if args.fetchuri:
83 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
84 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080085 logger.warning('-f/--fetch option is deprecated - you can now simply specify the URL to fetch as a positional argument instead')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050086 args.fetchuri = args.fetch
Patrick Williamsf1e5d692016-03-30 15:21:19 -050087
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050088 if args.recipename:
89 if args.recipename in workspace:
90 raise DevtoolError("recipe %s is already in your workspace" %
91 args.recipename)
92 reason = oe.recipeutils.validate_pn(args.recipename)
93 if reason:
94 raise DevtoolError(reason)
95
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050096 if args.srctree:
97 srctree = os.path.abspath(args.srctree)
98 srctreeparent = None
99 tmpsrcdir = None
100 else:
101 srctree = None
102 srctreeparent = get_default_srctree(config)
103 bb.utils.mkdirhier(srctreeparent)
104 tmpsrcdir = tempfile.mkdtemp(prefix='devtoolsrc', dir=srctreeparent)
105
106 if srctree and os.path.exists(srctree):
107 if args.fetchuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500108 if not os.path.isdir(srctree):
109 raise DevtoolError("Cannot fetch into source tree path %s as "
110 "it exists and is not a directory" %
111 srctree)
112 elif os.listdir(srctree):
113 raise DevtoolError("Cannot fetch into source tree path %s as "
114 "it already exists and is non-empty" %
115 srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500116 elif not args.fetchuri:
117 if args.srctree:
118 raise DevtoolError("Specified source tree %s could not be found" %
119 args.srctree)
120 elif srctree:
121 raise DevtoolError("No source tree exists at default path %s - "
122 "either create and populate this directory, "
123 "or specify a path to a source tree, or a "
124 "URI to fetch source from" % srctree)
125 else:
126 raise DevtoolError("You must either specify a source tree "
127 "or a URI to fetch source from")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500128
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500129 if args.version:
130 if '_' in args.version or ' ' in args.version:
131 raise DevtoolError('Invalid version string "%s"' % args.version)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500132
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500133 if args.color == 'auto' and sys.stdout.isatty():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500134 color = 'always'
135 else:
136 color = args.color
137 extracmdopts = ''
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500138 if args.fetchuri:
139 source = args.fetchuri
140 if srctree:
141 extracmdopts += ' -x %s' % srctree
142 else:
143 extracmdopts += ' -x %s' % tmpsrcdir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500144 else:
145 source = srctree
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500146 if args.recipename:
147 extracmdopts += ' -N %s' % args.recipename
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500148 if args.version:
149 extracmdopts += ' -V %s' % args.version
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500150 if args.binary:
151 extracmdopts += ' -b'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500152 if args.also_native:
153 extracmdopts += ' --also-native'
154 if args.src_subdir:
155 extracmdopts += ' --src-subdir "%s"' % args.src_subdir
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600156 if args.autorev:
157 extracmdopts += ' -a'
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500158 if args.fetch_dev:
159 extracmdopts += ' --fetch-dev'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500160 if args.mirrors:
161 extracmdopts += ' --mirrors'
162 if args.srcrev:
163 extracmdopts += ' --srcrev %s' % args.srcrev
164 if args.srcbranch:
165 extracmdopts += ' --srcbranch %s' % args.srcbranch
166 if args.provides:
167 extracmdopts += ' --provides %s' % args.provides
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500168
169 tempdir = tempfile.mkdtemp(prefix='devtool')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500171 try:
172 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create --devtool -o %s \'%s\' %s' % (color, tempdir, source, extracmdopts), watch=True)
173 except bb.process.ExecutionError as e:
174 if e.exitcode == 15:
175 raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line')
176 else:
177 raise DevtoolError('Command \'%s\' failed' % e.command)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500178
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500179 recipes = glob.glob(os.path.join(tempdir, '*.bb'))
180 if recipes:
181 recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0]
182 if recipename in workspace:
183 raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename)
184 recipedir = os.path.join(config.workspace_path, 'recipes', recipename)
185 bb.utils.mkdirhier(recipedir)
186 recipefile = os.path.join(recipedir, os.path.basename(recipes[0]))
187 appendfile = recipe_to_append(recipefile, config)
188 if os.path.exists(appendfile):
189 # This shouldn't be possible, but just in case
190 raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace')
191 if os.path.exists(recipefile):
192 raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile)
193 if tmpsrcdir:
194 srctree = os.path.join(srctreeparent, recipename)
195 if os.path.exists(tmpsrcdir):
196 if os.path.exists(srctree):
197 if os.path.isdir(srctree):
198 try:
199 os.rmdir(srctree)
200 except OSError as e:
201 if e.errno == errno.ENOTEMPTY:
202 raise DevtoolError('Source tree path %s already exists and is not empty' % srctree)
203 else:
204 raise
205 else:
206 raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree)
207 logger.info('Using default source tree path %s' % srctree)
208 shutil.move(tmpsrcdir, srctree)
209 else:
210 raise DevtoolError('Couldn\'t find source tree created by recipetool')
211 bb.utils.mkdirhier(recipedir)
212 shutil.move(recipes[0], recipefile)
213 # Move any additional files created by recipetool
214 for fn in os.listdir(tempdir):
215 shutil.move(os.path.join(tempdir, fn), recipedir)
216 else:
217 raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout))
218 attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile))
219 if os.path.exists(attic_recipe):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800220 logger.warning('A modified recipe from a previous invocation exists in %s - you may wish to move this over the top of the new recipe if you had changes in it that you want to continue with' % attic_recipe)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500221 finally:
222 if tmpsrcdir and os.path.exists(tmpsrcdir):
223 shutil.rmtree(tmpsrcdir)
224 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500226 for fn in os.listdir(recipedir):
227 _add_md5(config, recipename, os.path.join(recipedir, fn))
228
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500229 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600230 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500231 try:
232 rd = tinfoil.parse_recipe_file(recipefile, False)
233 except Exception as e:
234 logger.error(str(e))
235 rd = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600236 if not rd:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500237 # Parsing failed. We just created this recipe and we shouldn't
238 # leave it in the workdir or it'll prevent bitbake from starting
239 movefn = '%s.parsefailed' % recipefile
240 logger.error('Parsing newly created recipe failed, moving recipe to %s for reference. If this looks to be caused by the recipe itself, please report this error.' % movefn)
241 shutil.move(recipefile, movefn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600242 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500243
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600244 if args.fetchuri and not args.no_git:
245 setup_git_repo(srctree, args.version, 'devtool', d=tinfoil.config_data)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500246
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600247 initial_rev = None
248 if os.path.exists(os.path.join(srctree, '.git')):
249 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
250 initial_rev = stdout.rstrip()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500251
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600252 if args.src_subdir:
253 srctree = os.path.join(srctree, args.src_subdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600255 bb.utils.mkdirhier(os.path.dirname(appendfile))
256 with open(appendfile, 'w') as f:
257 f.write('inherit externalsrc\n')
258 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500259
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600260 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
261 if b_is_s:
262 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
263 if initial_rev:
264 f.write('\n# initial_rev: %s\n' % initial_rev)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500265
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600266 if args.binary:
267 f.write('do_install_append() {\n')
268 f.write(' rm -rf ${D}/.git\n')
269 f.write(' rm -f ${D}/singletask.lock\n')
270 f.write('}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500271
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600272 if bb.data.inherits_class('npm', rd):
273 f.write('do_install_append() {\n')
274 f.write(' # Remove files added to source dir by devtool/externalsrc\n')
275 f.write(' rm -f ${NPM_INSTALLDIR}/singletask.lock\n')
276 f.write(' rm -rf ${NPM_INSTALLDIR}/.git\n')
277 f.write(' rm -rf ${NPM_INSTALLDIR}/oe-local-files\n')
278 f.write(' for symlink in ${EXTERNALSRC_SYMLINKS} ; do\n')
279 f.write(' rm -f ${NPM_INSTALLDIR}/${symlink%%:*}\n')
280 f.write(' done\n')
281 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500282
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500283 # Check if the new layer provides recipes whose priorities have been
284 # overriden by PREFERRED_PROVIDER.
285 recipe_name = rd.getVar('PN')
286 provides = rd.getVar('PROVIDES')
287 # Search every item defined in PROVIDES
288 for recipe_provided in provides.split():
289 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
290 current_pprovider = rd.getVar(preferred_provider)
291 if current_pprovider and current_pprovider != recipe_name:
292 if args.fixed_setup:
293 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
294 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
295 with open(layerconf_file, 'a') as f:
296 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
297 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800298 logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500299 break
300
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600301 _add_md5(config, recipename, appendfile)
302
Brad Bishop316dfdd2018-06-25 12:45:53 -0400303 check_prerelease_version(rd.getVar('PV'), 'devtool add')
304
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600305 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
306
307 finally:
308 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500309
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500310 return 0
311
312
313def _check_compatible_recipe(pn, d):
314 """Check if the recipe is supported by devtool"""
315 if pn == 'perf':
316 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600317 "source and thus cannot be supported by this tool",
318 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319
320 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600321 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322
323 if bb.data.inherits_class('image', d):
324 raise DevtoolError("The %s recipe is an image, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600325 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326
327 if bb.data.inherits_class('populate_sdk', d):
328 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600329 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500330
331 if bb.data.inherits_class('packagegroup', d):
332 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600333 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500334
335 if bb.data.inherits_class('meta', d):
336 raise DevtoolError("The %s recipe is a meta-recipe, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600337 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500338
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500339 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600340 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341 raise DevtoolError("externalsrc is currently enabled for the %s "
342 "recipe. This prevents the normal do_patch task "
343 "from working. You will need to disable this "
344 "first." % pn)
345
Brad Bishop316dfdd2018-06-25 12:45:53 -0400346def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
347 """Common function for copying a file to the dry run output directory"""
348 relpath = os.path.relpath(dst, base_outdir)
349 if relpath.startswith('..'):
350 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
351 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500352 dst_d = os.path.dirname(dst)
353 if dst_d:
354 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400355 # Don't overwrite existing files, otherwise in the case of an upgrade
356 # the dry-run written out recipe will be overwritten with an unmodified
357 # version
358 if not os.path.exists(dst):
359 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500360
Brad Bishop316dfdd2018-06-25 12:45:53 -0400361def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
362 """Move a file. Creates all the directory components of destination path."""
363 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
364 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
365 if dry_run_outdir:
366 # We want to copy here, not move
367 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
368 else:
369 dst_d = os.path.dirname(dst)
370 if dst_d:
371 bb.utils.mkdirhier(dst_d)
372 shutil.move(src, dst)
373
374def _copy_file(src, dst, dry_run_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600375 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400376 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
377 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
378 if dry_run_outdir:
379 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
380 else:
381 dst_d = os.path.dirname(dst)
382 if dst_d:
383 bb.utils.mkdirhier(dst_d)
384 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600385
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500386def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
387 """List contents of a git treeish"""
388 import bb
389 cmd = ['git', 'ls-tree', '-z', treeish]
390 if recursive:
391 cmd.append('-r')
392 out, _ = bb.process.run(cmd, cwd=repodir)
393 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500394 if out:
395 for line in out.split('\0'):
396 if line:
397 split = line.split(None, 4)
398 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500399 return ret
400
401def _git_exclude_path(srctree, path):
402 """Return pathspec (list of paths) that excludes certain path"""
403 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
404 # we don't catch files that are deleted, for example. A more reliable way
405 # to implement this would be to use "negative pathspecs" which were
406 # introduced in Git v1.9.0. Revisit this when/if the required Git version
407 # becomes greater than that.
408 path = os.path.normpath(path)
409 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600410 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500411 if path in git_files:
412 git_files.remove(path)
413 return git_files
414 else:
415 return ['.']
416
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500417def _ls_tree(directory):
418 """Recursive listing of files in a directory"""
419 ret = []
420 for root, dirs, files in os.walk(directory):
421 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
422 fname in files])
423 return ret
424
425
426def extract(args, config, basepath, workspace):
427 """Entry point for the devtool 'extract' subcommand"""
428 import bb
429
Brad Bishop316dfdd2018-06-25 12:45:53 -0400430 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500431 if not tinfoil:
432 # Error already shown
433 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600434 try:
435 rd = parse_recipe(config, tinfoil, args.recipename, True)
436 if not rd:
437 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500438
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600439 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400440 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600441 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500442
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600443 if initial_rev:
444 return 0
445 else:
446 return 1
447 finally:
448 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500449
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500450def sync(args, config, basepath, workspace):
451 """Entry point for the devtool 'sync' subcommand"""
452 import bb
453
Brad Bishop316dfdd2018-06-25 12:45:53 -0400454 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500455 if not tinfoil:
456 # Error already shown
457 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600458 try:
459 rd = parse_recipe(config, tinfoil, args.recipename, True)
460 if not rd:
461 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500462
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600463 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400464 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, True, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600465 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500466
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600467 if initial_rev:
468 return 0
469 else:
470 return 1
471 finally:
472 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500473
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500474
Brad Bishop316dfdd2018-06-25 12:45:53 -0400475def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500476 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500477 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500478 import oe.patch
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500479
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500480 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500481
482 _check_compatible_recipe(pn, d)
483
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500484 if sync:
485 if not os.path.exists(srctree):
486 raise DevtoolError("output path %s does not exist" % srctree)
487 else:
488 if os.path.exists(srctree):
489 if not os.path.isdir(srctree):
490 raise DevtoolError("output path %s exists and is not a directory" %
491 srctree)
492 elif os.listdir(srctree):
493 raise DevtoolError("output path %s already exists and is "
494 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500495
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500496 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
497 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600498 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500499
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500500 if not sync:
501 # Prepare for shutil.move later on
502 bb.utils.mkdirhier(srctree)
503 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500504
Brad Bishop316dfdd2018-06-25 12:45:53 -0400505 extra_overrides = []
506 if not no_overrides:
507 history = d.varhistory.variable('SRC_URI')
508 for event in history:
509 if not 'flag' in event:
510 if event['op'].startswith(('_append[', '_prepend[')):
511 extra_overrides.append(event['op'].split('[')[1].split(']')[0])
Andrew Geissler99467da2019-02-25 18:54:23 -0600512 # We want to remove duplicate overrides. If a recipe had multiple
513 # SRC_URI_override += values it would cause mulitple instances of
514 # overrides. This doesn't play nicely with things like creating a
515 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
516 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400517 if extra_overrides:
518 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
519
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500521
522 appendexisted = False
523 recipefile = d.getVar('FILE')
524 appendfile = recipe_to_append(recipefile, config)
525 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
526
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500527 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
528 # directory so that:
529 # (a) we pick up all files that get unpacked to the WORKDIR, and
530 # (b) we don't disturb the existing build
531 # However, with recipe-specific sysroots the sysroots for the recipe
532 # will be prepared under WORKDIR, and if we used the system temporary
533 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
534 # our attempts to hardlink files into the recipe-specific sysroots
535 # will fail on systems where /tmp is a different filesystem, and it
536 # would have to fall back to copying the files which is a waste of
537 # time. Put the temp directory under the WORKDIR to prevent that from
538 # being a problem.
539 tempbasedir = d.getVar('WORKDIR')
540 bb.utils.mkdirhier(tempbasedir)
541 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500542 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500543 tinfoil.logger.setLevel(logging.WARNING)
544
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500545 # FIXME this results in a cache reload under control of tinfoil, which is fine
546 # except we don't get the knotty progress bar
547
548 if os.path.exists(appendfile):
549 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
550 shutil.copyfile(appendfile, appendbackup)
551 else:
552 appendbackup = None
553 bb.utils.mkdirhier(os.path.dirname(appendfile))
554 logger.debug('writing append file %s' % appendfile)
555 with open(appendfile, 'a') as f:
556 f.write('###--- _extract_source\n')
557 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
558 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
559 if not is_kernel_yocto:
560 f.write('PATCHTOOL = "git"\n')
561 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400562 if extra_overrides:
563 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500564 f.write('inherit devtool-source\n')
565 f.write('###--- _extract_source\n')
566
567 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
568
569 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
570 bb.utils.mkdirhier(sstate_manifests)
571 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
572 with open(preservestampfile, 'w') as f:
573 f.write(d.getVar('STAMP'))
574 try:
575 if bb.data.inherits_class('kernel-yocto', d):
576 # We need to generate the kernel config
577 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500578 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500579 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500580
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500581 # Run the fetch + unpack tasks
582 res = tinfoil.build_targets(pn,
583 task,
584 handle_events=True)
585 finally:
586 if os.path.exists(preservestampfile):
587 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500588
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500589 if not res:
590 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591
Brad Bishop316dfdd2018-06-25 12:45:53 -0400592 try:
593 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
594 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500595
Brad Bishop316dfdd2018-06-25 12:45:53 -0400596 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
597 srcsubdir = f.read()
598 except FileNotFoundError as e:
599 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
600 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500601
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500602 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
603 srctree_localdir = os.path.join(srctree, 'oe-local-files')
604
605 if sync:
606 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
607
608 # Move oe-local-files directory to srctree
609 # As the oe-local-files is not part of the constructed git tree,
610 # remove them directly during the synchrounizating might surprise
611 # the users. Instead, we move it to oe-local-files.bak and remind
612 # user in the log message.
613 if os.path.exists(srctree_localdir + '.bak'):
614 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
615
616 if os.path.exists(srctree_localdir):
617 logger.info('Backing up current local file directory %s' % srctree_localdir)
618 shutil.move(srctree_localdir, srctree_localdir + '.bak')
619
620 if os.path.exists(tempdir_localdir):
621 logger.info('Syncing local source files to srctree...')
622 shutil.copytree(tempdir_localdir, srctree_localdir)
623 else:
624 # Move oe-local-files directory to srctree
625 if os.path.exists(tempdir_localdir):
626 logger.info('Adding local source files to srctree...')
627 shutil.move(tempdir_localdir, srcsubdir)
628
629 shutil.move(srcsubdir, srctree)
630
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500631 if os.path.abspath(d.getVar('S')) == os.path.abspath(d.getVar('WORKDIR')):
632 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
633 # (otherwise the recipe won't build as expected)
634 local_files_dir = os.path.join(srctree, 'oe-local-files')
635 addfiles = []
636 for root, _, files in os.walk(local_files_dir):
637 relpth = os.path.relpath(root, local_files_dir)
638 if relpth != '.':
639 bb.utils.mkdirhier(os.path.join(srctree, relpth))
640 for fn in files:
641 if fn == '.gitignore':
642 continue
643 destpth = os.path.join(srctree, relpth, fn)
644 if os.path.exists(destpth):
645 os.unlink(destpth)
646 os.symlink('oe-local-files/%s' % fn, destpth)
647 addfiles.append(os.path.join(relpth, fn))
648 if addfiles:
649 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
650 useroptions = []
651 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=d)
652 bb.process.run('git %s commit -a -m "Committing local file symlinks\n\n%s"' % (' '.join(useroptions), oe.patch.GitApplyTree.ignore_commit_prefix), cwd=srctree)
653
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500654 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500655 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500656 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500657
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500658 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500659 if appendbackup:
660 shutil.copyfile(appendbackup, appendfile)
661 elif os.path.exists(appendfile):
662 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500663 if keep_temp:
664 logger.info('Preserving temporary directory %s' % tempdir)
665 else:
666 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400667 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500668
669def _add_md5(config, recipename, filename):
670 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
671 import bb.utils
672
673 def addfile(fn):
674 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500675 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
676 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
677 f.seek(0, os.SEEK_SET)
678 if not md5_str in f.read():
679 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500680
681 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500682 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500683 for f in files:
684 addfile(os.path.join(root, f))
685 else:
686 addfile(filename)
687
688def _check_preserve(config, recipename):
689 """Check if a file was manually changed and needs to be saved in 'attic'
690 directory"""
691 import bb.utils
692 origfile = os.path.join(config.workspace_path, '.devtool_md5')
693 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500694 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500695 with open(origfile, 'r') as f:
696 with open(newfile, 'w') as tf:
697 for line in f.readlines():
698 splitline = line.rstrip().split('|')
699 if splitline[0] == recipename:
700 removefile = os.path.join(config.workspace_path, splitline[1])
701 try:
702 md5 = bb.utils.md5_file(removefile)
703 except IOError as err:
704 if err.errno == 2:
705 # File no longer exists, skip it
706 continue
707 else:
708 raise
709 if splitline[2] != md5:
710 bb.utils.mkdirhier(preservepath)
711 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800712 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500713 shutil.move(removefile, os.path.join(preservepath, preservefile))
714 else:
715 os.remove(removefile)
716 else:
717 tf.write(line)
718 os.rename(newfile, origfile)
719
720def modify(args, config, basepath, workspace):
721 """Entry point for the devtool 'modify' subcommand"""
722 import bb
723 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400724 import oe.patch
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500725
726 if args.recipename in workspace:
727 raise DevtoolError("recipe %s is already in your workspace" %
728 args.recipename)
729
Brad Bishop316dfdd2018-06-25 12:45:53 -0400730 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600731 try:
732 rd = parse_recipe(config, tinfoil, args.recipename, True)
733 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500734 return 1
735
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500736 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600737 if pn != args.recipename:
738 logger.info('Mapping %s to %s' % (args.recipename, pn))
739 if pn in workspace:
740 raise DevtoolError("recipe %s is already in your workspace" %
741 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600743 if args.srctree:
744 srctree = os.path.abspath(args.srctree)
745 else:
746 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500747
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600748 if args.no_extract and not os.path.isdir(srctree):
749 raise DevtoolError("--no-extract specified and source path %s does "
750 "not exist or is not a directory" %
751 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600752
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500753 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600754 appendfile = recipe_to_append(recipefile, config, args.wildcard)
755 if os.path.exists(appendfile):
756 raise DevtoolError("Another variant of recipe %s is already in your "
757 "workspace (only one variant of a recipe can "
758 "currently be worked on at once)"
759 % pn)
760
761 _check_compatible_recipe(pn, rd)
762
763 initial_rev = None
764 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400765 check_commits = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600766 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400767 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500768 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600769 return 1
770 logger.info('Source tree extracted to %s' % srctree)
771 # Get list of commits since this revision
772 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
773 commits = stdout.split()
Brad Bishop316dfdd2018-06-25 12:45:53 -0400774 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600775 else:
776 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600777 # Check if it's a tree previously extracted by us. This is done
778 # by ensuring that devtool-base and args.branch (devtool) exist.
779 # The check_commits logic will cause an exception if either one
780 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600781 try:
782 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600783 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600784 except bb.process.ExecutionError:
785 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400786 if stdout:
787 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600788 for line in stdout.splitlines():
789 if line.startswith('*'):
790 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
791 initial_rev = stdout.rstrip()
792 if not initial_rev:
793 # Otherwise, just grab the head revision
794 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
795 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500796
Brad Bishop316dfdd2018-06-25 12:45:53 -0400797 branch_patches = {}
798 if check_commits:
799 # Check if there are override branches
800 (stdout, _) = bb.process.run('git branch', cwd=srctree)
801 branches = []
802 for line in stdout.rstrip().splitlines():
803 branchname = line[2:].rstrip()
804 if branchname.startswith(override_branch_prefix):
805 branches.append(branchname)
806 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800807 logger.warning('SRC_URI is conditionally overridden in this recipe, thus several %s* branches have been created, one for each override that makes changes to SRC_URI. It is recommended that you make changes to the %s branch first, then checkout and rebase each %s* branch and update any unique patches there (duplicates on those branches will be ignored by devtool finish/update-recipe)' % (override_branch_prefix, args.branch, override_branch_prefix))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400808 branches.insert(0, args.branch)
809 seen_patches = []
810 for branch in branches:
811 branch_patches[branch] = []
812 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
813 for line in stdout.splitlines():
814 line = line.strip()
815 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
816 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
817 if not origpatch in seen_patches:
818 seen_patches.append(origpatch)
819 branch_patches[branch].append(origpatch)
820
821 # Need to grab this here in case the source is within a subdirectory
822 srctreebase = srctree
823
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600824 # Check that recipe isn't using a shared workdir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500825 s = os.path.abspath(rd.getVar('S'))
826 workdir = os.path.abspath(rd.getVar('WORKDIR'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600827 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
828 # Handle if S is set to a subdirectory of the source
829 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
830 srctree = os.path.join(srctree, srcsubdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500831
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600832 bb.utils.mkdirhier(os.path.dirname(appendfile))
833 with open(appendfile, 'w') as f:
834 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
835 # Local files can be modified/tracked in separate subdir under srctree
836 # Mostly useful for packages with S != WORKDIR
837 f.write('FILESPATH_prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400838 os.path.join(srctreebase, 'oe-local-files'))
839 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500840
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600841 f.write('\ninherit externalsrc\n')
842 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
843 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500844
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600845 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
846 if b_is_s:
847 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500848
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600849 if bb.data.inherits_class('kernel', rd):
850 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500851 'do_fetch do_unpack do_kernel_configme do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400852 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600853 f.write('\ndo_configure_append() {\n'
854 ' cp ${B}/.config ${S}/.config.baseline\n'
855 ' ln -sfT ${B}/.config ${S}/.config.new\n'
856 '}\n')
857 if initial_rev:
858 f.write('\n# initial_rev: %s\n' % initial_rev)
859 for commit in commits:
860 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400861 if branch_patches:
862 for branch in branch_patches:
863 if branch == args.branch:
864 continue
865 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500866
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500867 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
868
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600869 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500870
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600871 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500872
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600873 finally:
874 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500875
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500876 return 0
877
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500878
879def rename(args, config, basepath, workspace):
880 """Entry point for the devtool 'rename' subcommand"""
881 import bb
882 import oe.recipeutils
883
884 check_workspace_recipe(workspace, args.recipename)
885
886 if not (args.newname or args.version):
887 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
888
889 recipefile = workspace[args.recipename]['recipefile']
890 if not recipefile:
891 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
892
893 if args.newname and args.newname != args.recipename:
894 reason = oe.recipeutils.validate_pn(args.newname)
895 if reason:
896 raise DevtoolError(reason)
897 newname = args.newname
898 else:
899 newname = args.recipename
900
901 append = workspace[args.recipename]['bbappend']
902 appendfn = os.path.splitext(os.path.basename(append))[0]
903 splitfn = appendfn.split('_')
904 if len(splitfn) > 1:
905 origfnver = appendfn.split('_')[1]
906 else:
907 origfnver = ''
908
909 recipefilemd5 = None
910 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
911 try:
912 rd = parse_recipe(config, tinfoil, args.recipename, True)
913 if not rd:
914 return 1
915
916 bp = rd.getVar('BP')
917 bpn = rd.getVar('BPN')
918 if newname != args.recipename:
919 localdata = rd.createCopy()
920 localdata.setVar('PN', newname)
921 newbpn = localdata.getVar('BPN')
922 else:
923 newbpn = bpn
924 s = rd.getVar('S', False)
925 src_uri = rd.getVar('SRC_URI', False)
926 pv = rd.getVar('PV')
927
928 # Correct variable values that refer to the upstream source - these
929 # values must stay the same, so if the name/version are changing then
930 # we need to fix them up
931 new_s = s
932 new_src_uri = src_uri
933 if newbpn != bpn:
934 # ${PN} here is technically almost always incorrect, but people do use it
935 new_s = new_s.replace('${BPN}', bpn)
936 new_s = new_s.replace('${PN}', bpn)
937 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
938 new_src_uri = new_src_uri.replace('${BPN}', bpn)
939 new_src_uri = new_src_uri.replace('${PN}', bpn)
940 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
941 if args.version and origfnver == pv:
942 new_s = new_s.replace('${PV}', pv)
943 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
944 new_src_uri = new_src_uri.replace('${PV}', pv)
945 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
946 patchfields = {}
947 if new_s != s:
948 patchfields['S'] = new_s
949 if new_src_uri != src_uri:
950 patchfields['SRC_URI'] = new_src_uri
951 if patchfields:
952 recipefilemd5 = bb.utils.md5_file(recipefile)
953 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
954 newrecipefilemd5 = bb.utils.md5_file(recipefile)
955 finally:
956 tinfoil.shutdown()
957
958 if args.version:
959 newver = args.version
960 else:
961 newver = origfnver
962
963 if newver:
964 newappend = '%s_%s.bbappend' % (newname, newver)
965 newfile = '%s_%s.bb' % (newname, newver)
966 else:
967 newappend = '%s.bbappend' % newname
968 newfile = '%s.bb' % newname
969
970 oldrecipedir = os.path.dirname(recipefile)
971 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
972 if oldrecipedir != newrecipedir:
973 bb.utils.mkdirhier(newrecipedir)
974
975 newappend = os.path.join(os.path.dirname(append), newappend)
976 newfile = os.path.join(newrecipedir, newfile)
977
978 # Rename bbappend
979 logger.info('Renaming %s to %s' % (append, newappend))
980 os.rename(append, newappend)
981 # Rename recipe file
982 logger.info('Renaming %s to %s' % (recipefile, newfile))
983 os.rename(recipefile, newfile)
984
985 # Rename source tree if it's the default path
986 appendmd5 = None
987 if not args.no_srctree:
988 srctree = workspace[args.recipename]['srctree']
989 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
990 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
991 logger.info('Renaming %s to %s' % (srctree, newsrctree))
992 shutil.move(srctree, newsrctree)
993 # Correct any references (basically EXTERNALSRC*) in the .bbappend
994 appendmd5 = bb.utils.md5_file(newappend)
995 appendlines = []
996 with open(newappend, 'r') as f:
997 for line in f:
998 appendlines.append(line)
999 with open(newappend, 'w') as f:
1000 for line in appendlines:
1001 if srctree in line:
1002 line = line.replace(srctree, newsrctree)
1003 f.write(line)
1004 newappendmd5 = bb.utils.md5_file(newappend)
1005
1006 bpndir = None
1007 newbpndir = None
1008 if newbpn != bpn:
1009 bpndir = os.path.join(oldrecipedir, bpn)
1010 if os.path.exists(bpndir):
1011 newbpndir = os.path.join(newrecipedir, newbpn)
1012 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1013 shutil.move(bpndir, newbpndir)
1014
1015 bpdir = None
1016 newbpdir = None
1017 if newver != origfnver or newbpn != bpn:
1018 bpdir = os.path.join(oldrecipedir, bp)
1019 if os.path.exists(bpdir):
1020 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1021 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1022 shutil.move(bpdir, newbpdir)
1023
1024 if oldrecipedir != newrecipedir:
1025 # Move any stray files and delete the old recipe directory
1026 for entry in os.listdir(oldrecipedir):
1027 oldpath = os.path.join(oldrecipedir, entry)
1028 newpath = os.path.join(newrecipedir, entry)
1029 logger.info('Renaming %s to %s' % (oldpath, newpath))
1030 shutil.move(oldpath, newpath)
1031 os.rmdir(oldrecipedir)
1032
1033 # Now take care of entries in .devtool_md5
1034 md5entries = []
1035 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1036 for line in f:
1037 md5entries.append(line)
1038
1039 if bpndir and newbpndir:
1040 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1041 else:
1042 relbpndir = None
1043 if bpdir and newbpdir:
1044 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1045 else:
1046 relbpdir = None
1047
1048 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1049 for entry in md5entries:
1050 splitentry = entry.rstrip().split('|')
1051 if len(splitentry) > 2:
1052 if splitentry[0] == args.recipename:
1053 splitentry[0] = newname
1054 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1055 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1056 if appendmd5 and splitentry[2] == appendmd5:
1057 splitentry[2] = newappendmd5
1058 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1059 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1060 if recipefilemd5 and splitentry[2] == recipefilemd5:
1061 splitentry[2] = newrecipefilemd5
1062 elif relbpndir and splitentry[1].startswith(relbpndir):
1063 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1064 elif relbpdir and splitentry[1].startswith(relbpdir):
1065 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1066 entry = '|'.join(splitentry) + '\n'
1067 f.write(entry)
1068 return 0
1069
1070
Brad Bishop316dfdd2018-06-25 12:45:53 -04001071def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001072 """Get initial and update rev of a recipe. These are the start point of the
1073 whole patchset and start point for the patches to be re-generated/updated.
1074 """
1075 import bb
1076
Brad Bishop316dfdd2018-06-25 12:45:53 -04001077 # Get current branch
1078 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1079 cwd=srctree)
1080 branchname = stdout.rstrip()
1081
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001082 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001083 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001084 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001085 with open(recipe_path, 'r') as f:
1086 for line in f:
1087 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001088 if not initial_rev:
1089 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001090 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001091 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001092 elif line.startswith('# patches_%s:' % branchname):
1093 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001094
1095 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001096 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001097 if initial_rev:
1098 # Find first actually changed revision
1099 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1100 initial_rev, cwd=srctree)
1101 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001102 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001103 if newcommits[i] == commits[i]:
1104 update_rev = commits[i]
1105
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001106 try:
1107 stdout, _ = bb.process.run('git cherry devtool-patched',
1108 cwd=srctree)
1109 except bb.process.ExecutionError as err:
1110 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001111
Brad Bishop316dfdd2018-06-25 12:45:53 -04001112 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001113 changed_revs = []
1114 for line in stdout.splitlines():
1115 if line.startswith('+ '):
1116 rev = line.split()[1]
1117 if rev in newcommits:
1118 changed_revs.append(rev)
1119
Brad Bishop316dfdd2018-06-25 12:45:53 -04001120 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001121
1122def _remove_file_entries(srcuri, filelist):
1123 """Remove file:// entries from SRC_URI"""
1124 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001125 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001126 for fname in filelist:
1127 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001128 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001129 if (srcuri[i].startswith('file://') and
1130 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001131 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001132 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001133 srcuri.pop(i)
1134 break
1135 return entries, remaining
1136
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001137def _replace_srcuri_entry(srcuri, filename, newentry):
1138 """Replace entry corresponding to specified file with a new entry"""
1139 basename = os.path.basename(filename)
1140 for i in range(len(srcuri)):
1141 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1142 srcuri.pop(i)
1143 srcuri.insert(i, newentry)
1144 break
1145
Brad Bishop316dfdd2018-06-25 12:45:53 -04001146def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001147 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001148
1149 dry_run_suffix = ' (dry-run)' if dry_run else ''
1150
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001151 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001152 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001153 if not destpath:
1154 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001155 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001156
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001157 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001158 if not no_report_remove:
1159 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1160 if not dry_run:
1161 # FIXME "git rm" here would be nice if the file in question is
1162 # tracked
1163 # FIXME there's a chance that this file is referred to by
1164 # another recipe, in which case deleting wouldn't be the
1165 # right thing to do
1166 os.remove(path)
1167 # Remove directory if empty
1168 try:
1169 os.rmdir(os.path.dirname(path))
1170 except OSError as ose:
1171 if ose.errno != errno.ENOTEMPTY:
1172 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001173
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001174
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001175def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001176 """Export patches from srctree to given location.
1177 Returns three-tuple of dicts:
1178 1. updated - patches that already exist in SRCURI
1179 2. added - new patches that don't exist in SRCURI
1180 3 removed - patches that exist in SRCURI but not in exported patches
1181 In each dict the key is the 'basepath' of the URI and value is the
1182 absolute path to the existing file in recipe space (if any).
1183 """
1184 import oe.recipeutils
1185 from oe.patch import GitApplyTree
1186 updated = OrderedDict()
1187 added = OrderedDict()
1188 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1189
1190 existing_patches = dict((os.path.basename(path), path) for path in
1191 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001192 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001193
1194 # Generate patches from Git, exclude local files directory
1195 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1196 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1197
1198 new_patches = sorted(os.listdir(destdir))
1199 for new_patch in new_patches:
1200 # Strip numbering from patch names. If it's a git sequence named patch,
1201 # the numbers might not match up since we are starting from a different
1202 # revision This does assume that people are using unique shortlog
1203 # values, but they ought to be anyway...
1204 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001205 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001206 for old_patch in existing_patches:
1207 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001208 old_basename_splitext = os.path.splitext(old_basename)
1209 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1210 old_patch_noext = os.path.splitext(old_patch)[0]
1211 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001212 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001213 elif new_basename == old_basename:
1214 match_name = old_patch
1215 break
1216 if match_name:
1217 # Rename patch files
1218 if new_patch != match_name:
1219 os.rename(os.path.join(destdir, new_patch),
1220 os.path.join(destdir, match_name))
1221 # Need to pop it off the list now before checking changed_revs
1222 oldpath = existing_patches.pop(old_patch)
1223 if changed_revs is not None:
1224 # Avoid updating patches that have not actually changed
1225 with open(os.path.join(destdir, match_name), 'r') as f:
1226 firstlineitems = f.readline().split()
1227 # Looking for "From <hash>" line
1228 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1229 if not firstlineitems[1] in changed_revs:
1230 continue
1231 # Recompress if necessary
1232 if oldpath.endswith(('.gz', '.Z')):
1233 bb.process.run(['gzip', match_name], cwd=destdir)
1234 if oldpath.endswith('.gz'):
1235 match_name += '.gz'
1236 else:
1237 match_name += '.Z'
1238 elif oldpath.endswith('.bz2'):
1239 bb.process.run(['bzip2', match_name], cwd=destdir)
1240 match_name += '.bz2'
1241 updated[match_name] = oldpath
1242 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001243 added[new_patch] = None
1244 return (updated, added, existing_patches)
1245
1246
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001247def _create_kconfig_diff(srctree, rd, outfile):
1248 """Create a kconfig fragment"""
1249 # Only update config fragment if both config files exist
1250 orig_config = os.path.join(srctree, '.config.baseline')
1251 new_config = os.path.join(srctree, '.config.new')
1252 if os.path.exists(orig_config) and os.path.exists(new_config):
1253 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1254 '--unchanged-line-format=', orig_config, new_config]
1255 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1256 stderr=subprocess.PIPE)
1257 stdout, stderr = pipe.communicate()
1258 if pipe.returncode == 1:
1259 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001260 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001261 fobj.write(stdout)
1262 elif pipe.returncode == 0:
1263 logger.info("Would remove config fragment %s" % outfile)
1264 if os.path.exists(outfile):
1265 # Remove fragment file in case of empty diff
1266 logger.info("Removing config fragment %s" % outfile)
1267 os.unlink(outfile)
1268 else:
1269 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1270 return True
1271 return False
1272
1273
Brad Bishop316dfdd2018-06-25 12:45:53 -04001274def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001275 """Copy local files from srctree to given location.
1276 Returns three-tuple of dicts:
1277 1. updated - files that already exist in SRCURI
1278 2. added - new files files that don't exist in SRCURI
1279 3 removed - files that exist in SRCURI but not in exported files
1280 In each dict the key is the 'basepath' of the URI and value is the
1281 absolute path to the existing file in recipe space (if any).
1282 """
1283 import oe.recipeutils
1284
1285 # Find out local files (SRC_URI files that exist in the "recipe space").
1286 # Local files that reside in srctree are not included in patch generation.
1287 # Instead they are directly copied over the original source files (in
1288 # recipe space).
1289 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1290 new_set = None
1291 updated = OrderedDict()
1292 added = OrderedDict()
1293 removed = OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001294 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001295 git_files = _git_ls_tree(srctree)
1296 if 'oe-local-files' in git_files:
1297 # If tracked by Git, take the files from srctree HEAD. First get
1298 # the tree object of the directory
1299 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1300 tree = git_files['oe-local-files'][2]
1301 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1302 env=dict(os.environ, GIT_WORK_TREE=destdir,
1303 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001304 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001305 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001306 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001307 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001308 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001309 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001310 else:
1311 new_set = []
1312
1313 # Special handling for kernel config
1314 if bb.data.inherits_class('kernel-yocto', rd):
1315 fragment_fn = 'devtool-fragment.cfg'
1316 fragment_path = os.path.join(destdir, fragment_fn)
1317 if _create_kconfig_diff(srctree, rd, fragment_path):
1318 if os.path.exists(fragment_path):
1319 if fragment_fn not in new_set:
1320 new_set.append(fragment_fn)
1321 # Copy fragment to local-files
1322 if os.path.isdir(local_files_dir):
1323 shutil.copy2(fragment_path, local_files_dir)
1324 else:
1325 if fragment_fn in new_set:
1326 new_set.remove(fragment_fn)
1327 # Remove fragment from local-files
1328 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1329 os.unlink(os.path.join(local_files_dir, fragment_fn))
1330
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001331 # Special handling for cml1, ccmake, etc bbclasses that generated
1332 # configuration fragment files that are consumed as source files
1333 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1334 if bb.data.inherits_class(frag_class, rd):
1335 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1336 if os.path.exists(srcpath):
1337 if frag_name not in new_set:
1338 new_set.append(frag_name)
1339 # copy fragment into destdir
1340 shutil.copy2(srcpath, destdir)
1341 # copy fragment into local files if exists
1342 if os.path.isdir(local_files_dir):
1343 shutil.copy2(srcpath, local_files_dir)
1344
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001345 if new_set is not None:
1346 for fname in new_set:
1347 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001348 origpath = existing_files.pop(fname)
1349 workpath = os.path.join(local_files_dir, fname)
1350 if not filecmp.cmp(origpath, workpath):
1351 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001352 elif fname != '.gitignore':
1353 added[fname] = None
1354
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001355 workdir = rd.getVar('WORKDIR')
1356 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001357 if not s.endswith(os.sep):
1358 s += os.sep
1359
1360 if workdir != s:
1361 # Handle files where subdir= was specified
1362 for fname in list(existing_files.keys()):
1363 # FIXME handle both subdir starting with BP and not?
1364 fworkpath = os.path.join(workdir, fname)
1365 if fworkpath.startswith(s):
1366 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1367 if os.path.exists(fpath):
1368 origpath = existing_files.pop(fname)
1369 if not filecmp.cmp(origpath, fpath):
1370 updated[fpath] = origpath
1371
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001372 removed = existing_files
1373 return (updated, added, removed)
1374
1375
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001376def _determine_files_dir(rd):
1377 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001378 recipedir = rd.getVar('FILE_DIRNAME')
1379 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001380 relpth = os.path.relpath(entry, recipedir)
1381 if not os.sep in relpth:
1382 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1383 if os.path.isdir(entry):
1384 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001385 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001386
1387
Brad Bishop316dfdd2018-06-25 12:45:53 -04001388def _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 -05001389 """Implement the 'srcrev' mode of update-recipe"""
1390 import bb
1391 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001392
Brad Bishop316dfdd2018-06-25 12:45:53 -04001393 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1394
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001395 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001396 recipedir = os.path.basename(recipefile)
1397 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001398
1399 # Get HEAD revision
1400 try:
1401 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1402 except bb.process.ExecutionError as err:
1403 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1404 (srctree, err))
1405 srcrev = stdout.strip()
1406 if len(srcrev) != 40:
1407 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1408
1409 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001410 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001411 patchfields = {}
1412 patchfields['SRCREV'] = srcrev
1413 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001414 srcuri = orig_src_uri.split()
1415 tempdir = tempfile.mkdtemp(prefix='devtool')
1416 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001417 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001418 try:
1419 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001420 srctreebase = workspace[recipename]['srctreebase']
1421 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001422 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001423 # Find list of existing patches in recipe file
1424 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001425 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001426 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1427 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001428 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 -05001429
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001430 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001431 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001432 if remove_files:
1433 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1434 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001435
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001436 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001437 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001438 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001439 removevalues = {}
1440 if update_srcuri:
1441 removevalues = {'SRC_URI': removedentries}
1442 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001443 if dry_run_outdir:
1444 logger.info('Creating bbappend (dry-run)')
1445 else:
1446 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1447 rd, appendlayerdir, files, wildcardver=wildcard_version,
1448 extralines=patchfields, removevalues=removevalues,
1449 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001450 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001451 files_dir = _determine_files_dir(rd)
1452 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001453 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001454 if os.path.isabs(basepath):
1455 # Original file (probably with subdir pointing inside source tree)
1456 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001457 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001458 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001459 _move_file(os.path.join(local_files_dir, basepath), path,
1460 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001461 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001462 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001463 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001464 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001465 os.path.join(files_dir, basepath),
1466 dry_run_outdir=dry_run_outdir,
1467 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001468 srcuri.append('file://%s' % basepath)
1469 update_srcuri = True
1470 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001471 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001472 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001473 finally:
1474 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001475 if not 'git://' in orig_src_uri:
1476 logger.info('You will need to update SRC_URI within the recipe to '
1477 'point to a git repository where you have pushed your '
1478 'changes')
1479
Brad Bishop316dfdd2018-06-25 12:45:53 -04001480 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1481 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001482
Brad Bishop316dfdd2018-06-25 12:45:53 -04001483def _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 -05001484 """Implement the 'patch' mode of update-recipe"""
1485 import bb
1486 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001487
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001488 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001489 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001490 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001491 if not os.path.exists(append):
1492 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001493 recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001494
Brad Bishop316dfdd2018-06-25 12:45:53 -04001495 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 -05001496 if not initial_rev:
1497 raise DevtoolError('Unable to find initial revision - please specify '
1498 'it with --initial-rev')
1499
Brad Bishop316dfdd2018-06-25 12:45:53 -04001500 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001501 dl_dir = rd.getVar('DL_DIR')
1502 if not dl_dir.endswith('/'):
1503 dl_dir += '/'
1504
Brad Bishop316dfdd2018-06-25 12:45:53 -04001505 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1506
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001507 tempdir = tempfile.mkdtemp(prefix='devtool')
1508 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001509 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001510 if filter_patches:
1511 upd_f = {}
1512 new_f = {}
1513 del_f = {}
1514 else:
1515 srctreebase = workspace[recipename]['srctreebase']
1516 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001517
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001518 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001519 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001520 # Get all patches from source tree and check if any should be removed
1521 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001522 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1523 all_patches_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001524 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001525 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001526
1527 # Get updated patches from source tree
1528 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001529 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1530 patches_dir, changed_revs)
1531 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1532 if filter_patches:
1533 new_p = {}
1534 upd_p = {k:v for k,v in upd_p.items() if k in filter_patches}
1535 remove_files = [f for f in remove_files if f in filter_patches]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001536 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001537 updaterecipe = False
1538 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001539 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001540 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001541 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001542 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001543 files.update(dict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001544 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001545 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001546 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001547 if remove_files:
1548 removedentries, remaining = _remove_file_entries(
1549 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001550 if removedentries or remaining:
1551 remaining = ['file://' + os.path.basename(item) for
1552 item in remaining]
1553 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001554 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001555 rd, appendlayerdir, files,
1556 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001557 removevalues=removevalues,
1558 redirect_output=dry_run_outdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001559 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001560 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001561 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001562 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001563 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001564 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001565 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001566 if os.path.isabs(basepath):
1567 # Original file (probably with subdir pointing inside source tree)
1568 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001569 _copy_file(basepath, path,
1570 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001571 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001572 _move_file(os.path.join(local_files_dir, basepath), path,
1573 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001574 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001575 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001576 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001577 if os.path.dirname(path) + '/' == dl_dir:
1578 # This is a a downloaded patch file - we now need to
1579 # replace the entry in SRC_URI with our local version
1580 logger.info('Replacing remote patch %s with updated local version' % basepath)
1581 path = os.path.join(files_dir, basepath)
1582 _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath)
1583 updaterecipe = True
1584 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001585 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1586 _move_file(patchfn, path,
1587 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001588 updatefiles = True
1589 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001590 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001591 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001592 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001593 os.path.join(files_dir, basepath),
1594 dry_run_outdir=dry_run_outdir,
1595 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001596 srcuri.append('file://%s' % basepath)
1597 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001598 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001599 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001600 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001601 os.path.join(files_dir, basepath),
1602 dry_run_outdir=dry_run_outdir,
1603 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001604 srcuri.append('file://%s' % basepath)
1605 updaterecipe = True
1606 # Update recipe, if needed
1607 if _remove_file_entries(srcuri, remove_files)[0]:
1608 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001609 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001610 if not dry_run_outdir:
1611 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1612 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1613 {'SRC_URI': ' '.join(srcuri)},
1614 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001615 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001616 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001617 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001618 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001619 finally:
1620 shutil.rmtree(tempdir)
1621
Brad Bishop316dfdd2018-06-25 12:45:53 -04001622 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1623 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001624
1625def _guess_recipe_update_mode(srctree, rdata):
1626 """Guess the recipe update mode to use"""
1627 src_uri = (rdata.getVar('SRC_URI', False) or '').split()
1628 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1629 if not git_uris:
1630 return 'patch'
1631 # Just use the first URI for now
1632 uri = git_uris[0]
1633 # Check remote branch
1634 params = bb.fetch.decodeurl(uri)[5]
1635 upstr_branch = params['branch'] if 'branch' in params else 'master'
1636 # Check if current branch HEAD is found in upstream branch
1637 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1638 head_rev = stdout.rstrip()
1639 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1640 cwd=srctree)
1641 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1642 if 'origin/' + upstr_branch in remote_brs:
1643 return 'srcrev'
1644
1645 return 'patch'
1646
Brad Bishop316dfdd2018-06-25 12:45:53 -04001647def _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 -06001648 srctree = workspace[recipename]['srctree']
1649 if mode == 'auto':
1650 mode = _guess_recipe_update_mode(srctree, rd)
1651
Brad Bishop316dfdd2018-06-25 12:45:53 -04001652 override_branches = []
1653 mainbranch = None
1654 startbranch = None
1655 if not no_overrides:
1656 stdout, _ = bb.process.run('git branch', cwd=srctree)
1657 other_branches = []
1658 for line in stdout.splitlines():
1659 branchname = line[2:]
1660 if line.startswith('* '):
1661 startbranch = branchname
1662 if branchname.startswith(override_branch_prefix):
1663 override_branches.append(branchname)
1664 else:
1665 other_branches.append(branchname)
1666
1667 if override_branches:
1668 logger.debug('_update_recipe: override branches: %s' % override_branches)
1669 logger.debug('_update_recipe: other branches: %s' % other_branches)
1670 if startbranch.startswith(override_branch_prefix):
1671 if len(other_branches) == 1:
1672 mainbranch = other_branches[1]
1673 else:
1674 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1675 else:
1676 mainbranch = startbranch
1677
1678 checkedout = None
1679 anyupdated = False
1680 appendfile = None
1681 allremoved = []
1682 if override_branches:
1683 logger.info('Handling main branch (%s)...' % mainbranch)
1684 if startbranch != mainbranch:
1685 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1686 checkedout = mainbranch
1687 try:
1688 branchlist = [mainbranch] + override_branches
1689 for branch in branchlist:
1690 crd = bb.data.createCopy(rd)
1691 if branch != mainbranch:
1692 logger.info('Handling branch %s...' % branch)
1693 override = branch[len(override_branch_prefix):]
1694 crd.appendVar('OVERRIDES', ':%s' % override)
1695 bb.process.run('git checkout %s' % branch, cwd=srctree)
1696 checkedout = branch
1697
1698 if mode == 'srcrev':
1699 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1700 elif mode == 'patch':
1701 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)
1702 else:
1703 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1704 if updated:
1705 anyupdated = True
1706 if appendf:
1707 appendfile = appendf
1708 allremoved.extend(removed)
1709 finally:
1710 if startbranch and checkedout != startbranch:
1711 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1712
1713 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001714
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001715def update_recipe(args, config, basepath, workspace):
1716 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001717 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001718
1719 if args.append:
1720 if not os.path.exists(args.append):
1721 raise DevtoolError('bbappend destination layer directory "%s" '
1722 'does not exist' % args.append)
1723 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1724 raise DevtoolError('conf/layer.conf not found in bbappend '
1725 'destination layer "%s"' % args.append)
1726
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001727 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001728 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001729
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001730 rd = parse_recipe(config, tinfoil, args.recipename, True)
1731 if not rd:
1732 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001733
Brad Bishop316dfdd2018-06-25 12:45:53 -04001734 dry_run_output = None
1735 dry_run_outdir = None
1736 if args.dry_run:
1737 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1738 dry_run_outdir = dry_run_output.name
1739 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 -05001740
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001741 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001742 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001743 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001744 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 -06001745 finally:
1746 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001747
1748 return 0
1749
1750
1751def status(args, config, basepath, workspace):
1752 """Entry point for the devtool 'status' subcommand"""
1753 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001754 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001755 recipefile = value['recipefile']
1756 if recipefile:
1757 recipestr = ' (%s)' % recipefile
1758 else:
1759 recipestr = ''
1760 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001761 else:
1762 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')
1763 return 0
1764
1765
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001766def _reset(recipes, no_clean, config, basepath, workspace):
1767 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001768 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001769
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001770 def clean_preferred_provider(pn, layerconf_path):
1771 """Remove PREFERRED_PROVIDER from layer.conf'"""
1772 import re
1773 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1774 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1775 pprovider_found = False
1776 with open(layerconf_file, 'r') as f:
1777 lines = f.readlines()
1778 with open(new_layerconf_file, 'a') as nf:
1779 for line in lines:
1780 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1781 if not re.match(pprovider_exp, line):
1782 nf.write(line)
1783 else:
1784 pprovider_found = True
1785 if pprovider_found:
1786 shutil.move(new_layerconf_file, layerconf_file)
1787 else:
1788 os.remove(new_layerconf_file)
1789
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001790 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001791 if len(recipes) == 1:
1792 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1793 else:
1794 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001795 # If the recipe file itself was created in the workspace, and
1796 # it uses BBCLASSEXTEND, then we need to also clean the other
1797 # variants
1798 targets = []
1799 for recipe in recipes:
1800 targets.append(recipe)
1801 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001802 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001803 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001804 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001805 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001806 except bb.process.ExecutionError as e:
1807 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1808 'wish, you may specify -n/--no-clean to '
1809 'skip running this command when resetting' %
1810 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001811
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001812 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001813 _check_preserve(config, pn)
1814
Brad Bishop316dfdd2018-06-25 12:45:53 -04001815 appendfile = workspace[pn]['bbappend']
1816 if os.path.exists(appendfile):
1817 # This shouldn't happen, but is possible if devtool errored out prior to
1818 # writing the md5 file. We need to delete this here or the recipe won't
1819 # actually be reset
1820 os.remove(appendfile)
1821
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001822 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001823 def preservedir(origdir):
1824 if os.path.exists(origdir):
1825 for root, dirs, files in os.walk(origdir):
1826 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001827 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001828 _move_file(os.path.join(origdir, fn),
1829 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001830 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001831 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001832 os.rmdir(origdir)
1833
Brad Bishop316dfdd2018-06-25 12:45:53 -04001834 recipefile = workspace[pn]['recipefile']
1835 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1836 # This should always be true if recipefile is set, but just in case
1837 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001838 # We don't automatically create this dir next to appends, but the user can
1839 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1840
Brad Bishop316dfdd2018-06-25 12:45:53 -04001841 srctreebase = workspace[pn]['srctreebase']
1842 if os.path.isdir(srctreebase):
1843 if os.listdir(srctreebase):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001844 # We don't want to risk wiping out any work in progress
1845 logger.info('Leaving source tree %s as-is; if you no '
1846 'longer need it then please delete it manually'
Brad Bishop316dfdd2018-06-25 12:45:53 -04001847 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001848 else:
1849 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04001850 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001851
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001852 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001853
1854def reset(args, config, basepath, workspace):
1855 """Entry point for the devtool 'reset' subcommand"""
1856 import bb
1857 if args.recipename:
1858 if args.all:
1859 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1860 else:
1861 for recipe in args.recipename:
1862 check_workspace_recipe(workspace, recipe, checksrc=False)
1863 elif not args.all:
1864 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1865 "reset all recipes")
1866 if args.all:
1867 recipes = list(workspace.keys())
1868 else:
1869 recipes = args.recipename
1870
1871 _reset(recipes, args.no_clean, config, basepath, workspace)
1872
1873 return 0
1874
1875
1876def _get_layer(layername, d):
1877 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001878 layerdirs = d.getVar('BBLAYERS').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001879 layers = {os.path.basename(p): p for p in layerdirs}
1880 # Provide some shortcuts
1881 if layername.lower() in ['oe-core', 'openembedded-core']:
1882 layerdir = layers.get('meta', None)
1883 else:
1884 layerdir = layers.get(layername, None)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001885 return os.path.abspath(layerdir or layername)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001886
1887def finish(args, config, basepath, workspace):
1888 """Entry point for the devtool 'finish' subcommand"""
1889 import bb
1890 import oe.recipeutils
1891
1892 check_workspace_recipe(workspace, args.recipename)
1893
Brad Bishop316dfdd2018-06-25 12:45:53 -04001894 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
1895
1896 # Grab the equivalent of COREBASE without having to initialise tinfoil
1897 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
1898
1899 srctree = workspace[args.recipename]['srctree']
1900 check_git_repo_op(srctree, [corebasedir])
1901 dirty = check_git_repo_dirty(srctree)
1902 if dirty:
1903 if args.force:
1904 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
1905 else:
1906 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)
1907
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001908 no_clean = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001909 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1910 try:
1911 rd = parse_recipe(config, tinfoil, args.recipename, True)
1912 if not rd:
1913 return 1
1914
1915 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001916 recipefile = rd.getVar('FILE')
1917 recipedir = os.path.dirname(recipefile)
1918 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001919
1920 if not os.path.isdir(destlayerdir):
1921 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
1922
1923 if os.path.abspath(destlayerdir) == config.workspace_path:
1924 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
1925
1926 # If it's an upgrade, grab the original path
1927 origpath = None
1928 origfilelist = None
1929 append = workspace[args.recipename]['bbappend']
1930 with open(append, 'r') as f:
1931 for line in f:
1932 if line.startswith('# original_path:'):
1933 origpath = line.split(':')[1].strip()
1934 elif line.startswith('# original_files:'):
1935 origfilelist = line.split(':')[1].split()
1936
Brad Bishop316dfdd2018-06-25 12:45:53 -04001937 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
1938
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001939 if origlayerdir == config.workspace_path:
1940 # Recipe file itself is in workspace, update it there first
1941 appendlayerdir = None
1942 origrelpath = None
1943 if origpath:
1944 origlayerpath = oe.recipeutils.find_layerdir(origpath)
1945 if origlayerpath:
1946 origrelpath = os.path.relpath(origpath, origlayerpath)
1947 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
1948 if not destpath:
1949 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 -05001950 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
1951 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04001952 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001953 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)
1954
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001955 elif destlayerdir == origlayerdir:
1956 # Same layer, update the original recipe
1957 appendlayerdir = None
1958 destpath = None
1959 else:
1960 # Create/update a bbappend in the specified layer
1961 appendlayerdir = destlayerdir
1962 destpath = None
1963
Brad Bishop316dfdd2018-06-25 12:45:53 -04001964 # Actually update the recipe / bbappend
1965 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
1966 dry_run_output = None
1967 dry_run_outdir = None
1968 if args.dry_run:
1969 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1970 dry_run_outdir = dry_run_output.name
1971 updated, 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)
1972 removed = [os.path.relpath(pth, recipedir) for pth in removed]
1973
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001974 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04001975 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001976 for fn in origfilelist:
1977 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001978 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
1979 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
1980 if not args.dry_run:
1981 try:
1982 os.remove(fnp)
1983 except FileNotFoundError:
1984 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001985
1986 if origlayerdir == config.workspace_path and destpath:
1987 # Recipe file itself is in the workspace - need to move it and any
1988 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001989 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04001990 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001991 for root, _, files in os.walk(recipedir):
1992 for fn in files:
1993 srcpath = os.path.join(root, fn)
1994 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
1995 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001996 destfp = os.path.join(destdir, fn)
1997 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001998
Brad Bishop316dfdd2018-06-25 12:45:53 -04001999 if dry_run_outdir:
2000 import difflib
2001 comparelist = []
2002 for root, _, files in os.walk(dry_run_outdir):
2003 for fn in files:
2004 outf = os.path.join(root, fn)
2005 relf = os.path.relpath(outf, dry_run_outdir)
2006 logger.debug('dry-run: output file %s' % relf)
2007 if fn.endswith('.bb'):
2008 if origfilelist and origpath and destpath:
2009 # Need to match this up with the pre-upgrade recipe file
2010 for origf in origfilelist:
2011 if origf.endswith('.bb'):
2012 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2013 outf,
2014 os.path.abspath(os.path.join(destpath, relf))))
2015 break
2016 else:
2017 # Compare to the existing recipe
2018 comparelist.append((recipefile, outf, recipefile))
2019 elif fn.endswith('.bbappend'):
2020 if appendfile:
2021 if os.path.exists(appendfile):
2022 comparelist.append((appendfile, outf, appendfile))
2023 else:
2024 comparelist.append((None, outf, appendfile))
2025 else:
2026 if destpath:
2027 recipedest = destpath
2028 elif appendfile:
2029 recipedest = os.path.dirname(appendfile)
2030 else:
2031 recipedest = os.path.dirname(recipefile)
2032 destfp = os.path.join(recipedest, relf)
2033 if os.path.exists(destfp):
2034 comparelist.append((destfp, outf, destfp))
2035 output = ''
2036 for oldfile, newfile, newfileshow in comparelist:
2037 if oldfile:
2038 with open(oldfile, 'r') as f:
2039 oldlines = f.readlines()
2040 else:
2041 oldfile = '/dev/null'
2042 oldlines = []
2043 with open(newfile, 'r') as f:
2044 newlines = f.readlines()
2045 if not newfileshow:
2046 newfileshow = newfile
2047 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2048 difflines = list(diff)
2049 if difflines:
2050 output += ''.join(difflines)
2051 if output:
2052 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002053 finally:
2054 tinfoil.shutdown()
2055
2056 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002057 if args.dry_run:
2058 logger.info('Resetting recipe (dry-run)')
2059 else:
2060 _reset([args.recipename], no_clean=no_clean, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002061
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002062 return 0
2063
2064
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002065def get_default_srctree(config, recipename=''):
2066 """Get the default srctree path"""
2067 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2068 if recipename:
2069 return os.path.join(srctreeparent, 'sources', recipename)
2070 else:
2071 return os.path.join(srctreeparent, 'sources')
2072
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002073def register_commands(subparsers, context):
2074 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002075
2076 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002077 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002078 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.',
2079 group='starting', order=100)
2080 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.')
2081 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2082 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 -05002083 group = parser_add.add_mutually_exclusive_group()
2084 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2085 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002086 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 -05002087 parser_add.add_argument('--fetch-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002088 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002089 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 -05002090 group = parser_add.add_mutually_exclusive_group()
2091 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2092 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")
2093 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 -05002094 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')
2095 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')
2096 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002097 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2098 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 -06002099 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002100
2101 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002102 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.',
2103 group='starting', order=90)
2104 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2105 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 -05002106 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002107 group = parser_modify.add_mutually_exclusive_group()
2108 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2109 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 -05002110 group = parser_modify.add_mutually_exclusive_group()
2111 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2112 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002113 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 -04002114 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 -05002115 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002116 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002117
2118 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2119 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002120 group='advanced')
2121 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002122 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002123 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 -04002124 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 -05002125 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002126 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002127
2128 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2129 description='Synchronize the previously extracted source tree for an existing recipe',
2130 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2131 group='advanced')
2132 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2133 parser_sync.add_argument('srctree', help='Path to the source tree')
2134 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2135 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002136 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002137
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002138 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2139 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.',
2140 group='working', order=10)
2141 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2142 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2143 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)')
2144 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')
2145 parser_rename.set_defaults(func=rename)
2146
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002147 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002148 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.',
2149 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002150 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2151 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 -05002152 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002153 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2154 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')
2155 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 -04002156 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2157 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2158 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 -05002159 parser_update_recipe.set_defaults(func=update_recipe)
2160
2161 parser_status = subparsers.add_parser('status', help='Show workspace status',
2162 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002163 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002164 parser_status.set_defaults(func=status)
2165
2166 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002167 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 -05002168 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002169 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002170 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2171 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
2172 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002173
2174 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002175 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 -06002176 group='working', order=-100)
2177 parser_finish.add_argument('recipename', help='Recipe to finish')
2178 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.')
2179 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')
2180 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002181 parser_finish.add_argument('--force', '-f', action="store_true", help='Force continuing even if there are uncommitted changes in the source tree repository')
2182 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2183 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2184 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 -06002185 parser_finish.set_defaults(func=finish)