blob: de1fbdd3a8c82822c4c10538969417edda7af7aa [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#
Brad Bishopc342db32019-05-15 21:57:59 -04007# SPDX-License-Identifier: GPL-2.0-only
8#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05009
10import sys
11import os
12import os.path
13import tempfile
14import textwrap
15import difflib
Patrick Williamsc0f7c042017-02-23 20:41:17 -060016from . import utils
Patrick Williamsc124f4f2015-09-15 14:41:29 -050017import shutil
18import re
19import fnmatch
Patrick Williamsc0f7c042017-02-23 20:41:17 -060020import glob
Brad Bishop19323692019-04-05 15:28:33 -040021import bb.tinfoil
Patrick Williamsc124f4f2015-09-15 14:41:29 -050022
Brad Bishop19323692019-04-05 15:28:33 -040023from collections import OrderedDict, defaultdict
24from bb.utils import vercmp_string
Patrick Williamsc124f4f2015-09-15 14:41:29 -050025
26# Help us to find places to insert values
Andrew Geissler5082cc72023-09-11 08:41:39 -040027recipe_progression = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION', 'LICENSE', 'LICENSE_FLAGS', 'LIC_FILES_CHKSUM', 'PROVIDES', 'DEPENDS', 'PR', 'PV', 'SRCREV', '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()', 'BBCLASSEXTEND']
Patrick Williamsc124f4f2015-09-15 14:41:29 -050028# Variables that sometimes are a bit long but shouldn't be wrapped
Patrick Williams169d7bc2024-01-05 11:33:25 -060029nowrap_vars = ['SUMMARY', 'HOMEPAGE', 'BUGTRACKER', r'SRC_URI\[(.+\.)?md5sum\]', r'SRC_URI\[(.+\.)?sha[0-9]+sum\]']
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030list_vars = ['SRC_URI', 'LIC_FILES_CHKSUM']
31meta_vars = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION']
32
33
Brad Bishop19323692019-04-05 15:28:33 -040034def simplify_history(history, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050035 """
Brad Bishop19323692019-04-05 15:28:33 -040036 Eliminate any irrelevant events from a variable history
Patrick Williamsc124f4f2015-09-15 14:41:29 -050037 """
Brad Bishop19323692019-04-05 15:28:33 -040038 ret_history = []
39 has_set = False
40 # Go backwards through the history and remove any immediate operations
41 # before the most recent set
42 for event in reversed(history):
43 if 'flag' in event or not 'file' in event:
44 continue
45 if event['op'] == 'set':
46 if has_set:
47 continue
48 has_set = True
49 elif event['op'] in ('append', 'prepend', 'postdot', 'predot'):
Patrick Williams213cb262021-08-07 19:21:33 -050050 # Reminder: "append" and "prepend" mean += and =+ respectively, NOT :append / :prepend
Brad Bishop19323692019-04-05 15:28:33 -040051 if has_set:
52 continue
53 ret_history.insert(0, event)
54 return ret_history
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055
56
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057def get_var_files(fn, varlist, d):
58 """Find the file in which each of a list of variables is set.
59 Note: requires variable history to be enabled when parsing.
60 """
61 varfiles = {}
62 for v in varlist:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050063 files = []
Brad Bishop19323692019-04-05 15:28:33 -040064 if '[' in v:
65 varsplit = v.split('[')
66 varflag = varsplit[1].split(']')[0]
67 history = d.varhistory.variable(varsplit[0])
68 for event in history:
69 if 'file' in event and event.get('flag', '') == varflag:
70 files.append(event['file'])
71 else:
72 history = d.varhistory.variable(v)
73 for event in history:
74 if 'file' in event and not 'flag' in event:
75 files.append(event['file'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -050076 if files:
77 actualfile = files[-1]
78 else:
79 actualfile = None
80 varfiles[v] = actualfile
81
82 return varfiles
83
84
Patrick Williamsf1e5d692016-03-30 15:21:19 -050085def split_var_value(value, assignment=True):
86 """
87 Split a space-separated variable's value into a list of items,
88 taking into account that some of the items might be made up of
89 expressions containing spaces that should not be split.
90 Parameters:
91 value:
92 The string value to split
93 assignment:
94 True to assume that the value represents an assignment
95 statement, False otherwise. If True, and an assignment
96 statement is passed in the first item in
97 the returned list will be the part of the assignment
98 statement up to and including the opening quote character,
99 and the last item will be the closing quote.
100 """
101 inexpr = 0
102 lastchar = None
103 out = []
104 buf = ''
105 for char in value:
106 if char == '{':
107 if lastchar == '$':
108 inexpr += 1
109 elif char == '}':
110 inexpr -= 1
111 elif assignment and char in '"\'' and inexpr == 0:
112 if buf:
113 out.append(buf)
114 out.append(char)
115 char = ''
116 buf = ''
117 elif char.isspace() and inexpr == 0:
118 char = ''
119 if buf:
120 out.append(buf)
121 buf = ''
122 buf += char
123 lastchar = char
124 if buf:
125 out.append(buf)
126
127 # Join together assignment statement and opening quote
128 outlist = out
129 if assignment:
130 assigfound = False
131 for idx, item in enumerate(out):
132 if '=' in item:
133 assigfound = True
134 if assigfound:
135 if '"' in item or "'" in item:
136 outlist = [' '.join(out[:idx+1])]
137 outlist.extend(out[idx+1:])
138 break
139 return outlist
140
141
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600142def patch_recipe_lines(fromlines, values, trailing_newline=True):
143 """Update or insert variable values into lines from a recipe.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500144 Note that some manual inspection/intervention may be required
145 since this cannot handle all situations.
146 """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500147
148 import bb.utils
149
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600150 if trailing_newline:
151 newline = '\n'
152 else:
153 newline = ''
154
Brad Bishop316dfdd2018-06-25 12:45:53 -0400155 nowrap_vars_res = []
156 for item in nowrap_vars:
157 nowrap_vars_res.append(re.compile('^%s$' % item))
158
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500159 recipe_progression_res = []
160 recipe_progression_restrs = []
161 for item in recipe_progression:
162 if item.endswith('()'):
163 key = item[:-2]
164 else:
165 key = item
Brad Bishop19323692019-04-05 15:28:33 -0400166 restr = r'%s(_[a-zA-Z0-9-_$(){}]+|\[[^\]]*\])?' % key
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500167 if item.endswith('()'):
168 recipe_progression_restrs.append(restr + '()')
169 else:
170 recipe_progression_restrs.append(restr)
171 recipe_progression_res.append(re.compile('^%s$' % restr))
172
173 def get_recipe_pos(variable):
174 for i, p in enumerate(recipe_progression_res):
175 if p.match(variable):
176 return i
177 return -1
178
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500179 remainingnames = {}
180 for k in values.keys():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500181 remainingnames[k] = get_recipe_pos(k)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600182 remainingnames = OrderedDict(sorted(remainingnames.items(), key=lambda x: x[1]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500183
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500184 modifying = False
185
186 def outputvalue(name, lines, rewindcomments=False):
187 if values[name] is None:
188 return
Brad Bishop19323692019-04-05 15:28:33 -0400189 if isinstance(values[name], tuple):
190 op, value = values[name]
191 if op == '+=' and value.strip() == '':
192 return
193 else:
194 value = values[name]
195 op = '='
196 rawtext = '%s %s "%s"%s' % (name, op, value, newline)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500197 addlines = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400198 nowrap = False
199 for nowrap_re in nowrap_vars_res:
200 if nowrap_re.match(name):
201 nowrap = True
202 break
203 if nowrap:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500204 addlines.append(rawtext)
205 elif name in list_vars:
Brad Bishop19323692019-04-05 15:28:33 -0400206 splitvalue = split_var_value(value, assignment=False)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500207 if len(splitvalue) > 1:
208 linesplit = ' \\\n' + (' ' * (len(name) + 4))
Brad Bishop19323692019-04-05 15:28:33 -0400209 addlines.append('%s %s "%s%s"%s' % (name, op, linesplit.join(splitvalue), linesplit, newline))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500210 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500211 addlines.append(rawtext)
212 else:
213 wrapped = textwrap.wrap(rawtext)
214 for wrapline in wrapped[:-1]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600215 addlines.append('%s \\%s' % (wrapline, newline))
216 addlines.append('%s%s' % (wrapped[-1], newline))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500217
218 # Split on newlines - this isn't strictly necessary if you are only
219 # going to write the output to disk, but if you want to compare it
220 # (as patch_recipe_file() will do if patch=True) then it's important.
221 addlines = [line for l in addlines for line in l.splitlines(True)]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500222 if rewindcomments:
223 # Ensure we insert the lines before any leading comments
224 # (that we'd want to ensure remain leading the next value)
225 for i, ln in reversed(list(enumerate(lines))):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600226 if not ln.startswith('#'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500227 lines[i+1:i+1] = addlines
228 break
229 else:
230 lines.extend(addlines)
231 else:
232 lines.extend(addlines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500233
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500234 existingnames = []
235 def patch_recipe_varfunc(varname, origvalue, op, newlines):
236 if modifying:
237 # Insert anything that should come before this variable
238 pos = get_recipe_pos(varname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600239 for k in list(remainingnames):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500240 if remainingnames[k] > -1 and pos >= remainingnames[k] and not k in existingnames:
241 outputvalue(k, newlines, rewindcomments=True)
242 del remainingnames[k]
243 # Now change this variable, if it needs to be changed
244 if varname in existingnames and op in ['+=', '=', '=+']:
245 if varname in remainingnames:
246 outputvalue(varname, newlines)
247 del remainingnames[varname]
248 return None, None, 0, True
249 else:
250 if varname in values:
251 existingnames.append(varname)
252 return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500253
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500254 # First run - establish which values we want to set are already in the file
255 varlist = [re.escape(item) for item in values.keys()]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600256 bb.utils.edit_metadata(fromlines, varlist, patch_recipe_varfunc)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500257 # Second run - actually set everything
258 modifying = True
259 varlist.extend(recipe_progression_restrs)
260 changed, tolines = bb.utils.edit_metadata(fromlines, varlist, patch_recipe_varfunc, match_overrides=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500261
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500262 if remainingnames:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600263 if tolines and tolines[-1].strip() != '':
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500264 tolines.append('\n')
265 for k in remainingnames.keys():
266 outputvalue(k, tolines)
267
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600268 return changed, tolines
269
270
Brad Bishop316dfdd2018-06-25 12:45:53 -0400271def patch_recipe_file(fn, values, patch=False, relpath='', redirect_output=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600272 """Update or insert variable values into a recipe file (assuming you
273 have already identified the exact file you want to update.)
274 Note that some manual inspection/intervention may be required
275 since this cannot handle all situations.
276 """
277
278 with open(fn, 'r') as f:
279 fromlines = f.readlines()
280
281 _, tolines = patch_recipe_lines(fromlines, values)
282
Brad Bishop316dfdd2018-06-25 12:45:53 -0400283 if redirect_output:
284 with open(os.path.join(redirect_output, os.path.basename(fn)), 'w') as f:
285 f.writelines(tolines)
286 return None
287 elif patch:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500288 relfn = os.path.relpath(fn, relpath)
289 diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500290 return diff
291 else:
292 with open(fn, 'w') as f:
293 f.writelines(tolines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500294 return None
295
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500296
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500297def localise_file_vars(fn, varfiles, varlist):
298 """Given a list of variables and variable history (fetched with get_var_files())
299 find where each variable should be set/changed. This handles for example where a
300 recipe includes an inc file where variables might be changed - in most cases
301 we want to update the inc file when changing the variable value rather than adding
302 it to the recipe itself.
303 """
304 fndir = os.path.dirname(fn) + os.sep
305
306 first_meta_file = None
307 for v in meta_vars:
308 f = varfiles.get(v, None)
309 if f:
310 actualdir = os.path.dirname(f) + os.sep
311 if actualdir.startswith(fndir):
312 first_meta_file = f
313 break
314
315 filevars = defaultdict(list)
316 for v in varlist:
317 f = varfiles[v]
318 # Only return files that are in the same directory as the recipe or in some directory below there
319 # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable
320 # in if we were going to set a value specific to this recipe)
321 if f:
322 actualfile = f
323 else:
324 # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it
325 if first_meta_file:
326 actualfile = first_meta_file
327 else:
328 actualfile = fn
329
330 actualdir = os.path.dirname(actualfile) + os.sep
331 if not actualdir.startswith(fndir):
332 actualfile = fn
333 filevars[actualfile].append(v)
334
335 return filevars
336
Brad Bishop316dfdd2018-06-25 12:45:53 -0400337def patch_recipe(d, fn, varvalues, patch=False, relpath='', redirect_output=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500338 """Modify a list of variable values in the specified recipe. Handles inc files if
339 used by the recipe.
340 """
Brad Bishop19323692019-04-05 15:28:33 -0400341 overrides = d.getVar('OVERRIDES').split(':')
342 def override_applicable(hevent):
343 op = hevent['op']
344 if '[' in op:
Patrick Williams213cb262021-08-07 19:21:33 -0500345 opoverrides = op.split('[')[1].split(']')[0].split(':')
Brad Bishop19323692019-04-05 15:28:33 -0400346 for opoverride in opoverrides:
347 if not opoverride in overrides:
348 return False
349 return True
350
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500351 varlist = varvalues.keys()
Brad Bishop19323692019-04-05 15:28:33 -0400352 fn = os.path.abspath(fn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500353 varfiles = get_var_files(fn, varlist, d)
354 locs = localise_file_vars(fn, varfiles, varlist)
355 patches = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600356 for f,v in locs.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500357 vals = {k: varvalues[k] for k in v}
Brad Bishop19323692019-04-05 15:28:33 -0400358 f = os.path.abspath(f)
359 if f == fn:
360 extravals = {}
361 for var, value in vals.items():
362 if var in list_vars:
363 history = simplify_history(d.varhistory.variable(var), d)
364 recipe_set = False
365 for event in history:
366 if os.path.abspath(event['file']) == fn:
367 if event['op'] == 'set':
368 recipe_set = True
369 if not recipe_set:
370 for event in history:
Patrick Williams213cb262021-08-07 19:21:33 -0500371 if event['op'].startswith(':remove'):
Brad Bishop19323692019-04-05 15:28:33 -0400372 continue
373 if not override_applicable(event):
374 continue
375 newvalue = value.replace(event['detail'], '')
Patrick Williams213cb262021-08-07 19:21:33 -0500376 if newvalue == value and os.path.abspath(event['file']) == fn and event['op'].startswith(':'):
377 op = event['op'].replace('[', ':').replace(']', '')
Brad Bishop19323692019-04-05 15:28:33 -0400378 extravals[var + op] = None
379 value = newvalue
380 vals[var] = ('+=', value)
381 vals.update(extravals)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400382 patchdata = patch_recipe_file(f, vals, patch, relpath, redirect_output)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500383 if patch:
384 patches.append(patchdata)
385
386 if patch:
387 return patches
388 else:
389 return None
390
391
392
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500393def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True, all_variants=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500394 """Copy (local) recipe files, including both files included via include/require,
395 and files referred to in the SRC_URI variable."""
396 import bb.fetch2
397 import oe.path
398
399 # FIXME need a warning if the unexpanded SRC_URI value contains variable references
400
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500401 uri_values = []
402 localpaths = []
403 def fetch_urls(rdata):
404 # Collect the local paths from SRC_URI
405 srcuri = rdata.getVar('SRC_URI') or ""
406 if srcuri not in uri_values:
407 fetch = bb.fetch2.Fetch(srcuri.split(), rdata)
408 if download:
409 fetch.download()
410 for pth in fetch.localpaths():
411 if pth not in localpaths:
Andrew Geissler9b4d8b02021-02-19 12:26:16 -0600412 localpaths.append(os.path.abspath(pth))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500413 uri_values.append(srcuri)
414
415 fetch_urls(d)
416 if all_variants:
Patrick Williams213cb262021-08-07 19:21:33 -0500417 # Get files for other variants e.g. in the case of a SRC_URI:append
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500418 localdata = bb.data.createCopy(d)
419 variants = (localdata.getVar('BBCLASSEXTEND') or '').split()
420 if variants:
421 # Ensure we handle class-target if we're dealing with one of the variants
422 variants.append('target')
423 for variant in variants:
424 localdata.setVar('CLASSOVERRIDE', 'class-%s' % variant)
425 fetch_urls(localdata)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500426
427 # Copy local files to target directory and gather any remote files
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500428 bb_dir = os.path.abspath(os.path.dirname(d.getVar('FILE'))) + os.sep
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500429 remotes = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600430 copied = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500431 # Need to do this in two steps since we want to check against the absolute path
432 includes = [os.path.abspath(path) for path in d.getVar('BBINCLUDED').split() if os.path.exists(path)]
433 # We also check this below, but we don't want any items in this list being considered remotes
434 includes = [path for path in includes if path.startswith(bb_dir)]
435 for path in localpaths + includes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500436 # Only import files that are under the meta directory
437 if path.startswith(bb_dir):
438 if not whole_dir:
439 relpath = os.path.relpath(path, bb_dir)
440 subdir = os.path.join(tgt_dir, os.path.dirname(relpath))
441 if not os.path.exists(subdir):
442 os.makedirs(subdir)
443 shutil.copy2(path, os.path.join(tgt_dir, relpath))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600444 copied.append(relpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500445 else:
446 remotes.append(path)
447 # Simply copy whole meta dir, if requested
448 if whole_dir:
449 shutil.copytree(bb_dir, tgt_dir)
450
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600451 return copied, remotes
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500452
453
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500454def get_recipe_local_files(d, patches=False, archives=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500455 """Get a list of local files in SRC_URI within a recipe."""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500456 import oe.patch
457 uris = (d.getVar('SRC_URI') or "").split()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500458 fetch = bb.fetch2.Fetch(uris, d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500459 # FIXME this list should be factored out somewhere else (such as the
460 # fetcher) though note that this only encompasses actual container formats
461 # i.e. that can contain multiple files as opposed to those that only
462 # contain a compressed stream (i.e. .tar.gz as opposed to just .gz)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400463 archive_exts = ['.tar', '.tgz', '.tar.gz', '.tar.Z', '.tbz', '.tbz2', '.tar.bz2', '.txz', '.tar.xz', '.tar.lz', '.zip', '.jar', '.rpm', '.srpm', '.deb', '.ipk', '.tar.7z', '.7z']
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500464 ret = {}
465 for uri in uris:
466 if fetch.ud[uri].type == 'file':
467 if (not patches and
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500468 oe.patch.patch_path(uri, fetch, '', expand=False)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500469 continue
470 # Skip files that are referenced by absolute path
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600471 fname = fetch.ud[uri].basepath
472 if os.path.isabs(fname):
473 continue
474 # Handle subdir=
475 subdir = fetch.ud[uri].parm.get('subdir', '')
476 if subdir:
477 if os.path.isabs(subdir):
478 continue
479 fname = os.path.join(subdir, fname)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500480 localpath = fetch.localpath(uri)
481 if not archives:
482 # Ignore archives that will be unpacked
483 if localpath.endswith(tuple(archive_exts)):
484 unpack = fetch.ud[uri].parm.get('unpack', True)
485 if unpack:
486 continue
Brad Bishop19323692019-04-05 15:28:33 -0400487 if os.path.isdir(localpath):
488 for root, dirs, files in os.walk(localpath):
489 for fname in files:
490 fileabspath = os.path.join(root,fname)
491 srcdir = os.path.dirname(localpath)
492 ret[os.path.relpath(fileabspath,srcdir)] = fileabspath
493 else:
494 ret[fname] = localpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500495 return ret
496
497
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500498def get_recipe_patches(d):
499 """Get a list of the patches included in SRC_URI within a recipe."""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500500 import oe.patch
501 patches = oe.patch.src_patches(d, expand=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500502 patchfiles = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500503 for patch in patches:
504 _, _, local, _, _, parm = bb.fetch.decodeurl(patch)
505 patchfiles.append(local)
506 return patchfiles
507
508
509def get_recipe_patched_files(d):
510 """
511 Get the list of patches for a recipe along with the files each patch modifies.
512 Params:
513 d: the datastore for the recipe
514 Returns:
515 a dict mapping patch file path to a list of tuples of changed files and
516 change mode ('A' for add, 'D' for delete or 'M' for modify)
517 """
518 import oe.patch
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500519 patches = oe.patch.src_patches(d, expand=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520 patchedfiles = {}
521 for patch in patches:
522 _, _, patchfile, _, _, parm = bb.fetch.decodeurl(patch)
523 striplevel = int(parm['striplevel'])
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500524 patchedfiles[patchfile] = oe.patch.PatchSet.getPatchedFiles(patchfile, striplevel, os.path.join(d.getVar('S'), parm.get('patchdir', '')))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500525 return patchedfiles
526
527
528def validate_pn(pn):
529 """Perform validation on a recipe name (PN) for a new recipe."""
530 reserved_names = ['forcevariable', 'append', 'prepend', 'remove']
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600531 if not re.match('^[0-9a-z-.+]+$', pn):
532 return 'Recipe name "%s" is invalid: only characters 0-9, a-z, -, + and . are allowed' % pn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500533 elif pn in reserved_names:
534 return 'Recipe name "%s" is invalid: is a reserved keyword' % pn
535 elif pn.startswith('pn-'):
536 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500537 elif pn.endswith(('.bb', '.bbappend', '.bbclass', '.inc', '.conf')):
538 return 'Recipe name "%s" is invalid: should be just a name, not a file name' % pn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500539 return ''
540
541
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600542def get_bbfile_path(d, destdir, extrapathhint=None):
543 """
544 Determine the correct path for a recipe within a layer
545 Parameters:
546 d: Recipe-specific datastore
547 destdir: destination directory. Can be the path to the base of the layer or a
548 partial path somewhere within the layer.
549 extrapathhint: a path relative to the base of the layer to try
550 """
551 import bb.cookerdata
552
553 destdir = os.path.abspath(destdir)
554 destlayerdir = find_layerdir(destdir)
555
556 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
557 confdata = d.createCopy()
558 confdata.setVar('BBFILES', '')
559 confdata.setVar('LAYERDIR', destlayerdir)
560 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
561 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500562 pn = d.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600563
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500564 # Parse BBFILES_DYNAMIC and append to BBFILES
565 bbfiles_dynamic = (confdata.getVar('BBFILES_DYNAMIC') or "").split()
566 collections = (confdata.getVar('BBFILE_COLLECTIONS') or "").split()
567 invalid = []
568 for entry in bbfiles_dynamic:
569 parts = entry.split(":", 1)
570 if len(parts) != 2:
571 invalid.append(entry)
572 continue
573 l, f = parts
574 invert = l[0] == "!"
575 if invert:
576 l = l[1:]
577 if (l in collections and not invert) or (l not in collections and invert):
578 confdata.appendVar("BBFILES", " " + f)
579 if invalid:
580 return None
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500581 bbfilespecs = (confdata.getVar('BBFILES') or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600582 if destdir == destlayerdir:
583 for bbfilespec in bbfilespecs:
584 if not bbfilespec.endswith('.bbappend'):
585 for match in glob.glob(bbfilespec):
586 splitext = os.path.splitext(os.path.basename(match))
587 if splitext[1] == '.bb':
588 mpn = splitext[0].split('_')[0]
589 if mpn == pn:
590 return os.path.dirname(match)
591
592 # Try to make up a path that matches BBFILES
593 # this is a little crude, but better than nothing
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500594 bpn = d.getVar('BPN')
595 recipefn = os.path.basename(d.getVar('FILE'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600596 pathoptions = [destdir]
597 if extrapathhint:
598 pathoptions.append(os.path.join(destdir, extrapathhint))
599 if destdir == destlayerdir:
600 pathoptions.append(os.path.join(destdir, 'recipes-%s' % bpn, bpn))
601 pathoptions.append(os.path.join(destdir, 'recipes', bpn))
602 pathoptions.append(os.path.join(destdir, bpn))
603 elif not destdir.endswith(('/' + pn, '/' + bpn)):
604 pathoptions.append(os.path.join(destdir, bpn))
605 closepath = ''
606 for pathoption in pathoptions:
607 bbfilepath = os.path.join(pathoption, 'test.bb')
608 for bbfilespec in bbfilespecs:
609 if fnmatch.fnmatchcase(bbfilepath, bbfilespec):
610 return pathoption
611 return None
612
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500613def get_bbappend_path(d, destlayerdir, wildcardver=False):
614 """Determine how a bbappend for a recipe should be named and located within another layer"""
615
616 import bb.cookerdata
617
618 destlayerdir = os.path.abspath(destlayerdir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500619 recipefile = d.getVar('FILE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500620 recipefn = os.path.splitext(os.path.basename(recipefile))[0]
621 if wildcardver and '_' in recipefn:
622 recipefn = recipefn.split('_', 1)[0] + '_%'
623 appendfn = recipefn + '.bbappend'
624
625 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
626 confdata = d.createCopy()
627 confdata.setVar('BBFILES', '')
628 confdata.setVar('LAYERDIR', destlayerdir)
629 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
630 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
631
632 origlayerdir = find_layerdir(recipefile)
633 if not origlayerdir:
634 return (None, False)
635 # Now join this to the path where the bbappend is going and check if it is covered by BBFILES
636 appendpath = os.path.join(destlayerdir, os.path.relpath(os.path.dirname(recipefile), origlayerdir), appendfn)
637 closepath = ''
638 pathok = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500639 for bbfilespec in confdata.getVar('BBFILES').split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500640 if fnmatch.fnmatchcase(appendpath, bbfilespec):
641 # Our append path works, we're done
642 break
643 elif bbfilespec.startswith(destlayerdir) and fnmatch.fnmatchcase('test.bbappend', os.path.basename(bbfilespec)):
644 # Try to find the longest matching path
645 if len(bbfilespec) > len(closepath):
646 closepath = bbfilespec
647 else:
648 # Unfortunately the bbappend layer and the original recipe's layer don't have the same structure
649 if closepath:
650 # bbappend layer's layer.conf at least has a spec that picks up .bbappend files
651 # Now we just need to substitute out any wildcards
652 appendsubdir = os.path.relpath(os.path.dirname(closepath), destlayerdir)
653 if 'recipes-*' in appendsubdir:
654 # Try to copy this part from the original recipe path
655 res = re.search('/recipes-[^/]+/', recipefile)
656 if res:
657 appendsubdir = appendsubdir.replace('/recipes-*/', res.group(0))
658 # This is crude, but we have to do something
659 appendsubdir = appendsubdir.replace('*', recipefn.split('_')[0])
660 appendsubdir = appendsubdir.replace('?', 'a')
661 appendpath = os.path.join(destlayerdir, appendsubdir, appendfn)
662 else:
663 pathok = False
664 return (appendpath, pathok)
665
666
Patrick Williams169d7bc2024-01-05 11:33:25 -0600667def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, machine=None, extralines=None, removevalues=None, redirect_output=None, params=None, update_original_recipe=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500668 """
669 Writes a bbappend file for a recipe
670 Parameters:
671 rd: data dictionary for the recipe
672 destlayerdir: base directory of the layer to place the bbappend in
673 (subdirectory path from there will be determined automatically)
Patrick Williams169d7bc2024-01-05 11:33:25 -0600674 srcfiles: dict of source files to add to SRC_URI, where the key
Patrick Williamsda295312023-12-05 16:48:56 -0600675 is the full path to the file to be added, and the value is a
Patrick Williams169d7bc2024-01-05 11:33:25 -0600676 dict with following optional keys:
677 path: the original filename as it would appear in SRC_URI
678 or None if it isn't already present.
679 patchdir: the patchdir parameter
680 newname: the name to give to the new added file. None to use
681 the default value: basename(path)
Patrick Williamsda295312023-12-05 16:48:56 -0600682 You may pass None for this parameter if you simply want to specify
683 your own content via the extralines parameter.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500684 install: dict mapping entries in srcfiles to a tuple of two elements:
685 install path (*without* ${D} prefix) and permission value (as a
686 string, e.g. '0644').
687 wildcardver: True to use a % wildcard in the bbappend filename, or
688 False to make the bbappend specific to the recipe version.
689 machine:
690 If specified, make the changes in the bbappend specific to this
691 machine. This will also cause PACKAGE_ARCH = "${MACHINE_ARCH}"
692 to be added to the bbappend.
693 extralines:
694 Extra lines to add to the bbappend. This may be a dict of name
695 value pairs, or simply a list of the lines.
696 removevalues:
697 Variable values to remove - a dict of names/values.
Brad Bishop316dfdd2018-06-25 12:45:53 -0400698 redirect_output:
699 If specified, redirects writing the output file to the
700 specified directory (for dry-run purposes)
Andrew Geissler615f2f12022-07-15 14:00:58 -0500701 params:
702 Parameters to use when adding entries to SRC_URI. If specified,
703 should be a list of dicts with the same length as srcfiles.
Patrick Williams169d7bc2024-01-05 11:33:25 -0600704 update_original_recipe:
705 Force to update the original recipe instead of creating/updating
706 a bbapend. destlayerdir must contain the original recipe
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500707 """
708
709 if not removevalues:
710 removevalues = {}
711
Patrick Williams169d7bc2024-01-05 11:33:25 -0600712 recipefile = rd.getVar('FILE')
713 if update_original_recipe:
714 if destlayerdir not in recipefile:
715 bb.error("destlayerdir %s doesn't contain the original recipe (%s), cannot update it" % (destlayerdir, recipefile))
716 return (None, None)
717
718 appendpath = recipefile
719 else:
720 # Determine how the bbappend should be named
721 appendpath, pathok = get_bbappend_path(rd, destlayerdir, wildcardver)
722 if not appendpath:
723 bb.error('Unable to determine layer directory containing %s' % recipefile)
724 return (None, None)
725 if not pathok:
726 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)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500727
728 appenddir = os.path.dirname(appendpath)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400729 if not redirect_output:
730 bb.utils.mkdirhier(appenddir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500731
732 # FIXME check if the bbappend doesn't get overridden by a higher priority layer?
733
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500734 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500735 if not os.path.abspath(destlayerdir) in layerdirs:
736 bb.warn('Specified layer is not currently enabled in bblayers.conf, you will need to add it before this bbappend will be active')
737
738 bbappendlines = []
739 if extralines:
740 if isinstance(extralines, dict):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600741 for name, value in extralines.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742 bbappendlines.append((name, '=', value))
743 else:
744 # Do our best to split it
745 for line in extralines:
746 if line[-1] == '\n':
747 line = line[:-1]
748 splitline = line.split(None, 2)
749 if len(splitline) == 3:
750 bbappendlines.append(tuple(splitline))
751 else:
752 raise Exception('Invalid extralines value passed')
753
754 def popline(varname):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600755 for i in range(0, len(bbappendlines)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500756 if bbappendlines[i][0] == varname:
757 line = bbappendlines.pop(i)
758 return line
759 return None
760
761 def appendline(varname, op, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600762 for i in range(0, len(bbappendlines)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500763 item = bbappendlines[i]
764 if item[0] == varname:
765 bbappendlines[i] = (item[0], item[1], item[2] + ' ' + value)
766 break
767 else:
768 bbappendlines.append((varname, op, value))
769
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500770 destsubdir = rd.getVar('PN')
Patrick Williams169d7bc2024-01-05 11:33:25 -0600771 if not update_original_recipe and srcfiles:
Patrick Williams213cb262021-08-07 19:21:33 -0500772 bbappendlines.append(('FILESEXTRAPATHS:prepend', ':=', '${THISDIR}/${PN}:'))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500773
774 appendoverride = ''
775 if machine:
776 bbappendlines.append(('PACKAGE_ARCH', '=', '${MACHINE_ARCH}'))
Patrick Williams213cb262021-08-07 19:21:33 -0500777 appendoverride = ':%s' % machine
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500778 copyfiles = {}
779 if srcfiles:
780 instfunclines = []
Patrick Williamsda295312023-12-05 16:48:56 -0600781 for i, (newfile, param) in enumerate(srcfiles.items()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500782 srcurientry = None
Patrick Williamsda295312023-12-05 16:48:56 -0600783 if not 'path' in param or not param['path']:
Patrick Williams169d7bc2024-01-05 11:33:25 -0600784 if 'newname' in param and param['newname']:
785 srcfile = param['newname']
786 else:
787 srcfile = os.path.basename(newfile)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500788 srcurientry = 'file://%s' % srcfile
Patrick Williams169d7bc2024-01-05 11:33:25 -0600789 oldentry = None
790 for uri in rd.getVar('SRC_URI').split():
791 if srcurientry in uri:
792 oldentry = uri
Andrew Geissler615f2f12022-07-15 14:00:58 -0500793 if params and params[i]:
794 srcurientry = '%s;%s' % (srcurientry, ';'.join('%s=%s' % (k,v) for k,v in params[i].items()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500795 # Double-check it's not there already
796 # FIXME do we care if the entry is added by another bbappend that might go away?
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500797 if not srcurientry in rd.getVar('SRC_URI').split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500798 if machine:
Patrick Williams169d7bc2024-01-05 11:33:25 -0600799 if oldentry:
800 appendline('SRC_URI:remove%s' % appendoverride, '=', ' ' + oldentry)
Patrick Williams213cb262021-08-07 19:21:33 -0500801 appendline('SRC_URI:append%s' % appendoverride, '=', ' ' + srcurientry)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500802 else:
Patrick Williams169d7bc2024-01-05 11:33:25 -0600803 if oldentry:
804 if update_original_recipe:
805 removevalues['SRC_URI'] = oldentry
806 else:
807 appendline('SRC_URI:remove', '=', oldentry)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500808 appendline('SRC_URI', '+=', srcurientry)
Patrick Williamsda295312023-12-05 16:48:56 -0600809 param['path'] = srcfile
810 else:
811 srcfile = param['path']
812 copyfiles[newfile] = param
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500813 if install:
814 institem = install.pop(newfile, None)
815 if institem:
816 (destpath, perms) = institem
817 instdestpath = replace_dir_vars(destpath, rd)
818 instdirline = 'install -d ${D}%s' % os.path.dirname(instdestpath)
819 if not instdirline in instfunclines:
820 instfunclines.append(instdirline)
821 instfunclines.append('install -m %s ${WORKDIR}/%s ${D}%s' % (perms, os.path.basename(srcfile), instdestpath))
822 if instfunclines:
Patrick Williams213cb262021-08-07 19:21:33 -0500823 bbappendlines.append(('do_install:append%s()' % appendoverride, '', instfunclines))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500824
Brad Bishop316dfdd2018-06-25 12:45:53 -0400825 if redirect_output:
826 bb.note('Writing append file %s (dry-run)' % appendpath)
827 outfile = os.path.join(redirect_output, os.path.basename(appendpath))
828 # Only take a copy if the file isn't already there (this function may be called
829 # multiple times per operation when we're handling overrides)
830 if os.path.exists(appendpath) and not os.path.exists(outfile):
831 shutil.copy2(appendpath, outfile)
Patrick Williams169d7bc2024-01-05 11:33:25 -0600832 elif update_original_recipe:
833 outfile = recipefile
Brad Bishop316dfdd2018-06-25 12:45:53 -0400834 else:
835 bb.note('Writing append file %s' % appendpath)
836 outfile = appendpath
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500837
Brad Bishop316dfdd2018-06-25 12:45:53 -0400838 if os.path.exists(outfile):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500839 # Work around lack of nonlocal in python 2
840 extvars = {'destsubdir': destsubdir}
841
842 def appendfile_varfunc(varname, origvalue, op, newlines):
Patrick Williams213cb262021-08-07 19:21:33 -0500843 if varname == 'FILESEXTRAPATHS:prepend':
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500844 if origvalue.startswith('${THISDIR}/'):
Patrick Williams213cb262021-08-07 19:21:33 -0500845 popline('FILESEXTRAPATHS:prepend')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500846 extvars['destsubdir'] = rd.expand(origvalue.split('${THISDIR}/', 1)[1].rstrip(':'))
847 elif varname == 'PACKAGE_ARCH':
848 if machine:
849 popline('PACKAGE_ARCH')
850 return (machine, None, 4, False)
Patrick Williams213cb262021-08-07 19:21:33 -0500851 elif varname.startswith('do_install:append'):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500852 func = popline(varname)
853 if func:
854 instfunclines = [line.strip() for line in origvalue.strip('\n').splitlines()]
855 for line in func[2]:
856 if not line in instfunclines:
857 instfunclines.append(line)
858 return (instfunclines, None, 4, False)
859 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500860 splitval = split_var_value(origvalue, assignment=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500861 changed = False
862 removevar = varname
Patrick Williams213cb262021-08-07 19:21:33 -0500863 if varname in ['SRC_URI', 'SRC_URI:append%s' % appendoverride]:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500864 removevar = 'SRC_URI'
865 line = popline(varname)
866 if line:
867 if line[2] not in splitval:
868 splitval.append(line[2])
869 changed = True
870 else:
871 line = popline(varname)
872 if line:
873 splitval = [line[2]]
874 changed = True
875
876 if removevar in removevalues:
877 remove = removevalues[removevar]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600878 if isinstance(remove, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500879 if remove in splitval:
880 splitval.remove(remove)
881 changed = True
882 else:
883 for removeitem in remove:
884 if removeitem in splitval:
885 splitval.remove(removeitem)
886 changed = True
887
888 if changed:
889 newvalue = splitval
890 if len(newvalue) == 1:
891 # Ensure it's written out as one line
Patrick Williams213cb262021-08-07 19:21:33 -0500892 if ':append' in varname:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500893 newvalue = ' ' + newvalue[0]
894 else:
895 newvalue = newvalue[0]
Patrick Williams213cb262021-08-07 19:21:33 -0500896 if not newvalue and (op in ['+=', '.='] or ':append' in varname):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500897 # There's no point appending nothing
898 newvalue = None
899 if varname.endswith('()'):
900 indent = 4
901 else:
902 indent = -1
903 return (newvalue, None, indent, True)
904 return (origvalue, None, 4, False)
905
906 varnames = [item[0] for item in bbappendlines]
907 if removevalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600908 varnames.extend(list(removevalues.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500909
Brad Bishop316dfdd2018-06-25 12:45:53 -0400910 with open(outfile, 'r') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500911 (updated, newlines) = bb.utils.edit_metadata(f, varnames, appendfile_varfunc)
912
913 destsubdir = extvars['destsubdir']
914 else:
915 updated = False
916 newlines = []
917
918 if bbappendlines:
919 for line in bbappendlines:
920 if line[0].endswith('()'):
921 newlines.append('%s {\n %s\n}\n' % (line[0], '\n '.join(line[2])))
922 else:
923 newlines.append('%s %s "%s"\n\n' % line)
924 updated = True
925
926 if updated:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400927 with open(outfile, 'w') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500928 f.writelines(newlines)
929
930 if copyfiles:
931 if machine:
932 destsubdir = os.path.join(destsubdir, machine)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400933 if redirect_output:
934 outdir = redirect_output
935 else:
936 outdir = appenddir
Patrick Williamsda295312023-12-05 16:48:56 -0600937 for newfile, param in copyfiles.items():
938 srcfile = param['path']
939 patchdir = param.get('patchdir', ".")
940
941 if patchdir != ".":
942 newfile = os.path.join(os.path.split(newfile)[0], patchdir, os.path.split(newfile)[1])
Brad Bishop316dfdd2018-06-25 12:45:53 -0400943 filedest = os.path.join(outdir, destsubdir, os.path.basename(srcfile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500944 if os.path.abspath(newfile) != os.path.abspath(filedest):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500945 if newfile.startswith(tempfile.gettempdir()):
946 newfiledisp = os.path.basename(newfile)
947 else:
948 newfiledisp = newfile
Brad Bishop316dfdd2018-06-25 12:45:53 -0400949 if redirect_output:
950 bb.note('Copying %s to %s (dry-run)' % (newfiledisp, os.path.join(appenddir, destsubdir, os.path.basename(srcfile))))
951 else:
952 bb.note('Copying %s to %s' % (newfiledisp, filedest))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500953 bb.utils.mkdirhier(os.path.dirname(filedest))
954 shutil.copyfile(newfile, filedest)
955
956 return (appendpath, os.path.join(appenddir, destsubdir))
957
958
959def find_layerdir(fn):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600960 """ Figure out the path to the base of the layer containing a file (e.g. a recipe)"""
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500961 pth = os.path.abspath(fn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500962 layerdir = ''
963 while pth:
964 if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')):
965 layerdir = pth
966 break
967 pth = os.path.dirname(pth)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600968 if pth == '/':
969 return None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500970 return layerdir
971
972
973def replace_dir_vars(path, d):
974 """Replace common directory paths with appropriate variable references (e.g. /etc becomes ${sysconfdir})"""
975 dirvars = {}
976 # Sort by length so we get the variables we're interested in first
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600977 for var in sorted(list(d.keys()), key=len):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500978 if var.endswith('dir') and var.lower() == var:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500979 value = d.getVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500980 if value.startswith('/') and not '\n' in value and value not in dirvars:
981 dirvars[value] = var
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600982 for dirpath in sorted(list(dirvars.keys()), reverse=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500983 path = path.replace(dirpath, '${%s}' % dirvars[dirpath])
984 return path
985
Andrew Geissler5082cc72023-09-11 08:41:39 -0400986def get_recipe_pv_with_pfx_sfx(pv, uri_type):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500987 """
Andrew Geissler5082cc72023-09-11 08:41:39 -0400988 Get PV separating prefix and suffix components.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500989
990 Returns tuple with pv, prefix and suffix.
991 """
992 pfx = ''
993 sfx = ''
994
995 if uri_type == 'git':
Andrew Geissler5082cc72023-09-11 08:41:39 -0400996 git_regex = re.compile(r"(?P<pfx>v?)(?P<ver>.*?)(?P<sfx>\+[^\+]*(git)?r?(AUTOINC\+)?)(?P<rev>.*)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500997 m = git_regex.match(pv)
998
999 if m:
1000 pv = m.group('ver')
1001 pfx = m.group('pfx')
1002 sfx = m.group('sfx')
1003 else:
Brad Bishop19323692019-04-05 15:28:33 -04001004 regex = re.compile(r"(?P<pfx>(v|r)?)(?P<ver>.*)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001005 m = regex.match(pv)
1006 if m:
1007 pv = m.group('ver')
1008 pfx = m.group('pfx')
1009
1010 return (pv, pfx, sfx)
1011
1012def get_recipe_upstream_version(rd):
1013 """
1014 Get upstream version of recipe using bb.fetch2 methods with support for
1015 http, https, ftp and git.
1016
1017 bb.fetch2 exceptions can be raised,
1018 FetchError when don't have network access or upstream site don't response.
1019 NoMethodError when uri latest_versionstring method isn't implemented.
1020
Brad Bishop316dfdd2018-06-25 12:45:53 -04001021 Returns a dictonary with version, repository revision, current_version, type and datetime.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001022 Type can be A for Automatic, M for Manual and U for Unknown.
1023 """
1024 from bb.fetch2 import decodeurl
1025 from datetime import datetime
1026
1027 ru = {}
Brad Bishop316dfdd2018-06-25 12:45:53 -04001028 ru['current_version'] = rd.getVar('PV')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001029 ru['version'] = ''
1030 ru['type'] = 'U'
1031 ru['datetime'] = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -04001032 ru['revision'] = ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001033
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001034 # XXX: If don't have SRC_URI means that don't have upstream sources so
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001035 # returns the current recipe version, so that upstream version check
1036 # declares a match.
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001037 src_uris = rd.getVar('SRC_URI')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001038 if not src_uris:
Brad Bishop316dfdd2018-06-25 12:45:53 -04001039 ru['version'] = ru['current_version']
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001040 ru['type'] = 'M'
1041 ru['datetime'] = datetime.now()
1042 return ru
1043
1044 # XXX: we suppose that the first entry points to the upstream sources
1045 src_uri = src_uris.split()[0]
1046 uri_type, _, _, _, _, _ = decodeurl(src_uri)
1047
Andrew Geissler5082cc72023-09-11 08:41:39 -04001048 (pv, pfx, sfx) = get_recipe_pv_with_pfx_sfx(rd.getVar('PV'), uri_type)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001049 ru['current_version'] = pv
1050
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001051 manual_upstream_version = rd.getVar("RECIPE_UPSTREAM_VERSION")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001052 if manual_upstream_version:
1053 # manual tracking of upstream version.
1054 ru['version'] = manual_upstream_version
1055 ru['type'] = 'M'
1056
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001057 manual_upstream_date = rd.getVar("CHECK_DATE")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001058 if manual_upstream_date:
1059 date = datetime.strptime(manual_upstream_date, "%b %d, %Y")
1060 else:
1061 date = datetime.now()
1062 ru['datetime'] = date
1063
1064 elif uri_type == "file":
1065 # files are always up-to-date
1066 ru['version'] = pv
1067 ru['type'] = 'A'
1068 ru['datetime'] = datetime.now()
1069 else:
1070 ud = bb.fetch2.FetchData(src_uri, rd)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001071 if rd.getVar("UPSTREAM_CHECK_COMMITS") == "1":
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001072 bb.fetch2.get_srcrev(rd)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001073 revision = ud.method.latest_revision(ud, rd, 'default')
1074 upversion = pv
1075 if revision != rd.getVar("SRCREV"):
Patrick Williamsda295312023-12-05 16:48:56 -06001076 upversion = upversion + "-new-commits-available"
Brad Bishop316dfdd2018-06-25 12:45:53 -04001077 else:
1078 pupver = ud.method.latest_versionstring(ud, rd)
1079 (upversion, revision) = pupver
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001080
1081 if upversion:
1082 ru['version'] = upversion
1083 ru['type'] = 'A'
1084
Brad Bishop316dfdd2018-06-25 12:45:53 -04001085 if revision:
1086 ru['revision'] = revision
1087
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001088 ru['datetime'] = datetime.now()
1089
1090 return ru
Brad Bishop19323692019-04-05 15:28:33 -04001091
1092def _get_recipe_upgrade_status(data):
1093 uv = get_recipe_upstream_version(data)
1094
1095 pn = data.getVar('PN')
1096 cur_ver = uv['current_version']
1097
1098 upstream_version_unknown = data.getVar('UPSTREAM_VERSION_UNKNOWN')
1099 if not uv['version']:
1100 status = "UNKNOWN" if upstream_version_unknown else "UNKNOWN_BROKEN"
1101 else:
1102 cmp = vercmp_string(uv['current_version'], uv['version'])
1103 if cmp == -1:
1104 status = "UPDATE" if not upstream_version_unknown else "KNOWN_BROKEN"
1105 elif cmp == 0:
1106 status = "MATCH" if not upstream_version_unknown else "KNOWN_BROKEN"
1107 else:
1108 status = "UNKNOWN" if upstream_version_unknown else "UNKNOWN_BROKEN"
1109
1110 next_ver = uv['version'] if uv['version'] else "N/A"
1111 revision = uv['revision'] if uv['revision'] else "N/A"
1112 maintainer = data.getVar('RECIPE_MAINTAINER')
1113 no_upgrade_reason = data.getVar('RECIPE_NO_UPDATE_REASON')
1114
1115 return (pn, status, cur_ver, next_ver, maintainer, revision, no_upgrade_reason)
1116
1117def get_recipe_upgrade_status(recipes=None):
1118 pkgs_list = []
1119 data_copy_list = []
1120 copy_vars = ('SRC_URI',
1121 'PV',
Brad Bishop19323692019-04-05 15:28:33 -04001122 'DL_DIR',
1123 'PN',
1124 'CACHE',
1125 'PERSISTENT_DIR',
1126 'BB_URI_HEADREVS',
1127 'UPSTREAM_CHECK_COMMITS',
1128 'UPSTREAM_CHECK_GITTAGREGEX',
1129 'UPSTREAM_CHECK_REGEX',
1130 'UPSTREAM_CHECK_URI',
1131 'UPSTREAM_VERSION_UNKNOWN',
1132 'RECIPE_MAINTAINER',
1133 'RECIPE_NO_UPDATE_REASON',
1134 'RECIPE_UPSTREAM_VERSION',
1135 'RECIPE_UPSTREAM_DATE',
1136 'CHECK_DATE',
Andrew Geissler1e34c2d2020-05-29 16:02:59 -05001137 'FETCHCMD_bzr',
1138 'FETCHCMD_ccrc',
1139 'FETCHCMD_cvs',
1140 'FETCHCMD_git',
1141 'FETCHCMD_hg',
1142 'FETCHCMD_npm',
1143 'FETCHCMD_osc',
1144 'FETCHCMD_p4',
1145 'FETCHCMD_repo',
1146 'FETCHCMD_s3',
1147 'FETCHCMD_svn',
1148 'FETCHCMD_wget',
Brad Bishop19323692019-04-05 15:28:33 -04001149 )
1150
1151 with bb.tinfoil.Tinfoil() as tinfoil:
1152 tinfoil.prepare(config_only=False)
1153
1154 if not recipes:
1155 recipes = tinfoil.all_recipe_files(variants=False)
1156
1157 for fn in recipes:
1158 try:
1159 if fn.startswith("/"):
1160 data = tinfoil.parse_recipe_file(fn)
1161 else:
1162 data = tinfoil.parse_recipe(fn)
1163 except bb.providers.NoProvider:
1164 bb.note(" No provider for %s" % fn)
1165 continue
1166
1167 unreliable = data.getVar('UPSTREAM_CHECK_UNRELIABLE')
1168 if unreliable == "1":
1169 bb.note(" Skip package %s as upstream check unreliable" % pn)
1170 continue
1171
1172 data_copy = bb.data.init()
1173 for var in copy_vars:
1174 data_copy.setVar(var, data.getVar(var))
1175 for k in data:
1176 if k.startswith('SRCREV'):
1177 data_copy.setVar(k, data.getVar(k))
1178
1179 data_copy_list.append(data_copy)
1180
1181 from concurrent.futures import ProcessPoolExecutor
1182 with ProcessPoolExecutor(max_workers=utils.cpu_count()) as executor:
1183 pkgs_list = executor.map(_get_recipe_upgrade_status, data_copy_list)
1184
1185 return pkgs_list