blob: 119a68821baba5a8aa6bf9e39ec7b1c11c65c282 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Utility functions for reading and modifying recipes
2#
3# Some code borrowed from the OE layer index
4#
5# Copyright (C) 2013-2015 Intel Corporation
6#
7
8import sys
9import os
10import os.path
11import tempfile
12import textwrap
13import difflib
14import utils
15import shutil
16import re
17import fnmatch
18from collections import OrderedDict, defaultdict
19
20
21# Help us to find places to insert values
22recipe_progression = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION', 'LICENSE', 'LIC_FILES_CHKSUM', 'PROVIDES', 'DEPENDS', 'PR', 'PV', 'SRCREV', 'SRC_URI', 'S', 'do_fetch', 'do_unpack', 'do_patch', 'EXTRA_OECONF', 'do_configure', 'EXTRA_OEMAKE', 'do_compile', 'do_install', 'do_populate_sysroot', 'INITSCRIPT', 'USERADD', 'GROUPADD', 'PACKAGES', 'FILES', 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RPROVIDES', 'RREPLACES', 'RCONFLICTS', 'ALLOW_EMPTY', 'do_package', 'do_deploy']
23# Variables that sometimes are a bit long but shouldn't be wrapped
24nowrap_vars = ['SUMMARY', 'HOMEPAGE', 'BUGTRACKER']
25list_vars = ['SRC_URI', 'LIC_FILES_CHKSUM']
26meta_vars = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION']
27
28
29def pn_to_recipe(cooker, pn):
30 """Convert a recipe name (PN) to the path to the recipe file"""
31 import bb.providers
32
33 if pn in cooker.recipecache.pkg_pn:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034 best = bb.providers.findBestProvider(pn, cooker.data, cooker.recipecache, cooker.recipecache.pkg_pn)
35 return best[3]
Patrick Williamsf1e5d692016-03-30 15:21:19 -050036 elif pn in cooker.recipecache.providers:
37 filenames = cooker.recipecache.providers[pn]
38 eligible, foundUnique = bb.providers.filterProviders(filenames, pn, cooker.expanded_data, cooker.recipecache)
39 filename = eligible[0]
40 return filename
Patrick Williamsc124f4f2015-09-15 14:41:29 -050041 else:
42 return None
43
44
45def get_unavailable_reasons(cooker, pn):
46 """If a recipe could not be found, find out why if possible"""
47 import bb.taskdata
48 taskdata = bb.taskdata.TaskData(None, skiplist=cooker.skiplist)
49 return taskdata.get_reasons(pn)
50
51
52def parse_recipe(fn, appendfiles, d):
53 """
54 Parse an individual recipe file, optionally with a list of
55 bbappend files.
56 """
57 import bb.cache
58 envdata = bb.cache.Cache.loadDataFull(fn, appendfiles, d)
59 return envdata
60
61
62def parse_recipe_simple(cooker, pn, d, appends=True):
63 """
64 Parse a recipe and optionally all bbappends that apply to it
65 in the current configuration.
66 """
67 import bb.providers
68
69 recipefile = pn_to_recipe(cooker, pn)
70 if not recipefile:
71 skipreasons = get_unavailable_reasons(cooker, pn)
72 # We may as well re-use bb.providers.NoProvider here
73 if skipreasons:
74 raise bb.providers.NoProvider(skipreasons)
75 else:
76 raise bb.providers.NoProvider('Unable to find any recipe file matching %s' % pn)
77 if appends:
78 appendfiles = cooker.collection.get_file_appends(recipefile)
Patrick Williamsf1e5d692016-03-30 15:21:19 -050079 else:
80 appendfiles = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -050081 return parse_recipe(recipefile, appendfiles, d)
82
83
84def get_var_files(fn, varlist, d):
85 """Find the file in which each of a list of variables is set.
86 Note: requires variable history to be enabled when parsing.
87 """
88 varfiles = {}
89 for v in varlist:
90 history = d.varhistory.variable(v)
91 files = []
92 for event in history:
93 if 'file' in event and not 'flag' in event:
94 files.append(event['file'])
95 if files:
96 actualfile = files[-1]
97 else:
98 actualfile = None
99 varfiles[v] = actualfile
100
101 return varfiles
102
103
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500104def split_var_value(value, assignment=True):
105 """
106 Split a space-separated variable's value into a list of items,
107 taking into account that some of the items might be made up of
108 expressions containing spaces that should not be split.
109 Parameters:
110 value:
111 The string value to split
112 assignment:
113 True to assume that the value represents an assignment
114 statement, False otherwise. If True, and an assignment
115 statement is passed in the first item in
116 the returned list will be the part of the assignment
117 statement up to and including the opening quote character,
118 and the last item will be the closing quote.
119 """
120 inexpr = 0
121 lastchar = None
122 out = []
123 buf = ''
124 for char in value:
125 if char == '{':
126 if lastchar == '$':
127 inexpr += 1
128 elif char == '}':
129 inexpr -= 1
130 elif assignment and char in '"\'' and inexpr == 0:
131 if buf:
132 out.append(buf)
133 out.append(char)
134 char = ''
135 buf = ''
136 elif char.isspace() and inexpr == 0:
137 char = ''
138 if buf:
139 out.append(buf)
140 buf = ''
141 buf += char
142 lastchar = char
143 if buf:
144 out.append(buf)
145
146 # Join together assignment statement and opening quote
147 outlist = out
148 if assignment:
149 assigfound = False
150 for idx, item in enumerate(out):
151 if '=' in item:
152 assigfound = True
153 if assigfound:
154 if '"' in item or "'" in item:
155 outlist = [' '.join(out[:idx+1])]
156 outlist.extend(out[idx+1:])
157 break
158 return outlist
159
160
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500161def patch_recipe_file(fn, values, patch=False, relpath=''):
162 """Update or insert variable values into a recipe file (assuming you
163 have already identified the exact file you want to update.)
164 Note that some manual inspection/intervention may be required
165 since this cannot handle all situations.
166 """
167 remainingnames = {}
168 for k in values.keys():
169 remainingnames[k] = recipe_progression.index(k) if k in recipe_progression else -1
170 remainingnames = OrderedDict(sorted(remainingnames.iteritems(), key=lambda x: x[1]))
171
172 with tempfile.NamedTemporaryFile('w', delete=False) as tf:
173 def outputvalue(name):
174 rawtext = '%s = "%s"\n' % (name, values[name])
175 if name in nowrap_vars:
176 tf.write(rawtext)
177 elif name in list_vars:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500178 splitvalue = split_var_value(values[name], assignment=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500179 if len(splitvalue) > 1:
180 linesplit = ' \\\n' + (' ' * (len(name) + 4))
181 tf.write('%s = "%s%s"\n' % (name, linesplit.join(splitvalue), linesplit))
182 else:
183 tf.write(rawtext)
184 else:
185 wrapped = textwrap.wrap(rawtext)
186 for wrapline in wrapped[:-1]:
187 tf.write('%s \\\n' % wrapline)
188 tf.write('%s\n' % wrapped[-1])
189
190 tfn = tf.name
191 with open(fn, 'r') as f:
192 # First runthrough - find existing names (so we know not to insert based on recipe_progression)
193 # Second runthrough - make the changes
194 existingnames = []
195 for runthrough in [1, 2]:
196 currname = None
197 for line in f:
198 if not currname:
199 insert = False
200 for k in remainingnames.keys():
201 for p in recipe_progression:
202 if re.match('^%s(_prepend|_append)*[ ?:=(]' % p, line):
203 if remainingnames[k] > -1 and recipe_progression.index(p) > remainingnames[k] and runthrough > 1 and not k in existingnames:
204 outputvalue(k)
205 del remainingnames[k]
206 break
207 for k in remainingnames.keys():
208 if re.match('^%s[ ?:=]' % k, line):
209 currname = k
210 if runthrough == 1:
211 existingnames.append(k)
212 else:
213 del remainingnames[k]
214 break
215 if currname and runthrough > 1:
216 outputvalue(currname)
217
218 if currname:
219 sline = line.rstrip()
220 if not sline.endswith('\\'):
221 currname = None
222 continue
223 if runthrough > 1:
224 tf.write(line)
225 f.seek(0)
226 if remainingnames:
227 tf.write('\n')
228 for k in remainingnames.keys():
229 outputvalue(k)
230
231 with open(tfn, 'U') as f:
232 tolines = f.readlines()
233 if patch:
234 with open(fn, 'U') as f:
235 fromlines = f.readlines()
236 relfn = os.path.relpath(fn, relpath)
237 diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn)
238 os.remove(tfn)
239 return diff
240 else:
241 with open(fn, 'w') as f:
242 f.writelines(tolines)
243 os.remove(tfn)
244 return None
245
246def localise_file_vars(fn, varfiles, varlist):
247 """Given a list of variables and variable history (fetched with get_var_files())
248 find where each variable should be set/changed. This handles for example where a
249 recipe includes an inc file where variables might be changed - in most cases
250 we want to update the inc file when changing the variable value rather than adding
251 it to the recipe itself.
252 """
253 fndir = os.path.dirname(fn) + os.sep
254
255 first_meta_file = None
256 for v in meta_vars:
257 f = varfiles.get(v, None)
258 if f:
259 actualdir = os.path.dirname(f) + os.sep
260 if actualdir.startswith(fndir):
261 first_meta_file = f
262 break
263
264 filevars = defaultdict(list)
265 for v in varlist:
266 f = varfiles[v]
267 # Only return files that are in the same directory as the recipe or in some directory below there
268 # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable
269 # in if we were going to set a value specific to this recipe)
270 if f:
271 actualfile = f
272 else:
273 # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it
274 if first_meta_file:
275 actualfile = first_meta_file
276 else:
277 actualfile = fn
278
279 actualdir = os.path.dirname(actualfile) + os.sep
280 if not actualdir.startswith(fndir):
281 actualfile = fn
282 filevars[actualfile].append(v)
283
284 return filevars
285
286def patch_recipe(d, fn, varvalues, patch=False, relpath=''):
287 """Modify a list of variable values in the specified recipe. Handles inc files if
288 used by the recipe.
289 """
290 varlist = varvalues.keys()
291 varfiles = get_var_files(fn, varlist, d)
292 locs = localise_file_vars(fn, varfiles, varlist)
293 patches = []
294 for f,v in locs.iteritems():
295 vals = {k: varvalues[k] for k in v}
296 patchdata = patch_recipe_file(f, vals, patch, relpath)
297 if patch:
298 patches.append(patchdata)
299
300 if patch:
301 return patches
302 else:
303 return None
304
305
306
307def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True):
308 """Copy (local) recipe files, including both files included via include/require,
309 and files referred to in the SRC_URI variable."""
310 import bb.fetch2
311 import oe.path
312
313 # FIXME need a warning if the unexpanded SRC_URI value contains variable references
314
315 uris = (d.getVar('SRC_URI', True) or "").split()
316 fetch = bb.fetch2.Fetch(uris, d)
317 if download:
318 fetch.download()
319
320 # Copy local files to target directory and gather any remote files
321 bb_dir = os.path.dirname(d.getVar('FILE', True)) + os.sep
322 remotes = []
323 includes = [path for path in d.getVar('BBINCLUDED', True).split() if
324 path.startswith(bb_dir) and os.path.exists(path)]
325 for path in fetch.localpaths() + includes:
326 # Only import files that are under the meta directory
327 if path.startswith(bb_dir):
328 if not whole_dir:
329 relpath = os.path.relpath(path, bb_dir)
330 subdir = os.path.join(tgt_dir, os.path.dirname(relpath))
331 if not os.path.exists(subdir):
332 os.makedirs(subdir)
333 shutil.copy2(path, os.path.join(tgt_dir, relpath))
334 else:
335 remotes.append(path)
336 # Simply copy whole meta dir, if requested
337 if whole_dir:
338 shutil.copytree(bb_dir, tgt_dir)
339
340 return remotes
341
342
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500343def get_recipe_local_files(d, patches=False):
344 """Get a list of local files in SRC_URI within a recipe."""
345 uris = (d.getVar('SRC_URI', True) or "").split()
346 fetch = bb.fetch2.Fetch(uris, d)
347 ret = {}
348 for uri in uris:
349 if fetch.ud[uri].type == 'file':
350 if (not patches and
351 bb.utils.exec_flat_python_func('patch_path', uri, fetch, '')):
352 continue
353 # Skip files that are referenced by absolute path
354 if not os.path.isabs(fetch.ud[uri].basepath):
355 ret[fetch.ud[uri].basepath] = fetch.localpath(uri)
356 return ret
357
358
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500359def get_recipe_patches(d):
360 """Get a list of the patches included in SRC_URI within a recipe."""
361 patchfiles = []
362 # Execute src_patches() defined in patch.bbclass - this works since that class
363 # is inherited globally
364 patches = bb.utils.exec_flat_python_func('src_patches', d)
365 for patch in patches:
366 _, _, local, _, _, parm = bb.fetch.decodeurl(patch)
367 patchfiles.append(local)
368 return patchfiles
369
370
371def get_recipe_patched_files(d):
372 """
373 Get the list of patches for a recipe along with the files each patch modifies.
374 Params:
375 d: the datastore for the recipe
376 Returns:
377 a dict mapping patch file path to a list of tuples of changed files and
378 change mode ('A' for add, 'D' for delete or 'M' for modify)
379 """
380 import oe.patch
381 # Execute src_patches() defined in patch.bbclass - this works since that class
382 # is inherited globally
383 patches = bb.utils.exec_flat_python_func('src_patches', d)
384 patchedfiles = {}
385 for patch in patches:
386 _, _, patchfile, _, _, parm = bb.fetch.decodeurl(patch)
387 striplevel = int(parm['striplevel'])
388 patchedfiles[patchfile] = oe.patch.PatchSet.getPatchedFiles(patchfile, striplevel, os.path.join(d.getVar('S', True), parm.get('patchdir', '')))
389 return patchedfiles
390
391
392def validate_pn(pn):
393 """Perform validation on a recipe name (PN) for a new recipe."""
394 reserved_names = ['forcevariable', 'append', 'prepend', 'remove']
395 if not re.match('[0-9a-z-.]+', pn):
396 return 'Recipe name "%s" is invalid: only characters 0-9, a-z, - and . are allowed' % pn
397 elif pn in reserved_names:
398 return 'Recipe name "%s" is invalid: is a reserved keyword' % pn
399 elif pn.startswith('pn-'):
400 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn
401 return ''
402
403
404def get_bbappend_path(d, destlayerdir, wildcardver=False):
405 """Determine how a bbappend for a recipe should be named and located within another layer"""
406
407 import bb.cookerdata
408
409 destlayerdir = os.path.abspath(destlayerdir)
410 recipefile = d.getVar('FILE', True)
411 recipefn = os.path.splitext(os.path.basename(recipefile))[0]
412 if wildcardver and '_' in recipefn:
413 recipefn = recipefn.split('_', 1)[0] + '_%'
414 appendfn = recipefn + '.bbappend'
415
416 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
417 confdata = d.createCopy()
418 confdata.setVar('BBFILES', '')
419 confdata.setVar('LAYERDIR', destlayerdir)
420 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
421 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
422
423 origlayerdir = find_layerdir(recipefile)
424 if not origlayerdir:
425 return (None, False)
426 # Now join this to the path where the bbappend is going and check if it is covered by BBFILES
427 appendpath = os.path.join(destlayerdir, os.path.relpath(os.path.dirname(recipefile), origlayerdir), appendfn)
428 closepath = ''
429 pathok = True
430 for bbfilespec in confdata.getVar('BBFILES', True).split():
431 if fnmatch.fnmatchcase(appendpath, bbfilespec):
432 # Our append path works, we're done
433 break
434 elif bbfilespec.startswith(destlayerdir) and fnmatch.fnmatchcase('test.bbappend', os.path.basename(bbfilespec)):
435 # Try to find the longest matching path
436 if len(bbfilespec) > len(closepath):
437 closepath = bbfilespec
438 else:
439 # Unfortunately the bbappend layer and the original recipe's layer don't have the same structure
440 if closepath:
441 # bbappend layer's layer.conf at least has a spec that picks up .bbappend files
442 # Now we just need to substitute out any wildcards
443 appendsubdir = os.path.relpath(os.path.dirname(closepath), destlayerdir)
444 if 'recipes-*' in appendsubdir:
445 # Try to copy this part from the original recipe path
446 res = re.search('/recipes-[^/]+/', recipefile)
447 if res:
448 appendsubdir = appendsubdir.replace('/recipes-*/', res.group(0))
449 # This is crude, but we have to do something
450 appendsubdir = appendsubdir.replace('*', recipefn.split('_')[0])
451 appendsubdir = appendsubdir.replace('?', 'a')
452 appendpath = os.path.join(destlayerdir, appendsubdir, appendfn)
453 else:
454 pathok = False
455 return (appendpath, pathok)
456
457
458def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, machine=None, extralines=None, removevalues=None):
459 """
460 Writes a bbappend file for a recipe
461 Parameters:
462 rd: data dictionary for the recipe
463 destlayerdir: base directory of the layer to place the bbappend in
464 (subdirectory path from there will be determined automatically)
465 srcfiles: dict of source files to add to SRC_URI, where the value
466 is the full path to the file to be added, and the value is the
467 original filename as it would appear in SRC_URI or None if it
468 isn't already present. You may pass None for this parameter if
469 you simply want to specify your own content via the extralines
470 parameter.
471 install: dict mapping entries in srcfiles to a tuple of two elements:
472 install path (*without* ${D} prefix) and permission value (as a
473 string, e.g. '0644').
474 wildcardver: True to use a % wildcard in the bbappend filename, or
475 False to make the bbappend specific to the recipe version.
476 machine:
477 If specified, make the changes in the bbappend specific to this
478 machine. This will also cause PACKAGE_ARCH = "${MACHINE_ARCH}"
479 to be added to the bbappend.
480 extralines:
481 Extra lines to add to the bbappend. This may be a dict of name
482 value pairs, or simply a list of the lines.
483 removevalues:
484 Variable values to remove - a dict of names/values.
485 """
486
487 if not removevalues:
488 removevalues = {}
489
490 # Determine how the bbappend should be named
491 appendpath, pathok = get_bbappend_path(rd, destlayerdir, wildcardver)
492 if not appendpath:
493 bb.error('Unable to determine layer directory containing %s' % recipefile)
494 return (None, None)
495 if not pathok:
496 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)))
497
498 appenddir = os.path.dirname(appendpath)
499 bb.utils.mkdirhier(appenddir)
500
501 # FIXME check if the bbappend doesn't get overridden by a higher priority layer?
502
503 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS', True).split()]
504 if not os.path.abspath(destlayerdir) in layerdirs:
505 bb.warn('Specified layer is not currently enabled in bblayers.conf, you will need to add it before this bbappend will be active')
506
507 bbappendlines = []
508 if extralines:
509 if isinstance(extralines, dict):
510 for name, value in extralines.iteritems():
511 bbappendlines.append((name, '=', value))
512 else:
513 # Do our best to split it
514 for line in extralines:
515 if line[-1] == '\n':
516 line = line[:-1]
517 splitline = line.split(None, 2)
518 if len(splitline) == 3:
519 bbappendlines.append(tuple(splitline))
520 else:
521 raise Exception('Invalid extralines value passed')
522
523 def popline(varname):
524 for i in xrange(0, len(bbappendlines)):
525 if bbappendlines[i][0] == varname:
526 line = bbappendlines.pop(i)
527 return line
528 return None
529
530 def appendline(varname, op, value):
531 for i in xrange(0, len(bbappendlines)):
532 item = bbappendlines[i]
533 if item[0] == varname:
534 bbappendlines[i] = (item[0], item[1], item[2] + ' ' + value)
535 break
536 else:
537 bbappendlines.append((varname, op, value))
538
539 destsubdir = rd.getVar('PN', True)
540 if srcfiles:
541 bbappendlines.append(('FILESEXTRAPATHS_prepend', ':=', '${THISDIR}/${PN}:'))
542
543 appendoverride = ''
544 if machine:
545 bbappendlines.append(('PACKAGE_ARCH', '=', '${MACHINE_ARCH}'))
546 appendoverride = '_%s' % machine
547 copyfiles = {}
548 if srcfiles:
549 instfunclines = []
550 for newfile, origsrcfile in srcfiles.iteritems():
551 srcfile = origsrcfile
552 srcurientry = None
553 if not srcfile:
554 srcfile = os.path.basename(newfile)
555 srcurientry = 'file://%s' % srcfile
556 # Double-check it's not there already
557 # FIXME do we care if the entry is added by another bbappend that might go away?
558 if not srcurientry in rd.getVar('SRC_URI', True).split():
559 if machine:
560 appendline('SRC_URI_append%s' % appendoverride, '=', ' ' + srcurientry)
561 else:
562 appendline('SRC_URI', '+=', srcurientry)
563 copyfiles[newfile] = srcfile
564 if install:
565 institem = install.pop(newfile, None)
566 if institem:
567 (destpath, perms) = institem
568 instdestpath = replace_dir_vars(destpath, rd)
569 instdirline = 'install -d ${D}%s' % os.path.dirname(instdestpath)
570 if not instdirline in instfunclines:
571 instfunclines.append(instdirline)
572 instfunclines.append('install -m %s ${WORKDIR}/%s ${D}%s' % (perms, os.path.basename(srcfile), instdestpath))
573 if instfunclines:
574 bbappendlines.append(('do_install_append%s()' % appendoverride, '', instfunclines))
575
576 bb.note('Writing append file %s' % appendpath)
577
578 if os.path.exists(appendpath):
579 # Work around lack of nonlocal in python 2
580 extvars = {'destsubdir': destsubdir}
581
582 def appendfile_varfunc(varname, origvalue, op, newlines):
583 if varname == 'FILESEXTRAPATHS_prepend':
584 if origvalue.startswith('${THISDIR}/'):
585 popline('FILESEXTRAPATHS_prepend')
586 extvars['destsubdir'] = rd.expand(origvalue.split('${THISDIR}/', 1)[1].rstrip(':'))
587 elif varname == 'PACKAGE_ARCH':
588 if machine:
589 popline('PACKAGE_ARCH')
590 return (machine, None, 4, False)
591 elif varname.startswith('do_install_append'):
592 func = popline(varname)
593 if func:
594 instfunclines = [line.strip() for line in origvalue.strip('\n').splitlines()]
595 for line in func[2]:
596 if not line in instfunclines:
597 instfunclines.append(line)
598 return (instfunclines, None, 4, False)
599 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500600 splitval = split_var_value(origvalue, assignment=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500601 changed = False
602 removevar = varname
603 if varname in ['SRC_URI', 'SRC_URI_append%s' % appendoverride]:
604 removevar = 'SRC_URI'
605 line = popline(varname)
606 if line:
607 if line[2] not in splitval:
608 splitval.append(line[2])
609 changed = True
610 else:
611 line = popline(varname)
612 if line:
613 splitval = [line[2]]
614 changed = True
615
616 if removevar in removevalues:
617 remove = removevalues[removevar]
618 if isinstance(remove, basestring):
619 if remove in splitval:
620 splitval.remove(remove)
621 changed = True
622 else:
623 for removeitem in remove:
624 if removeitem in splitval:
625 splitval.remove(removeitem)
626 changed = True
627
628 if changed:
629 newvalue = splitval
630 if len(newvalue) == 1:
631 # Ensure it's written out as one line
632 if '_append' in varname:
633 newvalue = ' ' + newvalue[0]
634 else:
635 newvalue = newvalue[0]
636 if not newvalue and (op in ['+=', '.='] or '_append' in varname):
637 # There's no point appending nothing
638 newvalue = None
639 if varname.endswith('()'):
640 indent = 4
641 else:
642 indent = -1
643 return (newvalue, None, indent, True)
644 return (origvalue, None, 4, False)
645
646 varnames = [item[0] for item in bbappendlines]
647 if removevalues:
648 varnames.extend(removevalues.keys())
649
650 with open(appendpath, 'r') as f:
651 (updated, newlines) = bb.utils.edit_metadata(f, varnames, appendfile_varfunc)
652
653 destsubdir = extvars['destsubdir']
654 else:
655 updated = False
656 newlines = []
657
658 if bbappendlines:
659 for line in bbappendlines:
660 if line[0].endswith('()'):
661 newlines.append('%s {\n %s\n}\n' % (line[0], '\n '.join(line[2])))
662 else:
663 newlines.append('%s %s "%s"\n\n' % line)
664 updated = True
665
666 if updated:
667 with open(appendpath, 'w') as f:
668 f.writelines(newlines)
669
670 if copyfiles:
671 if machine:
672 destsubdir = os.path.join(destsubdir, machine)
673 for newfile, srcfile in copyfiles.iteritems():
674 filedest = os.path.join(appenddir, destsubdir, os.path.basename(srcfile))
675 if os.path.abspath(newfile) != os.path.abspath(filedest):
676 bb.note('Copying %s to %s' % (newfile, filedest))
677 bb.utils.mkdirhier(os.path.dirname(filedest))
678 shutil.copyfile(newfile, filedest)
679
680 return (appendpath, os.path.join(appenddir, destsubdir))
681
682
683def find_layerdir(fn):
684 """ Figure out relative path to base of layer for a file (e.g. a recipe)"""
685 pth = os.path.dirname(fn)
686 layerdir = ''
687 while pth:
688 if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')):
689 layerdir = pth
690 break
691 pth = os.path.dirname(pth)
692 return layerdir
693
694
695def replace_dir_vars(path, d):
696 """Replace common directory paths with appropriate variable references (e.g. /etc becomes ${sysconfdir})"""
697 dirvars = {}
698 # Sort by length so we get the variables we're interested in first
699 for var in sorted(d.keys(), key=len):
700 if var.endswith('dir') and var.lower() == var:
701 value = d.getVar(var, True)
702 if value.startswith('/') and not '\n' in value and value not in dirvars:
703 dirvars[value] = var
704 for dirpath in sorted(dirvars.keys(), reverse=True):
705 path = path.replace(dirpath, '${%s}' % dirvars[dirpath])
706 return path
707
708def get_recipe_pv_without_srcpv(pv, uri_type):
709 """
710 Get PV without SRCPV common in SCM's for now only
711 support git.
712
713 Returns tuple with pv, prefix and suffix.
714 """
715 pfx = ''
716 sfx = ''
717
718 if uri_type == 'git':
719 git_regex = re.compile("(?P<pfx>v?)(?P<ver>[^\+]*)((?P<sfx>\+(git)?r?(AUTOINC\+))(?P<rev>.*))?")
720 m = git_regex.match(pv)
721
722 if m:
723 pv = m.group('ver')
724 pfx = m.group('pfx')
725 sfx = m.group('sfx')
726 else:
727 regex = re.compile("(?P<pfx>(v|r)?)(?P<ver>.*)")
728 m = regex.match(pv)
729 if m:
730 pv = m.group('ver')
731 pfx = m.group('pfx')
732
733 return (pv, pfx, sfx)
734
735def get_recipe_upstream_version(rd):
736 """
737 Get upstream version of recipe using bb.fetch2 methods with support for
738 http, https, ftp and git.
739
740 bb.fetch2 exceptions can be raised,
741 FetchError when don't have network access or upstream site don't response.
742 NoMethodError when uri latest_versionstring method isn't implemented.
743
744 Returns a dictonary with version, type and datetime.
745 Type can be A for Automatic, M for Manual and U for Unknown.
746 """
747 from bb.fetch2 import decodeurl
748 from datetime import datetime
749
750 ru = {}
751 ru['version'] = ''
752 ru['type'] = 'U'
753 ru['datetime'] = ''
754
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500755 pv = rd.getVar('PV', True)
756
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500757 # XXX: If don't have SRC_URI means that don't have upstream sources so
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500758 # returns the current recipe version, so that upstream version check
759 # declares a match.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500760 src_uris = rd.getVar('SRC_URI', True)
761 if not src_uris:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500762 ru['version'] = pv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500763 ru['type'] = 'M'
764 ru['datetime'] = datetime.now()
765 return ru
766
767 # XXX: we suppose that the first entry points to the upstream sources
768 src_uri = src_uris.split()[0]
769 uri_type, _, _, _, _, _ = decodeurl(src_uri)
770
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500771 manual_upstream_version = rd.getVar("RECIPE_UPSTREAM_VERSION", True)
772 if manual_upstream_version:
773 # manual tracking of upstream version.
774 ru['version'] = manual_upstream_version
775 ru['type'] = 'M'
776
777 manual_upstream_date = rd.getVar("CHECK_DATE", True)
778 if manual_upstream_date:
779 date = datetime.strptime(manual_upstream_date, "%b %d, %Y")
780 else:
781 date = datetime.now()
782 ru['datetime'] = date
783
784 elif uri_type == "file":
785 # files are always up-to-date
786 ru['version'] = pv
787 ru['type'] = 'A'
788 ru['datetime'] = datetime.now()
789 else:
790 ud = bb.fetch2.FetchData(src_uri, rd)
791 pupver = ud.method.latest_versionstring(ud, rd)
792 (upversion, revision) = pupver
793
794 # format git version version+gitAUTOINC+HASH
795 if uri_type == 'git':
796 (pv, pfx, sfx) = get_recipe_pv_without_srcpv(pv, uri_type)
797
798 # if contains revision but not upversion use current pv
799 if upversion == '' and revision:
800 upversion = pv
801
802 if upversion:
803 tmp = upversion
804 upversion = ''
805
806 if pfx:
807 upversion = pfx + tmp
808 else:
809 upversion = tmp
810
811 if sfx:
812 upversion = upversion + sfx + revision[:10]
813
814 if upversion:
815 ru['version'] = upversion
816 ru['type'] = 'A'
817
818 ru['datetime'] = datetime.now()
819
820 return ru