blob: 4ca200d83441f21b480b40ec1eeafdb2bce488cc [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
Brad Bishop19323692019-04-05 15:28:33 -040019import bb.tinfoil
Patrick Williamsc124f4f2015-09-15 14:41:29 -050020
Brad Bishop19323692019-04-05 15:28:33 -040021from collections import OrderedDict, defaultdict
22from bb.utils import vercmp_string
Patrick Williamsc124f4f2015-09-15 14:41:29 -050023
24# Help us to find places to insert values
Patrick Williamsc0f7c042017-02-23 20:41:17 -060025recipe_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 -050026# Variables that sometimes are a bit long but shouldn't be wrapped
Brad Bishop19323692019-04-05 15:28:33 -040027nowrap_vars = ['SUMMARY', 'HOMEPAGE', 'BUGTRACKER', r'SRC_URI\[(.+\.)?md5sum\]', r'SRC_URI\[(.+\.)?sha256sum\]']
Patrick Williamsc124f4f2015-09-15 14:41:29 -050028list_vars = ['SRC_URI', 'LIC_FILES_CHKSUM']
29meta_vars = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION']
30
31
Brad Bishop19323692019-04-05 15:28:33 -040032def simplify_history(history, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050033 """
Brad Bishop19323692019-04-05 15:28:33 -040034 Eliminate any irrelevant events from a variable history
Patrick Williamsc124f4f2015-09-15 14:41:29 -050035 """
Brad Bishop19323692019-04-05 15:28:33 -040036 ret_history = []
37 has_set = False
38 # Go backwards through the history and remove any immediate operations
39 # before the most recent set
40 for event in reversed(history):
41 if 'flag' in event or not 'file' in event:
42 continue
43 if event['op'] == 'set':
44 if has_set:
45 continue
46 has_set = True
47 elif event['op'] in ('append', 'prepend', 'postdot', 'predot'):
48 # Reminder: "append" and "prepend" mean += and =+ respectively, NOT _append / _prepend
49 if has_set:
50 continue
51 ret_history.insert(0, event)
52 return ret_history
Patrick Williamsc124f4f2015-09-15 14:41:29 -050053
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:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050061 files = []
Brad Bishop19323692019-04-05 15:28:33 -040062 if '[' in v:
63 varsplit = v.split('[')
64 varflag = varsplit[1].split(']')[0]
65 history = d.varhistory.variable(varsplit[0])
66 for event in history:
67 if 'file' in event and event.get('flag', '') == varflag:
68 files.append(event['file'])
69 else:
70 history = d.varhistory.variable(v)
71 for event in history:
72 if 'file' in event and not 'flag' in event:
73 files.append(event['file'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -050074 if files:
75 actualfile = files[-1]
76 else:
77 actualfile = None
78 varfiles[v] = actualfile
79
80 return varfiles
81
82
Patrick Williamsf1e5d692016-03-30 15:21:19 -050083def split_var_value(value, assignment=True):
84 """
85 Split a space-separated variable's value into a list of items,
86 taking into account that some of the items might be made up of
87 expressions containing spaces that should not be split.
88 Parameters:
89 value:
90 The string value to split
91 assignment:
92 True to assume that the value represents an assignment
93 statement, False otherwise. If True, and an assignment
94 statement is passed in the first item in
95 the returned list will be the part of the assignment
96 statement up to and including the opening quote character,
97 and the last item will be the closing quote.
98 """
99 inexpr = 0
100 lastchar = None
101 out = []
102 buf = ''
103 for char in value:
104 if char == '{':
105 if lastchar == '$':
106 inexpr += 1
107 elif char == '}':
108 inexpr -= 1
109 elif assignment and char in '"\'' and inexpr == 0:
110 if buf:
111 out.append(buf)
112 out.append(char)
113 char = ''
114 buf = ''
115 elif char.isspace() and inexpr == 0:
116 char = ''
117 if buf:
118 out.append(buf)
119 buf = ''
120 buf += char
121 lastchar = char
122 if buf:
123 out.append(buf)
124
125 # Join together assignment statement and opening quote
126 outlist = out
127 if assignment:
128 assigfound = False
129 for idx, item in enumerate(out):
130 if '=' in item:
131 assigfound = True
132 if assigfound:
133 if '"' in item or "'" in item:
134 outlist = [' '.join(out[:idx+1])]
135 outlist.extend(out[idx+1:])
136 break
137 return outlist
138
139
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600140def patch_recipe_lines(fromlines, values, trailing_newline=True):
141 """Update or insert variable values into lines from a recipe.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500142 Note that some manual inspection/intervention may be required
143 since this cannot handle all situations.
144 """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500145
146 import bb.utils
147
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600148 if trailing_newline:
149 newline = '\n'
150 else:
151 newline = ''
152
Brad Bishop316dfdd2018-06-25 12:45:53 -0400153 nowrap_vars_res = []
154 for item in nowrap_vars:
155 nowrap_vars_res.append(re.compile('^%s$' % item))
156
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500157 recipe_progression_res = []
158 recipe_progression_restrs = []
159 for item in recipe_progression:
160 if item.endswith('()'):
161 key = item[:-2]
162 else:
163 key = item
Brad Bishop19323692019-04-05 15:28:33 -0400164 restr = r'%s(_[a-zA-Z0-9-_$(){}]+|\[[^\]]*\])?' % key
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500165 if item.endswith('()'):
166 recipe_progression_restrs.append(restr + '()')
167 else:
168 recipe_progression_restrs.append(restr)
169 recipe_progression_res.append(re.compile('^%s$' % restr))
170
171 def get_recipe_pos(variable):
172 for i, p in enumerate(recipe_progression_res):
173 if p.match(variable):
174 return i
175 return -1
176
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500177 remainingnames = {}
178 for k in values.keys():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500179 remainingnames[k] = get_recipe_pos(k)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600180 remainingnames = OrderedDict(sorted(remainingnames.items(), key=lambda x: x[1]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500181
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500182 modifying = False
183
184 def outputvalue(name, lines, rewindcomments=False):
185 if values[name] is None:
186 return
Brad Bishop19323692019-04-05 15:28:33 -0400187 if isinstance(values[name], tuple):
188 op, value = values[name]
189 if op == '+=' and value.strip() == '':
190 return
191 else:
192 value = values[name]
193 op = '='
194 rawtext = '%s %s "%s"%s' % (name, op, value, newline)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500195 addlines = []
Brad Bishop316dfdd2018-06-25 12:45:53 -0400196 nowrap = False
197 for nowrap_re in nowrap_vars_res:
198 if nowrap_re.match(name):
199 nowrap = True
200 break
201 if nowrap:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500202 addlines.append(rawtext)
203 elif name in list_vars:
Brad Bishop19323692019-04-05 15:28:33 -0400204 splitvalue = split_var_value(value, assignment=False)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500205 if len(splitvalue) > 1:
206 linesplit = ' \\\n' + (' ' * (len(name) + 4))
Brad Bishop19323692019-04-05 15:28:33 -0400207 addlines.append('%s %s "%s%s"%s' % (name, op, linesplit.join(splitvalue), linesplit, newline))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500208 else:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500209 addlines.append(rawtext)
210 else:
211 wrapped = textwrap.wrap(rawtext)
212 for wrapline in wrapped[:-1]:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600213 addlines.append('%s \\%s' % (wrapline, newline))
214 addlines.append('%s%s' % (wrapped[-1], newline))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500215
216 # Split on newlines - this isn't strictly necessary if you are only
217 # going to write the output to disk, but if you want to compare it
218 # (as patch_recipe_file() will do if patch=True) then it's important.
219 addlines = [line for l in addlines for line in l.splitlines(True)]
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500220 if rewindcomments:
221 # Ensure we insert the lines before any leading comments
222 # (that we'd want to ensure remain leading the next value)
223 for i, ln in reversed(list(enumerate(lines))):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600224 if not ln.startswith('#'):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500225 lines[i+1:i+1] = addlines
226 break
227 else:
228 lines.extend(addlines)
229 else:
230 lines.extend(addlines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500231
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500232 existingnames = []
233 def patch_recipe_varfunc(varname, origvalue, op, newlines):
234 if modifying:
235 # Insert anything that should come before this variable
236 pos = get_recipe_pos(varname)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600237 for k in list(remainingnames):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500238 if remainingnames[k] > -1 and pos >= remainingnames[k] and not k in existingnames:
239 outputvalue(k, newlines, rewindcomments=True)
240 del remainingnames[k]
241 # Now change this variable, if it needs to be changed
242 if varname in existingnames and op in ['+=', '=', '=+']:
243 if varname in remainingnames:
244 outputvalue(varname, newlines)
245 del remainingnames[varname]
246 return None, None, 0, True
247 else:
248 if varname in values:
249 existingnames.append(varname)
250 return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500251
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500252 # First run - establish which values we want to set are already in the file
253 varlist = [re.escape(item) for item in values.keys()]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600254 bb.utils.edit_metadata(fromlines, varlist, patch_recipe_varfunc)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500255 # Second run - actually set everything
256 modifying = True
257 varlist.extend(recipe_progression_restrs)
258 changed, tolines = bb.utils.edit_metadata(fromlines, varlist, patch_recipe_varfunc, match_overrides=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500259
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500260 if remainingnames:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600261 if tolines and tolines[-1].strip() != '':
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500262 tolines.append('\n')
263 for k in remainingnames.keys():
264 outputvalue(k, tolines)
265
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600266 return changed, tolines
267
268
Brad Bishop316dfdd2018-06-25 12:45:53 -0400269def patch_recipe_file(fn, values, patch=False, relpath='', redirect_output=None):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600270 """Update or insert variable values into a recipe file (assuming you
271 have already identified the exact file you want to update.)
272 Note that some manual inspection/intervention may be required
273 since this cannot handle all situations.
274 """
275
276 with open(fn, 'r') as f:
277 fromlines = f.readlines()
278
279 _, tolines = patch_recipe_lines(fromlines, values)
280
Brad Bishop316dfdd2018-06-25 12:45:53 -0400281 if redirect_output:
282 with open(os.path.join(redirect_output, os.path.basename(fn)), 'w') as f:
283 f.writelines(tolines)
284 return None
285 elif patch:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500286 relfn = os.path.relpath(fn, relpath)
287 diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500288 return diff
289 else:
290 with open(fn, 'w') as f:
291 f.writelines(tolines)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500292 return None
293
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500294
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500295def localise_file_vars(fn, varfiles, varlist):
296 """Given a list of variables and variable history (fetched with get_var_files())
297 find where each variable should be set/changed. This handles for example where a
298 recipe includes an inc file where variables might be changed - in most cases
299 we want to update the inc file when changing the variable value rather than adding
300 it to the recipe itself.
301 """
302 fndir = os.path.dirname(fn) + os.sep
303
304 first_meta_file = None
305 for v in meta_vars:
306 f = varfiles.get(v, None)
307 if f:
308 actualdir = os.path.dirname(f) + os.sep
309 if actualdir.startswith(fndir):
310 first_meta_file = f
311 break
312
313 filevars = defaultdict(list)
314 for v in varlist:
315 f = varfiles[v]
316 # Only return files that are in the same directory as the recipe or in some directory below there
317 # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable
318 # in if we were going to set a value specific to this recipe)
319 if f:
320 actualfile = f
321 else:
322 # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it
323 if first_meta_file:
324 actualfile = first_meta_file
325 else:
326 actualfile = fn
327
328 actualdir = os.path.dirname(actualfile) + os.sep
329 if not actualdir.startswith(fndir):
330 actualfile = fn
331 filevars[actualfile].append(v)
332
333 return filevars
334
Brad Bishop316dfdd2018-06-25 12:45:53 -0400335def patch_recipe(d, fn, varvalues, patch=False, relpath='', redirect_output=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500336 """Modify a list of variable values in the specified recipe. Handles inc files if
337 used by the recipe.
338 """
Brad Bishop19323692019-04-05 15:28:33 -0400339 overrides = d.getVar('OVERRIDES').split(':')
340 def override_applicable(hevent):
341 op = hevent['op']
342 if '[' in op:
343 opoverrides = op.split('[')[1].split(']')[0].split('_')
344 for opoverride in opoverrides:
345 if not opoverride in overrides:
346 return False
347 return True
348
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500349 varlist = varvalues.keys()
Brad Bishop19323692019-04-05 15:28:33 -0400350 fn = os.path.abspath(fn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500351 varfiles = get_var_files(fn, varlist, d)
352 locs = localise_file_vars(fn, varfiles, varlist)
353 patches = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600354 for f,v in locs.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500355 vals = {k: varvalues[k] for k in v}
Brad Bishop19323692019-04-05 15:28:33 -0400356 f = os.path.abspath(f)
357 if f == fn:
358 extravals = {}
359 for var, value in vals.items():
360 if var in list_vars:
361 history = simplify_history(d.varhistory.variable(var), d)
362 recipe_set = False
363 for event in history:
364 if os.path.abspath(event['file']) == fn:
365 if event['op'] == 'set':
366 recipe_set = True
367 if not recipe_set:
368 for event in history:
369 if event['op'].startswith('_remove'):
370 continue
371 if not override_applicable(event):
372 continue
373 newvalue = value.replace(event['detail'], '')
374 if newvalue == value and os.path.abspath(event['file']) == fn and event['op'].startswith('_'):
375 op = event['op'].replace('[', '_').replace(']', '')
376 extravals[var + op] = None
377 value = newvalue
378 vals[var] = ('+=', value)
379 vals.update(extravals)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400380 patchdata = patch_recipe_file(f, vals, patch, relpath, redirect_output)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500381 if patch:
382 patches.append(patchdata)
383
384 if patch:
385 return patches
386 else:
387 return None
388
389
390
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500391def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True, all_variants=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500392 """Copy (local) recipe files, including both files included via include/require,
393 and files referred to in the SRC_URI variable."""
394 import bb.fetch2
395 import oe.path
396
397 # FIXME need a warning if the unexpanded SRC_URI value contains variable references
398
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500399 uri_values = []
400 localpaths = []
401 def fetch_urls(rdata):
402 # Collect the local paths from SRC_URI
403 srcuri = rdata.getVar('SRC_URI') or ""
404 if srcuri not in uri_values:
405 fetch = bb.fetch2.Fetch(srcuri.split(), rdata)
406 if download:
407 fetch.download()
408 for pth in fetch.localpaths():
409 if pth not in localpaths:
410 localpaths.append(pth)
411 uri_values.append(srcuri)
412
413 fetch_urls(d)
414 if all_variants:
415 # Get files for other variants e.g. in the case of a SRC_URI_append
416 localdata = bb.data.createCopy(d)
417 variants = (localdata.getVar('BBCLASSEXTEND') or '').split()
418 if variants:
419 # Ensure we handle class-target if we're dealing with one of the variants
420 variants.append('target')
421 for variant in variants:
422 localdata.setVar('CLASSOVERRIDE', 'class-%s' % variant)
423 fetch_urls(localdata)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500424
425 # Copy local files to target directory and gather any remote files
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500426 bb_dir = os.path.abspath(os.path.dirname(d.getVar('FILE'))) + os.sep
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500427 remotes = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600428 copied = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500429 # Need to do this in two steps since we want to check against the absolute path
430 includes = [os.path.abspath(path) for path in d.getVar('BBINCLUDED').split() if os.path.exists(path)]
431 # We also check this below, but we don't want any items in this list being considered remotes
432 includes = [path for path in includes if path.startswith(bb_dir)]
433 for path in localpaths + includes:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500434 # Only import files that are under the meta directory
435 if path.startswith(bb_dir):
436 if not whole_dir:
437 relpath = os.path.relpath(path, bb_dir)
438 subdir = os.path.join(tgt_dir, os.path.dirname(relpath))
439 if not os.path.exists(subdir):
440 os.makedirs(subdir)
441 shutil.copy2(path, os.path.join(tgt_dir, relpath))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600442 copied.append(relpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500443 else:
444 remotes.append(path)
445 # Simply copy whole meta dir, if requested
446 if whole_dir:
447 shutil.copytree(bb_dir, tgt_dir)
448
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600449 return copied, remotes
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500450
451
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500452def get_recipe_local_files(d, patches=False, archives=False):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500453 """Get a list of local files in SRC_URI within a recipe."""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500454 import oe.patch
455 uris = (d.getVar('SRC_URI') or "").split()
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500456 fetch = bb.fetch2.Fetch(uris, d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500457 # FIXME this list should be factored out somewhere else (such as the
458 # fetcher) though note that this only encompasses actual container formats
459 # i.e. that can contain multiple files as opposed to those that only
460 # contain a compressed stream (i.e. .tar.gz as opposed to just .gz)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400461 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 -0500462 ret = {}
463 for uri in uris:
464 if fetch.ud[uri].type == 'file':
465 if (not patches and
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500466 oe.patch.patch_path(uri, fetch, '', expand=False)):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500467 continue
468 # Skip files that are referenced by absolute path
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600469 fname = fetch.ud[uri].basepath
470 if os.path.isabs(fname):
471 continue
472 # Handle subdir=
473 subdir = fetch.ud[uri].parm.get('subdir', '')
474 if subdir:
475 if os.path.isabs(subdir):
476 continue
477 fname = os.path.join(subdir, fname)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500478 localpath = fetch.localpath(uri)
479 if not archives:
480 # Ignore archives that will be unpacked
481 if localpath.endswith(tuple(archive_exts)):
482 unpack = fetch.ud[uri].parm.get('unpack', True)
483 if unpack:
484 continue
Brad Bishop19323692019-04-05 15:28:33 -0400485 if os.path.isdir(localpath):
486 for root, dirs, files in os.walk(localpath):
487 for fname in files:
488 fileabspath = os.path.join(root,fname)
489 srcdir = os.path.dirname(localpath)
490 ret[os.path.relpath(fileabspath,srcdir)] = fileabspath
491 else:
492 ret[fname] = localpath
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500493 return ret
494
495
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500496def get_recipe_patches(d):
497 """Get a list of the patches included in SRC_URI within a recipe."""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500498 import oe.patch
499 patches = oe.patch.src_patches(d, expand=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500500 patchfiles = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500501 for patch in patches:
502 _, _, local, _, _, parm = bb.fetch.decodeurl(patch)
503 patchfiles.append(local)
504 return patchfiles
505
506
507def get_recipe_patched_files(d):
508 """
509 Get the list of patches for a recipe along with the files each patch modifies.
510 Params:
511 d: the datastore for the recipe
512 Returns:
513 a dict mapping patch file path to a list of tuples of changed files and
514 change mode ('A' for add, 'D' for delete or 'M' for modify)
515 """
516 import oe.patch
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500517 patches = oe.patch.src_patches(d, expand=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500518 patchedfiles = {}
519 for patch in patches:
520 _, _, patchfile, _, _, parm = bb.fetch.decodeurl(patch)
521 striplevel = int(parm['striplevel'])
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500522 patchedfiles[patchfile] = oe.patch.PatchSet.getPatchedFiles(patchfile, striplevel, os.path.join(d.getVar('S'), parm.get('patchdir', '')))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500523 return patchedfiles
524
525
526def validate_pn(pn):
527 """Perform validation on a recipe name (PN) for a new recipe."""
528 reserved_names = ['forcevariable', 'append', 'prepend', 'remove']
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600529 if not re.match('^[0-9a-z-.+]+$', pn):
530 return 'Recipe name "%s" is invalid: only characters 0-9, a-z, -, + and . are allowed' % pn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500531 elif pn in reserved_names:
532 return 'Recipe name "%s" is invalid: is a reserved keyword' % pn
533 elif pn.startswith('pn-'):
534 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500535 elif pn.endswith(('.bb', '.bbappend', '.bbclass', '.inc', '.conf')):
536 return 'Recipe name "%s" is invalid: should be just a name, not a file name' % pn
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500537 return ''
538
539
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600540def get_bbfile_path(d, destdir, extrapathhint=None):
541 """
542 Determine the correct path for a recipe within a layer
543 Parameters:
544 d: Recipe-specific datastore
545 destdir: destination directory. Can be the path to the base of the layer or a
546 partial path somewhere within the layer.
547 extrapathhint: a path relative to the base of the layer to try
548 """
549 import bb.cookerdata
550
551 destdir = os.path.abspath(destdir)
552 destlayerdir = find_layerdir(destdir)
553
554 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
555 confdata = d.createCopy()
556 confdata.setVar('BBFILES', '')
557 confdata.setVar('LAYERDIR', destlayerdir)
558 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
559 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500560 pn = d.getVar('PN')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600561
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500562 bbfilespecs = (confdata.getVar('BBFILES') or '').split()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600563 if destdir == destlayerdir:
564 for bbfilespec in bbfilespecs:
565 if not bbfilespec.endswith('.bbappend'):
566 for match in glob.glob(bbfilespec):
567 splitext = os.path.splitext(os.path.basename(match))
568 if splitext[1] == '.bb':
569 mpn = splitext[0].split('_')[0]
570 if mpn == pn:
571 return os.path.dirname(match)
572
573 # Try to make up a path that matches BBFILES
574 # this is a little crude, but better than nothing
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500575 bpn = d.getVar('BPN')
576 recipefn = os.path.basename(d.getVar('FILE'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600577 pathoptions = [destdir]
578 if extrapathhint:
579 pathoptions.append(os.path.join(destdir, extrapathhint))
580 if destdir == destlayerdir:
581 pathoptions.append(os.path.join(destdir, 'recipes-%s' % bpn, bpn))
582 pathoptions.append(os.path.join(destdir, 'recipes', bpn))
583 pathoptions.append(os.path.join(destdir, bpn))
584 elif not destdir.endswith(('/' + pn, '/' + bpn)):
585 pathoptions.append(os.path.join(destdir, bpn))
586 closepath = ''
587 for pathoption in pathoptions:
588 bbfilepath = os.path.join(pathoption, 'test.bb')
589 for bbfilespec in bbfilespecs:
590 if fnmatch.fnmatchcase(bbfilepath, bbfilespec):
591 return pathoption
592 return None
593
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594def get_bbappend_path(d, destlayerdir, wildcardver=False):
595 """Determine how a bbappend for a recipe should be named and located within another layer"""
596
597 import bb.cookerdata
598
599 destlayerdir = os.path.abspath(destlayerdir)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500600 recipefile = d.getVar('FILE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500601 recipefn = os.path.splitext(os.path.basename(recipefile))[0]
602 if wildcardver and '_' in recipefn:
603 recipefn = recipefn.split('_', 1)[0] + '_%'
604 appendfn = recipefn + '.bbappend'
605
606 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
607 confdata = d.createCopy()
608 confdata.setVar('BBFILES', '')
609 confdata.setVar('LAYERDIR', destlayerdir)
610 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
611 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
612
613 origlayerdir = find_layerdir(recipefile)
614 if not origlayerdir:
615 return (None, False)
616 # Now join this to the path where the bbappend is going and check if it is covered by BBFILES
617 appendpath = os.path.join(destlayerdir, os.path.relpath(os.path.dirname(recipefile), origlayerdir), appendfn)
618 closepath = ''
619 pathok = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500620 for bbfilespec in confdata.getVar('BBFILES').split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500621 if fnmatch.fnmatchcase(appendpath, bbfilespec):
622 # Our append path works, we're done
623 break
624 elif bbfilespec.startswith(destlayerdir) and fnmatch.fnmatchcase('test.bbappend', os.path.basename(bbfilespec)):
625 # Try to find the longest matching path
626 if len(bbfilespec) > len(closepath):
627 closepath = bbfilespec
628 else:
629 # Unfortunately the bbappend layer and the original recipe's layer don't have the same structure
630 if closepath:
631 # bbappend layer's layer.conf at least has a spec that picks up .bbappend files
632 # Now we just need to substitute out any wildcards
633 appendsubdir = os.path.relpath(os.path.dirname(closepath), destlayerdir)
634 if 'recipes-*' in appendsubdir:
635 # Try to copy this part from the original recipe path
636 res = re.search('/recipes-[^/]+/', recipefile)
637 if res:
638 appendsubdir = appendsubdir.replace('/recipes-*/', res.group(0))
639 # This is crude, but we have to do something
640 appendsubdir = appendsubdir.replace('*', recipefn.split('_')[0])
641 appendsubdir = appendsubdir.replace('?', 'a')
642 appendpath = os.path.join(destlayerdir, appendsubdir, appendfn)
643 else:
644 pathok = False
645 return (appendpath, pathok)
646
647
Brad Bishop316dfdd2018-06-25 12:45:53 -0400648def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, machine=None, extralines=None, removevalues=None, redirect_output=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500649 """
650 Writes a bbappend file for a recipe
651 Parameters:
652 rd: data dictionary for the recipe
653 destlayerdir: base directory of the layer to place the bbappend in
654 (subdirectory path from there will be determined automatically)
655 srcfiles: dict of source files to add to SRC_URI, where the value
656 is the full path to the file to be added, and the value is the
657 original filename as it would appear in SRC_URI or None if it
658 isn't already present. You may pass None for this parameter if
659 you simply want to specify your own content via the extralines
660 parameter.
661 install: dict mapping entries in srcfiles to a tuple of two elements:
662 install path (*without* ${D} prefix) and permission value (as a
663 string, e.g. '0644').
664 wildcardver: True to use a % wildcard in the bbappend filename, or
665 False to make the bbappend specific to the recipe version.
666 machine:
667 If specified, make the changes in the bbappend specific to this
668 machine. This will also cause PACKAGE_ARCH = "${MACHINE_ARCH}"
669 to be added to the bbappend.
670 extralines:
671 Extra lines to add to the bbappend. This may be a dict of name
672 value pairs, or simply a list of the lines.
673 removevalues:
674 Variable values to remove - a dict of names/values.
Brad Bishop316dfdd2018-06-25 12:45:53 -0400675 redirect_output:
676 If specified, redirects writing the output file to the
677 specified directory (for dry-run purposes)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500678 """
679
680 if not removevalues:
681 removevalues = {}
682
683 # Determine how the bbappend should be named
684 appendpath, pathok = get_bbappend_path(rd, destlayerdir, wildcardver)
685 if not appendpath:
686 bb.error('Unable to determine layer directory containing %s' % recipefile)
687 return (None, None)
688 if not pathok:
689 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)))
690
691 appenddir = os.path.dirname(appendpath)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400692 if not redirect_output:
693 bb.utils.mkdirhier(appenddir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500694
695 # FIXME check if the bbappend doesn't get overridden by a higher priority layer?
696
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500697 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS').split()]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500698 if not os.path.abspath(destlayerdir) in layerdirs:
699 bb.warn('Specified layer is not currently enabled in bblayers.conf, you will need to add it before this bbappend will be active')
700
701 bbappendlines = []
702 if extralines:
703 if isinstance(extralines, dict):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600704 for name, value in extralines.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500705 bbappendlines.append((name, '=', value))
706 else:
707 # Do our best to split it
708 for line in extralines:
709 if line[-1] == '\n':
710 line = line[:-1]
711 splitline = line.split(None, 2)
712 if len(splitline) == 3:
713 bbappendlines.append(tuple(splitline))
714 else:
715 raise Exception('Invalid extralines value passed')
716
717 def popline(varname):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600718 for i in range(0, len(bbappendlines)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500719 if bbappendlines[i][0] == varname:
720 line = bbappendlines.pop(i)
721 return line
722 return None
723
724 def appendline(varname, op, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600725 for i in range(0, len(bbappendlines)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500726 item = bbappendlines[i]
727 if item[0] == varname:
728 bbappendlines[i] = (item[0], item[1], item[2] + ' ' + value)
729 break
730 else:
731 bbappendlines.append((varname, op, value))
732
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500733 destsubdir = rd.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500734 if srcfiles:
735 bbappendlines.append(('FILESEXTRAPATHS_prepend', ':=', '${THISDIR}/${PN}:'))
736
737 appendoverride = ''
738 if machine:
739 bbappendlines.append(('PACKAGE_ARCH', '=', '${MACHINE_ARCH}'))
740 appendoverride = '_%s' % machine
741 copyfiles = {}
742 if srcfiles:
743 instfunclines = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600744 for newfile, origsrcfile in srcfiles.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500745 srcfile = origsrcfile
746 srcurientry = None
747 if not srcfile:
748 srcfile = os.path.basename(newfile)
749 srcurientry = 'file://%s' % srcfile
750 # Double-check it's not there already
751 # FIXME do we care if the entry is added by another bbappend that might go away?
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500752 if not srcurientry in rd.getVar('SRC_URI').split():
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500753 if machine:
754 appendline('SRC_URI_append%s' % appendoverride, '=', ' ' + srcurientry)
755 else:
756 appendline('SRC_URI', '+=', srcurientry)
757 copyfiles[newfile] = srcfile
758 if install:
759 institem = install.pop(newfile, None)
760 if institem:
761 (destpath, perms) = institem
762 instdestpath = replace_dir_vars(destpath, rd)
763 instdirline = 'install -d ${D}%s' % os.path.dirname(instdestpath)
764 if not instdirline in instfunclines:
765 instfunclines.append(instdirline)
766 instfunclines.append('install -m %s ${WORKDIR}/%s ${D}%s' % (perms, os.path.basename(srcfile), instdestpath))
767 if instfunclines:
768 bbappendlines.append(('do_install_append%s()' % appendoverride, '', instfunclines))
769
Brad Bishop316dfdd2018-06-25 12:45:53 -0400770 if redirect_output:
771 bb.note('Writing append file %s (dry-run)' % appendpath)
772 outfile = os.path.join(redirect_output, os.path.basename(appendpath))
773 # Only take a copy if the file isn't already there (this function may be called
774 # multiple times per operation when we're handling overrides)
775 if os.path.exists(appendpath) and not os.path.exists(outfile):
776 shutil.copy2(appendpath, outfile)
777 else:
778 bb.note('Writing append file %s' % appendpath)
779 outfile = appendpath
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500780
Brad Bishop316dfdd2018-06-25 12:45:53 -0400781 if os.path.exists(outfile):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500782 # Work around lack of nonlocal in python 2
783 extvars = {'destsubdir': destsubdir}
784
785 def appendfile_varfunc(varname, origvalue, op, newlines):
786 if varname == 'FILESEXTRAPATHS_prepend':
787 if origvalue.startswith('${THISDIR}/'):
788 popline('FILESEXTRAPATHS_prepend')
789 extvars['destsubdir'] = rd.expand(origvalue.split('${THISDIR}/', 1)[1].rstrip(':'))
790 elif varname == 'PACKAGE_ARCH':
791 if machine:
792 popline('PACKAGE_ARCH')
793 return (machine, None, 4, False)
794 elif varname.startswith('do_install_append'):
795 func = popline(varname)
796 if func:
797 instfunclines = [line.strip() for line in origvalue.strip('\n').splitlines()]
798 for line in func[2]:
799 if not line in instfunclines:
800 instfunclines.append(line)
801 return (instfunclines, None, 4, False)
802 else:
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500803 splitval = split_var_value(origvalue, assignment=False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500804 changed = False
805 removevar = varname
806 if varname in ['SRC_URI', 'SRC_URI_append%s' % appendoverride]:
807 removevar = 'SRC_URI'
808 line = popline(varname)
809 if line:
810 if line[2] not in splitval:
811 splitval.append(line[2])
812 changed = True
813 else:
814 line = popline(varname)
815 if line:
816 splitval = [line[2]]
817 changed = True
818
819 if removevar in removevalues:
820 remove = removevalues[removevar]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600821 if isinstance(remove, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500822 if remove in splitval:
823 splitval.remove(remove)
824 changed = True
825 else:
826 for removeitem in remove:
827 if removeitem in splitval:
828 splitval.remove(removeitem)
829 changed = True
830
831 if changed:
832 newvalue = splitval
833 if len(newvalue) == 1:
834 # Ensure it's written out as one line
835 if '_append' in varname:
836 newvalue = ' ' + newvalue[0]
837 else:
838 newvalue = newvalue[0]
839 if not newvalue and (op in ['+=', '.='] or '_append' in varname):
840 # There's no point appending nothing
841 newvalue = None
842 if varname.endswith('()'):
843 indent = 4
844 else:
845 indent = -1
846 return (newvalue, None, indent, True)
847 return (origvalue, None, 4, False)
848
849 varnames = [item[0] for item in bbappendlines]
850 if removevalues:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600851 varnames.extend(list(removevalues.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500852
Brad Bishop316dfdd2018-06-25 12:45:53 -0400853 with open(outfile, 'r') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500854 (updated, newlines) = bb.utils.edit_metadata(f, varnames, appendfile_varfunc)
855
856 destsubdir = extvars['destsubdir']
857 else:
858 updated = False
859 newlines = []
860
861 if bbappendlines:
862 for line in bbappendlines:
863 if line[0].endswith('()'):
864 newlines.append('%s {\n %s\n}\n' % (line[0], '\n '.join(line[2])))
865 else:
866 newlines.append('%s %s "%s"\n\n' % line)
867 updated = True
868
869 if updated:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400870 with open(outfile, 'w') as f:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500871 f.writelines(newlines)
872
873 if copyfiles:
874 if machine:
875 destsubdir = os.path.join(destsubdir, machine)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400876 if redirect_output:
877 outdir = redirect_output
878 else:
879 outdir = appenddir
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600880 for newfile, srcfile in copyfiles.items():
Brad Bishop316dfdd2018-06-25 12:45:53 -0400881 filedest = os.path.join(outdir, destsubdir, os.path.basename(srcfile))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500882 if os.path.abspath(newfile) != os.path.abspath(filedest):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500883 if newfile.startswith(tempfile.gettempdir()):
884 newfiledisp = os.path.basename(newfile)
885 else:
886 newfiledisp = newfile
Brad Bishop316dfdd2018-06-25 12:45:53 -0400887 if redirect_output:
888 bb.note('Copying %s to %s (dry-run)' % (newfiledisp, os.path.join(appenddir, destsubdir, os.path.basename(srcfile))))
889 else:
890 bb.note('Copying %s to %s' % (newfiledisp, filedest))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500891 bb.utils.mkdirhier(os.path.dirname(filedest))
892 shutil.copyfile(newfile, filedest)
893
894 return (appendpath, os.path.join(appenddir, destsubdir))
895
896
897def find_layerdir(fn):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600898 """ Figure out the path to the base of the layer containing a file (e.g. a recipe)"""
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500899 pth = os.path.abspath(fn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500900 layerdir = ''
901 while pth:
902 if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')):
903 layerdir = pth
904 break
905 pth = os.path.dirname(pth)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600906 if pth == '/':
907 return None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500908 return layerdir
909
910
911def replace_dir_vars(path, d):
912 """Replace common directory paths with appropriate variable references (e.g. /etc becomes ${sysconfdir})"""
913 dirvars = {}
914 # Sort by length so we get the variables we're interested in first
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600915 for var in sorted(list(d.keys()), key=len):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500916 if var.endswith('dir') and var.lower() == var:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500917 value = d.getVar(var)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500918 if value.startswith('/') and not '\n' in value and value not in dirvars:
919 dirvars[value] = var
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600920 for dirpath in sorted(list(dirvars.keys()), reverse=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500921 path = path.replace(dirpath, '${%s}' % dirvars[dirpath])
922 return path
923
924def get_recipe_pv_without_srcpv(pv, uri_type):
925 """
926 Get PV without SRCPV common in SCM's for now only
927 support git.
928
929 Returns tuple with pv, prefix and suffix.
930 """
931 pfx = ''
932 sfx = ''
933
934 if uri_type == 'git':
Brad Bishop19323692019-04-05 15:28:33 -0400935 git_regex = re.compile(r"(?P<pfx>v?)(?P<ver>[^\+]*)((?P<sfx>\+(git)?r?(AUTOINC\+))(?P<rev>.*))?")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500936 m = git_regex.match(pv)
937
938 if m:
939 pv = m.group('ver')
940 pfx = m.group('pfx')
941 sfx = m.group('sfx')
942 else:
Brad Bishop19323692019-04-05 15:28:33 -0400943 regex = re.compile(r"(?P<pfx>(v|r)?)(?P<ver>.*)")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500944 m = regex.match(pv)
945 if m:
946 pv = m.group('ver')
947 pfx = m.group('pfx')
948
949 return (pv, pfx, sfx)
950
951def get_recipe_upstream_version(rd):
952 """
953 Get upstream version of recipe using bb.fetch2 methods with support for
954 http, https, ftp and git.
955
956 bb.fetch2 exceptions can be raised,
957 FetchError when don't have network access or upstream site don't response.
958 NoMethodError when uri latest_versionstring method isn't implemented.
959
Brad Bishop316dfdd2018-06-25 12:45:53 -0400960 Returns a dictonary with version, repository revision, current_version, type and datetime.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500961 Type can be A for Automatic, M for Manual and U for Unknown.
962 """
963 from bb.fetch2 import decodeurl
964 from datetime import datetime
965
966 ru = {}
Brad Bishop316dfdd2018-06-25 12:45:53 -0400967 ru['current_version'] = rd.getVar('PV')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500968 ru['version'] = ''
969 ru['type'] = 'U'
970 ru['datetime'] = ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400971 ru['revision'] = ''
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500972
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500973 # XXX: If don't have SRC_URI means that don't have upstream sources so
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500974 # returns the current recipe version, so that upstream version check
975 # declares a match.
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500976 src_uris = rd.getVar('SRC_URI')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500977 if not src_uris:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400978 ru['version'] = ru['current_version']
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500979 ru['type'] = 'M'
980 ru['datetime'] = datetime.now()
981 return ru
982
983 # XXX: we suppose that the first entry points to the upstream sources
984 src_uri = src_uris.split()[0]
985 uri_type, _, _, _, _, _ = decodeurl(src_uri)
986
Brad Bishop316dfdd2018-06-25 12:45:53 -0400987 (pv, pfx, sfx) = get_recipe_pv_without_srcpv(rd.getVar('PV'), uri_type)
988 ru['current_version'] = pv
989
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500990 manual_upstream_version = rd.getVar("RECIPE_UPSTREAM_VERSION")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500991 if manual_upstream_version:
992 # manual tracking of upstream version.
993 ru['version'] = manual_upstream_version
994 ru['type'] = 'M'
995
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500996 manual_upstream_date = rd.getVar("CHECK_DATE")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500997 if manual_upstream_date:
998 date = datetime.strptime(manual_upstream_date, "%b %d, %Y")
999 else:
1000 date = datetime.now()
1001 ru['datetime'] = date
1002
1003 elif uri_type == "file":
1004 # files are always up-to-date
1005 ru['version'] = pv
1006 ru['type'] = 'A'
1007 ru['datetime'] = datetime.now()
1008 else:
1009 ud = bb.fetch2.FetchData(src_uri, rd)
Brad Bishop316dfdd2018-06-25 12:45:53 -04001010 if rd.getVar("UPSTREAM_CHECK_COMMITS") == "1":
1011 revision = ud.method.latest_revision(ud, rd, 'default')
1012 upversion = pv
1013 if revision != rd.getVar("SRCREV"):
1014 upversion = upversion + "-new-commits-available"
1015 else:
1016 pupver = ud.method.latest_versionstring(ud, rd)
1017 (upversion, revision) = pupver
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001018
1019 if upversion:
1020 ru['version'] = upversion
1021 ru['type'] = 'A'
1022
Brad Bishop316dfdd2018-06-25 12:45:53 -04001023 if revision:
1024 ru['revision'] = revision
1025
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001026 ru['datetime'] = datetime.now()
1027
1028 return ru
Brad Bishop19323692019-04-05 15:28:33 -04001029
1030def _get_recipe_upgrade_status(data):
1031 uv = get_recipe_upstream_version(data)
1032
1033 pn = data.getVar('PN')
1034 cur_ver = uv['current_version']
1035
1036 upstream_version_unknown = data.getVar('UPSTREAM_VERSION_UNKNOWN')
1037 if not uv['version']:
1038 status = "UNKNOWN" if upstream_version_unknown else "UNKNOWN_BROKEN"
1039 else:
1040 cmp = vercmp_string(uv['current_version'], uv['version'])
1041 if cmp == -1:
1042 status = "UPDATE" if not upstream_version_unknown else "KNOWN_BROKEN"
1043 elif cmp == 0:
1044 status = "MATCH" if not upstream_version_unknown else "KNOWN_BROKEN"
1045 else:
1046 status = "UNKNOWN" if upstream_version_unknown else "UNKNOWN_BROKEN"
1047
1048 next_ver = uv['version'] if uv['version'] else "N/A"
1049 revision = uv['revision'] if uv['revision'] else "N/A"
1050 maintainer = data.getVar('RECIPE_MAINTAINER')
1051 no_upgrade_reason = data.getVar('RECIPE_NO_UPDATE_REASON')
1052
1053 return (pn, status, cur_ver, next_ver, maintainer, revision, no_upgrade_reason)
1054
1055def get_recipe_upgrade_status(recipes=None):
1056 pkgs_list = []
1057 data_copy_list = []
1058 copy_vars = ('SRC_URI',
1059 'PV',
1060 'GITDIR',
1061 'DL_DIR',
1062 'PN',
1063 'CACHE',
1064 'PERSISTENT_DIR',
1065 'BB_URI_HEADREVS',
1066 'UPSTREAM_CHECK_COMMITS',
1067 'UPSTREAM_CHECK_GITTAGREGEX',
1068 'UPSTREAM_CHECK_REGEX',
1069 'UPSTREAM_CHECK_URI',
1070 'UPSTREAM_VERSION_UNKNOWN',
1071 'RECIPE_MAINTAINER',
1072 'RECIPE_NO_UPDATE_REASON',
1073 'RECIPE_UPSTREAM_VERSION',
1074 'RECIPE_UPSTREAM_DATE',
1075 'CHECK_DATE',
1076 )
1077
1078 with bb.tinfoil.Tinfoil() as tinfoil:
1079 tinfoil.prepare(config_only=False)
1080
1081 if not recipes:
1082 recipes = tinfoil.all_recipe_files(variants=False)
1083
1084 for fn in recipes:
1085 try:
1086 if fn.startswith("/"):
1087 data = tinfoil.parse_recipe_file(fn)
1088 else:
1089 data = tinfoil.parse_recipe(fn)
1090 except bb.providers.NoProvider:
1091 bb.note(" No provider for %s" % fn)
1092 continue
1093
1094 unreliable = data.getVar('UPSTREAM_CHECK_UNRELIABLE')
1095 if unreliable == "1":
1096 bb.note(" Skip package %s as upstream check unreliable" % pn)
1097 continue
1098
1099 data_copy = bb.data.init()
1100 for var in copy_vars:
1101 data_copy.setVar(var, data.getVar(var))
1102 for k in data:
1103 if k.startswith('SRCREV'):
1104 data_copy.setVar(k, data.getVar(k))
1105
1106 data_copy_list.append(data_copy)
1107
1108 from concurrent.futures import ProcessPoolExecutor
1109 with ProcessPoolExecutor(max_workers=utils.cpu_count()) as executor:
1110 pkgs_list = executor.map(_get_recipe_upgrade_status, data_copy_list)
1111
1112 return pkgs_list