summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/lib/oeqa/selftest/devtool.py70
-rw-r--r--scripts/lib/devtool/standard.py230
2 files changed, 213 insertions, 87 deletions
diff --git a/meta/lib/oeqa/selftest/devtool.py b/meta/lib/oeqa/selftest/devtool.py
index 8caf07aaec..f147f248b3 100644
--- a/meta/lib/oeqa/selftest/devtool.py
+++ b/meta/lib/oeqa/selftest/devtool.py
@@ -233,6 +233,8 @@ class DevtoolTests(oeSelfTest):
233 self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') 233 self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory')
234 testrecipe = 'minicom' 234 testrecipe = 'minicom'
235 recipefile = get_bb_var('FILE', testrecipe) 235 recipefile = get_bb_var('FILE', testrecipe)
236 src_uri = get_bb_var('SRC_URI', testrecipe)
237 self.assertNotIn('git://', src_uri, 'This test expects the %s recipe to NOT be a git recipe' % testrecipe)
236 result = runCmd('git status . --porcelain', cwd=os.path.dirname(recipefile)) 238 result = runCmd('git status . --porcelain', cwd=os.path.dirname(recipefile))
237 self.assertEqual(result.output.strip(), "", '%s recipe is not clean' % testrecipe) 239 self.assertEqual(result.output.strip(), "", '%s recipe is not clean' % testrecipe)
238 # First, modify a recipe 240 # First, modify a recipe
@@ -266,11 +268,77 @@ class DevtoolTests(oeSelfTest):
266 self.assertEqual(line[:3], '?? ', 'Unexpected status in line: %s' % line) 268 self.assertEqual(line[:3], '?? ', 'Unexpected status in line: %s' % line)
267 elif line.endswith('0002-Add-a-new-file.patch'): 269 elif line.endswith('0002-Add-a-new-file.patch'):
268 self.assertEqual(line[:3], '?? ', 'Unexpected status in line: %s' % line) 270 self.assertEqual(line[:3], '?? ', 'Unexpected status in line: %s' % line)
269 elif re.search('minicom_[^_]*.bb$', line): 271 elif re.search('%s_[^_]*.bb$' % testrecipe, line):
270 self.assertEqual(line[:3], ' M ', 'Unexpected status in line: %s' % line) 272 self.assertEqual(line[:3], ' M ', 'Unexpected status in line: %s' % line)
271 else: 273 else:
272 raise AssertionError('Unexpected modified file in status: %s' % line) 274 raise AssertionError('Unexpected modified file in status: %s' % line)
273 275
276 def test_devtool_update_recipe_git(self):
277 # Check preconditions
278 workspacedir = os.path.join(self.builddir, 'workspace')
279 self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory')
280 testrecipe = 'mtd-utils'
281 recipefile = get_bb_var('FILE', testrecipe)
282 src_uri = get_bb_var('SRC_URI', testrecipe)
283 self.assertIn('git://', src_uri, 'This test expects the %s recipe to be a git recipe' % testrecipe)
284 result = runCmd('git status . --porcelain', cwd=os.path.dirname(recipefile))
285 self.assertEqual(result.output.strip(), "", '%s recipe is not clean' % testrecipe)
286 # First, modify a recipe
287 tempdir = tempfile.mkdtemp(prefix='devtoolqa')
288 self.track_for_cleanup(tempdir)
289 self.track_for_cleanup(workspacedir)
290 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
291 # (don't bother with cleaning the recipe on teardown, we won't be building it)
292 result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
293 # Check git repo
294 self.assertTrue(os.path.isdir(os.path.join(tempdir, '.git')), 'git repository for external source tree not found')
295 result = runCmd('git status --porcelain', cwd=tempdir)
296 self.assertEqual(result.output.strip(), "", 'Created git repo is not clean')
297 result = runCmd('git symbolic-ref HEAD', cwd=tempdir)
298 self.assertEqual(result.output.strip(), "refs/heads/devtool", 'Wrong branch in git repo')
299 # Add a couple of commits
300 # FIXME: this only tests adding, need to also test update and remove
301 result = runCmd('echo "# Additional line" >> Makefile', cwd=tempdir)
302 result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempdir)
303 result = runCmd('echo "A new file" > devtool-new-file', cwd=tempdir)
304 result = runCmd('git add devtool-new-file', cwd=tempdir)
305 result = runCmd('git commit -m "Add a new file"', cwd=tempdir)
306 self.add_command_to_tearDown('cd %s; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, os.path.basename(recipefile)))
307 result = runCmd('devtool update-recipe %s' % testrecipe)
308 result = runCmd('git status . --porcelain', cwd=os.path.dirname(recipefile))
309 self.assertNotEqual(result.output.strip(), "", '%s recipe should be modified' % testrecipe)
310 status = result.output.splitlines()
311 self.assertEqual(len(status), 3, 'Less/more files modified than expected. Entire status:\n%s' % result.output)
312 for line in status:
313 if line.endswith('add-exclusion-to-mkfs-jffs2-git-2.patch'):
314 self.assertEqual(line[:3], ' D ', 'Unexpected status in line: %s' % line)
315 elif line.endswith('fix-armv7-neon-alignment.patch'):
316 self.assertEqual(line[:3], ' D ', 'Unexpected status in line: %s' % line)
317 elif re.search('%s_[^_]*.bb$' % testrecipe, line):
318 self.assertEqual(line[:3], ' M ', 'Unexpected status in line: %s' % line)
319 else:
320 raise AssertionError('Unexpected modified file in status: %s' % line)
321 result = runCmd('git diff %s' % os.path.basename(recipefile), cwd=os.path.dirname(recipefile))
322 addlines = ['SRCREV = ".*"', 'SRC_URI = "git://git.infradead.org/mtd-utils.git"']
323 removelines = ['SRCREV = ".*"', 'SRC_URI = "git://git.infradead.org/mtd-utils.git \\\\', 'file://add-exclusion-to-mkfs-jffs2-git-2.patch \\\\', 'file://fix-armv7-neon-alignment.patch \\\\', '"']
324 for line in result.output.splitlines():
325 if line.startswith('+++') or line.startswith('---'):
326 continue
327 elif line.startswith('+'):
328 matched = False
329 for item in addlines:
330 if re.match(item, line[1:].strip()):
331 matched = True
332 break
333 self.assertTrue(matched, 'Unexpected diff add line: %s' % line)
334 elif line.startswith('-'):
335 matched = False
336 for item in removelines:
337 if re.match(item, line[1:].strip()):
338 matched = True
339 break
340 self.assertTrue(matched, 'Unexpected diff remove line: %s' % line)
341
274 def test_devtool_extract(self): 342 def test_devtool_extract(self):
275 # Check preconditions 343 # Check preconditions
276 workspacedir = os.path.join(self.builddir, 'workspace') 344 workspacedir = os.path.join(self.builddir, 'workspace')
diff --git a/scripts/lib/devtool/standard.py b/scripts/lib/devtool/standard.py
index 9b5a0855b2..3a8c66c131 100644
--- a/scripts/lib/devtool/standard.py
+++ b/scripts/lib/devtool/standard.py
@@ -359,104 +359,162 @@ def update_recipe(args, config, basepath, workspace):
359 from oe.patch import GitApplyTree 359 from oe.patch import GitApplyTree
360 import oe.recipeutils 360 import oe.recipeutils
361 361
362 srctree = workspace[args.recipename]
363 commits = []
364 update_rev = None
365 if args.initial_rev:
366 initial_rev = args.initial_rev
367 else:
368 initial_rev = None
369 with open(appends[0], 'r') as f:
370 for line in f:
371 if line.startswith('# initial_rev:'):
372 initial_rev = line.split(':')[-1].strip()
373 elif line.startswith('# commit:'):
374 commits.append(line.split(':')[-1].strip())
375
376 if initial_rev:
377 # Find first actually changed revision
378 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
379 newcommits = stdout.split()
380 for i in xrange(min(len(commits), len(newcommits))):
381 if newcommits[i] == commits[i]:
382 update_rev = commits[i]
383
384 if not initial_rev:
385 logger.error('Unable to find initial revision - please specify it with --initial-rev')
386 return -1
387
388 if not update_rev:
389 update_rev = initial_rev
390
391 # Find list of existing patches in recipe file
392 recipefile = _get_recipe_file(tinfoil.cooker, args.recipename) 362 recipefile = _get_recipe_file(tinfoil.cooker, args.recipename)
393 if not recipefile: 363 if not recipefile:
394 # Error already logged 364 # Error already logged
395 return -1 365 return -1
396 rd = oe.recipeutils.parse_recipe(recipefile, tinfoil.config_data) 366 rd = oe.recipeutils.parse_recipe(recipefile, tinfoil.config_data)
397 existing_patches = oe.recipeutils.get_recipe_patches(rd)
398 367
399 removepatches = [] 368 orig_src_uri = rd.getVar('SRC_URI', False) or ''
400 if not args.no_remove: 369 if args.mode == 'auto':
401 # Get all patches from source tree and check if any should be removed 370 if 'git://' in orig_src_uri:
371 mode = 'srcrev'
372 else:
373 mode = 'patch'
374 else:
375 mode = args.mode
376
377 def remove_patches(srcuri, patchlist):
378 # Remove any patches that we don't need
379 updated = False
380 for patch in patchlist:
381 patchfile = os.path.basename(patch)
382 for i in xrange(len(srcuri)):
383 if srcuri[i].startswith('file://') and os.path.basename(srcuri[i]).split(';')[0] == patchfile:
384 logger.info('Removing patch %s' % patchfile)
385 srcuri.pop(i)
386 # FIXME "git rm" here would be nice if the file in question is tracked
387 # FIXME there's a chance that this file is referred to by another recipe, in which case deleting wouldn't be the right thing to do
388 if patch.startswith(os.path.dirname(recipefile)):
389 os.remove(patch)
390 updated = True
391 break
392 return updated
393
394 srctree = workspace[args.recipename]
395 if mode == 'srcrev':
396 (stdout, _) = bb.process.run('git rev-parse HEAD', cwd=srctree)
397 srcrev = stdout.strip()
398 if len(srcrev) != 40:
399 logger.error('Invalid hash returned by git: %s' % stdout)
400 return 1
401
402 logger.info('Updating SRCREV in recipe %s' % os.path.basename(recipefile))
403 patchfields = {}
404 patchfields['SRCREV'] = srcrev
405 if not args.no_remove:
406 # Find list of existing patches in recipe file
407 existing_patches = oe.recipeutils.get_recipe_patches(rd)
408
409 old_srcrev = (rd.getVar('SRCREV', False) or '')
410 tempdir = tempfile.mkdtemp(prefix='devtool')
411 removepatches = []
412 try:
413 GitApplyTree.extractPatches(srctree, old_srcrev, tempdir)
414 newpatches = os.listdir(tempdir)
415 for patch in existing_patches:
416 patchfile = os.path.basename(patch)
417 if patchfile in newpatches:
418 removepatches.append(patch)
419 finally:
420 shutil.rmtree(tempdir)
421 if removepatches:
422 srcuri = (rd.getVar('SRC_URI', False) or '').split()
423 if remove_patches(srcuri, removepatches):
424 patchfields['SRC_URI'] = ' '.join(srcuri)
425
426 oe.recipeutils.patch_recipe(rd, recipefile, patchfields)
427
428 if not 'git://' in orig_src_uri:
429 logger.info('You will need to update SRC_URI within the recipe to point to a git repository where you have pushed your changes')
430
431 elif mode == 'patch':
432 commits = []
433 update_rev = None
434 if args.initial_rev:
435 initial_rev = args.initial_rev
436 else:
437 initial_rev = None
438 with open(appends[0], 'r') as f:
439 for line in f:
440 if line.startswith('# initial_rev:'):
441 initial_rev = line.split(':')[-1].strip()
442 elif line.startswith('# commit:'):
443 commits.append(line.split(':')[-1].strip())
444
445 if initial_rev:
446 # Find first actually changed revision
447 (stdout, _) = bb.process.run('git rev-list --reverse %s..HEAD' % initial_rev, cwd=srctree)
448 newcommits = stdout.split()
449 for i in xrange(min(len(commits), len(newcommits))):
450 if newcommits[i] == commits[i]:
451 update_rev = commits[i]
452
453 if not initial_rev:
454 logger.error('Unable to find initial revision - please specify it with --initial-rev')
455 return -1
456
457 if not update_rev:
458 update_rev = initial_rev
459
460 # Find list of existing patches in recipe file
461 existing_patches = oe.recipeutils.get_recipe_patches(rd)
462
463 removepatches = []
464 if not args.no_remove:
465 # Get all patches from source tree and check if any should be removed
466 tempdir = tempfile.mkdtemp(prefix='devtool')
467 try:
468 GitApplyTree.extractPatches(srctree, initial_rev, tempdir)
469 newpatches = os.listdir(tempdir)
470 for patch in existing_patches:
471 patchfile = os.path.basename(patch)
472 if patchfile not in newpatches:
473 removepatches.append(patch)
474 finally:
475 shutil.rmtree(tempdir)
476
477 # Get updated patches from source tree
402 tempdir = tempfile.mkdtemp(prefix='devtool') 478 tempdir = tempfile.mkdtemp(prefix='devtool')
403 try: 479 try:
404 GitApplyTree.extractPatches(srctree, initial_rev, tempdir) 480 GitApplyTree.extractPatches(srctree, update_rev, tempdir)
481
482 # Match up and replace existing patches with corresponding new patches
483 updatepatches = False
484 updaterecipe = False
405 newpatches = os.listdir(tempdir) 485 newpatches = os.listdir(tempdir)
406 for patch in existing_patches: 486 for patch in existing_patches:
407 patchfile = os.path.basename(patch) 487 patchfile = os.path.basename(patch)
408 if patchfile not in newpatches: 488 if patchfile in newpatches:
409 removepatches.append(patch) 489 logger.info('Updating patch %s' % patchfile)
490 shutil.move(os.path.join(tempdir, patchfile), patch)
491 newpatches.remove(patchfile)
492 updatepatches = True
493 srcuri = (rd.getVar('SRC_URI', False) or '').split()
494 if newpatches:
495 # Add any patches left over
496 patchdir = os.path.join(os.path.dirname(recipefile), rd.getVar('BPN', True))
497 bb.utils.mkdirhier(patchdir)
498 for patchfile in newpatches:
499 logger.info('Adding new patch %s' % patchfile)
500 shutil.move(os.path.join(tempdir, patchfile), os.path.join(patchdir, patchfile))
501 srcuri.append('file://%s' % patchfile)
502 updaterecipe = True
503 if removepatches:
504 if remove_patches(srcuri, removepatches):
505 updaterecipe = True
506 if updaterecipe:
507 logger.info('Updating recipe %s' % os.path.basename(recipefile))
508 oe.recipeutils.patch_recipe(rd, recipefile, {'SRC_URI': ' '.join(srcuri)})
509 elif not updatepatches:
510 # Neither patches nor recipe were updated
511 logger.info('No patches need updating')
410 finally: 512 finally:
411 shutil.rmtree(tempdir) 513 shutil.rmtree(tempdir)
412 514
413 # Get updated patches from source tree 515 else:
414 tempdir = tempfile.mkdtemp(prefix='devtool') 516 logger.error('update_recipe: invalid mode %s' % mode)
415 try: 517 return 1
416 GitApplyTree.extractPatches(srctree, update_rev, tempdir)
417
418 # Match up and replace existing patches with corresponding new patches
419 updatepatches = False
420 updaterecipe = False
421 newpatches = os.listdir(tempdir)
422 for patch in existing_patches:
423 patchfile = os.path.basename(patch)
424 if patchfile in newpatches:
425 logger.info('Updating patch %s' % patchfile)
426 shutil.move(os.path.join(tempdir, patchfile), patch)
427 newpatches.remove(patchfile)
428 updatepatches = True
429 srcuri = (rd.getVar('SRC_URI', False) or '').split()
430 if newpatches:
431 # Add any patches left over
432 patchdir = os.path.join(os.path.dirname(recipefile), rd.getVar('BPN', True))
433 bb.utils.mkdirhier(patchdir)
434 for patchfile in newpatches:
435 logger.info('Adding new patch %s' % patchfile)
436 shutil.move(os.path.join(tempdir, patchfile), os.path.join(patchdir, patchfile))
437 srcuri.append('file://%s' % patchfile)
438 updaterecipe = True
439 if removepatches:
440 # Remove any patches that we don't need
441 for patch in removepatches:
442 patchfile = os.path.basename(patch)
443 for i in xrange(len(srcuri)):
444 if srcuri[i].startswith('file://') and os.path.basename(srcuri[i]).split(';')[0] == patchfile:
445 logger.info('Removing patch %s' % patchfile)
446 srcuri.pop(i)
447 # FIXME "git rm" here would be nice if the file in question is tracked
448 # FIXME there's a chance that this file is referred to by another recipe, in which case deleting wouldn't be the right thing to do
449 os.remove(patch)
450 updaterecipe = True
451 break
452 if updaterecipe:
453 logger.info('Updating recipe %s' % os.path.basename(recipefile))
454 oe.recipeutils.patch_recipe(rd, recipefile, {'SRC_URI': ' '.join(srcuri)})
455 elif not updatepatches:
456 # Neither patches nor recipe were updated
457 logger.info('No patches need updating')
458 finally:
459 shutil.rmtree(tempdir)
460 518
461 return 0 519 return 0
462 520
@@ -539,9 +597,9 @@ def register_commands(subparsers, context):
539 parser_add.set_defaults(func=extract) 597 parser_add.set_defaults(func=extract)
540 598
541 parser_add = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe', 599 parser_add = subparsers.add_parser('update-recipe', help='Apply changes from external source tree to recipe',
542 description='Applies changes from external source tree to a recipe (updating/adding/removing patches as necessary)', 600 description='Applies changes from external source tree to a recipe (updating/adding/removing patches as necessary, or by updating SRCREV)')
543 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
544 parser_add.add_argument('recipename', help='Name of recipe to update') 601 parser_add.add_argument('recipename', help='Name of recipe to update')
602 parser_add.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')
545 parser_add.add_argument('--initial-rev', help='Starting revision for patches') 603 parser_add.add_argument('--initial-rev', help='Starting revision for patches')
546 parser_add.add_argument('--no-remove', '-n', action="store_true", help='Don\'t remove patches, only add or update') 604 parser_add.add_argument('--no-remove', '-n', action="store_true", help='Don\'t remove patches, only add or update')
547 parser_add.set_defaults(func=update_recipe) 605 parser_add.set_defaults(func=update_recipe)