Merge the BBCLASSEXTEND code from Poky. This allows once recipe to provide mutliple...
[bitbake.git] / lib / bb / parse / parse_py / BBHandler.py
1 #!/usr/bin/env python
2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 """
5    class for handling .bb files
6
7    Reads a .bb file and obtains its metadata
8
9 """
10
11
12 #  Copyright (C) 2003, 2004  Chris Larson
13 #  Copyright (C) 2003, 2004  Phil Blundell
14 #   
15 # This program is free software; you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License version 2 as
17 # published by the Free Software Foundation.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License along
25 # with this program; if not, write to the Free Software Foundation, Inc.,
26 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27
28 import re, bb, os, sys, time
29 import bb.fetch, bb.build, bb.utils
30 from bb import data, fetch, methodpool
31
32 from ConfHandler import include, localpath, obtain, init
33 from bb.parse import ParseError
34
35 __func_start_regexp__    = re.compile( r"(((?P<py>python)|(?P<fr>fakeroot))\s*)*(?P<func>[\w\.\-\+\{\}\$]+)?\s*\(\s*\)\s*{$" )
36 __inherit_regexp__       = re.compile( r"inherit\s+(.+)" )
37 __export_func_regexp__   = re.compile( r"EXPORT_FUNCTIONS\s+(.+)" )
38 __addtask_regexp__       = re.compile("addtask\s+(?P<func>\w+)\s*((before\s*(?P<before>((.*(?=after))|(.*))))|(after\s*(?P<after>((.*(?=before))|(.*)))))*")
39 __addhandler_regexp__    = re.compile( r"addhandler\s+(.+)" )
40 __def_regexp__           = re.compile( r"def\s+(\w+).*:" )
41 __python_func_regexp__   = re.compile( r"(\s+.*)|(^$)" )
42 __word__ = re.compile(r"\S+")
43
44 __infunc__ = ""
45 __inpython__ = False
46 __body__   = []
47 __classname__ = ""
48 classes = [ None, ]
49
50 # We need to indicate EOF to the feeder. This code is so messy that
51 # factoring it out to a close_parse_file method is out of question.
52 # We will use the IN_PYTHON_EOF as an indicator to just close the method
53 #
54 # The two parts using it are tightly integrated anyway
55 IN_PYTHON_EOF = -9999999999999
56
57 __parsed_methods__ = methodpool.get_parsed_dict()
58
59 def supports(fn, d):
60     localfn = localpath(fn, d)
61     return localfn[-3:] == ".bb" or localfn[-8:] == ".bbclass" or localfn[-4:] == ".inc"
62
63 def inherit(files, d):
64     __inherit_cache = data.getVar('__inherit_cache', d) or []
65     fn = ""
66     lineno = 0
67     files = data.expand(files, d)
68     for file in files:
69         if file[0] != "/" and file[-8:] != ".bbclass":
70             file = os.path.join('classes', '%s.bbclass' % file)
71
72         if not file in __inherit_cache:
73             bb.msg.debug(2, bb.msg.domain.Parsing, "BB %s:%d: inheriting %s" % (fn, lineno, file))
74             __inherit_cache.append( file )
75             data.setVar('__inherit_cache', __inherit_cache, d)
76             include(fn, file, d, "inherit")
77             __inherit_cache = data.getVar('__inherit_cache', d) or []
78
79
80 def finalise(fn, d):
81     data.expandKeys(d)
82     data.update_data(d)
83     anonqueue = data.getVar("__anonqueue", d, 1) or []
84     body = [x['content'] for x in anonqueue]
85     flag = { 'python' : 1, 'func' : 1 }
86     data.setVar("__anonfunc", "\n".join(body), d)
87     data.setVarFlags("__anonfunc", flag, d)
88     from bb import build
89     try:
90         t = data.getVar('T', d)
91         data.setVar('T', '${TMPDIR}/anonfunc/', d)
92         build.exec_func("__anonfunc", d)
93         data.delVar('T', d)
94         if t:
95             data.setVar('T', t, d)
96     except Exception, e:
97         bb.msg.debug(1, bb.msg.domain.Parsing, "Exception when executing anonymous function: %s" % e)
98         raise
99     data.delVar("__anonqueue", d)
100     data.delVar("__anonfunc", d)
101     data.update_data(d)
102
103     all_handlers = {} 
104     for var in data.getVar('__BBHANDLERS', d) or []:
105         # try to add the handler
106         handler = data.getVar(var,d)
107         bb.event.register(var, handler)
108
109     tasklist = data.getVar('__BBTASKS', d) or []
110     bb.build.add_tasks(tasklist, d)
111
112
113 def handle(fn, d, include = 0):
114     global __func_start_regexp__, __inherit_regexp__, __export_func_regexp__, __addtask_regexp__, __addhandler_regexp__, __infunc__, __body__, __residue__
115     __body__ = []
116     __infunc__ = ""
117     __classname__ = ""
118     __residue__ = []
119
120     if include == 0:
121         bb.msg.debug(2, bb.msg.domain.Parsing, "BB " + fn + ": handle(data)")
122     else:
123         bb.msg.debug(2, bb.msg.domain.Parsing, "BB " + fn + ": handle(data, include)")
124
125     (root, ext) = os.path.splitext(os.path.basename(fn))
126     base_name = "%s%s" % (root,ext)
127     init(d)
128
129     if ext == ".bbclass":
130         __classname__ = root
131         classes.append(__classname__)
132         __inherit_cache = data.getVar('__inherit_cache', d) or []
133         if not fn in __inherit_cache:
134             __inherit_cache.append(fn)
135             data.setVar('__inherit_cache', __inherit_cache, d)
136
137     if include != 0:
138         oldfile = data.getVar('FILE', d)
139     else:
140         oldfile = None
141
142     fn = obtain(fn, d)
143     bbpath = (data.getVar('BBPATH', d, 1) or '').split(':')
144     if not os.path.isabs(fn):
145         f = None
146         for p in bbpath:
147             j = os.path.join(p, fn)
148             if os.access(j, os.R_OK):
149                 abs_fn = j
150                 f = open(j, 'r')
151                 break
152         if f is None:
153             raise IOError("file %s not found" % fn)
154     else:
155         f = open(fn,'r')
156         abs_fn = fn
157
158     if include:
159         bb.parse.mark_dependency(d, abs_fn)
160
161     if ext != ".bbclass":
162         data.setVar('FILE', fn, d)
163
164     lineno = 0
165     while 1:
166         lineno = lineno + 1
167         s = f.readline()
168         if not s: break
169         s = s.rstrip()
170         feeder(lineno, s, fn, base_name, d)
171     if __inpython__:
172         # add a blank line to close out any python definition
173         feeder(IN_PYTHON_EOF, "", fn, base_name, d)
174     if ext == ".bbclass":
175         classes.remove(__classname__)
176     else:
177         if include == 0:
178             multi = data.getVar('BBCLASSEXTEND', d, 1)
179             if multi:
180                 based = bb.data.createCopy(d)
181                 finalise(fn, based)
182                 darray = {"": based}
183                 for cls in multi.split():
184                     pn = data.getVar('PN', d, True)
185                     based = bb.data.createCopy(d)
186                     data.setVar('PN', pn + '-' + cls, based)
187                     inherit([cls], based)
188                     finalise(fn, based)
189                     darray[cls] = based
190                 return darray
191             else:
192                 finalise(fn, d)
193         bbpath.pop(0)
194     if oldfile:
195         bb.data.setVar("FILE", oldfile, d)
196
197     # we have parsed the bb class now
198     if ext == ".bbclass" or ext == ".inc":
199         __parsed_methods__[base_name] = 1
200
201     return d
202
203 def feeder(lineno, s, fn, root, d):
204     global __func_start_regexp__, __inherit_regexp__, __export_func_regexp__, __addtask_regexp__, __addhandler_regexp__, __def_regexp__, __python_func_regexp__, __inpython__,__infunc__, __body__, classes, bb, __residue__
205     if __infunc__:
206         if s == '}':
207             __body__.append('')
208             data.setVar(__infunc__, '\n'.join(__body__), d)
209             data.setVarFlag(__infunc__, "func", 1, d)
210             if __infunc__ == "__anonymous":
211                 anonqueue = bb.data.getVar("__anonqueue", d) or []
212                 anonitem = {}
213                 anonitem["content"] = bb.data.getVar("__anonymous", d)
214                 anonitem["flags"] = bb.data.getVarFlags("__anonymous", d)
215                 anonqueue.append(anonitem)
216                 bb.data.setVar("__anonqueue", anonqueue, d)
217                 bb.data.delVarFlags("__anonymous", d)
218                 bb.data.delVar("__anonymous", d)
219             __infunc__ = ""
220             __body__ = []
221         else:
222             __body__.append(s)
223         return
224
225     if __inpython__:
226         m = __python_func_regexp__.match(s)
227         if m and lineno != IN_PYTHON_EOF:
228             __body__.append(s)
229             return
230         else:
231             # Note we will add root to parsedmethods after having parse
232             # 'this' file. This means we will not parse methods from
233             # bb classes twice
234             if not root  in __parsed_methods__:
235                 text = '\n'.join(__body__)
236                 methodpool.insert_method( root, text, fn )
237                 funcs = data.getVar('__functions__', d) or {}
238                 if not funcs.has_key( root ):
239                     funcs[root] = text 
240                 else:
241                     funcs[root] = "%s\n%s" % (funcs[root], text)
242
243                 data.setVar('__functions__', funcs, d)
244             __body__ = []
245             __inpython__ = False
246
247             if lineno == IN_PYTHON_EOF:
248                 return
249
250 #           fall through
251
252     if s == '' or s[0] == '#': return          # skip comments and empty lines
253
254     if s[-1] == '\\':
255         __residue__.append(s[:-1])
256         return
257
258     s = "".join(__residue__) + s
259     __residue__ = []
260
261     m = __func_start_regexp__.match(s)
262     if m:
263         __infunc__ = m.group("func") or "__anonymous"
264         key = __infunc__
265         if data.getVar(key, d):
266 #           clean up old version of this piece of metadata, as its
267 #           flags could cause problems
268             data.setVarFlag(key, 'python', None, d)
269             data.setVarFlag(key, 'fakeroot', None, d)
270         if m.group("py") is not None:
271             data.setVarFlag(key, "python", "1", d)
272         else:
273             data.delVarFlag(key, "python", d)
274         if m.group("fr") is not None:
275             data.setVarFlag(key, "fakeroot", "1", d)
276         else:
277             data.delVarFlag(key, "fakeroot", d)
278         return
279
280     m = __def_regexp__.match(s)
281     if m:
282         __body__.append(s)
283         __inpython__ = True
284         return
285
286     m = __export_func_regexp__.match(s)
287     if m:
288         fns = m.group(1)
289         n = __word__.findall(fns)
290         for f in n:
291             allvars = []
292             allvars.append(f)
293             allvars.append(classes[-1] + "_" + f)
294
295             vars = [[ allvars[0], allvars[1] ]]
296             if len(classes) > 1 and classes[-2] is not None:
297                 allvars.append(classes[-2] + "_" + f)
298                 vars = []
299                 vars.append([allvars[2], allvars[1]])
300                 vars.append([allvars[0], allvars[2]])
301
302             for (var, calledvar) in vars:
303                 if data.getVar(var, d) and not data.getVarFlag(var, 'export_func', d):
304                     continue
305
306                 if data.getVar(var, d):
307                     data.setVarFlag(var, 'python', None, d)
308                     data.setVarFlag(var, 'func', None, d)
309
310                 for flag in [ "func", "python" ]:
311                     if data.getVarFlag(calledvar, flag, d):
312                         data.setVarFlag(var, flag, data.getVarFlag(calledvar, flag, d), d)
313                 for flag in [ "dirs" ]:
314                     if data.getVarFlag(var, flag, d):
315                         data.setVarFlag(calledvar, flag, data.getVarFlag(var, flag, d), d)
316
317                 if data.getVarFlag(calledvar, "python", d):
318                     data.setVar(var, "\tbb.build.exec_func('" + calledvar + "', d)\n", d)
319                 else:
320                     data.setVar(var, "\t" + calledvar + "\n", d)
321                 data.setVarFlag(var, 'export_func', '1', d)
322
323         return
324
325     m = __addtask_regexp__.match(s)
326     if m:
327         func = m.group("func")
328         before = m.group("before")
329         after = m.group("after")
330         if func is None:
331             return
332         var = "do_" + func
333
334         data.setVarFlag(var, "task", 1, d)
335
336         bbtasks = data.getVar('__BBTASKS', d) or []
337         if not var in bbtasks:
338             bbtasks.append(var)
339         data.setVar('__BBTASKS', bbtasks, d)
340
341         existing = data.getVarFlag(var, "deps", d) or []
342         if after is not None:
343             # set up deps for function
344             for entry in after.split():
345                 if entry not in existing:
346                     existing.append(entry)
347         data.setVarFlag(var, "deps", existing, d)
348         if before is not None:
349             # set up things that depend on this func
350             for entry in before.split():
351                 existing = data.getVarFlag(entry, "deps", d) or []
352                 if var not in existing:
353                     data.setVarFlag(entry, "deps", [var] + existing, d)
354         return
355
356     m = __addhandler_regexp__.match(s)
357     if m:
358         fns = m.group(1)
359         hs = __word__.findall(fns)
360         bbhands = data.getVar('__BBHANDLERS', d) or []
361         for h in hs:
362             bbhands.append(h)
363             data.setVarFlag(h, "handler", 1, d)
364         data.setVar('__BBHANDLERS', bbhands, d)
365         return
366
367     m = __inherit_regexp__.match(s)
368     if m:
369
370         files = m.group(1)
371         n = __word__.findall(files)
372         inherit(n, d)
373         return
374
375     from bb.parse import ConfHandler
376     return ConfHandler.feeder(lineno, s, fn, d)
377
378 __pkgsplit_cache__={}
379 def vars_from_file(mypkg, d):
380     if not mypkg:
381         return (None, None, None)
382     if mypkg in __pkgsplit_cache__:
383         return __pkgsplit_cache__[mypkg]
384
385     myfile = os.path.splitext(os.path.basename(mypkg))
386     parts = myfile[0].split('_')
387     __pkgsplit_cache__[mypkg] = parts
388     if len(parts) > 3:
389         raise ParseError("Unable to generate default variables from the filename: %s (too many underscores)" % mypkg)
390     exp = 3 - len(parts)
391     tmplist = []
392     while exp != 0:
393         exp -= 1
394         tmplist.append(None)
395     parts.extend(tmplist)
396     return parts
397
398 # Add us to the handlers list
399 from bb.parse import handlers
400 handlers.append({'supports': supports, 'handle': handle, 'init': init})
401 del handlers