build.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. # ex:ts=4:sw=4:sts=4:et
  2. # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
  3. #
  4. # BitBake 'Build' implementation
  5. #
  6. # Core code for function execution and task handling in the
  7. # BitBake build tools.
  8. #
  9. # Copyright (C) 2003, 2004 Chris Larson
  10. #
  11. # Based on Gentoo's portage.py.
  12. #
  13. # This program is free software; you can redistribute it and/or modify
  14. # it under the terms of the GNU General Public License version 2 as
  15. # published by the Free Software Foundation.
  16. #
  17. # This program is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. # GNU General Public License for more details.
  21. #
  22. # You should have received a copy of the GNU General Public License along
  23. # with this program; if not, write to the Free Software Foundation, Inc.,
  24. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  25. #
  26. #Based on functions from the base bb module, Copyright 2003 Holger Schurig
  27. from bb import data, event, mkdirhier, utils
  28. import bb, os, sys
  29. # When we execute a python function we'd like certain things
  30. # in all namespaces, hence we add them to __builtins__
  31. # If we do not do this and use the exec globals, they will
  32. # not be available to subfunctions.
  33. __builtins__['bb'] = bb
  34. __builtins__['os'] = os
  35. # events
  36. class FuncFailed(Exception):
  37. """
  38. Executed function failed
  39. First parameter a message
  40. Second paramter is a logfile (optional)
  41. """
  42. class EventException(Exception):
  43. """Exception which is associated with an Event."""
  44. def __init__(self, msg, event):
  45. self.args = msg, event
  46. class TaskBase(event.Event):
  47. """Base class for task events"""
  48. def __init__(self, t, d ):
  49. self._task = t
  50. self._package = bb.data.getVar("PF", d, 1)
  51. event.Event.__init__(self, d)
  52. self._message = "package %s: task %s: %s" % (bb.data.getVar("PF", d, 1), t, bb.event.getName(self)[4:])
  53. def getTask(self):
  54. return self._task
  55. def setTask(self, task):
  56. self._task = task
  57. task = property(getTask, setTask, None, "task property")
  58. class TaskStarted(TaskBase):
  59. """Task execution started"""
  60. class TaskSucceeded(TaskBase):
  61. """Task execution completed"""
  62. class TaskFailed(TaskBase):
  63. """Task execution failed"""
  64. def __init__(self, msg, logfile, t, d ):
  65. self.logfile = logfile
  66. self.msg = msg
  67. TaskBase.__init__(self, t, d)
  68. class InvalidTask(TaskBase):
  69. """Invalid Task"""
  70. # functions
  71. def exec_func(func, d, dirs = None):
  72. """Execute an BB 'function'"""
  73. body = data.getVar(func, d)
  74. if not body:
  75. return
  76. flags = data.getVarFlags(func, d)
  77. for item in ['deps', 'check', 'interactive', 'python', 'cleandirs', 'dirs', 'lockfiles', 'fakeroot']:
  78. if not item in flags:
  79. flags[item] = None
  80. ispython = flags['python']
  81. cleandirs = (data.expand(flags['cleandirs'], d) or "").split()
  82. for cdir in cleandirs:
  83. os.system("rm -rf %s" % cdir)
  84. if dirs:
  85. dirs = data.expand(dirs, d)
  86. else:
  87. dirs = (data.expand(flags['dirs'], d) or "").split()
  88. for adir in dirs:
  89. mkdirhier(adir)
  90. if len(dirs) > 0:
  91. adir = dirs[-1]
  92. else:
  93. adir = data.getVar('B', d, 1)
  94. # Save current directory
  95. try:
  96. prevdir = os.getcwd()
  97. except OSError:
  98. prevdir = data.getVar('TOPDIR', d, True)
  99. # Setup logfiles
  100. t = data.getVar('T', d, 1)
  101. if not t:
  102. bb.msg.fatal(bb.msg.domain.Build, "T not set")
  103. mkdirhier(t)
  104. # Gross hack, FIXME
  105. import random
  106. logfile = "%s/log.%s.%s.%s" % (t, func, str(os.getpid()),random.random())
  107. runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
  108. # Change to correct directory (if specified)
  109. if adir and os.access(adir, os.F_OK):
  110. os.chdir(adir)
  111. # Handle logfiles
  112. si = file('/dev/null', 'r')
  113. try:
  114. if bb.msg.debug_level['default'] > 0 or ispython:
  115. so = os.popen("tee \"%s\"" % logfile, "w")
  116. else:
  117. so = file(logfile, 'w')
  118. except OSError, e:
  119. bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
  120. pass
  121. se = so
  122. # Dup the existing fds so we dont lose them
  123. osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
  124. oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
  125. ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
  126. # Replace those fds with our own
  127. os.dup2(si.fileno(), osi[1])
  128. os.dup2(so.fileno(), oso[1])
  129. os.dup2(se.fileno(), ose[1])
  130. locks = []
  131. lockfiles = (data.expand(flags['lockfiles'], d) or "").split()
  132. for lock in lockfiles:
  133. locks.append(bb.utils.lockfile(lock))
  134. try:
  135. # Run the function
  136. if ispython:
  137. exec_func_python(func, d, runfile, logfile)
  138. else:
  139. exec_func_shell(func, d, runfile, logfile, flags)
  140. # Restore original directory
  141. try:
  142. os.chdir(prevdir)
  143. except:
  144. pass
  145. finally:
  146. # Unlock any lockfiles
  147. for lock in locks:
  148. bb.utils.unlockfile(lock)
  149. # Restore the backup fds
  150. os.dup2(osi[0], osi[1])
  151. os.dup2(oso[0], oso[1])
  152. os.dup2(ose[0], ose[1])
  153. # Close our logs
  154. si.close()
  155. so.close()
  156. se.close()
  157. if os.path.exists(logfile) and os.path.getsize(logfile) == 0:
  158. bb.msg.debug(2, bb.msg.domain.Build, "Zero size logfile %s, removing" % logfile)
  159. os.remove(logfile)
  160. # Close the backup fds
  161. os.close(osi[0])
  162. os.close(oso[0])
  163. os.close(ose[0])
  164. def exec_func_python(func, d, runfile, logfile):
  165. """Execute a python BB 'function'"""
  166. import re, os
  167. bbfile = bb.data.getVar('FILE', d, 1)
  168. tmp = "def " + func + "():\n%s" % data.getVar(func, d)
  169. tmp += '\n' + func + '()'
  170. f = open(runfile, "w")
  171. f.write(tmp)
  172. comp = utils.better_compile(tmp, func, bbfile)
  173. g = {} # globals
  174. g['d'] = d
  175. try:
  176. utils.better_exec(comp, g, tmp, bbfile)
  177. except:
  178. (t,value,tb) = sys.exc_info()
  179. if t in [bb.parse.SkipPackage, bb.build.FuncFailed]:
  180. raise
  181. bb.msg.error(bb.msg.domain.Build, "Function %s failed" % func)
  182. raise FuncFailed("function %s failed" % func, logfile)
  183. def exec_func_shell(func, d, runfile, logfile, flags):
  184. """Execute a shell BB 'function' Returns true if execution was successful.
  185. For this, it creates a bash shell script in the tmp dectory, writes the local
  186. data into it and finally executes. The output of the shell will end in a log file and stdout.
  187. Note on directory behavior. The 'dirs' varflag should contain a list
  188. of the directories you need created prior to execution. The last
  189. item in the list is where we will chdir/cd to.
  190. """
  191. deps = flags['deps']
  192. check = flags['check']
  193. if check in globals():
  194. if globals()[check](func, deps):
  195. return
  196. f = open(runfile, "w")
  197. f.write("#!/bin/sh -e\n")
  198. if bb.msg.debug_level['default'] > 0: f.write("set -x\n")
  199. data.emit_env(f, d)
  200. f.write("cd %s\n" % os.getcwd())
  201. if func: f.write("%s\n" % func)
  202. f.close()
  203. os.chmod(runfile, 0775)
  204. if not func:
  205. bb.msg.error(bb.msg.domain.Build, "Function not specified")
  206. raise FuncFailed("Function not specified for exec_func_shell")
  207. # execute function
  208. if flags['fakeroot']:
  209. maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
  210. else:
  211. maybe_fakeroot = ''
  212. lang_environment = "LC_ALL=C "
  213. ret = os.system('%s%ssh -e %s' % (lang_environment, maybe_fakeroot, runfile))
  214. if ret == 0:
  215. return
  216. bb.msg.error(bb.msg.domain.Build, "Function %s failed" % func)
  217. raise FuncFailed("function %s failed" % func, logfile)
  218. def exec_task(task, d):
  219. """Execute an BB 'task'
  220. The primary difference between executing a task versus executing
  221. a function is that a task exists in the task digraph, and therefore
  222. has dependencies amongst other tasks."""
  223. # Check whther this is a valid task
  224. if not data.getVarFlag(task, 'task', d):
  225. raise EventException("No such task", InvalidTask(task, d))
  226. try:
  227. bb.msg.debug(1, bb.msg.domain.Build, "Executing task %s" % task)
  228. old_overrides = data.getVar('OVERRIDES', d, 0)
  229. localdata = data.createCopy(d)
  230. data.setVar('OVERRIDES', 'task-%s:%s' % (task[3:], old_overrides), localdata)
  231. data.update_data(localdata)
  232. data.expandKeys(localdata)
  233. event.fire(TaskStarted(task, localdata))
  234. exec_func(task, localdata)
  235. event.fire(TaskSucceeded(task, localdata))
  236. except FuncFailed, message:
  237. # Try to extract the optional logfile
  238. try:
  239. (msg, logfile) = message
  240. except:
  241. logfile = None
  242. msg = message
  243. bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % message )
  244. failedevent = TaskFailed(msg, logfile, task, d)
  245. event.fire(failedevent)
  246. raise EventException("Function failed in task: %s" % message, failedevent)
  247. # make stamp, or cause event and raise exception
  248. if not data.getVarFlag(task, 'nostamp', d) and not data.getVarFlag(task, 'selfstamp', d):
  249. make_stamp(task, d)
  250. def extract_stamp(d, fn):
  251. """
  252. Extracts stamp format which is either a data dictonary (fn unset)
  253. or a dataCache entry (fn set).
  254. """
  255. if fn:
  256. return d.stamp[fn]
  257. return data.getVar('STAMP', d, 1)
  258. def stamp_internal(task, d, file_name):
  259. """
  260. Internal stamp helper function
  261. Removes any stamp for the given task
  262. Makes sure the stamp directory exists
  263. Returns the stamp path+filename
  264. """
  265. stamp = extract_stamp(d, file_name)
  266. if not stamp:
  267. return
  268. stamp = "%s.%s" % (stamp, task)
  269. mkdirhier(os.path.dirname(stamp))
  270. # Remove the file and recreate to force timestamp
  271. # change on broken NFS filesystems
  272. if os.access(stamp, os.F_OK):
  273. os.remove(stamp)
  274. return stamp
  275. def make_stamp(task, d, file_name = None):
  276. """
  277. Creates/updates a stamp for a given task
  278. (d can be a data dict or dataCache)
  279. """
  280. stamp = stamp_internal(task, d, file_name)
  281. if stamp:
  282. f = open(stamp, "w")
  283. f.close()
  284. def del_stamp(task, d, file_name = None):
  285. """
  286. Removes a stamp for a given task
  287. (d can be a data dict or dataCache)
  288. """
  289. stamp_internal(task, d, file_name)
  290. def add_tasks(tasklist, d):
  291. task_deps = data.getVar('_task_deps', d)
  292. if not task_deps:
  293. task_deps = {}
  294. if not 'tasks' in task_deps:
  295. task_deps['tasks'] = []
  296. if not 'parents' in task_deps:
  297. task_deps['parents'] = {}
  298. for task in tasklist:
  299. task = data.expand(task, d)
  300. data.setVarFlag(task, 'task', 1, d)
  301. if not task in task_deps['tasks']:
  302. task_deps['tasks'].append(task)
  303. flags = data.getVarFlags(task, d)
  304. def getTask(name):
  305. if not name in task_deps:
  306. task_deps[name] = {}
  307. if name in flags:
  308. deptask = data.expand(flags[name], d)
  309. task_deps[name][task] = deptask
  310. getTask('depends')
  311. getTask('deptask')
  312. getTask('rdeptask')
  313. getTask('recrdeptask')
  314. getTask('nostamp')
  315. task_deps['parents'][task] = []
  316. for dep in flags['deps']:
  317. dep = data.expand(dep, d)
  318. task_deps['parents'][task].append(dep)
  319. # don't assume holding a reference
  320. data.setVar('_task_deps', task_deps, d)
  321. def remove_task(task, kill, d):
  322. """Remove an BB 'task'.
  323. If kill is 1, also remove tasks that depend on this task."""
  324. data.delVarFlag(task, 'task', d)