blob: d4fa72651caaa906a8fb184e29bd38646e2bcbf5 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# Utility functions for reading and modifying recipes
2#
3# Some code borrowed from the OE layer index
4#
5# Copyright (C) 2013-2015 Intel Corporation
6#
7
8import sys
9import os
10import os.path
11import tempfile
12import textwrap
13import difflib
14import utils
15import shutil
16import re
17import fnmatch
18from collections import OrderedDict, defaultdict
19
20
21# Help us to find places to insert values
22recipe_progression = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION', 'LICENSE', 'LIC_FILES_CHKSUM', 'PROVIDES', 'DEPENDS', 'PR', 'PV', 'SRCREV', 'SRC_URI', 'S', 'do_fetch', 'do_unpack', 'do_patch', 'EXTRA_OECONF', 'do_configure', 'EXTRA_OEMAKE', 'do_compile', 'do_install', 'do_populate_sysroot', 'INITSCRIPT', 'USERADD', 'GROUPADD', 'PACKAGES', 'FILES', 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RPROVIDES', 'RREPLACES', 'RCONFLICTS', 'ALLOW_EMPTY', 'do_package', 'do_deploy']
23# Variables that sometimes are a bit long but shouldn't be wrapped
24nowrap_vars = ['SUMMARY', 'HOMEPAGE', 'BUGTRACKER']
25list_vars = ['SRC_URI', 'LIC_FILES_CHKSUM']
26meta_vars = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION']
27
28
29def pn_to_recipe(cooker, pn):
30 """Convert a recipe name (PN) to the path to the recipe file"""
31 import bb.providers
32
33 if pn in cooker.recipecache.pkg_pn:
34 filenames = cooker.recipecache.pkg_pn[pn]
35 best = bb.providers.findBestProvider(pn, cooker.data, cooker.recipecache, cooker.recipecache.pkg_pn)
36 return best[3]
37 else:
38 return None
39
40
41def get_unavailable_reasons(cooker, pn):
42 """If a recipe could not be found, find out why if possible"""
43 import bb.taskdata
44 taskdata = bb.taskdata.TaskData(None, skiplist=cooker.skiplist)
45 return taskdata.get_reasons(pn)
46
47
48def parse_recipe(fn, appendfiles, d):
49 """
50 Parse an individual recipe file, optionally with a list of
51 bbappend files.
52 """
53 import bb.cache
54 envdata = bb.cache.Cache.loadDataFull(fn, appendfiles, d)
55 return envdata
56
57
58def parse_recipe_simple(cooker, pn, d, appends=True):
59 """
60 Parse a recipe and optionally all bbappends that apply to it
61 in the current configuration.
62 """
63 import bb.providers
64
65 recipefile = pn_to_recipe(cooker, pn)
66 if not recipefile:
67 skipreasons = get_unavailable_reasons(cooker, pn)
68 # We may as well re-use bb.providers.NoProvider here
69 if skipreasons:
70 raise bb.providers.NoProvider(skipreasons)
71 else:
72 raise bb.providers.NoProvider('Unable to find any recipe file matching %s' % pn)
73 if appends:
74 appendfiles = cooker.collection.get_file_appends(recipefile)
75 return parse_recipe(recipefile, appendfiles, d)
76
77
78def get_var_files(fn, varlist, d):
79 """Find the file in which each of a list of variables is set.
80 Note: requires variable history to be enabled when parsing.
81 """
82 varfiles = {}
83 for v in varlist:
84 history = d.varhistory.variable(v)
85 files = []
86 for event in history:
87 if 'file' in event and not 'flag' in event:
88 files.append(event['file'])
89 if files:
90 actualfile = files[-1]
91 else:
92 actualfile = None
93 varfiles[v] = actualfile
94
95 return varfiles
96
97
98def patch_recipe_file(fn, values, patch=False, relpath=''):
99 """Update or insert variable values into a recipe file (assuming you
100 have already identified the exact file you want to update.)
101 Note that some manual inspection/intervention may be required
102 since this cannot handle all situations.
103 """
104 remainingnames = {}
105 for k in values.keys():
106 remainingnames[k] = recipe_progression.index(k) if k in recipe_progression else -1
107 remainingnames = OrderedDict(sorted(remainingnames.iteritems(), key=lambda x: x[1]))
108
109 with tempfile.NamedTemporaryFile('w', delete=False) as tf:
110 def outputvalue(name):
111 rawtext = '%s = "%s"\n' % (name, values[name])
112 if name in nowrap_vars:
113 tf.write(rawtext)
114 elif name in list_vars:
115 splitvalue = values[name].split()
116 if len(splitvalue) > 1:
117 linesplit = ' \\\n' + (' ' * (len(name) + 4))
118 tf.write('%s = "%s%s"\n' % (name, linesplit.join(splitvalue), linesplit))
119 else:
120 tf.write(rawtext)
121 else:
122 wrapped = textwrap.wrap(rawtext)
123 for wrapline in wrapped[:-1]:
124 tf.write('%s \\\n' % wrapline)
125 tf.write('%s\n' % wrapped[-1])
126
127 tfn = tf.name
128 with open(fn, 'r') as f:
129 # First runthrough - find existing names (so we know not to insert based on recipe_progression)
130 # Second runthrough - make the changes
131 existingnames = []
132 for runthrough in [1, 2]:
133 currname = None
134 for line in f:
135 if not currname:
136 insert = False
137 for k in remainingnames.keys():
138 for p in recipe_progression:
139 if re.match('^%s(_prepend|_append)*[ ?:=(]' % p, line):
140 if remainingnames[k] > -1 and recipe_progression.index(p) > remainingnames[k] and runthrough > 1 and not k in existingnames:
141 outputvalue(k)
142 del remainingnames[k]
143 break
144 for k in remainingnames.keys():
145 if re.match('^%s[ ?:=]' % k, line):
146 currname = k
147 if runthrough == 1:
148 existingnames.append(k)
149 else:
150 del remainingnames[k]
151 break
152 if currname and runthrough > 1:
153 outputvalue(currname)
154
155 if currname:
156 sline = line.rstrip()
157 if not sline.endswith('\\'):
158 currname = None
159 continue
160 if runthrough > 1:
161 tf.write(line)
162 f.seek(0)
163 if remainingnames:
164 tf.write('\n')
165 for k in remainingnames.keys():
166 outputvalue(k)
167
168 with open(tfn, 'U') as f:
169 tolines = f.readlines()
170 if patch:
171 with open(fn, 'U') as f:
172 fromlines = f.readlines()
173 relfn = os.path.relpath(fn, relpath)
174 diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn)
175 os.remove(tfn)
176 return diff
177 else:
178 with open(fn, 'w') as f:
179 f.writelines(tolines)
180 os.remove(tfn)
181 return None
182
183def localise_file_vars(fn, varfiles, varlist):
184 """Given a list of variables and variable history (fetched with get_var_files())
185 find where each variable should be set/changed. This handles for example where a
186 recipe includes an inc file where variables might be changed - in most cases
187 we want to update the inc file when changing the variable value rather than adding
188 it to the recipe itself.
189 """
190 fndir = os.path.dirname(fn) + os.sep
191
192 first_meta_file = None
193 for v in meta_vars:
194 f = varfiles.get(v, None)
195 if f:
196 actualdir = os.path.dirname(f) + os.sep
197 if actualdir.startswith(fndir):
198 first_meta_file = f
199 break
200
201 filevars = defaultdict(list)
202 for v in varlist:
203 f = varfiles[v]
204 # Only return files that are in the same directory as the recipe or in some directory below there
205 # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable
206 # in if we were going to set a value specific to this recipe)
207 if f:
208 actualfile = f
209 else:
210 # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it
211 if first_meta_file:
212 actualfile = first_meta_file
213 else:
214 actualfile = fn
215
216 actualdir = os.path.dirname(actualfile) + os.sep
217 if not actualdir.startswith(fndir):
218 actualfile = fn
219 filevars[actualfile].append(v)
220
221 return filevars
222
223def patch_recipe(d, fn, varvalues, patch=False, relpath=''):
224 """Modify a list of variable values in the specified recipe. Handles inc files if
225 used by the recipe.
226 """
227 varlist = varvalues.keys()
228 varfiles = get_var_files(fn, varlist, d)
229 locs = localise_file_vars(fn, varfiles, varlist)
230 patches = []
231 for f,v in locs.iteritems():
232 vals = {k: varvalues[k] for k in v}
233 patchdata = patch_recipe_file(f, vals, patch, relpath)
234 if patch:
235 patches.append(patchdata)
236
237 if patch:
238 return patches
239 else:
240 return None
241
242
243
244def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True):
245 """Copy (local) recipe files, including both files included via include/require,
246 and files referred to in the SRC_URI variable."""
247 import bb.fetch2
248 import oe.path
249
250 # FIXME need a warning if the unexpanded SRC_URI value contains variable references
251
252 uris = (d.getVar('SRC_URI', True) or "").split()
253 fetch = bb.fetch2.Fetch(uris, d)
254 if download:
255 fetch.download()
256
257 # Copy local files to target directory and gather any remote files
258 bb_dir = os.path.dirname(d.getVar('FILE', True)) + os.sep
259 remotes = []
260 includes = [path for path in d.getVar('BBINCLUDED', True).split() if
261 path.startswith(bb_dir) and os.path.exists(path)]
262 for path in fetch.localpaths() + includes:
263 # Only import files that are under the meta directory
264 if path.startswith(bb_dir):
265 if not whole_dir:
266 relpath = os.path.relpath(path, bb_dir)
267 subdir = os.path.join(tgt_dir, os.path.dirname(relpath))
268 if not os.path.exists(subdir):
269 os.makedirs(subdir)
270 shutil.copy2(path, os.path.join(tgt_dir, relpath))
271 else:
272 remotes.append(path)
273 # Simply copy whole meta dir, if requested
274 if whole_dir:
275 shutil.copytree(bb_dir, tgt_dir)
276
277 return remotes
278
279
280def get_recipe_patches(d):
281 """Get a list of the patches included in SRC_URI within a recipe."""
282 patchfiles = []
283 # Execute src_patches() defined in patch.bbclass - this works since that class
284 # is inherited globally
285 patches = bb.utils.exec_flat_python_func('src_patches', d)
286 for patch in patches:
287 _, _, local, _, _, parm = bb.fetch.decodeurl(patch)
288 patchfiles.append(local)
289 return patchfiles
290
291
292def get_recipe_patched_files(d):
293 """
294 Get the list of patches for a recipe along with the files each patch modifies.
295 Params:
296 d: the datastore for the recipe
297 Returns:
298 a dict mapping patch file path to a list of tuples of changed files and
299 change mode ('A' for add, 'D' for delete or 'M' for modify)
300 """
301 import oe.patch
302 # Execute src_patches() defined in patch.bbclass - this works since that class
303 # is inherited globally
304 patches = bb.utils.exec_flat_python_func('src_patches', d)
305 patchedfiles = {}
306 for patch in patches:
307 _, _, patchfile, _, _, parm = bb.fetch.decodeurl(patch)
308 striplevel = int(parm['striplevel'])
309 patchedfiles[patchfile] = oe.patch.PatchSet.getPatchedFiles(patchfile, striplevel, os.path.join(d.getVar('S', True), parm.get('patchdir', '')))
310 return patchedfiles
311
312
313def validate_pn(pn):
314 """Perform validation on a recipe name (PN) for a new recipe."""
315 reserved_names = ['forcevariable', 'append', 'prepend', 'remove']
316 if not re.match('[0-9a-z-.]+', pn):
317 return 'Recipe name "%s" is invalid: only characters 0-9, a-z, - and . are allowed' % pn
318 elif pn in reserved_names:
319 return 'Recipe name "%s" is invalid: is a reserved keyword' % pn
320 elif pn.startswith('pn-'):
321 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn
322 return ''
323
324
325def get_bbappend_path(d, destlayerdir, wildcardver=False):
326 """Determine how a bbappend for a recipe should be named and located within another layer"""
327
328 import bb.cookerdata
329
330 destlayerdir = os.path.abspath(destlayerdir)
331 recipefile = d.getVar('FILE', True)
332 recipefn = os.path.splitext(os.path.basename(recipefile))[0]
333 if wildcardver and '_' in recipefn:
334 recipefn = recipefn.split('_', 1)[0] + '_%'
335 appendfn = recipefn + '.bbappend'
336
337 # Parse the specified layer's layer.conf file directly, in case the layer isn't in bblayers.conf
338 confdata = d.createCopy()
339 confdata.setVar('BBFILES', '')
340 confdata.setVar('LAYERDIR', destlayerdir)
341 destlayerconf = os.path.join(destlayerdir, "conf", "layer.conf")
342 confdata = bb.cookerdata.parse_config_file(destlayerconf, confdata)
343
344 origlayerdir = find_layerdir(recipefile)
345 if not origlayerdir:
346 return (None, False)
347 # Now join this to the path where the bbappend is going and check if it is covered by BBFILES
348 appendpath = os.path.join(destlayerdir, os.path.relpath(os.path.dirname(recipefile), origlayerdir), appendfn)
349 closepath = ''
350 pathok = True
351 for bbfilespec in confdata.getVar('BBFILES', True).split():
352 if fnmatch.fnmatchcase(appendpath, bbfilespec):
353 # Our append path works, we're done
354 break
355 elif bbfilespec.startswith(destlayerdir) and fnmatch.fnmatchcase('test.bbappend', os.path.basename(bbfilespec)):
356 # Try to find the longest matching path
357 if len(bbfilespec) > len(closepath):
358 closepath = bbfilespec
359 else:
360 # Unfortunately the bbappend layer and the original recipe's layer don't have the same structure
361 if closepath:
362 # bbappend layer's layer.conf at least has a spec that picks up .bbappend files
363 # Now we just need to substitute out any wildcards
364 appendsubdir = os.path.relpath(os.path.dirname(closepath), destlayerdir)
365 if 'recipes-*' in appendsubdir:
366 # Try to copy this part from the original recipe path
367 res = re.search('/recipes-[^/]+/', recipefile)
368 if res:
369 appendsubdir = appendsubdir.replace('/recipes-*/', res.group(0))
370 # This is crude, but we have to do something
371 appendsubdir = appendsubdir.replace('*', recipefn.split('_')[0])
372 appendsubdir = appendsubdir.replace('?', 'a')
373 appendpath = os.path.join(destlayerdir, appendsubdir, appendfn)
374 else:
375 pathok = False
376 return (appendpath, pathok)
377
378
379def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, machine=None, extralines=None, removevalues=None):
380 """
381 Writes a bbappend file for a recipe
382 Parameters:
383 rd: data dictionary for the recipe
384 destlayerdir: base directory of the layer to place the bbappend in
385 (subdirectory path from there will be determined automatically)
386 srcfiles: dict of source files to add to SRC_URI, where the value
387 is the full path to the file to be added, and the value is the
388 original filename as it would appear in SRC_URI or None if it
389 isn't already present. You may pass None for this parameter if
390 you simply want to specify your own content via the extralines
391 parameter.
392 install: dict mapping entries in srcfiles to a tuple of two elements:
393 install path (*without* ${D} prefix) and permission value (as a
394 string, e.g. '0644').
395 wildcardver: True to use a % wildcard in the bbappend filename, or
396 False to make the bbappend specific to the recipe version.
397 machine:
398 If specified, make the changes in the bbappend specific to this
399 machine. This will also cause PACKAGE_ARCH = "${MACHINE_ARCH}"
400 to be added to the bbappend.
401 extralines:
402 Extra lines to add to the bbappend. This may be a dict of name
403 value pairs, or simply a list of the lines.
404 removevalues:
405 Variable values to remove - a dict of names/values.
406 """
407
408 if not removevalues:
409 removevalues = {}
410
411 # Determine how the bbappend should be named
412 appendpath, pathok = get_bbappend_path(rd, destlayerdir, wildcardver)
413 if not appendpath:
414 bb.error('Unable to determine layer directory containing %s' % recipefile)
415 return (None, None)
416 if not pathok:
417 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)))
418
419 appenddir = os.path.dirname(appendpath)
420 bb.utils.mkdirhier(appenddir)
421
422 # FIXME check if the bbappend doesn't get overridden by a higher priority layer?
423
424 layerdirs = [os.path.abspath(layerdir) for layerdir in rd.getVar('BBLAYERS', True).split()]
425 if not os.path.abspath(destlayerdir) in layerdirs:
426 bb.warn('Specified layer is not currently enabled in bblayers.conf, you will need to add it before this bbappend will be active')
427
428 bbappendlines = []
429 if extralines:
430 if isinstance(extralines, dict):
431 for name, value in extralines.iteritems():
432 bbappendlines.append((name, '=', value))
433 else:
434 # Do our best to split it
435 for line in extralines:
436 if line[-1] == '\n':
437 line = line[:-1]
438 splitline = line.split(None, 2)
439 if len(splitline) == 3:
440 bbappendlines.append(tuple(splitline))
441 else:
442 raise Exception('Invalid extralines value passed')
443
444 def popline(varname):
445 for i in xrange(0, len(bbappendlines)):
446 if bbappendlines[i][0] == varname:
447 line = bbappendlines.pop(i)
448 return line
449 return None
450
451 def appendline(varname, op, value):
452 for i in xrange(0, len(bbappendlines)):
453 item = bbappendlines[i]
454 if item[0] == varname:
455 bbappendlines[i] = (item[0], item[1], item[2] + ' ' + value)
456 break
457 else:
458 bbappendlines.append((varname, op, value))
459
460 destsubdir = rd.getVar('PN', True)
461 if srcfiles:
462 bbappendlines.append(('FILESEXTRAPATHS_prepend', ':=', '${THISDIR}/${PN}:'))
463
464 appendoverride = ''
465 if machine:
466 bbappendlines.append(('PACKAGE_ARCH', '=', '${MACHINE_ARCH}'))
467 appendoverride = '_%s' % machine
468 copyfiles = {}
469 if srcfiles:
470 instfunclines = []
471 for newfile, origsrcfile in srcfiles.iteritems():
472 srcfile = origsrcfile
473 srcurientry = None
474 if not srcfile:
475 srcfile = os.path.basename(newfile)
476 srcurientry = 'file://%s' % srcfile
477 # Double-check it's not there already
478 # FIXME do we care if the entry is added by another bbappend that might go away?
479 if not srcurientry in rd.getVar('SRC_URI', True).split():
480 if machine:
481 appendline('SRC_URI_append%s' % appendoverride, '=', ' ' + srcurientry)
482 else:
483 appendline('SRC_URI', '+=', srcurientry)
484 copyfiles[newfile] = srcfile
485 if install:
486 institem = install.pop(newfile, None)
487 if institem:
488 (destpath, perms) = institem
489 instdestpath = replace_dir_vars(destpath, rd)
490 instdirline = 'install -d ${D}%s' % os.path.dirname(instdestpath)
491 if not instdirline in instfunclines:
492 instfunclines.append(instdirline)
493 instfunclines.append('install -m %s ${WORKDIR}/%s ${D}%s' % (perms, os.path.basename(srcfile), instdestpath))
494 if instfunclines:
495 bbappendlines.append(('do_install_append%s()' % appendoverride, '', instfunclines))
496
497 bb.note('Writing append file %s' % appendpath)
498
499 if os.path.exists(appendpath):
500 # Work around lack of nonlocal in python 2
501 extvars = {'destsubdir': destsubdir}
502
503 def appendfile_varfunc(varname, origvalue, op, newlines):
504 if varname == 'FILESEXTRAPATHS_prepend':
505 if origvalue.startswith('${THISDIR}/'):
506 popline('FILESEXTRAPATHS_prepend')
507 extvars['destsubdir'] = rd.expand(origvalue.split('${THISDIR}/', 1)[1].rstrip(':'))
508 elif varname == 'PACKAGE_ARCH':
509 if machine:
510 popline('PACKAGE_ARCH')
511 return (machine, None, 4, False)
512 elif varname.startswith('do_install_append'):
513 func = popline(varname)
514 if func:
515 instfunclines = [line.strip() for line in origvalue.strip('\n').splitlines()]
516 for line in func[2]:
517 if not line in instfunclines:
518 instfunclines.append(line)
519 return (instfunclines, None, 4, False)
520 else:
521 splitval = origvalue.split()
522 changed = False
523 removevar = varname
524 if varname in ['SRC_URI', 'SRC_URI_append%s' % appendoverride]:
525 removevar = 'SRC_URI'
526 line = popline(varname)
527 if line:
528 if line[2] not in splitval:
529 splitval.append(line[2])
530 changed = True
531 else:
532 line = popline(varname)
533 if line:
534 splitval = [line[2]]
535 changed = True
536
537 if removevar in removevalues:
538 remove = removevalues[removevar]
539 if isinstance(remove, basestring):
540 if remove in splitval:
541 splitval.remove(remove)
542 changed = True
543 else:
544 for removeitem in remove:
545 if removeitem in splitval:
546 splitval.remove(removeitem)
547 changed = True
548
549 if changed:
550 newvalue = splitval
551 if len(newvalue) == 1:
552 # Ensure it's written out as one line
553 if '_append' in varname:
554 newvalue = ' ' + newvalue[0]
555 else:
556 newvalue = newvalue[0]
557 if not newvalue and (op in ['+=', '.='] or '_append' in varname):
558 # There's no point appending nothing
559 newvalue = None
560 if varname.endswith('()'):
561 indent = 4
562 else:
563 indent = -1
564 return (newvalue, None, indent, True)
565 return (origvalue, None, 4, False)
566
567 varnames = [item[0] for item in bbappendlines]
568 if removevalues:
569 varnames.extend(removevalues.keys())
570
571 with open(appendpath, 'r') as f:
572 (updated, newlines) = bb.utils.edit_metadata(f, varnames, appendfile_varfunc)
573
574 destsubdir = extvars['destsubdir']
575 else:
576 updated = False
577 newlines = []
578
579 if bbappendlines:
580 for line in bbappendlines:
581 if line[0].endswith('()'):
582 newlines.append('%s {\n %s\n}\n' % (line[0], '\n '.join(line[2])))
583 else:
584 newlines.append('%s %s "%s"\n\n' % line)
585 updated = True
586
587 if updated:
588 with open(appendpath, 'w') as f:
589 f.writelines(newlines)
590
591 if copyfiles:
592 if machine:
593 destsubdir = os.path.join(destsubdir, machine)
594 for newfile, srcfile in copyfiles.iteritems():
595 filedest = os.path.join(appenddir, destsubdir, os.path.basename(srcfile))
596 if os.path.abspath(newfile) != os.path.abspath(filedest):
597 bb.note('Copying %s to %s' % (newfile, filedest))
598 bb.utils.mkdirhier(os.path.dirname(filedest))
599 shutil.copyfile(newfile, filedest)
600
601 return (appendpath, os.path.join(appenddir, destsubdir))
602
603
604def find_layerdir(fn):
605 """ Figure out relative path to base of layer for a file (e.g. a recipe)"""
606 pth = os.path.dirname(fn)
607 layerdir = ''
608 while pth:
609 if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')):
610 layerdir = pth
611 break
612 pth = os.path.dirname(pth)
613 return layerdir
614
615
616def replace_dir_vars(path, d):
617 """Replace common directory paths with appropriate variable references (e.g. /etc becomes ${sysconfdir})"""
618 dirvars = {}
619 # Sort by length so we get the variables we're interested in first
620 for var in sorted(d.keys(), key=len):
621 if var.endswith('dir') and var.lower() == var:
622 value = d.getVar(var, True)
623 if value.startswith('/') and not '\n' in value and value not in dirvars:
624 dirvars[value] = var
625 for dirpath in sorted(dirvars.keys(), reverse=True):
626 path = path.replace(dirpath, '${%s}' % dirvars[dirpath])
627 return path
628
629def get_recipe_pv_without_srcpv(pv, uri_type):
630 """
631 Get PV without SRCPV common in SCM's for now only
632 support git.
633
634 Returns tuple with pv, prefix and suffix.
635 """
636 pfx = ''
637 sfx = ''
638
639 if uri_type == 'git':
640 git_regex = re.compile("(?P<pfx>v?)(?P<ver>[^\+]*)((?P<sfx>\+(git)?r?(AUTOINC\+))(?P<rev>.*))?")
641 m = git_regex.match(pv)
642
643 if m:
644 pv = m.group('ver')
645 pfx = m.group('pfx')
646 sfx = m.group('sfx')
647 else:
648 regex = re.compile("(?P<pfx>(v|r)?)(?P<ver>.*)")
649 m = regex.match(pv)
650 if m:
651 pv = m.group('ver')
652 pfx = m.group('pfx')
653
654 return (pv, pfx, sfx)
655
656def get_recipe_upstream_version(rd):
657 """
658 Get upstream version of recipe using bb.fetch2 methods with support for
659 http, https, ftp and git.
660
661 bb.fetch2 exceptions can be raised,
662 FetchError when don't have network access or upstream site don't response.
663 NoMethodError when uri latest_versionstring method isn't implemented.
664
665 Returns a dictonary with version, type and datetime.
666 Type can be A for Automatic, M for Manual and U for Unknown.
667 """
668 from bb.fetch2 import decodeurl
669 from datetime import datetime
670
671 ru = {}
672 ru['version'] = ''
673 ru['type'] = 'U'
674 ru['datetime'] = ''
675
676 # XXX: If don't have SRC_URI means that don't have upstream sources so
677 # returns 1.0.
678 src_uris = rd.getVar('SRC_URI', True)
679 if not src_uris:
680 ru['version'] = '1.0'
681 ru['type'] = 'M'
682 ru['datetime'] = datetime.now()
683 return ru
684
685 # XXX: we suppose that the first entry points to the upstream sources
686 src_uri = src_uris.split()[0]
687 uri_type, _, _, _, _, _ = decodeurl(src_uri)
688
689 pv = rd.getVar('PV', True)
690
691 manual_upstream_version = rd.getVar("RECIPE_UPSTREAM_VERSION", True)
692 if manual_upstream_version:
693 # manual tracking of upstream version.
694 ru['version'] = manual_upstream_version
695 ru['type'] = 'M'
696
697 manual_upstream_date = rd.getVar("CHECK_DATE", True)
698 if manual_upstream_date:
699 date = datetime.strptime(manual_upstream_date, "%b %d, %Y")
700 else:
701 date = datetime.now()
702 ru['datetime'] = date
703
704 elif uri_type == "file":
705 # files are always up-to-date
706 ru['version'] = pv
707 ru['type'] = 'A'
708 ru['datetime'] = datetime.now()
709 else:
710 ud = bb.fetch2.FetchData(src_uri, rd)
711 pupver = ud.method.latest_versionstring(ud, rd)
712 (upversion, revision) = pupver
713
714 # format git version version+gitAUTOINC+HASH
715 if uri_type == 'git':
716 (pv, pfx, sfx) = get_recipe_pv_without_srcpv(pv, uri_type)
717
718 # if contains revision but not upversion use current pv
719 if upversion == '' and revision:
720 upversion = pv
721
722 if upversion:
723 tmp = upversion
724 upversion = ''
725
726 if pfx:
727 upversion = pfx + tmp
728 else:
729 upversion = tmp
730
731 if sfx:
732 upversion = upversion + sfx + revision[:10]
733
734 if upversion:
735 ru['version'] = upversion
736 ru['type'] = 'A'
737
738 ru['datetime'] = datetime.now()
739
740 return ru