blob: beea0d4c274f99b077fdf815a7708e3167534919 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Development tool - standard commands plugin
2#
Brad Bishopd7bf8c12018-02-25 22:55:05 -05003# Copyright (C) 2014-2017 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05004#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17"""Devtool standard plugins"""
18
19import os
20import sys
21import re
22import shutil
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050023import subprocess
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024import tempfile
25import logging
26import argparse
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050027import argparse_oe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050028import scriptutils
29import errno
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050030import glob
Patrick Williamsc0f7c042017-02-23 20:41:17 -060031import filecmp
Patrick Williamsf1e5d692016-03-30 15:21:19 -050032from collections import OrderedDict
Brad Bishopd7bf8c12018-02-25 22:55:05 -050033from 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, DevtoolError
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034from devtool import parse_recipe
35
36logger = logging.getLogger('devtool')
37
38
39def add(args, config, basepath, workspace):
40 """Entry point for the devtool 'add' subcommand"""
41 import bb
42 import oe.recipeutils
43
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050044 if not args.recipename and not args.srctree and not args.fetch and not args.fetchuri:
45 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 -050046
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050047 # These are positional arguments, but because we're nice, allow
48 # specifying e.g. source tree without name, or fetch URI without name or
49 # source tree (if we can detect that that is what the user meant)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060050 if scriptutils.is_src_url(args.recipename):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050051 if not args.fetchuri:
52 if args.fetch:
53 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
54 args.fetchuri = args.recipename
55 args.recipename = ''
Patrick Williamsc0f7c042017-02-23 20:41:17 -060056 elif scriptutils.is_src_url(args.srctree):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050057 if not args.fetchuri:
58 if args.fetch:
59 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
60 args.fetchuri = args.srctree
61 args.srctree = ''
62 elif args.recipename and not args.srctree:
63 if os.sep in args.recipename:
64 args.srctree = args.recipename
65 args.recipename = None
66 elif os.path.isdir(args.recipename):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060067 logger.warn('Ambiguous argument "%s" - assuming you mean it to be the recipe name' % args.recipename)
68
Brad Bishopd7bf8c12018-02-25 22:55:05 -050069 if not args.fetchuri:
70 if args.srcrev:
71 raise DevtoolError('The -S/--srcrev option is only valid when fetching from an SCM repository')
72 if args.srcbranch:
73 raise DevtoolError('The -B/--srcbranch option is only valid when fetching from an SCM repository')
74
Patrick Williamsc0f7c042017-02-23 20:41:17 -060075 if args.srctree and os.path.isfile(args.srctree):
76 args.fetchuri = 'file://' + os.path.abspath(args.srctree)
77 args.srctree = ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -050078
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050079 if args.fetch:
80 if args.fetchuri:
81 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
82 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -060083 logger.warn('-f/--fetch option is deprecated - you can now simply specify the URL to fetch as a positional argument instead')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050084 args.fetchuri = args.fetch
Patrick Williamsf1e5d692016-03-30 15:21:19 -050085
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050086 if args.recipename:
87 if args.recipename in workspace:
88 raise DevtoolError("recipe %s is already in your workspace" %
89 args.recipename)
90 reason = oe.recipeutils.validate_pn(args.recipename)
91 if reason:
92 raise DevtoolError(reason)
93
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050094 if args.srctree:
95 srctree = os.path.abspath(args.srctree)
96 srctreeparent = None
97 tmpsrcdir = None
98 else:
99 srctree = None
100 srctreeparent = get_default_srctree(config)
101 bb.utils.mkdirhier(srctreeparent)
102 tmpsrcdir = tempfile.mkdtemp(prefix='devtoolsrc', dir=srctreeparent)
103
104 if srctree and os.path.exists(srctree):
105 if args.fetchuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500106 if not os.path.isdir(srctree):
107 raise DevtoolError("Cannot fetch into source tree path %s as "
108 "it exists and is not a directory" %
109 srctree)
110 elif os.listdir(srctree):
111 raise DevtoolError("Cannot fetch into source tree path %s as "
112 "it already exists and is non-empty" %
113 srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500114 elif not args.fetchuri:
115 if args.srctree:
116 raise DevtoolError("Specified source tree %s could not be found" %
117 args.srctree)
118 elif srctree:
119 raise DevtoolError("No source tree exists at default path %s - "
120 "either create and populate this directory, "
121 "or specify a path to a source tree, or a "
122 "URI to fetch source from" % srctree)
123 else:
124 raise DevtoolError("You must either specify a source tree "
125 "or a URI to fetch source from")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500126
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500127 if args.version:
128 if '_' in args.version or ' ' in args.version:
129 raise DevtoolError('Invalid version string "%s"' % args.version)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500130
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500131 if args.color == 'auto' and sys.stdout.isatty():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500132 color = 'always'
133 else:
134 color = args.color
135 extracmdopts = ''
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500136 if args.fetchuri:
137 source = args.fetchuri
138 if srctree:
139 extracmdopts += ' -x %s' % srctree
140 else:
141 extracmdopts += ' -x %s' % tmpsrcdir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500142 else:
143 source = srctree
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500144 if args.recipename:
145 extracmdopts += ' -N %s' % args.recipename
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500146 if args.version:
147 extracmdopts += ' -V %s' % args.version
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500148 if args.binary:
149 extracmdopts += ' -b'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500150 if args.also_native:
151 extracmdopts += ' --also-native'
152 if args.src_subdir:
153 extracmdopts += ' --src-subdir "%s"' % args.src_subdir
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600154 if args.autorev:
155 extracmdopts += ' -a'
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500156 if args.fetch_dev:
157 extracmdopts += ' --fetch-dev'
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500158 if args.mirrors:
159 extracmdopts += ' --mirrors'
160 if args.srcrev:
161 extracmdopts += ' --srcrev %s' % args.srcrev
162 if args.srcbranch:
163 extracmdopts += ' --srcbranch %s' % args.srcbranch
164 if args.provides:
165 extracmdopts += ' --provides %s' % args.provides
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500166
167 tempdir = tempfile.mkdtemp(prefix='devtool')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500168 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500169 try:
170 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create --devtool -o %s \'%s\' %s' % (color, tempdir, source, extracmdopts), watch=True)
171 except bb.process.ExecutionError as e:
172 if e.exitcode == 15:
173 raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line')
174 else:
175 raise DevtoolError('Command \'%s\' failed' % e.command)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500176
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500177 recipes = glob.glob(os.path.join(tempdir, '*.bb'))
178 if recipes:
179 recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0]
180 if recipename in workspace:
181 raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename)
182 recipedir = os.path.join(config.workspace_path, 'recipes', recipename)
183 bb.utils.mkdirhier(recipedir)
184 recipefile = os.path.join(recipedir, os.path.basename(recipes[0]))
185 appendfile = recipe_to_append(recipefile, config)
186 if os.path.exists(appendfile):
187 # This shouldn't be possible, but just in case
188 raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace')
189 if os.path.exists(recipefile):
190 raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile)
191 if tmpsrcdir:
192 srctree = os.path.join(srctreeparent, recipename)
193 if os.path.exists(tmpsrcdir):
194 if os.path.exists(srctree):
195 if os.path.isdir(srctree):
196 try:
197 os.rmdir(srctree)
198 except OSError as e:
199 if e.errno == errno.ENOTEMPTY:
200 raise DevtoolError('Source tree path %s already exists and is not empty' % srctree)
201 else:
202 raise
203 else:
204 raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree)
205 logger.info('Using default source tree path %s' % srctree)
206 shutil.move(tmpsrcdir, srctree)
207 else:
208 raise DevtoolError('Couldn\'t find source tree created by recipetool')
209 bb.utils.mkdirhier(recipedir)
210 shutil.move(recipes[0], recipefile)
211 # Move any additional files created by recipetool
212 for fn in os.listdir(tempdir):
213 shutil.move(os.path.join(tempdir, fn), recipedir)
214 else:
215 raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout))
216 attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile))
217 if os.path.exists(attic_recipe):
218 logger.warn('A modified recipe from a previous invocation exists in %s - you may wish to move this over the top of the new recipe if you had changes in it that you want to continue with' % attic_recipe)
219 finally:
220 if tmpsrcdir and os.path.exists(tmpsrcdir):
221 shutil.rmtree(tmpsrcdir)
222 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500223
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500224 for fn in os.listdir(recipedir):
225 _add_md5(config, recipename, os.path.join(recipedir, fn))
226
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500227 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600228 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500229 try:
230 rd = tinfoil.parse_recipe_file(recipefile, False)
231 except Exception as e:
232 logger.error(str(e))
233 rd = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600234 if not rd:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500235 # Parsing failed. We just created this recipe and we shouldn't
236 # leave it in the workdir or it'll prevent bitbake from starting
237 movefn = '%s.parsefailed' % recipefile
238 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)
239 shutil.move(recipefile, movefn)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600240 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500241
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600242 if args.fetchuri and not args.no_git:
243 setup_git_repo(srctree, args.version, 'devtool', d=tinfoil.config_data)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500244
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600245 initial_rev = None
246 if os.path.exists(os.path.join(srctree, '.git')):
247 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
248 initial_rev = stdout.rstrip()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500249
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600250 if args.src_subdir:
251 srctree = os.path.join(srctree, args.src_subdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500252
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600253 bb.utils.mkdirhier(os.path.dirname(appendfile))
254 with open(appendfile, 'w') as f:
255 f.write('inherit externalsrc\n')
256 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500257
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600258 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
259 if b_is_s:
260 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
261 if initial_rev:
262 f.write('\n# initial_rev: %s\n' % initial_rev)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500263
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600264 if args.binary:
265 f.write('do_install_append() {\n')
266 f.write(' rm -rf ${D}/.git\n')
267 f.write(' rm -f ${D}/singletask.lock\n')
268 f.write('}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500269
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600270 if bb.data.inherits_class('npm', rd):
271 f.write('do_install_append() {\n')
272 f.write(' # Remove files added to source dir by devtool/externalsrc\n')
273 f.write(' rm -f ${NPM_INSTALLDIR}/singletask.lock\n')
274 f.write(' rm -rf ${NPM_INSTALLDIR}/.git\n')
275 f.write(' rm -rf ${NPM_INSTALLDIR}/oe-local-files\n')
276 f.write(' for symlink in ${EXTERNALSRC_SYMLINKS} ; do\n')
277 f.write(' rm -f ${NPM_INSTALLDIR}/${symlink%%:*}\n')
278 f.write(' done\n')
279 f.write('}\n')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500280
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500281 # Check if the new layer provides recipes whose priorities have been
282 # overriden by PREFERRED_PROVIDER.
283 recipe_name = rd.getVar('PN')
284 provides = rd.getVar('PROVIDES')
285 # Search every item defined in PROVIDES
286 for recipe_provided in provides.split():
287 preferred_provider = 'PREFERRED_PROVIDER_' + recipe_provided
288 current_pprovider = rd.getVar(preferred_provider)
289 if current_pprovider and current_pprovider != recipe_name:
290 if args.fixed_setup:
291 #if we are inside the eSDK add the new PREFERRED_PROVIDER in the workspace layer.conf
292 layerconf_file = os.path.join(config.workspace_path, "conf", "layer.conf")
293 with open(layerconf_file, 'a') as f:
294 f.write('%s = "%s"\n' % (preferred_provider, recipe_name))
295 else:
296 logger.warn('Set \'%s\' in order to use the recipe' % preferred_provider)
297 break
298
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600299 _add_md5(config, recipename, appendfile)
300
301 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
302
303 finally:
304 tinfoil.shutdown()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500305
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500306 return 0
307
308
309def _check_compatible_recipe(pn, d):
310 """Check if the recipe is supported by devtool"""
311 if pn == 'perf':
312 raise DevtoolError("The perf recipe does not actually check out "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600313 "source and thus cannot be supported by this tool",
314 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500315
316 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600317 raise DevtoolError("The %s recipe is not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500318
319 if bb.data.inherits_class('image', d):
320 raise DevtoolError("The %s recipe is an image, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600321 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322
323 if bb.data.inherits_class('populate_sdk', d):
324 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600325 "supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326
327 if bb.data.inherits_class('packagegroup', d):
328 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600329 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500330
331 if bb.data.inherits_class('meta', d):
332 raise DevtoolError("The %s recipe is a meta-recipe, and therefore is "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600333 "not supported by this tool" % pn, 4)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500334
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500335 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600336 # Not an incompatibility error per se, so we don't pass the error code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500337 raise DevtoolError("externalsrc is currently enabled for the %s "
338 "recipe. This prevents the normal do_patch task "
339 "from working. You will need to disable this "
340 "first." % pn)
341
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500342def _move_file(src, dst):
343 """Move a file. Creates all the directory components of destination path."""
344 dst_d = os.path.dirname(dst)
345 if dst_d:
346 bb.utils.mkdirhier(dst_d)
347 shutil.move(src, dst)
348
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600349def _copy_file(src, dst):
350 """Copy a file. Creates all the directory components of destination path."""
351 dst_d = os.path.dirname(dst)
352 if dst_d:
353 bb.utils.mkdirhier(dst_d)
354 shutil.copy(src, dst)
355
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500356def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
357 """List contents of a git treeish"""
358 import bb
359 cmd = ['git', 'ls-tree', '-z', treeish]
360 if recursive:
361 cmd.append('-r')
362 out, _ = bb.process.run(cmd, cwd=repodir)
363 ret = {}
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500364 if out:
365 for line in out.split('\0'):
366 if line:
367 split = line.split(None, 4)
368 ret[split[3]] = split[0:3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500369 return ret
370
371def _git_exclude_path(srctree, path):
372 """Return pathspec (list of paths) that excludes certain path"""
373 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
374 # we don't catch files that are deleted, for example. A more reliable way
375 # to implement this would be to use "negative pathspecs" which were
376 # introduced in Git v1.9.0. Revisit this when/if the required Git version
377 # becomes greater than that.
378 path = os.path.normpath(path)
379 recurse = True if len(path.split(os.path.sep)) > 1 else False
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600380 git_files = list(_git_ls_tree(srctree, 'HEAD', recurse).keys())
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500381 if path in git_files:
382 git_files.remove(path)
383 return git_files
384 else:
385 return ['.']
386
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500387def _ls_tree(directory):
388 """Recursive listing of files in a directory"""
389 ret = []
390 for root, dirs, files in os.walk(directory):
391 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
392 fname in files])
393 return ret
394
395
396def extract(args, config, basepath, workspace):
397 """Entry point for the devtool 'extract' subcommand"""
398 import bb
399
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500400 tinfoil = setup_tinfoil(basepath=basepath)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500401 if not tinfoil:
402 # Error already shown
403 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600404 try:
405 rd = parse_recipe(config, tinfoil, args.recipename, True)
406 if not rd:
407 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500408
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600409 srctree = os.path.abspath(args.srctree)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500410 initial_rev = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600411 logger.info('Source tree extracted to %s' % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500412
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600413 if initial_rev:
414 return 0
415 else:
416 return 1
417 finally:
418 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500419
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500420def sync(args, config, basepath, workspace):
421 """Entry point for the devtool 'sync' subcommand"""
422 import bb
423
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500424 tinfoil = setup_tinfoil(basepath=basepath)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500425 if not tinfoil:
426 # Error already shown
427 return 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600428 try:
429 rd = parse_recipe(config, tinfoil, args.recipename, True)
430 if not rd:
431 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500432
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600433 srctree = os.path.abspath(args.srctree)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500434 initial_rev = _extract_source(srctree, args.keep_temp, args.branch, True, config, basepath, workspace, args.fixed_setup, rd, tinfoil)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600435 logger.info('Source tree %s synchronized' % srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500436
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600437 if initial_rev:
438 return 0
439 else:
440 return 1
441 finally:
442 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500443
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500444
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500445def _extract_source(srctree, keep_temp, devbranch, sync, config, basepath, workspace, fixed_setup, d, tinfoil):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500446 """Extract sources of a recipe"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500447 import oe.recipeutils
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500448 import oe.patch
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500449
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500450 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500451
452 _check_compatible_recipe(pn, d)
453
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500454 if sync:
455 if not os.path.exists(srctree):
456 raise DevtoolError("output path %s does not exist" % srctree)
457 else:
458 if os.path.exists(srctree):
459 if not os.path.isdir(srctree):
460 raise DevtoolError("output path %s exists and is not a directory" %
461 srctree)
462 elif os.listdir(srctree):
463 raise DevtoolError("output path %s already exists and is "
464 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500465
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500466 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
467 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600468 "extract source" % pn, 4)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500469
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500470 if not sync:
471 # Prepare for shutil.move later on
472 bb.utils.mkdirhier(srctree)
473 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500474
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500475 initial_rev = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500476
477 appendexisted = False
478 recipefile = d.getVar('FILE')
479 appendfile = recipe_to_append(recipefile, config)
480 is_kernel_yocto = bb.data.inherits_class('kernel-yocto', d)
481
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500482 # We need to redirect WORKDIR, STAMPS_DIR etc. under a temporary
483 # directory so that:
484 # (a) we pick up all files that get unpacked to the WORKDIR, and
485 # (b) we don't disturb the existing build
486 # However, with recipe-specific sysroots the sysroots for the recipe
487 # will be prepared under WORKDIR, and if we used the system temporary
488 # directory (i.e. usually /tmp) as used by mkdtemp by default, then
489 # our attempts to hardlink files into the recipe-specific sysroots
490 # will fail on systems where /tmp is a different filesystem, and it
491 # would have to fall back to copying the files which is a waste of
492 # time. Put the temp directory under the WORKDIR to prevent that from
493 # being a problem.
494 tempbasedir = d.getVar('WORKDIR')
495 bb.utils.mkdirhier(tempbasedir)
496 tempdir = tempfile.mkdtemp(prefix='devtooltmp-', dir=tempbasedir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500497 try:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500498 tinfoil.logger.setLevel(logging.WARNING)
499
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500500 # FIXME this results in a cache reload under control of tinfoil, which is fine
501 # except we don't get the knotty progress bar
502
503 if os.path.exists(appendfile):
504 appendbackup = os.path.join(tempdir, os.path.basename(appendfile) + '.bak')
505 shutil.copyfile(appendfile, appendbackup)
506 else:
507 appendbackup = None
508 bb.utils.mkdirhier(os.path.dirname(appendfile))
509 logger.debug('writing append file %s' % appendfile)
510 with open(appendfile, 'a') as f:
511 f.write('###--- _extract_source\n')
512 f.write('DEVTOOL_TEMPDIR = "%s"\n' % tempdir)
513 f.write('DEVTOOL_DEVBRANCH = "%s"\n' % devbranch)
514 if not is_kernel_yocto:
515 f.write('PATCHTOOL = "git"\n')
516 f.write('PATCH_COMMIT_FUNCTIONS = "1"\n')
517 f.write('inherit devtool-source\n')
518 f.write('###--- _extract_source\n')
519
520 update_unlockedsigs(basepath, workspace, fixed_setup, [pn])
521
522 sstate_manifests = d.getVar('SSTATE_MANIFESTS')
523 bb.utils.mkdirhier(sstate_manifests)
524 preservestampfile = os.path.join(sstate_manifests, 'preserve-stamps')
525 with open(preservestampfile, 'w') as f:
526 f.write(d.getVar('STAMP'))
527 try:
528 if bb.data.inherits_class('kernel-yocto', d):
529 # We need to generate the kernel config
530 task = 'do_configure'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500531 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500532 task = 'do_patch'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500533
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500534 # Run the fetch + unpack tasks
535 res = tinfoil.build_targets(pn,
536 task,
537 handle_events=True)
538 finally:
539 if os.path.exists(preservestampfile):
540 os.remove(preservestampfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500541
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500542 if not res:
543 raise DevtoolError('Extracting source for %s failed' % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500544
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500545 with open(os.path.join(tempdir, 'initial_rev'), 'r') as f:
546 initial_rev = f.read()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500547
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500548 with open(os.path.join(tempdir, 'srcsubdir'), 'r') as f:
549 srcsubdir = f.read()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500550
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500551 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
552 srctree_localdir = os.path.join(srctree, 'oe-local-files')
553
554 if sync:
555 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
556
557 # Move oe-local-files directory to srctree
558 # As the oe-local-files is not part of the constructed git tree,
559 # remove them directly during the synchrounizating might surprise
560 # the users. Instead, we move it to oe-local-files.bak and remind
561 # user in the log message.
562 if os.path.exists(srctree_localdir + '.bak'):
563 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
564
565 if os.path.exists(srctree_localdir):
566 logger.info('Backing up current local file directory %s' % srctree_localdir)
567 shutil.move(srctree_localdir, srctree_localdir + '.bak')
568
569 if os.path.exists(tempdir_localdir):
570 logger.info('Syncing local source files to srctree...')
571 shutil.copytree(tempdir_localdir, srctree_localdir)
572 else:
573 # Move oe-local-files directory to srctree
574 if os.path.exists(tempdir_localdir):
575 logger.info('Adding local source files to srctree...')
576 shutil.move(tempdir_localdir, srcsubdir)
577
578 shutil.move(srcsubdir, srctree)
579
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500580 if os.path.abspath(d.getVar('S')) == os.path.abspath(d.getVar('WORKDIR')):
581 # If recipe extracts to ${WORKDIR}, symlink the files into the srctree
582 # (otherwise the recipe won't build as expected)
583 local_files_dir = os.path.join(srctree, 'oe-local-files')
584 addfiles = []
585 for root, _, files in os.walk(local_files_dir):
586 relpth = os.path.relpath(root, local_files_dir)
587 if relpth != '.':
588 bb.utils.mkdirhier(os.path.join(srctree, relpth))
589 for fn in files:
590 if fn == '.gitignore':
591 continue
592 destpth = os.path.join(srctree, relpth, fn)
593 if os.path.exists(destpth):
594 os.unlink(destpth)
595 os.symlink('oe-local-files/%s' % fn, destpth)
596 addfiles.append(os.path.join(relpth, fn))
597 if addfiles:
598 bb.process.run('git add %s' % ' '.join(addfiles), cwd=srctree)
599 useroptions = []
600 oe.patch.GitApplyTree.gitCommandUserOptions(useroptions, d=d)
601 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)
602
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500603 if is_kernel_yocto:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500604 logger.info('Copying kernel config to srctree')
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500605 shutil.copy2(os.path.join(tempdir, '.config'), srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500606
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500607 finally:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500608 if appendbackup:
609 shutil.copyfile(appendbackup, appendfile)
610 elif os.path.exists(appendfile):
611 os.remove(appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500612 if keep_temp:
613 logger.info('Preserving temporary directory %s' % tempdir)
614 else:
615 shutil.rmtree(tempdir)
616 return initial_rev
617
618def _add_md5(config, recipename, filename):
619 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
620 import bb.utils
621
622 def addfile(fn):
623 md5 = bb.utils.md5_file(fn)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500624 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a+') as f:
625 md5_str = '%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5)
626 f.seek(0, os.SEEK_SET)
627 if not md5_str in f.read():
628 f.write(md5_str)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500629
630 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500631 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500632 for f in files:
633 addfile(os.path.join(root, f))
634 else:
635 addfile(filename)
636
637def _check_preserve(config, recipename):
638 """Check if a file was manually changed and needs to be saved in 'attic'
639 directory"""
640 import bb.utils
641 origfile = os.path.join(config.workspace_path, '.devtool_md5')
642 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500643 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500644 with open(origfile, 'r') as f:
645 with open(newfile, 'w') as tf:
646 for line in f.readlines():
647 splitline = line.rstrip().split('|')
648 if splitline[0] == recipename:
649 removefile = os.path.join(config.workspace_path, splitline[1])
650 try:
651 md5 = bb.utils.md5_file(removefile)
652 except IOError as err:
653 if err.errno == 2:
654 # File no longer exists, skip it
655 continue
656 else:
657 raise
658 if splitline[2] != md5:
659 bb.utils.mkdirhier(preservepath)
660 preservefile = os.path.basename(removefile)
661 logger.warn('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
662 shutil.move(removefile, os.path.join(preservepath, preservefile))
663 else:
664 os.remove(removefile)
665 else:
666 tf.write(line)
667 os.rename(newfile, origfile)
668
669def modify(args, config, basepath, workspace):
670 """Entry point for the devtool 'modify' subcommand"""
671 import bb
672 import oe.recipeutils
673
674 if args.recipename in workspace:
675 raise DevtoolError("recipe %s is already in your workspace" %
676 args.recipename)
677
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500678 tinfoil = setup_tinfoil(basepath=basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600679 try:
680 rd = parse_recipe(config, tinfoil, args.recipename, True)
681 if not rd:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500682 return 1
683
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500684 pn = rd.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600685 if pn != args.recipename:
686 logger.info('Mapping %s to %s' % (args.recipename, pn))
687 if pn in workspace:
688 raise DevtoolError("recipe %s is already in your workspace" %
689 pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500690
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600691 if args.srctree:
692 srctree = os.path.abspath(args.srctree)
693 else:
694 srctree = get_default_srctree(config, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500695
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600696 if args.no_extract and not os.path.isdir(srctree):
697 raise DevtoolError("--no-extract specified and source path %s does "
698 "not exist or is not a directory" %
699 srctree)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600700
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500701 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600702 appendfile = recipe_to_append(recipefile, config, args.wildcard)
703 if os.path.exists(appendfile):
704 raise DevtoolError("Another variant of recipe %s is already in your "
705 "workspace (only one variant of a recipe can "
706 "currently be worked on at once)"
707 % pn)
708
709 _check_compatible_recipe(pn, rd)
710
711 initial_rev = None
712 commits = []
713 if not args.no_extract:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500714 initial_rev = _extract_source(srctree, args.keep_temp, args.branch, False, config, basepath, workspace, args.fixed_setup, rd, tinfoil)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500715 if not initial_rev:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600716 return 1
717 logger.info('Source tree extracted to %s' % srctree)
718 # Get list of commits since this revision
719 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
720 commits = stdout.split()
721 else:
722 if os.path.exists(os.path.join(srctree, '.git')):
723 # Check if it's a tree previously extracted by us
724 try:
725 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
726 except bb.process.ExecutionError:
727 stdout = ''
728 for line in stdout.splitlines():
729 if line.startswith('*'):
730 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
731 initial_rev = stdout.rstrip()
732 if not initial_rev:
733 # Otherwise, just grab the head revision
734 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
735 initial_rev = stdout.rstrip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500736
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600737 # Check that recipe isn't using a shared workdir
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500738 s = os.path.abspath(rd.getVar('S'))
739 workdir = os.path.abspath(rd.getVar('WORKDIR'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600740 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
741 # Handle if S is set to a subdirectory of the source
742 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
743 srctree = os.path.join(srctree, srcsubdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500744
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600745 bb.utils.mkdirhier(os.path.dirname(appendfile))
746 with open(appendfile, 'w') as f:
747 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
748 # Local files can be modified/tracked in separate subdir under srctree
749 # Mostly useful for packages with S != WORKDIR
750 f.write('FILESPATH_prepend := "%s:"\n' %
751 os.path.join(srctree, 'oe-local-files'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500752
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600753 f.write('\ninherit externalsrc\n')
754 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
755 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500756
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600757 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
758 if b_is_s:
759 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500760
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600761 if bb.data.inherits_class('kernel', rd):
762 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500763 'do_fetch do_unpack do_kernel_configme do_kernel_configcheck"\n')
764 f.write('\ndo_patch() {\n'
765 ' :\n'
766 '}\n')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600767 f.write('\ndo_configure_append() {\n'
768 ' cp ${B}/.config ${S}/.config.baseline\n'
769 ' ln -sfT ${B}/.config ${S}/.config.new\n'
770 '}\n')
771 if initial_rev:
772 f.write('\n# initial_rev: %s\n' % initial_rev)
773 for commit in commits:
774 f.write('# commit: %s\n' % commit)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500775
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500776 update_unlockedsigs(basepath, workspace, args.fixed_setup, [pn])
777
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600778 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500779
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600780 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500781
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600782 finally:
783 tinfoil.shutdown()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500784
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500785 return 0
786
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500787
788def rename(args, config, basepath, workspace):
789 """Entry point for the devtool 'rename' subcommand"""
790 import bb
791 import oe.recipeutils
792
793 check_workspace_recipe(workspace, args.recipename)
794
795 if not (args.newname or args.version):
796 raise DevtoolError('You must specify a new name, a version with -V/--version, or both')
797
798 recipefile = workspace[args.recipename]['recipefile']
799 if not recipefile:
800 raise DevtoolError('devtool rename can only be used where the recipe file itself is in the workspace (e.g. after devtool add)')
801
802 if args.newname and args.newname != args.recipename:
803 reason = oe.recipeutils.validate_pn(args.newname)
804 if reason:
805 raise DevtoolError(reason)
806 newname = args.newname
807 else:
808 newname = args.recipename
809
810 append = workspace[args.recipename]['bbappend']
811 appendfn = os.path.splitext(os.path.basename(append))[0]
812 splitfn = appendfn.split('_')
813 if len(splitfn) > 1:
814 origfnver = appendfn.split('_')[1]
815 else:
816 origfnver = ''
817
818 recipefilemd5 = None
819 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
820 try:
821 rd = parse_recipe(config, tinfoil, args.recipename, True)
822 if not rd:
823 return 1
824
825 bp = rd.getVar('BP')
826 bpn = rd.getVar('BPN')
827 if newname != args.recipename:
828 localdata = rd.createCopy()
829 localdata.setVar('PN', newname)
830 newbpn = localdata.getVar('BPN')
831 else:
832 newbpn = bpn
833 s = rd.getVar('S', False)
834 src_uri = rd.getVar('SRC_URI', False)
835 pv = rd.getVar('PV')
836
837 # Correct variable values that refer to the upstream source - these
838 # values must stay the same, so if the name/version are changing then
839 # we need to fix them up
840 new_s = s
841 new_src_uri = src_uri
842 if newbpn != bpn:
843 # ${PN} here is technically almost always incorrect, but people do use it
844 new_s = new_s.replace('${BPN}', bpn)
845 new_s = new_s.replace('${PN}', bpn)
846 new_s = new_s.replace('${BP}', '%s-${PV}' % bpn)
847 new_src_uri = new_src_uri.replace('${BPN}', bpn)
848 new_src_uri = new_src_uri.replace('${PN}', bpn)
849 new_src_uri = new_src_uri.replace('${BP}', '%s-${PV}' % bpn)
850 if args.version and origfnver == pv:
851 new_s = new_s.replace('${PV}', pv)
852 new_s = new_s.replace('${BP}', '${BPN}-%s' % pv)
853 new_src_uri = new_src_uri.replace('${PV}', pv)
854 new_src_uri = new_src_uri.replace('${BP}', '${BPN}-%s' % pv)
855 patchfields = {}
856 if new_s != s:
857 patchfields['S'] = new_s
858 if new_src_uri != src_uri:
859 patchfields['SRC_URI'] = new_src_uri
860 if patchfields:
861 recipefilemd5 = bb.utils.md5_file(recipefile)
862 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
863 newrecipefilemd5 = bb.utils.md5_file(recipefile)
864 finally:
865 tinfoil.shutdown()
866
867 if args.version:
868 newver = args.version
869 else:
870 newver = origfnver
871
872 if newver:
873 newappend = '%s_%s.bbappend' % (newname, newver)
874 newfile = '%s_%s.bb' % (newname, newver)
875 else:
876 newappend = '%s.bbappend' % newname
877 newfile = '%s.bb' % newname
878
879 oldrecipedir = os.path.dirname(recipefile)
880 newrecipedir = os.path.join(config.workspace_path, 'recipes', newname)
881 if oldrecipedir != newrecipedir:
882 bb.utils.mkdirhier(newrecipedir)
883
884 newappend = os.path.join(os.path.dirname(append), newappend)
885 newfile = os.path.join(newrecipedir, newfile)
886
887 # Rename bbappend
888 logger.info('Renaming %s to %s' % (append, newappend))
889 os.rename(append, newappend)
890 # Rename recipe file
891 logger.info('Renaming %s to %s' % (recipefile, newfile))
892 os.rename(recipefile, newfile)
893
894 # Rename source tree if it's the default path
895 appendmd5 = None
896 if not args.no_srctree:
897 srctree = workspace[args.recipename]['srctree']
898 if os.path.abspath(srctree) == os.path.join(config.workspace_path, 'sources', args.recipename):
899 newsrctree = os.path.join(config.workspace_path, 'sources', newname)
900 logger.info('Renaming %s to %s' % (srctree, newsrctree))
901 shutil.move(srctree, newsrctree)
902 # Correct any references (basically EXTERNALSRC*) in the .bbappend
903 appendmd5 = bb.utils.md5_file(newappend)
904 appendlines = []
905 with open(newappend, 'r') as f:
906 for line in f:
907 appendlines.append(line)
908 with open(newappend, 'w') as f:
909 for line in appendlines:
910 if srctree in line:
911 line = line.replace(srctree, newsrctree)
912 f.write(line)
913 newappendmd5 = bb.utils.md5_file(newappend)
914
915 bpndir = None
916 newbpndir = None
917 if newbpn != bpn:
918 bpndir = os.path.join(oldrecipedir, bpn)
919 if os.path.exists(bpndir):
920 newbpndir = os.path.join(newrecipedir, newbpn)
921 logger.info('Renaming %s to %s' % (bpndir, newbpndir))
922 shutil.move(bpndir, newbpndir)
923
924 bpdir = None
925 newbpdir = None
926 if newver != origfnver or newbpn != bpn:
927 bpdir = os.path.join(oldrecipedir, bp)
928 if os.path.exists(bpdir):
929 newbpdir = os.path.join(newrecipedir, '%s-%s' % (newbpn, newver))
930 logger.info('Renaming %s to %s' % (bpdir, newbpdir))
931 shutil.move(bpdir, newbpdir)
932
933 if oldrecipedir != newrecipedir:
934 # Move any stray files and delete the old recipe directory
935 for entry in os.listdir(oldrecipedir):
936 oldpath = os.path.join(oldrecipedir, entry)
937 newpath = os.path.join(newrecipedir, entry)
938 logger.info('Renaming %s to %s' % (oldpath, newpath))
939 shutil.move(oldpath, newpath)
940 os.rmdir(oldrecipedir)
941
942 # Now take care of entries in .devtool_md5
943 md5entries = []
944 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'r') as f:
945 for line in f:
946 md5entries.append(line)
947
948 if bpndir and newbpndir:
949 relbpndir = os.path.relpath(bpndir, config.workspace_path) + '/'
950 else:
951 relbpndir = None
952 if bpdir and newbpdir:
953 relbpdir = os.path.relpath(bpdir, config.workspace_path) + '/'
954 else:
955 relbpdir = None
956
957 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'w') as f:
958 for entry in md5entries:
959 splitentry = entry.rstrip().split('|')
960 if len(splitentry) > 2:
961 if splitentry[0] == args.recipename:
962 splitentry[0] = newname
963 if splitentry[1] == os.path.relpath(append, config.workspace_path):
964 splitentry[1] = os.path.relpath(newappend, config.workspace_path)
965 if appendmd5 and splitentry[2] == appendmd5:
966 splitentry[2] = newappendmd5
967 elif splitentry[1] == os.path.relpath(recipefile, config.workspace_path):
968 splitentry[1] = os.path.relpath(newfile, config.workspace_path)
969 if recipefilemd5 and splitentry[2] == recipefilemd5:
970 splitentry[2] = newrecipefilemd5
971 elif relbpndir and splitentry[1].startswith(relbpndir):
972 splitentry[1] = os.path.relpath(os.path.join(newbpndir, splitentry[1][len(relbpndir):]), config.workspace_path)
973 elif relbpdir and splitentry[1].startswith(relbpdir):
974 splitentry[1] = os.path.relpath(os.path.join(newbpdir, splitentry[1][len(relbpdir):]), config.workspace_path)
975 entry = '|'.join(splitentry) + '\n'
976 f.write(entry)
977 return 0
978
979
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600980def _get_patchset_revs(srctree, recipe_path, initial_rev=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500981 """Get initial and update rev of a recipe. These are the start point of the
982 whole patchset and start point for the patches to be re-generated/updated.
983 """
984 import bb
985
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600986 # Parse initial rev from recipe if not specified
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500987 commits = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500988 with open(recipe_path, 'r') as f:
989 for line in f:
990 if line.startswith('# initial_rev:'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600991 if not initial_rev:
992 initial_rev = line.split(':')[-1].strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500993 elif line.startswith('# commit:'):
994 commits.append(line.split(':')[-1].strip())
995
996 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500997 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500998 if initial_rev:
999 # Find first actually changed revision
1000 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
1001 initial_rev, cwd=srctree)
1002 newcommits = stdout.split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001003 for i in range(min(len(commits), len(newcommits))):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001004 if newcommits[i] == commits[i]:
1005 update_rev = commits[i]
1006
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001007 try:
1008 stdout, _ = bb.process.run('git cherry devtool-patched',
1009 cwd=srctree)
1010 except bb.process.ExecutionError as err:
1011 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001012
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001013 if stdout is not None:
1014 changed_revs = []
1015 for line in stdout.splitlines():
1016 if line.startswith('+ '):
1017 rev = line.split()[1]
1018 if rev in newcommits:
1019 changed_revs.append(rev)
1020
1021 return initial_rev, update_rev, changed_revs
1022
1023def _remove_file_entries(srcuri, filelist):
1024 """Remove file:// entries from SRC_URI"""
1025 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001026 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001027 for fname in filelist:
1028 basename = os.path.basename(fname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001029 for i in range(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001030 if (srcuri[i].startswith('file://') and
1031 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001032 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001033 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001034 srcuri.pop(i)
1035 break
1036 return entries, remaining
1037
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001038def _replace_srcuri_entry(srcuri, filename, newentry):
1039 """Replace entry corresponding to specified file with a new entry"""
1040 basename = os.path.basename(filename)
1041 for i in range(len(srcuri)):
1042 if os.path.basename(srcuri[i].split(';')[0]) == basename:
1043 srcuri.pop(i)
1044 srcuri.insert(i, newentry)
1045 break
1046
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001047def _remove_source_files(append, files, destpath):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001048 """Unlink existing patch files"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001049 for path in files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001050 if append:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001051 if not destpath:
1052 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001053 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001054
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001055 if os.path.exists(path):
1056 logger.info('Removing file %s' % path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001057 # FIXME "git rm" here would be nice if the file in question is
1058 # tracked
1059 # FIXME there's a chance that this file is referred to by
1060 # another recipe, in which case deleting wouldn't be the
1061 # right thing to do
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001062 os.remove(path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001063 # Remove directory if empty
1064 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001065 os.rmdir(os.path.dirname(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001066 except OSError as ose:
1067 if ose.errno != errno.ENOTEMPTY:
1068 raise
1069
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001070
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001071def _export_patches(srctree, rd, start_rev, destdir, changed_revs=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001072 """Export patches from srctree to given location.
1073 Returns three-tuple of dicts:
1074 1. updated - patches that already exist in SRCURI
1075 2. added - new patches that don't exist in SRCURI
1076 3 removed - patches that exist in SRCURI but not in exported patches
1077 In each dict the key is the 'basepath' of the URI and value is the
1078 absolute path to the existing file in recipe space (if any).
1079 """
1080 import oe.recipeutils
1081 from oe.patch import GitApplyTree
1082 updated = OrderedDict()
1083 added = OrderedDict()
1084 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
1085
1086 existing_patches = dict((os.path.basename(path), path) for path in
1087 oe.recipeutils.get_recipe_patches(rd))
1088
1089 # Generate patches from Git, exclude local files directory
1090 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
1091 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
1092
1093 new_patches = sorted(os.listdir(destdir))
1094 for new_patch in new_patches:
1095 # Strip numbering from patch names. If it's a git sequence named patch,
1096 # the numbers might not match up since we are starting from a different
1097 # revision This does assume that people are using unique shortlog
1098 # values, but they ought to be anyway...
1099 new_basename = seqpatch_re.match(new_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001100 match_name = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001101 for old_patch in existing_patches:
1102 old_basename = seqpatch_re.match(old_patch).group(2)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001103 old_basename_splitext = os.path.splitext(old_basename)
1104 if old_basename.endswith(('.gz', '.bz2', '.Z')) and old_basename_splitext[0] == new_basename:
1105 old_patch_noext = os.path.splitext(old_patch)[0]
1106 match_name = old_patch_noext
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001107 break
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001108 elif new_basename == old_basename:
1109 match_name = old_patch
1110 break
1111 if match_name:
1112 # Rename patch files
1113 if new_patch != match_name:
1114 os.rename(os.path.join(destdir, new_patch),
1115 os.path.join(destdir, match_name))
1116 # Need to pop it off the list now before checking changed_revs
1117 oldpath = existing_patches.pop(old_patch)
1118 if changed_revs is not None:
1119 # Avoid updating patches that have not actually changed
1120 with open(os.path.join(destdir, match_name), 'r') as f:
1121 firstlineitems = f.readline().split()
1122 # Looking for "From <hash>" line
1123 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1124 if not firstlineitems[1] in changed_revs:
1125 continue
1126 # Recompress if necessary
1127 if oldpath.endswith(('.gz', '.Z')):
1128 bb.process.run(['gzip', match_name], cwd=destdir)
1129 if oldpath.endswith('.gz'):
1130 match_name += '.gz'
1131 else:
1132 match_name += '.Z'
1133 elif oldpath.endswith('.bz2'):
1134 bb.process.run(['bzip2', match_name], cwd=destdir)
1135 match_name += '.bz2'
1136 updated[match_name] = oldpath
1137 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001138 added[new_patch] = None
1139 return (updated, added, existing_patches)
1140
1141
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001142def _create_kconfig_diff(srctree, rd, outfile):
1143 """Create a kconfig fragment"""
1144 # Only update config fragment if both config files exist
1145 orig_config = os.path.join(srctree, '.config.baseline')
1146 new_config = os.path.join(srctree, '.config.new')
1147 if os.path.exists(orig_config) and os.path.exists(new_config):
1148 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
1149 '--unchanged-line-format=', orig_config, new_config]
1150 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
1151 stderr=subprocess.PIPE)
1152 stdout, stderr = pipe.communicate()
1153 if pipe.returncode == 1:
1154 logger.info("Updating config fragment %s" % outfile)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001155 with open(outfile, 'wb') as fobj:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001156 fobj.write(stdout)
1157 elif pipe.returncode == 0:
1158 logger.info("Would remove config fragment %s" % outfile)
1159 if os.path.exists(outfile):
1160 # Remove fragment file in case of empty diff
1161 logger.info("Removing config fragment %s" % outfile)
1162 os.unlink(outfile)
1163 else:
1164 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
1165 return True
1166 return False
1167
1168
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001169def _export_local_files(srctree, rd, destdir):
1170 """Copy local files from srctree to given location.
1171 Returns three-tuple of dicts:
1172 1. updated - files that already exist in SRCURI
1173 2. added - new files files that don't exist in SRCURI
1174 3 removed - files that exist in SRCURI but not in exported files
1175 In each dict the key is the 'basepath' of the URI and value is the
1176 absolute path to the existing file in recipe space (if any).
1177 """
1178 import oe.recipeutils
1179
1180 # Find out local files (SRC_URI files that exist in the "recipe space").
1181 # Local files that reside in srctree are not included in patch generation.
1182 # Instead they are directly copied over the original source files (in
1183 # recipe space).
1184 existing_files = oe.recipeutils.get_recipe_local_files(rd)
1185 new_set = None
1186 updated = OrderedDict()
1187 added = OrderedDict()
1188 removed = OrderedDict()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001189 local_files_dir = os.path.join(srctree, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001190 git_files = _git_ls_tree(srctree)
1191 if 'oe-local-files' in git_files:
1192 # If tracked by Git, take the files from srctree HEAD. First get
1193 # the tree object of the directory
1194 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
1195 tree = git_files['oe-local-files'][2]
1196 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
1197 env=dict(os.environ, GIT_WORK_TREE=destdir,
1198 GIT_INDEX_FILE=tmp_index))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001199 new_set = list(_git_ls_tree(srctree, tree, True).keys())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001200 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001201 # If not tracked by Git, just copy from working copy
1202 new_set = _ls_tree(os.path.join(srctree, 'oe-local-files'))
1203 bb.process.run(['cp', '-ax',
1204 os.path.join(srctree, 'oe-local-files', '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001205 else:
1206 new_set = []
1207
1208 # Special handling for kernel config
1209 if bb.data.inherits_class('kernel-yocto', rd):
1210 fragment_fn = 'devtool-fragment.cfg'
1211 fragment_path = os.path.join(destdir, fragment_fn)
1212 if _create_kconfig_diff(srctree, rd, fragment_path):
1213 if os.path.exists(fragment_path):
1214 if fragment_fn not in new_set:
1215 new_set.append(fragment_fn)
1216 # Copy fragment to local-files
1217 if os.path.isdir(local_files_dir):
1218 shutil.copy2(fragment_path, local_files_dir)
1219 else:
1220 if fragment_fn in new_set:
1221 new_set.remove(fragment_fn)
1222 # Remove fragment from local-files
1223 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1224 os.unlink(os.path.join(local_files_dir, fragment_fn))
1225
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001226 if new_set is not None:
1227 for fname in new_set:
1228 if fname in existing_files:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001229 origpath = existing_files.pop(fname)
1230 workpath = os.path.join(local_files_dir, fname)
1231 if not filecmp.cmp(origpath, workpath):
1232 updated[fname] = origpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001233 elif fname != '.gitignore':
1234 added[fname] = None
1235
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001236 workdir = rd.getVar('WORKDIR')
1237 s = rd.getVar('S')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001238 if not s.endswith(os.sep):
1239 s += os.sep
1240
1241 if workdir != s:
1242 # Handle files where subdir= was specified
1243 for fname in list(existing_files.keys()):
1244 # FIXME handle both subdir starting with BP and not?
1245 fworkpath = os.path.join(workdir, fname)
1246 if fworkpath.startswith(s):
1247 fpath = os.path.join(srctree, os.path.relpath(fworkpath, s))
1248 if os.path.exists(fpath):
1249 origpath = existing_files.pop(fname)
1250 if not filecmp.cmp(origpath, fpath):
1251 updated[fpath] = origpath
1252
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001253 removed = existing_files
1254 return (updated, added, removed)
1255
1256
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001257def _determine_files_dir(rd):
1258 """Determine the appropriate files directory for a recipe"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001259 recipedir = rd.getVar('FILE_DIRNAME')
1260 for entry in rd.getVar('FILESPATH').split(':'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001261 relpth = os.path.relpath(entry, recipedir)
1262 if not os.sep in relpth:
1263 # One (or zero) levels below only, so we don't put anything in machine-specific directories
1264 if os.path.isdir(entry):
1265 return entry
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001266 return os.path.join(recipedir, rd.getVar('BPN'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001267
1268
1269def _update_recipe_srcrev(srctree, rd, appendlayerdir, wildcard_version, no_remove):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001270 """Implement the 'srcrev' mode of update-recipe"""
1271 import bb
1272 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001273
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001274 recipefile = rd.getVar('FILE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001275 logger.info('Updating SRCREV in recipe %s' % os.path.basename(recipefile))
1276
1277 # Get HEAD revision
1278 try:
1279 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1280 except bb.process.ExecutionError as err:
1281 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1282 (srctree, err))
1283 srcrev = stdout.strip()
1284 if len(srcrev) != 40:
1285 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1286
1287 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001288 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001289 patchfields = {}
1290 patchfields['SRCREV'] = srcrev
1291 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001292 srcuri = orig_src_uri.split()
1293 tempdir = tempfile.mkdtemp(prefix='devtool')
1294 update_srcuri = False
1295 try:
1296 local_files_dir = tempfile.mkdtemp(dir=tempdir)
1297 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001298 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001299 # Find list of existing patches in recipe file
1300 patches_dir = tempfile.mkdtemp(dir=tempdir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001301 old_srcrev = rd.getVar('SRCREV') or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001302 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1303 patches_dir)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001304 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 -05001305
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001306 # Remove deleted local files and "overlapping" patches
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001307 remove_files = list(del_f.values()) + list(upd_p.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001308 if remove_files:
1309 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1310 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001311
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001312 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001313 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001314 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001315 removevalues = {}
1316 if update_srcuri:
1317 removevalues = {'SRC_URI': removedentries}
1318 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
1319 _, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001320 rd, appendlayerdir, files, wildcardver=wildcard_version,
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001321 extralines=patchfields, removevalues=removevalues)
1322 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001323 files_dir = _determine_files_dir(rd)
1324 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001325 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001326 if os.path.isabs(basepath):
1327 # Original file (probably with subdir pointing inside source tree)
1328 # so we do not want to move it, just copy
1329 _copy_file(basepath, path)
1330 else:
1331 _move_file(os.path.join(local_files_dir, basepath), path)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001332 update_srcuri= True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001333 for basepath, path in new_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001334 logger.info('Adding new file %s' % basepath)
1335 _move_file(os.path.join(local_files_dir, basepath),
1336 os.path.join(files_dir, basepath))
1337 srcuri.append('file://%s' % basepath)
1338 update_srcuri = True
1339 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001340 patchfields['SRC_URI'] = ' '.join(srcuri)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001341 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1342 finally:
1343 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001344 if not 'git://' in orig_src_uri:
1345 logger.info('You will need to update SRC_URI within the recipe to '
1346 'point to a git repository where you have pushed your '
1347 'changes')
1348
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001349 _remove_source_files(appendlayerdir, remove_files, destpath)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001350 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001351
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001352def _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wildcard_version, no_remove, initial_rev):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001353 """Implement the 'patch' mode of update-recipe"""
1354 import bb
1355 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001356
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001357 recipefile = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001358 append = workspace[recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001359 if not os.path.exists(append):
1360 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001361 recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001362
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001363 initial_rev, update_rev, changed_revs = _get_patchset_revs(srctree, append, initial_rev)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001364 if not initial_rev:
1365 raise DevtoolError('Unable to find initial revision - please specify '
1366 'it with --initial-rev')
1367
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001368 dl_dir = rd.getVar('DL_DIR')
1369 if not dl_dir.endswith('/'):
1370 dl_dir += '/'
1371
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001372 tempdir = tempfile.mkdtemp(prefix='devtool')
1373 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001374 local_files_dir = tempfile.mkdtemp(dir=tempdir)
1375 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001376
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001377 remove_files = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001378 if not no_remove:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001379 # Get all patches from source tree and check if any should be removed
1380 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
1381 upd_p, new_p, del_p = _export_patches(srctree, rd, initial_rev,
1382 all_patches_dir)
1383 # Remove deleted local files and patches
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001384 remove_files = list(del_f.values()) + list(del_p.values())
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001385
1386 # Get updated patches from source tree
1387 patches_dir = tempfile.mkdtemp(dir=tempdir)
1388 upd_p, new_p, del_p = _export_patches(srctree, rd, update_rev,
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001389 patches_dir, changed_revs)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001390 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001391 updaterecipe = False
1392 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001393 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001394 if appendlayerdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001395 files = dict((os.path.join(local_files_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001396 key, val in list(upd_f.items()) + list(new_f.items()))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001397 files.update(dict((os.path.join(patches_dir, key), val) for
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001398 key, val in list(upd_p.items()) + list(new_p.items())))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001399 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001400 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001401 if remove_files:
1402 removedentries, remaining = _remove_file_entries(
1403 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001404 if removedentries or remaining:
1405 remaining = ['file://' + os.path.basename(item) for
1406 item in remaining]
1407 removevalues = {'SRC_URI': removedentries + remaining}
1408 _, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001409 rd, appendlayerdir, files,
1410 wildcardver=wildcard_version,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001411 removevalues=removevalues)
1412 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001413 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001414 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001415 # Update existing files
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001416 files_dir = _determine_files_dir(rd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001417 for basepath, path in upd_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001418 logger.info('Updating file %s' % basepath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001419 if os.path.isabs(basepath):
1420 # Original file (probably with subdir pointing inside source tree)
1421 # so we do not want to move it, just copy
1422 _copy_file(basepath, path)
1423 else:
1424 _move_file(os.path.join(local_files_dir, basepath), path)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001425 updatefiles = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001426 for basepath, path in upd_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001427 patchfn = os.path.join(patches_dir, basepath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001428 if os.path.dirname(path) + '/' == dl_dir:
1429 # This is a a downloaded patch file - we now need to
1430 # replace the entry in SRC_URI with our local version
1431 logger.info('Replacing remote patch %s with updated local version' % basepath)
1432 path = os.path.join(files_dir, basepath)
1433 _replace_srcuri_entry(srcuri, basepath, 'file://%s' % basepath)
1434 updaterecipe = True
1435 else:
1436 logger.info('Updating patch %s' % basepath)
1437 logger.debug('Moving new patch %s to %s' % (patchfn, path))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001438 _move_file(patchfn, path)
1439 updatefiles = True
1440 # Add any new files
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001441 for basepath, path in new_f.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001442 logger.info('Adding new file %s' % basepath)
1443 _move_file(os.path.join(local_files_dir, basepath),
1444 os.path.join(files_dir, basepath))
1445 srcuri.append('file://%s' % basepath)
1446 updaterecipe = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001447 for basepath, path in new_p.items():
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001448 logger.info('Adding new patch %s' % basepath)
1449 _move_file(os.path.join(patches_dir, basepath),
1450 os.path.join(files_dir, basepath))
1451 srcuri.append('file://%s' % basepath)
1452 updaterecipe = True
1453 # Update recipe, if needed
1454 if _remove_file_entries(srcuri, remove_files)[0]:
1455 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001456 if updaterecipe:
1457 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1458 oe.recipeutils.patch_recipe(rd, recipefile,
1459 {'SRC_URI': ' '.join(srcuri)})
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001460 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001461 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001462 logger.info('No patches or files need updating')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001463 return False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001464 finally:
1465 shutil.rmtree(tempdir)
1466
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001467 _remove_source_files(appendlayerdir, remove_files, destpath)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001468 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001469
1470def _guess_recipe_update_mode(srctree, rdata):
1471 """Guess the recipe update mode to use"""
1472 src_uri = (rdata.getVar('SRC_URI', False) or '').split()
1473 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1474 if not git_uris:
1475 return 'patch'
1476 # Just use the first URI for now
1477 uri = git_uris[0]
1478 # Check remote branch
1479 params = bb.fetch.decodeurl(uri)[5]
1480 upstr_branch = params['branch'] if 'branch' in params else 'master'
1481 # Check if current branch HEAD is found in upstream branch
1482 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1483 head_rev = stdout.rstrip()
1484 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1485 cwd=srctree)
1486 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1487 if 'origin/' + upstr_branch in remote_brs:
1488 return 'srcrev'
1489
1490 return 'patch'
1491
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001492def _update_recipe(recipename, workspace, rd, mode, appendlayerdir, wildcard_version, no_remove, initial_rev):
1493 srctree = workspace[recipename]['srctree']
1494 if mode == 'auto':
1495 mode = _guess_recipe_update_mode(srctree, rd)
1496
1497 if mode == 'srcrev':
1498 updated = _update_recipe_srcrev(srctree, rd, appendlayerdir, wildcard_version, no_remove)
1499 elif mode == 'patch':
1500 updated = _update_recipe_patch(recipename, workspace, srctree, rd, appendlayerdir, wildcard_version, no_remove, initial_rev)
1501 else:
1502 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1503 return updated
1504
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001505def update_recipe(args, config, basepath, workspace):
1506 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001507 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001508
1509 if args.append:
1510 if not os.path.exists(args.append):
1511 raise DevtoolError('bbappend destination layer directory "%s" '
1512 'does not exist' % args.append)
1513 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1514 raise DevtoolError('conf/layer.conf not found in bbappend '
1515 'destination layer "%s"' % args.append)
1516
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001517 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001518 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001519
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001520 rd = parse_recipe(config, tinfoil, args.recipename, True)
1521 if not rd:
1522 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001523
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001524 updated = _update_recipe(args.recipename, workspace, rd, args.mode, args.append, args.wildcard_version, args.no_remove, args.initial_rev)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001525
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001526 if updated:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001527 rf = rd.getVar('FILE')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001528 if rf.startswith(config.workspace_path):
1529 logger.warn('Recipe file %s has been updated but is inside the workspace - you will need to move it (and any associated files next to it) out to the desired layer before using "devtool reset" in order to keep any changes' % rf)
1530 finally:
1531 tinfoil.shutdown()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001532
1533 return 0
1534
1535
1536def status(args, config, basepath, workspace):
1537 """Entry point for the devtool 'status' subcommand"""
1538 if workspace:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001539 for recipe, value in sorted(workspace.items()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001540 recipefile = value['recipefile']
1541 if recipefile:
1542 recipestr = ' (%s)' % recipefile
1543 else:
1544 recipestr = ''
1545 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001546 else:
1547 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')
1548 return 0
1549
1550
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001551def _reset(recipes, no_clean, config, basepath, workspace):
1552 """Reset one or more recipes"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001553
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001554 def clean_preferred_provider(pn, layerconf_path):
1555 """Remove PREFERRED_PROVIDER from layer.conf'"""
1556 import re
1557 layerconf_file = os.path.join(layerconf_path, 'conf', 'layer.conf')
1558 new_layerconf_file = os.path.join(layerconf_path, 'conf', '.layer.conf')
1559 pprovider_found = False
1560 with open(layerconf_file, 'r') as f:
1561 lines = f.readlines()
1562 with open(new_layerconf_file, 'a') as nf:
1563 for line in lines:
1564 pprovider_exp = r'^PREFERRED_PROVIDER_.*? = "' + pn + r'"$'
1565 if not re.match(pprovider_exp, line):
1566 nf.write(line)
1567 else:
1568 pprovider_found = True
1569 if pprovider_found:
1570 shutil.move(new_layerconf_file, layerconf_file)
1571 else:
1572 os.remove(new_layerconf_file)
1573
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001574 if recipes and not no_clean:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001575 if len(recipes) == 1:
1576 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1577 else:
1578 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001579 # If the recipe file itself was created in the workspace, and
1580 # it uses BBCLASSEXTEND, then we need to also clean the other
1581 # variants
1582 targets = []
1583 for recipe in recipes:
1584 targets.append(recipe)
1585 recipefile = workspace[recipe]['recipefile']
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001586 if recipefile and os.path.exists(recipefile):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001587 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001588 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001589 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001590 except bb.process.ExecutionError as e:
1591 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1592 'wish, you may specify -n/--no-clean to '
1593 'skip running this command when resetting' %
1594 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001595
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001596 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001597 _check_preserve(config, pn)
1598
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001599 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001600 def preservedir(origdir):
1601 if os.path.exists(origdir):
1602 for root, dirs, files in os.walk(origdir):
1603 for fn in files:
1604 logger.warn('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001605 _move_file(os.path.join(origdir, fn),
1606 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001607 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001608 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001609 os.rmdir(origdir)
1610
1611 preservedir(os.path.join(config.workspace_path, 'recipes', pn))
1612 # We don't automatically create this dir next to appends, but the user can
1613 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1614
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001615 srctree = workspace[pn]['srctree']
1616 if os.path.isdir(srctree):
1617 if os.listdir(srctree):
1618 # We don't want to risk wiping out any work in progress
1619 logger.info('Leaving source tree %s as-is; if you no '
1620 'longer need it then please delete it manually'
1621 % srctree)
1622 else:
1623 # This is unlikely, but if it's empty we can just remove it
1624 os.rmdir(srctree)
1625
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001626 clean_preferred_provider(pn, config.workspace_path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001627
1628def reset(args, config, basepath, workspace):
1629 """Entry point for the devtool 'reset' subcommand"""
1630 import bb
1631 if args.recipename:
1632 if args.all:
1633 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
1634 else:
1635 for recipe in args.recipename:
1636 check_workspace_recipe(workspace, recipe, checksrc=False)
1637 elif not args.all:
1638 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1639 "reset all recipes")
1640 if args.all:
1641 recipes = list(workspace.keys())
1642 else:
1643 recipes = args.recipename
1644
1645 _reset(recipes, args.no_clean, config, basepath, workspace)
1646
1647 return 0
1648
1649
1650def _get_layer(layername, d):
1651 """Determine the base layer path for the specified layer name/path"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001652 layerdirs = d.getVar('BBLAYERS').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001653 layers = {os.path.basename(p): p for p in layerdirs}
1654 # Provide some shortcuts
1655 if layername.lower() in ['oe-core', 'openembedded-core']:
1656 layerdir = layers.get('meta', None)
1657 else:
1658 layerdir = layers.get(layername, None)
1659 if layerdir:
1660 layerdir = os.path.abspath(layerdir)
1661 return layerdir or layername
1662
1663def finish(args, config, basepath, workspace):
1664 """Entry point for the devtool 'finish' subcommand"""
1665 import bb
1666 import oe.recipeutils
1667
1668 check_workspace_recipe(workspace, args.recipename)
1669
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001670 no_clean = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001671 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
1672 try:
1673 rd = parse_recipe(config, tinfoil, args.recipename, True)
1674 if not rd:
1675 return 1
1676
1677 destlayerdir = _get_layer(args.destination, tinfoil.config_data)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001678 origlayerdir = oe.recipeutils.find_layerdir(rd.getVar('FILE'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001679
1680 if not os.path.isdir(destlayerdir):
1681 raise DevtoolError('Unable to find layer or directory matching "%s"' % args.destination)
1682
1683 if os.path.abspath(destlayerdir) == config.workspace_path:
1684 raise DevtoolError('"%s" specifies the workspace layer - that is not a valid destination' % args.destination)
1685
1686 # If it's an upgrade, grab the original path
1687 origpath = None
1688 origfilelist = None
1689 append = workspace[args.recipename]['bbappend']
1690 with open(append, 'r') as f:
1691 for line in f:
1692 if line.startswith('# original_path:'):
1693 origpath = line.split(':')[1].strip()
1694 elif line.startswith('# original_files:'):
1695 origfilelist = line.split(':')[1].split()
1696
1697 if origlayerdir == config.workspace_path:
1698 # Recipe file itself is in workspace, update it there first
1699 appendlayerdir = None
1700 origrelpath = None
1701 if origpath:
1702 origlayerpath = oe.recipeutils.find_layerdir(origpath)
1703 if origlayerpath:
1704 origrelpath = os.path.relpath(origpath, origlayerpath)
1705 destpath = oe.recipeutils.get_bbfile_path(rd, destlayerdir, origrelpath)
1706 if not destpath:
1707 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 -05001708 # Warn if the layer isn't in bblayers.conf (the code to create a bbappend will do this in other cases)
1709 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
1710 if not os.path.abspath(destlayerdir) in layerdirs:
1711 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)
1712
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001713 elif destlayerdir == origlayerdir:
1714 # Same layer, update the original recipe
1715 appendlayerdir = None
1716 destpath = None
1717 else:
1718 # Create/update a bbappend in the specified layer
1719 appendlayerdir = destlayerdir
1720 destpath = None
1721
1722 # Remove any old files in the case of an upgrade
1723 if origpath and origfilelist and oe.recipeutils.find_layerdir(origpath) == oe.recipeutils.find_layerdir(destlayerdir):
1724 for fn in origfilelist:
1725 fnp = os.path.join(origpath, fn)
1726 try:
1727 os.remove(fnp)
1728 except FileNotFoundError:
1729 pass
1730
1731 # Actually update the recipe / bbappend
1732 _update_recipe(args.recipename, workspace, rd, args.mode, appendlayerdir, wildcard_version=True, no_remove=False, initial_rev=args.initial_rev)
1733
1734 if origlayerdir == config.workspace_path and destpath:
1735 # Recipe file itself is in the workspace - need to move it and any
1736 # associated files to the specified layer
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001737 no_clean = True
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001738 logger.info('Moving recipe file to %s' % destpath)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001739 recipedir = os.path.dirname(rd.getVar('FILE'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001740 for root, _, files in os.walk(recipedir):
1741 for fn in files:
1742 srcpath = os.path.join(root, fn)
1743 relpth = os.path.relpath(os.path.dirname(srcpath), recipedir)
1744 destdir = os.path.abspath(os.path.join(destpath, relpth))
1745 bb.utils.mkdirhier(destdir)
1746 shutil.move(srcpath, os.path.join(destdir, fn))
1747
1748 finally:
1749 tinfoil.shutdown()
1750
1751 # Everything else has succeeded, we can now reset
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001752 _reset([args.recipename], no_clean=no_clean, config=config, basepath=basepath, workspace=workspace)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001753
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001754 return 0
1755
1756
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001757def get_default_srctree(config, recipename=''):
1758 """Get the default srctree path"""
1759 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
1760 if recipename:
1761 return os.path.join(srctreeparent, 'sources', recipename)
1762 else:
1763 return os.path.join(srctreeparent, 'sources')
1764
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001765def register_commands(subparsers, context):
1766 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001767
1768 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001769 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001770 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.',
1771 group='starting', order=100)
1772 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.')
1773 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
1774 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 -05001775 group = parser_add.add_mutually_exclusive_group()
1776 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
1777 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001778 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 -05001779 parser_add.add_argument('--fetch-dev', help='For npm, also fetch devDependencies', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001780 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001781 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 -05001782 group = parser_add.add_mutually_exclusive_group()
1783 group.add_argument('--srcrev', '-S', help='Source revision to fetch if fetching from an SCM such as git (default latest)')
1784 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")
1785 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 -05001786 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')
1787 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')
1788 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001789 parser_add.add_argument('--mirrors', help='Enable PREMIRRORS and MIRRORS for source tree fetching (disable by default).', action="store_true")
1790 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 -06001791 parser_add.set_defaults(func=add, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001792
1793 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001794 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.',
1795 group='starting', order=90)
1796 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
1797 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 -05001798 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001799 group = parser_modify.add_mutually_exclusive_group()
1800 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
1801 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 -05001802 group = parser_modify.add_mutually_exclusive_group()
1803 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
1804 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001805 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 Bishop6e60e8b2018-02-01 10:27:11 -05001806 parser_modify.add_argument('--keep-temp', help='Keep temporary directory (for debugging)', action="store_true")
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001807 parser_modify.set_defaults(func=modify, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001808
1809 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
1810 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001811 group='advanced')
1812 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001813 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001814 parser_extract.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (default "%(default)s")')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001815 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001816 parser_extract.set_defaults(func=extract, fixed_setup=context.fixed_setup)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001817
1818 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
1819 description='Synchronize the previously extracted source tree for an existing recipe',
1820 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
1821 group='advanced')
1822 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
1823 parser_sync.add_argument('srctree', help='Path to the source tree')
1824 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
1825 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001826 parser_sync.set_defaults(func=sync, fixed_setup=context.fixed_setup)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001827
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001828 parser_rename = subparsers.add_parser('rename', help='Rename a recipe file in the workspace',
1829 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.',
1830 group='working', order=10)
1831 parser_rename.add_argument('recipename', help='Current name of recipe to rename')
1832 parser_rename.add_argument('newname', nargs='?', help='New name for recipe (optional, not needed if you only want to change the version)')
1833 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)')
1834 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')
1835 parser_rename.set_defaults(func=rename)
1836
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001837 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001838 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.',
1839 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001840 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
1841 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 -05001842 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001843 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
1844 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')
1845 parser_update_recipe.add_argument('--no-remove', '-n', action="store_true", help='Don\'t remove patches, only add or update')
1846 parser_update_recipe.set_defaults(func=update_recipe)
1847
1848 parser_status = subparsers.add_parser('status', help='Show workspace status',
1849 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001850 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001851 parser_status.set_defaults(func=status)
1852
1853 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001854 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 -05001855 group='working', order=-100)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001856 parser_reset.add_argument('recipename', nargs='*', help='Recipe to reset')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001857 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
1858 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
1859 parser_reset.set_defaults(func=reset)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001860
1861 parser_finish = subparsers.add_parser('finish', help='Finish working on a recipe in your workspace',
1862 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.',
1863 group='working', order=-100)
1864 parser_finish.add_argument('recipename', help='Recipe to finish')
1865 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.')
1866 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')
1867 parser_finish.add_argument('--initial-rev', help='Override starting revision for patches')
1868 parser_finish.set_defaults(func=finish)