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