summaryrefslogtreecommitdiffstats
path: root/meta
diff options
context:
space:
mode:
Diffstat (limited to 'meta')
-rw-r--r--meta/lib/oe/recipeutils.py279
1 files changed, 279 insertions, 0 deletions
diff --git a/meta/lib/oe/recipeutils.py b/meta/lib/oe/recipeutils.py
new file mode 100644
index 0000000000..1758dcecba
--- /dev/null
+++ b/meta/lib/oe/recipeutils.py
@@ -0,0 +1,279 @@
1# Utility functions for reading and modifying recipes
2#
3# Some code borrowed from the OE layer index
4#
5# Copyright (C) 2013-2014 Intel Corporation
6#
7
8import sys
9import os
10import os.path
11import tempfile
12import textwrap
13import difflib
14import utils
15import shutil
16import re
17from collections import OrderedDict, defaultdict
18
19
20# Help us to find places to insert values
21recipe_progression = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION', 'LICENSE', 'LIC_FILES_CHKSUM', 'PROVIDES', 'DEPENDS', 'PR', 'PV', 'SRC_URI', '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']
22# Variables that sometimes are a bit long but shouldn't be wrapped
23nowrap_vars = ['SUMMARY', 'HOMEPAGE', 'BUGTRACKER']
24list_vars = ['SRC_URI', 'LIC_FILES_CHKSUM']
25meta_vars = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION']
26
27
28def pn_to_recipe(cooker, pn):
29 """Convert a recipe name (PN) to the path to the recipe file"""
30 import bb.providers
31
32 if pn in cooker.recipecache.pkg_pn:
33 filenames = cooker.recipecache.pkg_pn[pn]
34 best = bb.providers.findBestProvider(pn, cooker.data, cooker.recipecache, cooker.recipecache.pkg_pn)
35 return best[3]
36 else:
37 return None
38
39
40def get_unavailable_reasons(cooker, pn):
41 """If a recipe could not be found, find out why if possible"""
42 import bb.taskdata
43 taskdata = bb.taskdata.TaskData(None, skiplist=cooker.skiplist)
44 return taskdata.get_reasons(pn)
45
46
47def parse_recipe(fn, d):
48 """Parse an individual recipe"""
49 import bb.cache
50 envdata = bb.cache.Cache.loadDataFull(fn, [], d)
51 return envdata
52
53
54def get_var_files(fn, varlist, d):
55 """Find the file in which each of a list of variables is set.
56 Note: requires variable history to be enabled when parsing.
57 """
58 envdata = parse_recipe(fn, d)
59 varfiles = {}
60 for v in varlist:
61 history = envdata.varhistory.variable(v)
62 files = []
63 for event in history:
64 if 'file' in event and not 'flag' in event:
65 files.append(event['file'])
66 if files:
67 actualfile = files[-1]
68 else:
69 actualfile = None
70 varfiles[v] = actualfile
71
72 return varfiles
73
74
75def patch_recipe_file(fn, values, patch=False, relpath=''):
76 """Update or insert variable values into a recipe file (assuming you
77 have already identified the exact file you want to update.)
78 Note that some manual inspection/intervention may be required
79 since this cannot handle all situations.
80 """
81 remainingnames = {}
82 for k in values.keys():
83 remainingnames[k] = recipe_progression.index(k) if k in recipe_progression else -1
84 remainingnames = OrderedDict(sorted(remainingnames.iteritems(), key=lambda x: x[1]))
85
86 with tempfile.NamedTemporaryFile('w', delete=False) as tf:
87 def outputvalue(name):
88 rawtext = '%s = "%s"\n' % (name, values[name])
89 if name in nowrap_vars:
90 tf.write(rawtext)
91 elif name in list_vars:
92 splitvalue = values[name].split()
93 if len(splitvalue) > 1:
94 linesplit = ' \\\n' + (' ' * (len(name) + 4))
95 tf.write('%s = "%s%s"\n' % (name, linesplit.join(splitvalue), linesplit))
96 else:
97 tf.write(rawtext)
98 else:
99 wrapped = textwrap.wrap(rawtext)
100 for wrapline in wrapped[:-1]:
101 tf.write('%s \\\n' % wrapline)
102 tf.write('%s\n' % wrapped[-1])
103
104 tfn = tf.name
105 with open(fn, 'r') as f:
106 # First runthrough - find existing names (so we know not to insert based on recipe_progression)
107 # Second runthrough - make the changes
108 existingnames = []
109 for runthrough in [1, 2]:
110 currname = None
111 for line in f:
112 if not currname:
113 insert = False
114 for k in remainingnames.keys():
115 for p in recipe_progression:
116 if re.match('^%s[ ?:=]' % p, line):
117 if remainingnames[k] > -1 and recipe_progression.index(p) > remainingnames[k] and runthrough > 1 and not k in existingnames:
118 outputvalue(k)
119 del remainingnames[k]
120 break
121 for k in remainingnames.keys():
122 if re.match('^%s[ ?:=]' % k, line):
123 currname = k
124 if runthrough == 1:
125 existingnames.append(k)
126 else:
127 del remainingnames[k]
128 break
129 if currname and runthrough > 1:
130 outputvalue(currname)
131
132 if currname:
133 sline = line.rstrip()
134 if not sline.endswith('\\'):
135 currname = None
136 continue
137 if runthrough > 1:
138 tf.write(line)
139 f.seek(0)
140 if remainingnames:
141 tf.write('\n')
142 for k in remainingnames.keys():
143 outputvalue(k)
144
145 with open(tfn, 'U') as f:
146 tolines = f.readlines()
147 if patch:
148 with open(fn, 'U') as f:
149 fromlines = f.readlines()
150 relfn = os.path.relpath(fn, relpath)
151 diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn)
152 os.remove(tfn)
153 return diff
154 else:
155 with open(fn, 'w') as f:
156 f.writelines(tolines)
157 os.remove(tfn)
158 return None
159
160def localise_file_vars(fn, varfiles, varlist):
161 """Given a list of variables and variable history (fetched with get_var_files())
162 find where each variable should be set/changed. This handles for example where a
163 recipe includes an inc file where variables might be changed - in most cases
164 we want to update the inc file when changing the variable value rather than adding
165 it to the recipe itself.
166 """
167 fndir = os.path.dirname(fn) + os.sep
168
169 first_meta_file = None
170 for v in meta_vars:
171 f = varfiles.get(v, None)
172 if f:
173 actualdir = os.path.dirname(f) + os.sep
174 if actualdir.startswith(fndir):
175 first_meta_file = f
176 break
177
178 filevars = defaultdict(list)
179 for v in varlist:
180 f = varfiles[v]
181 # Only return files that are in the same directory as the recipe or in some directory below there
182 # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable
183 # in if we were going to set a value specific to this recipe)
184 if f:
185 actualfile = f
186 else:
187 # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it
188 if first_meta_file:
189 actualfile = first_meta_file
190 else:
191 actualfile = fn
192
193 actualdir = os.path.dirname(actualfile) + os.sep
194 if not actualdir.startswith(fndir):
195 actualfile = fn
196 filevars[actualfile].append(v)
197
198 return filevars
199
200def patch_recipe(d, fn, varvalues, patch=False, relpath=''):
201 """Modify a list of variable values in the specified recipe. Handles inc files if
202 used by the recipe.
203 """
204 varlist = varvalues.keys()
205 varfiles = get_var_files(fn, varlist, d)
206 locs = localise_file_vars(fn, varfiles, varlist)
207 patches = []
208 for f,v in locs.iteritems():
209 vals = {k: varvalues[k] for k in v}
210 patchdata = patch_recipe_file(f, vals, patch, relpath)
211 if patch:
212 patches.append(patchdata)
213
214 if patch:
215 return patches
216 else:
217 return None
218
219
220
221def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True):
222 """Copy (local) recipe files, including both files included via include/require,
223 and files referred to in the SRC_URI variable."""
224 import bb.fetch2
225 import oe.path
226
227 # FIXME need a warning if the unexpanded SRC_URI value contains variable references
228
229 uris = (d.getVar('SRC_URI', True) or "").split()
230 fetch = bb.fetch2.Fetch(uris, d)
231 if download:
232 fetch.download()
233
234 # Copy local files to target directory and gather any remote files
235 bb_dir = os.path.dirname(d.getVar('FILE', True)) + os.sep
236 remotes = []
237 includes = [path for path in d.getVar('BBINCLUDED', True).split() if
238 path.startswith(bb_dir) and os.path.exists(path)]
239 for path in fetch.localpaths() + includes:
240 # Only import files that are under the meta directory
241 if path.startswith(bb_dir):
242 if not whole_dir:
243 relpath = os.path.relpath(path, bb_dir)
244 subdir = os.path.join(tgt_dir, os.path.dirname(relpath))
245 if not os.path.exists(subdir):
246 os.makedirs(subdir)
247 shutil.copy2(path, os.path.join(tgt_dir, relpath))
248 else:
249 remotes.append(path)
250 # Simply copy whole meta dir, if requested
251 if whole_dir:
252 shutil.copytree(bb_dir, tgt_dir)
253
254 return remotes
255
256
257def get_recipe_patches(d):
258 """Get a list of the patches included in SRC_URI within a recipe."""
259 patchfiles = []
260 # Execute src_patches() defined in patch.bbclass - this works since that class
261 # is inherited globally
262 patches = bb.utils.exec_flat_python_func('src_patches', d)
263 for patch in patches:
264 _, _, local, _, _, parm = bb.fetch.decodeurl(patch)
265 patchfiles.append(local)
266 return patchfiles
267
268
269def validate_pn(pn):
270 """Perform validation on a recipe name (PN) for a new recipe."""
271 reserved_names = ['forcevariable', 'append', 'prepend', 'remove']
272 if not re.match('[0-9a-z-]+', pn):
273 return 'Recipe name "%s" is invalid: only characters 0-9, a-z and - are allowed' % pn
274 elif pn in reserved_names:
275 return 'Recipe name "%s" is invalid: is a reserved keyword' % pn
276 elif pn.startswith('pn-'):
277 return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn
278 return ''
279