blob: 5fab7043ccf7b0f950cbb59839c1939c0ccbce23 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#!/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"""
5BitBake 'TaskData' implementation
6
7Task data collection and handling
8
9"""
10
11# Copyright (C) 2006 Richard Purdie
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
26import logging
27import re
28import bb
29
30logger = logging.getLogger("BitBake.TaskData")
31
32def re_match_strings(target, strings):
33 """
34 Whether or not the string 'target' matches
35 any one string of the strings which can be regular expression string
36 """
37 return any(name == target or re.match(name, target)
38 for name in strings)
39
40class TaskData:
41 """
42 BitBake Task Data implementation
43 """
44 def __init__(self, abort = True, tryaltconfigs = False, skiplist = None, allowincomplete = False):
45 self.build_names_index = []
46 self.run_names_index = []
47 self.fn_index = []
48
49 self.build_targets = {}
50 self.run_targets = {}
51
52 self.external_targets = []
53
54 self.tasks_fnid = []
55 self.tasks_name = []
56 self.tasks_tdepends = []
57 self.tasks_idepends = []
58 self.tasks_irdepends = []
59 # Cache to speed up task ID lookups
60 self.tasks_lookup = {}
61
62 self.depids = {}
63 self.rdepids = {}
64
65 self.consider_msgs_cache = []
66
67 self.failed_deps = []
68 self.failed_rdeps = []
69 self.failed_fnids = []
70
71 self.abort = abort
72 self.tryaltconfigs = tryaltconfigs
73 self.allowincomplete = allowincomplete
74
75 self.skiplist = skiplist
76
77 def getbuild_id(self, name):
78 """
79 Return an ID number for the build target name.
80 If it doesn't exist, create one.
81 """
82 if not name in self.build_names_index:
83 self.build_names_index.append(name)
84 return len(self.build_names_index) - 1
85
86 return self.build_names_index.index(name)
87
88 def getrun_id(self, name):
89 """
90 Return an ID number for the run target name.
91 If it doesn't exist, create one.
92 """
93 if not name in self.run_names_index:
94 self.run_names_index.append(name)
95 return len(self.run_names_index) - 1
96
97 return self.run_names_index.index(name)
98
99 def getfn_id(self, name):
100 """
101 Return an ID number for the filename.
102 If it doesn't exist, create one.
103 """
104 if not name in self.fn_index:
105 self.fn_index.append(name)
106 return len(self.fn_index) - 1
107
108 return self.fn_index.index(name)
109
110 def gettask_ids(self, fnid):
111 """
112 Return an array of the ID numbers matching a given fnid.
113 """
114 ids = []
115 if fnid in self.tasks_lookup:
116 for task in self.tasks_lookup[fnid]:
117 ids.append(self.tasks_lookup[fnid][task])
118 return ids
119
120 def gettask_id_fromfnid(self, fnid, task):
121 """
122 Return an ID number for the task matching fnid and task.
123 """
124 if fnid in self.tasks_lookup:
125 if task in self.tasks_lookup[fnid]:
126 return self.tasks_lookup[fnid][task]
127
128 return None
129
130 def gettask_id(self, fn, task, create = True):
131 """
132 Return an ID number for the task matching fn and task.
133 If it doesn't exist, create one by default.
134 Optionally return None instead.
135 """
136 fnid = self.getfn_id(fn)
137
138 if fnid in self.tasks_lookup:
139 if task in self.tasks_lookup[fnid]:
140 return self.tasks_lookup[fnid][task]
141
142 if not create:
143 return None
144
145 self.tasks_name.append(task)
146 self.tasks_fnid.append(fnid)
147 self.tasks_tdepends.append([])
148 self.tasks_idepends.append([])
149 self.tasks_irdepends.append([])
150
151 listid = len(self.tasks_name) - 1
152
153 if fnid not in self.tasks_lookup:
154 self.tasks_lookup[fnid] = {}
155 self.tasks_lookup[fnid][task] = listid
156
157 return listid
158
159 def add_tasks(self, fn, dataCache):
160 """
161 Add tasks for a given fn to the database
162 """
163
164 task_deps = dataCache.task_deps[fn]
165
166 fnid = self.getfn_id(fn)
167
168 if fnid in self.failed_fnids:
169 bb.msg.fatal("TaskData", "Trying to re-add a failed file? Something is broken...")
170
171 # Check if we've already seen this fn
172 if fnid in self.tasks_fnid:
173 return
174
175 for task in task_deps['tasks']:
176
177 # Work out task dependencies
178 parentids = []
179 for dep in task_deps['parents'][task]:
180 if dep not in task_deps['tasks']:
181 bb.debug(2, "Not adding dependeny of %s on %s since %s does not exist" % (task, dep, dep))
182 continue
183 parentid = self.gettask_id(fn, dep)
184 parentids.append(parentid)
185 taskid = self.gettask_id(fn, task)
186 self.tasks_tdepends[taskid].extend(parentids)
187
188 # Touch all intertask dependencies
189 if 'depends' in task_deps and task in task_deps['depends']:
190 ids = []
191 for dep in task_deps['depends'][task].split():
192 if dep:
193 if ":" not in dep:
194 bb.msg.fatal("TaskData", "Error for %s, dependency %s does not contain ':' character\n. Task 'depends' should be specified in the form 'packagename:task'" % (fn, dep))
195 ids.append(((self.getbuild_id(dep.split(":")[0])), dep.split(":")[1]))
196 self.tasks_idepends[taskid].extend(ids)
197 if 'rdepends' in task_deps and task in task_deps['rdepends']:
198 ids = []
199 for dep in task_deps['rdepends'][task].split():
200 if dep:
201 if ":" not in dep:
202 bb.msg.fatal("TaskData", "Error for %s, dependency %s does not contain ':' character\n. Task 'rdepends' should be specified in the form 'packagename:task'" % (fn, dep))
203 ids.append(((self.getrun_id(dep.split(":")[0])), dep.split(":")[1]))
204 self.tasks_irdepends[taskid].extend(ids)
205
206
207 # Work out build dependencies
208 if not fnid in self.depids:
209 dependids = {}
210 for depend in dataCache.deps[fn]:
211 dependids[self.getbuild_id(depend)] = None
212 self.depids[fnid] = dependids.keys()
213 logger.debug(2, "Added dependencies %s for %s", str(dataCache.deps[fn]), fn)
214
215 # Work out runtime dependencies
216 if not fnid in self.rdepids:
217 rdependids = {}
218 rdepends = dataCache.rundeps[fn]
219 rrecs = dataCache.runrecs[fn]
220 rdependlist = []
221 rreclist = []
222 for package in rdepends:
223 for rdepend in rdepends[package]:
224 rdependlist.append(rdepend)
225 rdependids[self.getrun_id(rdepend)] = None
226 for package in rrecs:
227 for rdepend in rrecs[package]:
228 rreclist.append(rdepend)
229 rdependids[self.getrun_id(rdepend)] = None
230 if rdependlist:
231 logger.debug(2, "Added runtime dependencies %s for %s", str(rdependlist), fn)
232 if rreclist:
233 logger.debug(2, "Added runtime recommendations %s for %s", str(rreclist), fn)
234 self.rdepids[fnid] = rdependids.keys()
235
236 for dep in self.depids[fnid]:
237 if dep in self.failed_deps:
238 self.fail_fnid(fnid)
239 return
240 for dep in self.rdepids[fnid]:
241 if dep in self.failed_rdeps:
242 self.fail_fnid(fnid)
243 return
244
245 def have_build_target(self, target):
246 """
247 Have we a build target matching this name?
248 """
249 targetid = self.getbuild_id(target)
250
251 if targetid in self.build_targets:
252 return True
253 return False
254
255 def have_runtime_target(self, target):
256 """
257 Have we a runtime target matching this name?
258 """
259 targetid = self.getrun_id(target)
260
261 if targetid in self.run_targets:
262 return True
263 return False
264
265 def add_build_target(self, fn, item):
266 """
267 Add a build target.
268 If already present, append the provider fn to the list
269 """
270 targetid = self.getbuild_id(item)
271 fnid = self.getfn_id(fn)
272
273 if targetid in self.build_targets:
274 if fnid in self.build_targets[targetid]:
275 return
276 self.build_targets[targetid].append(fnid)
277 return
278 self.build_targets[targetid] = [fnid]
279
280 def add_runtime_target(self, fn, item):
281 """
282 Add a runtime target.
283 If already present, append the provider fn to the list
284 """
285 targetid = self.getrun_id(item)
286 fnid = self.getfn_id(fn)
287
288 if targetid in self.run_targets:
289 if fnid in self.run_targets[targetid]:
290 return
291 self.run_targets[targetid].append(fnid)
292 return
293 self.run_targets[targetid] = [fnid]
294
295 def mark_external_target(self, item):
296 """
297 Mark a build target as being externally requested
298 """
299 targetid = self.getbuild_id(item)
300
301 if targetid not in self.external_targets:
302 self.external_targets.append(targetid)
303
304 def get_unresolved_build_targets(self, dataCache):
305 """
306 Return a list of build targets who's providers
307 are unknown.
308 """
309 unresolved = []
310 for target in self.build_names_index:
311 if re_match_strings(target, dataCache.ignored_dependencies):
312 continue
313 if self.build_names_index.index(target) in self.failed_deps:
314 continue
315 if not self.have_build_target(target):
316 unresolved.append(target)
317 return unresolved
318
319 def get_unresolved_run_targets(self, dataCache):
320 """
321 Return a list of runtime targets who's providers
322 are unknown.
323 """
324 unresolved = []
325 for target in self.run_names_index:
326 if re_match_strings(target, dataCache.ignored_dependencies):
327 continue
328 if self.run_names_index.index(target) in self.failed_rdeps:
329 continue
330 if not self.have_runtime_target(target):
331 unresolved.append(target)
332 return unresolved
333
334 def get_provider(self, item):
335 """
336 Return a list of providers of item
337 """
338 targetid = self.getbuild_id(item)
339
340 return self.build_targets[targetid]
341
342 def get_dependees(self, itemid):
343 """
344 Return a list of targets which depend on item
345 """
346 dependees = []
347 for fnid in self.depids:
348 if itemid in self.depids[fnid]:
349 dependees.append(fnid)
350 return dependees
351
352 def get_dependees_str(self, item):
353 """
354 Return a list of targets which depend on item as a user readable string
355 """
356 itemid = self.getbuild_id(item)
357 dependees = []
358 for fnid in self.depids:
359 if itemid in self.depids[fnid]:
360 dependees.append(self.fn_index[fnid])
361 return dependees
362
363 def get_rdependees(self, itemid):
364 """
365 Return a list of targets which depend on runtime item
366 """
367 dependees = []
368 for fnid in self.rdepids:
369 if itemid in self.rdepids[fnid]:
370 dependees.append(fnid)
371 return dependees
372
373 def get_rdependees_str(self, item):
374 """
375 Return a list of targets which depend on runtime item as a user readable string
376 """
377 itemid = self.getrun_id(item)
378 dependees = []
379 for fnid in self.rdepids:
380 if itemid in self.rdepids[fnid]:
381 dependees.append(self.fn_index[fnid])
382 return dependees
383
384 def get_reasons(self, item, runtime=False):
385 """
386 Get the reason(s) for an item not being provided, if any
387 """
388 reasons = []
389 if self.skiplist:
390 for fn in self.skiplist:
391 skipitem = self.skiplist[fn]
392 if skipitem.pn == item:
393 reasons.append("%s was skipped: %s" % (skipitem.pn, skipitem.skipreason))
394 elif runtime and item in skipitem.rprovides:
395 reasons.append("%s RPROVIDES %s but was skipped: %s" % (skipitem.pn, item, skipitem.skipreason))
396 elif not runtime and item in skipitem.provides:
397 reasons.append("%s PROVIDES %s but was skipped: %s" % (skipitem.pn, item, skipitem.skipreason))
398 return reasons
399
400 def get_close_matches(self, item, provider_list):
401 import difflib
402 if self.skiplist:
403 skipped = []
404 for fn in self.skiplist:
405 skipped.append(self.skiplist[fn].pn)
406 full_list = provider_list + skipped
407 else:
408 full_list = provider_list
409 return difflib.get_close_matches(item, full_list, cutoff=0.7)
410
411 def add_provider(self, cfgData, dataCache, item):
412 try:
413 self.add_provider_internal(cfgData, dataCache, item)
414 except bb.providers.NoProvider:
415 if self.abort:
416 raise
417 self.remove_buildtarget(self.getbuild_id(item))
418
419 self.mark_external_target(item)
420
421 def add_provider_internal(self, cfgData, dataCache, item):
422 """
423 Add the providers of item to the task data
424 Mark entries were specifically added externally as against dependencies
425 added internally during dependency resolution
426 """
427
428 if re_match_strings(item, dataCache.ignored_dependencies):
429 return
430
431 if not item in dataCache.providers:
432 bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees_str(item), reasons=self.get_reasons(item), close_matches=self.get_close_matches(item, dataCache.providers.keys())), cfgData)
433 raise bb.providers.NoProvider(item)
434
435 if self.have_build_target(item):
436 return
437
438 all_p = dataCache.providers[item]
439
440 eligible, foundUnique = bb.providers.filterProviders(all_p, item, cfgData, dataCache)
441 eligible = [p for p in eligible if not self.getfn_id(p) in self.failed_fnids]
442
443 if not eligible:
444 bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees_str(item), reasons=["No eligible PROVIDERs exist for '%s'" % item]), cfgData)
445 raise bb.providers.NoProvider(item)
446
447 if len(eligible) > 1 and foundUnique == False:
448 if item not in self.consider_msgs_cache:
449 providers_list = []
450 for fn in eligible:
451 providers_list.append(dataCache.pkg_fn[fn])
452 bb.event.fire(bb.event.MultipleProviders(item, providers_list), cfgData)
453 self.consider_msgs_cache.append(item)
454
455 for fn in eligible:
456 fnid = self.getfn_id(fn)
457 if fnid in self.failed_fnids:
458 continue
459 logger.debug(2, "adding %s to satisfy %s", fn, item)
460 self.add_build_target(fn, item)
461 self.add_tasks(fn, dataCache)
462
463
464 #item = dataCache.pkg_fn[fn]
465
466 def add_rprovider(self, cfgData, dataCache, item):
467 """
468 Add the runtime providers of item to the task data
469 (takes item names from RDEPENDS/PACKAGES namespace)
470 """
471
472 if re_match_strings(item, dataCache.ignored_dependencies):
473 return
474
475 if self.have_runtime_target(item):
476 return
477
478 all_p = bb.providers.getRuntimeProviders(dataCache, item)
479
480 if not all_p:
481 bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees_str(item), reasons=self.get_reasons(item, True)), cfgData)
482 raise bb.providers.NoRProvider(item)
483
484 eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache)
485 eligible = [p for p in eligible if not self.getfn_id(p) in self.failed_fnids]
486
487 if not eligible:
488 bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees_str(item), reasons=["No eligible RPROVIDERs exist for '%s'" % item]), cfgData)
489 raise bb.providers.NoRProvider(item)
490
491 if len(eligible) > 1 and numberPreferred == 0:
492 if item not in self.consider_msgs_cache:
493 providers_list = []
494 for fn in eligible:
495 providers_list.append(dataCache.pkg_fn[fn])
496 bb.event.fire(bb.event.MultipleProviders(item, providers_list, runtime=True), cfgData)
497 self.consider_msgs_cache.append(item)
498
499 if numberPreferred > 1:
500 if item not in self.consider_msgs_cache:
501 providers_list = []
502 for fn in eligible:
503 providers_list.append(dataCache.pkg_fn[fn])
504 bb.event.fire(bb.event.MultipleProviders(item, providers_list, runtime=True), cfgData)
505 self.consider_msgs_cache.append(item)
506 raise bb.providers.MultipleRProvider(item)
507
508 # run through the list until we find one that we can build
509 for fn in eligible:
510 fnid = self.getfn_id(fn)
511 if fnid in self.failed_fnids:
512 continue
513 logger.debug(2, "adding '%s' to satisfy runtime '%s'", fn, item)
514 self.add_runtime_target(fn, item)
515 self.add_tasks(fn, dataCache)
516
517 def fail_fnid(self, fnid, missing_list=None):
518 """
519 Mark a file as failed (unbuildable)
520 Remove any references from build and runtime provider lists
521
522 missing_list, A list of missing requirements for this target
523 """
524 if fnid in self.failed_fnids:
525 return
526 if not missing_list:
527 missing_list = []
528 logger.debug(1, "File '%s' is unbuildable, removing...", self.fn_index[fnid])
529 self.failed_fnids.append(fnid)
530 for target in self.build_targets:
531 if fnid in self.build_targets[target]:
532 self.build_targets[target].remove(fnid)
533 if len(self.build_targets[target]) == 0:
534 self.remove_buildtarget(target, missing_list)
535 for target in self.run_targets:
536 if fnid in self.run_targets[target]:
537 self.run_targets[target].remove(fnid)
538 if len(self.run_targets[target]) == 0:
539 self.remove_runtarget(target, missing_list)
540
541 def remove_buildtarget(self, targetid, missing_list=None):
542 """
543 Mark a build target as failed (unbuildable)
544 Trigger removal of any files that have this as a dependency
545 """
546 if not missing_list:
547 missing_list = [self.build_names_index[targetid]]
548 else:
549 missing_list = [self.build_names_index[targetid]] + missing_list
550 logger.verbose("Target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", self.build_names_index[targetid], missing_list)
551 self.failed_deps.append(targetid)
552 dependees = self.get_dependees(targetid)
553 for fnid in dependees:
554 self.fail_fnid(fnid, missing_list)
555 for taskid in xrange(len(self.tasks_idepends)):
556 idepends = self.tasks_idepends[taskid]
557 for (idependid, idependtask) in idepends:
558 if idependid == targetid:
559 self.fail_fnid(self.tasks_fnid[taskid], missing_list)
560
561 if self.abort and targetid in self.external_targets:
562 target = self.build_names_index[targetid]
563 logger.error("Required build target '%s' has no buildable providers.\nMissing or unbuildable dependency chain was: %s", target, missing_list)
564 raise bb.providers.NoProvider(target)
565
566 def remove_runtarget(self, targetid, missing_list=None):
567 """
568 Mark a run target as failed (unbuildable)
569 Trigger removal of any files that have this as a dependency
570 """
571 if not missing_list:
572 missing_list = [self.run_names_index[targetid]]
573 else:
574 missing_list = [self.run_names_index[targetid]] + missing_list
575
576 logger.info("Runtime target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", self.run_names_index[targetid], missing_list)
577 self.failed_rdeps.append(targetid)
578 dependees = self.get_rdependees(targetid)
579 for fnid in dependees:
580 self.fail_fnid(fnid, missing_list)
581 for taskid in xrange(len(self.tasks_irdepends)):
582 irdepends = self.tasks_irdepends[taskid]
583 for (idependid, idependtask) in irdepends:
584 if idependid == targetid:
585 self.fail_fnid(self.tasks_fnid[taskid], missing_list)
586
587 def add_unresolved(self, cfgData, dataCache):
588 """
589 Resolve all unresolved build and runtime targets
590 """
591 logger.info("Resolving any missing task queue dependencies")
592 while True:
593 added = 0
594 for target in self.get_unresolved_build_targets(dataCache):
595 try:
596 self.add_provider_internal(cfgData, dataCache, target)
597 added = added + 1
598 except bb.providers.NoProvider:
599 targetid = self.getbuild_id(target)
600 if self.abort and targetid in self.external_targets and not self.allowincomplete:
601 raise
602 if not self.allowincomplete:
603 self.remove_buildtarget(targetid)
604 for target in self.get_unresolved_run_targets(dataCache):
605 try:
606 self.add_rprovider(cfgData, dataCache, target)
607 added = added + 1
608 except (bb.providers.NoRProvider, bb.providers.MultipleRProvider):
609 self.remove_runtarget(self.getrun_id(target))
610 logger.debug(1, "Resolved " + str(added) + " extra dependencies")
611 if added == 0:
612 break
613 # self.dump_data()
614
615 def dump_data(self):
616 """
617 Dump some debug information on the internal data structures
618 """
619 logger.debug(3, "build_names:")
620 logger.debug(3, ", ".join(self.build_names_index))
621
622 logger.debug(3, "run_names:")
623 logger.debug(3, ", ".join(self.run_names_index))
624
625 logger.debug(3, "build_targets:")
626 for buildid in xrange(len(self.build_names_index)):
627 target = self.build_names_index[buildid]
628 targets = "None"
629 if buildid in self.build_targets:
630 targets = self.build_targets[buildid]
631 logger.debug(3, " (%s)%s: %s", buildid, target, targets)
632
633 logger.debug(3, "run_targets:")
634 for runid in xrange(len(self.run_names_index)):
635 target = self.run_names_index[runid]
636 targets = "None"
637 if runid in self.run_targets:
638 targets = self.run_targets[runid]
639 logger.debug(3, " (%s)%s: %s", runid, target, targets)
640
641 logger.debug(3, "tasks:")
642 for task in xrange(len(self.tasks_name)):
643 logger.debug(3, " (%s)%s - %s: %s",
644 task,
645 self.fn_index[self.tasks_fnid[task]],
646 self.tasks_name[task],
647 self.tasks_tdepends[task])
648
649 logger.debug(3, "dependency ids (per fn):")
650 for fnid in self.depids:
651 logger.debug(3, " %s %s: %s", fnid, self.fn_index[fnid], self.depids[fnid])
652
653 logger.debug(3, "runtime dependency ids (per fn):")
654 for fnid in self.rdepids:
655 logger.debug(3, " %s %s: %s", fnid, self.fn_index[fnid], self.rdepids[fnid])