summaryrefslogtreecommitdiffstats
path: root/meta/lib/oe/recipeutils.py
diff options
context:
space:
mode:
authorPaul Eggleton <paul.eggleton@linux.intel.com>2015-05-18 16:15:07 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-05-20 21:41:04 +0100
commitfbfc06a969200e582a059c9943e6fd17aca70e30 (patch)
tree1862fb721f550d42f10e8867224712754a865d34 /meta/lib/oe/recipeutils.py
parentc63adf5c5b4b5984c315e914a7d3cb4b51040602 (diff)
downloadpoky-fbfc06a969200e582a059c9943e6fd17aca70e30.tar.gz
recipetool: add appendfile subcommand
Locating which recipe provides a file in an image that you want to modify and then figuring out how to bbappend the recipe in order to replace it can be a tedious process. Thus, add a new appendfile subcommand to recipetool, providing the ability to create a bbappend file to add/replace any file in the target system. Without the -r option, it will search for the recipe packaging the specified file (using pkgdata from previously built recipes). The bbappend will be created at the appropriate path within the specified layer directory (which may or may not be in your bblayers.conf) or if one already exists it will be updated appropriately. Fairly extensive oe-selftest tests are also provided. Implements [YOCTO #6447]. (From OE-Core rev: dd2aa93b3c13d2c6464ef0fda59620c7dba450bb) Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib/oe/recipeutils.py')
-rw-r--r--meta/lib/oe/recipeutils.py328
1 files changed, 327 insertions, 1 deletions
diff --git a/meta/lib/oe/recipeutils.py b/meta/lib/oe/recipeutils.py
index 0689fb0c71..f05b6c06ba 100644
--- a/meta/lib/oe/recipeutils.py
+++ b/meta/lib/oe/recipeutils.py
@@ -2,7 +2,7 @@
2# 2#
3# Some code borrowed from the OE layer index 3# Some code borrowed from the OE layer index
4# 4#
5# Copyright (C) 2013-2014 Intel Corporation 5# Copyright (C) 2013-2015 Intel Corporation
6# 6#
7 7
8import sys 8import sys
@@ -14,6 +14,7 @@ import difflib
14import utils 14import utils
15import shutil 15import shutil
16import re 16import re
17import fnmatch
17from collections import OrderedDict, defaultdict 18from collections import OrderedDict, defaultdict
18 19
19 20
@@ -289,6 +290,27 @@ def get_recipe_patches(d):
289 return patchfiles 290 return patchfiles
290 291
291 292
293def get_recipe_patched_files(d):
294 """
295 Get the list of patches for a recipe along with the files each patch modifies.
296 Params:
297 d: the datastore for the recipe
298 Returns:
299 a dict mapping patch file path to a list of tuples of changed files and
300 change mode ('A' for add, 'D' for delete or 'M' for modify)
301 """
302 import oe.patch
303 # Execute src_patches() defined in patch.bbclass - this works since that class
304 # is inherited globally
305 patches = bb.utils.exec_flat_python_func('src_patches', d)
306 patchedfiles = {}
307 for patch in patches:
308 _, _, patchfile, _, _, parm = bb.fetch.decodeurl(patch)
309 striplevel = int(parm['striplevel'])
310 patchedfiles[patchfile] = oe.patch.PatchSet.getPatchedFiles(patchfile, striplevel, os.path.join(d.getVar('S', True), parm.get('patchdir', '')))
311 return patchedfiles
312
313
292def validate_pn(pn): 314def validate_pn(pn):
293 """Perform validation on a recipe name (PN) for a new recipe.""" 315 """Perform validation on a recipe name (PN) for a new recipe."""
294 reserved_names = ['forcevariable', 'append', 'prepend', 'remove'] 316 reserved_names = ['forcevariable', 'append', 'prepend', 'remove']
@@ -300,3 +322,307 @@ def validate_pn(pn):
300 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn 322 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn
301 return '' 323 return ''
302 324
325
326def get_bbappend_path(d, destlayerdir, wildcardver=False):
327 """Determine how a bbappend for a recipe should be named and located within another layer"""
328
329 import bb.cookerdata
330
331 destlayerdir = os.path.abspath(destlayerdir)
332 recipefile = d.getVar('FILE', True)
333 recipefn = os.path.splitext(os.path.basename(recipefile))[0]
334 if wildcardver and '_' in recipefn:
335 recipefn = recipefn.split('_', 1)[0] + '_%'
336 appendfn = recipefn + '.bbappend'
337
338 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
339 confdata = d.createCopy()
340 confdata.setVar('BBFILES', '')
341 confdata.setVar('LAYERDIR', destlayerdir)
342 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
343 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
344
345 origlayerdir = find_layerdir(recipefile)
346 if not origlayerdir:
347 return (None, False)
348 # Now join this to the path where the bbappend is going and check if it is covered by BBFILES
349 appendpath = os.path.join(destlayerdir, os.path.relpath(os.path.dirname(recipefile), origlayerdir), appendfn)
350 closepath = ''
351 pathok = True
352 for bbfilespec in confdata.getVar('BBFILES', True).split():
353 if fnmatch.fnmatchcase(appendpath, bbfilespec):
354 # Our append path works, we're done
355 break
356 elif bbfilespec.startswith(destlayerdir) and fnmatch.fnmatchcase('test.bbappend', os.path.basename(bbfilespec)):
357 # Try to find the longest matching path
358 if len(bbfilespec) > len(closepath):
359 closepath = bbfilespec
360 else:
361 # Unfortunately the bbappend layer and the original recipe's layer don't have the same structure
362 if closepath:
363 # bbappend layer's layer.conf at least has a spec that picks up .bbappend files
364 # Now we just need to substitute out any wildcards
365 appendsubdir = os.path.relpath(os.path.dirname(closepath), destlayerdir)
366 if 'recipes-*' in appendsubdir:
367 # Try to copy this part from the original recipe path
368 res = re.search('/recipes-[^/]+/', recipefile)
369 if res:
370 appendsubdir = appendsubdir.replace('/recipes-*/', res.group(0))
371 # This is crude, but we have to do something
372 appendsubdir = appendsubdir.replace('*', recipefn.split('_')[0])
373 appendsubdir = appendsubdir.replace('?', 'a')
374 appendpath = os.path.join(destlayerdir, appendsubdir, appendfn)
375 else:
376 pathok = False
377 return (appendpath, pathok)
378
379
380def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, machine=None, extralines=None, removevalues=None):
381 """
382 Writes a bbappend file for a recipe
383 Parameters:
384 rd: data dictionary for the recipe
385 destlayerdir: base directory of the layer to place the bbappend in
386 (subdirectory path from there will be determined automatically)
387 srcfiles: dict of source files to add to SRC_URI, where the value
388 is the full path to the file to be added, and the value is the
389 original filename as it would appear in SRC_URI or None if it
390 isn't already present. You may pass None for this parameter if
391 you simply want to specify your own content via the extralines
392 parameter.
393 install: dict mapping entries in srcfiles to a tuple of two elements:
394 install path (*without* ${D} prefix) and permission value (as a
395 string, e.g. '0644').
396 wildcardver: True to use a % wildcard in the bbappend filename, or
397 False to make the bbappend specific to the recipe version.
398 machine:
399 If specified, make the changes in the bbappend specific to this
400 machine. This will also cause PACKAGE_ARCH = "${MACHINE_ARCH}"
401 to be added to the bbappend.
402 extralines:
403 Extra lines to add to the bbappend. This may be a dict of name
404 value pairs, or simply a list of the lines.
405 removevalues:
406 Variable values to remove - a dict of names/values.
407 """
408
409 if not removevalues:
410 removevalues = {}
411
412 # Determine how the bbappend should be named
413 appendpath, pathok = get_bbappend_path(rd, destlayerdir, wildcardver)
414 if not appendpath:
415 bb.error('Unable to determine layer directory containing %s' % recipefile)
416 return (None, None)
417 if not pathok:
418 bb.warn('Unable to determine correct subdirectory path for bbappend file - check that what %s adds to BBFILES also matches .bbappend files. Using %s for now, but until you fix this the bbappend will not be applied.' % (os.path.join(destlayerdir, 'conf', 'layer.conf'), os.path.dirname(appendpath)))
419
420 appenddir = os.path.dirname(appendpath)
421 bb.utils.mkdirhier(appenddir)
422
423 # FIXME check if the bbappend doesn't get overridden by a higher priority layer?
424
425 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS', True).split()]
426 if not os.path.abspath(destlayerdir) in layerdirs:
427 bb.warn('Specified layer is not currently enabled in bblayers.conf, you will need to add it before this bbappend will be active')
428
429 bbappendlines = []
430 if extralines:
431 if isinstance(extralines, dict):
432 for name, value in extralines.iteritems():
433 bbappendlines.append((name, '=', value))
434 else:
435 # Do our best to split it
436 for line in extralines:
437 if line[-1] == '\n':
438 line = line[:-1]
439 splitline = line.split(maxsplit=2)
440 if len(splitline) == 3:
441 bbappendlines.append(tuple(splitline))
442 else:
443 raise Exception('Invalid extralines value passed')
444
445 def popline(varname):
446 for i in xrange(0, len(bbappendlines)):
447 if bbappendlines[i][0] == varname:
448 line = bbappendlines.pop(i)
449 return line
450 return None
451
452 def appendline(varname, op, value):
453 for i in xrange(0, len(bbappendlines)):
454 item = bbappendlines[i]
455 if item[0] == varname:
456 bbappendlines[i] = (item[0], item[1], item[2] + ' ' + value)
457 break
458 else:
459 bbappendlines.append((varname, op, value))
460
461 destsubdir = rd.getVar('PN', True)
462 if srcfiles:
463 bbappendlines.append(('FILESEXTRAPATHS_prepend', ':=', '${THISDIR}/${PN}:'))
464
465 appendoverride = ''
466 if machine:
467 bbappendlines.append(('PACKAGE_ARCH', '=', '${MACHINE_ARCH}'))
468 appendoverride = '_%s' % machine
469 copyfiles = {}
470 if srcfiles:
471 instfunclines = []
472 for newfile, origsrcfile in srcfiles.iteritems():
473 srcfile = origsrcfile
474 srcurientry = None
475 if not srcfile:
476 srcfile = os.path.basename(newfile)
477 srcurientry = 'file://%s' % srcfile
478 # Double-check it's not there already
479 # FIXME do we care if the entry is added by another bbappend that might go away?
480 if not srcurientry in rd.getVar('SRC_URI', True).split():
481 if machine:
482 appendline('SRC_URI_append%s' % appendoverride, '=', ' ' + srcurientry)
483 else:
484 appendline('SRC_URI', '+=', srcurientry)
485 copyfiles[newfile] = srcfile
486 if install:
487 institem = install.pop(newfile, None)
488 if institem:
489 (destpath, perms) = institem
490 instdestpath = replace_dir_vars(destpath, rd)
491 instdirline = 'install -d ${D}%s' % os.path.dirname(instdestpath)
492 if not instdirline in instfunclines:
493 instfunclines.append(instdirline)
494 instfunclines.append('install -m %s ${WORKDIR}/%s ${D}%s' % (perms, os.path.basename(srcfile), instdestpath))
495 if instfunclines:
496 bbappendlines.append(('do_install_append%s()' % appendoverride, '', instfunclines))
497
498 bb.note('Writing append file %s' % appendpath)
499
500 if os.path.exists(appendpath):
501 # Work around lack of nonlocal in python 2
502 extvars = {'destsubdir': destsubdir}
503
504 def appendfile_varfunc(varname, origvalue, op, newlines):
505 if varname == 'FILESEXTRAPATHS_prepend':
506 if origvalue.startswith('${THISDIR}/'):
507 popline('FILESEXTRAPATHS_prepend')
508 extvars['destsubdir'] = rd.expand(origvalue.split('${THISDIR}/', 1)[1].rstrip(':'))
509 elif varname == 'PACKAGE_ARCH':
510 if machine:
511 popline('PACKAGE_ARCH')
512 return (machine, None, 4, False)
513 elif varname.startswith('do_install_append'):
514 func = popline(varname)
515 if func:
516 instfunclines = [line.strip() for line in origvalue.strip('\n').splitlines()]
517 for line in func[2]:
518 if not line in instfunclines:
519 instfunclines.append(line)
520 return (instfunclines, None, 4, False)
521 else:
522 splitval = origvalue.split()
523 changed = False
524 removevar = varname
525 if varname in ['SRC_URI', 'SRC_URI_append%s' % appendoverride]:
526 removevar = 'SRC_URI'
527 line = popline(varname)
528 if line:
529 if line[2] not in splitval:
530 splitval.append(line[2])
531 changed = True
532 else:
533 line = popline(varname)
534 if line:
535 splitval = [line[2]]
536 changed = True
537
538 if removevar in removevalues:
539 remove = removevalues[removevar]
540 if isinstance(remove, basestring):
541 if remove in splitval:
542 splitval.remove(remove)
543 changed = True
544 else:
545 for removeitem in remove:
546 if removeitem in splitval:
547 splitval.remove(removeitem)
548 changed = True
549
550 if changed:
551 newvalue = splitval
552 if len(newvalue) == 1:
553 # Ensure it's written out as one line
554 if '_append' in varname:
555 newvalue = ' ' + newvalue[0]
556 else:
557 newvalue = newvalue[0]
558 if not newvalue and (op in ['+=', '.='] or '_append' in varname):
559 # There's no point appending nothing
560 newvalue = None
561 if varname.endswith('()'):
562 indent = 4
563 else:
564 indent = -1
565 return (newvalue, None, indent, True)
566 return (origvalue, None, 4, False)
567
568 varnames = [item[0] for item in bbappendlines]
569 if removevalues:
570 varnames.extend(removevalues.keys())
571
572 with open(appendpath, 'r') as f:
573 (updated, newlines) = bb.utils.edit_metadata(f, varnames, appendfile_varfunc)
574
575 destsubdir = extvars['destsubdir']
576 else:
577 updated = False
578 newlines = []
579
580 if bbappendlines:
581 for line in bbappendlines:
582 if line[0].endswith('()'):
583 newlines.append('%s {\n %s\n}\n' % (line[0], '\n '.join(line[2])))
584 else:
585 newlines.append('%s %s "%s"\n\n' % line)
586 updated = True
587
588 if updated:
589 with open(appendpath, 'w') as f:
590 f.writelines(newlines)
591
592 if copyfiles:
593 if machine:
594 destsubdir = os.path.join(destsubdir, machine)
595 for newfile, srcfile in copyfiles.iteritems():
596 filedest = os.path.join(appenddir, destsubdir, os.path.basename(srcfile))
597 if os.path.abspath(newfile) != os.path.abspath(filedest):
598 bb.note('Copying %s to %s' % (newfile, filedest))
599 bb.utils.mkdirhier(os.path.dirname(filedest))
600 shutil.copyfile(newfile, filedest)
601
602 return (appendpath, os.path.join(appenddir, destsubdir))
603
604
605def find_layerdir(fn):
606 """ Figure out relative path to base of layer for a file (e.g. a recipe)"""
607 pth = os.path.dirname(fn)
608 layerdir = ''
609 while pth:
610 if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')):
611 layerdir = pth
612 break
613 pth = os.path.dirname(pth)
614 return layerdir
615
616
617def replace_dir_vars(path, d):
618 """Replace common directory paths with appropriate variable references (e.g. /etc becomes ${sysconfdir})"""
619 dirvars = {}
620 for var in d:
621 if var.endswith('dir') and var.lower() == var:
622 value = d.getVar(var, True)
623 if value.startswith('/') and not '\n' in value:
624 dirvars[value] = var
625 for dirpath in sorted(dirvars.keys(), reverse=True):
626 path = path.replace(dirpath, '${%s}' % dirvars[dirpath])
627 return path
628