blob: 77a82d5590f259eb031dc24090bb2b28f01f3757 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Development tool - standard commands plugin
2#
3# Copyright (C) 2014-2015 Intel Corporation
4#
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 Williamsf1e5d692016-03-30 15:21:19 -050031from collections import OrderedDict
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050032from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, use_external_build, setup_git_repo, recipe_to_append, get_bbclassextend_targets, DevtoolError
Patrick Williamsc124f4f2015-09-15 14:41:29 -050033from devtool import parse_recipe
34
35logger = logging.getLogger('devtool')
36
37
38def add(args, config, basepath, workspace):
39 """Entry point for the devtool 'add' subcommand"""
40 import bb
41 import oe.recipeutils
42
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050043 if not args.recipename and not args.srctree and not args.fetch and not args.fetchuri:
44 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 -050045
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050046 # These are positional arguments, but because we're nice, allow
47 # specifying e.g. source tree without name, or fetch URI without name or
48 # source tree (if we can detect that that is what the user meant)
49 if '://' in args.recipename:
50 if not args.fetchuri:
51 if args.fetch:
52 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
53 args.fetchuri = args.recipename
54 args.recipename = ''
55 elif args.srctree and '://' in args.srctree:
56 if not args.fetchuri:
57 if args.fetch:
58 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
59 args.fetchuri = args.srctree
60 args.srctree = ''
61 elif args.recipename and not args.srctree:
62 if os.sep in args.recipename:
63 args.srctree = args.recipename
64 args.recipename = None
65 elif os.path.isdir(args.recipename):
66 logger.warn('Ambiguous argument %s - assuming you mean it to be the recipe name')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050067
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050068 if args.fetch:
69 if args.fetchuri:
70 raise DevtoolError('URI specified as positional argument as well as -f/--fetch')
71 else:
72 # FIXME should show a warning that -f/--fetch is deprecated here
73 args.fetchuri = args.fetch
Patrick Williamsf1e5d692016-03-30 15:21:19 -050074
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050075 if args.recipename:
76 if args.recipename in workspace:
77 raise DevtoolError("recipe %s is already in your workspace" %
78 args.recipename)
79 reason = oe.recipeutils.validate_pn(args.recipename)
80 if reason:
81 raise DevtoolError(reason)
82
83 # FIXME this ought to be in validate_pn but we're using that in other contexts
84 if '/' in args.recipename:
85 raise DevtoolError('"/" is not a valid character in recipe names')
86
87 if args.srctree:
88 srctree = os.path.abspath(args.srctree)
89 srctreeparent = None
90 tmpsrcdir = None
91 else:
92 srctree = None
93 srctreeparent = get_default_srctree(config)
94 bb.utils.mkdirhier(srctreeparent)
95 tmpsrcdir = tempfile.mkdtemp(prefix='devtoolsrc', dir=srctreeparent)
96
97 if srctree and os.path.exists(srctree):
98 if args.fetchuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050099 if not os.path.isdir(srctree):
100 raise DevtoolError("Cannot fetch into source tree path %s as "
101 "it exists and is not a directory" %
102 srctree)
103 elif os.listdir(srctree):
104 raise DevtoolError("Cannot fetch into source tree path %s as "
105 "it already exists and is non-empty" %
106 srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500107 elif not args.fetchuri:
108 if args.srctree:
109 raise DevtoolError("Specified source tree %s could not be found" %
110 args.srctree)
111 elif srctree:
112 raise DevtoolError("No source tree exists at default path %s - "
113 "either create and populate this directory, "
114 "or specify a path to a source tree, or a "
115 "URI to fetch source from" % srctree)
116 else:
117 raise DevtoolError("You must either specify a source tree "
118 "or a URI to fetch source from")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500120 if args.version:
121 if '_' in args.version or ' ' in args.version:
122 raise DevtoolError('Invalid version string "%s"' % args.version)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500123
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500124 if args.color == 'auto' and sys.stdout.isatty():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500125 color = 'always'
126 else:
127 color = args.color
128 extracmdopts = ''
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500129 if args.fetchuri:
130 source = args.fetchuri
131 if srctree:
132 extracmdopts += ' -x %s' % srctree
133 else:
134 extracmdopts += ' -x %s' % tmpsrcdir
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500135 else:
136 source = srctree
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500137 if args.recipename:
138 extracmdopts += ' -N %s' % args.recipename
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500139 if args.version:
140 extracmdopts += ' -V %s' % args.version
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500141 if args.binary:
142 extracmdopts += ' -b'
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500143 if args.also_native:
144 extracmdopts += ' --also-native'
145 if args.src_subdir:
146 extracmdopts += ' --src-subdir "%s"' % args.src_subdir
147
148 tempdir = tempfile.mkdtemp(prefix='devtool')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500150 try:
151 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create -o %s "%s" %s' % (color, tempdir, source, extracmdopts))
152 except bb.process.ExecutionError as e:
153 if e.exitcode == 15:
154 raise DevtoolError('Could not auto-determine recipe name, please specify it on the command line')
155 else:
156 raise DevtoolError('Command \'%s\' failed:\n%s' % (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500157
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500158 recipes = glob.glob(os.path.join(tempdir, '*.bb'))
159 if recipes:
160 recipename = os.path.splitext(os.path.basename(recipes[0]))[0].split('_')[0]
161 if recipename in workspace:
162 raise DevtoolError('A recipe with the same name as the one being created (%s) already exists in your workspace' % recipename)
163 recipedir = os.path.join(config.workspace_path, 'recipes', recipename)
164 bb.utils.mkdirhier(recipedir)
165 recipefile = os.path.join(recipedir, os.path.basename(recipes[0]))
166 appendfile = recipe_to_append(recipefile, config)
167 if os.path.exists(appendfile):
168 # This shouldn't be possible, but just in case
169 raise DevtoolError('A recipe with the same name as the one being created already exists in your workspace')
170 if os.path.exists(recipefile):
171 raise DevtoolError('A recipe file %s already exists in your workspace; this shouldn\'t be there - please delete it before continuing' % recipefile)
172 if tmpsrcdir:
173 srctree = os.path.join(srctreeparent, recipename)
174 if os.path.exists(tmpsrcdir):
175 if os.path.exists(srctree):
176 if os.path.isdir(srctree):
177 try:
178 os.rmdir(srctree)
179 except OSError as e:
180 if e.errno == errno.ENOTEMPTY:
181 raise DevtoolError('Source tree path %s already exists and is not empty' % srctree)
182 else:
183 raise
184 else:
185 raise DevtoolError('Source tree path %s already exists and is not a directory' % srctree)
186 logger.info('Using default source tree path %s' % srctree)
187 shutil.move(tmpsrcdir, srctree)
188 else:
189 raise DevtoolError('Couldn\'t find source tree created by recipetool')
190 bb.utils.mkdirhier(recipedir)
191 shutil.move(recipes[0], recipefile)
192 # Move any additional files created by recipetool
193 for fn in os.listdir(tempdir):
194 shutil.move(os.path.join(tempdir, fn), recipedir)
195 else:
196 raise DevtoolError('Command \'%s\' did not create any recipe file:\n%s' % (e.command, e.stdout))
197 attic_recipe = os.path.join(config.workspace_path, 'attic', recipename, os.path.basename(recipefile))
198 if os.path.exists(attic_recipe):
199 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)
200 finally:
201 if tmpsrcdir and os.path.exists(tmpsrcdir):
202 shutil.rmtree(tmpsrcdir)
203 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500204
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500205 for fn in os.listdir(recipedir):
206 _add_md5(config, recipename, os.path.join(recipedir, fn))
207
208 if args.fetchuri and not args.no_git:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500209 setup_git_repo(srctree, args.version, 'devtool')
210
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500211 initial_rev = None
212 if os.path.exists(os.path.join(srctree, '.git')):
213 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
214 initial_rev = stdout.rstrip()
215
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500216 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
217 rd = oe.recipeutils.parse_recipe(recipefile, None, tinfoil.config_data)
218 if not rd:
219 return 1
220
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500221 if args.src_subdir:
222 srctree = os.path.join(srctree, args.src_subdir)
223
224 bb.utils.mkdirhier(os.path.dirname(appendfile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225 with open(appendfile, 'w') as f:
226 f.write('inherit externalsrc\n')
227 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500228
229 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
230 if b_is_s:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500231 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
232 if initial_rev:
233 f.write('\n# initial_rev: %s\n' % initial_rev)
234
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500235 if args.binary:
236 f.write('do_install_append() {\n')
237 f.write(' rm -rf ${D}/.git\n')
238 f.write(' rm -f ${D}/singletask.lock\n')
239 f.write('}\n')
240
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500241 if bb.data.inherits_class('npm', rd):
242 f.write('do_install_append() {\n')
243 f.write(' # Remove files added to source dir by devtool/externalsrc\n')
244 f.write(' rm -f ${NPM_INSTALLDIR}/singletask.lock\n')
245 f.write(' rm -rf ${NPM_INSTALLDIR}/.git\n')
246 f.write(' rm -rf ${NPM_INSTALLDIR}/oe-local-files\n')
247 f.write(' for symlink in ${EXTERNALSRC_SYMLINKS} ; do\n')
248 f.write(' rm -f ${NPM_INSTALLDIR}/${symlink%%:*}\n')
249 f.write(' done\n')
250 f.write('}\n')
251
252 _add_md5(config, recipename, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500253
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500254 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
255
256 tinfoil.shutdown()
257
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500258 return 0
259
260
261def _check_compatible_recipe(pn, d):
262 """Check if the recipe is supported by devtool"""
263 if pn == 'perf':
264 raise DevtoolError("The perf recipe does not actually check out "
265 "source and thus cannot be supported by this tool")
266
267 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
268 raise DevtoolError("The %s recipe is not supported by this tool" % pn)
269
270 if bb.data.inherits_class('image', d):
271 raise DevtoolError("The %s recipe is an image, and therefore is not "
272 "supported by this tool" % pn)
273
274 if bb.data.inherits_class('populate_sdk', d):
275 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
276 "supported by this tool" % pn)
277
278 if bb.data.inherits_class('packagegroup', d):
279 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
280 "not supported by this tool" % pn)
281
282 if bb.data.inherits_class('meta', d):
283 raise DevtoolError("The %s recipe is a meta-recipe, and therefore is "
284 "not supported by this tool" % pn)
285
286 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC', True):
287 raise DevtoolError("externalsrc is currently enabled for the %s "
288 "recipe. This prevents the normal do_patch task "
289 "from working. You will need to disable this "
290 "first." % pn)
291
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500292def _move_file(src, dst):
293 """Move a file. Creates all the directory components of destination path."""
294 dst_d = os.path.dirname(dst)
295 if dst_d:
296 bb.utils.mkdirhier(dst_d)
297 shutil.move(src, dst)
298
299def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
300 """List contents of a git treeish"""
301 import bb
302 cmd = ['git', 'ls-tree', '-z', treeish]
303 if recursive:
304 cmd.append('-r')
305 out, _ = bb.process.run(cmd, cwd=repodir)
306 ret = {}
307 for line in out.split('\0'):
308 if line:
309 split = line.split(None, 4)
310 ret[split[3]] = split[0:3]
311 return ret
312
313def _git_exclude_path(srctree, path):
314 """Return pathspec (list of paths) that excludes certain path"""
315 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
316 # we don't catch files that are deleted, for example. A more reliable way
317 # to implement this would be to use "negative pathspecs" which were
318 # introduced in Git v1.9.0. Revisit this when/if the required Git version
319 # becomes greater than that.
320 path = os.path.normpath(path)
321 recurse = True if len(path.split(os.path.sep)) > 1 else False
322 git_files = _git_ls_tree(srctree, 'HEAD', recurse).keys()
323 if path in git_files:
324 git_files.remove(path)
325 return git_files
326 else:
327 return ['.']
328
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500329def _ls_tree(directory):
330 """Recursive listing of files in a directory"""
331 ret = []
332 for root, dirs, files in os.walk(directory):
333 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
334 fname in files])
335 return ret
336
337
338def extract(args, config, basepath, workspace):
339 """Entry point for the devtool 'extract' subcommand"""
340 import bb
341
342 tinfoil = _prep_extract_operation(config, basepath, args.recipename)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500343 if not tinfoil:
344 # Error already shown
345 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500346
347 rd = parse_recipe(config, tinfoil, args.recipename, True)
348 if not rd:
349 return 1
350
351 srctree = os.path.abspath(args.srctree)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500352 initial_rev = _extract_source(srctree, args.keep_temp, args.branch, False, rd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500353 logger.info('Source tree extracted to %s' % srctree)
354
355 if initial_rev:
356 return 0
357 else:
358 return 1
359
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500360def sync(args, config, basepath, workspace):
361 """Entry point for the devtool 'sync' subcommand"""
362 import bb
363
364 tinfoil = _prep_extract_operation(config, basepath, args.recipename)
365 if not tinfoil:
366 # Error already shown
367 return 1
368
369 rd = parse_recipe(config, tinfoil, args.recipename, True)
370 if not rd:
371 return 1
372
373 srctree = os.path.abspath(args.srctree)
374 initial_rev = _extract_source(srctree, args.keep_temp, args.branch, True, rd)
375 logger.info('Source tree %s synchronized' % srctree)
376
377 if initial_rev:
378 return 0
379 else:
380 return 1
381
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500382class BbTaskExecutor(object):
383 """Class for executing bitbake tasks for a recipe
384
385 FIXME: This is very awkward. Unfortunately it's not currently easy to
386 properly execute tasks outside of bitbake itself, until then this has to
387 suffice if we are to handle e.g. linux-yocto's extra tasks
388 """
389
390 def __init__(self, rdata):
391 self.rdata = rdata
392 self.executed = []
393
394 def exec_func(self, func, report):
395 """Run bitbake task function"""
396 if not func in self.executed:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500397 deps = self.rdata.getVarFlag(func, 'deps', False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500398 if deps:
399 for taskdepfunc in deps:
400 self.exec_func(taskdepfunc, True)
401 if report:
402 logger.info('Executing %s...' % func)
403 fn = self.rdata.getVar('FILE', True)
404 localdata = bb.build._task_data(fn, func, self.rdata)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500405 try:
406 bb.build.exec_func(func, localdata)
407 except bb.build.FuncFailed as e:
408 raise DevtoolError(str(e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500409 self.executed.append(func)
410
411
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500412class PatchTaskExecutor(BbTaskExecutor):
413 def __init__(self, rdata):
414 self.check_git = False
415 super(PatchTaskExecutor, self).__init__(rdata)
416
417 def exec_func(self, func, report):
418 from oe.patch import GitApplyTree
419 srcsubdir = self.rdata.getVar('S', True)
420 haspatches = False
421 if func == 'do_patch':
422 patchdir = os.path.join(srcsubdir, 'patches')
423 if os.path.exists(patchdir):
424 if os.listdir(patchdir):
425 haspatches = True
426 else:
427 os.rmdir(patchdir)
428
429 super(PatchTaskExecutor, self).exec_func(func, report)
430 if self.check_git and os.path.exists(srcsubdir):
431 if func == 'do_patch':
432 if os.path.exists(patchdir):
433 shutil.rmtree(patchdir)
434 if haspatches:
435 stdout, _ = bb.process.run('git status --porcelain patches', cwd=srcsubdir)
436 if stdout:
437 bb.process.run('git checkout patches', cwd=srcsubdir)
438
439 stdout, _ = bb.process.run('git status --porcelain', cwd=srcsubdir)
440 if stdout:
441 bb.process.run('git add .; git commit -a -m "Committing changes from %s\n\n%s"' % (func, GitApplyTree.ignore_commit_prefix + ' - from %s' % func), cwd=srcsubdir)
442
443
444def _prep_extract_operation(config, basepath, recipename, tinfoil=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500445 """HACK: Ugly workaround for making sure that requirements are met when
446 trying to extract a package. Returns the tinfoil instance to be used."""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500447 if not tinfoil:
448 tinfoil = setup_tinfoil(basepath=basepath)
449
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500450 rd = parse_recipe(config, tinfoil, recipename, True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500451 if not rd:
452 return None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500453
454 if bb.data.inherits_class('kernel-yocto', rd):
455 tinfoil.shutdown()
456 try:
457 stdout, _ = exec_build_env_command(config.init_path, basepath,
458 'bitbake kern-tools-native')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500459 tinfoil = setup_tinfoil(basepath=basepath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500460 except bb.process.ExecutionError as err:
461 raise DevtoolError("Failed to build kern-tools-native:\n%s" %
462 err.stdout)
463 return tinfoil
464
465
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500466def _extract_source(srctree, keep_temp, devbranch, sync, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500467 """Extract sources of a recipe"""
468 import bb.event
469 import oe.recipeutils
470
471 def eventfilter(name, handler, event, d):
472 """Bitbake event filter for devtool extract operation"""
473 if name == 'base_eventhandler':
474 return True
475 else:
476 return False
477
478 if hasattr(bb.event, 'set_eventfilter'):
479 bb.event.set_eventfilter(eventfilter)
480
481 pn = d.getVar('PN', True)
482
483 _check_compatible_recipe(pn, d)
484
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500485 if sync:
486 if not os.path.exists(srctree):
487 raise DevtoolError("output path %s does not exist" % srctree)
488 else:
489 if os.path.exists(srctree):
490 if not os.path.isdir(srctree):
491 raise DevtoolError("output path %s exists and is not a directory" %
492 srctree)
493 elif os.listdir(srctree):
494 raise DevtoolError("output path %s already exists and is "
495 "non-empty" % srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500496
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500497 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
498 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
499 "extract source" % pn)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500500
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500501 if not sync:
502 # Prepare for shutil.move later on
503 bb.utils.mkdirhier(srctree)
504 os.rmdir(srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500505
506 # We don't want notes to be printed, they are too verbose
507 origlevel = bb.logger.getEffectiveLevel()
508 if logger.getEffectiveLevel() > logging.DEBUG:
509 bb.logger.setLevel(logging.WARNING)
510
511 initial_rev = None
512 tempdir = tempfile.mkdtemp(prefix='devtool')
513 try:
514 crd = d.createCopy()
515 # Make a subdir so we guard against WORKDIR==S
516 workdir = os.path.join(tempdir, 'workdir')
517 crd.setVar('WORKDIR', workdir)
518 crd.setVar('T', os.path.join(tempdir, 'temp'))
519 if not crd.getVar('S', True).startswith(workdir):
520 # Usually a shared workdir recipe (kernel, gcc)
521 # Try to set a reasonable default
522 if bb.data.inherits_class('kernel', d):
523 crd.setVar('S', '${WORKDIR}/source')
524 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500525 crd.setVar('S', '${WORKDIR}/%s' % os.path.basename(d.getVar('S', True)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500526 if bb.data.inherits_class('kernel', d):
527 # We don't want to move the source to STAGING_KERNEL_DIR here
528 crd.setVar('STAGING_KERNEL_DIR', '${S}')
529
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500530 task_executor = PatchTaskExecutor(crd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500531
532 crd.setVar('EXTERNALSRC_forcevariable', '')
533
534 logger.info('Fetching %s...' % pn)
535 task_executor.exec_func('do_fetch', False)
536 logger.info('Unpacking...')
537 task_executor.exec_func('do_unpack', False)
538 if bb.data.inherits_class('kernel-yocto', d):
539 # Extra step for kernel to populate the source directory
540 logger.info('Doing kernel checkout...')
541 task_executor.exec_func('do_kernel_checkout', False)
542 srcsubdir = crd.getVar('S', True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500543
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500544 task_executor.check_git = True
545
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500546 # Move local source files into separate subdir
547 recipe_patches = [os.path.basename(patch) for patch in
548 oe.recipeutils.get_recipe_patches(crd)]
549 local_files = oe.recipeutils.get_recipe_local_files(crd)
550 local_files = [fname for fname in local_files if
551 os.path.exists(os.path.join(workdir, fname))]
552 if local_files:
553 for fname in local_files:
554 _move_file(os.path.join(workdir, fname),
555 os.path.join(tempdir, 'oe-local-files', fname))
556 with open(os.path.join(tempdir, 'oe-local-files', '.gitignore'),
557 'w') as f:
558 f.write('# Ignore local files, by default. Remove this file '
559 'if you want to commit the directory to Git\n*\n')
560
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500561 if srcsubdir == workdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500562 # Find non-patch non-local sources that were "unpacked" to srctree
563 # directory
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500564 src_files = [fname for fname in _ls_tree(workdir) if
565 os.path.basename(fname) not in recipe_patches]
566 # Force separate S so that patch files can be left out from srctree
567 srcsubdir = tempfile.mkdtemp(dir=workdir)
568 crd.setVar('S', srcsubdir)
569 # Move source files to S
570 for path in src_files:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500571 _move_file(os.path.join(workdir, path),
572 os.path.join(srcsubdir, path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500573 elif os.path.dirname(srcsubdir) != workdir:
574 # Handle if S is set to a subdirectory of the source
575 srcsubdir = os.path.join(workdir, os.path.relpath(srcsubdir, workdir).split(os.sep)[0])
576
577 scriptutils.git_convert_standalone_clone(srcsubdir)
578
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500579 # Make sure that srcsubdir exists
580 bb.utils.mkdirhier(srcsubdir)
581 if not os.path.exists(srcsubdir) or not os.listdir(srcsubdir):
582 logger.warning("no source unpacked to S, either the %s recipe "
583 "doesn't use any source or the correct source "
584 "directory could not be determined" % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500585
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500586 setup_git_repo(srcsubdir, crd.getVar('PV', True), devbranch)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500587
588 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srcsubdir)
589 initial_rev = stdout.rstrip()
590
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591 crd.setVar('PATCHTOOL', 'git')
592
593 logger.info('Patching...')
594 task_executor.exec_func('do_patch', False)
595
596 bb.process.run('git tag -f devtool-patched', cwd=srcsubdir)
597
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500598 kconfig = None
599 if bb.data.inherits_class('kernel-yocto', d):
600 # Store generate and store kernel config
601 logger.info('Generating kernel config')
602 task_executor.exec_func('do_configure', False)
603 kconfig = os.path.join(crd.getVar('B', True), '.config')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500604
605
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500606 tempdir_localdir = os.path.join(tempdir, 'oe-local-files')
607 srctree_localdir = os.path.join(srctree, 'oe-local-files')
608
609 if sync:
610 bb.process.run('git fetch file://' + srcsubdir + ' ' + devbranch + ':' + devbranch, cwd=srctree)
611
612 # Move oe-local-files directory to srctree
613 # As the oe-local-files is not part of the constructed git tree,
614 # remove them directly during the synchrounizating might surprise
615 # the users. Instead, we move it to oe-local-files.bak and remind
616 # user in the log message.
617 if os.path.exists(srctree_localdir + '.bak'):
618 shutil.rmtree(srctree_localdir, srctree_localdir + '.bak')
619
620 if os.path.exists(srctree_localdir):
621 logger.info('Backing up current local file directory %s' % srctree_localdir)
622 shutil.move(srctree_localdir, srctree_localdir + '.bak')
623
624 if os.path.exists(tempdir_localdir):
625 logger.info('Syncing local source files to srctree...')
626 shutil.copytree(tempdir_localdir, srctree_localdir)
627 else:
628 # Move oe-local-files directory to srctree
629 if os.path.exists(tempdir_localdir):
630 logger.info('Adding local source files to srctree...')
631 shutil.move(tempdir_localdir, srcsubdir)
632
633 shutil.move(srcsubdir, srctree)
634
635 if kconfig:
636 logger.info('Copying kernel config to srctree')
637 shutil.copy2(kconfig, srctree)
638
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500639 finally:
640 bb.logger.setLevel(origlevel)
641
642 if keep_temp:
643 logger.info('Preserving temporary directory %s' % tempdir)
644 else:
645 shutil.rmtree(tempdir)
646 return initial_rev
647
648def _add_md5(config, recipename, filename):
649 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
650 import bb.utils
651
652 def addfile(fn):
653 md5 = bb.utils.md5_file(fn)
654 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a') as f:
655 f.write('%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5))
656
657 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500658 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500659 for f in files:
660 addfile(os.path.join(root, f))
661 else:
662 addfile(filename)
663
664def _check_preserve(config, recipename):
665 """Check if a file was manually changed and needs to be saved in 'attic'
666 directory"""
667 import bb.utils
668 origfile = os.path.join(config.workspace_path, '.devtool_md5')
669 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500670 preservepath = os.path.join(config.workspace_path, 'attic', recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500671 with open(origfile, 'r') as f:
672 with open(newfile, 'w') as tf:
673 for line in f.readlines():
674 splitline = line.rstrip().split('|')
675 if splitline[0] == recipename:
676 removefile = os.path.join(config.workspace_path, splitline[1])
677 try:
678 md5 = bb.utils.md5_file(removefile)
679 except IOError as err:
680 if err.errno == 2:
681 # File no longer exists, skip it
682 continue
683 else:
684 raise
685 if splitline[2] != md5:
686 bb.utils.mkdirhier(preservepath)
687 preservefile = os.path.basename(removefile)
688 logger.warn('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
689 shutil.move(removefile, os.path.join(preservepath, preservefile))
690 else:
691 os.remove(removefile)
692 else:
693 tf.write(line)
694 os.rename(newfile, origfile)
695
696def modify(args, config, basepath, workspace):
697 """Entry point for the devtool 'modify' subcommand"""
698 import bb
699 import oe.recipeutils
700
701 if args.recipename in workspace:
702 raise DevtoolError("recipe %s is already in your workspace" %
703 args.recipename)
704
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500705 tinfoil = setup_tinfoil(basepath=basepath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500706 rd = parse_recipe(config, tinfoil, args.recipename, True)
707 if not rd:
708 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500709
710 pn = rd.getVar('PN', True)
711 if pn != args.recipename:
712 logger.info('Mapping %s to %s' % (args.recipename, pn))
713 if pn in workspace:
714 raise DevtoolError("recipe %s is already in your workspace" %
715 pn)
716
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500717 if args.srctree:
718 srctree = os.path.abspath(args.srctree)
719 else:
720 srctree = get_default_srctree(config, pn)
721
722 if args.no_extract and not os.path.isdir(srctree):
723 raise DevtoolError("--no-extract specified and source path %s does "
724 "not exist or is not a directory" %
725 srctree)
726 if not args.no_extract:
727 tinfoil = _prep_extract_operation(config, basepath, pn, tinfoil)
728 if not tinfoil:
729 # Error already shown
730 return 1
731
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500732 recipefile = rd.getVar('FILE', True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500733 appendfile = recipe_to_append(recipefile, config, args.wildcard)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500734 if os.path.exists(appendfile):
735 raise DevtoolError("Another variant of recipe %s is already in your "
736 "workspace (only one variant of a recipe can "
737 "currently be worked on at once)"
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500738 % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500739
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500740 _check_compatible_recipe(pn, rd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500741
742 initial_rev = None
743 commits = []
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500744 if not args.no_extract:
745 initial_rev = _extract_source(srctree, False, args.branch, False, rd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500746 if not initial_rev:
747 return 1
748 logger.info('Source tree extracted to %s' % srctree)
749 # Get list of commits since this revision
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500750 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751 commits = stdout.split()
752 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500753 if os.path.exists(os.path.join(srctree, '.git')):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500754 # Check if it's a tree previously extracted by us
755 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500756 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500757 except bb.process.ExecutionError:
758 stdout = ''
759 for line in stdout.splitlines():
760 if line.startswith('*'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500761 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500762 initial_rev = stdout.rstrip()
763 if not initial_rev:
764 # Otherwise, just grab the head revision
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500765 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500766 initial_rev = stdout.rstrip()
767
768 # Check that recipe isn't using a shared workdir
769 s = os.path.abspath(rd.getVar('S', True))
770 workdir = os.path.abspath(rd.getVar('WORKDIR', True))
771 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
772 # Handle if S is set to a subdirectory of the source
773 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
774 srctree = os.path.join(srctree, srcsubdir)
775
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500776 bb.utils.mkdirhier(os.path.dirname(appendfile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500777 with open(appendfile, 'w') as f:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500778 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
779 # Local files can be modified/tracked in separate subdir under srctree
780 # Mostly useful for packages with S != WORKDIR
781 f.write('FILESPATH_prepend := "%s:"\n' %
782 os.path.join(srctree, 'oe-local-files'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500783
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500784 f.write('\ninherit externalsrc\n')
785 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
786 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
787
788 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500789 if b_is_s:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500790 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500791
792 if bb.data.inherits_class('kernel', rd):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500793 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout '
794 'do_fetch do_unpack do_patch do_kernel_configme do_kernel_configcheck"\n')
795 f.write('\ndo_configure_append() {\n'
796 ' cp ${B}/.config ${S}/.config.baseline\n'
797 ' ln -sfT ${B}/.config ${S}/.config.new\n'
798 '}\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500799 if initial_rev:
800 f.write('\n# initial_rev: %s\n' % initial_rev)
801 for commit in commits:
802 f.write('# commit: %s\n' % commit)
803
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500804 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500805
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500806 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500807
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500808 tinfoil.shutdown()
809
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500810 return 0
811
812def _get_patchset_revs(args, srctree, recipe_path):
813 """Get initial and update rev of a recipe. These are the start point of the
814 whole patchset and start point for the patches to be re-generated/updated.
815 """
816 import bb
817
818 if args.initial_rev:
819 return args.initial_rev, args.initial_rev
820
821 # Parse initial rev from recipe
822 commits = []
823 initial_rev = None
824 with open(recipe_path, 'r') as f:
825 for line in f:
826 if line.startswith('# initial_rev:'):
827 initial_rev = line.split(':')[-1].strip()
828 elif line.startswith('# commit:'):
829 commits.append(line.split(':')[-1].strip())
830
831 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500832 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500833 if initial_rev:
834 # Find first actually changed revision
835 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
836 initial_rev, cwd=srctree)
837 newcommits = stdout.split()
838 for i in xrange(min(len(commits), len(newcommits))):
839 if newcommits[i] == commits[i]:
840 update_rev = commits[i]
841
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500842 try:
843 stdout, _ = bb.process.run('git cherry devtool-patched',
844 cwd=srctree)
845 except bb.process.ExecutionError as err:
846 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500847
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500848 if stdout is not None:
849 changed_revs = []
850 for line in stdout.splitlines():
851 if line.startswith('+ '):
852 rev = line.split()[1]
853 if rev in newcommits:
854 changed_revs.append(rev)
855
856 return initial_rev, update_rev, changed_revs
857
858def _remove_file_entries(srcuri, filelist):
859 """Remove file:// entries from SRC_URI"""
860 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500861 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500862 for fname in filelist:
863 basename = os.path.basename(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500864 for i in xrange(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500865 if (srcuri[i].startswith('file://') and
866 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500867 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500868 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500869 srcuri.pop(i)
870 break
871 return entries, remaining
872
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500873def _remove_source_files(args, files, destpath):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500874 """Unlink existing patch files"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500875 for path in files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500876 if args.append:
877 if not destpath:
878 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500879 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500880
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500881 if os.path.exists(path):
882 logger.info('Removing file %s' % path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500883 # FIXME "git rm" here would be nice if the file in question is
884 # tracked
885 # FIXME there's a chance that this file is referred to by
886 # another recipe, in which case deleting wouldn't be the
887 # right thing to do
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500888 os.remove(path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500889 # Remove directory if empty
890 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500891 os.rmdir(os.path.dirname(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500892 except OSError as ose:
893 if ose.errno != errno.ENOTEMPTY:
894 raise
895
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500896
897def _export_patches(srctree, rd, start_rev, destdir):
898 """Export patches from srctree to given location.
899 Returns three-tuple of dicts:
900 1. updated - patches that already exist in SRCURI
901 2. added - new patches that don't exist in SRCURI
902 3 removed - patches that exist in SRCURI but not in exported patches
903 In each dict the key is the 'basepath' of the URI and value is the
904 absolute path to the existing file in recipe space (if any).
905 """
906 import oe.recipeutils
907 from oe.patch import GitApplyTree
908 updated = OrderedDict()
909 added = OrderedDict()
910 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
911
912 existing_patches = dict((os.path.basename(path), path) for path in
913 oe.recipeutils.get_recipe_patches(rd))
914
915 # Generate patches from Git, exclude local files directory
916 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
917 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
918
919 new_patches = sorted(os.listdir(destdir))
920 for new_patch in new_patches:
921 # Strip numbering from patch names. If it's a git sequence named patch,
922 # the numbers might not match up since we are starting from a different
923 # revision This does assume that people are using unique shortlog
924 # values, but they ought to be anyway...
925 new_basename = seqpatch_re.match(new_patch).group(2)
926 found = False
927 for old_patch in existing_patches:
928 old_basename = seqpatch_re.match(old_patch).group(2)
929 if new_basename == old_basename:
930 updated[new_patch] = existing_patches.pop(old_patch)
931 found = True
932 # Rename patch files
933 if new_patch != old_patch:
934 os.rename(os.path.join(destdir, new_patch),
935 os.path.join(destdir, old_patch))
936 break
937 if not found:
938 added[new_patch] = None
939 return (updated, added, existing_patches)
940
941
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500942def _create_kconfig_diff(srctree, rd, outfile):
943 """Create a kconfig fragment"""
944 # Only update config fragment if both config files exist
945 orig_config = os.path.join(srctree, '.config.baseline')
946 new_config = os.path.join(srctree, '.config.new')
947 if os.path.exists(orig_config) and os.path.exists(new_config):
948 cmd = ['diff', '--new-line-format=%L', '--old-line-format=',
949 '--unchanged-line-format=', orig_config, new_config]
950 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
951 stderr=subprocess.PIPE)
952 stdout, stderr = pipe.communicate()
953 if pipe.returncode == 1:
954 logger.info("Updating config fragment %s" % outfile)
955 with open(outfile, 'w') as fobj:
956 fobj.write(stdout)
957 elif pipe.returncode == 0:
958 logger.info("Would remove config fragment %s" % outfile)
959 if os.path.exists(outfile):
960 # Remove fragment file in case of empty diff
961 logger.info("Removing config fragment %s" % outfile)
962 os.unlink(outfile)
963 else:
964 raise bb.process.ExecutionError(cmd, pipe.returncode, stdout, stderr)
965 return True
966 return False
967
968
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500969def _export_local_files(srctree, rd, destdir):
970 """Copy local files from srctree to given location.
971 Returns three-tuple of dicts:
972 1. updated - files that already exist in SRCURI
973 2. added - new files files that don't exist in SRCURI
974 3 removed - files that exist in SRCURI but not in exported files
975 In each dict the key is the 'basepath' of the URI and value is the
976 absolute path to the existing file in recipe space (if any).
977 """
978 import oe.recipeutils
979
980 # Find out local files (SRC_URI files that exist in the "recipe space").
981 # Local files that reside in srctree are not included in patch generation.
982 # Instead they are directly copied over the original source files (in
983 # recipe space).
984 existing_files = oe.recipeutils.get_recipe_local_files(rd)
985 new_set = None
986 updated = OrderedDict()
987 added = OrderedDict()
988 removed = OrderedDict()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500989 local_files_dir = os.path.join(srctree, 'oe-local-files')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500990 git_files = _git_ls_tree(srctree)
991 if 'oe-local-files' in git_files:
992 # If tracked by Git, take the files from srctree HEAD. First get
993 # the tree object of the directory
994 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
995 tree = git_files['oe-local-files'][2]
996 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
997 env=dict(os.environ, GIT_WORK_TREE=destdir,
998 GIT_INDEX_FILE=tmp_index))
999 new_set = _git_ls_tree(srctree, tree, True).keys()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001000 elif os.path.isdir(local_files_dir):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001001 # If not tracked by Git, just copy from working copy
1002 new_set = _ls_tree(os.path.join(srctree, 'oe-local-files'))
1003 bb.process.run(['cp', '-ax',
1004 os.path.join(srctree, 'oe-local-files', '.'), destdir])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001005 else:
1006 new_set = []
1007
1008 # Special handling for kernel config
1009 if bb.data.inherits_class('kernel-yocto', rd):
1010 fragment_fn = 'devtool-fragment.cfg'
1011 fragment_path = os.path.join(destdir, fragment_fn)
1012 if _create_kconfig_diff(srctree, rd, fragment_path):
1013 if os.path.exists(fragment_path):
1014 if fragment_fn not in new_set:
1015 new_set.append(fragment_fn)
1016 # Copy fragment to local-files
1017 if os.path.isdir(local_files_dir):
1018 shutil.copy2(fragment_path, local_files_dir)
1019 else:
1020 if fragment_fn in new_set:
1021 new_set.remove(fragment_fn)
1022 # Remove fragment from local-files
1023 if os.path.exists(os.path.join(local_files_dir, fragment_fn)):
1024 os.unlink(os.path.join(local_files_dir, fragment_fn))
1025
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001026 if new_set is not None:
1027 for fname in new_set:
1028 if fname in existing_files:
1029 updated[fname] = existing_files.pop(fname)
1030 elif fname != '.gitignore':
1031 added[fname] = None
1032
1033 removed = existing_files
1034 return (updated, added, removed)
1035
1036
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001037def _update_recipe_srcrev(args, srctree, rd, config_data):
1038 """Implement the 'srcrev' mode of update-recipe"""
1039 import bb
1040 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001041
1042 recipefile = rd.getVar('FILE', True)
1043 logger.info('Updating SRCREV in recipe %s' % os.path.basename(recipefile))
1044
1045 # Get HEAD revision
1046 try:
1047 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1048 except bb.process.ExecutionError as err:
1049 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
1050 (srctree, err))
1051 srcrev = stdout.strip()
1052 if len(srcrev) != 40:
1053 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
1054
1055 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001056 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001057 patchfields = {}
1058 patchfields['SRCREV'] = srcrev
1059 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001060 srcuri = orig_src_uri.split()
1061 tempdir = tempfile.mkdtemp(prefix='devtool')
1062 update_srcuri = False
1063 try:
1064 local_files_dir = tempfile.mkdtemp(dir=tempdir)
1065 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir)
1066 if not args.no_remove:
1067 # Find list of existing patches in recipe file
1068 patches_dir = tempfile.mkdtemp(dir=tempdir)
1069 old_srcrev = (rd.getVar('SRCREV', False) or '')
1070 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
1071 patches_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001072
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001073 # Remove deleted local files and "overlapping" patches
1074 remove_files = del_f.values() + upd_p.values()
1075 if remove_files:
1076 removedentries = _remove_file_entries(srcuri, remove_files)[0]
1077 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001078
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001079 if args.append:
1080 files = dict((os.path.join(local_files_dir, key), val) for
1081 key, val in upd_f.items() + new_f.items())
1082 removevalues = {}
1083 if update_srcuri:
1084 removevalues = {'SRC_URI': removedentries}
1085 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
1086 _, destpath = oe.recipeutils.bbappend_recipe(
1087 rd, args.append, files, wildcardver=args.wildcard_version,
1088 extralines=patchfields, removevalues=removevalues)
1089 else:
1090 files_dir = os.path.join(os.path.dirname(recipefile),
1091 rd.getVar('BPN', True))
1092 for basepath, path in upd_f.iteritems():
1093 logger.info('Updating file %s' % basepath)
1094 _move_file(os.path.join(local_files_dir, basepath), path)
1095 update_srcuri= True
1096 for basepath, path in new_f.iteritems():
1097 logger.info('Adding new file %s' % basepath)
1098 _move_file(os.path.join(local_files_dir, basepath),
1099 os.path.join(files_dir, basepath))
1100 srcuri.append('file://%s' % basepath)
1101 update_srcuri = True
1102 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001103 patchfields['SRC_URI'] = ' '.join(srcuri)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001104 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
1105 finally:
1106 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001107 if not 'git://' in orig_src_uri:
1108 logger.info('You will need to update SRC_URI within the recipe to '
1109 'point to a git repository where you have pushed your '
1110 'changes')
1111
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001112 _remove_source_files(args, remove_files, destpath)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001113 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001114
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001115def _update_recipe_patch(args, config, workspace, srctree, rd, config_data):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001116 """Implement the 'patch' mode of update-recipe"""
1117 import bb
1118 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001119
1120 recipefile = rd.getVar('FILE', True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001121 append = workspace[args.recipename]['bbappend']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001122 if not os.path.exists(append):
1123 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
1124 args.recipename)
1125
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001126 initial_rev, update_rev, changed_revs = _get_patchset_revs(args, srctree, append)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001127 if not initial_rev:
1128 raise DevtoolError('Unable to find initial revision - please specify '
1129 'it with --initial-rev')
1130
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001131 tempdir = tempfile.mkdtemp(prefix='devtool')
1132 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001133 local_files_dir = tempfile.mkdtemp(dir=tempdir)
1134 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001135
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001136 remove_files = []
1137 if not args.no_remove:
1138 # Get all patches from source tree and check if any should be removed
1139 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
1140 upd_p, new_p, del_p = _export_patches(srctree, rd, initial_rev,
1141 all_patches_dir)
1142 # Remove deleted local files and patches
1143 remove_files = del_f.values() + del_p.values()
1144
1145 # Get updated patches from source tree
1146 patches_dir = tempfile.mkdtemp(dir=tempdir)
1147 upd_p, new_p, del_p = _export_patches(srctree, rd, update_rev,
1148 patches_dir)
1149 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001150 updaterecipe = False
1151 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001152 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001153 if args.append:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001154 files = dict((os.path.join(local_files_dir, key), val) for
1155 key, val in upd_f.items() + new_f.items())
1156 files.update(dict((os.path.join(patches_dir, key), val) for
1157 key, val in upd_p.items() + new_p.items()))
1158 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001159 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001160 if remove_files:
1161 removedentries, remaining = _remove_file_entries(
1162 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001163 if removedentries or remaining:
1164 remaining = ['file://' + os.path.basename(item) for
1165 item in remaining]
1166 removevalues = {'SRC_URI': removedentries + remaining}
1167 _, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001168 rd, args.append, files,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001169 removevalues=removevalues)
1170 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001171 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001172 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001173 # Update existing files
1174 for basepath, path in upd_f.iteritems():
1175 logger.info('Updating file %s' % basepath)
1176 _move_file(os.path.join(local_files_dir, basepath), path)
1177 updatefiles = True
1178 for basepath, path in upd_p.iteritems():
1179 patchfn = os.path.join(patches_dir, basepath)
1180 if changed_revs is not None:
1181 # Avoid updating patches that have not actually changed
1182 with open(patchfn, 'r') as f:
1183 firstlineitems = f.readline().split()
1184 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
1185 if not firstlineitems[1] in changed_revs:
1186 continue
1187 logger.info('Updating patch %s' % basepath)
1188 _move_file(patchfn, path)
1189 updatefiles = True
1190 # Add any new files
1191 files_dir = os.path.join(os.path.dirname(recipefile),
1192 rd.getVar('BPN', True))
1193 for basepath, path in new_f.iteritems():
1194 logger.info('Adding new file %s' % basepath)
1195 _move_file(os.path.join(local_files_dir, basepath),
1196 os.path.join(files_dir, basepath))
1197 srcuri.append('file://%s' % basepath)
1198 updaterecipe = True
1199 for basepath, path in new_p.iteritems():
1200 logger.info('Adding new patch %s' % basepath)
1201 _move_file(os.path.join(patches_dir, basepath),
1202 os.path.join(files_dir, basepath))
1203 srcuri.append('file://%s' % basepath)
1204 updaterecipe = True
1205 # Update recipe, if needed
1206 if _remove_file_entries(srcuri, remove_files)[0]:
1207 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001208 if updaterecipe:
1209 logger.info('Updating recipe %s' % os.path.basename(recipefile))
1210 oe.recipeutils.patch_recipe(rd, recipefile,
1211 {'SRC_URI': ' '.join(srcuri)})
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001212 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001213 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001214 logger.info('No patches or files need updating')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001215 return False
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001216 finally:
1217 shutil.rmtree(tempdir)
1218
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001219 _remove_source_files(args, remove_files, destpath)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001220 return True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001221
1222def _guess_recipe_update_mode(srctree, rdata):
1223 """Guess the recipe update mode to use"""
1224 src_uri = (rdata.getVar('SRC_URI', False) or '').split()
1225 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
1226 if not git_uris:
1227 return 'patch'
1228 # Just use the first URI for now
1229 uri = git_uris[0]
1230 # Check remote branch
1231 params = bb.fetch.decodeurl(uri)[5]
1232 upstr_branch = params['branch'] if 'branch' in params else 'master'
1233 # Check if current branch HEAD is found in upstream branch
1234 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
1235 head_rev = stdout.rstrip()
1236 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
1237 cwd=srctree)
1238 remote_brs = [branch.strip() for branch in stdout.splitlines()]
1239 if 'origin/' + upstr_branch in remote_brs:
1240 return 'srcrev'
1241
1242 return 'patch'
1243
1244def update_recipe(args, config, basepath, workspace):
1245 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001246 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001247
1248 if args.append:
1249 if not os.path.exists(args.append):
1250 raise DevtoolError('bbappend destination layer directory "%s" '
1251 'does not exist' % args.append)
1252 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
1253 raise DevtoolError('conf/layer.conf not found in bbappend '
1254 'destination layer "%s"' % args.append)
1255
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001256 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001257
1258 rd = parse_recipe(config, tinfoil, args.recipename, True)
1259 if not rd:
1260 return 1
1261
1262 srctree = workspace[args.recipename]['srctree']
1263 if args.mode == 'auto':
1264 mode = _guess_recipe_update_mode(srctree, rd)
1265 else:
1266 mode = args.mode
1267
1268 if mode == 'srcrev':
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001269 updated = _update_recipe_srcrev(args, srctree, rd, tinfoil.config_data)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001270 elif mode == 'patch':
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001271 updated = _update_recipe_patch(args, config, workspace, srctree, rd, tinfoil.config_data)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001272 else:
1273 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1274
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001275 if updated:
1276 rf = rd.getVar('FILE', True)
1277 if rf.startswith(config.workspace_path):
1278 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)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001279
1280 return 0
1281
1282
1283def status(args, config, basepath, workspace):
1284 """Entry point for the devtool 'status' subcommand"""
1285 if workspace:
1286 for recipe, value in workspace.iteritems():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001287 recipefile = value['recipefile']
1288 if recipefile:
1289 recipestr = ' (%s)' % recipefile
1290 else:
1291 recipestr = ''
1292 print("%s: %s%s" % (recipe, value['srctree'], recipestr))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001293 else:
1294 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')
1295 return 0
1296
1297
1298def reset(args, config, basepath, workspace):
1299 """Entry point for the devtool 'reset' subcommand"""
1300 import bb
1301 if args.recipename:
1302 if args.all:
1303 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001304 else:
1305 check_workspace_recipe(workspace, args.recipename, checksrc=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001306 elif not args.all:
1307 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1308 "reset all recipes")
1309 if args.all:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001310 recipes = workspace.keys()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001311 else:
1312 recipes = [args.recipename]
1313
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001314 if recipes and not args.no_clean:
1315 if len(recipes) == 1:
1316 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1317 else:
1318 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001319 # If the recipe file itself was created in the workspace, and
1320 # it uses BBCLASSEXTEND, then we need to also clean the other
1321 # variants
1322 targets = []
1323 for recipe in recipes:
1324 targets.append(recipe)
1325 recipefile = workspace[recipe]['recipefile']
1326 if recipefile:
1327 targets.extend(get_bbclassextend_targets(recipefile, recipe))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001328 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001329 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(targets))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001330 except bb.process.ExecutionError as e:
1331 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1332 'wish, you may specify -n/--no-clean to '
1333 'skip running this command when resetting' %
1334 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001335
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001336 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001337 _check_preserve(config, pn)
1338
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001339 preservepath = os.path.join(config.workspace_path, 'attic', pn, pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001340 def preservedir(origdir):
1341 if os.path.exists(origdir):
1342 for root, dirs, files in os.walk(origdir):
1343 for fn in files:
1344 logger.warn('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001345 _move_file(os.path.join(origdir, fn),
1346 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001347 for dn in dirs:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001348 preservedir(os.path.join(root, dn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001349 os.rmdir(origdir)
1350
1351 preservedir(os.path.join(config.workspace_path, 'recipes', pn))
1352 # We don't automatically create this dir next to appends, but the user can
1353 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1354
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001355 srctree = workspace[pn]['srctree']
1356 if os.path.isdir(srctree):
1357 if os.listdir(srctree):
1358 # We don't want to risk wiping out any work in progress
1359 logger.info('Leaving source tree %s as-is; if you no '
1360 'longer need it then please delete it manually'
1361 % srctree)
1362 else:
1363 # This is unlikely, but if it's empty we can just remove it
1364 os.rmdir(srctree)
1365
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001366 return 0
1367
1368
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001369def get_default_srctree(config, recipename=''):
1370 """Get the default srctree path"""
1371 srctreeparent = config.get('General', 'default_source_parent_dir', config.workspace_path)
1372 if recipename:
1373 return os.path.join(srctreeparent, 'sources', recipename)
1374 else:
1375 return os.path.join(srctreeparent, 'sources')
1376
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001377def register_commands(subparsers, context):
1378 """Register devtool subcommands from this plugin"""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001379
1380 defsrctree = get_default_srctree(context.config)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001381 parser_add = subparsers.add_parser('add', help='Add a new recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001382 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.',
1383 group='starting', order=100)
1384 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.')
1385 parser_add.add_argument('srctree', nargs='?', help='Path to external source tree. If not specified, a subdirectory of %s will be used.' % defsrctree)
1386 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 -05001387 group = parser_add.add_mutually_exclusive_group()
1388 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
1389 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001390 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')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001391 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001392 parser_add.add_argument('--no-git', '-g', help='If fetching source, do not set up source tree as a git repository', action="store_true")
1393 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')
1394 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')
1395 parser_add.add_argument('--src-subdir', help='Specify subdirectory within source tree to use', metavar='SUBDIR')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001396 parser_add.set_defaults(func=add)
1397
1398 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001399 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.',
1400 group='starting', order=90)
1401 parser_modify.add_argument('recipename', help='Name of existing recipe to edit (just name - no version, path or extension)')
1402 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 -05001403 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001404 group = parser_modify.add_mutually_exclusive_group()
1405 group.add_argument('--extract', '-x', action="store_true", help='Extract source for recipe (default)')
1406 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 -05001407 group = parser_modify.add_mutually_exclusive_group()
1408 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
1409 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001410 parser_modify.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (when not using -n/--no-extract) (default "%(default)s")')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001411 parser_modify.set_defaults(func=modify)
1412
1413 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
1414 description='Extracts the source for an existing recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001415 group='advanced')
1416 parser_extract.add_argument('recipename', help='Name of recipe to extract the source for')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001417 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001418 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 -05001419 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001420 parser_extract.set_defaults(func=extract, no_workspace=True)
1421
1422 parser_sync = subparsers.add_parser('sync', help='Synchronize the source tree for an existing recipe',
1423 description='Synchronize the previously extracted source tree for an existing recipe',
1424 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
1425 group='advanced')
1426 parser_sync.add_argument('recipename', help='Name of recipe to sync the source for')
1427 parser_sync.add_argument('srctree', help='Path to the source tree')
1428 parser_sync.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
1429 parser_sync.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
1430 parser_sync.set_defaults(func=sync)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001431
1432 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001433 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.',
1434 group='working', order=-90)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001435 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
1436 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 -05001437 parser_update_recipe.add_argument('--initial-rev', help='Override starting revision for patches')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001438 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
1439 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')
1440 parser_update_recipe.add_argument('--no-remove', '-n', action="store_true", help='Don\'t remove patches, only add or update')
1441 parser_update_recipe.set_defaults(func=update_recipe)
1442
1443 parser_status = subparsers.add_parser('status', help='Show workspace status',
1444 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001445 group='info', order=100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001446 parser_status.set_defaults(func=status)
1447
1448 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
1449 description='Removes the specified recipe from your workspace (resetting its state)',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001450 group='working', order=-100)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001451 parser_reset.add_argument('recipename', nargs='?', help='Recipe to reset')
1452 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
1453 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
1454 parser_reset.set_defaults(func=reset)