taskdata.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  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 'TaskData' implementation
  6. Task data collection and handling
  7. """
  8. # Copyright (C) 2006 Richard Purdie
  9. #
  10. # This program is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License version 2 as
  12. # published by the Free Software Foundation.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License along
  20. # with this program; if not, write to the Free Software Foundation, Inc.,
  21. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  22. import logging
  23. import re
  24. import bb
  25. logger = logging.getLogger("BitBake.TaskData")
  26. def re_match_strings(target, strings):
  27. """
  28. Whether or not the string 'target' matches
  29. any one string of the strings which can be regular expression string
  30. """
  31. return any(name == target or re.match(name, target)
  32. for name in strings)
  33. class TaskData:
  34. """
  35. BitBake Task Data implementation
  36. """
  37. def __init__(self, abort = True, tryaltconfigs = False, skiplist = None):
  38. self.build_names_index = []
  39. self.run_names_index = []
  40. self.fn_index = []
  41. self.build_targets = {}
  42. self.run_targets = {}
  43. self.external_targets = []
  44. self.tasks_fnid = []
  45. self.tasks_name = []
  46. self.tasks_tdepends = []
  47. self.tasks_idepends = []
  48. # Cache to speed up task ID lookups
  49. self.tasks_lookup = {}
  50. self.depids = {}
  51. self.rdepids = {}
  52. self.consider_msgs_cache = []
  53. self.failed_deps = []
  54. self.failed_rdeps = []
  55. self.failed_fnids = []
  56. self.abort = abort
  57. self.tryaltconfigs = tryaltconfigs
  58. self.skiplist = skiplist
  59. def getbuild_id(self, name):
  60. """
  61. Return an ID number for the build target name.
  62. If it doesn't exist, create one.
  63. """
  64. if not name in self.build_names_index:
  65. self.build_names_index.append(name)
  66. return len(self.build_names_index) - 1
  67. return self.build_names_index.index(name)
  68. def getrun_id(self, name):
  69. """
  70. Return an ID number for the run target name.
  71. If it doesn't exist, create one.
  72. """
  73. if not name in self.run_names_index:
  74. self.run_names_index.append(name)
  75. return len(self.run_names_index) - 1
  76. return self.run_names_index.index(name)
  77. def getfn_id(self, name):
  78. """
  79. Return an ID number for the filename.
  80. If it doesn't exist, create one.
  81. """
  82. if not name in self.fn_index:
  83. self.fn_index.append(name)
  84. return len(self.fn_index) - 1
  85. return self.fn_index.index(name)
  86. def gettask_ids(self, fnid):
  87. """
  88. Return an array of the ID numbers matching a given fnid.
  89. """
  90. ids = []
  91. if fnid in self.tasks_lookup:
  92. for task in self.tasks_lookup[fnid]:
  93. ids.append(self.tasks_lookup[fnid][task])
  94. return ids
  95. def gettask_id(self, fn, task, create = True):
  96. """
  97. Return an ID number for the task matching fn and task.
  98. If it doesn't exist, create one by default.
  99. Optionally return None instead.
  100. """
  101. fnid = self.getfn_id(fn)
  102. if fnid in self.tasks_lookup:
  103. if task in self.tasks_lookup[fnid]:
  104. return self.tasks_lookup[fnid][task]
  105. if not create:
  106. return None
  107. self.tasks_name.append(task)
  108. self.tasks_fnid.append(fnid)
  109. self.tasks_tdepends.append([])
  110. self.tasks_idepends.append([])
  111. listid = len(self.tasks_name) - 1
  112. if fnid not in self.tasks_lookup:
  113. self.tasks_lookup[fnid] = {}
  114. self.tasks_lookup[fnid][task] = listid
  115. return listid
  116. def add_tasks(self, fn, dataCache):
  117. """
  118. Add tasks for a given fn to the database
  119. """
  120. task_deps = dataCache.task_deps[fn]
  121. fnid = self.getfn_id(fn)
  122. if fnid in self.failed_fnids:
  123. bb.msg.fatal(bb.msg.domain.TaskData, "Trying to re-add a failed file? Something is broken...")
  124. # Check if we've already seen this fn
  125. if fnid in self.tasks_fnid:
  126. return
  127. for task in task_deps['tasks']:
  128. # Work out task dependencies
  129. parentids = []
  130. for dep in task_deps['parents'][task]:
  131. parentid = self.gettask_id(fn, dep)
  132. parentids.append(parentid)
  133. taskid = self.gettask_id(fn, task)
  134. self.tasks_tdepends[taskid].extend(parentids)
  135. # Touch all intertask dependencies
  136. if 'depends' in task_deps and task in task_deps['depends']:
  137. ids = []
  138. for dep in task_deps['depends'][task].split():
  139. if dep:
  140. if ":" not in dep:
  141. bb.msg.fatal(bb.msg.domain.TaskData, "Error, dependency %s does not contain ':' character\n. Task 'depends' should be specified in the form 'packagename:task'" % (dep, fn))
  142. ids.append(((self.getbuild_id(dep.split(":")[0])), dep.split(":")[1]))
  143. self.tasks_idepends[taskid].extend(ids)
  144. # Work out build dependencies
  145. if not fnid in self.depids:
  146. dependids = {}
  147. for depend in dataCache.deps[fn]:
  148. logger.debug(2, "Added dependency %s for %s", depend, fn)
  149. dependids[self.getbuild_id(depend)] = None
  150. self.depids[fnid] = dependids.keys()
  151. # Work out runtime dependencies
  152. if not fnid in self.rdepids:
  153. rdependids = {}
  154. rdepends = dataCache.rundeps[fn]
  155. rrecs = dataCache.runrecs[fn]
  156. for package in rdepends:
  157. for rdepend in rdepends[package]:
  158. logger.debug(2, "Added runtime dependency %s for %s", rdepend, fn)
  159. rdependids[self.getrun_id(rdepend)] = None
  160. for package in rrecs:
  161. for rdepend in rrecs[package]:
  162. logger.debug(2, "Added runtime recommendation %s for %s", rdepend, fn)
  163. rdependids[self.getrun_id(rdepend)] = None
  164. self.rdepids[fnid] = rdependids.keys()
  165. for dep in self.depids[fnid]:
  166. if dep in self.failed_deps:
  167. self.fail_fnid(fnid)
  168. return
  169. for dep in self.rdepids[fnid]:
  170. if dep in self.failed_rdeps:
  171. self.fail_fnid(fnid)
  172. return
  173. def have_build_target(self, target):
  174. """
  175. Have we a build target matching this name?
  176. """
  177. targetid = self.getbuild_id(target)
  178. if targetid in self.build_targets:
  179. return True
  180. return False
  181. def have_runtime_target(self, target):
  182. """
  183. Have we a runtime target matching this name?
  184. """
  185. targetid = self.getrun_id(target)
  186. if targetid in self.run_targets:
  187. return True
  188. return False
  189. def add_build_target(self, fn, item):
  190. """
  191. Add a build target.
  192. If already present, append the provider fn to the list
  193. """
  194. targetid = self.getbuild_id(item)
  195. fnid = self.getfn_id(fn)
  196. if targetid in self.build_targets:
  197. if fnid in self.build_targets[targetid]:
  198. return
  199. self.build_targets[targetid].append(fnid)
  200. return
  201. self.build_targets[targetid] = [fnid]
  202. def add_runtime_target(self, fn, item):
  203. """
  204. Add a runtime target.
  205. If already present, append the provider fn to the list
  206. """
  207. targetid = self.getrun_id(item)
  208. fnid = self.getfn_id(fn)
  209. if targetid in self.run_targets:
  210. if fnid in self.run_targets[targetid]:
  211. return
  212. self.run_targets[targetid].append(fnid)
  213. return
  214. self.run_targets[targetid] = [fnid]
  215. def mark_external_target(self, item):
  216. """
  217. Mark a build target as being externally requested
  218. """
  219. targetid = self.getbuild_id(item)
  220. if targetid not in self.external_targets:
  221. self.external_targets.append(targetid)
  222. def get_unresolved_build_targets(self, dataCache):
  223. """
  224. Return a list of build targets who's providers
  225. are unknown.
  226. """
  227. unresolved = []
  228. for target in self.build_names_index:
  229. if re_match_strings(target, dataCache.ignored_dependencies):
  230. continue
  231. if self.build_names_index.index(target) in self.failed_deps:
  232. continue
  233. if not self.have_build_target(target):
  234. unresolved.append(target)
  235. return unresolved
  236. def get_unresolved_run_targets(self, dataCache):
  237. """
  238. Return a list of runtime targets who's providers
  239. are unknown.
  240. """
  241. unresolved = []
  242. for target in self.run_names_index:
  243. if re_match_strings(target, dataCache.ignored_dependencies):
  244. continue
  245. if self.run_names_index.index(target) in self.failed_rdeps:
  246. continue
  247. if not self.have_runtime_target(target):
  248. unresolved.append(target)
  249. return unresolved
  250. def get_provider(self, item):
  251. """
  252. Return a list of providers of item
  253. """
  254. targetid = self.getbuild_id(item)
  255. return self.build_targets[targetid]
  256. def get_dependees(self, itemid):
  257. """
  258. Return a list of targets which depend on item
  259. """
  260. dependees = []
  261. for fnid in self.depids:
  262. if itemid in self.depids[fnid]:
  263. dependees.append(fnid)
  264. return dependees
  265. def get_dependees_str(self, item):
  266. """
  267. Return a list of targets which depend on item as a user readable string
  268. """
  269. itemid = self.getbuild_id(item)
  270. dependees = []
  271. for fnid in self.depids:
  272. if itemid in self.depids[fnid]:
  273. dependees.append(self.fn_index[fnid])
  274. return dependees
  275. def get_rdependees(self, itemid):
  276. """
  277. Return a list of targets which depend on runtime item
  278. """
  279. dependees = []
  280. for fnid in self.rdepids:
  281. if itemid in self.rdepids[fnid]:
  282. dependees.append(fnid)
  283. return dependees
  284. def get_rdependees_str(self, item):
  285. """
  286. Return a list of targets which depend on runtime item as a user readable string
  287. """
  288. itemid = self.getrun_id(item)
  289. dependees = []
  290. for fnid in self.rdepids:
  291. if itemid in self.rdepids[fnid]:
  292. dependees.append(self.fn_index[fnid])
  293. return dependees
  294. def get_reasons(self, item, runtime=False):
  295. """
  296. Get the reason(s) for an item not being provided, if any
  297. """
  298. reasons = []
  299. if self.skiplist:
  300. for fn in self.skiplist:
  301. skipitem = self.skiplist[fn]
  302. if skipitem.pn == item:
  303. reasons.append("%s was skipped: %s" % (skipitem.pn, skipitem.skipreason))
  304. elif runtime and item in skipitem.rprovides:
  305. reasons.append("%s RPROVIDES %s but was skipped: %s" % (skipitem.pn, item, skipitem.skipreason))
  306. elif not runtime and item in skipitem.provides:
  307. reasons.append("%s PROVIDES %s but was skipped: %s" % (skipitem.pn, item, skipitem.skipreason))
  308. return reasons
  309. def add_provider(self, cfgData, dataCache, item):
  310. try:
  311. self.add_provider_internal(cfgData, dataCache, item)
  312. except bb.providers.NoProvider:
  313. if self.abort:
  314. raise
  315. self.remove_buildtarget(self.getbuild_id(item))
  316. self.mark_external_target(item)
  317. def add_provider_internal(self, cfgData, dataCache, item):
  318. """
  319. Add the providers of item to the task data
  320. Mark entries were specifically added externally as against dependencies
  321. added internally during dependency resolution
  322. """
  323. if re_match_strings(item, dataCache.ignored_dependencies):
  324. return
  325. if not item in dataCache.providers:
  326. bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees_str(item), reasons=self.get_reasons(item)), cfgData)
  327. raise bb.providers.NoProvider(item)
  328. if self.have_build_target(item):
  329. return
  330. all_p = dataCache.providers[item]
  331. eligible, foundUnique = bb.providers.filterProviders(all_p, item, cfgData, dataCache)
  332. eligible = [p for p in eligible if not self.getfn_id(p) in self.failed_fnids]
  333. if not eligible:
  334. bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees_str(item), reasons=["No eligible PROVIDERs exist for '%s'" % item]), cfgData)
  335. raise bb.providers.NoProvider(item)
  336. if len(eligible) > 1 and foundUnique == False:
  337. if item not in self.consider_msgs_cache:
  338. providers_list = []
  339. for fn in eligible:
  340. providers_list.append(dataCache.pkg_fn[fn])
  341. bb.event.fire(bb.event.MultipleProviders(item, providers_list), cfgData)
  342. self.consider_msgs_cache.append(item)
  343. for fn in eligible:
  344. fnid = self.getfn_id(fn)
  345. if fnid in self.failed_fnids:
  346. continue
  347. logger.debug(2, "adding %s to satisfy %s", fn, item)
  348. self.add_build_target(fn, item)
  349. self.add_tasks(fn, dataCache)
  350. #item = dataCache.pkg_fn[fn]
  351. def add_rprovider(self, cfgData, dataCache, item):
  352. """
  353. Add the runtime providers of item to the task data
  354. (takes item names from RDEPENDS/PACKAGES namespace)
  355. """
  356. if re_match_strings(item, dataCache.ignored_dependencies):
  357. return
  358. if self.have_runtime_target(item):
  359. return
  360. all_p = bb.providers.getRuntimeProviders(dataCache, item)
  361. if not all_p:
  362. bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees_str(item), reasons=self.get_reasons(item, True)), cfgData)
  363. raise bb.providers.NoRProvider(item)
  364. eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache)
  365. eligible = [p for p in eligible if not self.getfn_id(p) in self.failed_fnids]
  366. if not eligible:
  367. bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees_str(item), reasons=["No eligible RPROVIDERs exist for '%s'" % item]), cfgData)
  368. raise bb.providers.NoRProvider(item)
  369. if len(eligible) > 1 and numberPreferred == 0:
  370. if item not in self.consider_msgs_cache:
  371. providers_list = []
  372. for fn in eligible:
  373. providers_list.append(dataCache.pkg_fn[fn])
  374. bb.event.fire(bb.event.MultipleProviders(item, providers_list, runtime=True), cfgData)
  375. self.consider_msgs_cache.append(item)
  376. if numberPreferred > 1:
  377. if item not in self.consider_msgs_cache:
  378. providers_list = []
  379. for fn in eligible:
  380. providers_list.append(dataCache.pkg_fn[fn])
  381. bb.event.fire(bb.event.MultipleProviders(item, providers_list, runtime=True), cfgData)
  382. self.consider_msgs_cache.append(item)
  383. # run through the list until we find one that we can build
  384. for fn in eligible:
  385. fnid = self.getfn_id(fn)
  386. if fnid in self.failed_fnids:
  387. continue
  388. logger.debug(2, "adding '%s' to satisfy runtime '%s'", fn, item)
  389. self.add_runtime_target(fn, item)
  390. self.add_tasks(fn, dataCache)
  391. def fail_fnid(self, fnid, missing_list = []):
  392. """
  393. Mark a file as failed (unbuildable)
  394. Remove any references from build and runtime provider lists
  395. missing_list, A list of missing requirements for this target
  396. """
  397. if fnid in self.failed_fnids:
  398. return
  399. logger.debug(1, "File '%s' is unbuildable, removing...", self.fn_index[fnid])
  400. self.failed_fnids.append(fnid)
  401. for target in self.build_targets:
  402. if fnid in self.build_targets[target]:
  403. self.build_targets[target].remove(fnid)
  404. if len(self.build_targets[target]) == 0:
  405. self.remove_buildtarget(target, missing_list)
  406. for target in self.run_targets:
  407. if fnid in self.run_targets[target]:
  408. self.run_targets[target].remove(fnid)
  409. if len(self.run_targets[target]) == 0:
  410. self.remove_runtarget(target, missing_list)
  411. def remove_buildtarget(self, targetid, missing_list = []):
  412. """
  413. Mark a build target as failed (unbuildable)
  414. Trigger removal of any files that have this as a dependency
  415. """
  416. if not missing_list:
  417. missing_list = [self.build_names_index[targetid]]
  418. else:
  419. missing_list = [self.build_names_index[targetid]] + missing_list
  420. logger.verbose("Target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", self.build_names_index[targetid], missing_list)
  421. self.failed_deps.append(targetid)
  422. dependees = self.get_dependees(targetid)
  423. for fnid in dependees:
  424. self.fail_fnid(fnid, missing_list)
  425. for taskid in xrange(len(self.tasks_idepends)):
  426. idepends = self.tasks_idepends[taskid]
  427. for (idependid, idependtask) in idepends:
  428. if idependid == targetid:
  429. self.fail_fnid(self.tasks_fnid[taskid], missing_list)
  430. if self.abort and targetid in self.external_targets:
  431. target = self.build_names_index[targetid]
  432. logger.error("Required build target '%s' has no buildable providers.\nMissing or unbuildable dependency chain was: %s", target, missing_list)
  433. raise bb.providers.NoProvider(target)
  434. def remove_runtarget(self, targetid, missing_list = []):
  435. """
  436. Mark a run target as failed (unbuildable)
  437. Trigger removal of any files that have this as a dependency
  438. """
  439. if not missing_list:
  440. missing_list = [self.run_names_index[targetid]]
  441. else:
  442. missing_list = [self.run_names_index[targetid]] + missing_list
  443. logger.info("Runtime target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", self.run_names_index[targetid], missing_list)
  444. self.failed_rdeps.append(targetid)
  445. dependees = self.get_rdependees(targetid)
  446. for fnid in dependees:
  447. self.fail_fnid(fnid, missing_list)
  448. def add_unresolved(self, cfgData, dataCache):
  449. """
  450. Resolve all unresolved build and runtime targets
  451. """
  452. logger.info("Resolving any missing task queue dependencies")
  453. while True:
  454. added = 0
  455. for target in self.get_unresolved_build_targets(dataCache):
  456. try:
  457. self.add_provider_internal(cfgData, dataCache, target)
  458. added = added + 1
  459. except bb.providers.NoProvider:
  460. targetid = self.getbuild_id(target)
  461. if self.abort and targetid in self.external_targets:
  462. raise
  463. self.remove_buildtarget(targetid)
  464. for target in self.get_unresolved_run_targets(dataCache):
  465. try:
  466. self.add_rprovider(cfgData, dataCache, target)
  467. added = added + 1
  468. except bb.providers.NoRProvider:
  469. self.remove_runtarget(self.getrun_id(target))
  470. logger.debug(1, "Resolved " + str(added) + " extra dependencies")
  471. if added == 0:
  472. break
  473. # self.dump_data()
  474. def dump_data(self):
  475. """
  476. Dump some debug information on the internal data structures
  477. """
  478. logger.debug(3, "build_names:")
  479. logger.debug(3, ", ".join(self.build_names_index))
  480. logger.debug(3, "run_names:")
  481. logger.debug(3, ", ".join(self.run_names_index))
  482. logger.debug(3, "build_targets:")
  483. for buildid in xrange(len(self.build_names_index)):
  484. target = self.build_names_index[buildid]
  485. targets = "None"
  486. if buildid in self.build_targets:
  487. targets = self.build_targets[buildid]
  488. logger.debug(3, " (%s)%s: %s", buildid, target, targets)
  489. logger.debug(3, "run_targets:")
  490. for runid in xrange(len(self.run_names_index)):
  491. target = self.run_names_index[runid]
  492. targets = "None"
  493. if runid in self.run_targets:
  494. targets = self.run_targets[runid]
  495. logger.debug(3, " (%s)%s: %s", runid, target, targets)
  496. logger.debug(3, "tasks:")
  497. for task in xrange(len(self.tasks_name)):
  498. logger.debug(3, " (%s)%s - %s: %s",
  499. task,
  500. self.fn_index[self.tasks_fnid[task]],
  501. self.tasks_name[task],
  502. self.tasks_tdepends[task])
  503. logger.debug(3, "dependency ids (per fn):")
  504. for fnid in self.depids:
  505. logger.debug(3, " %s %s: %s", fnid, self.fn_index[fnid], self.depids[fnid])
  506. logger.debug(3, "runtime dependency ids (per fn):")
  507. for fnid in self.rdepids:
  508. logger.debug(3, " %s %s: %s", fnid, self.fn_index[fnid], self.rdepids[fnid])