blob: 5464d7b1f29c7b6d6f15174b2ad17dd9470e1130 [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
23import tempfile
24import logging
25import argparse
26import scriptutils
27import errno
Patrick Williamsf1e5d692016-03-30 15:21:19 -050028from collections import OrderedDict
29from devtool import exec_build_env_command, setup_tinfoil, check_workspace_recipe, use_external_build, setup_git_repo, DevtoolError
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030from devtool import parse_recipe
31
32logger = logging.getLogger('devtool')
33
34
35def add(args, config, basepath, workspace):
36 """Entry point for the devtool 'add' subcommand"""
37 import bb
38 import oe.recipeutils
39
40 if args.recipename in workspace:
41 raise DevtoolError("recipe %s is already in your workspace" %
42 args.recipename)
43
44 reason = oe.recipeutils.validate_pn(args.recipename)
45 if reason:
46 raise DevtoolError(reason)
47
Patrick Williamsf1e5d692016-03-30 15:21:19 -050048 # FIXME this ought to be in validate_pn but we're using that in other contexts
49 if '/' in args.recipename:
50 raise DevtoolError('"/" is not a valid character in recipe names')
51
Patrick Williamsc124f4f2015-09-15 14:41:29 -050052 srctree = os.path.abspath(args.srctree)
53 if os.path.exists(srctree):
54 if args.fetch:
55 if not os.path.isdir(srctree):
56 raise DevtoolError("Cannot fetch into source tree path %s as "
57 "it exists and is not a directory" %
58 srctree)
59 elif os.listdir(srctree):
60 raise DevtoolError("Cannot fetch into source tree path %s as "
61 "it already exists and is non-empty" %
62 srctree)
63 elif not args.fetch:
64 raise DevtoolError("Specified source tree %s could not be found" %
65 srctree)
66
67 appendpath = os.path.join(config.workspace_path, 'appends')
68 if not os.path.exists(appendpath):
69 os.makedirs(appendpath)
70
71 recipedir = os.path.join(config.workspace_path, 'recipes', args.recipename)
72 bb.utils.mkdirhier(recipedir)
73 rfv = None
74 if args.version:
75 if '_' in args.version or ' ' in args.version:
76 raise DevtoolError('Invalid version string "%s"' % args.version)
77 rfv = args.version
78 if args.fetch:
79 if args.fetch.startswith('git://'):
80 rfv = 'git'
81 elif args.fetch.startswith('svn://'):
82 rfv = 'svn'
83 elif args.fetch.startswith('hg://'):
84 rfv = 'hg'
85 if rfv:
86 bp = "%s_%s" % (args.recipename, rfv)
87 else:
88 bp = args.recipename
89 recipefile = os.path.join(recipedir, "%s.bb" % bp)
Patrick Williamsf1e5d692016-03-30 15:21:19 -050090 if args.color == 'auto' and sys.stdout.isatty():
Patrick Williamsc124f4f2015-09-15 14:41:29 -050091 color = 'always'
92 else:
93 color = args.color
94 extracmdopts = ''
95 if args.fetch:
96 source = args.fetch
97 extracmdopts = '-x %s' % srctree
98 else:
99 source = srctree
100 if args.version:
101 extracmdopts += ' -V %s' % args.version
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500102 if args.binary:
103 extracmdopts += ' -b'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500104 try:
105 stdout, _ = exec_build_env_command(config.init_path, basepath, 'recipetool --color=%s create -o %s "%s" %s' % (color, recipefile, source, extracmdopts))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500106 except bb.process.ExecutionError as e:
107 raise DevtoolError('Command \'%s\' failed:\n%s' % (e.command, e.stdout))
108
109 _add_md5(config, args.recipename, recipefile)
110
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500111 if args.fetch and not args.no_git:
112 setup_git_repo(srctree, args.version, 'devtool')
113
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500114 initial_rev = None
115 if os.path.exists(os.path.join(srctree, '.git')):
116 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
117 initial_rev = stdout.rstrip()
118
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500119 tinfoil = setup_tinfoil(config_only=True, basepath=basepath)
120 rd = oe.recipeutils.parse_recipe(recipefile, None, tinfoil.config_data)
121 if not rd:
122 return 1
123
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500124 appendfile = os.path.join(appendpath, '%s.bbappend' % bp)
125 with open(appendfile, 'w') as f:
126 f.write('inherit externalsrc\n')
127 f.write('EXTERNALSRC = "%s"\n' % srctree)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500128
129 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
130 if b_is_s:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500131 f.write('EXTERNALSRC_BUILD = "%s"\n' % srctree)
132 if initial_rev:
133 f.write('\n# initial_rev: %s\n' % initial_rev)
134
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500135 if args.binary:
136 f.write('do_install_append() {\n')
137 f.write(' rm -rf ${D}/.git\n')
138 f.write(' rm -f ${D}/singletask.lock\n')
139 f.write('}\n')
140
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500141 _add_md5(config, args.recipename, appendfile)
142
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500143 logger.info('Recipe %s has been automatically created; further editing may be required to make it fully functional' % recipefile)
144
145 tinfoil.shutdown()
146
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500147 return 0
148
149
150def _check_compatible_recipe(pn, d):
151 """Check if the recipe is supported by devtool"""
152 if pn == 'perf':
153 raise DevtoolError("The perf recipe does not actually check out "
154 "source and thus cannot be supported by this tool")
155
156 if pn in ['kernel-devsrc', 'package-index'] or pn.startswith('gcc-source'):
157 raise DevtoolError("The %s recipe is not supported by this tool" % pn)
158
159 if bb.data.inherits_class('image', d):
160 raise DevtoolError("The %s recipe is an image, and therefore is not "
161 "supported by this tool" % pn)
162
163 if bb.data.inherits_class('populate_sdk', d):
164 raise DevtoolError("The %s recipe is an SDK, and therefore is not "
165 "supported by this tool" % pn)
166
167 if bb.data.inherits_class('packagegroup', d):
168 raise DevtoolError("The %s recipe is a packagegroup, and therefore is "
169 "not supported by this tool" % pn)
170
171 if bb.data.inherits_class('meta', d):
172 raise DevtoolError("The %s recipe is a meta-recipe, and therefore is "
173 "not supported by this tool" % pn)
174
175 if bb.data.inherits_class('externalsrc', d) and d.getVar('EXTERNALSRC', True):
176 raise DevtoolError("externalsrc is currently enabled for the %s "
177 "recipe. This prevents the normal do_patch task "
178 "from working. You will need to disable this "
179 "first." % pn)
180
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500181def _move_file(src, dst):
182 """Move a file. Creates all the directory components of destination path."""
183 dst_d = os.path.dirname(dst)
184 if dst_d:
185 bb.utils.mkdirhier(dst_d)
186 shutil.move(src, dst)
187
188def _git_ls_tree(repodir, treeish='HEAD', recursive=False):
189 """List contents of a git treeish"""
190 import bb
191 cmd = ['git', 'ls-tree', '-z', treeish]
192 if recursive:
193 cmd.append('-r')
194 out, _ = bb.process.run(cmd, cwd=repodir)
195 ret = {}
196 for line in out.split('\0'):
197 if line:
198 split = line.split(None, 4)
199 ret[split[3]] = split[0:3]
200 return ret
201
202def _git_exclude_path(srctree, path):
203 """Return pathspec (list of paths) that excludes certain path"""
204 # NOTE: "Filtering out" files/paths in this way is not entirely reliable -
205 # we don't catch files that are deleted, for example. A more reliable way
206 # to implement this would be to use "negative pathspecs" which were
207 # introduced in Git v1.9.0. Revisit this when/if the required Git version
208 # becomes greater than that.
209 path = os.path.normpath(path)
210 recurse = True if len(path.split(os.path.sep)) > 1 else False
211 git_files = _git_ls_tree(srctree, 'HEAD', recurse).keys()
212 if path in git_files:
213 git_files.remove(path)
214 return git_files
215 else:
216 return ['.']
217
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500218def _ls_tree(directory):
219 """Recursive listing of files in a directory"""
220 ret = []
221 for root, dirs, files in os.walk(directory):
222 ret.extend([os.path.relpath(os.path.join(root, fname), directory) for
223 fname in files])
224 return ret
225
226
227def extract(args, config, basepath, workspace):
228 """Entry point for the devtool 'extract' subcommand"""
229 import bb
230
231 tinfoil = _prep_extract_operation(config, basepath, args.recipename)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500232 if not tinfoil:
233 # Error already shown
234 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500235
236 rd = parse_recipe(config, tinfoil, args.recipename, True)
237 if not rd:
238 return 1
239
240 srctree = os.path.abspath(args.srctree)
241 initial_rev = _extract_source(srctree, args.keep_temp, args.branch, rd)
242 logger.info('Source tree extracted to %s' % srctree)
243
244 if initial_rev:
245 return 0
246 else:
247 return 1
248
249class BbTaskExecutor(object):
250 """Class for executing bitbake tasks for a recipe
251
252 FIXME: This is very awkward. Unfortunately it's not currently easy to
253 properly execute tasks outside of bitbake itself, until then this has to
254 suffice if we are to handle e.g. linux-yocto's extra tasks
255 """
256
257 def __init__(self, rdata):
258 self.rdata = rdata
259 self.executed = []
260
261 def exec_func(self, func, report):
262 """Run bitbake task function"""
263 if not func in self.executed:
264 deps = self.rdata.getVarFlag(func, 'deps')
265 if deps:
266 for taskdepfunc in deps:
267 self.exec_func(taskdepfunc, True)
268 if report:
269 logger.info('Executing %s...' % func)
270 fn = self.rdata.getVar('FILE', True)
271 localdata = bb.build._task_data(fn, func, self.rdata)
272 bb.build.exec_func(func, localdata)
273 self.executed.append(func)
274
275
276def _prep_extract_operation(config, basepath, recipename):
277 """HACK: Ugly workaround for making sure that requirements are met when
278 trying to extract a package. Returns the tinfoil instance to be used."""
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500279 tinfoil = setup_tinfoil(basepath=basepath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500280 rd = parse_recipe(config, tinfoil, recipename, True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500281 if not rd:
282 return None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500283
284 if bb.data.inherits_class('kernel-yocto', rd):
285 tinfoil.shutdown()
286 try:
287 stdout, _ = exec_build_env_command(config.init_path, basepath,
288 'bitbake kern-tools-native')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500289 tinfoil = setup_tinfoil(basepath=basepath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500290 except bb.process.ExecutionError as err:
291 raise DevtoolError("Failed to build kern-tools-native:\n%s" %
292 err.stdout)
293 return tinfoil
294
295
296def _extract_source(srctree, keep_temp, devbranch, d):
297 """Extract sources of a recipe"""
298 import bb.event
299 import oe.recipeutils
300
301 def eventfilter(name, handler, event, d):
302 """Bitbake event filter for devtool extract operation"""
303 if name == 'base_eventhandler':
304 return True
305 else:
306 return False
307
308 if hasattr(bb.event, 'set_eventfilter'):
309 bb.event.set_eventfilter(eventfilter)
310
311 pn = d.getVar('PN', True)
312
313 _check_compatible_recipe(pn, d)
314
315 if os.path.exists(srctree):
316 if not os.path.isdir(srctree):
317 raise DevtoolError("output path %s exists and is not a directory" %
318 srctree)
319 elif os.listdir(srctree):
320 raise DevtoolError("output path %s already exists and is "
321 "non-empty" % srctree)
322
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500323 if 'noexec' in (d.getVarFlags('do_unpack', False) or []):
324 raise DevtoolError("The %s recipe has do_unpack disabled, unable to "
325 "extract source" % pn)
326
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500327 # Prepare for shutil.move later on
328 bb.utils.mkdirhier(srctree)
329 os.rmdir(srctree)
330
331 # We don't want notes to be printed, they are too verbose
332 origlevel = bb.logger.getEffectiveLevel()
333 if logger.getEffectiveLevel() > logging.DEBUG:
334 bb.logger.setLevel(logging.WARNING)
335
336 initial_rev = None
337 tempdir = tempfile.mkdtemp(prefix='devtool')
338 try:
339 crd = d.createCopy()
340 # Make a subdir so we guard against WORKDIR==S
341 workdir = os.path.join(tempdir, 'workdir')
342 crd.setVar('WORKDIR', workdir)
343 crd.setVar('T', os.path.join(tempdir, 'temp'))
344 if not crd.getVar('S', True).startswith(workdir):
345 # Usually a shared workdir recipe (kernel, gcc)
346 # Try to set a reasonable default
347 if bb.data.inherits_class('kernel', d):
348 crd.setVar('S', '${WORKDIR}/source')
349 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500350 crd.setVar('S', '${WORKDIR}/%s' % os.path.basename(d.getVar('S', True)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500351 if bb.data.inherits_class('kernel', d):
352 # We don't want to move the source to STAGING_KERNEL_DIR here
353 crd.setVar('STAGING_KERNEL_DIR', '${S}')
354
355 task_executor = BbTaskExecutor(crd)
356
357 crd.setVar('EXTERNALSRC_forcevariable', '')
358
359 logger.info('Fetching %s...' % pn)
360 task_executor.exec_func('do_fetch', False)
361 logger.info('Unpacking...')
362 task_executor.exec_func('do_unpack', False)
363 if bb.data.inherits_class('kernel-yocto', d):
364 # Extra step for kernel to populate the source directory
365 logger.info('Doing kernel checkout...')
366 task_executor.exec_func('do_kernel_checkout', False)
367 srcsubdir = crd.getVar('S', True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500368
369 # Move local source files into separate subdir
370 recipe_patches = [os.path.basename(patch) for patch in
371 oe.recipeutils.get_recipe_patches(crd)]
372 local_files = oe.recipeutils.get_recipe_local_files(crd)
373 local_files = [fname for fname in local_files if
374 os.path.exists(os.path.join(workdir, fname))]
375 if local_files:
376 for fname in local_files:
377 _move_file(os.path.join(workdir, fname),
378 os.path.join(tempdir, 'oe-local-files', fname))
379 with open(os.path.join(tempdir, 'oe-local-files', '.gitignore'),
380 'w') as f:
381 f.write('# Ignore local files, by default. Remove this file '
382 'if you want to commit the directory to Git\n*\n')
383
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500384 if srcsubdir == workdir:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500385 # Find non-patch non-local sources that were "unpacked" to srctree
386 # directory
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500387 src_files = [fname for fname in _ls_tree(workdir) if
388 os.path.basename(fname) not in recipe_patches]
389 # Force separate S so that patch files can be left out from srctree
390 srcsubdir = tempfile.mkdtemp(dir=workdir)
391 crd.setVar('S', srcsubdir)
392 # Move source files to S
393 for path in src_files:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500394 _move_file(os.path.join(workdir, path),
395 os.path.join(srcsubdir, path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500396 elif os.path.dirname(srcsubdir) != workdir:
397 # Handle if S is set to a subdirectory of the source
398 srcsubdir = os.path.join(workdir, os.path.relpath(srcsubdir, workdir).split(os.sep)[0])
399
400 scriptutils.git_convert_standalone_clone(srcsubdir)
401
402 patchdir = os.path.join(srcsubdir, 'patches')
403 haspatches = False
404 if os.path.exists(patchdir):
405 if os.listdir(patchdir):
406 haspatches = True
407 else:
408 os.rmdir(patchdir)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500409 # Make sure that srcsubdir exists
410 bb.utils.mkdirhier(srcsubdir)
411 if not os.path.exists(srcsubdir) or not os.listdir(srcsubdir):
412 logger.warning("no source unpacked to S, either the %s recipe "
413 "doesn't use any source or the correct source "
414 "directory could not be determined" % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500415
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500416 setup_git_repo(srcsubdir, crd.getVar('PV', True), devbranch)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500417
418 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srcsubdir)
419 initial_rev = stdout.rstrip()
420
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500421 crd.setVar('PATCHTOOL', 'git')
422
423 logger.info('Patching...')
424 task_executor.exec_func('do_patch', False)
425
426 bb.process.run('git tag -f devtool-patched', cwd=srcsubdir)
427
428 if os.path.exists(patchdir):
429 shutil.rmtree(patchdir)
430 if haspatches:
431 bb.process.run('git checkout patches', cwd=srcsubdir)
432
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500433 # Move oe-local-files directory to srctree
434 if os.path.exists(os.path.join(tempdir, 'oe-local-files')):
435 logger.info('Adding local source files to srctree...')
436 shutil.move(os.path.join(tempdir, 'oe-local-files'), srcsubdir)
437
438
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500439 shutil.move(srcsubdir, srctree)
440 finally:
441 bb.logger.setLevel(origlevel)
442
443 if keep_temp:
444 logger.info('Preserving temporary directory %s' % tempdir)
445 else:
446 shutil.rmtree(tempdir)
447 return initial_rev
448
449def _add_md5(config, recipename, filename):
450 """Record checksum of a file (or recursively for a directory) to the md5-file of the workspace"""
451 import bb.utils
452
453 def addfile(fn):
454 md5 = bb.utils.md5_file(fn)
455 with open(os.path.join(config.workspace_path, '.devtool_md5'), 'a') as f:
456 f.write('%s|%s|%s\n' % (recipename, os.path.relpath(fn, config.workspace_path), md5))
457
458 if os.path.isdir(filename):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500459 for root, _, files in os.walk(filename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500460 for f in files:
461 addfile(os.path.join(root, f))
462 else:
463 addfile(filename)
464
465def _check_preserve(config, recipename):
466 """Check if a file was manually changed and needs to be saved in 'attic'
467 directory"""
468 import bb.utils
469 origfile = os.path.join(config.workspace_path, '.devtool_md5')
470 newfile = os.path.join(config.workspace_path, '.devtool_md5_new')
471 preservepath = os.path.join(config.workspace_path, 'attic')
472 with open(origfile, 'r') as f:
473 with open(newfile, 'w') as tf:
474 for line in f.readlines():
475 splitline = line.rstrip().split('|')
476 if splitline[0] == recipename:
477 removefile = os.path.join(config.workspace_path, splitline[1])
478 try:
479 md5 = bb.utils.md5_file(removefile)
480 except IOError as err:
481 if err.errno == 2:
482 # File no longer exists, skip it
483 continue
484 else:
485 raise
486 if splitline[2] != md5:
487 bb.utils.mkdirhier(preservepath)
488 preservefile = os.path.basename(removefile)
489 logger.warn('File %s modified since it was written, preserving in %s' % (preservefile, preservepath))
490 shutil.move(removefile, os.path.join(preservepath, preservefile))
491 else:
492 os.remove(removefile)
493 else:
494 tf.write(line)
495 os.rename(newfile, origfile)
496
497def modify(args, config, basepath, workspace):
498 """Entry point for the devtool 'modify' subcommand"""
499 import bb
500 import oe.recipeutils
501
502 if args.recipename in workspace:
503 raise DevtoolError("recipe %s is already in your workspace" %
504 args.recipename)
505
506 if not args.extract and not os.path.isdir(args.srctree):
507 raise DevtoolError("directory %s does not exist or not a directory "
508 "(specify -x to extract source from recipe)" %
509 args.srctree)
510 if args.extract:
511 tinfoil = _prep_extract_operation(config, basepath, args.recipename)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500512 if not tinfoil:
513 # Error already shown
514 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500515 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500516 tinfoil = setup_tinfoil(basepath=basepath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500517
518 rd = parse_recipe(config, tinfoil, args.recipename, True)
519 if not rd:
520 return 1
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500521
522 pn = rd.getVar('PN', True)
523 if pn != args.recipename:
524 logger.info('Mapping %s to %s' % (args.recipename, pn))
525 if pn in workspace:
526 raise DevtoolError("recipe %s is already in your workspace" %
527 pn)
528
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500529 recipefile = rd.getVar('FILE', True)
530 appendname = os.path.splitext(os.path.basename(recipefile))[0]
531 if args.wildcard:
532 appendname = re.sub(r'_.*', '_%', appendname)
533 appendpath = os.path.join(config.workspace_path, 'appends')
534 appendfile = os.path.join(appendpath, appendname + '.bbappend')
535 if os.path.exists(appendfile):
536 raise DevtoolError("Another variant of recipe %s is already in your "
537 "workspace (only one variant of a recipe can "
538 "currently be worked on at once)"
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500539 % pn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500540
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500541 _check_compatible_recipe(pn, rd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500542
543 initial_rev = None
544 commits = []
545 srctree = os.path.abspath(args.srctree)
546 if args.extract:
547 initial_rev = _extract_source(args.srctree, False, args.branch, rd)
548 if not initial_rev:
549 return 1
550 logger.info('Source tree extracted to %s' % srctree)
551 # Get list of commits since this revision
552 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=args.srctree)
553 commits = stdout.split()
554 else:
555 if os.path.exists(os.path.join(args.srctree, '.git')):
556 # Check if it's a tree previously extracted by us
557 try:
558 (stdout, _) = bb.process.run('git branch --contains devtool-base', cwd=args.srctree)
559 except bb.process.ExecutionError:
560 stdout = ''
561 for line in stdout.splitlines():
562 if line.startswith('*'):
563 (stdout, _) = bb.process.run('git rev-parse devtool-base', cwd=args.srctree)
564 initial_rev = stdout.rstrip()
565 if not initial_rev:
566 # Otherwise, just grab the head revision
567 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=args.srctree)
568 initial_rev = stdout.rstrip()
569
570 # Check that recipe isn't using a shared workdir
571 s = os.path.abspath(rd.getVar('S', True))
572 workdir = os.path.abspath(rd.getVar('WORKDIR', True))
573 if s.startswith(workdir) and s != workdir and os.path.dirname(s) != workdir:
574 # Handle if S is set to a subdirectory of the source
575 srcsubdir = os.path.relpath(s, workdir).split(os.sep, 1)[1]
576 srctree = os.path.join(srctree, srcsubdir)
577
578 if not os.path.exists(appendpath):
579 os.makedirs(appendpath)
580 with open(appendfile, 'w') as f:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500581 f.write('FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n')
582 # Local files can be modified/tracked in separate subdir under srctree
583 # Mostly useful for packages with S != WORKDIR
584 f.write('FILESPATH_prepend := "%s:"\n' %
585 os.path.join(srctree, 'oe-local-files'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500586
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500587 f.write('\ninherit externalsrc\n')
588 f.write('# NOTE: We use pn- overrides here to avoid affecting multiple variants in the case where the recipe uses BBCLASSEXTEND\n')
589 f.write('EXTERNALSRC_pn-%s = "%s"\n' % (pn, srctree))
590
591 b_is_s = use_external_build(args.same_dir, args.no_same_dir, rd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500592 if b_is_s:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500593 f.write('EXTERNALSRC_BUILD_pn-%s = "%s"\n' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594
595 if bb.data.inherits_class('kernel', rd):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500596 f.write('SRCTREECOVEREDTASKS = "do_validate_branches do_kernel_checkout do_fetch do_unpack do_patch"\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500597 if initial_rev:
598 f.write('\n# initial_rev: %s\n' % initial_rev)
599 for commit in commits:
600 f.write('# commit: %s\n' % commit)
601
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500602 _add_md5(config, pn, appendfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500603
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500604 logger.info('Recipe %s now set up to build from %s' % (pn, srctree))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500605
606 return 0
607
608def _get_patchset_revs(args, srctree, recipe_path):
609 """Get initial and update rev of a recipe. These are the start point of the
610 whole patchset and start point for the patches to be re-generated/updated.
611 """
612 import bb
613
614 if args.initial_rev:
615 return args.initial_rev, args.initial_rev
616
617 # Parse initial rev from recipe
618 commits = []
619 initial_rev = None
620 with open(recipe_path, 'r') as f:
621 for line in f:
622 if line.startswith('# initial_rev:'):
623 initial_rev = line.split(':')[-1].strip()
624 elif line.startswith('# commit:'):
625 commits.append(line.split(':')[-1].strip())
626
627 update_rev = initial_rev
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500628 changed_revs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500629 if initial_rev:
630 # Find first actually changed revision
631 stdout, _ = bb.process.run('git rev-list --reverse %s..HEAD' %
632 initial_rev, cwd=srctree)
633 newcommits = stdout.split()
634 for i in xrange(min(len(commits), len(newcommits))):
635 if newcommits[i] == commits[i]:
636 update_rev = commits[i]
637
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500638 try:
639 stdout, _ = bb.process.run('git cherry devtool-patched',
640 cwd=srctree)
641 except bb.process.ExecutionError as err:
642 stdout = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500643
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500644 if stdout is not None:
645 changed_revs = []
646 for line in stdout.splitlines():
647 if line.startswith('+ '):
648 rev = line.split()[1]
649 if rev in newcommits:
650 changed_revs.append(rev)
651
652 return initial_rev, update_rev, changed_revs
653
654def _remove_file_entries(srcuri, filelist):
655 """Remove file:// entries from SRC_URI"""
656 remaining = filelist[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500657 entries = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500658 for fname in filelist:
659 basename = os.path.basename(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500660 for i in xrange(len(srcuri)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500661 if (srcuri[i].startswith('file://') and
662 os.path.basename(srcuri[i].split(';')[0]) == basename):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500663 entries.append(srcuri[i])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500664 remaining.remove(fname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500665 srcuri.pop(i)
666 break
667 return entries, remaining
668
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500669def _remove_source_files(args, files, destpath):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500670 """Unlink existing patch files"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500671 for path in files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500672 if args.append:
673 if not destpath:
674 raise Exception('destpath should be set here')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500675 path = os.path.join(destpath, os.path.basename(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500676
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500677 if os.path.exists(path):
678 logger.info('Removing file %s' % path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500679 # FIXME "git rm" here would be nice if the file in question is
680 # tracked
681 # FIXME there's a chance that this file is referred to by
682 # another recipe, in which case deleting wouldn't be the
683 # right thing to do
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500684 os.remove(path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500685 # Remove directory if empty
686 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500687 os.rmdir(os.path.dirname(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500688 except OSError as ose:
689 if ose.errno != errno.ENOTEMPTY:
690 raise
691
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500692
693def _export_patches(srctree, rd, start_rev, destdir):
694 """Export patches from srctree to given location.
695 Returns three-tuple of dicts:
696 1. updated - patches that already exist in SRCURI
697 2. added - new patches that don't exist in SRCURI
698 3 removed - patches that exist in SRCURI but not in exported patches
699 In each dict the key is the 'basepath' of the URI and value is the
700 absolute path to the existing file in recipe space (if any).
701 """
702 import oe.recipeutils
703 from oe.patch import GitApplyTree
704 updated = OrderedDict()
705 added = OrderedDict()
706 seqpatch_re = re.compile('^([0-9]{4}-)?(.+)')
707
708 existing_patches = dict((os.path.basename(path), path) for path in
709 oe.recipeutils.get_recipe_patches(rd))
710
711 # Generate patches from Git, exclude local files directory
712 patch_pathspec = _git_exclude_path(srctree, 'oe-local-files')
713 GitApplyTree.extractPatches(srctree, start_rev, destdir, patch_pathspec)
714
715 new_patches = sorted(os.listdir(destdir))
716 for new_patch in new_patches:
717 # Strip numbering from patch names. If it's a git sequence named patch,
718 # the numbers might not match up since we are starting from a different
719 # revision This does assume that people are using unique shortlog
720 # values, but they ought to be anyway...
721 new_basename = seqpatch_re.match(new_patch).group(2)
722 found = False
723 for old_patch in existing_patches:
724 old_basename = seqpatch_re.match(old_patch).group(2)
725 if new_basename == old_basename:
726 updated[new_patch] = existing_patches.pop(old_patch)
727 found = True
728 # Rename patch files
729 if new_patch != old_patch:
730 os.rename(os.path.join(destdir, new_patch),
731 os.path.join(destdir, old_patch))
732 break
733 if not found:
734 added[new_patch] = None
735 return (updated, added, existing_patches)
736
737
738def _export_local_files(srctree, rd, destdir):
739 """Copy local files from srctree to given location.
740 Returns three-tuple of dicts:
741 1. updated - files that already exist in SRCURI
742 2. added - new files files that don't exist in SRCURI
743 3 removed - files that exist in SRCURI but not in exported files
744 In each dict the key is the 'basepath' of the URI and value is the
745 absolute path to the existing file in recipe space (if any).
746 """
747 import oe.recipeutils
748
749 # Find out local files (SRC_URI files that exist in the "recipe space").
750 # Local files that reside in srctree are not included in patch generation.
751 # Instead they are directly copied over the original source files (in
752 # recipe space).
753 existing_files = oe.recipeutils.get_recipe_local_files(rd)
754 new_set = None
755 updated = OrderedDict()
756 added = OrderedDict()
757 removed = OrderedDict()
758 git_files = _git_ls_tree(srctree)
759 if 'oe-local-files' in git_files:
760 # If tracked by Git, take the files from srctree HEAD. First get
761 # the tree object of the directory
762 tmp_index = os.path.join(srctree, '.git', 'index.tmp.devtool')
763 tree = git_files['oe-local-files'][2]
764 bb.process.run(['git', 'checkout', tree, '--', '.'], cwd=srctree,
765 env=dict(os.environ, GIT_WORK_TREE=destdir,
766 GIT_INDEX_FILE=tmp_index))
767 new_set = _git_ls_tree(srctree, tree, True).keys()
768 elif os.path.isdir(os.path.join(srctree, 'oe-local-files')):
769 # If not tracked by Git, just copy from working copy
770 new_set = _ls_tree(os.path.join(srctree, 'oe-local-files'))
771 bb.process.run(['cp', '-ax',
772 os.path.join(srctree, 'oe-local-files', '.'), destdir])
773 if new_set is not None:
774 for fname in new_set:
775 if fname in existing_files:
776 updated[fname] = existing_files.pop(fname)
777 elif fname != '.gitignore':
778 added[fname] = None
779
780 removed = existing_files
781 return (updated, added, removed)
782
783
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500784def _update_recipe_srcrev(args, srctree, rd, config_data):
785 """Implement the 'srcrev' mode of update-recipe"""
786 import bb
787 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500788
789 recipefile = rd.getVar('FILE', True)
790 logger.info('Updating SRCREV in recipe %s' % os.path.basename(recipefile))
791
792 # Get HEAD revision
793 try:
794 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
795 except bb.process.ExecutionError as err:
796 raise DevtoolError('Failed to get HEAD revision in %s: %s' %
797 (srctree, err))
798 srcrev = stdout.strip()
799 if len(srcrev) != 40:
800 raise DevtoolError('Invalid hash returned by git: %s' % stdout)
801
802 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500803 remove_files = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500804 patchfields = {}
805 patchfields['SRCREV'] = srcrev
806 orig_src_uri = rd.getVar('SRC_URI', False) or ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500807 srcuri = orig_src_uri.split()
808 tempdir = tempfile.mkdtemp(prefix='devtool')
809 update_srcuri = False
810 try:
811 local_files_dir = tempfile.mkdtemp(dir=tempdir)
812 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir)
813 if not args.no_remove:
814 # Find list of existing patches in recipe file
815 patches_dir = tempfile.mkdtemp(dir=tempdir)
816 old_srcrev = (rd.getVar('SRCREV', False) or '')
817 upd_p, new_p, del_p = _export_patches(srctree, rd, old_srcrev,
818 patches_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500819
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500820 # Remove deleted local files and "overlapping" patches
821 remove_files = del_f.values() + upd_p.values()
822 if remove_files:
823 removedentries = _remove_file_entries(srcuri, remove_files)[0]
824 update_srcuri = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500825
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500826 if args.append:
827 files = dict((os.path.join(local_files_dir, key), val) for
828 key, val in upd_f.items() + new_f.items())
829 removevalues = {}
830 if update_srcuri:
831 removevalues = {'SRC_URI': removedentries}
832 patchfields['SRC_URI'] = '\\\n '.join(srcuri)
833 _, destpath = oe.recipeutils.bbappend_recipe(
834 rd, args.append, files, wildcardver=args.wildcard_version,
835 extralines=patchfields, removevalues=removevalues)
836 else:
837 files_dir = os.path.join(os.path.dirname(recipefile),
838 rd.getVar('BPN', True))
839 for basepath, path in upd_f.iteritems():
840 logger.info('Updating file %s' % basepath)
841 _move_file(os.path.join(local_files_dir, basepath), path)
842 update_srcuri= True
843 for basepath, path in new_f.iteritems():
844 logger.info('Adding new file %s' % basepath)
845 _move_file(os.path.join(local_files_dir, basepath),
846 os.path.join(files_dir, basepath))
847 srcuri.append('file://%s' % basepath)
848 update_srcuri = True
849 if update_srcuri:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500850 patchfields['SRC_URI'] = ' '.join(srcuri)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500851 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
852 finally:
853 shutil.rmtree(tempdir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500854 if not 'git://' in orig_src_uri:
855 logger.info('You will need to update SRC_URI within the recipe to '
856 'point to a git repository where you have pushed your '
857 'changes')
858
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500859 _remove_source_files(args, remove_files, destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500860
861def _update_recipe_patch(args, config, srctree, rd, config_data):
862 """Implement the 'patch' mode of update-recipe"""
863 import bb
864 import oe.recipeutils
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500865
866 recipefile = rd.getVar('FILE', True)
867 append = os.path.join(config.workspace_path, 'appends', '%s.bbappend' %
868 os.path.splitext(os.path.basename(recipefile))[0])
869 if not os.path.exists(append):
870 raise DevtoolError('unable to find workspace bbappend for recipe %s' %
871 args.recipename)
872
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500873 initial_rev, update_rev, changed_revs = _get_patchset_revs(args, srctree, append)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500874 if not initial_rev:
875 raise DevtoolError('Unable to find initial revision - please specify '
876 'it with --initial-rev')
877
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500878 tempdir = tempfile.mkdtemp(prefix='devtool')
879 try:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500880 local_files_dir = tempfile.mkdtemp(dir=tempdir)
881 upd_f, new_f, del_f = _export_local_files(srctree, rd, local_files_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500882
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500883 remove_files = []
884 if not args.no_remove:
885 # Get all patches from source tree and check if any should be removed
886 all_patches_dir = tempfile.mkdtemp(dir=tempdir)
887 upd_p, new_p, del_p = _export_patches(srctree, rd, initial_rev,
888 all_patches_dir)
889 # Remove deleted local files and patches
890 remove_files = del_f.values() + del_p.values()
891
892 # Get updated patches from source tree
893 patches_dir = tempfile.mkdtemp(dir=tempdir)
894 upd_p, new_p, del_p = _export_patches(srctree, rd, update_rev,
895 patches_dir)
896 updatefiles = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500897 updaterecipe = False
898 destpath = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500899 srcuri = (rd.getVar('SRC_URI', False) or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500900 if args.append:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500901 files = dict((os.path.join(local_files_dir, key), val) for
902 key, val in upd_f.items() + new_f.items())
903 files.update(dict((os.path.join(patches_dir, key), val) for
904 key, val in upd_p.items() + new_p.items()))
905 if files or remove_files:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500906 removevalues = None
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500907 if remove_files:
908 removedentries, remaining = _remove_file_entries(
909 srcuri, remove_files)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500910 if removedentries or remaining:
911 remaining = ['file://' + os.path.basename(item) for
912 item in remaining]
913 removevalues = {'SRC_URI': removedentries + remaining}
914 _, destpath = oe.recipeutils.bbappend_recipe(
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500915 rd, args.append, files,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500916 removevalues=removevalues)
917 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500918 logger.info('No patches or local source files needed updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500919 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500920 # Update existing files
921 for basepath, path in upd_f.iteritems():
922 logger.info('Updating file %s' % basepath)
923 _move_file(os.path.join(local_files_dir, basepath), path)
924 updatefiles = True
925 for basepath, path in upd_p.iteritems():
926 patchfn = os.path.join(patches_dir, basepath)
927 if changed_revs is not None:
928 # Avoid updating patches that have not actually changed
929 with open(patchfn, 'r') as f:
930 firstlineitems = f.readline().split()
931 if len(firstlineitems) > 1 and len(firstlineitems[1]) == 40:
932 if not firstlineitems[1] in changed_revs:
933 continue
934 logger.info('Updating patch %s' % basepath)
935 _move_file(patchfn, path)
936 updatefiles = True
937 # Add any new files
938 files_dir = os.path.join(os.path.dirname(recipefile),
939 rd.getVar('BPN', True))
940 for basepath, path in new_f.iteritems():
941 logger.info('Adding new file %s' % basepath)
942 _move_file(os.path.join(local_files_dir, basepath),
943 os.path.join(files_dir, basepath))
944 srcuri.append('file://%s' % basepath)
945 updaterecipe = True
946 for basepath, path in new_p.iteritems():
947 logger.info('Adding new patch %s' % basepath)
948 _move_file(os.path.join(patches_dir, basepath),
949 os.path.join(files_dir, basepath))
950 srcuri.append('file://%s' % basepath)
951 updaterecipe = True
952 # Update recipe, if needed
953 if _remove_file_entries(srcuri, remove_files)[0]:
954 updaterecipe = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500955 if updaterecipe:
956 logger.info('Updating recipe %s' % os.path.basename(recipefile))
957 oe.recipeutils.patch_recipe(rd, recipefile,
958 {'SRC_URI': ' '.join(srcuri)})
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500959 elif not updatefiles:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500960 # Neither patches nor recipe were updated
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500961 logger.info('No patches or files need updating')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500962 finally:
963 shutil.rmtree(tempdir)
964
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500965 _remove_source_files(args, remove_files, destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500966
967def _guess_recipe_update_mode(srctree, rdata):
968 """Guess the recipe update mode to use"""
969 src_uri = (rdata.getVar('SRC_URI', False) or '').split()
970 git_uris = [uri for uri in src_uri if uri.startswith('git://')]
971 if not git_uris:
972 return 'patch'
973 # Just use the first URI for now
974 uri = git_uris[0]
975 # Check remote branch
976 params = bb.fetch.decodeurl(uri)[5]
977 upstr_branch = params['branch'] if 'branch' in params else 'master'
978 # Check if current branch HEAD is found in upstream branch
979 stdout, _ = bb.process.run('git rev-parse HEAD', cwd=srctree)
980 head_rev = stdout.rstrip()
981 stdout, _ = bb.process.run('git branch -r --contains %s' % head_rev,
982 cwd=srctree)
983 remote_brs = [branch.strip() for branch in stdout.splitlines()]
984 if 'origin/' + upstr_branch in remote_brs:
985 return 'srcrev'
986
987 return 'patch'
988
989def update_recipe(args, config, basepath, workspace):
990 """Entry point for the devtool 'update-recipe' subcommand"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500991 check_workspace_recipe(workspace, args.recipename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500992
993 if args.append:
994 if not os.path.exists(args.append):
995 raise DevtoolError('bbappend destination layer directory "%s" '
996 'does not exist' % args.append)
997 if not os.path.exists(os.path.join(args.append, 'conf', 'layer.conf')):
998 raise DevtoolError('conf/layer.conf not found in bbappend '
999 'destination layer "%s"' % args.append)
1000
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001001 tinfoil = setup_tinfoil(basepath=basepath, tracking=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001002
1003 rd = parse_recipe(config, tinfoil, args.recipename, True)
1004 if not rd:
1005 return 1
1006
1007 srctree = workspace[args.recipename]['srctree']
1008 if args.mode == 'auto':
1009 mode = _guess_recipe_update_mode(srctree, rd)
1010 else:
1011 mode = args.mode
1012
1013 if mode == 'srcrev':
1014 _update_recipe_srcrev(args, srctree, rd, tinfoil.config_data)
1015 elif mode == 'patch':
1016 _update_recipe_patch(args, config, srctree, rd, tinfoil.config_data)
1017 else:
1018 raise DevtoolError('update_recipe: invalid mode %s' % mode)
1019
1020 rf = rd.getVar('FILE', True)
1021 if rf.startswith(config.workspace_path):
1022 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)
1023
1024 return 0
1025
1026
1027def status(args, config, basepath, workspace):
1028 """Entry point for the devtool 'status' subcommand"""
1029 if workspace:
1030 for recipe, value in workspace.iteritems():
1031 print("%s: %s" % (recipe, value['srctree']))
1032 else:
1033 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')
1034 return 0
1035
1036
1037def reset(args, config, basepath, workspace):
1038 """Entry point for the devtool 'reset' subcommand"""
1039 import bb
1040 if args.recipename:
1041 if args.all:
1042 raise DevtoolError("Recipe cannot be specified if -a/--all is used")
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001043 else:
1044 check_workspace_recipe(workspace, args.recipename, checksrc=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001045 elif not args.all:
1046 raise DevtoolError("Recipe must be specified, or specify -a/--all to "
1047 "reset all recipes")
1048 if args.all:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001049 recipes = workspace.keys()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001050 else:
1051 recipes = [args.recipename]
1052
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001053 if recipes and not args.no_clean:
1054 if len(recipes) == 1:
1055 logger.info('Cleaning sysroot for recipe %s...' % recipes[0])
1056 else:
1057 logger.info('Cleaning sysroot for recipes %s...' % ', '.join(recipes))
1058 try:
1059 exec_build_env_command(config.init_path, basepath, 'bitbake -c clean %s' % ' '.join(recipes))
1060 except bb.process.ExecutionError as e:
1061 raise DevtoolError('Command \'%s\' failed, output:\n%s\nIf you '
1062 'wish, you may specify -n/--no-clean to '
1063 'skip running this command when resetting' %
1064 (e.command, e.stdout))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001065
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001066 for pn in recipes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001067 _check_preserve(config, pn)
1068
1069 preservepath = os.path.join(config.workspace_path, 'attic', pn)
1070 def preservedir(origdir):
1071 if os.path.exists(origdir):
1072 for root, dirs, files in os.walk(origdir):
1073 for fn in files:
1074 logger.warn('Preserving %s in %s' % (fn, preservepath))
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001075 _move_file(os.path.join(origdir, fn),
1076 os.path.join(preservepath, fn))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001077 for dn in dirs:
1078 os.rmdir(os.path.join(root, dn))
1079 os.rmdir(origdir)
1080
1081 preservedir(os.path.join(config.workspace_path, 'recipes', pn))
1082 # We don't automatically create this dir next to appends, but the user can
1083 preservedir(os.path.join(config.workspace_path, 'appends', pn))
1084
1085 return 0
1086
1087
1088def register_commands(subparsers, context):
1089 """Register devtool subcommands from this plugin"""
1090 parser_add = subparsers.add_parser('add', help='Add a new recipe',
1091 description='Adds a new recipe')
1092 parser_add.add_argument('recipename', help='Name for new recipe to add')
1093 parser_add.add_argument('srctree', help='Path to external source tree')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001094 group = parser_add.add_mutually_exclusive_group()
1095 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
1096 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001097 parser_add.add_argument('--fetch', '-f', help='Fetch the specified URI and extract it to create the source tree', metavar='URI')
1098 parser_add.add_argument('--version', '-V', help='Version to use within recipe (PV)')
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001099 parser_add.add_argument('--no-git', '-g', help='If -f/--fetch is specified, do not set up source tree as a git repository', action="store_true")
1100 parser_add.add_argument('--binary', '-b', help='Treat the source tree as something that should be installed verbatim (no compilation, same directory structure)', action='store_true')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001101 parser_add.set_defaults(func=add)
1102
1103 parser_modify = subparsers.add_parser('modify', help='Modify the source for an existing recipe',
1104 description='Enables modifying the source for an existing recipe',
1105 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
1106 parser_modify.add_argument('recipename', help='Name for recipe to edit')
1107 parser_modify.add_argument('srctree', help='Path to external source tree')
1108 parser_modify.add_argument('--wildcard', '-w', action="store_true", help='Use wildcard for unversioned bbappend')
1109 parser_modify.add_argument('--extract', '-x', action="store_true", help='Extract source as well')
1110 group = parser_modify.add_mutually_exclusive_group()
1111 group.add_argument('--same-dir', '-s', help='Build in same directory as source', action="store_true")
1112 group.add_argument('--no-same-dir', help='Force build in a separate build directory', action="store_true")
1113 parser_modify.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout (only when using -x)')
1114 parser_modify.set_defaults(func=modify)
1115
1116 parser_extract = subparsers.add_parser('extract', help='Extract the source for an existing recipe',
1117 description='Extracts the source for an existing recipe',
1118 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
1119 parser_extract.add_argument('recipename', help='Name for recipe to extract the source for')
1120 parser_extract.add_argument('srctree', help='Path to where to extract the source tree')
1121 parser_extract.add_argument('--branch', '-b', default="devtool", help='Name for development branch to checkout')
1122 parser_extract.add_argument('--keep-temp', action="store_true", help='Keep temporary directory (for debugging)')
1123 parser_extract.set_defaults(func=extract)
1124
1125 parser_update_recipe = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
1126 description='Applies changes from external source tree to a recipe (updating/adding/removing patches as necessary, or by updating SRCREV)')
1127 parser_update_recipe.add_argument('recipename', help='Name of recipe to update')
1128 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')
1129 parser_update_recipe.add_argument('--initial-rev', help='Starting revision for patches')
1130 parser_update_recipe.add_argument('--append', '-a', help='Write changes to a bbappend in the specified layer instead of the recipe', metavar='LAYERDIR')
1131 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')
1132 parser_update_recipe.add_argument('--no-remove', '-n', action="store_true", help='Don\'t remove patches, only add or update')
1133 parser_update_recipe.set_defaults(func=update_recipe)
1134
1135 parser_status = subparsers.add_parser('status', help='Show workspace status',
1136 description='Lists recipes currently in your workspace and the paths to their respective external source trees',
1137 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
1138 parser_status.set_defaults(func=status)
1139
1140 parser_reset = subparsers.add_parser('reset', help='Remove a recipe from your workspace',
1141 description='Removes the specified recipe from your workspace (resetting its state)',
1142 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
1143 parser_reset.add_argument('recipename', nargs='?', help='Recipe to reset')
1144 parser_reset.add_argument('--all', '-a', action="store_true", help='Reset all recipes (clear workspace)')
1145 parser_reset.add_argument('--no-clean', '-n', action="store_true", help='Don\'t clean the sysroot to remove recipe output')
1146 parser_reset.set_defaults(func=reset)