blob: a7fdd36e40794e416592a5354e886594e93c1e91 [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#
Patrick Williamsc0f7c042017-02-23 20:41:17 -06005# Copyright (C) 2013-2016 Intel Corporation
Patrick Williamsc124f4f2015-09-15 14:41:29 -05006#
7
8import sys
9import os
10import os.path
11import tempfile
12import textwrap
13import difflib
Patrick Williamsc0f7c042017-02-23 20:41:17 -060014from . import utils
Patrick Williamsc124f4f2015-09-15 14:41:29 -050015import shutil
16import re
17import fnmatch
Patrick Williamsc0f7c042017-02-23 20:41:17 -060018import glob
Patrick Williamsc124f4f2015-09-15 14:41:29 -050019from collections import OrderedDict, defaultdict
20
21
22# Help us to find places to insert values
Patrick Williamsc0f7c042017-02-23 20:41:17 -060023recipe_progression = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION', 'LICENSE', 'LICENSE_FLAGS', 'LIC_FILES_CHKSUM', 'PROVIDES', 'DEPENDS', 'PR', 'PV', 'SRCREV', 'SRCPV', 'SRC_URI', 'S', 'do_fetch()', 'do_unpack()', 'do_patch()', 'EXTRA_OECONF', 'EXTRA_OECMAKE', 'EXTRA_OESCONS', 'do_configure()', 'EXTRA_OEMAKE', 'do_compile()', 'do_install()', 'do_populate_sysroot()', 'INITSCRIPT', 'USERADD', 'GROUPADD', 'PACKAGES', 'FILES', 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RPROVIDES', 'RREPLACES', 'RCONFLICTS', 'ALLOW_EMPTY', 'populate_packages()', 'do_package()', 'do_deploy()']
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024# Variables that sometimes are a bit long but shouldn't be wrapped
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050025nowrap_vars = ['SUMMARY', 'HOMEPAGE', 'BUGTRACKER', 'SRC_URI[md5sum]', 'SRC_URI[sha256sum]']
Patrick Williamsc124f4f2015-09-15 14:41:29 -050026list_vars = ['SRC_URI', 'LIC_FILES_CHKSUM']
27meta_vars = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION']
28
29
Patrick Williamsc0f7c042017-02-23 20:41:17 -060030def pn_to_recipe(cooker, pn, mc=''):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050031 """Convert a recipe name (PN) to the path to the recipe file"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032
Brad Bishop6e60e8b2018-02-01 10:27:11 -050033 best = cooker.findBestProvider(pn, mc)
34 return best[3]
Patrick Williamsc124f4f2015-09-15 14:41:29 -050035
36
37def get_unavailable_reasons(cooker, pn):
38 """If a recipe could not be found, find out why if possible"""
39 import bb.taskdata
40 taskdata = bb.taskdata.TaskData(None, skiplist=cooker.skiplist)
41 return taskdata.get_reasons(pn)
42
43
Patrick Williamsc0f7c042017-02-23 20:41:17 -060044def parse_recipe(cooker, fn, appendfiles):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050045 """
46 Parse an individual recipe file, optionally with a list of
47 bbappend files.
48 """
49 import bb.cache
Patrick Williamsc0f7c042017-02-23 20:41:17 -060050 parser = bb.cache.NoCache(cooker.databuilder)
51 envdata = parser.loadDataFull(fn, appendfiles)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050052 return envdata
53
54
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055def get_var_files(fn, varlist, d):
56 """Find the file in which each of a list of variables is set.
57 Note: requires variable history to be enabled when parsing.
58 """
59 varfiles = {}
60 for v in varlist:
61 history = d.varhistory.variable(v)
62 files = []
63 for event in history:
64 if 'file' in event and not 'flag' in event:
65 files.append(event['file'])
66 if files:
67 actualfile = files[-1]
68 else:
69 actualfile = None
70 varfiles[v] = actualfile
71
72 return varfiles
73
74
Patrick Williamsf1e5d692016-03-30 15:21:19 -050075def split_var_value(value, assignment=True):
76 """
77 Split a space-separated variable's value into a list of items,
78 taking into account that some of the items might be made up of
79 expressions containing spaces that should not be split.
80 Parameters:
81 value:
82 The string value to split
83 assignment:
84 True to assume that the value represents an assignment
85 statement, False otherwise. If True, and an assignment
86 statement is passed in the first item in
87 the returned list will be the part of the assignment
88 statement up to and including the opening quote character,
89 and the last item will be the closing quote.
90 """
91 inexpr = 0
92 lastchar = None
93 out = []
94 buf = ''
95 for char in value:
96 if char == '{':
97 if lastchar == '$':
98 inexpr += 1
99 elif char == '}':
100 inexpr -= 1
101 elif assignment and char in '"\'' and inexpr == 0:
102 if buf:
103 out.append(buf)
104 out.append(char)
105 char = ''
106 buf = ''
107 elif char.isspace() and inexpr == 0:
108 char = ''
109 if buf:
110 out.append(buf)
111 buf = ''
112 buf += char
113 lastchar = char
114 if buf:
115 out.append(buf)
116
117 # Join together assignment statement and opening quote
118 outlist = out
119 if assignment:
120 assigfound = False
121 for idx, item in enumerate(out):
122 if '=' in item:
123 assigfound = True
124 if assigfound:
125 if '"' in item or "'" in item:
126 outlist = [' '.join(out[:idx+1])]
127 outlist.extend(out[idx+1:])
128 break
129 return outlist
130
131
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600132def patch_recipe_lines(fromlines, values, trailing_newline=True):
133 """Update or insert variable values into lines from a recipe.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500134 Note that some manual inspection/intervention may be required
135 since this cannot handle all situations.
136 """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500137
138 import bb.utils
139
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600140 if trailing_newline:
141 newline = '\n'
142 else:
143 newline = ''
144
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500145 recipe_progression_res = []
146 recipe_progression_restrs = []
147 for item in recipe_progression:
148 if item.endswith('()'):
149 key = item[:-2]
150 else:
151 key = item
152 restr = '%s(_[a-zA-Z0-9-_$(){}]+|\[[^\]]*\])?' % key
153 if item.endswith('()'):
154 recipe_progression_restrs.append(restr + '()')
155 else:
156 recipe_progression_restrs.append(restr)
157 recipe_progression_res.append(re.compile('^%s$' % restr))
158
159 def get_recipe_pos(variable):
160 for i, p in enumerate(recipe_progression_res):
161 if p.match(variable):
162 return i
163 return -1
164
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500165 remainingnames = {}
166 for k in values.keys():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500167 remainingnames[k] = get_recipe_pos(k)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600168 remainingnames = OrderedDict(sorted(remainingnames.items(), key=lambda x: x[1]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500169
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500170 modifying = False
171
172 def outputvalue(name, lines, rewindcomments=False):
173 if values[name] is None:
174 return
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600175 rawtext = '%s = "%s"%s' % (name, values[name], newline)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500176 addlines = []
177 if name in nowrap_vars:
178 addlines.append(rawtext)
179 elif name in list_vars:
180 splitvalue = split_var_value(values[name], assignment=False)
181 if len(splitvalue) > 1:
182 linesplit = ' \\\n' + (' ' * (len(name) + 4))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600183 addlines.append('%s = "%s%s"%s' % (name, linesplit.join(splitvalue), linesplit, newline))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500184 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500185 addlines.append(rawtext)
186 else:
187 wrapped = textwrap.wrap(rawtext)
188 for wrapline in wrapped[:-1]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600189 addlines.append('%s \\%s' % (wrapline, newline))
190 addlines.append('%s%s' % (wrapped[-1], newline))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500191 if rewindcomments:
192 # Ensure we insert the lines before any leading comments
193 # (that we'd want to ensure remain leading the next value)
194 for i, ln in reversed(list(enumerate(lines))):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600195 if not ln.startswith('#'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500196 lines[i+1:i+1] = addlines
197 break
198 else:
199 lines.extend(addlines)
200 else:
201 lines.extend(addlines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500202
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500203 existingnames = []
204 def patch_recipe_varfunc(varname, origvalue, op, newlines):
205 if modifying:
206 # Insert anything that should come before this variable
207 pos = get_recipe_pos(varname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600208 for k in list(remainingnames):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500209 if remainingnames[k] > -1 and pos >= remainingnames[k] and not k in existingnames:
210 outputvalue(k, newlines, rewindcomments=True)
211 del remainingnames[k]
212 # Now change this variable, if it needs to be changed
213 if varname in existingnames and op in ['+=', '=', '=+']:
214 if varname in remainingnames:
215 outputvalue(varname, newlines)
216 del remainingnames[varname]
217 return None, None, 0, True
218 else:
219 if varname in values:
220 existingnames.append(varname)
221 return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500222
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500223 # First run - establish which values we want to set are already in the file
224 varlist = [re.escape(item) for item in values.keys()]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600225 bb.utils.edit_metadata(fromlines, varlist, patch_recipe_varfunc)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500226 # Second run - actually set everything
227 modifying = True
228 varlist.extend(recipe_progression_restrs)
229 changed, tolines = bb.utils.edit_metadata(fromlines, varlist, patch_recipe_varfunc, match_overrides=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500230
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500231 if remainingnames:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600232 if tolines and tolines[-1].strip() != '':
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500233 tolines.append('\n')
234 for k in remainingnames.keys():
235 outputvalue(k, tolines)
236
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600237 return changed, tolines
238
239
240def patch_recipe_file(fn, values, patch=False, relpath=''):
241 """Update or insert variable values into a recipe file (assuming you
242 have already identified the exact file you want to update.)
243 Note that some manual inspection/intervention may be required
244 since this cannot handle all situations.
245 """
246
247 with open(fn, 'r') as f:
248 fromlines = f.readlines()
249
250 _, tolines = patch_recipe_lines(fromlines, values)
251
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500252 if patch:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500253 relfn = os.path.relpath(fn, relpath)
254 diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500255 return diff
256 else:
257 with open(fn, 'w') as f:
258 f.writelines(tolines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500259 return None
260
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500261
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500262def localise_file_vars(fn, varfiles, varlist):
263 """Given a list of variables and variable history (fetched with get_var_files())
264 find where each variable should be set/changed. This handles for example where a
265 recipe includes an inc file where variables might be changed - in most cases
266 we want to update the inc file when changing the variable value rather than adding
267 it to the recipe itself.
268 """
269 fndir = os.path.dirname(fn) + os.sep
270
271 first_meta_file = None
272 for v in meta_vars:
273 f = varfiles.get(v, None)
274 if f:
275 actualdir = os.path.dirname(f) + os.sep
276 if actualdir.startswith(fndir):
277 first_meta_file = f
278 break
279
280 filevars = defaultdict(list)
281 for v in varlist:
282 f = varfiles[v]
283 # Only return files that are in the same directory as the recipe or in some directory below there
284 # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable
285 # in if we were going to set a value specific to this recipe)
286 if f:
287 actualfile = f
288 else:
289 # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it
290 if first_meta_file:
291 actualfile = first_meta_file
292 else:
293 actualfile = fn
294
295 actualdir = os.path.dirname(actualfile) + os.sep
296 if not actualdir.startswith(fndir):
297 actualfile = fn
298 filevars[actualfile].append(v)
299
300 return filevars
301
302def patch_recipe(d, fn, varvalues, patch=False, relpath=''):
303 """Modify a list of variable values in the specified recipe. Handles inc files if
304 used by the recipe.
305 """
306 varlist = varvalues.keys()
307 varfiles = get_var_files(fn, varlist, d)
308 locs = localise_file_vars(fn, varfiles, varlist)
309 patches = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600310 for f,v in locs.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500311 vals = {k: varvalues[k] for k in v}
312 patchdata = patch_recipe_file(f, vals, patch, relpath)
313 if patch:
314 patches.append(patchdata)
315
316 if patch:
317 return patches
318 else:
319 return None
320
321
322
323def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True):
324 """Copy (local) recipe files, including both files included via include/require,
325 and files referred to in the SRC_URI variable."""
326 import bb.fetch2
327 import oe.path
328
329 # FIXME need a warning if the unexpanded SRC_URI value contains variable references
330
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500331 uris = (d.getVar('SRC_URI') or "").split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500332 fetch = bb.fetch2.Fetch(uris, d)
333 if download:
334 fetch.download()
335
336 # Copy local files to target directory and gather any remote files
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500337 bb_dir = os.path.dirname(d.getVar('FILE')) + os.sep
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500338 remotes = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600339 copied = []
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500340 includes = [path for path in d.getVar('BBINCLUDED').split() if
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341 path.startswith(bb_dir) and os.path.exists(path)]
342 for path in fetch.localpaths() + includes:
343 # Only import files that are under the meta directory
344 if path.startswith(bb_dir):
345 if not whole_dir:
346 relpath = os.path.relpath(path, bb_dir)
347 subdir = os.path.join(tgt_dir, os.path.dirname(relpath))
348 if not os.path.exists(subdir):
349 os.makedirs(subdir)
350 shutil.copy2(path, os.path.join(tgt_dir, relpath))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600351 copied.append(relpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500352 else:
353 remotes.append(path)
354 # Simply copy whole meta dir, if requested
355 if whole_dir:
356 shutil.copytree(bb_dir, tgt_dir)
357
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600358 return copied, remotes
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500359
360
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500361def get_recipe_local_files(d, patches=False, archives=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500362 """Get a list of local files in SRC_URI within a recipe."""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500363 import oe.patch
364 uris = (d.getVar('SRC_URI') or "").split()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500365 fetch = bb.fetch2.Fetch(uris, d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500366 # FIXME this list should be factored out somewhere else (such as the
367 # fetcher) though note that this only encompasses actual container formats
368 # i.e. that can contain multiple files as opposed to those that only
369 # contain a compressed stream (i.e. .tar.gz as opposed to just .gz)
370 archive_exts = ['.tar', '.tgz', '.tar.gz', '.tar.Z', '.tbz', '.tbz2', '.tar.bz2', '.tar.xz', '.tar.lz', '.zip', '.jar', '.rpm', '.srpm', '.deb', '.ipk', '.tar.7z', '.7z']
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500371 ret = {}
372 for uri in uris:
373 if fetch.ud[uri].type == 'file':
374 if (not patches and
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500375 oe.patch.patch_path(uri, fetch, '', expand=False)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500376 continue
377 # Skip files that are referenced by absolute path
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600378 fname = fetch.ud[uri].basepath
379 if os.path.isabs(fname):
380 continue
381 # Handle subdir=
382 subdir = fetch.ud[uri].parm.get('subdir', '')
383 if subdir:
384 if os.path.isabs(subdir):
385 continue
386 fname = os.path.join(subdir, fname)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500387 localpath = fetch.localpath(uri)
388 if not archives:
389 # Ignore archives that will be unpacked
390 if localpath.endswith(tuple(archive_exts)):
391 unpack = fetch.ud[uri].parm.get('unpack', True)
392 if unpack:
393 continue
394 ret[fname] = localpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500395 return ret
396
397
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500398def get_recipe_patches(d):
399 """Get a list of the patches included in SRC_URI within a recipe."""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500400 import oe.patch
401 patches = oe.patch.src_patches(d, expand=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500402 patchfiles = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500403 for patch in patches:
404 _, _, local, _, _, parm = bb.fetch.decodeurl(patch)
405 patchfiles.append(local)
406 return patchfiles
407
408
409def get_recipe_patched_files(d):
410 """
411 Get the list of patches for a recipe along with the files each patch modifies.
412 Params:
413 d: the datastore for the recipe
414 Returns:
415 a dict mapping patch file path to a list of tuples of changed files and
416 change mode ('A' for add, 'D' for delete or 'M' for modify)
417 """
418 import oe.patch
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500419 patches = oe.patch.src_patches(d, expand=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500420 patchedfiles = {}
421 for patch in patches:
422 _, _, patchfile, _, _, parm = bb.fetch.decodeurl(patch)
423 striplevel = int(parm['striplevel'])
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500424 patchedfiles[patchfile] = oe.patch.PatchSet.getPatchedFiles(patchfile, striplevel, os.path.join(d.getVar('S'), parm.get('patchdir', '')))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425 return patchedfiles
426
427
428def validate_pn(pn):
429 """Perform validation on a recipe name (PN) for a new recipe."""
430 reserved_names = ['forcevariable', 'append', 'prepend', 'remove']
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600431 if not re.match('^[0-9a-z-.+]+$', pn):
432 return 'Recipe name "%s" is invalid: only characters 0-9, a-z, -, + and . are allowed' % pn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500433 elif pn in reserved_names:
434 return 'Recipe name "%s" is invalid: is a reserved keyword' % pn
435 elif pn.startswith('pn-'):
436 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500437 elif pn.endswith(('.bb', '.bbappend', '.bbclass', '.inc', '.conf')):
438 return 'Recipe name "%s" is invalid: should be just a name, not a file name' % pn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500439 return ''
440
441
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600442def get_bbfile_path(d, destdir, extrapathhint=None):
443 """
444 Determine the correct path for a recipe within a layer
445 Parameters:
446 d: Recipe-specific datastore
447 destdir: destination directory. Can be the path to the base of the layer or a
448 partial path somewhere within the layer.
449 extrapathhint: a path relative to the base of the layer to try
450 """
451 import bb.cookerdata
452
453 destdir = os.path.abspath(destdir)
454 destlayerdir = find_layerdir(destdir)
455
456 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
457 confdata = d.createCopy()
458 confdata.setVar('BBFILES', '')
459 confdata.setVar('LAYERDIR', destlayerdir)
460 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
461 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500462 pn = d.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600463
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500464 bbfilespecs = (confdata.getVar('BBFILES') or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600465 if destdir == destlayerdir:
466 for bbfilespec in bbfilespecs:
467 if not bbfilespec.endswith('.bbappend'):
468 for match in glob.glob(bbfilespec):
469 splitext = os.path.splitext(os.path.basename(match))
470 if splitext[1] == '.bb':
471 mpn = splitext[0].split('_')[0]
472 if mpn == pn:
473 return os.path.dirname(match)
474
475 # Try to make up a path that matches BBFILES
476 # this is a little crude, but better than nothing
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500477 bpn = d.getVar('BPN')
478 recipefn = os.path.basename(d.getVar('FILE'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600479 pathoptions = [destdir]
480 if extrapathhint:
481 pathoptions.append(os.path.join(destdir, extrapathhint))
482 if destdir == destlayerdir:
483 pathoptions.append(os.path.join(destdir, 'recipes-%s' % bpn, bpn))
484 pathoptions.append(os.path.join(destdir, 'recipes', bpn))
485 pathoptions.append(os.path.join(destdir, bpn))
486 elif not destdir.endswith(('/' + pn, '/' + bpn)):
487 pathoptions.append(os.path.join(destdir, bpn))
488 closepath = ''
489 for pathoption in pathoptions:
490 bbfilepath = os.path.join(pathoption, 'test.bb')
491 for bbfilespec in bbfilespecs:
492 if fnmatch.fnmatchcase(bbfilepath, bbfilespec):
493 return pathoption
494 return None
495
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500496def get_bbappend_path(d, destlayerdir, wildcardver=False):
497 """Determine how a bbappend for a recipe should be named and located within another layer"""
498
499 import bb.cookerdata
500
501 destlayerdir = os.path.abspath(destlayerdir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500502 recipefile = d.getVar('FILE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500503 recipefn = os.path.splitext(os.path.basename(recipefile))[0]
504 if wildcardver and '_' in recipefn:
505 recipefn = recipefn.split('_', 1)[0] + '_%'
506 appendfn = recipefn + '.bbappend'
507
508 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
509 confdata = d.createCopy()
510 confdata.setVar('BBFILES', '')
511 confdata.setVar('LAYERDIR', destlayerdir)
512 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
513 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
514
515 origlayerdir = find_layerdir(recipefile)
516 if not origlayerdir:
517 return (None, False)
518 # Now join this to the path where the bbappend is going and check if it is covered by BBFILES
519 appendpath = os.path.join(destlayerdir, os.path.relpath(os.path.dirname(recipefile), origlayerdir), appendfn)
520 closepath = ''
521 pathok = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500522 for bbfilespec in confdata.getVar('BBFILES').split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500523 if fnmatch.fnmatchcase(appendpath, bbfilespec):
524 # Our append path works, we're done
525 break
526 elif bbfilespec.startswith(destlayerdir) and fnmatch.fnmatchcase('test.bbappend', os.path.basename(bbfilespec)):
527 # Try to find the longest matching path
528 if len(bbfilespec) > len(closepath):
529 closepath = bbfilespec
530 else:
531 # Unfortunately the bbappend layer and the original recipe's layer don't have the same structure
532 if closepath:
533 # bbappend layer's layer.conf at least has a spec that picks up .bbappend files
534 # Now we just need to substitute out any wildcards
535 appendsubdir = os.path.relpath(os.path.dirname(closepath), destlayerdir)
536 if 'recipes-*' in appendsubdir:
537 # Try to copy this part from the original recipe path
538 res = re.search('/recipes-[^/]+/', recipefile)
539 if res:
540 appendsubdir = appendsubdir.replace('/recipes-*/', res.group(0))
541 # This is crude, but we have to do something
542 appendsubdir = appendsubdir.replace('*', recipefn.split('_')[0])
543 appendsubdir = appendsubdir.replace('?', 'a')
544 appendpath = os.path.join(destlayerdir, appendsubdir, appendfn)
545 else:
546 pathok = False
547 return (appendpath, pathok)
548
549
550def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, machine=None, extralines=None, removevalues=None):
551 """
552 Writes a bbappend file for a recipe
553 Parameters:
554 rd: data dictionary for the recipe
555 destlayerdir: base directory of the layer to place the bbappend in
556 (subdirectory path from there will be determined automatically)
557 srcfiles: dict of source files to add to SRC_URI, where the value
558 is the full path to the file to be added, and the value is the
559 original filename as it would appear in SRC_URI or None if it
560 isn't already present. You may pass None for this parameter if
561 you simply want to specify your own content via the extralines
562 parameter.
563 install: dict mapping entries in srcfiles to a tuple of two elements:
564 install path (*without* ${D} prefix) and permission value (as a
565 string, e.g. '0644').
566 wildcardver: True to use a % wildcard in the bbappend filename, or
567 False to make the bbappend specific to the recipe version.
568 machine:
569 If specified, make the changes in the bbappend specific to this
570 machine. This will also cause PACKAGE_ARCH = "${MACHINE_ARCH}"
571 to be added to the bbappend.
572 extralines:
573 Extra lines to add to the bbappend. This may be a dict of name
574 value pairs, or simply a list of the lines.
575 removevalues:
576 Variable values to remove - a dict of names/values.
577 """
578
579 if not removevalues:
580 removevalues = {}
581
582 # Determine how the bbappend should be named
583 appendpath, pathok = get_bbappend_path(rd, destlayerdir, wildcardver)
584 if not appendpath:
585 bb.error('Unable to determine layer directory containing %s' % recipefile)
586 return (None, None)
587 if not pathok:
588 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)))
589
590 appenddir = os.path.dirname(appendpath)
591 bb.utils.mkdirhier(appenddir)
592
593 # FIXME check if the bbappend doesn't get overridden by a higher priority layer?
594
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500595 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500596 if not os.path.abspath(destlayerdir) in layerdirs:
597 bb.warn('Specified layer is not currently enabled in bblayers.conf, you will need to add it before this bbappend will be active')
598
599 bbappendlines = []
600 if extralines:
601 if isinstance(extralines, dict):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600602 for name, value in extralines.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500603 bbappendlines.append((name, '=', value))
604 else:
605 # Do our best to split it
606 for line in extralines:
607 if line[-1] == '\n':
608 line = line[:-1]
609 splitline = line.split(None, 2)
610 if len(splitline) == 3:
611 bbappendlines.append(tuple(splitline))
612 else:
613 raise Exception('Invalid extralines value passed')
614
615 def popline(varname):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600616 for i in range(0, len(bbappendlines)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500617 if bbappendlines[i][0] == varname:
618 line = bbappendlines.pop(i)
619 return line
620 return None
621
622 def appendline(varname, op, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600623 for i in range(0, len(bbappendlines)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500624 item = bbappendlines[i]
625 if item[0] == varname:
626 bbappendlines[i] = (item[0], item[1], item[2] + ' ' + value)
627 break
628 else:
629 bbappendlines.append((varname, op, value))
630
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500631 destsubdir = rd.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500632 if srcfiles:
633 bbappendlines.append(('FILESEXTRAPATHS_prepend', ':=', '${THISDIR}/${PN}:'))
634
635 appendoverride = ''
636 if machine:
637 bbappendlines.append(('PACKAGE_ARCH', '=', '${MACHINE_ARCH}'))
638 appendoverride = '_%s' % machine
639 copyfiles = {}
640 if srcfiles:
641 instfunclines = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600642 for newfile, origsrcfile in srcfiles.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500643 srcfile = origsrcfile
644 srcurientry = None
645 if not srcfile:
646 srcfile = os.path.basename(newfile)
647 srcurientry = 'file://%s' % srcfile
648 # Double-check it's not there already
649 # FIXME do we care if the entry is added by another bbappend that might go away?
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500650 if not srcurientry in rd.getVar('SRC_URI').split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500651 if machine:
652 appendline('SRC_URI_append%s' % appendoverride, '=', ' ' + srcurientry)
653 else:
654 appendline('SRC_URI', '+=', srcurientry)
655 copyfiles[newfile] = srcfile
656 if install:
657 institem = install.pop(newfile, None)
658 if institem:
659 (destpath, perms) = institem
660 instdestpath = replace_dir_vars(destpath, rd)
661 instdirline = 'install -d ${D}%s' % os.path.dirname(instdestpath)
662 if not instdirline in instfunclines:
663 instfunclines.append(instdirline)
664 instfunclines.append('install -m %s ${WORKDIR}/%s ${D}%s' % (perms, os.path.basename(srcfile), instdestpath))
665 if instfunclines:
666 bbappendlines.append(('do_install_append%s()' % appendoverride, '', instfunclines))
667
668 bb.note('Writing append file %s' % appendpath)
669
670 if os.path.exists(appendpath):
671 # Work around lack of nonlocal in python 2
672 extvars = {'destsubdir': destsubdir}
673
674 def appendfile_varfunc(varname, origvalue, op, newlines):
675 if varname == 'FILESEXTRAPATHS_prepend':
676 if origvalue.startswith('${THISDIR}/'):
677 popline('FILESEXTRAPATHS_prepend')
678 extvars['destsubdir'] = rd.expand(origvalue.split('${THISDIR}/', 1)[1].rstrip(':'))
679 elif varname == 'PACKAGE_ARCH':
680 if machine:
681 popline('PACKAGE_ARCH')
682 return (machine, None, 4, False)
683 elif varname.startswith('do_install_append'):
684 func = popline(varname)
685 if func:
686 instfunclines = [line.strip() for line in origvalue.strip('\n').splitlines()]
687 for line in func[2]:
688 if not line in instfunclines:
689 instfunclines.append(line)
690 return (instfunclines, None, 4, False)
691 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500692 splitval = split_var_value(origvalue, assignment=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500693 changed = False
694 removevar = varname
695 if varname in ['SRC_URI', 'SRC_URI_append%s' % appendoverride]:
696 removevar = 'SRC_URI'
697 line = popline(varname)
698 if line:
699 if line[2] not in splitval:
700 splitval.append(line[2])
701 changed = True
702 else:
703 line = popline(varname)
704 if line:
705 splitval = [line[2]]
706 changed = True
707
708 if removevar in removevalues:
709 remove = removevalues[removevar]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600710 if isinstance(remove, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500711 if remove in splitval:
712 splitval.remove(remove)
713 changed = True
714 else:
715 for removeitem in remove:
716 if removeitem in splitval:
717 splitval.remove(removeitem)
718 changed = True
719
720 if changed:
721 newvalue = splitval
722 if len(newvalue) == 1:
723 # Ensure it's written out as one line
724 if '_append' in varname:
725 newvalue = ' ' + newvalue[0]
726 else:
727 newvalue = newvalue[0]
728 if not newvalue and (op in ['+=', '.='] or '_append' in varname):
729 # There's no point appending nothing
730 newvalue = None
731 if varname.endswith('()'):
732 indent = 4
733 else:
734 indent = -1
735 return (newvalue, None, indent, True)
736 return (origvalue, None, 4, False)
737
738 varnames = [item[0] for item in bbappendlines]
739 if removevalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600740 varnames.extend(list(removevalues.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500741
742 with open(appendpath, 'r') as f:
743 (updated, newlines) = bb.utils.edit_metadata(f, varnames, appendfile_varfunc)
744
745 destsubdir = extvars['destsubdir']
746 else:
747 updated = False
748 newlines = []
749
750 if bbappendlines:
751 for line in bbappendlines:
752 if line[0].endswith('()'):
753 newlines.append('%s {\n %s\n}\n' % (line[0], '\n '.join(line[2])))
754 else:
755 newlines.append('%s %s "%s"\n\n' % line)
756 updated = True
757
758 if updated:
759 with open(appendpath, 'w') as f:
760 f.writelines(newlines)
761
762 if copyfiles:
763 if machine:
764 destsubdir = os.path.join(destsubdir, machine)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600765 for newfile, srcfile in copyfiles.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500766 filedest = os.path.join(appenddir, destsubdir, os.path.basename(srcfile))
767 if os.path.abspath(newfile) != os.path.abspath(filedest):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500768 if newfile.startswith(tempfile.gettempdir()):
769 newfiledisp = os.path.basename(newfile)
770 else:
771 newfiledisp = newfile
772 bb.note('Copying %s to %s' % (newfiledisp, filedest))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500773 bb.utils.mkdirhier(os.path.dirname(filedest))
774 shutil.copyfile(newfile, filedest)
775
776 return (appendpath, os.path.join(appenddir, destsubdir))
777
778
779def find_layerdir(fn):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600780 """ Figure out the path to the base of the layer containing a file (e.g. a recipe)"""
781 pth = fn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500782 layerdir = ''
783 while pth:
784 if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')):
785 layerdir = pth
786 break
787 pth = os.path.dirname(pth)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600788 if pth == '/':
789 return None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500790 return layerdir
791
792
793def replace_dir_vars(path, d):
794 """Replace common directory paths with appropriate variable references (e.g. /etc becomes ${sysconfdir})"""
795 dirvars = {}
796 # Sort by length so we get the variables we're interested in first
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600797 for var in sorted(list(d.keys()), key=len):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500798 if var.endswith('dir') and var.lower() == var:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500799 value = d.getVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500800 if value.startswith('/') and not '\n' in value and value not in dirvars:
801 dirvars[value] = var
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600802 for dirpath in sorted(list(dirvars.keys()), reverse=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500803 path = path.replace(dirpath, '${%s}' % dirvars[dirpath])
804 return path
805
806def get_recipe_pv_without_srcpv(pv, uri_type):
807 """
808 Get PV without SRCPV common in SCM's for now only
809 support git.
810
811 Returns tuple with pv, prefix and suffix.
812 """
813 pfx = ''
814 sfx = ''
815
816 if uri_type == 'git':
817 git_regex = re.compile("(?P<pfx>v?)(?P<ver>[^\+]*)((?P<sfx>\+(git)?r?(AUTOINC\+))(?P<rev>.*))?")
818 m = git_regex.match(pv)
819
820 if m:
821 pv = m.group('ver')
822 pfx = m.group('pfx')
823 sfx = m.group('sfx')
824 else:
825 regex = re.compile("(?P<pfx>(v|r)?)(?P<ver>.*)")
826 m = regex.match(pv)
827 if m:
828 pv = m.group('ver')
829 pfx = m.group('pfx')
830
831 return (pv, pfx, sfx)
832
833def get_recipe_upstream_version(rd):
834 """
835 Get upstream version of recipe using bb.fetch2 methods with support for
836 http, https, ftp and git.
837
838 bb.fetch2 exceptions can be raised,
839 FetchError when don't have network access or upstream site don't response.
840 NoMethodError when uri latest_versionstring method isn't implemented.
841
842 Returns a dictonary with version, type and datetime.
843 Type can be A for Automatic, M for Manual and U for Unknown.
844 """
845 from bb.fetch2 import decodeurl
846 from datetime import datetime
847
848 ru = {}
849 ru['version'] = ''
850 ru['type'] = 'U'
851 ru['datetime'] = ''
852
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500853 pv = rd.getVar('PV')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500854
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500855 # XXX: If don't have SRC_URI means that don't have upstream sources so
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500856 # returns the current recipe version, so that upstream version check
857 # declares a match.
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500858 src_uris = rd.getVar('SRC_URI')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500859 if not src_uris:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500860 ru['version'] = pv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500861 ru['type'] = 'M'
862 ru['datetime'] = datetime.now()
863 return ru
864
865 # XXX: we suppose that the first entry points to the upstream sources
866 src_uri = src_uris.split()[0]
867 uri_type, _, _, _, _, _ = decodeurl(src_uri)
868
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500869 manual_upstream_version = rd.getVar("RECIPE_UPSTREAM_VERSION")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500870 if manual_upstream_version:
871 # manual tracking of upstream version.
872 ru['version'] = manual_upstream_version
873 ru['type'] = 'M'
874
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500875 manual_upstream_date = rd.getVar("CHECK_DATE")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500876 if manual_upstream_date:
877 date = datetime.strptime(manual_upstream_date, "%b %d, %Y")
878 else:
879 date = datetime.now()
880 ru['datetime'] = date
881
882 elif uri_type == "file":
883 # files are always up-to-date
884 ru['version'] = pv
885 ru['type'] = 'A'
886 ru['datetime'] = datetime.now()
887 else:
888 ud = bb.fetch2.FetchData(src_uri, rd)
889 pupver = ud.method.latest_versionstring(ud, rd)
890 (upversion, revision) = pupver
891
892 # format git version version+gitAUTOINC+HASH
893 if uri_type == 'git':
894 (pv, pfx, sfx) = get_recipe_pv_without_srcpv(pv, uri_type)
895
896 # if contains revision but not upversion use current pv
897 if upversion == '' and revision:
898 upversion = pv
899
900 if upversion:
901 tmp = upversion
902 upversion = ''
903
904 if pfx:
905 upversion = pfx + tmp
906 else:
907 upversion = tmp
908
909 if sfx:
910 upversion = upversion + sfx + revision[:10]
911
912 if upversion:
913 ru['version'] = upversion
914 ru['type'] = 'A'
915
916 ru['datetime'] = datetime.now()
917
918 return ru