have bb.parse.handle() throw ParseError if the input file is not
[bitbake.git] / lib / bb / build.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 BitBake 'Build' implementation
6
7 Core code for function execution and task handling in the
8 BitBake build tools.
9
10 Copyright (C) 2003, 2004  Chris Larson
11
12 Based on Gentoo's portage.py.
13
14 This program is free software; you can redistribute it and/or modify it under
15 the terms of the GNU General Public License as published by the Free Software
16 Foundation; either version 2 of the License, or (at your option) any later
17 version.
18
19 This program is distributed in the hope that it will be useful, but WITHOUT
20 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License along with
24
25 Based on functions from the base bb module, Copyright 2003 Holger Schurig
26 """
27
28 from bb import debug, data, fetch, fatal, error, note, event, mkdirhier
29 import bb, os
30
31 # data holds flags and function name for a given task
32 _task_data = data.init()
33
34 # graph represents task interdependencies
35 _task_graph = bb.digraph()
36
37 # stack represents execution order, excepting dependencies
38 _task_stack = []
39
40 # events
41 class FuncFailed(Exception):
42     """Executed function failed"""
43
44 class EventException(Exception):
45     """Exception which is associated with an Event."""
46
47     def __init__(self, msg, event):
48         self._event = event
49
50     def getEvent(self):
51         return self._event
52
53     def setEvent(self, event):
54         self._event = event
55
56     event = property(getEvent, setEvent, None, "event property")
57
58
59 class TaskBase(event.Event):
60     """Base class for task events"""
61
62     def __init__(self, t, d = {}):
63         self._task = t
64         event.Event.__init__(self, d)
65
66     def getTask(self):
67         return self._task
68
69     def setTask(self, task):
70         self._task = task
71
72     task = property(getTask, setTask, None, "task property")
73
74 class TaskStarted(TaskBase):
75     """Task execution started"""
76
77 class TaskSucceeded(TaskBase):
78     """Task execution completed"""
79
80 class TaskFailed(TaskBase):
81     """Task execution failed"""
82
83 class InvalidTask(TaskBase):
84     """Invalid Task"""
85
86 # functions
87
88 def init(data):
89     global _task_data, _task_graph, _task_stack
90     _task_data = data.init()
91     _task_graph = bb.digraph()
92     _task_stack = []
93
94
95 def exec_func(func, d, dirs = None):
96     """Execute an BB 'function'"""
97
98     body = data.getVar(func, d)
99     if not body:
100         return
101
102     if not dirs:
103         dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
104     for adir in dirs:
105         adir = data.expand(adir, d)
106         mkdirhier(adir)
107
108     if len(dirs) > 0:
109         adir = dirs[-1]
110     else:
111         adir = data.getVar('B', d, 1)
112
113     adir = data.expand(adir, d)
114
115     try:
116         prevdir = os.getcwd()
117     except OSError:
118         prevdir = data.expand('${TOPDIR}', d)
119     if adir and os.access(adir, os.F_OK):
120         os.chdir(adir)
121
122     if data.getVarFlag(func, "python", d):
123         exec_func_python(func, d)
124     else:
125         exec_func_shell(func, d)
126     os.chdir(prevdir)
127
128 def exec_func_python(func, d):
129     """Execute a python BB 'function'"""
130     import re, os
131
132     tmp = "def " + func + "():\n%s" % data.getVar(func, d)
133     comp = compile(tmp + '\n' + func + '()', bb.data.getVar('FILE', d, 1) + ':' + func, "exec")
134     prevdir = os.getcwd()
135     g = {} # globals
136     g['bb'] = bb
137     g['os'] = os
138     g['d'] = d
139     exec comp in g
140     if os.path.exists(prevdir):
141         os.chdir(prevdir)
142
143 def exec_func_shell(func, d):
144     """Execute a shell BB 'function' Returns true if execution was successful.
145
146     For this, it creates a bash shell script in the tmp dectory, writes the local
147     data into it and finally executes. The output of the shell will end in a log file and stdout.
148
149     Note on directory behavior.  The 'dirs' varflag should contain a list
150     of the directories you need created prior to execution.  The last
151     item in the list is where we will chdir/cd to.
152     """
153     import sys
154
155     deps = data.getVarFlag(func, 'deps', d)
156     check = data.getVarFlag(func, 'check', d)
157     if check in globals():
158         if globals()[check](func, deps):
159             return
160
161     global logfile
162     t = data.getVar('T', d, 1)
163     if not t:
164         return 0
165     mkdirhier(t)
166     logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
167     runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
168
169     f = open(runfile, "w")
170     f.write("#!/bin/sh -e\n")
171     if bb.debug_level > 0: f.write("set -x\n")
172     data.emit_env(f, d)
173
174     f.write("cd %s\n" % os.getcwd())
175     if func: f.write("%s\n" % func)
176     f.close()
177     os.chmod(runfile, 0775)
178     if not func:
179         error("Function not specified")
180         raise FuncFailed()
181
182     # open logs
183     si = file('/dev/null', 'r')
184     try:
185         if bb.debug_level > 0:
186             so = os.popen("tee \"%s\"" % logfile, "w")
187         else:
188             so = file(logfile, 'w')
189     except OSError, e:
190         bb.error("opening log file: %s" % e)
191         pass
192
193     se = so
194
195     # dup the existing fds so we dont lose them
196     osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
197     oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
198     ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
199
200     # replace those fds with our own
201     os.dup2(si.fileno(), osi[1])
202     os.dup2(so.fileno(), oso[1])
203     os.dup2(se.fileno(), ose[1])
204
205     # execute function
206     prevdir = os.getcwd()
207     if data.getVarFlag(func, "fakeroot", d):
208         maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
209     else:
210         maybe_fakeroot = ''
211     ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
212     os.chdir(prevdir)
213
214     # restore the backups
215     os.dup2(osi[0], osi[1])
216     os.dup2(oso[0], oso[1])
217     os.dup2(ose[0], ose[1])
218
219     # close our logs
220     si.close()
221     so.close()
222     se.close()
223
224     # close the backup fds
225     os.close(osi[0])
226     os.close(oso[0])
227     os.close(ose[0])
228
229     if ret==0:
230         if bb.debug_level > 0:
231             os.remove(runfile)
232 #            os.remove(logfile)
233         return
234     else:
235         error("function %s failed" % func)
236         if data.getVar("BBINCLUDELOGS", d):
237             error("log data follows (%s)" % logfile)
238             f = open(logfile, "r")
239             while True:
240                 l = f.readline()
241                 if l == '':
242                     break
243                 l = l.rstrip()
244                 print '| %s' % l
245             f.close()
246         else:
247             error("see log in %s" % logfile)
248         raise FuncFailed()
249
250
251 _task_cache = []
252
253 def exec_task(task, d):
254     """Execute an BB 'task'
255
256        The primary difference between executing a task versus executing
257        a function is that a task exists in the task digraph, and therefore
258        has dependencies amongst other tasks."""
259
260     # check if the task is in the graph..
261     task_graph = data.getVar('_task_graph', d)
262     if not task_graph:
263         task_graph = bb.digraph()
264         data.setVar('_task_graph', task_graph, d)
265     task_cache = data.getVar('_task_cache', d)
266     if not task_cache:
267         task_cache = []
268         data.setVar('_task_cache', task_cache, d)
269     if not task_graph.hasnode(task):
270         raise EventException("", InvalidTask(task, d))
271
272     # check whether this task needs executing..
273     if not data.getVarFlag(task, 'force', d):
274         if stamp_is_current(task, d):
275             return 1
276
277     # follow digraph path up, then execute our way back down
278     def execute(graph, item):
279         if data.getVarFlag(item, 'task', d):
280             if item in task_cache:
281                 return 1
282
283             if task != item:
284                 # deeper than toplevel, exec w/ deps
285                 exec_task(item, d)
286                 return 1
287
288             try:
289                 debug(1, "Executing task %s" % item)
290                 old_overrides = data.getVar('OVERRIDES', d, 0)
291                 localdata = data.createCopy(d)
292                 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
293                 data.update_data(localdata)
294                 event.fire(TaskStarted(item, localdata))
295                 exec_func(item, localdata)
296                 event.fire(TaskSucceeded(item, localdata))
297                 task_cache.append(item)
298             except FuncFailed, reason:
299                 note( "Task failed: %s" % reason )
300                 failedevent = TaskFailed(item, d)
301                 event.fire(failedevent)
302                 raise EventException(None, failedevent)
303
304     # execute
305     task_graph.walkdown(task, execute)
306
307     # make stamp, or cause event and raise exception
308     if not data.getVarFlag(task, 'nostamp', d):
309         mkstamp(task, d)
310
311
312 def stamp_is_current(task, d, checkdeps = 1):
313     """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
314     task_graph = data.getVar('_task_graph', d)
315     if not task_graph:
316         task_graph = bb.digraph()
317         data.setVar('_task_graph', task_graph, d)
318     stamp = data.getVar('STAMP', d)
319     if not stamp:
320         return 0
321     stampfile = "%s.%s" % (data.expand(stamp, d), task)
322     if not os.access(stampfile, os.F_OK):
323         return 0
324
325     if checkdeps == 0:
326         return 1
327
328     import stat
329     tasktime = os.stat(stampfile)[stat.ST_MTIME]
330
331     _deps = []
332     def checkStamp(graph, task):
333         # check for existance
334         if data.getVarFlag(task, 'nostamp', d):
335             return 1
336
337         if not stamp_is_current(task, d, 0):
338             return 0
339
340         depfile = "%s.%s" % (data.expand(stamp, d), task)
341         deptime = os.stat(depfile)[stat.ST_MTIME]
342         if deptime > tasktime:
343             return 0
344         return 1
345
346     return task_graph.walkdown(task, checkStamp)
347
348
349 def md5_is_current(task):
350     """Check if a md5 file for a given task is current"""
351
352
353 def mkstamp(task, d):
354     """Creates/updates a stamp for a given task"""
355     stamp = data.getVar('STAMP', d)
356     if not stamp:
357         return
358     stamp = "%s.%s" % (data.expand(stamp, d), task)
359     mkdirhier(os.path.dirname(stamp))
360     open(stamp, "w+")
361
362
363 def add_task(task, deps, d):
364     task_graph = data.getVar('_task_graph', d)
365     if not task_graph:
366         task_graph = bb.digraph()
367     data.setVarFlag(task, 'task', 1, d)
368     task_graph.addnode(task, None)
369     for dep in deps:
370         if not task_graph.hasnode(dep):
371             task_graph.addnode(dep, None)
372         task_graph.addnode(task, dep)
373     # don't assume holding a reference
374     data.setVar('_task_graph', task_graph, d)
375
376
377 def remove_task(task, kill, d):
378     """Remove an BB 'task'.
379
380        If kill is 1, also remove tasks that depend on this task."""
381
382     task_graph = data.getVar('_task_graph', d)
383     if not task_graph:
384         task_graph = bb.digraph()
385     if not task_graph.hasnode(task):
386         return
387
388     data.delVarFlag(task, 'task', d)
389     ref = 1
390     if kill == 1:
391         ref = 2
392     task_graph.delnode(task, ref)
393     data.setVar('_task_graph', task_graph, d)
394
395 def task_exists(task, d):
396     task_graph = data.getVar('_task_graph', d)
397     if not task_graph:
398         task_graph = bb.digraph()
399         data.setVar('_task_graph', task_graph, d)
400     return task_graph.hasnode(task)
401
402 def get_task_data():
403     return _task_data