blob: 96af48879885086100eae4f09b289139493b426b [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Development tool - standard commands plugin
2#
Brad Bishopd7bf8c12018-02-25 22:55:05 -05003# Copyright (C) 2014-2017 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004#
Brad Bishopc342db32019-05-15 21:57:59 -04005# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05006#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007"""Devtool standard plugins"""
8
9import os
10import sys
11import re
12import shutil
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050013import subprocess
Patrick Williamsc124f4f2015-09-15 14:41:29 -050014import tempfile
15import logging
16import argparse
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050017import argparse_oe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050018import scriptutils
19import errno
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050020import glob
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021import filecmp
Patrick Williamsf1e5d692016-03-30 15:21:19 -050022from collections import OrderedDict
Brad Bishop316dfdd2018-06-25 12:45:53 -040023from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, use_external_build, setup_git_repo, recipe_to_append, get_bbclassextend_targets, update_unlockedsigs, check_prerelease_version, check_git_repo_dirty, check_git_repo_op, DevtoolError
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024from devtool import parse_recipe
25
26logger = logging.getLogger('devtool')
27
Brad Bishop316dfdd2018-06-25 12:45:53 -040028override_branch_prefix = 'devtool-override-'
29
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030
31def add(args, config, basepath, workspace):
32 """Entry point for the devtool 'add' subcommand"""
33 import bb
34 import oe.recipeutils
35
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050036 if not args.recipename and not args.srctree and not args.fetch and not args.fetchuri:
37 raise argparse_oe.ArgumentUsageError('At least one of recipename, srctree, fetchuri or -f/--fetch must be specified', 'add')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050039 # These are positional arguments, but because we're nice, allow
40 # specifying e.g. source tree without name, or fetch URI without name or
41 # source tree (if we can detect that that is what the user meant)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060042 if scriptutils.is_src_url(args.recipename):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050043 if not args.fetchuri:
44 if args.fetch:
45 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
46 args.fetchuri = args.recipename
47 args.recipename = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -060048 elif scriptutils.is_src_url(args.srctree):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050049 if not args.fetchuri:
50 if args.fetch:
51 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
52 args.fetchuri = args.srctree
53 args.srctree = ''
54 elif args.recipename and not args.srctree:
55 if os.sep in args.recipename:
56 args.srctree = args.recipename
57 args.recipename = None
58 elif os.path.isdir(args.recipename):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080059 logger.warning('Ambiguous argument "%s" - assuming you mean it to be the recipe name' % args.recipename)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060060
Brad Bishopd7bf8c12018-02-25 22:55:05 -050061 if not args.fetchuri:
62 if args.srcrev:
63 raise DevtoolError('The -S/--srcrev option is only valid when fetching from an SCM repository')
64 if args.srcbranch:
65 raise DevtoolError('The -B/--srcbranch option is only valid when fetching from an SCM repository')
66
Patrick Williamsc0f7c042017-02-23 20:41:17 -060067 if args.srctree and os.path.isfile(args.srctree):
68 args.fetchuri = 'file://' + os.path.abspath(args.srctree)
69 args.srctree = ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -050070
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050071 if args.fetch:
72 if args.fetchuri:
73 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
74 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080075 logger.warning('-f/--fetch option is deprecated - you can now simply specify the URL to fetch as a positional argument instead')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050076 args.fetchuri = args.fetch
Patrick Williamsf1e5d692016-03-30 15:21:19 -050077
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050078 if args.recipename:
79 if args.recipename in workspace:
80 raise DevtoolError("recipe %s is already in your workspace" %
81 args.recipename)
82 reason = oe.recipeutils.validate_pn(args.recipename)
83 if reason:
84 raise DevtoolError(reason)
85
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050086 if args.srctree:
87 srctree = os.path.abspath(args.srctree)
88 srctreeparent = None
89 tmpsrcdir = None
90 else:
91 srctree = None
92 srctreeparent = get_default_srctree(config)
93 bb.utils.mkdirhier(srctreeparent)
94 tmpsrcdir = tempfile.mkdtemp(prefix='devtoolsrc', dir=srctreeparent)
95
96 if srctree and os.path.exists(srctree):
97 if args.fetchuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098 if not os.path.isdir(srctree):
99 raise DevtoolError("Cannot fetch into source tree path %s as "
100 "it exists and is not a directory" %
101 srctree)
102 elif os.listdir(srctree):
103 raise DevtoolError("Cannot fetch into source tree path %s as "
104 "it already exists and is non-empty" %
105 srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500106 elif not args.fetchuri:
107 if args.srctree:
108 raise DevtoolError("Specified source tree %s could not be found" %
109 args.srctree)
110 elif srctree:
111 raise DevtoolError("No source tree exists at default path %s - "
112 "either create and populate this directory, "
113 "or specify a path to a source tree, or a "
114 "URI to fetch source from" % srctree)
115 else:
116 raise DevtoolError("You must either specify a source tree "
117 "or a URI to fetch source from")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500118
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119 if args.version:
120 if '_' in args.version or ' ' in args.version:
121 raise DevtoolError('Invalid version string "%s"' % args.version)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500122
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500123 if args.color == 'auto' and sys.stdout.isatty():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500124 color = 'always'
125 else:
126 color = args.color
127 extracmdopts = ''
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500128 if args.fetchuri:
129 source = args.fetchuri
130 if srctree:
131 extracmdopts += ' -x %s' % srctree
132 else:
133 extracmdopts += ' -x %s' % tmpsrcdir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500134 else:
135 source = srctree
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500136 if args.recipename:
137 extracmdopts += ' -N %s' % args.recipename
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500138 if args.version:
139 extracmdopts += ' -V %s' % args.version
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500140 if args.binary:
141 extracmdopts += ' -b'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500142 if args.also_native:
143 extracmdopts += ' --also-native'
144 if args.src_subdir:
145 extracmdopts += ' --src-subdir "%s"' % args.src_subdir
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600146 if args.autorev:
147 extracmdopts += ' -a'
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500148 if args.fetch_dev:
149 extracmdopts += ' --fetch-dev'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500150 if args.mirrors:
151 extracmdopts += ' --mirrors'
152 if args.srcrev:
153 extracmdopts += ' --srcrev %s' % args.srcrev
154 if args.srcbranch:
155 extracmdopts += ' --srcbranch %s' % args.srcbranch
156 if args.provides:
157 extracmdopts += ' --provides %s' % args.provides
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500158
159 tempdir = tempfile.mkdtemp(prefix='devtool')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500161 try:
162 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create --devtool -o %s \'%s\' %s' % (color, tempdir, source, extracmdopts), watch=True)
163 except bb.process.ExecutionError as e:
164 if e.exitcode == 15:
165 raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line')
166 else:
167 raise DevtoolError('Command \'%s\' failed' % e.command)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500168
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500169 recipes = glob.glob(os.path.join(tempdir, '*.bb'))
170 if recipes:
171 recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0]
172 if recipename in workspace:
173 raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename)
174 recipedir = os.path.join(config.workspace_path, 'recipes', recipename)
175 bb.utils.mkdirhier(recipedir)
176 recipefile = os.path.join(recipedir, os.path.basename(recipes[0]))
177 appendfile = recipe_to_append(recipefile, config)
178 if os.path.exists(appendfile):
179 # This shouldn't be possible, but just in case
180 raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace')
181 if os.path.exists(recipefile):
182 raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile)
183 if tmpsrcdir:
184 srctree = os.path.join(srctreeparent, recipename)
185 if os.path.exists(tmpsrcdir):
186 if os.path.exists(srctree):
187 if os.path.isdir(srctree):
188 try:
189 os.rmdir(srctree)
190 except OSError as e:
191 if e.errno == errno.ENOTEMPTY:
192 raise DevtoolError('Source tree path %s already exists and is not empty' % srctree)
193 else:
194 raise
195 else:
196 raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree)
197 logger.info('Using default source tree path %s' % srctree)
198 shutil.move(tmpsrcdir, srctree)
199 else:
200 raise DevtoolError('Couldn\'t find source tree created by recipetool')
201 bb.utils.mkdirhier(recipedir)
202 shutil.move(recipes[0], recipefile)
203 # Move any additional files created by recipetool
204 for fn in os.listdir(tempdir):
205 shutil.move(os.path.join(tempdir, fn), recipedir)
206 else:
207 raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout))
208 attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile))
209 if os.path.exists(attic_recipe):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800210 logger.warning('A modified recipe from a previous invocation exists in %s - you may wish to move this over the top of the new recipe if you had changes in it that you want to continue with' % attic_recipe)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500211 finally:
212 if tmpsrcdir and os.path.exists(tmpsrcdir):
213 shutil.rmtree(tmpsrcdir)
214 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500216 for fn in os.listdir(recipedir):
217 _add_md5(config, recipename, os.path.join(recipedir, fn))
218
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500219 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600220 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500221 try:
222 rd = tinfoil.parse_recipe_file(recipefile, False)
223 except Exception as e:
224 logger.error(str(e))
225 rd = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600226 if not rd:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500227 # Parsing failed. We just created this recipe and we shouldn't
228 # leave it in the workdir or it'll prevent bitbake from starting
229 movefn = '%s.parsefailed' % recipefile
230 logger.error('Parsing newly created recipe failed, moving recipe to %s for reference. If this looks to be caused by the recipe itself, please report this error.' % movefn)
231 shutil.move(recipefile, movefn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600232 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500233
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600234 if args.fetchuri and not args.no_git:
235 setup_git_repo(srctree, args.version, 'devtool', d=tinfoil.config_data)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500236
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600237 initial_rev = None
238 if os.path.exists(os.path.join(srctree, '.git')):
239 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
240 initial_rev = stdout.rstrip()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500241
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600242 if args.src_subdir:
243 srctree = os.path.join(srctree, args.src_subdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500244
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600245 bb.utils.mkdirhier(os.path.dirname(appendfile))
246 with open(appendfile, 'w') as f:
247 f.write('inherit externalsrc\n')
248 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500249
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600250 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
251 if b_is_s:
252 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
253 if initial_rev:
254 f.write('\n# initial_rev: %s\n' % initial_rev)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500255
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600256 if args.binary:
257 f.write('do_install_append() {\n')
258 f.write(' rm -rf ${D}/.git\n')
259 f.write(' rm -f ${D}/singletask.lock\n')
260 f.write('}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500261
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600262 if bb.data.inherits_class('npm', rd):
263 f.write('do_install_append() {\n')
264 f.write(' # Remove files added to source dir by devtool/externalsrc\n')
265 f.write(' rm -f ${NPM_INSTALLDIR}/singletask.lock\n')
266 f.write(' rm -rf ${NPM_INSTALLDIR}/.git\n')
267 f.write(' rm -rf ${NPM_INSTALLDIR}/oe-local-files\n')
268 f.write(' for symlink in ${EXTERNALSRC_SYMLINKS} ; do\n')
269 f.write(' rm -f ${NPM_INSTALLDIR}/${symlink%%:*}\n')
270 f.write(' done\n')
271 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500272
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500273 # Check if the new layer provides recipes whose priorities have been
274 # overriden by PREFERRED_PROVIDER.
275 recipe_name = rd.getVar('PN')
276 provides = rd.getVar('PROVIDES')
277 # Search every item defined in PROVIDES
278 for recipe_provided in provides.split():
279 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
280 current_pprovider = rd.getVar(preferred_provider)
281 if current_pprovider and current_pprovider != recipe_name:
282 if args.fixed_setup:
283 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
284 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
285 with open(layerconf_file, 'a') as f:
286 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
287 else:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800288 logger.warning('Set \'%s\' in order to use the recipe' % preferred_provider)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500289 break
290
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291 _add_md5(config, recipename, appendfile)
292
Brad Bishop316dfdd2018-06-25 12:45:53 -0400293 check_prerelease_version(rd.getVar('PV'), 'devtool add')
294
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600295 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
296
297 finally:
298 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500299
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500300 return 0
301
302
303def _check_compatible_recipe(pn, d):
304 """Check if the recipe is supported by devtool"""
305 if pn == 'perf':
306 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600307 "source and thus cannot be supported by this tool",
308 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500309
310 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600311 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500312
313 if bb.data.inherits_class('image', d):
314 raise DevtoolError("The %s recipe is an image, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600315 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316
317 if bb.data.inherits_class('populate_sdk', d):
318 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600319 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500320
321 if bb.data.inherits_class('packagegroup', d):
322 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600323 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500324
325 if bb.data.inherits_class('meta', d):
326 raise DevtoolError("The %s recipe is a meta-recipe, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600327 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500328
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500329 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600330 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500331 raise DevtoolError("externalsrc is currently enabled for the %s "
332 "recipe. This prevents the normal do_patch task "
333 "from working. You will need to disable this "
334 "first." % pn)
335
Brad Bishop316dfdd2018-06-25 12:45:53 -0400336def _dry_run_copy(src, dst, dry_run_outdir, base_outdir):
337 """Common function for copying a file to the dry run output directory"""
338 relpath = os.path.relpath(dst, base_outdir)
339 if relpath.startswith('..'):
340 raise Exception('Incorrect base path %s for path %s' % (base_outdir, dst))
341 dst = os.path.join(dry_run_outdir, relpath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500342 dst_d = os.path.dirname(dst)
343 if dst_d:
344 bb.utils.mkdirhier(dst_d)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400345 # Don't overwrite existing files, otherwise in the case of an upgrade
346 # the dry-run written out recipe will be overwritten with an unmodified
347 # version
348 if not os.path.exists(dst):
349 shutil.copy(src, dst)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500350
Brad Bishop316dfdd2018-06-25 12:45:53 -0400351def _move_file(src, dst, dry_run_outdir=None, base_outdir=None):
352 """Move a file. Creates all the directory components of destination path."""
353 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
354 logger.debug('Moving %s to %s%s' % (src, dst, dry_run_suffix))
355 if dry_run_outdir:
356 # We want to copy here, not move
357 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
358 else:
359 dst_d = os.path.dirname(dst)
360 if dst_d:
361 bb.utils.mkdirhier(dst_d)
362 shutil.move(src, dst)
363
364def _copy_file(src, dst, dry_run_outdir=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600365 """Copy a file. Creates all the directory components of destination path."""
Brad Bishop316dfdd2018-06-25 12:45:53 -0400366 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
367 logger.debug('Copying %s to %s%s' % (src, dst, dry_run_suffix))
368 if dry_run_outdir:
369 _dry_run_copy(src, dst, dry_run_outdir, base_outdir)
370 else:
371 dst_d = os.path.dirname(dst)
372 if dst_d:
373 bb.utils.mkdirhier(dst_d)
374 shutil.copy(src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600375
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500376def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
377 """List contents of a git treeish"""
378 import bb
379 cmd = ['git', 'ls-tree', '-z', treeish]
380 if recursive:
381 cmd.append('-r')
382 out, _ = bb.process.run(cmd, cwd=repodir)
383 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500384 if out:
385 for line in out.split('\0'):
386 if line:
387 split = line.split(None, 4)
388 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500389 return ret
390
391def _git_exclude_path(srctree, path):
392 """Return pathspec (list of paths) that excludes certain path"""
393 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
394 # we don't catch files that are deleted, for example. A more reliable way
395 # to implement this would be to use "negative pathspecs" which were
396 # introduced in Git v1.9.0. Revisit this when/if the required Git version
397 # becomes greater than that.
398 path = os.path.normpath(path)
399 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600400 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500401 if path in git_files:
402 git_files.remove(path)
403 return git_files
404 else:
405 return ['.']
406
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500407def _ls_tree(directory):
408 """Recursive listing of files in a directory"""
409 ret = []
410 for root, dirs, files in os.walk(directory):
411 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
412 fname in files])
413 return ret
414
415
416def extract(args, config, basepath, workspace):
417 """Entry point for the devtool 'extract' subcommand"""
418 import bb
419
Brad Bishop316dfdd2018-06-25 12:45:53 -0400420 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500421 if not tinfoil:
422 # Error already shown
423 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600424 try:
425 rd = parse_recipe(config, tinfoil, args.recipename, True)
426 if not rd:
427 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500428
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600429 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400430 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=args.no_overrides)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600431 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500432
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600433 if initial_rev:
434 return 0
435 else:
436 return 1
437 finally:
438 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500439
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500440def sync(args, config, basepath, workspace):
441 """Entry point for the devtool 'sync' subcommand"""
442 import bb
443
Brad Bishop316dfdd2018-06-25 12:45:53 -0400444 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500445 if not tinfoil:
446 # Error already shown
447 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600448 try:
449 rd = parse_recipe(config, tinfoil, args.recipename, True)
450 if not rd:
451 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500452
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600453 srctree = os.path.abspath(args.srctree)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400454 initial_rev, _ = _extract_source(srctree, args.keep_temp, args.branch, True, config, basepath, workspace, args.fixed_setup, rd, tinfoil, no_overrides=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600455 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500456
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600457 if initial_rev:
458 return 0
459 else:
460 return 1
461 finally:
462 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500463
Brad Bishop96ff1982019-08-19 13:50:42 -0400464def symlink_oelocal_files_srctree(rd,srctree):
465 import oe.patch
466 if os.path.abspath(rd.getVar('S')) == os.path.abspath(rd.getVar('WORKDIR')):
467 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
468 # (otherwise the recipe won't build as expected)
469 local_files_dir = os.path.join(srctree, 'oe-local-files')
470 addfiles = []
471 for root, _, files in os.walk(local_files_dir):
472 relpth = os.path.relpath(root, local_files_dir)
473 if relpth != '.':
474 bb.utils.mkdirhier(os.path.join(srctree, relpth))
475 for fn in files:
476 if fn == '.gitignore':
477 continue
478 destpth = os.path.join(srctree, relpth, fn)
479 if os.path.exists(destpth):
480 os.unlink(destpth)
481 os.symlink('oe-local-files/%s' % fn, destpth)
482 addfiles.append(os.path.join(relpth, fn))
483 if addfiles:
484 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
485 useroptions = []
486 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=rd)
487 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)
488
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500489
Brad Bishop316dfdd2018-06-25 12:45:53 -0400490def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil, no_overrides=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500491 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500492 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500493 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400494 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500495
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500496 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500497
498 _check_compatible_recipe(pn, d)
499
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500500 if sync:
501 if not os.path.exists(srctree):
502 raise DevtoolError("output path %s does not exist" % srctree)
503 else:
504 if os.path.exists(srctree):
505 if not os.path.isdir(srctree):
506 raise DevtoolError("output path %s exists and is not a directory" %
507 srctree)
508 elif os.listdir(srctree):
509 raise DevtoolError("output path %s already exists and is "
510 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500511
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500512 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
513 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600514 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500515
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500516 if not sync:
517 # Prepare for shutil.move later on
518 bb.utils.mkdirhier(srctree)
519 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520
Brad Bishop316dfdd2018-06-25 12:45:53 -0400521 extra_overrides = []
522 if not no_overrides:
523 history = d.varhistory.variable('SRC_URI')
524 for event in history:
525 if not 'flag' in event:
526 if event['op'].startswith(('_append[', '_prepend[')):
527 extra_overrides.append(event['op'].split('[')[1].split(']')[0])
Andrew Geissler99467da2019-02-25 18:54:23 -0600528 # We want to remove duplicate overrides. If a recipe had multiple
529 # SRC_URI_override += values it would cause mulitple instances of
530 # overrides. This doesn't play nicely with things like creating a
531 # branch for every instance of DEVTOOL_EXTRA_OVERRIDES.
532 extra_overrides = list(set(extra_overrides))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400533 if extra_overrides:
534 logger.info('SRC_URI contains some conditional appends/prepends - will create branches to represent these')
535
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500536 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500537
538 appendexisted = False
539 recipefile = d.getVar('FILE')
540 appendfile = recipe_to_append(recipefile, config)
541 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
542
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500543 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
544 # directory so that:
545 # (a) we pick up all files that get unpacked to the WORKDIR, and
546 # (b) we don't disturb the existing build
547 # However, with recipe-specific sysroots the sysroots for the recipe
548 # will be prepared under WORKDIR, and if we used the system temporary
549 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
550 # our attempts to hardlink files into the recipe-specific sysroots
551 # will fail on systems where /tmp is a different filesystem, and it
552 # would have to fall back to copying the files which is a waste of
553 # time. Put the temp directory under the WORKDIR to prevent that from
554 # being a problem.
555 tempbasedir = d.getVar('WORKDIR')
556 bb.utils.mkdirhier(tempbasedir)
557 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500558 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500559 tinfoil.logger.setLevel(logging.WARNING)
560
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500561 # FIXME this results in a cache reload under control of tinfoil, which is fine
562 # except we don't get the knotty progress bar
563
564 if os.path.exists(appendfile):
565 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
566 shutil.copyfile(appendfile, appendbackup)
567 else:
568 appendbackup = None
569 bb.utils.mkdirhier(os.path.dirname(appendfile))
570 logger.debug('writing append file %s' % appendfile)
571 with open(appendfile, 'a') as f:
572 f.write('###--- _extract_source\n')
573 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
574 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
575 if not is_kernel_yocto:
576 f.write('PATCHTOOL = "git"\n')
577 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
Brad Bishop316dfdd2018-06-25 12:45:53 -0400578 if extra_overrides:
579 f.write('DEVTOOL_EXTRA_OVERRIDES = "%s"\n' % ':'.join(extra_overrides))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500580 f.write('inherit devtool-source\n')
581 f.write('###--- _extract_source\n')
582
583 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
584
585 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
586 bb.utils.mkdirhier(sstate_manifests)
587 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
588 with open(preservestampfile, 'w') as f:
589 f.write(d.getVar('STAMP'))
590 try:
Brad Bishop96ff1982019-08-19 13:50:42 -0400591 if is_kernel_yocto:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500592 # We need to generate the kernel config
593 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500595 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500596
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500597 # Run the fetch + unpack tasks
598 res = tinfoil.build_targets(pn,
599 task,
600 handle_events=True)
601 finally:
602 if os.path.exists(preservestampfile):
603 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500604
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500605 if not res:
606 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500607
Brad Bishop316dfdd2018-06-25 12:45:53 -0400608 try:
609 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
610 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500611
Brad Bishop316dfdd2018-06-25 12:45:53 -0400612 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
613 srcsubdir = f.read()
614 except FileNotFoundError as e:
615 raise DevtoolError('Something went wrong with source extraction - the devtool-source class was not active or did not function correctly:\n%s' % str(e))
616 srcsubdir_rel = os.path.relpath(srcsubdir, os.path.join(tempdir, 'workdir'))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500617
Brad Bishop96ff1982019-08-19 13:50:42 -0400618 # Check if work-shared is empty, if yes
619 # find source and copy to work-shared
620 if is_kernel_yocto:
621 workshareddir = d.getVar('STAGING_KERNEL_DIR')
622 staging_kerVer = get_staging_kver(workshareddir)
623 kernelVersion = d.getVar('LINUX_VERSION')
624
625 # handle dangling symbolic link in work-shared:
626 if os.path.islink(workshareddir):
627 os.unlink(workshareddir)
628
629 if os.path.exists(workshareddir) and (not os.listdir(workshareddir) or kernelVersion != staging_kerVer):
630 shutil.rmtree(workshareddir)
631 oe.path.copyhardlinktree(srcsubdir,workshareddir)
632 elif not os.path.exists(workshareddir):
633 oe.path.copyhardlinktree(srcsubdir,workshareddir)
634
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500635 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
636 srctree_localdir = os.path.join(srctree, 'oe-local-files')
637
638 if sync:
639 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
640
641 # Move oe-local-files directory to srctree
642 # As the oe-local-files is not part of the constructed git tree,
643 # remove them directly during the synchrounizating might surprise
644 # the users. Instead, we move it to oe-local-files.bak and remind
645 # user in the log message.
646 if os.path.exists(srctree_localdir + '.bak'):
647 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
648
649 if os.path.exists(srctree_localdir):
650 logger.info('Backing up current local file directory %s' % srctree_localdir)
651 shutil.move(srctree_localdir, srctree_localdir + '.bak')
652
653 if os.path.exists(tempdir_localdir):
654 logger.info('Syncing local source files to srctree...')
655 shutil.copytree(tempdir_localdir, srctree_localdir)
656 else:
657 # Move oe-local-files directory to srctree
658 if os.path.exists(tempdir_localdir):
659 logger.info('Adding local source files to srctree...')
660 shutil.move(tempdir_localdir, srcsubdir)
661
662 shutil.move(srcsubdir, srctree)
Brad Bishop96ff1982019-08-19 13:50:42 -0400663 symlink_oelocal_files_srctree(d,srctree)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500664
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500665 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500666 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500667 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500668
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500669 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500670 if appendbackup:
671 shutil.copyfile(appendbackup, appendfile)
672 elif os.path.exists(appendfile):
673 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500674 if keep_temp:
675 logger.info('Preserving temporary directory %s' % tempdir)
676 else:
677 shutil.rmtree(tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400678 return initial_rev, srcsubdir_rel
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500679
680def _add_md5(config, recipename, filename):
681 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
682 import bb.utils
683
684 def addfile(fn):
685 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500686 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
687 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
688 f.seek(0, os.SEEK_SET)
689 if not md5_str in f.read():
690 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500691
692 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500693 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500694 for f in files:
695 addfile(os.path.join(root, f))
696 else:
697 addfile(filename)
698
699def _check_preserve(config, recipename):
700 """Check if a file was manually changed and needs to be saved in 'attic'
701 directory"""
702 import bb.utils
703 origfile = os.path.join(config.workspace_path, '.devtool_md5')
704 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500705 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500706 with open(origfile, 'r') as f:
707 with open(newfile, 'w') as tf:
708 for line in f.readlines():
709 splitline = line.rstrip().split('|')
710 if splitline[0] == recipename:
711 removefile = os.path.join(config.workspace_path, splitline[1])
712 try:
713 md5 = bb.utils.md5_file(removefile)
714 except IOError as err:
715 if err.errno == 2:
716 # File no longer exists, skip it
717 continue
718 else:
719 raise
720 if splitline[2] != md5:
721 bb.utils.mkdirhier(preservepath)
722 preservefile = os.path.basename(removefile)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800723 logger.warning('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500724 shutil.move(removefile, os.path.join(preservepath, preservefile))
725 else:
726 os.remove(removefile)
727 else:
728 tf.write(line)
729 os.rename(newfile, origfile)
730
Brad Bishop96ff1982019-08-19 13:50:42 -0400731def get_staging_kver(srcdir):
732 # Kernel version from work-shared
733 kerver = []
734 staging_kerVer=""
735 if os.path.exists(srcdir) and os.listdir(srcdir):
736 with open(os.path.join(srcdir,"Makefile")) as f:
737 version = [next(f) for x in range(5)][1:4]
738 for word in version:
739 kerver.append(word.split('= ')[1].split('\n')[0])
740 staging_kerVer = ".".join(kerver)
741 return staging_kerVer
742
743def get_staging_kbranch(srcdir):
744 staging_kbranch = ""
745 if os.path.exists(srcdir) and os.listdir(srcdir):
746 (branch, _) = bb.process.run('git branch | grep \* | cut -d \' \' -f2', cwd=srcdir)
747 staging_kbranch = "".join(branch.split('\n')[0])
748 return staging_kbranch
749
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500750def modify(args, config, basepath, workspace):
751 """Entry point for the devtool 'modify' subcommand"""
752 import bb
753 import oe.recipeutils
Brad Bishop316dfdd2018-06-25 12:45:53 -0400754 import oe.patch
Brad Bishop96ff1982019-08-19 13:50:42 -0400755 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500756
757 if args.recipename in workspace:
758 raise DevtoolError("recipe %s is already in your workspace" %
759 args.recipename)
760
Brad Bishop316dfdd2018-06-25 12:45:53 -0400761 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600762 try:
763 rd = parse_recipe(config, tinfoil, args.recipename, True)
764 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500765 return 1
766
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500767 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600768 if pn != args.recipename:
769 logger.info('Mapping %s to %s' % (args.recipename, pn))
770 if pn in workspace:
771 raise DevtoolError("recipe %s is already in your workspace" %
772 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500773
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600774 if args.srctree:
775 srctree = os.path.abspath(args.srctree)
776 else:
777 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500778
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600779 if args.no_extract and not os.path.isdir(srctree):
780 raise DevtoolError("--no-extract specified and source path %s does "
781 "not exist or is not a directory" %
782 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600783
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500784 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600785 appendfile = recipe_to_append(recipefile, config, args.wildcard)
786 if os.path.exists(appendfile):
787 raise DevtoolError("Another variant of recipe %s is already in your "
788 "workspace (only one variant of a recipe can "
789 "currently be worked on at once)"
790 % pn)
791
792 _check_compatible_recipe(pn, rd)
793
794 initial_rev = None
795 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400796 check_commits = False
Brad Bishop96ff1982019-08-19 13:50:42 -0400797
798 if bb.data.inherits_class('kernel-yocto', rd):
799 # Current set kernel version
800 kernelVersion = rd.getVar('LINUX_VERSION')
801 srcdir = rd.getVar('STAGING_KERNEL_DIR')
802 kbranch = rd.getVar('KBRANCH')
803
804 staging_kerVer = get_staging_kver(srcdir)
805 staging_kbranch = get_staging_kbranch(srcdir)
806 if (os.path.exists(srcdir) and os.listdir(srcdir)) and (kernelVersion in staging_kerVer and staging_kbranch == kbranch):
807 oe.path.copyhardlinktree(srcdir,srctree)
808 workdir = rd.getVar('WORKDIR')
809 srcsubdir = rd.getVar('S')
810 localfilesdir = os.path.join(srctree,'oe-local-files')
811 # Move local source files into separate subdir
812 recipe_patches = [os.path.basename(patch) for patch in oe.recipeutils.get_recipe_patches(rd)]
813 local_files = oe.recipeutils.get_recipe_local_files(rd)
814
815 for key in local_files.copy():
816 if key.endswith('scc'):
817 sccfile = open(local_files[key], 'r')
818 for l in sccfile:
819 line = l.split()
820 if line and line[0] in ('kconf', 'patch'):
821 cfg = os.path.join(os.path.dirname(local_files[key]), line[-1])
822 if not cfg in local_files.values():
823 local_files[line[-1]] = cfg
824 shutil.copy2(cfg, workdir)
825 sccfile.close()
826
827 # Ignore local files with subdir={BP}
828 srcabspath = os.path.abspath(srcsubdir)
829 local_files = [fname for fname in local_files if os.path.exists(os.path.join(workdir, fname)) and (srcabspath == workdir or not os.path.join(workdir, fname).startswith(srcabspath + os.sep))]
830 if local_files:
831 for fname in local_files:
832 _move_file(os.path.join(workdir, fname), os.path.join(srctree, 'oe-local-files', fname))
833 with open(os.path.join(srctree, 'oe-local-files', '.gitignore'), 'w') as f:
834 f.write('# Ignore local files, by default. Remove this file ''if you want to commit the directory to Git\n*\n')
835
836 symlink_oelocal_files_srctree(rd,srctree)
837
838 task = 'do_configure'
839 res = tinfoil.build_targets(pn, task, handle_events=True)
840
841 # Copy .config to workspace
842 kconfpath = rd.getVar('B')
843 logger.info('Copying kernel config to workspace')
844 shutil.copy2(os.path.join(kconfpath, '.config'),srctree)
845
846 # Set this to true, we still need to get initial_rev
847 # by parsing the git repo
848 args.no_extract = True
849
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600850 if not args.no_extract:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400851 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 -0500852 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600853 return 1
854 logger.info('Source tree extracted to %s' % srctree)
855 # Get list of commits since this revision
856 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
857 commits = stdout.split()
Brad Bishop316dfdd2018-06-25 12:45:53 -0400858 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600859 else:
860 if os.path.exists(os.path.join(srctree, '.git')):
Andrew Geissler99467da2019-02-25 18:54:23 -0600861 # Check if it's a tree previously extracted by us. This is done
862 # by ensuring that devtool-base and args.branch (devtool) exist.
863 # The check_commits logic will cause an exception if either one
864 # of these doesn't exist
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600865 try:
866 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Andrew Geissler99467da2019-02-25 18:54:23 -0600867 bb.process.run('git rev-parse %s' % args.branch, cwd=srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600868 except bb.process.ExecutionError:
869 stdout = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400870 if stdout:
871 check_commits = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600872 for line in stdout.splitlines():
873 if line.startswith('*'):
874 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
875 initial_rev = stdout.rstrip()
876 if not initial_rev:
877 # Otherwise, just grab the head revision
878 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
879 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500880
Brad Bishop316dfdd2018-06-25 12:45:53 -0400881 branch_patches = {}
882 if check_commits:
883 # Check if there are override branches
884 (stdout, _) = bb.process.run('git branch', cwd=srctree)
885 branches = []
886 for line in stdout.rstrip().splitlines():
887 branchname = line[2:].rstrip()
888 if branchname.startswith(override_branch_prefix):
889 branches.append(branchname)
890 if branches:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800891 logger.warning('SRC_URI is conditionally overridden in this recipe, thus several %s* branches have been created, one for each override that makes changes to SRC_URI. It is recommended that you make changes to the %s branch first, then checkout and rebase each %s* branch and update any unique patches there (duplicates on those branches will be ignored by devtool finish/update-recipe)' % (override_branch_prefix, args.branch, override_branch_prefix))
Brad Bishop316dfdd2018-06-25 12:45:53 -0400892 branches.insert(0, args.branch)
893 seen_patches = []
894 for branch in branches:
895 branch_patches[branch] = []
896 (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree)
897 for line in stdout.splitlines():
898 line = line.strip()
899 if line.startswith(oe.patch.GitApplyTree.patch_line_prefix):
900 origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip()
901 if not origpatch in seen_patches:
902 seen_patches.append(origpatch)
903 branch_patches[branch].append(origpatch)
904
905 # Need to grab this here in case the source is within a subdirectory
906 srctreebase = srctree
907
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600908 # Check that recipe isn't using a shared workdir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500909 s = os.path.abspath(rd.getVar('S'))
910 workdir = os.path.abspath(rd.getVar('WORKDIR'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600911 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
912 # Handle if S is set to a subdirectory of the source
913 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
914 srctree = os.path.join(srctree, srcsubdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500915
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600916 bb.utils.mkdirhier(os.path.dirname(appendfile))
917 with open(appendfile, 'w') as f:
918 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
919 # Local files can be modified/tracked in separate subdir under srctree
920 # Mostly useful for packages with S != WORKDIR
921 f.write('FILESPATH_prepend := "%s:"\n' %
Brad Bishop316dfdd2018-06-25 12:45:53 -0400922 os.path.join(srctreebase, 'oe-local-files'))
923 f.write('# srctreebase: %s\n' % srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500924
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600925 f.write('\ninherit externalsrc\n')
926 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
927 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500928
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600929 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
930 if b_is_s:
931 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500932
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600933 if bb.data.inherits_class('kernel', rd):
934 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500935 'do_fetch do_unpack do_kernel_configme do_kernel_configcheck"\n')
Brad Bishop19323692019-04-05 15:28:33 -0400936 f.write('\ndo_patch[noexec] = "1"\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600937 f.write('\ndo_configure_append() {\n'
938 ' cp ${B}/.config ${S}/.config.baseline\n'
939 ' ln -sfT ${B}/.config ${S}/.config.new\n'
940 '}\n')
Brad Bishop96ff1982019-08-19 13:50:42 -0400941 if rd.getVarFlag('do_menuconfig','task'):
942 f.write('\ndo_configure_append() {\n'
943 ' cp ${B}/.config ${S}/.config.baseline\n'
944 ' ln -sfT ${B}/.config ${S}/.config.new\n'
945 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600946 if initial_rev:
947 f.write('\n# initial_rev: %s\n' % initial_rev)
948 for commit in commits:
949 f.write('# commit: %s\n' % commit)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400950 if branch_patches:
951 for branch in branch_patches:
952 if branch == args.branch:
953 continue
954 f.write('# patches_%s: %s\n' % (branch, ','.join(branch_patches[branch])))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500955
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500956 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
957
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600958 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500959
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600960 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500961
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600962 finally:
963 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500964
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500965 return 0
966
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500967
968def rename(args, config, basepath, workspace):
969 """Entry point for the devtool 'rename' subcommand"""
970 import bb
971 import oe.recipeutils
972
973 check_workspace_recipe(workspace, args.recipename)
974
975 if not (args.newname or args.version):
976 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
977
978 recipefile = workspace[args.recipename]['recipefile']
979 if not recipefile:
980 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
981
982 if args.newname and args.newname != args.recipename:
983 reason = oe.recipeutils.validate_pn(args.newname)
984 if reason:
985 raise DevtoolError(reason)
986 newname = args.newname
987 else:
988 newname = args.recipename
989
990 append = workspace[args.recipename]['bbappend']
991 appendfn = os.path.splitext(os.path.basename(append))[0]
992 splitfn = appendfn.split('_')
993 if len(splitfn) > 1:
994 origfnver = appendfn.split('_')[1]
995 else:
996 origfnver = ''
997
998 recipefilemd5 = None
999 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1000 try:
1001 rd = parse_recipe(config, tinfoil, args.recipename, True)
1002 if not rd:
1003 return 1
1004
1005 bp = rd.getVar('BP')
1006 bpn = rd.getVar('BPN')
1007 if newname != args.recipename:
1008 localdata = rd.createCopy()
1009 localdata.setVar('PN', newname)
1010 newbpn = localdata.getVar('BPN')
1011 else:
1012 newbpn = bpn
1013 s = rd.getVar('S', False)
1014 src_uri = rd.getVar('SRC_URI', False)
1015 pv = rd.getVar('PV')
1016
1017 # Correct variable values that refer to the upstream source - these
1018 # values must stay the same, so if the name/version are changing then
1019 # we need to fix them up
1020 new_s = s
1021 new_src_uri = src_uri
1022 if newbpn != bpn:
1023 # ${PN} here is technically almost always incorrect, but people do use it
1024 new_s = new_s.replace('${BPN}', bpn)
1025 new_s = new_s.replace('${PN}', bpn)
1026 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
1027 new_src_uri = new_src_uri.replace('${BPN}', bpn)
1028 new_src_uri = new_src_uri.replace('${PN}', bpn)
1029 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
1030 if args.version and origfnver == pv:
1031 new_s = new_s.replace('${PV}', pv)
1032 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
1033 new_src_uri = new_src_uri.replace('${PV}', pv)
1034 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
1035 patchfields = {}
1036 if new_s != s:
1037 patchfields['S'] = new_s
1038 if new_src_uri != src_uri:
1039 patchfields['SRC_URI'] = new_src_uri
1040 if patchfields:
1041 recipefilemd5 = bb.utils.md5_file(recipefile)
1042 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1043 newrecipefilemd5 = bb.utils.md5_file(recipefile)
1044 finally:
1045 tinfoil.shutdown()
1046
1047 if args.version:
1048 newver = args.version
1049 else:
1050 newver = origfnver
1051
1052 if newver:
1053 newappend = '%s_%s.bbappend' % (newname, newver)
1054 newfile = '%s_%s.bb' % (newname, newver)
1055 else:
1056 newappend = '%s.bbappend' % newname
1057 newfile = '%s.bb' % newname
1058
1059 oldrecipedir = os.path.dirname(recipefile)
1060 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
1061 if oldrecipedir != newrecipedir:
1062 bb.utils.mkdirhier(newrecipedir)
1063
1064 newappend = os.path.join(os.path.dirname(append), newappend)
1065 newfile = os.path.join(newrecipedir, newfile)
1066
1067 # Rename bbappend
1068 logger.info('Renaming %s to %s' % (append, newappend))
1069 os.rename(append, newappend)
1070 # Rename recipe file
1071 logger.info('Renaming %s to %s' % (recipefile, newfile))
1072 os.rename(recipefile, newfile)
1073
1074 # Rename source tree if it's the default path
1075 appendmd5 = None
1076 if not args.no_srctree:
1077 srctree = workspace[args.recipename]['srctree']
1078 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
1079 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
1080 logger.info('Renaming %s to %s' % (srctree, newsrctree))
1081 shutil.move(srctree, newsrctree)
1082 # Correct any references (basically EXTERNALSRC*) in the .bbappend
1083 appendmd5 = bb.utils.md5_file(newappend)
1084 appendlines = []
1085 with open(newappend, 'r') as f:
1086 for line in f:
1087 appendlines.append(line)
1088 with open(newappend, 'w') as f:
1089 for line in appendlines:
1090 if srctree in line:
1091 line = line.replace(srctree, newsrctree)
1092 f.write(line)
1093 newappendmd5 = bb.utils.md5_file(newappend)
1094
1095 bpndir = None
1096 newbpndir = None
1097 if newbpn != bpn:
1098 bpndir = os.path.join(oldrecipedir, bpn)
1099 if os.path.exists(bpndir):
1100 newbpndir = os.path.join(newrecipedir, newbpn)
1101 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
1102 shutil.move(bpndir, newbpndir)
1103
1104 bpdir = None
1105 newbpdir = None
1106 if newver != origfnver or newbpn != bpn:
1107 bpdir = os.path.join(oldrecipedir, bp)
1108 if os.path.exists(bpdir):
1109 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
1110 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
1111 shutil.move(bpdir, newbpdir)
1112
1113 if oldrecipedir != newrecipedir:
1114 # Move any stray files and delete the old recipe directory
1115 for entry in os.listdir(oldrecipedir):
1116 oldpath = os.path.join(oldrecipedir, entry)
1117 newpath = os.path.join(newrecipedir, entry)
1118 logger.info('Renaming %s to %s' % (oldpath, newpath))
1119 shutil.move(oldpath, newpath)
1120 os.rmdir(oldrecipedir)
1121
1122 # Now take care of entries in .devtool_md5
1123 md5entries = []
1124 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
1125 for line in f:
1126 md5entries.append(line)
1127
1128 if bpndir and newbpndir:
1129 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
1130 else:
1131 relbpndir = None
1132 if bpdir and newbpdir:
1133 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
1134 else:
1135 relbpdir = None
1136
1137 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
1138 for entry in md5entries:
1139 splitentry = entry.rstrip().split('|')
1140 if len(splitentry) > 2:
1141 if splitentry[0] == args.recipename:
1142 splitentry[0] = newname
1143 if splitentry[1] == os.path.relpath(append, config.workspace_path):
1144 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
1145 if appendmd5 and splitentry[2] == appendmd5:
1146 splitentry[2] = newappendmd5
1147 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
1148 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
1149 if recipefilemd5 and splitentry[2] == recipefilemd5:
1150 splitentry[2] = newrecipefilemd5
1151 elif relbpndir and splitentry[1].startswith(relbpndir):
1152 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
1153 elif relbpdir and splitentry[1].startswith(relbpdir):
1154 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
1155 entry = '|'.join(splitentry) + '\n'
1156 f.write(entry)
1157 return 0
1158
1159
Brad Bishop316dfdd2018-06-25 12:45:53 -04001160def _get_patchset_revs(srctree, recipe_path, initial_rev=None, force_patch_refresh=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001161 """Get initial and update rev of a recipe. These are the start point of the
1162 whole patchset and start point for the patches to be re-generated/updated.
1163 """
1164 import bb
1165
Brad Bishop316dfdd2018-06-25 12:45:53 -04001166 # Get current branch
1167 stdout, _ = bb.process.run('git rev-parse --abbrev-ref HEAD',
1168 cwd=srctree)
1169 branchname = stdout.rstrip()
1170
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001171 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001172 commits = []
Brad Bishop316dfdd2018-06-25 12:45:53 -04001173 patches = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001174 with open(recipe_path, 'r') as f:
1175 for line in f:
1176 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001177 if not initial_rev:
1178 initial_rev = line.split(':')[-1].strip()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001179 elif line.startswith('# commit:') and not force_patch_refresh:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001180 commits.append(line.split(':')[-1].strip())
Brad Bishop316dfdd2018-06-25 12:45:53 -04001181 elif line.startswith('# patches_%s:' % branchname):
1182 patches = line.split(':')[-1].strip().split(',')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001183
1184 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001185 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001186 if initial_rev:
1187 # Find first actually changed revision
1188 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1189 initial_rev, cwd=srctree)
1190 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001191 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001192 if newcommits[i] == commits[i]:
1193 update_rev = commits[i]
1194
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001195 try:
1196 stdout, _ = bb.process.run('git cherry devtool-patched',
1197 cwd=srctree)
1198 except bb.process.ExecutionError as err:
1199 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001200
Brad Bishop316dfdd2018-06-25 12:45:53 -04001201 if stdout is not None and not force_patch_refresh:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001202 changed_revs = []
1203 for line in stdout.splitlines():
1204 if line.startswith('+ '):
1205 rev = line.split()[1]
1206 if rev in newcommits:
1207 changed_revs.append(rev)
1208
Brad Bishop316dfdd2018-06-25 12:45:53 -04001209 return initial_rev, update_rev, changed_revs, patches
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001210
1211def _remove_file_entries(srcuri, filelist):
1212 """Remove file:// entries from SRC_URI"""
1213 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001214 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001215 for fname in filelist:
1216 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001217 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001218 if (srcuri[i].startswith('file://') and
1219 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001220 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001221 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001222 srcuri.pop(i)
1223 break
1224 return entries, remaining
1225
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001226def _replace_srcuri_entry(srcuri, filename, newentry):
1227 """Replace entry corresponding to specified file with a new entry"""
1228 basename = os.path.basename(filename)
1229 for i in range(len(srcuri)):
1230 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1231 srcuri.pop(i)
1232 srcuri.insert(i, newentry)
1233 break
1234
Brad Bishop316dfdd2018-06-25 12:45:53 -04001235def _remove_source_files(append, files, destpath, no_report_remove=False, dry_run=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001236 """Unlink existing patch files"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001237
1238 dry_run_suffix = ' (dry-run)' if dry_run else ''
1239
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001240 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001241 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001242 if not destpath:
1243 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001244 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001245
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001246 if os.path.exists(path):
Brad Bishop316dfdd2018-06-25 12:45:53 -04001247 if not no_report_remove:
1248 logger.info('Removing file %s%s' % (path, dry_run_suffix))
1249 if not dry_run:
1250 # FIXME "git rm" here would be nice if the file in question is
1251 # tracked
1252 # FIXME there's a chance that this file is referred to by
1253 # another recipe, in which case deleting wouldn't be the
1254 # right thing to do
1255 os.remove(path)
1256 # Remove directory if empty
1257 try:
1258 os.rmdir(os.path.dirname(path))
1259 except OSError as ose:
1260 if ose.errno != errno.ENOTEMPTY:
1261 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001262
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001263
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001264def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001265 """Export patches from srctree to given location.
1266 Returns three-tuple of dicts:
1267 1. updated - patches that already exist in SRCURI
1268 2. added - new patches that don't exist in SRCURI
1269 3 removed - patches that exist in SRCURI but not in exported patches
1270 In each dict the key is the 'basepath' of the URI and value is the
1271 absolute path to the existing file in recipe space (if any).
1272 """
1273 import oe.recipeutils
1274 from oe.patch import GitApplyTree
1275 updated = OrderedDict()
1276 added = OrderedDict()
1277 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1278
1279 existing_patches = dict((os.path.basename(path), path) for path in
1280 oe.recipeutils.get_recipe_patches(rd))
Brad Bishop316dfdd2018-06-25 12:45:53 -04001281 logger.debug('Existing patches: %s' % existing_patches)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001282
1283 # Generate patches from Git, exclude local files directory
1284 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1285 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1286
1287 new_patches = sorted(os.listdir(destdir))
1288 for new_patch in new_patches:
1289 # Strip numbering from patch names. If it's a git sequence named patch,
1290 # the numbers might not match up since we are starting from a different
1291 # revision This does assume that people are using unique shortlog
1292 # values, but they ought to be anyway...
1293 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001294 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001295 for old_patch in existing_patches:
1296 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001297 old_basename_splitext = os.path.splitext(old_basename)
1298 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1299 old_patch_noext = os.path.splitext(old_patch)[0]
1300 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001301 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001302 elif new_basename == old_basename:
1303 match_name = old_patch
1304 break
1305 if match_name:
1306 # Rename patch files
1307 if new_patch != match_name:
1308 os.rename(os.path.join(destdir, new_patch),
1309 os.path.join(destdir, match_name))
1310 # Need to pop it off the list now before checking changed_revs
1311 oldpath = existing_patches.pop(old_patch)
1312 if changed_revs is not None:
1313 # Avoid updating patches that have not actually changed
1314 with open(os.path.join(destdir, match_name), 'r') as f:
1315 firstlineitems = f.readline().split()
1316 # Looking for "From <hash>" line
1317 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1318 if not firstlineitems[1] in changed_revs:
1319 continue
1320 # Recompress if necessary
1321 if oldpath.endswith(('.gz', '.Z')):
1322 bb.process.run(['gzip', match_name], cwd=destdir)
1323 if oldpath.endswith('.gz'):
1324 match_name += '.gz'
1325 else:
1326 match_name += '.Z'
1327 elif oldpath.endswith('.bz2'):
1328 bb.process.run(['bzip2', match_name], cwd=destdir)
1329 match_name += '.bz2'
1330 updated[match_name] = oldpath
1331 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001332 added[new_patch] = None
1333 return (updated, added, existing_patches)
1334
1335
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001336def _create_kconfig_diff(srctree, rd, outfile):
1337 """Create a kconfig fragment"""
1338 # Only update config fragment if both config files exist
1339 orig_config = os.path.join(srctree, '.config.baseline')
1340 new_config = os.path.join(srctree, '.config.new')
1341 if os.path.exists(orig_config) and os.path.exists(new_config):
1342 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1343 '--unchanged-line-format=', orig_config, new_config]
1344 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1345 stderr=subprocess.PIPE)
1346 stdout, stderr = pipe.communicate()
1347 if pipe.returncode == 1:
1348 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001349 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001350 fobj.write(stdout)
1351 elif pipe.returncode == 0:
1352 logger.info("Would remove config fragment %s" % outfile)
1353 if os.path.exists(outfile):
1354 # Remove fragment file in case of empty diff
1355 logger.info("Removing config fragment %s" % outfile)
1356 os.unlink(outfile)
1357 else:
1358 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1359 return True
1360 return False
1361
1362
Brad Bishop316dfdd2018-06-25 12:45:53 -04001363def _export_local_files(srctree, rd, destdir, srctreebase):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001364 """Copy local files from srctree to given location.
1365 Returns three-tuple of dicts:
1366 1. updated - files that already exist in SRCURI
1367 2. added - new files files that don't exist in SRCURI
1368 3 removed - files that exist in SRCURI but not in exported files
1369 In each dict the key is the 'basepath' of the URI and value is the
1370 absolute path to the existing file in recipe space (if any).
1371 """
1372 import oe.recipeutils
1373
1374 # Find out local files (SRC_URI files that exist in the "recipe space").
1375 # Local files that reside in srctree are not included in patch generation.
1376 # Instead they are directly copied over the original source files (in
1377 # recipe space).
1378 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1379 new_set = None
1380 updated = OrderedDict()
1381 added = OrderedDict()
1382 removed = OrderedDict()
Brad Bishop316dfdd2018-06-25 12:45:53 -04001383 local_files_dir = os.path.join(srctreebase, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001384 git_files = _git_ls_tree(srctree)
1385 if 'oe-local-files' in git_files:
1386 # If tracked by Git, take the files from srctree HEAD. First get
1387 # the tree object of the directory
1388 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1389 tree = git_files['oe-local-files'][2]
1390 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1391 env=dict(os.environ, GIT_WORK_TREE=destdir,
1392 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001393 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001394 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001395 # If not tracked by Git, just copy from working copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001396 new_set = _ls_tree(local_files_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001397 bb.process.run(['cp', '-ax',
Brad Bishop316dfdd2018-06-25 12:45:53 -04001398 os.path.join(local_files_dir, '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001399 else:
1400 new_set = []
1401
1402 # Special handling for kernel config
1403 if bb.data.inherits_class('kernel-yocto', rd):
1404 fragment_fn = 'devtool-fragment.cfg'
1405 fragment_path = os.path.join(destdir, fragment_fn)
1406 if _create_kconfig_diff(srctree, rd, fragment_path):
1407 if os.path.exists(fragment_path):
1408 if fragment_fn not in new_set:
1409 new_set.append(fragment_fn)
1410 # Copy fragment to local-files
1411 if os.path.isdir(local_files_dir):
1412 shutil.copy2(fragment_path, local_files_dir)
1413 else:
1414 if fragment_fn in new_set:
1415 new_set.remove(fragment_fn)
1416 # Remove fragment from local-files
1417 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1418 os.unlink(os.path.join(local_files_dir, fragment_fn))
1419
Brad Bishopd89cb5f2019-04-10 09:02:41 -04001420 # Special handling for cml1, ccmake, etc bbclasses that generated
1421 # configuration fragment files that are consumed as source files
1422 for frag_class, frag_name in [("cml1", "fragment.cfg"), ("ccmake", "site-file.cmake")]:
1423 if bb.data.inherits_class(frag_class, rd):
1424 srcpath = os.path.join(rd.getVar('WORKDIR'), frag_name)
1425 if os.path.exists(srcpath):
1426 if frag_name not in new_set:
1427 new_set.append(frag_name)
1428 # copy fragment into destdir
1429 shutil.copy2(srcpath, destdir)
1430 # copy fragment into local files if exists
1431 if os.path.isdir(local_files_dir):
1432 shutil.copy2(srcpath, local_files_dir)
1433
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001434 if new_set is not None:
1435 for fname in new_set:
1436 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001437 origpath = existing_files.pop(fname)
1438 workpath = os.path.join(local_files_dir, fname)
1439 if not filecmp.cmp(origpath, workpath):
1440 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001441 elif fname != '.gitignore':
1442 added[fname] = None
1443
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001444 workdir = rd.getVar('WORKDIR')
1445 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001446 if not s.endswith(os.sep):
1447 s += os.sep
1448
1449 if workdir != s:
1450 # Handle files where subdir= was specified
1451 for fname in list(existing_files.keys()):
1452 # FIXME handle both subdir starting with BP and not?
1453 fworkpath = os.path.join(workdir, fname)
1454 if fworkpath.startswith(s):
1455 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1456 if os.path.exists(fpath):
1457 origpath = existing_files.pop(fname)
1458 if not filecmp.cmp(origpath, fpath):
1459 updated[fpath] = origpath
1460
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001461 removed = existing_files
1462 return (updated, added, removed)
1463
1464
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001465def _determine_files_dir(rd):
1466 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001467 recipedir = rd.getVar('FILE_DIRNAME')
1468 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001469 relpth = os.path.relpath(entry, recipedir)
1470 if not os.sep in relpth:
1471 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1472 if os.path.isdir(entry):
1473 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001474 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001475
1476
Brad Bishop316dfdd2018-06-25 12:45:53 -04001477def _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 -05001478 """Implement the 'srcrev' mode of update-recipe"""
1479 import bb
1480 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001481
Brad Bishop316dfdd2018-06-25 12:45:53 -04001482 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1483
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001484 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001485 recipedir = os.path.basename(recipefile)
1486 logger.info('Updating SRCREV in recipe %s%s' % (recipedir, dry_run_suffix))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001487
1488 # Get HEAD revision
1489 try:
1490 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1491 except bb.process.ExecutionError as err:
1492 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1493 (srctree, err))
1494 srcrev = stdout.strip()
1495 if len(srcrev) != 40:
1496 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1497
1498 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001499 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001500 patchfields = {}
1501 patchfields['SRCREV'] = srcrev
1502 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001503 srcuri = orig_src_uri.split()
1504 tempdir = tempfile.mkdtemp(prefix='devtool')
1505 update_srcuri = False
Brad Bishop316dfdd2018-06-25 12:45:53 -04001506 appendfile = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001507 try:
1508 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001509 srctreebase = workspace[recipename]['srctreebase']
1510 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001511 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001512 # Find list of existing patches in recipe file
1513 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001514 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001515 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1516 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001517 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 -05001518
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001519 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001520 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001521 if remove_files:
1522 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1523 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001524
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001525 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001526 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001527 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001528 removevalues = {}
1529 if update_srcuri:
1530 removevalues = {'SRC_URI': removedentries}
1531 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001532 if dry_run_outdir:
1533 logger.info('Creating bbappend (dry-run)')
1534 else:
1535 appendfile, destpath = oe.recipeutils.bbappend_recipe(
1536 rd, appendlayerdir, files, wildcardver=wildcard_version,
1537 extralines=patchfields, removevalues=removevalues,
1538 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001539 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001540 files_dir = _determine_files_dir(rd)
1541 for basepath, path in upd_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001542 logger.info('Updating file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001543 if os.path.isabs(basepath):
1544 # Original file (probably with subdir pointing inside source tree)
1545 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001546 _copy_file(basepath, path, dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001547 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001548 _move_file(os.path.join(local_files_dir, basepath), path,
1549 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001550 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001551 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001552 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001553 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001554 os.path.join(files_dir, basepath),
1555 dry_run_outdir=dry_run_outdir,
1556 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001557 srcuri.append('file://%s' % basepath)
1558 update_srcuri = True
1559 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001560 patchfields['SRC_URI'] = ' '.join(srcuri)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001561 ret = oe.recipeutils.patch_recipe(rd, recipefile, patchfields, redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001562 finally:
1563 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001564 if not 'git://' in orig_src_uri:
1565 logger.info('You will need to update SRC_URI within the recipe to '
1566 'point to a git repository where you have pushed your '
1567 'changes')
1568
Brad Bishop316dfdd2018-06-25 12:45:53 -04001569 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1570 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001571
Brad Bishop316dfdd2018-06-25 12:45:53 -04001572def _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 -05001573 """Implement the 'patch' mode of update-recipe"""
1574 import bb
1575 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001576
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001577 recipefile = rd.getVar('FILE')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001578 recipedir = os.path.dirname(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001579 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001580 if not os.path.exists(append):
1581 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001582 recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001583
Brad Bishop316dfdd2018-06-25 12:45:53 -04001584 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 -05001585 if not initial_rev:
1586 raise DevtoolError('Unable to find initial revision - please specify '
1587 'it with --initial-rev')
1588
Brad Bishop316dfdd2018-06-25 12:45:53 -04001589 appendfile = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001590 dl_dir = rd.getVar('DL_DIR')
1591 if not dl_dir.endswith('/'):
1592 dl_dir += '/'
1593
Brad Bishop316dfdd2018-06-25 12:45:53 -04001594 dry_run_suffix = ' (dry-run)' if dry_run_outdir else ''
1595
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001596 tempdir = tempfile.mkdtemp(prefix='devtool')
1597 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001598 local_files_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001599 if filter_patches:
1600 upd_f = {}
1601 new_f = {}
1602 del_f = {}
1603 else:
1604 srctreebase = workspace[recipename]['srctreebase']
1605 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir, srctreebase)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001606
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001607 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001608 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001609 # Get all patches from source tree and check if any should be removed
1610 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001611 _, _, del_p = _export_patches(srctree, rd, initial_rev,
1612 all_patches_dir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001613 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001614 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001615
1616 # Get updated patches from source tree
1617 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001618 upd_p, new_p, _ = _export_patches(srctree, rd, update_rev,
1619 patches_dir, changed_revs)
1620 logger.debug('Pre-filtering: update: %s, new: %s' % (dict(upd_p), dict(new_p)))
1621 if filter_patches:
1622 new_p = {}
1623 upd_p = {k:v for k,v in upd_p.items() if k in filter_patches}
1624 remove_files = [f for f in remove_files if f in filter_patches]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001625 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001626 updaterecipe = False
1627 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001628 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001629 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001630 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001631 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001632 files.update(dict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001633 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001634 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001635 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001636 if remove_files:
1637 removedentries, remaining = _remove_file_entries(
1638 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001639 if removedentries or remaining:
1640 remaining = ['file://' + os.path.basename(item) for
1641 item in remaining]
1642 removevalues = {'SRC_URI': removedentries + remaining}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001643 appendfile, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001644 rd, appendlayerdir, files,
1645 wildcardver=wildcard_version,
Brad Bishop316dfdd2018-06-25 12:45:53 -04001646 removevalues=removevalues,
1647 redirect_output=dry_run_outdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001648 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001649 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001650 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001651 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001652 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001653 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001654 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001655 if os.path.isabs(basepath):
1656 # Original file (probably with subdir pointing inside source tree)
1657 # so we do not want to move it, just copy
Brad Bishop316dfdd2018-06-25 12:45:53 -04001658 _copy_file(basepath, path,
1659 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001660 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001661 _move_file(os.path.join(local_files_dir, basepath), path,
1662 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001663 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001664 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001665 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001666 if os.path.dirname(path) + '/' == dl_dir:
1667 # This is a a downloaded patch file - we now need to
1668 # replace the entry in SRC_URI with our local version
1669 logger.info('Replacing remote patch %s with updated local version' % basepath)
1670 path = os.path.join(files_dir, basepath)
1671 _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath)
1672 updaterecipe = True
1673 else:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001674 logger.info('Updating patch %s%s' % (basepath, dry_run_suffix))
1675 _move_file(patchfn, path,
1676 dry_run_outdir=dry_run_outdir, base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001677 updatefiles = True
1678 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001679 for basepath, path in new_f.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001680 logger.info('Adding new file %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001681 _move_file(os.path.join(local_files_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001682 os.path.join(files_dir, basepath),
1683 dry_run_outdir=dry_run_outdir,
1684 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001685 srcuri.append('file://%s' % basepath)
1686 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001687 for basepath, path in new_p.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -04001688 logger.info('Adding new patch %s%s' % (basepath, dry_run_suffix))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001689 _move_file(os.path.join(patches_dir, basepath),
Brad Bishop316dfdd2018-06-25 12:45:53 -04001690 os.path.join(files_dir, basepath),
1691 dry_run_outdir=dry_run_outdir,
1692 base_outdir=recipedir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001693 srcuri.append('file://%s' % basepath)
1694 updaterecipe = True
1695 # Update recipe, if needed
1696 if _remove_file_entries(srcuri, remove_files)[0]:
1697 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001698 if updaterecipe:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001699 if not dry_run_outdir:
1700 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1701 ret = oe.recipeutils.patch_recipe(rd, recipefile,
1702 {'SRC_URI': ' '.join(srcuri)},
1703 redirect_output=dry_run_outdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001704 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001705 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001706 logger.info('No patches or files need updating')
Brad Bishop316dfdd2018-06-25 12:45:53 -04001707 return False, None, []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001708 finally:
1709 shutil.rmtree(tempdir)
1710
Brad Bishop316dfdd2018-06-25 12:45:53 -04001711 _remove_source_files(appendlayerdir, remove_files, destpath, no_report_remove, dry_run=dry_run_outdir)
1712 return True, appendfile, remove_files
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001713
1714def _guess_recipe_update_mode(srctree, rdata):
1715 """Guess the recipe update mode to use"""
1716 src_uri = (rdata.getVar('SRC_URI', False) or '').split()
1717 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1718 if not git_uris:
1719 return 'patch'
1720 # Just use the first URI for now
1721 uri = git_uris[0]
1722 # Check remote branch
1723 params = bb.fetch.decodeurl(uri)[5]
1724 upstr_branch = params['branch'] if 'branch' in params else 'master'
1725 # Check if current branch HEAD is found in upstream branch
1726 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1727 head_rev = stdout.rstrip()
1728 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1729 cwd=srctree)
1730 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1731 if 'origin/' + upstr_branch in remote_brs:
1732 return 'srcrev'
1733
1734 return 'patch'
1735
Brad Bishop316dfdd2018-06-25 12:45:53 -04001736def _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 -06001737 srctree = workspace[recipename]['srctree']
1738 if mode == 'auto':
1739 mode = _guess_recipe_update_mode(srctree, rd)
1740
Brad Bishop316dfdd2018-06-25 12:45:53 -04001741 override_branches = []
1742 mainbranch = None
1743 startbranch = None
1744 if not no_overrides:
1745 stdout, _ = bb.process.run('git branch', cwd=srctree)
1746 other_branches = []
1747 for line in stdout.splitlines():
1748 branchname = line[2:]
1749 if line.startswith('* '):
1750 startbranch = branchname
1751 if branchname.startswith(override_branch_prefix):
1752 override_branches.append(branchname)
1753 else:
1754 other_branches.append(branchname)
1755
1756 if override_branches:
1757 logger.debug('_update_recipe: override branches: %s' % override_branches)
1758 logger.debug('_update_recipe: other branches: %s' % other_branches)
1759 if startbranch.startswith(override_branch_prefix):
1760 if len(other_branches) == 1:
1761 mainbranch = other_branches[1]
1762 else:
1763 raise DevtoolError('Unable to determine main branch - please check out the main branch in source tree first')
1764 else:
1765 mainbranch = startbranch
1766
1767 checkedout = None
1768 anyupdated = False
1769 appendfile = None
1770 allremoved = []
1771 if override_branches:
1772 logger.info('Handling main branch (%s)...' % mainbranch)
1773 if startbranch != mainbranch:
1774 bb.process.run('git checkout %s' % mainbranch, cwd=srctree)
1775 checkedout = mainbranch
1776 try:
1777 branchlist = [mainbranch] + override_branches
1778 for branch in branchlist:
1779 crd = bb.data.createCopy(rd)
1780 if branch != mainbranch:
1781 logger.info('Handling branch %s...' % branch)
1782 override = branch[len(override_branch_prefix):]
1783 crd.appendVar('OVERRIDES', ':%s' % override)
1784 bb.process.run('git checkout %s' % branch, cwd=srctree)
1785 checkedout = branch
1786
1787 if mode == 'srcrev':
1788 updated, appendf, removed = _update_recipe_srcrev(recipename, workspace, srctree, crd, appendlayerdir, wildcard_version, no_remove, no_report_remove, dry_run_outdir)
1789 elif mode == 'patch':
1790 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)
1791 else:
1792 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1793 if updated:
1794 anyupdated = True
1795 if appendf:
1796 appendfile = appendf
1797 allremoved.extend(removed)
1798 finally:
1799 if startbranch and checkedout != startbranch:
1800 bb.process.run('git checkout %s' % startbranch, cwd=srctree)
1801
1802 return anyupdated, appendfile, allremoved
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001803
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001804def update_recipe(args, config, basepath, workspace):
1805 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001806 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001807
1808 if args.append:
1809 if not os.path.exists(args.append):
1810 raise DevtoolError('bbappend destination layer directory "%s" '
1811 'does not exist' % args.append)
1812 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1813 raise DevtoolError('conf/layer.conf not found in bbappend '
1814 'destination layer "%s"' % args.append)
1815
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001816 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001817 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001818
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001819 rd = parse_recipe(config, tinfoil, args.recipename, True)
1820 if not rd:
1821 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001822
Brad Bishop316dfdd2018-06-25 12:45:53 -04001823 dry_run_output = None
1824 dry_run_outdir = None
1825 if args.dry_run:
1826 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
1827 dry_run_outdir = dry_run_output.name
1828 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 -05001829
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001830 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001831 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001832 if rf.startswith(config.workspace_path):
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001833 logger.warning('Recipe file %s has been updated but is inside the workspace - you will need to move it (and any associated files next to it) out to the desired layer before using "devtool reset" in order to keep any changes' % rf)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001834 finally:
1835 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001836
1837 return 0
1838
1839
1840def status(args, config, basepath, workspace):
1841 """Entry point for the devtool 'status' subcommand"""
1842 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001843 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001844 recipefile = value['recipefile']
1845 if recipefile:
1846 recipestr = ' (%s)' % recipefile
1847 else:
1848 recipestr = ''
1849 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001850 else:
1851 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')
1852 return 0
1853
1854
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001855def _reset(recipes, no_clean, config, basepath, workspace):
1856 """Reset one or more recipes"""
Brad Bishop316dfdd2018-06-25 12:45:53 -04001857 import oe.path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001858
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001859 def clean_preferred_provider(pn, layerconf_path):
1860 """Remove PREFERRED_PROVIDER from layer.conf'"""
1861 import re
1862 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1863 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1864 pprovider_found = False
1865 with open(layerconf_file, 'r') as f:
1866 lines = f.readlines()
1867 with open(new_layerconf_file, 'a') as nf:
1868 for line in lines:
1869 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1870 if not re.match(pprovider_exp, line):
1871 nf.write(line)
1872 else:
1873 pprovider_found = True
1874 if pprovider_found:
1875 shutil.move(new_layerconf_file, layerconf_file)
1876 else:
1877 os.remove(new_layerconf_file)
1878
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001879 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001880 if len(recipes) == 1:
1881 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1882 else:
1883 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001884 # If the recipe file itself was created in the workspace, and
1885 # it uses BBCLASSEXTEND, then we need to also clean the other
1886 # variants
1887 targets = []
1888 for recipe in recipes:
1889 targets.append(recipe)
1890 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001891 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001892 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001893 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001894 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001895 except bb.process.ExecutionError as e:
1896 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1897 'wish, you may specify -n/--no-clean to '
1898 'skip running this command when resetting' %
1899 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001900
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001901 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001902 _check_preserve(config, pn)
1903
Brad Bishop316dfdd2018-06-25 12:45:53 -04001904 appendfile = workspace[pn]['bbappend']
1905 if os.path.exists(appendfile):
1906 # This shouldn't happen, but is possible if devtool errored out prior to
1907 # writing the md5 file. We need to delete this here or the recipe won't
1908 # actually be reset
1909 os.remove(appendfile)
1910
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001911 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001912 def preservedir(origdir):
1913 if os.path.exists(origdir):
1914 for root, dirs, files in os.walk(origdir):
1915 for fn in files:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001916 logger.warning('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001917 _move_file(os.path.join(origdir, fn),
1918 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001919 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001920 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001921 os.rmdir(origdir)
1922
Brad Bishop316dfdd2018-06-25 12:45:53 -04001923 recipefile = workspace[pn]['recipefile']
1924 if recipefile and oe.path.is_path_parent(config.workspace_path, recipefile):
1925 # This should always be true if recipefile is set, but just in case
1926 preservedir(os.path.dirname(recipefile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001927 # We don't automatically create this dir next to appends, but the user can
1928 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1929
Brad Bishop316dfdd2018-06-25 12:45:53 -04001930 srctreebase = workspace[pn]['srctreebase']
1931 if os.path.isdir(srctreebase):
1932 if os.listdir(srctreebase):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001933 # We don't want to risk wiping out any work in progress
1934 logger.info('Leaving source tree %s as-is; if you no '
1935 'longer need it then please delete it manually'
Brad Bishop316dfdd2018-06-25 12:45:53 -04001936 % srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001937 else:
1938 # This is unlikely, but if it's empty we can just remove it
Brad Bishop316dfdd2018-06-25 12:45:53 -04001939 os.rmdir(srctreebase)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001940
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001941 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001942
1943def reset(args, config, basepath, workspace):
1944 """Entry point for the devtool 'reset' subcommand"""
1945 import bb
1946 if args.recipename:
1947 if args.all:
1948 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1949 else:
1950 for recipe in args.recipename:
1951 check_workspace_recipe(workspace, recipe, checksrc=False)
1952 elif not args.all:
1953 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1954 "reset all recipes")
1955 if args.all:
1956 recipes = list(workspace.keys())
1957 else:
1958 recipes = args.recipename
1959
1960 _reset(recipes, args.no_clean, config, basepath, workspace)
1961
1962 return 0
1963
1964
1965def _get_layer(layername, d):
1966 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001967 layerdirs = d.getVar('BBLAYERS').split()
Brad Bishop96ff1982019-08-19 13:50:42 -04001968 layers = {} # {basename: layer_paths}
1969 for p in layerdirs:
1970 bn = os.path.basename(p)
1971 if bn not in layers:
1972 layers[bn] = [p]
1973 else:
1974 layers[bn].append(p)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001975 # Provide some shortcuts
1976 if layername.lower() in ['oe-core', 'openembedded-core']:
Brad Bishop96ff1982019-08-19 13:50:42 -04001977 layername = 'meta'
1978 layer_paths = layers.get(layername, None)
1979 if not layer_paths:
1980 return os.path.abspath(layername)
1981 elif len(layer_paths) == 1:
1982 return os.path.abspath(layer_paths[0])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001983 else:
Brad Bishop96ff1982019-08-19 13:50:42 -04001984 # multiple layers having the same base name
1985 logger.warning("Multiple layers have the same base name '%s', use the first one '%s'." % (layername, layer_paths[0]))
1986 logger.warning("Consider using path instead of base name to specify layer:\n\t\t%s" % '\n\t\t'.join(layer_paths))
1987 return os.path.abspath(layer_paths[0])
1988
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001989
1990def finish(args, config, basepath, workspace):
1991 """Entry point for the devtool 'finish' subcommand"""
1992 import bb
1993 import oe.recipeutils
1994
1995 check_workspace_recipe(workspace, args.recipename)
1996
Brad Bishop316dfdd2018-06-25 12:45:53 -04001997 dry_run_suffix = ' (dry-run)' if args.dry_run else ''
1998
1999 # Grab the equivalent of COREBASE without having to initialise tinfoil
2000 corebasedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
2001
2002 srctree = workspace[args.recipename]['srctree']
2003 check_git_repo_op(srctree, [corebasedir])
2004 dirty = check_git_repo_dirty(srctree)
2005 if dirty:
2006 if args.force:
2007 logger.warning('Source tree is not clean, continuing as requested by -f/--force')
2008 else:
2009 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)
2010
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002011 no_clean = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002012 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
2013 try:
2014 rd = parse_recipe(config, tinfoil, args.recipename, True)
2015 if not rd:
2016 return 1
2017
2018 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002019 recipefile = rd.getVar('FILE')
2020 recipedir = os.path.dirname(recipefile)
2021 origlayerdir = oe.recipeutils.find_layerdir(recipefile)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002022
2023 if not os.path.isdir(destlayerdir):
2024 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
2025
2026 if os.path.abspath(destlayerdir) == config.workspace_path:
2027 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
2028
2029 # If it's an upgrade, grab the original path
2030 origpath = None
2031 origfilelist = None
2032 append = workspace[args.recipename]['bbappend']
2033 with open(append, 'r') as f:
2034 for line in f:
2035 if line.startswith('# original_path:'):
2036 origpath = line.split(':')[1].strip()
2037 elif line.startswith('# original_files:'):
2038 origfilelist = line.split(':')[1].split()
2039
Brad Bishop316dfdd2018-06-25 12:45:53 -04002040 destlayerbasedir = oe.recipeutils.find_layerdir(destlayerdir)
2041
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002042 if origlayerdir == config.workspace_path:
2043 # Recipe file itself is in workspace, update it there first
2044 appendlayerdir = None
2045 origrelpath = None
2046 if origpath:
2047 origlayerpath = oe.recipeutils.find_layerdir(origpath)
2048 if origlayerpath:
2049 origrelpath = os.path.relpath(origpath, origlayerpath)
2050 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
2051 if not destpath:
2052 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 -05002053 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
2054 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Brad Bishop316dfdd2018-06-25 12:45:53 -04002055 if not os.path.abspath(destlayerbasedir) in layerdirs:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002056 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)
2057
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002058 elif destlayerdir == origlayerdir:
2059 # Same layer, update the original recipe
2060 appendlayerdir = None
2061 destpath = None
2062 else:
2063 # Create/update a bbappend in the specified layer
2064 appendlayerdir = destlayerdir
2065 destpath = None
2066
Brad Bishop316dfdd2018-06-25 12:45:53 -04002067 # Actually update the recipe / bbappend
2068 removing_original = (origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == destlayerbasedir)
2069 dry_run_output = None
2070 dry_run_outdir = None
2071 if args.dry_run:
2072 dry_run_output = tempfile.TemporaryDirectory(prefix='devtool')
2073 dry_run_outdir = dry_run_output.name
2074 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)
2075 removed = [os.path.relpath(pth, recipedir) for pth in removed]
2076
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002077 # Remove any old files in the case of an upgrade
Brad Bishop316dfdd2018-06-25 12:45:53 -04002078 if removing_original:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002079 for fn in origfilelist:
2080 fnp = os.path.join(origpath, fn)
Brad Bishop316dfdd2018-06-25 12:45:53 -04002081 if fn in removed or not os.path.exists(os.path.join(recipedir, fn)):
2082 logger.info('Removing file %s%s' % (fnp, dry_run_suffix))
2083 if not args.dry_run:
2084 try:
2085 os.remove(fnp)
2086 except FileNotFoundError:
2087 pass
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002088
2089 if origlayerdir == config.workspace_path and destpath:
2090 # Recipe file itself is in the workspace - need to move it and any
2091 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002092 no_clean = True
Brad Bishop316dfdd2018-06-25 12:45:53 -04002093 logger.info('Moving recipe file to %s%s' % (destpath, dry_run_suffix))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002094 for root, _, files in os.walk(recipedir):
2095 for fn in files:
2096 srcpath = os.path.join(root, fn)
2097 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
2098 destdir = os.path.abspath(os.path.join(destpath, relpth))
Brad Bishop316dfdd2018-06-25 12:45:53 -04002099 destfp = os.path.join(destdir, fn)
2100 _move_file(srcpath, destfp, dry_run_outdir=dry_run_outdir, base_outdir=destpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002101
Brad Bishop316dfdd2018-06-25 12:45:53 -04002102 if dry_run_outdir:
2103 import difflib
2104 comparelist = []
2105 for root, _, files in os.walk(dry_run_outdir):
2106 for fn in files:
2107 outf = os.path.join(root, fn)
2108 relf = os.path.relpath(outf, dry_run_outdir)
2109 logger.debug('dry-run: output file %s' % relf)
2110 if fn.endswith('.bb'):
2111 if origfilelist and origpath and destpath:
2112 # Need to match this up with the pre-upgrade recipe file
2113 for origf in origfilelist:
2114 if origf.endswith('.bb'):
2115 comparelist.append((os.path.abspath(os.path.join(origpath, origf)),
2116 outf,
2117 os.path.abspath(os.path.join(destpath, relf))))
2118 break
2119 else:
2120 # Compare to the existing recipe
2121 comparelist.append((recipefile, outf, recipefile))
2122 elif fn.endswith('.bbappend'):
2123 if appendfile:
2124 if os.path.exists(appendfile):
2125 comparelist.append((appendfile, outf, appendfile))
2126 else:
2127 comparelist.append((None, outf, appendfile))
2128 else:
2129 if destpath:
2130 recipedest = destpath
2131 elif appendfile:
2132 recipedest = os.path.dirname(appendfile)
2133 else:
2134 recipedest = os.path.dirname(recipefile)
2135 destfp = os.path.join(recipedest, relf)
2136 if os.path.exists(destfp):
2137 comparelist.append((destfp, outf, destfp))
2138 output = ''
2139 for oldfile, newfile, newfileshow in comparelist:
2140 if oldfile:
2141 with open(oldfile, 'r') as f:
2142 oldlines = f.readlines()
2143 else:
2144 oldfile = '/dev/null'
2145 oldlines = []
2146 with open(newfile, 'r') as f:
2147 newlines = f.readlines()
2148 if not newfileshow:
2149 newfileshow = newfile
2150 diff = difflib.unified_diff(oldlines, newlines, oldfile, newfileshow)
2151 difflines = list(diff)
2152 if difflines:
2153 output += ''.join(difflines)
2154 if output:
2155 logger.info('Diff of changed files:\n%s' % output)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002156 finally:
2157 tinfoil.shutdown()
2158
2159 # Everything else has succeeded, we can now reset
Brad Bishop316dfdd2018-06-25 12:45:53 -04002160 if args.dry_run:
2161 logger.info('Resetting recipe (dry-run)')
2162 else:
2163 _reset([args.recipename], no_clean=no_clean, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002164
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002165 return 0
2166
2167
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002168def get_default_srctree(config, recipename=''):
2169 """Get the default srctree path"""
2170 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
2171 if recipename:
2172 return os.path.join(srctreeparent, 'sources', recipename)
2173 else:
2174 return os.path.join(srctreeparent, 'sources')
2175
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002176def register_commands(subparsers, context):
2177 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002178
2179 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002180 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002181 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.',
2182 group='starting', order=100)
2183 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.')
2184 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
2185 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 -05002186 group = parser_add.add_mutually_exclusive_group()
2187 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2188 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002189 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 -05002190 parser_add.add_argument('--fetch-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002191 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002192 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 -05002193 group = parser_add.add_mutually_exclusive_group()
2194 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
2195 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")
2196 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 -05002197 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')
2198 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')
2199 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002200 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
2201 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 -06002202 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002203
2204 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002205 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.',
2206 group='starting', order=90)
2207 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
2208 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 -05002209 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002210 group = parser_modify.add_mutually_exclusive_group()
2211 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
2212 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 -05002213 group = parser_modify.add_mutually_exclusive_group()
2214 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
2215 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002216 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 -04002217 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 -05002218 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002219 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002220
2221 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
2222 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002223 group='advanced')
2224 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002225 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002226 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 -04002227 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 -05002228 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002229 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002230
2231 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
2232 description='Synchronize the previously extracted source tree for an existing recipe',
2233 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
2234 group='advanced')
2235 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
2236 parser_sync.add_argument('srctree', help='Path to the source tree')
2237 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
2238 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05002239 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002240
Brad Bishop6e60e8b2018-02-01 10:27:11 -05002241 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
2242 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.',
2243 group='working', order=10)
2244 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
2245 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
2246 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)')
2247 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')
2248 parser_rename.set_defaults(func=rename)
2249
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002250 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002251 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.',
2252 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002253 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
2254 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 -05002255 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002256 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
2257 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')
2258 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 -04002259 parser_update_recipe.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2260 parser_update_recipe.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2261 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 -05002262 parser_update_recipe.set_defaults(func=update_recipe)
2263
2264 parser_status = subparsers.add_parser('status', help='Show workspace status',
2265 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05002266 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002267 parser_status.set_defaults(func=status)
2268
2269 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002270 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 -05002271 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002272 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002273 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
2274 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
2275 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06002276
2277 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
Brad Bishop316dfdd2018-06-25 12:45:53 -04002278 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 -06002279 group='working', order=-100)
2280 parser_finish.add_argument('recipename', help='Recipe to finish')
2281 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.')
2282 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')
2283 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
Brad Bishop316dfdd2018-06-25 12:45:53 -04002284 parser_finish.add_argument('--force', '-f', action="store_true", help='Force continuing even if there are uncommitted changes in the source tree repository')
2285 parser_finish.add_argument('--no-overrides', '-O', action="store_true", help='Do not handle other override branches (if they exist)')
2286 parser_finish.add_argument('--dry-run', '-N', action="store_true", help='Dry-run (just report changes instead of writing them)')
2287 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 -06002288 parser_finish.set_defaults(func=finish)