blob: a1e8e1d322787ee16cbce00233be542892b24df3 [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):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060069 logger.warn('Ambiguous argument "%s" - assuming you mean it to be the recipe name' % args.recipename)
70
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:
Patrick Williamsc0f7c042017-02-23 20:41:17 -060085 logger.warn('-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):
220 logger.warn('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)
221 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:
298 logger.warn('Set \'%s\' in order to use the recipe' % preferred_provider)
299 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])
512 if extra_overrides:
513 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
514
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500515 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500516
517 appendexisted = False
518 recipefile = d.getVar('FILE')
519 appendfile = recipe_to_append(recipefile, config)
520 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
521
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500522 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
523 # directory so that:
524 # (a) we pick up all files that get unpacked to the WORKDIR, and
525 # (b) we don't disturb the existing build
526 # However, with recipe-specific sysroots the sysroots for the recipe
527 # will be prepared under WORKDIR, and if we used the system temporary
528 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
529 # our attempts to hardlink files into the recipe-specific sysroots
530 # will fail on systems where /tmp is a different filesystem, and it
531 # would have to fall back to copying the files which is a waste of
532 # time. Put the temp directory under the WORKDIR to prevent that from
533 # being a problem.
534 tempbasedir = d.getVar('WORKDIR')
535 bb.utils.mkdirhier(tempbasedir)
536 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500537 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500538 tinfoil.logger.setLevel(logging.WARNING)
539
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500540 # FIXME this results in a cache reload under control of tinfoil, which is fine
541 # except we don't get the knotty progress bar
542
543 if os.path.exists(appendfile):
544 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
545 shutil.copyfile(appendfile, appendbackup)
546 else:
547 appendbackup = None
548 bb.utils.mkdirhier(os.path.dirname(appendfile))
549 logger.debug('writing append file %s' % appendfile)
550 with open(appendfile, 'a') as f:
551 f.write('###--- _extract_source\n')
552 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
553 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
554 if not is_kernel_yocto:
555 f.write('PATCHTOOL = "git"\n')
556 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400557 if extra_overrides:
558 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500559 f.write('inherit devtool-source\n')
560 f.write('###--- _extract_source\n')
561
562 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
563
564 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
565 bb.utils.mkdirhier(sstate_manifests)
566 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
567 with open(preservestampfile, 'w') as f:
568 f.write(d.getVar('STAMP'))
569 try:
570 if bb.data.inherits_class('kernel-yocto', d):
571 # We need to generate the kernel config
572 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500573 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500574 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500575
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500576 # Run the fetch + unpack tasks
577 res = tinfoil.build_targets(pn,
578 task,
579 handle_events=True)
580 finally:
581 if os.path.exists(preservestampfile):
582 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500583
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500584 if not res:
585 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500586
Brad Bishop316dfdd2018-06-25 12:45:53 -0400587 try:
588 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
589 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500590
Brad Bishop316dfdd2018-06-25 12:45:53 -0400591 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
592 srcsubdir = f.read()
593 except FileNotFoundError as e:
594 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
595 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500596
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500597 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
598 srctree_localdir = os.path.join(srctree, 'oe-local-files')
599
600 if sync:
601 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
602
603 # Move oe-local-files directory to srctree
604 # As the oe-local-files is not part of the constructed git tree,
605 # remove them directly during the synchrounizating might surprise
606 # the users. Instead, we move it to oe-local-files.bak and remind
607 # user in the log message.
608 if os.path.exists(srctree_localdir + '.bak'):
609 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
610
611 if os.path.exists(srctree_localdir):
612 logger.info('Backing up current local file directory %s' % srctree_localdir)
613 shutil.move(srctree_localdir, srctree_localdir + '.bak')
614
615 if os.path.exists(tempdir_localdir):
616 logger.info('Syncing local source files to srctree...')
617 shutil.copytree(tempdir_localdir, srctree_localdir)
618 else:
619 # Move oe-local-files directory to srctree
620 if os.path.exists(tempdir_localdir):
621 logger.info('Adding local source files to srctree...')
622 shutil.move(tempdir_localdir, srcsubdir)
623
624 shutil.move(srcsubdir, srctree)
625
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500626 if os.path.abspath(d.getVar('S')) == os.path.abspath(d.getVar('WORKDIR')):
627 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
628 # (otherwise the recipe won't build as expected)
629 local_files_dir = os.path.join(srctree, 'oe-local-files')
630 addfiles = []
631 for root, _, files in os.walk(local_files_dir):
632 relpth = os.path.relpath(root, local_files_dir)
633 if relpth != '.':
634 bb.utils.mkdirhier(os.path.join(srctree, relpth))
635 for fn in files:
636 if fn == '.gitignore':
637 continue
638 destpth = os.path.join(srctree, relpth, fn)
639 if os.path.exists(destpth):
640 os.unlink(destpth)
641 os.symlink('oe-local-files/%s' % fn, destpth)
642 addfiles.append(os.path.join(relpth, fn))
643 if addfiles:
644 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
645 useroptions = []
646 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=d)
647 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)
648
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500649 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500650 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500651 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500652
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500653 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500654 if appendbackup:
655 shutil.copyfile(appendbackup, appendfile)
656 elif os.path.exists(appendfile):
657 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500658 if keep_temp:
659 logger.info('Preserving temporary directory %s' % tempdir)
660 else:
661 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400662 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500663
664def _add_md5(config, recipename, filename):
665 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
666 import bb.utils
667
668 def addfile(fn):
669 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500670 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
671 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
672 f.seek(0, os.SEEK_SET)
673 if not md5_str in f.read():
674 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500675
676 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500677 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500678 for f in files:
679 addfile(os.path.join(root, f))
680 else:
681 addfile(filename)
682
683def _check_preserve(config, recipename):
684 """Check if a file was manually changed and needs to be saved in 'attic'
685 directory"""
686 import bb.utils
687 origfile = os.path.join(config.workspace_path, '.devtool_md5')
688 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500689 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500690 with open(origfile, 'r') as f:
691 with open(newfile, 'w') as tf:
692 for line in f.readlines():
693 splitline = line.rstrip().split('|')
694 if splitline[0] == recipename:
695 removefile = os.path.join(config.workspace_path, splitline[1])
696 try:
697 md5 = bb.utils.md5_file(removefile)
698 except IOError as err:
699 if err.errno == 2:
700 # File no longer exists, skip it
701 continue
702 else:
703 raise
704 if splitline[2] != md5:
705 bb.utils.mkdirhier(preservepath)
706 preservefile = os.path.basename(removefile)
707 logger.warn('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
708 shutil.move(removefile, os.path.join(preservepath, preservefile))
709 else:
710 os.remove(removefile)
711 else:
712 tf.write(line)
713 os.rename(newfile, origfile)
714
715def modify(args, config, basepath, workspace):
716 """Entry point for the devtool 'modify' subcommand"""
717 import bb
718 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400719 import oe.patch
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500720
721 if args.recipename in workspace:
722 raise DevtoolError("recipe %s is already in your workspace" %
723 args.recipename)
724
Brad Bishop316dfdd2018-06-25 12:45:53 -0400725 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600726 try:
727 rd = parse_recipe(config, tinfoil, args.recipename, True)
728 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500729 return 1
730
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500731 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600732 if pn != args.recipename:
733 logger.info('Mapping %s to %s' % (args.recipename, pn))
734 if pn in workspace:
735 raise DevtoolError("recipe %s is already in your workspace" %
736 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500737
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600738 if args.srctree:
739 srctree = os.path.abspath(args.srctree)
740 else:
741 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600743 if args.no_extract and not os.path.isdir(srctree):
744 raise DevtoolError("--no-extract specified and source path %s does "
745 "not exist or is not a directory" %
746 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600747
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500748 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600749 appendfile = recipe_to_append(recipefile, config, args.wildcard)
750 if os.path.exists(appendfile):
751 raise DevtoolError("Another variant of recipe %s is already in your "
752 "workspace (only one variant of a recipe can "
753 "currently be worked on at once)"
754 % pn)
755
756 _check_compatible_recipe(pn, rd)
757
758 initial_rev = None
759 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400760 check_commits = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600761 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400762 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 -0500763 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600764 return 1
765 logger.info('Source tree extracted to %s' % srctree)
766 # Get list of commits since this revision
767 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
768 commits = stdout.split()
Brad Bishop316dfdd2018-06-25 12:45:53 -0400769 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600770 else:
771 if os.path.exists(os.path.join(srctree, '.git')):
772 # Check if it's a tree previously extracted by us
773 try:
774 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
775 except bb.process.ExecutionError:
776 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400777 if stdout:
778 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600779 for line in stdout.splitlines():
780 if line.startswith('*'):
781 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
782 initial_rev = stdout.rstrip()
783 if not initial_rev:
784 # Otherwise, just grab the head revision
785 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
786 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500787
Brad Bishop316dfdd2018-06-25 12:45:53 -0400788 branch_patches = {}
789 if check_commits:
790 # Check if there are override branches
791 (stdout, _) = bb.process.run('git branch', cwd=srctree)
792 branches = []
793 for line in stdout.rstrip().splitlines():
794 branchname = line[2:].rstrip()
795 if branchname.startswith(override_branch_prefix):
796 branches.append(branchname)
797 if branches:
798 logger.warn('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))
799 branches.insert(0, args.branch)
800 seen_patches = []
801 for branch in branches:
802 branch_patches[branch] = []
803 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
804 for line in stdout.splitlines():
805 line = line.strip()
806 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
807 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
808 if not origpatch in seen_patches:
809 seen_patches.append(origpatch)
810 branch_patches[branch].append(origpatch)
811
812 # Need to grab this here in case the source is within a subdirectory
813 srctreebase = srctree
814
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600815 # Check that recipe isn't using a shared workdir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500816 s = os.path.abspath(rd.getVar('S'))
817 workdir = os.path.abspath(rd.getVar('WORKDIR'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600818 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
819 # Handle if S is set to a subdirectory of the source
820 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
821 srctree = os.path.join(srctree, srcsubdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500822
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600823 bb.utils.mkdirhier(os.path.dirname(appendfile))
824 with open(appendfile, 'w') as f:
825 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
826 # Local files can be modified/tracked in separate subdir under srctree
827 # Mostly useful for packages with S != WORKDIR
828 f.write('FILESPATH_prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400829 os.path.join(srctreebase, 'oe-local-files'))
830 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500831
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600832 f.write('\ninherit externalsrc\n')
833 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
834 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500835
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600836 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
837 if b_is_s:
838 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500839
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600840 if bb.data.inherits_class('kernel', rd):
841 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500842 'do_fetch do_unpack do_kernel_configme do_kernel_configcheck"\n')
843 f.write('\ndo_patch() {\n'
844 ' :\n'
845 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600846 f.write('\ndo_configure_append() {\n'
847 ' cp ${B}/.config ${S}/.config.baseline\n'
848 ' ln -sfT ${B}/.config ${S}/.config.new\n'
849 '}\n')
850 if initial_rev:
851 f.write('\n# initial_rev: %s\n' % initial_rev)
852 for commit in commits:
853 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400854 if branch_patches:
855 for branch in branch_patches:
856 if branch == args.branch:
857 continue
858 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500859
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500860 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
861
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600862 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500863
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600864 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500865
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600866 finally:
867 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500868
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500869 return 0
870
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500871
872def rename(args, config, basepath, workspace):
873 """Entry point for the devtool 'rename' subcommand"""
874 import bb
875 import oe.recipeutils
876
877 check_workspace_recipe(workspace, args.recipename)
878
879 if not (args.newname or args.version):
880 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
881
882 recipefile = workspace[args.recipename]['recipefile']
883 if not recipefile:
884 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
885
886 if args.newname and args.newname != args.recipename:
887 reason = oe.recipeutils.validate_pn(args.newname)
888 if reason:
889 raise DevtoolError(reason)
890 newname = args.newname
891 else:
892 newname = args.recipename
893
894 append = workspace[args.recipename]['bbappend']
895 appendfn = os.path.splitext(os.path.basename(append))[0]
896 splitfn = appendfn.split('_')
897 if len(splitfn) > 1:
898 origfnver = appendfn.split('_')[1]
899 else:
900 origfnver = ''
901
902 recipefilemd5 = None
903 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
904 try:
905 rd = parse_recipe(config, tinfoil, args.recipename, True)
906 if not rd:
907 return 1
908
909 bp = rd.getVar('BP')
910 bpn = rd.getVar('BPN')
911 if newname != args.recipename:
912 localdata = rd.createCopy()
913 localdata.setVar('PN', newname)
914 newbpn = localdata.getVar('BPN')
915 else:
916 newbpn = bpn
917 s = rd.getVar('S', False)
918 src_uri = rd.getVar('SRC_URI', False)
919 pv = rd.getVar('PV')
920
921 # Correct variable values that refer to the upstream source - these
922 # values must stay the same, so if the name/version are changing then
923 # we need to fix them up
924 new_s = s
925 new_src_uri = src_uri
926 if newbpn != bpn:
927 # ${PN} here is technically almost always incorrect, but people do use it
928 new_s = new_s.replace('${BPN}', bpn)
929 new_s = new_s.replace('${PN}', bpn)
930 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
931 new_src_uri = new_src_uri.replace('${BPN}', bpn)
932 new_src_uri = new_src_uri.replace('${PN}', bpn)
933 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
934 if args.version and origfnver == pv:
935 new_s = new_s.replace('${PV}', pv)
936 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
937 new_src_uri = new_src_uri.replace('${PV}', pv)
938 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
939 patchfields = {}
940 if new_s != s:
941 patchfields['S'] = new_s
942 if new_src_uri != src_uri:
943 patchfields['SRC_URI'] = new_src_uri
944 if patchfields:
945 recipefilemd5 = bb.utils.md5_file(recipefile)
946 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
947 newrecipefilemd5 = bb.utils.md5_file(recipefile)
948 finally:
949 tinfoil.shutdown()
950
951 if args.version:
952 newver = args.version
953 else:
954 newver = origfnver
955
956 if newver:
957 newappend = '%s_%s.bbappend' % (newname, newver)
958 newfile = '%s_%s.bb' % (newname, newver)
959 else:
960 newappend = '%s.bbappend' % newname
961 newfile = '%s.bb' % newname
962
963 oldrecipedir = os.path.dirname(recipefile)
964 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
965 if oldrecipedir != newrecipedir:
966 bb.utils.mkdirhier(newrecipedir)
967
968 newappend = os.path.join(os.path.dirname(append), newappend)
969 newfile = os.path.join(newrecipedir, newfile)
970
971 # Rename bbappend
972 logger.info('Renaming %s to %s' % (append, newappend))
973 os.rename(append, newappend)
974 # Rename recipe file
975 logger.info('Renaming %s to %s' % (recipefile, newfile))
976 os.rename(recipefile, newfile)
977
978 # Rename source tree if it's the default path
979 appendmd5 = None
980 if not args.no_srctree:
981 srctree = workspace[args.recipename]['srctree']
982 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
983 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
984 logger.info('Renaming %s to %s' % (srctree, newsrctree))
985 shutil.move(srctree, newsrctree)
986 # Correct any references (basically EXTERNALSRC*) in the .bbappend
987 appendmd5 = bb.utils.md5_file(newappend)
988 appendlines = []
989 with open(newappend, 'r') as f:
990 for line in f:
991 appendlines.append(line)
992 with open(newappend, 'w') as f:
993 for line in appendlines:
994 if srctree in line:
995 line = line.replace(srctree, newsrctree)
996 f.write(line)
997 newappendmd5 = bb.utils.md5_file(newappend)
998
999 bpndir = None
1000 newbpndir = None
1001 if newbpn != bpn:
1002 bpndir = os.path.join(oldrecipedir, bpn)
1003 if os.path.exists(bpndir):
1004 newbpndir = os.path.join(newrecipedir, newbpn)
1005 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1006 shutil.move(bpndir, newbpndir)
1007
1008 bpdir = None
1009 newbpdir = None
1010 if newver != origfnver or newbpn != bpn:
1011 bpdir = os.path.join(oldrecipedir, bp)
1012 if os.path.exists(bpdir):
1013 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1014 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1015 shutil.move(bpdir, newbpdir)
1016
1017 if oldrecipedir != newrecipedir:
1018 # Move any stray files and delete the old recipe directory
1019 for entry in os.listdir(oldrecipedir):
1020 oldpath = os.path.join(oldrecipedir, entry)
1021 newpath = os.path.join(newrecipedir, entry)
1022 logger.info('Renaming %s to %s' % (oldpath, newpath))
1023 shutil.move(oldpath, newpath)
1024 os.rmdir(oldrecipedir)
1025
1026 # Now take care of entries in .devtool_md5
1027 md5entries = []
1028 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1029 for line in f:
1030 md5entries.append(line)
1031
1032 if bpndir and newbpndir:
1033 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1034 else:
1035 relbpndir = None
1036 if bpdir and newbpdir:
1037 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1038 else:
1039 relbpdir = None
1040
1041 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1042 for entry in md5entries:
1043 splitentry = entry.rstrip().split('|')
1044 if len(splitentry) > 2:
1045 if splitentry[0] == args.recipename:
1046 splitentry[0] = newname
1047 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1048 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1049 if appendmd5 and splitentry[2] == appendmd5:
1050 splitentry[2] = newappendmd5
1051 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1052 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1053 if recipefilemd5 and splitentry[2] == recipefilemd5:
1054 splitentry[2] = newrecipefilemd5
1055 elif relbpndir and splitentry[1].startswith(relbpndir):
1056 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1057 elif relbpdir and splitentry[1].startswith(relbpdir):
1058 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1059 entry = '|'.join(splitentry) + '\n'
1060 f.write(entry)
1061 return 0
1062
1063
Brad Bishop316dfdd2018-06-25 12:45:53 -04001064def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001065 """Get initial and update rev of a recipe. These are the start point of the
1066 whole patchset and start point for the patches to be re-generated/updated.
1067 """
1068 import bb
1069
Brad Bishop316dfdd2018-06-25 12:45:53 -04001070 # Get current branch
1071 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1072 cwd=srctree)
1073 branchname = stdout.rstrip()
1074
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001075 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001076 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001077 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001078 with open(recipe_path, 'r') as f:
1079 for line in f:
1080 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001081 if not initial_rev:
1082 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001083 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001084 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001085 elif line.startswith('# patches_%s:' % branchname):
1086 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001087
1088 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001089 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001090 if initial_rev:
1091 # Find first actually changed revision
1092 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1093 initial_rev, cwd=srctree)
1094 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001095 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001096 if newcommits[i] == commits[i]:
1097 update_rev = commits[i]
1098
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001099 try:
1100 stdout, _ = bb.process.run('git cherry devtool-patched',
1101 cwd=srctree)
1102 except bb.process.ExecutionError as err:
1103 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001104
Brad Bishop316dfdd2018-06-25 12:45:53 -04001105 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001106 changed_revs = []
1107 for line in stdout.splitlines():
1108 if line.startswith('+ '):
1109 rev = line.split()[1]
1110 if rev in newcommits:
1111 changed_revs.append(rev)
1112
Brad Bishop316dfdd2018-06-25 12:45:53 -04001113 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001114
1115def _remove_file_entries(srcuri, filelist):
1116 """Remove file:// entries from SRC_URI"""
1117 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001118 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001119 for fname in filelist:
1120 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001121 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001122 if (srcuri[i].startswith('file://') and
1123 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001124 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001125 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001126 srcuri.pop(i)
1127 break
1128 return entries, remaining
1129
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001130def _replace_srcuri_entry(srcuri, filename, newentry):
1131 """Replace entry corresponding to specified file with a new entry"""
1132 basename = os.path.basename(filename)
1133 for i in range(len(srcuri)):
1134 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1135 srcuri.pop(i)
1136 srcuri.insert(i, newentry)
1137 break
1138
Brad Bishop316dfdd2018-06-25 12:45:53 -04001139def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001140 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001141
1142 dry_run_suffix = ' (dry-run)' if dry_run else ''
1143
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001144 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001145 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001146 if not destpath:
1147 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001148 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001149
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001150 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001151 if not no_report_remove:
1152 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1153 if not dry_run:
1154 # FIXME "git rm" here would be nice if the file in question is
1155 # tracked
1156 # FIXME there's a chance that this file is referred to by
1157 # another recipe, in which case deleting wouldn't be the
1158 # right thing to do
1159 os.remove(path)
1160 # Remove directory if empty
1161 try:
1162 os.rmdir(os.path.dirname(path))
1163 except OSError as ose:
1164 if ose.errno != errno.ENOTEMPTY:
1165 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001166
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001167
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001168def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001169 """Export patches from srctree to given location.
1170 Returns three-tuple of dicts:
1171 1. updated - patches that already exist in SRCURI
1172 2. added - new patches that don't exist in SRCURI
1173 3 removed - patches that exist in SRCURI but not in exported patches
1174 In each dict the key is the 'basepath' of the URI and value is the
1175 absolute path to the existing file in recipe space (if any).
1176 """
1177 import oe.recipeutils
1178 from oe.patch import GitApplyTree
1179 updated = OrderedDict()
1180 added = OrderedDict()
1181 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1182
1183 existing_patches = dict((os.path.basename(path), path) for path in
1184 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001185 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001186
1187 # Generate patches from Git, exclude local files directory
1188 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1189 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1190
1191 new_patches = sorted(os.listdir(destdir))
1192 for new_patch in new_patches:
1193 # Strip numbering from patch names. If it's a git sequence named patch,
1194 # the numbers might not match up since we are starting from a different
1195 # revision This does assume that people are using unique shortlog
1196 # values, but they ought to be anyway...
1197 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001198 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001199 for old_patch in existing_patches:
1200 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001201 old_basename_splitext = os.path.splitext(old_basename)
1202 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1203 old_patch_noext = os.path.splitext(old_patch)[0]
1204 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001205 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001206 elif new_basename == old_basename:
1207 match_name = old_patch
1208 break
1209 if match_name:
1210 # Rename patch files
1211 if new_patch != match_name:
1212 os.rename(os.path.join(destdir, new_patch),
1213 os.path.join(destdir, match_name))
1214 # Need to pop it off the list now before checking changed_revs
1215 oldpath = existing_patches.pop(old_patch)
1216 if changed_revs is not None:
1217 # Avoid updating patches that have not actually changed
1218 with open(os.path.join(destdir, match_name), 'r') as f:
1219 firstlineitems = f.readline().split()
1220 # Looking for "From <hash>" line
1221 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1222 if not firstlineitems[1] in changed_revs:
1223 continue
1224 # Recompress if necessary
1225 if oldpath.endswith(('.gz', '.Z')):
1226 bb.process.run(['gzip', match_name], cwd=destdir)
1227 if oldpath.endswith('.gz'):
1228 match_name += '.gz'
1229 else:
1230 match_name += '.Z'
1231 elif oldpath.endswith('.bz2'):
1232 bb.process.run(['bzip2', match_name], cwd=destdir)
1233 match_name += '.bz2'
1234 updated[match_name] = oldpath
1235 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001236 added[new_patch] = None
1237 return (updated, added, existing_patches)
1238
1239
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001240def _create_kconfig_diff(srctree, rd, outfile):
1241 """Create a kconfig fragment"""
1242 # Only update config fragment if both config files exist
1243 orig_config = os.path.join(srctree, '.config.baseline')
1244 new_config = os.path.join(srctree, '.config.new')
1245 if os.path.exists(orig_config) and os.path.exists(new_config):
1246 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1247 '--unchanged-line-format=', orig_config, new_config]
1248 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1249 stderr=subprocess.PIPE)
1250 stdout, stderr = pipe.communicate()
1251 if pipe.returncode == 1:
1252 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001253 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001254 fobj.write(stdout)
1255 elif pipe.returncode == 0:
1256 logger.info("Would remove config fragment %s" % outfile)
1257 if os.path.exists(outfile):
1258 # Remove fragment file in case of empty diff
1259 logger.info("Removing config fragment %s" % outfile)
1260 os.unlink(outfile)
1261 else:
1262 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1263 return True
1264 return False
1265
1266
Brad Bishop316dfdd2018-06-25 12:45:53 -04001267def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001268 """Copy local files from srctree to given location.
1269 Returns three-tuple of dicts:
1270 1. updated - files that already exist in SRCURI
1271 2. added - new files files that don't exist in SRCURI
1272 3 removed - files that exist in SRCURI but not in exported files
1273 In each dict the key is the 'basepath' of the URI and value is the
1274 absolute path to the existing file in recipe space (if any).
1275 """
1276 import oe.recipeutils
1277
1278 # Find out local files (SRC_URI files that exist in the "recipe space").
1279 # Local files that reside in srctree are not included in patch generation.
1280 # Instead they are directly copied over the original source files (in
1281 # recipe space).
1282 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1283 new_set = None
1284 updated = OrderedDict()
1285 added = OrderedDict()
1286 removed = OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001287 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001288 git_files = _git_ls_tree(srctree)
1289 if 'oe-local-files' in git_files:
1290 # If tracked by Git, take the files from srctree HEAD. First get
1291 # the tree object of the directory
1292 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1293 tree = git_files['oe-local-files'][2]
1294 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1295 env=dict(os.environ, GIT_WORK_TREE=destdir,
1296 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001297 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001298 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001299 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001300 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001301 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001302 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001303 else:
1304 new_set = []
1305
1306 # Special handling for kernel config
1307 if bb.data.inherits_class('kernel-yocto', rd):
1308 fragment_fn = 'devtool-fragment.cfg'
1309 fragment_path = os.path.join(destdir, fragment_fn)
1310 if _create_kconfig_diff(srctree, rd, fragment_path):
1311 if os.path.exists(fragment_path):
1312 if fragment_fn not in new_set:
1313 new_set.append(fragment_fn)
1314 # Copy fragment to local-files
1315 if os.path.isdir(local_files_dir):
1316 shutil.copy2(fragment_path, local_files_dir)
1317 else:
1318 if fragment_fn in new_set:
1319 new_set.remove(fragment_fn)
1320 # Remove fragment from local-files
1321 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1322 os.unlink(os.path.join(local_files_dir, fragment_fn))
1323
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001324 if new_set is not None:
1325 for fname in new_set:
1326 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001327 origpath = existing_files.pop(fname)
1328 workpath = os.path.join(local_files_dir, fname)
1329 if not filecmp.cmp(origpath, workpath):
1330 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001331 elif fname != '.gitignore':
1332 added[fname] = None
1333
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001334 workdir = rd.getVar('WORKDIR')
1335 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001336 if not s.endswith(os.sep):
1337 s += os.sep
1338
1339 if workdir != s:
1340 # Handle files where subdir= was specified
1341 for fname in list(existing_files.keys()):
1342 # FIXME handle both subdir starting with BP and not?
1343 fworkpath = os.path.join(workdir, fname)
1344 if fworkpath.startswith(s):
1345 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1346 if os.path.exists(fpath):
1347 origpath = existing_files.pop(fname)
1348 if not filecmp.cmp(origpath, fpath):
1349 updated[fpath] = origpath
1350
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001351 removed = existing_files
1352 return (updated, added, removed)
1353
1354
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001355def _determine_files_dir(rd):
1356 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001357 recipedir = rd.getVar('FILE_DIRNAME')
1358 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001359 relpth = os.path.relpath(entry, recipedir)
1360 if not os.sep in relpth:
1361 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1362 if os.path.isdir(entry):
1363 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001364 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001365
1366
Brad Bishop316dfdd2018-06-25 12:45:53 -04001367def _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 -05001368 """Implement the 'srcrev' mode of update-recipe"""
1369 import bb
1370 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001371
Brad Bishop316dfdd2018-06-25 12:45:53 -04001372 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1373
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001374 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001375 recipedir = os.path.basename(recipefile)
1376 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001377
1378 # Get HEAD revision
1379 try:
1380 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1381 except bb.process.ExecutionError as err:
1382 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1383 (srctree, err))
1384 srcrev = stdout.strip()
1385 if len(srcrev) != 40:
1386 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1387
1388 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001389 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001390 patchfields = {}
1391 patchfields['SRCREV'] = srcrev
1392 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001393 srcuri = orig_src_uri.split()
1394 tempdir = tempfile.mkdtemp(prefix='devtool')
1395 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001396 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001397 try:
1398 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001399 srctreebase = workspace[recipename]['srctreebase']
1400 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001401 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001402 # Find list of existing patches in recipe file
1403 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001404 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001405 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1406 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001407 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 -05001408
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001409 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001410 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001411 if remove_files:
1412 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1413 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001414
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001415 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001416 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001417 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001418 removevalues = {}
1419 if update_srcuri:
1420 removevalues = {'SRC_URI': removedentries}
1421 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001422 if dry_run_outdir:
1423 logger.info('Creating bbappend (dry-run)')
1424 else:
1425 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1426 rd, appendlayerdir, files, wildcardver=wildcard_version,
1427 extralines=patchfields, removevalues=removevalues,
1428 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001429 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001430 files_dir = _determine_files_dir(rd)
1431 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001432 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001433 if os.path.isabs(basepath):
1434 # Original file (probably with subdir pointing inside source tree)
1435 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001436 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001437 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001438 _move_file(os.path.join(local_files_dir, basepath), path,
1439 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001440 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001441 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001442 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001443 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001444 os.path.join(files_dir, basepath),
1445 dry_run_outdir=dry_run_outdir,
1446 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001447 srcuri.append('file://%s' % basepath)
1448 update_srcuri = True
1449 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001450 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001451 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001452 finally:
1453 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001454 if not 'git://' in orig_src_uri:
1455 logger.info('You will need to update SRC_URI within the recipe to '
1456 'point to a git repository where you have pushed your '
1457 'changes')
1458
Brad Bishop316dfdd2018-06-25 12:45:53 -04001459 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1460 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001461
Brad Bishop316dfdd2018-06-25 12:45:53 -04001462def _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 -05001463 """Implement the 'patch' mode of update-recipe"""
1464 import bb
1465 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001466
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001467 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001468 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001469 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001470 if not os.path.exists(append):
1471 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001472 recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001473
Brad Bishop316dfdd2018-06-25 12:45:53 -04001474 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 -05001475 if not initial_rev:
1476 raise DevtoolError('Unable to find initial revision - please specify '
1477 'it with --initial-rev')
1478
Brad Bishop316dfdd2018-06-25 12:45:53 -04001479 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001480 dl_dir = rd.getVar('DL_DIR')
1481 if not dl_dir.endswith('/'):
1482 dl_dir += '/'
1483
Brad Bishop316dfdd2018-06-25 12:45:53 -04001484 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1485
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001486 tempdir = tempfile.mkdtemp(prefix='devtool')
1487 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001488 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001489 if filter_patches:
1490 upd_f = {}
1491 new_f = {}
1492 del_f = {}
1493 else:
1494 srctreebase = workspace[recipename]['srctreebase']
1495 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001496
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001497 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001498 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001499 # Get all patches from source tree and check if any should be removed
1500 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001501 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1502 all_patches_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001503 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001504 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001505
1506 # Get updated patches from source tree
1507 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001508 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1509 patches_dir, changed_revs)
1510 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1511 if filter_patches:
1512 new_p = {}
1513 upd_p = {k:v for k,v in upd_p.items() if k in filter_patches}
1514 remove_files = [f for f in remove_files if f in filter_patches]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001515 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001516 updaterecipe = False
1517 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001518 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001519 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001520 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001521 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001522 files.update(dict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001523 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001524 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001525 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001526 if remove_files:
1527 removedentries, remaining = _remove_file_entries(
1528 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001529 if removedentries or remaining:
1530 remaining = ['file://' + os.path.basename(item) for
1531 item in remaining]
1532 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001533 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001534 rd, appendlayerdir, files,
1535 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001536 removevalues=removevalues,
1537 redirect_output=dry_run_outdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001538 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001539 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001540 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001541 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001542 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001543 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001544 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001545 if os.path.isabs(basepath):
1546 # Original file (probably with subdir pointing inside source tree)
1547 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001548 _copy_file(basepath, path,
1549 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001550 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001551 _move_file(os.path.join(local_files_dir, basepath), path,
1552 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001553 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001554 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001555 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001556 if os.path.dirname(path) + '/' == dl_dir:
1557 # This is a a downloaded patch file - we now need to
1558 # replace the entry in SRC_URI with our local version
1559 logger.info('Replacing remote patch %s with updated local version' % basepath)
1560 path = os.path.join(files_dir, basepath)
1561 _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath)
1562 updaterecipe = True
1563 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001564 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1565 _move_file(patchfn, path,
1566 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001567 updatefiles = True
1568 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001569 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001570 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001571 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001572 os.path.join(files_dir, basepath),
1573 dry_run_outdir=dry_run_outdir,
1574 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001575 srcuri.append('file://%s' % basepath)
1576 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001577 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001578 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001579 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001580 os.path.join(files_dir, basepath),
1581 dry_run_outdir=dry_run_outdir,
1582 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001583 srcuri.append('file://%s' % basepath)
1584 updaterecipe = True
1585 # Update recipe, if needed
1586 if _remove_file_entries(srcuri, remove_files)[0]:
1587 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001588 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001589 if not dry_run_outdir:
1590 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1591 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1592 {'SRC_URI': ' '.join(srcuri)},
1593 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001594 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001595 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001596 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001597 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001598 finally:
1599 shutil.rmtree(tempdir)
1600
Brad Bishop316dfdd2018-06-25 12:45:53 -04001601 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1602 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001603
1604def _guess_recipe_update_mode(srctree, rdata):
1605 """Guess the recipe update mode to use"""
1606 src_uri = (rdata.getVar('SRC_URI', False) or '').split()
1607 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1608 if not git_uris:
1609 return 'patch'
1610 # Just use the first URI for now
1611 uri = git_uris[0]
1612 # Check remote branch
1613 params = bb.fetch.decodeurl(uri)[5]
1614 upstr_branch = params['branch'] if 'branch' in params else 'master'
1615 # Check if current branch HEAD is found in upstream branch
1616 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1617 head_rev = stdout.rstrip()
1618 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1619 cwd=srctree)
1620 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1621 if 'origin/' + upstr_branch in remote_brs:
1622 return 'srcrev'
1623
1624 return 'patch'
1625
Brad Bishop316dfdd2018-06-25 12:45:53 -04001626def _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 -06001627 srctree = workspace[recipename]['srctree']
1628 if mode == 'auto':
1629 mode = _guess_recipe_update_mode(srctree, rd)
1630
Brad Bishop316dfdd2018-06-25 12:45:53 -04001631 override_branches = []
1632 mainbranch = None
1633 startbranch = None
1634 if not no_overrides:
1635 stdout, _ = bb.process.run('git branch', cwd=srctree)
1636 other_branches = []
1637 for line in stdout.splitlines():
1638 branchname = line[2:]
1639 if line.startswith('* '):
1640 startbranch = branchname
1641 if branchname.startswith(override_branch_prefix):
1642 override_branches.append(branchname)
1643 else:
1644 other_branches.append(branchname)
1645
1646 if override_branches:
1647 logger.debug('_update_recipe: override branches: %s' % override_branches)
1648 logger.debug('_update_recipe: other branches: %s' % other_branches)
1649 if startbranch.startswith(override_branch_prefix):
1650 if len(other_branches) == 1:
1651 mainbranch = other_branches[1]
1652 else:
1653 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1654 else:
1655 mainbranch = startbranch
1656
1657 checkedout = None
1658 anyupdated = False
1659 appendfile = None
1660 allremoved = []
1661 if override_branches:
1662 logger.info('Handling main branch (%s)...' % mainbranch)
1663 if startbranch != mainbranch:
1664 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1665 checkedout = mainbranch
1666 try:
1667 branchlist = [mainbranch] + override_branches
1668 for branch in branchlist:
1669 crd = bb.data.createCopy(rd)
1670 if branch != mainbranch:
1671 logger.info('Handling branch %s...' % branch)
1672 override = branch[len(override_branch_prefix):]
1673 crd.appendVar('OVERRIDES', ':%s' % override)
1674 bb.process.run('git checkout %s' % branch, cwd=srctree)
1675 checkedout = branch
1676
1677 if mode == 'srcrev':
1678 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1679 elif mode == 'patch':
1680 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)
1681 else:
1682 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1683 if updated:
1684 anyupdated = True
1685 if appendf:
1686 appendfile = appendf
1687 allremoved.extend(removed)
1688 finally:
1689 if startbranch and checkedout != startbranch:
1690 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1691
1692 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001693
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001694def update_recipe(args, config, basepath, workspace):
1695 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001696 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001697
1698 if args.append:
1699 if not os.path.exists(args.append):
1700 raise DevtoolError('bbappend destination layer directory "%s" '
1701 'does not exist' % args.append)
1702 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1703 raise DevtoolError('conf/layer.conf not found in bbappend '
1704 'destination layer "%s"' % args.append)
1705
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001706 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001707 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001708
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001709 rd = parse_recipe(config, tinfoil, args.recipename, True)
1710 if not rd:
1711 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001712
Brad Bishop316dfdd2018-06-25 12:45:53 -04001713 dry_run_output = None
1714 dry_run_outdir = None
1715 if args.dry_run:
1716 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1717 dry_run_outdir = dry_run_output.name
1718 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 -05001719
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001720 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001721 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001722 if rf.startswith(config.workspace_path):
1723 logger.warn('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)
1724 finally:
1725 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001726
1727 return 0
1728
1729
1730def status(args, config, basepath, workspace):
1731 """Entry point for the devtool 'status' subcommand"""
1732 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001733 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001734 recipefile = value['recipefile']
1735 if recipefile:
1736 recipestr = ' (%s)' % recipefile
1737 else:
1738 recipestr = ''
1739 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001740 else:
1741 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')
1742 return 0
1743
1744
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001745def _reset(recipes, no_clean, config, basepath, workspace):
1746 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001747 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001748
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001749 def clean_preferred_provider(pn, layerconf_path):
1750 """Remove PREFERRED_PROVIDER from layer.conf'"""
1751 import re
1752 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1753 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1754 pprovider_found = False
1755 with open(layerconf_file, 'r') as f:
1756 lines = f.readlines()
1757 with open(new_layerconf_file, 'a') as nf:
1758 for line in lines:
1759 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1760 if not re.match(pprovider_exp, line):
1761 nf.write(line)
1762 else:
1763 pprovider_found = True
1764 if pprovider_found:
1765 shutil.move(new_layerconf_file, layerconf_file)
1766 else:
1767 os.remove(new_layerconf_file)
1768
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001769 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001770 if len(recipes) == 1:
1771 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1772 else:
1773 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001774 # If the recipe file itself was created in the workspace, and
1775 # it uses BBCLASSEXTEND, then we need to also clean the other
1776 # variants
1777 targets = []
1778 for recipe in recipes:
1779 targets.append(recipe)
1780 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001781 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001782 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001783 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001784 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001785 except bb.process.ExecutionError as e:
1786 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1787 'wish, you may specify -n/--no-clean to '
1788 'skip running this command when resetting' %
1789 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001790
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001791 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001792 _check_preserve(config, pn)
1793
Brad Bishop316dfdd2018-06-25 12:45:53 -04001794 appendfile = workspace[pn]['bbappend']
1795 if os.path.exists(appendfile):
1796 # This shouldn't happen, but is possible if devtool errored out prior to
1797 # writing the md5 file. We need to delete this here or the recipe won't
1798 # actually be reset
1799 os.remove(appendfile)
1800
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001801 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001802 def preservedir(origdir):
1803 if os.path.exists(origdir):
1804 for root, dirs, files in os.walk(origdir):
1805 for fn in files:
1806 logger.warn('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001807 _move_file(os.path.join(origdir, fn),
1808 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001809 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001810 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001811 os.rmdir(origdir)
1812
Brad Bishop316dfdd2018-06-25 12:45:53 -04001813 recipefile = workspace[pn]['recipefile']
1814 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1815 # This should always be true if recipefile is set, but just in case
1816 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001817 # We don't automatically create this dir next to appends, but the user can
1818 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1819
Brad Bishop316dfdd2018-06-25 12:45:53 -04001820 srctreebase = workspace[pn]['srctreebase']
1821 if os.path.isdir(srctreebase):
1822 if os.listdir(srctreebase):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001823 # We don't want to risk wiping out any work in progress
1824 logger.info('Leaving source tree %s as-is; if you no '
1825 'longer need it then please delete it manually'
Brad Bishop316dfdd2018-06-25 12:45:53 -04001826 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001827 else:
1828 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04001829 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001830
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001831 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001832
1833def reset(args, config, basepath, workspace):
1834 """Entry point for the devtool 'reset' subcommand"""
1835 import bb
1836 if args.recipename:
1837 if args.all:
1838 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1839 else:
1840 for recipe in args.recipename:
1841 check_workspace_recipe(workspace, recipe, checksrc=False)
1842 elif not args.all:
1843 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1844 "reset all recipes")
1845 if args.all:
1846 recipes = list(workspace.keys())
1847 else:
1848 recipes = args.recipename
1849
1850 _reset(recipes, args.no_clean, config, basepath, workspace)
1851
1852 return 0
1853
1854
1855def _get_layer(layername, d):
1856 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001857 layerdirs = d.getVar('BBLAYERS').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001858 layers = {os.path.basename(p): p for p in layerdirs}
1859 # Provide some shortcuts
1860 if layername.lower() in ['oe-core', 'openembedded-core']:
1861 layerdir = layers.get('meta', None)
1862 else:
1863 layerdir = layers.get(layername, None)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001864 return os.path.abspath(layerdir or layername)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001865
1866def finish(args, config, basepath, workspace):
1867 """Entry point for the devtool 'finish' subcommand"""
1868 import bb
1869 import oe.recipeutils
1870
1871 check_workspace_recipe(workspace, args.recipename)
1872
Brad Bishop316dfdd2018-06-25 12:45:53 -04001873 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
1874
1875 # Grab the equivalent of COREBASE without having to initialise tinfoil
1876 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
1877
1878 srctree = workspace[args.recipename]['srctree']
1879 check_git_repo_op(srctree, [corebasedir])
1880 dirty = check_git_repo_dirty(srctree)
1881 if dirty:
1882 if args.force:
1883 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
1884 else:
1885 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)
1886
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001887 no_clean = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001888 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1889 try:
1890 rd = parse_recipe(config, tinfoil, args.recipename, True)
1891 if not rd:
1892 return 1
1893
1894 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001895 recipefile = rd.getVar('FILE')
1896 recipedir = os.path.dirname(recipefile)
1897 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001898
1899 if not os.path.isdir(destlayerdir):
1900 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
1901
1902 if os.path.abspath(destlayerdir) == config.workspace_path:
1903 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
1904
1905 # If it's an upgrade, grab the original path
1906 origpath = None
1907 origfilelist = None
1908 append = workspace[args.recipename]['bbappend']
1909 with open(append, 'r') as f:
1910 for line in f:
1911 if line.startswith('# original_path:'):
1912 origpath = line.split(':')[1].strip()
1913 elif line.startswith('# original_files:'):
1914 origfilelist = line.split(':')[1].split()
1915
Brad Bishop316dfdd2018-06-25 12:45:53 -04001916 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
1917
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001918 if origlayerdir == config.workspace_path:
1919 # Recipe file itself is in workspace, update it there first
1920 appendlayerdir = None
1921 origrelpath = None
1922 if origpath:
1923 origlayerpath = oe.recipeutils.find_layerdir(origpath)
1924 if origlayerpath:
1925 origrelpath = os.path.relpath(origpath, origlayerpath)
1926 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
1927 if not destpath:
1928 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 -05001929 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
1930 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04001931 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001932 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)
1933
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001934 elif destlayerdir == origlayerdir:
1935 # Same layer, update the original recipe
1936 appendlayerdir = None
1937 destpath = None
1938 else:
1939 # Create/update a bbappend in the specified layer
1940 appendlayerdir = destlayerdir
1941 destpath = None
1942
Brad Bishop316dfdd2018-06-25 12:45:53 -04001943 # Actually update the recipe / bbappend
1944 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
1945 dry_run_output = None
1946 dry_run_outdir = None
1947 if args.dry_run:
1948 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1949 dry_run_outdir = dry_run_output.name
1950 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)
1951 removed = [os.path.relpath(pth, recipedir) for pth in removed]
1952
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001953 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04001954 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001955 for fn in origfilelist:
1956 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001957 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
1958 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
1959 if not args.dry_run:
1960 try:
1961 os.remove(fnp)
1962 except FileNotFoundError:
1963 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001964
1965 if origlayerdir == config.workspace_path and destpath:
1966 # Recipe file itself is in the workspace - need to move it and any
1967 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001968 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04001969 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001970 for root, _, files in os.walk(recipedir):
1971 for fn in files:
1972 srcpath = os.path.join(root, fn)
1973 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
1974 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001975 destfp = os.path.join(destdir, fn)
1976 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001977
Brad Bishop316dfdd2018-06-25 12:45:53 -04001978 if dry_run_outdir:
1979 import difflib
1980 comparelist = []
1981 for root, _, files in os.walk(dry_run_outdir):
1982 for fn in files:
1983 outf = os.path.join(root, fn)
1984 relf = os.path.relpath(outf, dry_run_outdir)
1985 logger.debug('dry-run: output file %s' % relf)
1986 if fn.endswith('.bb'):
1987 if origfilelist and origpath and destpath:
1988 # Need to match this up with the pre-upgrade recipe file
1989 for origf in origfilelist:
1990 if origf.endswith('.bb'):
1991 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
1992 outf,
1993 os.path.abspath(os.path.join(destpath, relf))))
1994 break
1995 else:
1996 # Compare to the existing recipe
1997 comparelist.append((recipefile, outf, recipefile))
1998 elif fn.endswith('.bbappend'):
1999 if appendfile:
2000 if os.path.exists(appendfile):
2001 comparelist.append((appendfile, outf, appendfile))
2002 else:
2003 comparelist.append((None, outf, appendfile))
2004 else:
2005 if destpath:
2006 recipedest = destpath
2007 elif appendfile:
2008 recipedest = os.path.dirname(appendfile)
2009 else:
2010 recipedest = os.path.dirname(recipefile)
2011 destfp = os.path.join(recipedest, relf)
2012 if os.path.exists(destfp):
2013 comparelist.append((destfp, outf, destfp))
2014 output = ''
2015 for oldfile, newfile, newfileshow in comparelist:
2016 if oldfile:
2017 with open(oldfile, 'r') as f:
2018 oldlines = f.readlines()
2019 else:
2020 oldfile = '/dev/null'
2021 oldlines = []
2022 with open(newfile, 'r') as f:
2023 newlines = f.readlines()
2024 if not newfileshow:
2025 newfileshow = newfile
2026 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2027 difflines = list(diff)
2028 if difflines:
2029 output += ''.join(difflines)
2030 if output:
2031 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002032 finally:
2033 tinfoil.shutdown()
2034
2035 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002036 if args.dry_run:
2037 logger.info('Resetting recipe (dry-run)')
2038 else:
2039 _reset([args.recipename], no_clean=no_clean, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002040
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002041 return 0
2042
2043
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002044def get_default_srctree(config, recipename=''):
2045 """Get the default srctree path"""
2046 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2047 if recipename:
2048 return os.path.join(srctreeparent, 'sources', recipename)
2049 else:
2050 return os.path.join(srctreeparent, 'sources')
2051
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002052def register_commands(subparsers, context):
2053 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002054
2055 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002056 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002057 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.',
2058 group='starting', order=100)
2059 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.')
2060 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2061 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 -05002062 group = parser_add.add_mutually_exclusive_group()
2063 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2064 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002065 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 -05002066 parser_add.add_argument('--fetch-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002067 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002068 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 -05002069 group = parser_add.add_mutually_exclusive_group()
2070 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2071 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")
2072 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 -05002073 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')
2074 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')
2075 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002076 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2077 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 -06002078 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002079
2080 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002081 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.',
2082 group='starting', order=90)
2083 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2084 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 -05002085 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002086 group = parser_modify.add_mutually_exclusive_group()
2087 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2088 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 -05002089 group = parser_modify.add_mutually_exclusive_group()
2090 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2091 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002092 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 -04002093 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 -05002094 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002095 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002096
2097 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2098 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002099 group='advanced')
2100 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002101 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002102 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 -04002103 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 -05002104 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002105 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002106
2107 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2108 description='Synchronize the previously extracted source tree for an existing recipe',
2109 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2110 group='advanced')
2111 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2112 parser_sync.add_argument('srctree', help='Path to the source tree')
2113 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2114 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002115 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002116
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002117 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2118 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.',
2119 group='working', order=10)
2120 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2121 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2122 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)')
2123 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')
2124 parser_rename.set_defaults(func=rename)
2125
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002126 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002127 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.',
2128 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002129 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2130 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 -05002131 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002132 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2133 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')
2134 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 -04002135 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2136 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2137 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 -05002138 parser_update_recipe.set_defaults(func=update_recipe)
2139
2140 parser_status = subparsers.add_parser('status', help='Show workspace status',
2141 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002142 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002143 parser_status.set_defaults(func=status)
2144
2145 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002146 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 -05002147 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002148 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002149 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2150 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
2151 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002152
2153 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002154 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 -06002155 group='working', order=-100)
2156 parser_finish.add_argument('recipename', help='Recipe to finish')
2157 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.')
2158 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')
2159 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002160 parser_finish.add_argument('--force', '-f', action="store_true", help='Force continuing even if there are uncommitted changes in the source tree repository')
2161 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2162 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2163 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 -06002164 parser_finish.set_defaults(func=finish)