blob: cab8e40152e2d08060be26a198ddb8a1993d6cde [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#
Brad Bishopd7bf8c12018-02-25 22:55:05 -05005# Copyright (C) 2013-2017 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))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500191
192 # Split on newlines - this isn't strictly necessary if you are only
193 # going to write the output to disk, but if you want to compare it
194 # (as patch_recipe_file() will do if patch=True) then it's important.
195 addlines = [line for l in addlines for line in l.splitlines(True)]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500196 if rewindcomments:
197 # Ensure we insert the lines before any leading comments
198 # (that we'd want to ensure remain leading the next value)
199 for i, ln in reversed(list(enumerate(lines))):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600200 if not ln.startswith('#'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500201 lines[i+1:i+1] = addlines
202 break
203 else:
204 lines.extend(addlines)
205 else:
206 lines.extend(addlines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500207
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500208 existingnames = []
209 def patch_recipe_varfunc(varname, origvalue, op, newlines):
210 if modifying:
211 # Insert anything that should come before this variable
212 pos = get_recipe_pos(varname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600213 for k in list(remainingnames):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500214 if remainingnames[k] > -1 and pos >= remainingnames[k] and not k in existingnames:
215 outputvalue(k, newlines, rewindcomments=True)
216 del remainingnames[k]
217 # Now change this variable, if it needs to be changed
218 if varname in existingnames and op in ['+=', '=', '=+']:
219 if varname in remainingnames:
220 outputvalue(varname, newlines)
221 del remainingnames[varname]
222 return None, None, 0, True
223 else:
224 if varname in values:
225 existingnames.append(varname)
226 return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500227
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500228 # First run - establish which values we want to set are already in the file
229 varlist = [re.escape(item) for item in values.keys()]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600230 bb.utils.edit_metadata(fromlines, varlist, patch_recipe_varfunc)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500231 # Second run - actually set everything
232 modifying = True
233 varlist.extend(recipe_progression_restrs)
234 changed, tolines = bb.utils.edit_metadata(fromlines, varlist, patch_recipe_varfunc, match_overrides=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500235
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500236 if remainingnames:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600237 if tolines and tolines[-1].strip() != '':
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500238 tolines.append('\n')
239 for k in remainingnames.keys():
240 outputvalue(k, tolines)
241
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600242 return changed, tolines
243
244
245def patch_recipe_file(fn, values, patch=False, relpath=''):
246 """Update or insert variable values into a recipe file (assuming you
247 have already identified the exact file you want to update.)
248 Note that some manual inspection/intervention may be required
249 since this cannot handle all situations.
250 """
251
252 with open(fn, 'r') as f:
253 fromlines = f.readlines()
254
255 _, tolines = patch_recipe_lines(fromlines, values)
256
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500257 if patch:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500258 relfn = os.path.relpath(fn, relpath)
259 diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500260 return diff
261 else:
262 with open(fn, 'w') as f:
263 f.writelines(tolines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500264 return None
265
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500266
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500267def localise_file_vars(fn, varfiles, varlist):
268 """Given a list of variables and variable history (fetched with get_var_files())
269 find where each variable should be set/changed. This handles for example where a
270 recipe includes an inc file where variables might be changed - in most cases
271 we want to update the inc file when changing the variable value rather than adding
272 it to the recipe itself.
273 """
274 fndir = os.path.dirname(fn) + os.sep
275
276 first_meta_file = None
277 for v in meta_vars:
278 f = varfiles.get(v, None)
279 if f:
280 actualdir = os.path.dirname(f) + os.sep
281 if actualdir.startswith(fndir):
282 first_meta_file = f
283 break
284
285 filevars = defaultdict(list)
286 for v in varlist:
287 f = varfiles[v]
288 # Only return files that are in the same directory as the recipe or in some directory below there
289 # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable
290 # in if we were going to set a value specific to this recipe)
291 if f:
292 actualfile = f
293 else:
294 # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it
295 if first_meta_file:
296 actualfile = first_meta_file
297 else:
298 actualfile = fn
299
300 actualdir = os.path.dirname(actualfile) + os.sep
301 if not actualdir.startswith(fndir):
302 actualfile = fn
303 filevars[actualfile].append(v)
304
305 return filevars
306
307def patch_recipe(d, fn, varvalues, patch=False, relpath=''):
308 """Modify a list of variable values in the specified recipe. Handles inc files if
309 used by the recipe.
310 """
311 varlist = varvalues.keys()
312 varfiles = get_var_files(fn, varlist, d)
313 locs = localise_file_vars(fn, varfiles, varlist)
314 patches = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600315 for f,v in locs.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316 vals = {k: varvalues[k] for k in v}
317 patchdata = patch_recipe_file(f, vals, patch, relpath)
318 if patch:
319 patches.append(patchdata)
320
321 if patch:
322 return patches
323 else:
324 return None
325
326
327
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500328def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True, all_variants=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500329 """Copy (local) recipe files, including both files included via include/require,
330 and files referred to in the SRC_URI variable."""
331 import bb.fetch2
332 import oe.path
333
334 # FIXME need a warning if the unexpanded SRC_URI value contains variable references
335
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500336 uri_values = []
337 localpaths = []
338 def fetch_urls(rdata):
339 # Collect the local paths from SRC_URI
340 srcuri = rdata.getVar('SRC_URI') or ""
341 if srcuri not in uri_values:
342 fetch = bb.fetch2.Fetch(srcuri.split(), rdata)
343 if download:
344 fetch.download()
345 for pth in fetch.localpaths():
346 if pth not in localpaths:
347 localpaths.append(pth)
348 uri_values.append(srcuri)
349
350 fetch_urls(d)
351 if all_variants:
352 # Get files for other variants e.g. in the case of a SRC_URI_append
353 localdata = bb.data.createCopy(d)
354 variants = (localdata.getVar('BBCLASSEXTEND') or '').split()
355 if variants:
356 # Ensure we handle class-target if we're dealing with one of the variants
357 variants.append('target')
358 for variant in variants:
359 localdata.setVar('CLASSOVERRIDE', 'class-%s' % variant)
360 fetch_urls(localdata)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500361
362 # Copy local files to target directory and gather any remote files
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500363 bb_dir = os.path.abspath(os.path.dirname(d.getVar('FILE'))) + os.sep
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500364 remotes = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600365 copied = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500366 # Need to do this in two steps since we want to check against the absolute path
367 includes = [os.path.abspath(path) for path in d.getVar('BBINCLUDED').split() if os.path.exists(path)]
368 # We also check this below, but we don't want any items in this list being considered remotes
369 includes = [path for path in includes if path.startswith(bb_dir)]
370 for path in localpaths + includes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500371 # Only import files that are under the meta directory
372 if path.startswith(bb_dir):
373 if not whole_dir:
374 relpath = os.path.relpath(path, bb_dir)
375 subdir = os.path.join(tgt_dir, os.path.dirname(relpath))
376 if not os.path.exists(subdir):
377 os.makedirs(subdir)
378 shutil.copy2(path, os.path.join(tgt_dir, relpath))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600379 copied.append(relpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500380 else:
381 remotes.append(path)
382 # Simply copy whole meta dir, if requested
383 if whole_dir:
384 shutil.copytree(bb_dir, tgt_dir)
385
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600386 return copied, remotes
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500387
388
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500389def get_recipe_local_files(d, patches=False, archives=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500390 """Get a list of local files in SRC_URI within a recipe."""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500391 import oe.patch
392 uris = (d.getVar('SRC_URI') or "").split()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500393 fetch = bb.fetch2.Fetch(uris, d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500394 # FIXME this list should be factored out somewhere else (such as the
395 # fetcher) though note that this only encompasses actual container formats
396 # i.e. that can contain multiple files as opposed to those that only
397 # contain a compressed stream (i.e. .tar.gz as opposed to just .gz)
398 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 -0500399 ret = {}
400 for uri in uris:
401 if fetch.ud[uri].type == 'file':
402 if (not patches and
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500403 oe.patch.patch_path(uri, fetch, '', expand=False)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500404 continue
405 # Skip files that are referenced by absolute path
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600406 fname = fetch.ud[uri].basepath
407 if os.path.isabs(fname):
408 continue
409 # Handle subdir=
410 subdir = fetch.ud[uri].parm.get('subdir', '')
411 if subdir:
412 if os.path.isabs(subdir):
413 continue
414 fname = os.path.join(subdir, fname)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500415 localpath = fetch.localpath(uri)
416 if not archives:
417 # Ignore archives that will be unpacked
418 if localpath.endswith(tuple(archive_exts)):
419 unpack = fetch.ud[uri].parm.get('unpack', True)
420 if unpack:
421 continue
422 ret[fname] = localpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500423 return ret
424
425
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500426def get_recipe_patches(d):
427 """Get a list of the patches included in SRC_URI within a recipe."""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500428 import oe.patch
429 patches = oe.patch.src_patches(d, expand=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500430 patchfiles = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500431 for patch in patches:
432 _, _, local, _, _, parm = bb.fetch.decodeurl(patch)
433 patchfiles.append(local)
434 return patchfiles
435
436
437def get_recipe_patched_files(d):
438 """
439 Get the list of patches for a recipe along with the files each patch modifies.
440 Params:
441 d: the datastore for the recipe
442 Returns:
443 a dict mapping patch file path to a list of tuples of changed files and
444 change mode ('A' for add, 'D' for delete or 'M' for modify)
445 """
446 import oe.patch
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500447 patches = oe.patch.src_patches(d, expand=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500448 patchedfiles = {}
449 for patch in patches:
450 _, _, patchfile, _, _, parm = bb.fetch.decodeurl(patch)
451 striplevel = int(parm['striplevel'])
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500452 patchedfiles[patchfile] = oe.patch.PatchSet.getPatchedFiles(patchfile, striplevel, os.path.join(d.getVar('S'), parm.get('patchdir', '')))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500453 return patchedfiles
454
455
456def validate_pn(pn):
457 """Perform validation on a recipe name (PN) for a new recipe."""
458 reserved_names = ['forcevariable', 'append', 'prepend', 'remove']
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600459 if not re.match('^[0-9a-z-.+]+$', pn):
460 return 'Recipe name "%s" is invalid: only characters 0-9, a-z, -, + and . are allowed' % pn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500461 elif pn in reserved_names:
462 return 'Recipe name "%s" is invalid: is a reserved keyword' % pn
463 elif pn.startswith('pn-'):
464 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500465 elif pn.endswith(('.bb', '.bbappend', '.bbclass', '.inc', '.conf')):
466 return 'Recipe name "%s" is invalid: should be just a name, not a file name' % pn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500467 return ''
468
469
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600470def get_bbfile_path(d, destdir, extrapathhint=None):
471 """
472 Determine the correct path for a recipe within a layer
473 Parameters:
474 d: Recipe-specific datastore
475 destdir: destination directory. Can be the path to the base of the layer or a
476 partial path somewhere within the layer.
477 extrapathhint: a path relative to the base of the layer to try
478 """
479 import bb.cookerdata
480
481 destdir = os.path.abspath(destdir)
482 destlayerdir = find_layerdir(destdir)
483
484 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
485 confdata = d.createCopy()
486 confdata.setVar('BBFILES', '')
487 confdata.setVar('LAYERDIR', destlayerdir)
488 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
489 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500490 pn = d.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600491
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500492 bbfilespecs = (confdata.getVar('BBFILES') or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600493 if destdir == destlayerdir:
494 for bbfilespec in bbfilespecs:
495 if not bbfilespec.endswith('.bbappend'):
496 for match in glob.glob(bbfilespec):
497 splitext = os.path.splitext(os.path.basename(match))
498 if splitext[1] == '.bb':
499 mpn = splitext[0].split('_')[0]
500 if mpn == pn:
501 return os.path.dirname(match)
502
503 # Try to make up a path that matches BBFILES
504 # this is a little crude, but better than nothing
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500505 bpn = d.getVar('BPN')
506 recipefn = os.path.basename(d.getVar('FILE'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600507 pathoptions = [destdir]
508 if extrapathhint:
509 pathoptions.append(os.path.join(destdir, extrapathhint))
510 if destdir == destlayerdir:
511 pathoptions.append(os.path.join(destdir, 'recipes-%s' % bpn, bpn))
512 pathoptions.append(os.path.join(destdir, 'recipes', bpn))
513 pathoptions.append(os.path.join(destdir, bpn))
514 elif not destdir.endswith(('/' + pn, '/' + bpn)):
515 pathoptions.append(os.path.join(destdir, bpn))
516 closepath = ''
517 for pathoption in pathoptions:
518 bbfilepath = os.path.join(pathoption, 'test.bb')
519 for bbfilespec in bbfilespecs:
520 if fnmatch.fnmatchcase(bbfilepath, bbfilespec):
521 return pathoption
522 return None
523
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500524def get_bbappend_path(d, destlayerdir, wildcardver=False):
525 """Determine how a bbappend for a recipe should be named and located within another layer"""
526
527 import bb.cookerdata
528
529 destlayerdir = os.path.abspath(destlayerdir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500530 recipefile = d.getVar('FILE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500531 recipefn = os.path.splitext(os.path.basename(recipefile))[0]
532 if wildcardver and '_' in recipefn:
533 recipefn = recipefn.split('_', 1)[0] + '_%'
534 appendfn = recipefn + '.bbappend'
535
536 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
537 confdata = d.createCopy()
538 confdata.setVar('BBFILES', '')
539 confdata.setVar('LAYERDIR', destlayerdir)
540 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
541 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
542
543 origlayerdir = find_layerdir(recipefile)
544 if not origlayerdir:
545 return (None, False)
546 # Now join this to the path where the bbappend is going and check if it is covered by BBFILES
547 appendpath = os.path.join(destlayerdir, os.path.relpath(os.path.dirname(recipefile), origlayerdir), appendfn)
548 closepath = ''
549 pathok = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500550 for bbfilespec in confdata.getVar('BBFILES').split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500551 if fnmatch.fnmatchcase(appendpath, bbfilespec):
552 # Our append path works, we're done
553 break
554 elif bbfilespec.startswith(destlayerdir) and fnmatch.fnmatchcase('test.bbappend', os.path.basename(bbfilespec)):
555 # Try to find the longest matching path
556 if len(bbfilespec) > len(closepath):
557 closepath = bbfilespec
558 else:
559 # Unfortunately the bbappend layer and the original recipe's layer don't have the same structure
560 if closepath:
561 # bbappend layer's layer.conf at least has a spec that picks up .bbappend files
562 # Now we just need to substitute out any wildcards
563 appendsubdir = os.path.relpath(os.path.dirname(closepath), destlayerdir)
564 if 'recipes-*' in appendsubdir:
565 # Try to copy this part from the original recipe path
566 res = re.search('/recipes-[^/]+/', recipefile)
567 if res:
568 appendsubdir = appendsubdir.replace('/recipes-*/', res.group(0))
569 # This is crude, but we have to do something
570 appendsubdir = appendsubdir.replace('*', recipefn.split('_')[0])
571 appendsubdir = appendsubdir.replace('?', 'a')
572 appendpath = os.path.join(destlayerdir, appendsubdir, appendfn)
573 else:
574 pathok = False
575 return (appendpath, pathok)
576
577
578def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, machine=None, extralines=None, removevalues=None):
579 """
580 Writes a bbappend file for a recipe
581 Parameters:
582 rd: data dictionary for the recipe
583 destlayerdir: base directory of the layer to place the bbappend in
584 (subdirectory path from there will be determined automatically)
585 srcfiles: dict of source files to add to SRC_URI, where the value
586 is the full path to the file to be added, and the value is the
587 original filename as it would appear in SRC_URI or None if it
588 isn't already present. You may pass None for this parameter if
589 you simply want to specify your own content via the extralines
590 parameter.
591 install: dict mapping entries in srcfiles to a tuple of two elements:
592 install path (*without* ${D} prefix) and permission value (as a
593 string, e.g. '0644').
594 wildcardver: True to use a % wildcard in the bbappend filename, or
595 False to make the bbappend specific to the recipe version.
596 machine:
597 If specified, make the changes in the bbappend specific to this
598 machine. This will also cause PACKAGE_ARCH = "${MACHINE_ARCH}"
599 to be added to the bbappend.
600 extralines:
601 Extra lines to add to the bbappend. This may be a dict of name
602 value pairs, or simply a list of the lines.
603 removevalues:
604 Variable values to remove - a dict of names/values.
605 """
606
607 if not removevalues:
608 removevalues = {}
609
610 # Determine how the bbappend should be named
611 appendpath, pathok = get_bbappend_path(rd, destlayerdir, wildcardver)
612 if not appendpath:
613 bb.error('Unable to determine layer directory containing %s' % recipefile)
614 return (None, None)
615 if not pathok:
616 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)))
617
618 appenddir = os.path.dirname(appendpath)
619 bb.utils.mkdirhier(appenddir)
620
621 # FIXME check if the bbappend doesn't get overridden by a higher priority layer?
622
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500623 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500624 if not os.path.abspath(destlayerdir) in layerdirs:
625 bb.warn('Specified layer is not currently enabled in bblayers.conf, you will need to add it before this bbappend will be active')
626
627 bbappendlines = []
628 if extralines:
629 if isinstance(extralines, dict):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600630 for name, value in extralines.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500631 bbappendlines.append((name, '=', value))
632 else:
633 # Do our best to split it
634 for line in extralines:
635 if line[-1] == '\n':
636 line = line[:-1]
637 splitline = line.split(None, 2)
638 if len(splitline) == 3:
639 bbappendlines.append(tuple(splitline))
640 else:
641 raise Exception('Invalid extralines value passed')
642
643 def popline(varname):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600644 for i in range(0, len(bbappendlines)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500645 if bbappendlines[i][0] == varname:
646 line = bbappendlines.pop(i)
647 return line
648 return None
649
650 def appendline(varname, op, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600651 for i in range(0, len(bbappendlines)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500652 item = bbappendlines[i]
653 if item[0] == varname:
654 bbappendlines[i] = (item[0], item[1], item[2] + ' ' + value)
655 break
656 else:
657 bbappendlines.append((varname, op, value))
658
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500659 destsubdir = rd.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500660 if srcfiles:
661 bbappendlines.append(('FILESEXTRAPATHS_prepend', ':=', '${THISDIR}/${PN}:'))
662
663 appendoverride = ''
664 if machine:
665 bbappendlines.append(('PACKAGE_ARCH', '=', '${MACHINE_ARCH}'))
666 appendoverride = '_%s' % machine
667 copyfiles = {}
668 if srcfiles:
669 instfunclines = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600670 for newfile, origsrcfile in srcfiles.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500671 srcfile = origsrcfile
672 srcurientry = None
673 if not srcfile:
674 srcfile = os.path.basename(newfile)
675 srcurientry = 'file://%s' % srcfile
676 # Double-check it's not there already
677 # FIXME do we care if the entry is added by another bbappend that might go away?
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500678 if not srcurientry in rd.getVar('SRC_URI').split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500679 if machine:
680 appendline('SRC_URI_append%s' % appendoverride, '=', ' ' + srcurientry)
681 else:
682 appendline('SRC_URI', '+=', srcurientry)
683 copyfiles[newfile] = srcfile
684 if install:
685 institem = install.pop(newfile, None)
686 if institem:
687 (destpath, perms) = institem
688 instdestpath = replace_dir_vars(destpath, rd)
689 instdirline = 'install -d ${D}%s' % os.path.dirname(instdestpath)
690 if not instdirline in instfunclines:
691 instfunclines.append(instdirline)
692 instfunclines.append('install -m %s ${WORKDIR}/%s ${D}%s' % (perms, os.path.basename(srcfile), instdestpath))
693 if instfunclines:
694 bbappendlines.append(('do_install_append%s()' % appendoverride, '', instfunclines))
695
696 bb.note('Writing append file %s' % appendpath)
697
698 if os.path.exists(appendpath):
699 # Work around lack of nonlocal in python 2
700 extvars = {'destsubdir': destsubdir}
701
702 def appendfile_varfunc(varname, origvalue, op, newlines):
703 if varname == 'FILESEXTRAPATHS_prepend':
704 if origvalue.startswith('${THISDIR}/'):
705 popline('FILESEXTRAPATHS_prepend')
706 extvars['destsubdir'] = rd.expand(origvalue.split('${THISDIR}/', 1)[1].rstrip(':'))
707 elif varname == 'PACKAGE_ARCH':
708 if machine:
709 popline('PACKAGE_ARCH')
710 return (machine, None, 4, False)
711 elif varname.startswith('do_install_append'):
712 func = popline(varname)
713 if func:
714 instfunclines = [line.strip() for line in origvalue.strip('\n').splitlines()]
715 for line in func[2]:
716 if not line in instfunclines:
717 instfunclines.append(line)
718 return (instfunclines, None, 4, False)
719 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500720 splitval = split_var_value(origvalue, assignment=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500721 changed = False
722 removevar = varname
723 if varname in ['SRC_URI', 'SRC_URI_append%s' % appendoverride]:
724 removevar = 'SRC_URI'
725 line = popline(varname)
726 if line:
727 if line[2] not in splitval:
728 splitval.append(line[2])
729 changed = True
730 else:
731 line = popline(varname)
732 if line:
733 splitval = [line[2]]
734 changed = True
735
736 if removevar in removevalues:
737 remove = removevalues[removevar]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600738 if isinstance(remove, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500739 if remove in splitval:
740 splitval.remove(remove)
741 changed = True
742 else:
743 for removeitem in remove:
744 if removeitem in splitval:
745 splitval.remove(removeitem)
746 changed = True
747
748 if changed:
749 newvalue = splitval
750 if len(newvalue) == 1:
751 # Ensure it's written out as one line
752 if '_append' in varname:
753 newvalue = ' ' + newvalue[0]
754 else:
755 newvalue = newvalue[0]
756 if not newvalue and (op in ['+=', '.='] or '_append' in varname):
757 # There's no point appending nothing
758 newvalue = None
759 if varname.endswith('()'):
760 indent = 4
761 else:
762 indent = -1
763 return (newvalue, None, indent, True)
764 return (origvalue, None, 4, False)
765
766 varnames = [item[0] for item in bbappendlines]
767 if removevalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600768 varnames.extend(list(removevalues.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500769
770 with open(appendpath, 'r') as f:
771 (updated, newlines) = bb.utils.edit_metadata(f, varnames, appendfile_varfunc)
772
773 destsubdir = extvars['destsubdir']
774 else:
775 updated = False
776 newlines = []
777
778 if bbappendlines:
779 for line in bbappendlines:
780 if line[0].endswith('()'):
781 newlines.append('%s {\n %s\n}\n' % (line[0], '\n '.join(line[2])))
782 else:
783 newlines.append('%s %s "%s"\n\n' % line)
784 updated = True
785
786 if updated:
787 with open(appendpath, 'w') as f:
788 f.writelines(newlines)
789
790 if copyfiles:
791 if machine:
792 destsubdir = os.path.join(destsubdir, machine)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600793 for newfile, srcfile in copyfiles.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500794 filedest = os.path.join(appenddir, destsubdir, os.path.basename(srcfile))
795 if os.path.abspath(newfile) != os.path.abspath(filedest):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500796 if newfile.startswith(tempfile.gettempdir()):
797 newfiledisp = os.path.basename(newfile)
798 else:
799 newfiledisp = newfile
800 bb.note('Copying %s to %s' % (newfiledisp, filedest))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500801 bb.utils.mkdirhier(os.path.dirname(filedest))
802 shutil.copyfile(newfile, filedest)
803
804 return (appendpath, os.path.join(appenddir, destsubdir))
805
806
807def find_layerdir(fn):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600808 """ Figure out the path to the base of the layer containing a file (e.g. a recipe)"""
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500809 pth = os.path.abspath(fn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500810 layerdir = ''
811 while pth:
812 if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')):
813 layerdir = pth
814 break
815 pth = os.path.dirname(pth)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600816 if pth == '/':
817 return None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500818 return layerdir
819
820
821def replace_dir_vars(path, d):
822 """Replace common directory paths with appropriate variable references (e.g. /etc becomes ${sysconfdir})"""
823 dirvars = {}
824 # Sort by length so we get the variables we're interested in first
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600825 for var in sorted(list(d.keys()), key=len):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500826 if var.endswith('dir') and var.lower() == var:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500827 value = d.getVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500828 if value.startswith('/') and not '\n' in value and value not in dirvars:
829 dirvars[value] = var
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600830 for dirpath in sorted(list(dirvars.keys()), reverse=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500831 path = path.replace(dirpath, '${%s}' % dirvars[dirpath])
832 return path
833
834def get_recipe_pv_without_srcpv(pv, uri_type):
835 """
836 Get PV without SRCPV common in SCM's for now only
837 support git.
838
839 Returns tuple with pv, prefix and suffix.
840 """
841 pfx = ''
842 sfx = ''
843
844 if uri_type == 'git':
845 git_regex = re.compile("(?P<pfx>v?)(?P<ver>[^\+]*)((?P<sfx>\+(git)?r?(AUTOINC\+))(?P<rev>.*))?")
846 m = git_regex.match(pv)
847
848 if m:
849 pv = m.group('ver')
850 pfx = m.group('pfx')
851 sfx = m.group('sfx')
852 else:
853 regex = re.compile("(?P<pfx>(v|r)?)(?P<ver>.*)")
854 m = regex.match(pv)
855 if m:
856 pv = m.group('ver')
857 pfx = m.group('pfx')
858
859 return (pv, pfx, sfx)
860
861def get_recipe_upstream_version(rd):
862 """
863 Get upstream version of recipe using bb.fetch2 methods with support for
864 http, https, ftp and git.
865
866 bb.fetch2 exceptions can be raised,
867 FetchError when don't have network access or upstream site don't response.
868 NoMethodError when uri latest_versionstring method isn't implemented.
869
870 Returns a dictonary with version, type and datetime.
871 Type can be A for Automatic, M for Manual and U for Unknown.
872 """
873 from bb.fetch2 import decodeurl
874 from datetime import datetime
875
876 ru = {}
877 ru['version'] = ''
878 ru['type'] = 'U'
879 ru['datetime'] = ''
880
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500881 pv = rd.getVar('PV')
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500882
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500883 # XXX: If don't have SRC_URI means that don't have upstream sources so
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500884 # returns the current recipe version, so that upstream version check
885 # declares a match.
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500886 src_uris = rd.getVar('SRC_URI')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500887 if not src_uris:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500888 ru['version'] = pv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500889 ru['type'] = 'M'
890 ru['datetime'] = datetime.now()
891 return ru
892
893 # XXX: we suppose that the first entry points to the upstream sources
894 src_uri = src_uris.split()[0]
895 uri_type, _, _, _, _, _ = decodeurl(src_uri)
896
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500897 manual_upstream_version = rd.getVar("RECIPE_UPSTREAM_VERSION")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500898 if manual_upstream_version:
899 # manual tracking of upstream version.
900 ru['version'] = manual_upstream_version
901 ru['type'] = 'M'
902
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500903 manual_upstream_date = rd.getVar("CHECK_DATE")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500904 if manual_upstream_date:
905 date = datetime.strptime(manual_upstream_date, "%b %d, %Y")
906 else:
907 date = datetime.now()
908 ru['datetime'] = date
909
910 elif uri_type == "file":
911 # files are always up-to-date
912 ru['version'] = pv
913 ru['type'] = 'A'
914 ru['datetime'] = datetime.now()
915 else:
916 ud = bb.fetch2.FetchData(src_uri, rd)
917 pupver = ud.method.latest_versionstring(ud, rd)
918 (upversion, revision) = pupver
919
920 # format git version version+gitAUTOINC+HASH
921 if uri_type == 'git':
922 (pv, pfx, sfx) = get_recipe_pv_without_srcpv(pv, uri_type)
923
924 # if contains revision but not upversion use current pv
925 if upversion == '' and revision:
926 upversion = pv
927
928 if upversion:
929 tmp = upversion
930 upversion = ''
931
932 if pfx:
933 upversion = pfx + tmp
934 else:
935 upversion = tmp
936
937 if sfx:
938 upversion = upversion + sfx + revision[:10]
939
940 if upversion:
941 ru['version'] = upversion
942 ru['type'] = 'A'
943
944 ru['datetime'] = datetime.now()
945
946 return ru