summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Eggleton <paul.eggleton@linux.intel.com>2015-02-19 16:40:00 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-02-23 17:35:29 +0000
commitefedd4323b719679b3f6c050e1c7c29e2804cd10 (patch)
treeefdbc21949d9b671e63cd3bb9ca78b4f3c68e230
parent6015deb9f37d1e15bee0594252bfc474cca66902 (diff)
downloadpoky-efedd4323b719679b3f6c050e1c7c29e2804cd10.tar.gz
devtool: update-recipe: add handling for git recipes
When updating git-based recipes, in a lot of cases what you want is to push the changes to the repository and update SRCREV rather than to apply patches within the recipe. Updating SRCREV is now the default behaviour for recipes that fetch from git, but this can be overridden in both directions using a new -m/--mode option. (From OE-Core rev: 654792bb87610ee3569d02a85fa9ec071bf8ab6d) Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-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)