blob: 6c7adb5bdbf967a69dfd8408c9e1dc08410bca11 [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
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050022recipe_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()']
Patrick Williamsc124f4f2015-09-15 14:41:29 -050023# Variables that sometimes are a bit long but shouldn't be wrapped
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050024nowrap_vars = ['SUMMARY', 'HOMEPAGE', 'BUGTRACKER', 'SRC_URI[md5sum]', 'SRC_URI[sha256sum]']
Patrick Williamsc124f4f2015-09-15 14:41:29 -050025list_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 """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500167
168 import bb.utils
169
170 recipe_progression_res = []
171 recipe_progression_restrs = []
172 for item in recipe_progression:
173 if item.endswith('()'):
174 key = item[:-2]
175 else:
176 key = item
177 restr = '%s(_[a-zA-Z0-9-_$(){}]+|\[[^\]]*\])?' % key
178 if item.endswith('()'):
179 recipe_progression_restrs.append(restr + '()')
180 else:
181 recipe_progression_restrs.append(restr)
182 recipe_progression_res.append(re.compile('^%s$' % restr))
183
184 def get_recipe_pos(variable):
185 for i, p in enumerate(recipe_progression_res):
186 if p.match(variable):
187 return i
188 return -1
189
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500190 remainingnames = {}
191 for k in values.keys():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500192 remainingnames[k] = get_recipe_pos(k)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500193 remainingnames = OrderedDict(sorted(remainingnames.iteritems(), key=lambda x: x[1]))
194
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500195 modifying = False
196
197 def outputvalue(name, lines, rewindcomments=False):
198 if values[name] is None:
199 return
200 rawtext = '%s = "%s"\n' % (name, values[name])
201 addlines = []
202 if name in nowrap_vars:
203 addlines.append(rawtext)
204 elif name in list_vars:
205 splitvalue = split_var_value(values[name], assignment=False)
206 if len(splitvalue) > 1:
207 linesplit = ' \\\n' + (' ' * (len(name) + 4))
208 addlines.append('%s = "%s%s"\n' % (name, linesplit.join(splitvalue), linesplit))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500209 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500210 addlines.append(rawtext)
211 else:
212 wrapped = textwrap.wrap(rawtext)
213 for wrapline in wrapped[:-1]:
214 addlines.append('%s \\\n' % wrapline)
215 addlines.append('%s\n' % wrapped[-1])
216 if rewindcomments:
217 # Ensure we insert the lines before any leading comments
218 # (that we'd want to ensure remain leading the next value)
219 for i, ln in reversed(list(enumerate(lines))):
220 if ln[0] != '#':
221 lines[i+1:i+1] = addlines
222 break
223 else:
224 lines.extend(addlines)
225 else:
226 lines.extend(addlines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500227
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500228 existingnames = []
229 def patch_recipe_varfunc(varname, origvalue, op, newlines):
230 if modifying:
231 # Insert anything that should come before this variable
232 pos = get_recipe_pos(varname)
233 for k in remainingnames.keys()[:]:
234 if remainingnames[k] > -1 and pos >= remainingnames[k] and not k in existingnames:
235 outputvalue(k, newlines, rewindcomments=True)
236 del remainingnames[k]
237 # Now change this variable, if it needs to be changed
238 if varname in existingnames and op in ['+=', '=', '=+']:
239 if varname in remainingnames:
240 outputvalue(varname, newlines)
241 del remainingnames[varname]
242 return None, None, 0, True
243 else:
244 if varname in values:
245 existingnames.append(varname)
246 return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500247
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500248 # First run - establish which values we want to set are already in the file
249 varlist = [re.escape(item) for item in values.keys()]
250 with open(fn, 'r') as f:
251 changed, fromlines = bb.utils.edit_metadata(f, varlist, patch_recipe_varfunc)
252 # Second run - actually set everything
253 modifying = True
254 varlist.extend(recipe_progression_restrs)
255 changed, tolines = bb.utils.edit_metadata(fromlines, varlist, patch_recipe_varfunc, match_overrides=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500256
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500257 if remainingnames:
258 if tolines[-1].strip() != '':
259 tolines.append('\n')
260 for k in remainingnames.keys():
261 outputvalue(k, tolines)
262
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500263 if patch:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500264 relfn = os.path.relpath(fn, relpath)
265 diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500266 return diff
267 else:
268 with open(fn, 'w') as f:
269 f.writelines(tolines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500270 return None
271
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500272
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500273def localise_file_vars(fn, varfiles, varlist):
274 """Given a list of variables and variable history (fetched with get_var_files())
275 find where each variable should be set/changed. This handles for example where a
276 recipe includes an inc file where variables might be changed - in most cases
277 we want to update the inc file when changing the variable value rather than adding
278 it to the recipe itself.
279 """
280 fndir = os.path.dirname(fn) + os.sep
281
282 first_meta_file = None
283 for v in meta_vars:
284 f = varfiles.get(v, None)
285 if f:
286 actualdir = os.path.dirname(f) + os.sep
287 if actualdir.startswith(fndir):
288 first_meta_file = f
289 break
290
291 filevars = defaultdict(list)
292 for v in varlist:
293 f = varfiles[v]
294 # Only return files that are in the same directory as the recipe or in some directory below there
295 # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable
296 # in if we were going to set a value specific to this recipe)
297 if f:
298 actualfile = f
299 else:
300 # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it
301 if first_meta_file:
302 actualfile = first_meta_file
303 else:
304 actualfile = fn
305
306 actualdir = os.path.dirname(actualfile) + os.sep
307 if not actualdir.startswith(fndir):
308 actualfile = fn
309 filevars[actualfile].append(v)
310
311 return filevars
312
313def patch_recipe(d, fn, varvalues, patch=False, relpath=''):
314 """Modify a list of variable values in the specified recipe. Handles inc files if
315 used by the recipe.
316 """
317 varlist = varvalues.keys()
318 varfiles = get_var_files(fn, varlist, d)
319 locs = localise_file_vars(fn, varfiles, varlist)
320 patches = []
321 for f,v in locs.iteritems():
322 vals = {k: varvalues[k] for k in v}
323 patchdata = patch_recipe_file(f, vals, patch, relpath)
324 if patch:
325 patches.append(patchdata)
326
327 if patch:
328 return patches
329 else:
330 return None
331
332
333
334def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True):
335 """Copy (local) recipe files, including both files included via include/require,
336 and files referred to in the SRC_URI variable."""
337 import bb.fetch2
338 import oe.path
339
340 # FIXME need a warning if the unexpanded SRC_URI value contains variable references
341
342 uris = (d.getVar('SRC_URI', True) or "").split()
343 fetch = bb.fetch2.Fetch(uris, d)
344 if download:
345 fetch.download()
346
347 # Copy local files to target directory and gather any remote files
348 bb_dir = os.path.dirname(d.getVar('FILE', True)) + os.sep
349 remotes = []
350 includes = [path for path in d.getVar('BBINCLUDED', True).split() if
351 path.startswith(bb_dir) and os.path.exists(path)]
352 for path in fetch.localpaths() + includes:
353 # Only import files that are under the meta directory
354 if path.startswith(bb_dir):
355 if not whole_dir:
356 relpath = os.path.relpath(path, bb_dir)
357 subdir = os.path.join(tgt_dir, os.path.dirname(relpath))
358 if not os.path.exists(subdir):
359 os.makedirs(subdir)
360 shutil.copy2(path, os.path.join(tgt_dir, relpath))
361 else:
362 remotes.append(path)
363 # Simply copy whole meta dir, if requested
364 if whole_dir:
365 shutil.copytree(bb_dir, tgt_dir)
366
367 return remotes
368
369
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500370def get_recipe_local_files(d, patches=False):
371 """Get a list of local files in SRC_URI within a recipe."""
372 uris = (d.getVar('SRC_URI', True) or "").split()
373 fetch = bb.fetch2.Fetch(uris, d)
374 ret = {}
375 for uri in uris:
376 if fetch.ud[uri].type == 'file':
377 if (not patches and
378 bb.utils.exec_flat_python_func('patch_path', uri, fetch, '')):
379 continue
380 # Skip files that are referenced by absolute path
381 if not os.path.isabs(fetch.ud[uri].basepath):
382 ret[fetch.ud[uri].basepath] = fetch.localpath(uri)
383 return ret
384
385
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500386def get_recipe_patches(d):
387 """Get a list of the patches included in SRC_URI within a recipe."""
388 patchfiles = []
389 # Execute src_patches() defined in patch.bbclass - this works since that class
390 # is inherited globally
391 patches = bb.utils.exec_flat_python_func('src_patches', d)
392 for patch in patches:
393 _, _, local, _, _, parm = bb.fetch.decodeurl(patch)
394 patchfiles.append(local)
395 return patchfiles
396
397
398def get_recipe_patched_files(d):
399 """
400 Get the list of patches for a recipe along with the files each patch modifies.
401 Params:
402 d: the datastore for the recipe
403 Returns:
404 a dict mapping patch file path to a list of tuples of changed files and
405 change mode ('A' for add, 'D' for delete or 'M' for modify)
406 """
407 import oe.patch
408 # Execute src_patches() defined in patch.bbclass - this works since that class
409 # is inherited globally
410 patches = bb.utils.exec_flat_python_func('src_patches', d)
411 patchedfiles = {}
412 for patch in patches:
413 _, _, patchfile, _, _, parm = bb.fetch.decodeurl(patch)
414 striplevel = int(parm['striplevel'])
415 patchedfiles[patchfile] = oe.patch.PatchSet.getPatchedFiles(patchfile, striplevel, os.path.join(d.getVar('S', True), parm.get('patchdir', '')))
416 return patchedfiles
417
418
419def validate_pn(pn):
420 """Perform validation on a recipe name (PN) for a new recipe."""
421 reserved_names = ['forcevariable', 'append', 'prepend', 'remove']
422 if not re.match('[0-9a-z-.]+', pn):
423 return 'Recipe name "%s" is invalid: only characters 0-9, a-z, - and . are allowed' % pn
424 elif pn in reserved_names:
425 return 'Recipe name "%s" is invalid: is a reserved keyword' % pn
426 elif pn.startswith('pn-'):
427 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500428 elif pn.endswith(('.bb', '.bbappend', '.bbclass', '.inc', '.conf')):
429 return 'Recipe name "%s" is invalid: should be just a name, not a file name' % pn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500430 return ''
431
432
433def get_bbappend_path(d, destlayerdir, wildcardver=False):
434 """Determine how a bbappend for a recipe should be named and located within another layer"""
435
436 import bb.cookerdata
437
438 destlayerdir = os.path.abspath(destlayerdir)
439 recipefile = d.getVar('FILE', True)
440 recipefn = os.path.splitext(os.path.basename(recipefile))[0]
441 if wildcardver and '_' in recipefn:
442 recipefn = recipefn.split('_', 1)[0] + '_%'
443 appendfn = recipefn + '.bbappend'
444
445 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
446 confdata = d.createCopy()
447 confdata.setVar('BBFILES', '')
448 confdata.setVar('LAYERDIR', destlayerdir)
449 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
450 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
451
452 origlayerdir = find_layerdir(recipefile)
453 if not origlayerdir:
454 return (None, False)
455 # Now join this to the path where the bbappend is going and check if it is covered by BBFILES
456 appendpath = os.path.join(destlayerdir, os.path.relpath(os.path.dirname(recipefile), origlayerdir), appendfn)
457 closepath = ''
458 pathok = True
459 for bbfilespec in confdata.getVar('BBFILES', True).split():
460 if fnmatch.fnmatchcase(appendpath, bbfilespec):
461 # Our append path works, we're done
462 break
463 elif bbfilespec.startswith(destlayerdir) and fnmatch.fnmatchcase('test.bbappend', os.path.basename(bbfilespec)):
464 # Try to find the longest matching path
465 if len(bbfilespec) > len(closepath):
466 closepath = bbfilespec
467 else:
468 # Unfortunately the bbappend layer and the original recipe's layer don't have the same structure
469 if closepath:
470 # bbappend layer's layer.conf at least has a spec that picks up .bbappend files
471 # Now we just need to substitute out any wildcards
472 appendsubdir = os.path.relpath(os.path.dirname(closepath), destlayerdir)
473 if 'recipes-*' in appendsubdir:
474 # Try to copy this part from the original recipe path
475 res = re.search('/recipes-[^/]+/', recipefile)
476 if res:
477 appendsubdir = appendsubdir.replace('/recipes-*/', res.group(0))
478 # This is crude, but we have to do something
479 appendsubdir = appendsubdir.replace('*', recipefn.split('_')[0])
480 appendsubdir = appendsubdir.replace('?', 'a')
481 appendpath = os.path.join(destlayerdir, appendsubdir, appendfn)
482 else:
483 pathok = False
484 return (appendpath, pathok)
485
486
487def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, machine=None, extralines=None, removevalues=None):
488 """
489 Writes a bbappend file for a recipe
490 Parameters:
491 rd: data dictionary for the recipe
492 destlayerdir: base directory of the layer to place the bbappend in
493 (subdirectory path from there will be determined automatically)
494 srcfiles: dict of source files to add to SRC_URI, where the value
495 is the full path to the file to be added, and the value is the
496 original filename as it would appear in SRC_URI or None if it
497 isn't already present. You may pass None for this parameter if
498 you simply want to specify your own content via the extralines
499 parameter.
500 install: dict mapping entries in srcfiles to a tuple of two elements:
501 install path (*without* ${D} prefix) and permission value (as a
502 string, e.g. '0644').
503 wildcardver: True to use a % wildcard in the bbappend filename, or
504 False to make the bbappend specific to the recipe version.
505 machine:
506 If specified, make the changes in the bbappend specific to this
507 machine. This will also cause PACKAGE_ARCH = "${MACHINE_ARCH}"
508 to be added to the bbappend.
509 extralines:
510 Extra lines to add to the bbappend. This may be a dict of name
511 value pairs, or simply a list of the lines.
512 removevalues:
513 Variable values to remove - a dict of names/values.
514 """
515
516 if not removevalues:
517 removevalues = {}
518
519 # Determine how the bbappend should be named
520 appendpath, pathok = get_bbappend_path(rd, destlayerdir, wildcardver)
521 if not appendpath:
522 bb.error('Unable to determine layer directory containing %s' % recipefile)
523 return (None, None)
524 if not pathok:
525 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)))
526
527 appenddir = os.path.dirname(appendpath)
528 bb.utils.mkdirhier(appenddir)
529
530 # FIXME check if the bbappend doesn't get overridden by a higher priority layer?
531
532 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS', True).split()]
533 if not os.path.abspath(destlayerdir) in layerdirs:
534 bb.warn('Specified layer is not currently enabled in bblayers.conf, you will need to add it before this bbappend will be active')
535
536 bbappendlines = []
537 if extralines:
538 if isinstance(extralines, dict):
539 for name, value in extralines.iteritems():
540 bbappendlines.append((name, '=', value))
541 else:
542 # Do our best to split it
543 for line in extralines:
544 if line[-1] == '\n':
545 line = line[:-1]
546 splitline = line.split(None, 2)
547 if len(splitline) == 3:
548 bbappendlines.append(tuple(splitline))
549 else:
550 raise Exception('Invalid extralines value passed')
551
552 def popline(varname):
553 for i in xrange(0, len(bbappendlines)):
554 if bbappendlines[i][0] == varname:
555 line = bbappendlines.pop(i)
556 return line
557 return None
558
559 def appendline(varname, op, value):
560 for i in xrange(0, len(bbappendlines)):
561 item = bbappendlines[i]
562 if item[0] == varname:
563 bbappendlines[i] = (item[0], item[1], item[2] + ' ' + value)
564 break
565 else:
566 bbappendlines.append((varname, op, value))
567
568 destsubdir = rd.getVar('PN', True)
569 if srcfiles:
570 bbappendlines.append(('FILESEXTRAPATHS_prepend', ':=', '${THISDIR}/${PN}:'))
571
572 appendoverride = ''
573 if machine:
574 bbappendlines.append(('PACKAGE_ARCH', '=', '${MACHINE_ARCH}'))
575 appendoverride = '_%s' % machine
576 copyfiles = {}
577 if srcfiles:
578 instfunclines = []
579 for newfile, origsrcfile in srcfiles.iteritems():
580 srcfile = origsrcfile
581 srcurientry = None
582 if not srcfile:
583 srcfile = os.path.basename(newfile)
584 srcurientry = 'file://%s' % srcfile
585 # Double-check it's not there already
586 # FIXME do we care if the entry is added by another bbappend that might go away?
587 if not srcurientry in rd.getVar('SRC_URI', True).split():
588 if machine:
589 appendline('SRC_URI_append%s' % appendoverride, '=', ' ' + srcurientry)
590 else:
591 appendline('SRC_URI', '+=', srcurientry)
592 copyfiles[newfile] = srcfile
593 if install:
594 institem = install.pop(newfile, None)
595 if institem:
596 (destpath, perms) = institem
597 instdestpath = replace_dir_vars(destpath, rd)
598 instdirline = 'install -d ${D}%s' % os.path.dirname(instdestpath)
599 if not instdirline in instfunclines:
600 instfunclines.append(instdirline)
601 instfunclines.append('install -m %s ${WORKDIR}/%s ${D}%s' % (perms, os.path.basename(srcfile), instdestpath))
602 if instfunclines:
603 bbappendlines.append(('do_install_append%s()' % appendoverride, '', instfunclines))
604
605 bb.note('Writing append file %s' % appendpath)
606
607 if os.path.exists(appendpath):
608 # Work around lack of nonlocal in python 2
609 extvars = {'destsubdir': destsubdir}
610
611 def appendfile_varfunc(varname, origvalue, op, newlines):
612 if varname == 'FILESEXTRAPATHS_prepend':
613 if origvalue.startswith('${THISDIR}/'):
614 popline('FILESEXTRAPATHS_prepend')
615 extvars['destsubdir'] = rd.expand(origvalue.split('${THISDIR}/', 1)[1].rstrip(':'))
616 elif varname == 'PACKAGE_ARCH':
617 if machine:
618 popline('PACKAGE_ARCH')
619 return (machine, None, 4, False)
620 elif varname.startswith('do_install_append'):
621 func = popline(varname)
622 if func:
623 instfunclines = [line.strip() for line in origvalue.strip('\n').splitlines()]
624 for line in func[2]:
625 if not line in instfunclines:
626 instfunclines.append(line)
627 return (instfunclines, None, 4, False)
628 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500629 splitval = split_var_value(origvalue, assignment=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500630 changed = False
631 removevar = varname
632 if varname in ['SRC_URI', 'SRC_URI_append%s' % appendoverride]:
633 removevar = 'SRC_URI'
634 line = popline(varname)
635 if line:
636 if line[2] not in splitval:
637 splitval.append(line[2])
638 changed = True
639 else:
640 line = popline(varname)
641 if line:
642 splitval = [line[2]]
643 changed = True
644
645 if removevar in removevalues:
646 remove = removevalues[removevar]
647 if isinstance(remove, basestring):
648 if remove in splitval:
649 splitval.remove(remove)
650 changed = True
651 else:
652 for removeitem in remove:
653 if removeitem in splitval:
654 splitval.remove(removeitem)
655 changed = True
656
657 if changed:
658 newvalue = splitval
659 if len(newvalue) == 1:
660 # Ensure it's written out as one line
661 if '_append' in varname:
662 newvalue = ' ' + newvalue[0]
663 else:
664 newvalue = newvalue[0]
665 if not newvalue and (op in ['+=', '.='] or '_append' in varname):
666 # There's no point appending nothing
667 newvalue = None
668 if varname.endswith('()'):
669 indent = 4
670 else:
671 indent = -1
672 return (newvalue, None, indent, True)
673 return (origvalue, None, 4, False)
674
675 varnames = [item[0] for item in bbappendlines]
676 if removevalues:
677 varnames.extend(removevalues.keys())
678
679 with open(appendpath, 'r') as f:
680 (updated, newlines) = bb.utils.edit_metadata(f, varnames, appendfile_varfunc)
681
682 destsubdir = extvars['destsubdir']
683 else:
684 updated = False
685 newlines = []
686
687 if bbappendlines:
688 for line in bbappendlines:
689 if line[0].endswith('()'):
690 newlines.append('%s {\n %s\n}\n' % (line[0], '\n '.join(line[2])))
691 else:
692 newlines.append('%s %s "%s"\n\n' % line)
693 updated = True
694
695 if updated:
696 with open(appendpath, 'w') as f:
697 f.writelines(newlines)
698
699 if copyfiles:
700 if machine:
701 destsubdir = os.path.join(destsubdir, machine)
702 for newfile, srcfile in copyfiles.iteritems():
703 filedest = os.path.join(appenddir, destsubdir, os.path.basename(srcfile))
704 if os.path.abspath(newfile) != os.path.abspath(filedest):
705 bb.note('Copying %s to %s' % (newfile, filedest))
706 bb.utils.mkdirhier(os.path.dirname(filedest))
707 shutil.copyfile(newfile, filedest)
708
709 return (appendpath, os.path.join(appenddir, destsubdir))
710
711
712def find_layerdir(fn):
713 """ Figure out relative path to base of layer for a file (e.g. a recipe)"""
714 pth = os.path.dirname(fn)
715 layerdir = ''
716 while pth:
717 if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')):
718 layerdir = pth
719 break
720 pth = os.path.dirname(pth)
721 return layerdir
722
723
724def replace_dir_vars(path, d):
725 """Replace common directory paths with appropriate variable references (e.g. /etc becomes ${sysconfdir})"""
726 dirvars = {}
727 # Sort by length so we get the variables we're interested in first
728 for var in sorted(d.keys(), key=len):
729 if var.endswith('dir') and var.lower() == var:
730 value = d.getVar(var, True)
731 if value.startswith('/') and not '\n' in value and value not in dirvars:
732 dirvars[value] = var
733 for dirpath in sorted(dirvars.keys(), reverse=True):
734 path = path.replace(dirpath, '${%s}' % dirvars[dirpath])
735 return path
736
737def get_recipe_pv_without_srcpv(pv, uri_type):
738 """
739 Get PV without SRCPV common in SCM's for now only
740 support git.
741
742 Returns tuple with pv, prefix and suffix.
743 """
744 pfx = ''
745 sfx = ''
746
747 if uri_type == 'git':
748 git_regex = re.compile("(?P<pfx>v?)(?P<ver>[^\+]*)((?P<sfx>\+(git)?r?(AUTOINC\+))(?P<rev>.*))?")
749 m = git_regex.match(pv)
750
751 if m:
752 pv = m.group('ver')
753 pfx = m.group('pfx')
754 sfx = m.group('sfx')
755 else:
756 regex = re.compile("(?P<pfx>(v|r)?)(?P<ver>.*)")
757 m = regex.match(pv)
758 if m:
759 pv = m.group('ver')
760 pfx = m.group('pfx')
761
762 return (pv, pfx, sfx)
763
764def get_recipe_upstream_version(rd):
765 """
766 Get upstream version of recipe using bb.fetch2 methods with support for
767 http, https, ftp and git.
768
769 bb.fetch2 exceptions can be raised,
770 FetchError when don't have network access or upstream site don't response.
771 NoMethodError when uri latest_versionstring method isn't implemented.
772
773 Returns a dictonary with version, type and datetime.
774 Type can be A for Automatic, M for Manual and U for Unknown.
775 """
776 from bb.fetch2 import decodeurl
777 from datetime import datetime
778
779 ru = {}
780 ru['version'] = ''
781 ru['type'] = 'U'
782 ru['datetime'] = ''
783
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500784 pv = rd.getVar('PV', True)
785
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500786 # XXX: If don't have SRC_URI means that don't have upstream sources so
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500787 # returns the current recipe version, so that upstream version check
788 # declares a match.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500789 src_uris = rd.getVar('SRC_URI', True)
790 if not src_uris:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500791 ru['version'] = pv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500792 ru['type'] = 'M'
793 ru['datetime'] = datetime.now()
794 return ru
795
796 # XXX: we suppose that the first entry points to the upstream sources
797 src_uri = src_uris.split()[0]
798 uri_type, _, _, _, _, _ = decodeurl(src_uri)
799
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500800 manual_upstream_version = rd.getVar("RECIPE_UPSTREAM_VERSION", True)
801 if manual_upstream_version:
802 # manual tracking of upstream version.
803 ru['version'] = manual_upstream_version
804 ru['type'] = 'M'
805
806 manual_upstream_date = rd.getVar("CHECK_DATE", True)
807 if manual_upstream_date:
808 date = datetime.strptime(manual_upstream_date, "%b %d, %Y")
809 else:
810 date = datetime.now()
811 ru['datetime'] = date
812
813 elif uri_type == "file":
814 # files are always up-to-date
815 ru['version'] = pv
816 ru['type'] = 'A'
817 ru['datetime'] = datetime.now()
818 else:
819 ud = bb.fetch2.FetchData(src_uri, rd)
820 pupver = ud.method.latest_versionstring(ud, rd)
821 (upversion, revision) = pupver
822
823 # format git version version+gitAUTOINC+HASH
824 if uri_type == 'git':
825 (pv, pfx, sfx) = get_recipe_pv_without_srcpv(pv, uri_type)
826
827 # if contains revision but not upversion use current pv
828 if upversion == '' and revision:
829 upversion = pv
830
831 if upversion:
832 tmp = upversion
833 upversion = ''
834
835 if pfx:
836 upversion = pfx + tmp
837 else:
838 upversion = tmp
839
840 if sfx:
841 upversion = upversion + sfx + revision[:10]
842
843 if upversion:
844 ru['version'] = upversion
845 ru['type'] = 'A'
846
847 ru['datetime'] = datetime.now()
848
849 return ru