taskdata/runqueue.py: Make taskdata and runqueue errors more user friendly
[vuplus_bitbake] / 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_graph = dataCache.task_queues[fn]
128         task_deps = dataCache.task_deps[fn]
129
130         fnid = self.getfn_id(fn)
131
132         if fnid in self.failed_fnids:
133             bb.msg.fatal(bb.msg.domain.TaskData, "Trying to re-add a failed file? Something is broken...")
134
135         # Check if we've already seen this fn
136         if fnid in self.tasks_fnid:
137             return
138
139         for task in task_graph.allnodes():
140
141             # Work out task dependencies
142             parentids = []
143             for dep in task_graph.getparents(task):
144                 parentid = self.gettask_id(fn, dep)
145                 parentids.append(parentid)
146             taskid = self.gettask_id(fn, task)
147             self.tasks_tdepends[taskid].extend(parentids)
148
149             # Touch all intertask dependencies
150             if 'depends' in task_deps and task in task_deps['depends']:
151                 ids = []
152                 for dep in task_deps['depends'][task].split():
153                     if dep:
154                         ids.append(str(self.getbuild_id(dep.split(":")[0])) + ":" + dep.split(":")[1])
155                 self.tasks_idepends[taskid].extend(ids)
156
157         # Work out build dependencies
158         if not fnid in self.depids:
159             dependids = {}
160             for depend in dataCache.deps[fn]:
161                 bb.msg.debug(2, bb.msg.domain.TaskData, "Added dependency %s for %s" % (depend, fn))
162                 dependids[self.getbuild_id(depend)] = None
163             self.depids[fnid] = dependids.keys()
164
165         # Work out runtime dependencies
166         if not fnid in self.rdepids:
167             rdependids = {}
168             rdepends = dataCache.rundeps[fn]
169             rrecs = dataCache.runrecs[fn]
170             for package in rdepends:
171                 for rdepend in rdepends[package]:
172                     bb.msg.debug(2, bb.msg.domain.TaskData, "Added runtime dependency %s for %s" % (rdepend, fn))
173                     rdependids[self.getrun_id(rdepend)] = None
174             for package in rrecs:
175                 for rdepend in rrecs[package]:
176                     bb.msg.debug(2, bb.msg.domain.TaskData, "Added runtime recommendation %s for %s" % (rdepend, fn))
177                     rdependids[self.getrun_id(rdepend)] = None
178             self.rdepids[fnid] = rdependids.keys()
179
180         for dep in self.depids[fnid]:
181             if dep in self.failed_deps:
182                 self.fail_fnid(fnid)
183                 return
184         for dep in self.rdepids[fnid]:
185             if dep in self.failed_rdeps:
186                 self.fail_fnid(fnid)
187                 return
188
189     def have_build_target(self, target):
190         """
191         Have we a build target matching this name?
192         """
193         targetid = self.getbuild_id(target)
194
195         if targetid in self.build_targets:
196             return True
197         return False
198
199     def have_runtime_target(self, target):
200         """
201         Have we a runtime target matching this name?
202         """
203         targetid = self.getrun_id(target)
204
205         if targetid in self.run_targets:
206             return True
207         return False
208
209     def add_build_target(self, fn, item):
210         """
211         Add a build target.
212         If already present, append the provider fn to the list
213         """
214         targetid = self.getbuild_id(item)
215         fnid = self.getfn_id(fn)
216
217         if targetid in self.build_targets:
218             if fnid in self.build_targets[targetid]:
219                 return
220             self.build_targets[targetid].append(fnid)
221             return
222         self.build_targets[targetid] = [fnid]
223
224     def add_runtime_target(self, fn, item):
225         """
226         Add a runtime target.
227         If already present, append the provider fn to the list
228         """
229         targetid = self.getrun_id(item)
230         fnid = self.getfn_id(fn)
231
232         if targetid in self.run_targets:
233             if fnid in self.run_targets[targetid]:
234                 return
235             self.run_targets[targetid].append(fnid)
236             return
237         self.run_targets[targetid] = [fnid]
238
239     def mark_external_target(self, item):
240         """
241         Mark a build target as being externally requested
242         """
243         targetid = self.getbuild_id(item)
244
245         if targetid not in self.external_targets:
246             self.external_targets.append(targetid)
247
248     def get_unresolved_build_targets(self, dataCache):
249         """
250         Return a list of build targets who's providers 
251         are unknown.
252         """
253         unresolved = []
254         for target in self.build_names_index:
255             if target in dataCache.ignored_dependencies:
256                 continue
257             if self.build_names_index.index(target) in self.failed_deps:
258                 continue
259             if not self.have_build_target(target):
260                 unresolved.append(target)
261         return unresolved
262
263     def get_unresolved_run_targets(self, dataCache):
264         """
265         Return a list of runtime targets who's providers 
266         are unknown.
267         """
268         unresolved = []
269         for target in self.run_names_index:
270             if target in dataCache.ignored_dependencies:
271                 continue
272             if self.run_names_index.index(target) in self.failed_rdeps:
273                 continue
274             if not self.have_runtime_target(target):
275                 unresolved.append(target)
276         return unresolved
277
278     def get_provider(self, item):
279         """
280         Return a list of providers of item
281         """
282         targetid = self.getbuild_id(item)
283    
284         return self.build_targets[targetid]
285
286     def get_dependees(self, itemid):
287         """
288         Return a list of targets which depend on item
289         """
290         dependees = []
291         for fnid in self.depids:
292             if itemid in self.depids[fnid]:
293                 dependees.append(fnid)
294         return dependees
295
296     def get_dependees_str(self, item):
297         """
298         Return a list of targets which depend on item as a user readable string
299         """
300         itemid = self.getbuild_id(item)
301         dependees = []
302         for fnid in self.depids:
303             if itemid in self.depids[fnid]:
304                 dependees.append(self.fn_index[fnid])
305         return dependees
306
307     def get_rdependees(self, itemid):
308         """
309         Return a list of targets which depend on runtime item
310         """
311         dependees = []
312         for fnid in self.rdepids:
313             if itemid in self.rdepids[fnid]:
314                 dependees.append(fnid)
315         return dependees
316
317     def get_rdependees_str(self, item):
318         """
319         Return a list of targets which depend on runtime item as a user readable string
320         """
321         itemid = self.getrun_id(item)
322         dependees = []
323         for fnid in self.rdepids:
324             if itemid in self.rdepids[fnid]:
325                 dependees.append(self.fn_index[fnid])
326         return dependees
327
328     def add_provider(self, cfgData, dataCache, item):
329         try:
330             self.add_provider_internal(cfgData, dataCache, item)
331         except bb.providers.NoProvider:
332             if self.abort:
333                 bb.msg.error(bb.msg.domain.Provider, "Nothing PROVIDES '%s' (but '%s' DEPENDS on or otherwise requires it)" % (item, self.get_dependees_str(item)))
334                 raise
335             targetid = self.getbuild_id(item)
336             self.remove_buildtarget(targetid)
337
338         self.mark_external_target(item)
339
340     def add_provider_internal(self, cfgData, dataCache, item):
341         """
342         Add the providers of item to the task data
343         Mark entries were specifically added externally as against dependencies 
344         added internally during dependency resolution
345         """
346
347         if item in dataCache.ignored_dependencies:
348             return
349
350         if not item in dataCache.providers:
351             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)))
352             bb.event.fire(bb.event.NoProvider(item, cfgData))
353             raise bb.providers.NoProvider(item)
354
355         if self.have_build_target(item):
356             return
357
358         all_p = dataCache.providers[item]
359
360         eligible, foundUnique = bb.providers.filterProviders(all_p, item, cfgData, dataCache)
361
362         for p in eligible:
363             fnid = self.getfn_id(p)
364             if fnid in self.failed_fnids:
365                 eligible.remove(p)
366
367         if not eligible:
368             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)))
369             bb.event.fire(bb.event.NoProvider(item, cfgData))
370             raise bb.providers.NoProvider(item)
371
372         if len(eligible) > 1 and foundUnique == False:
373             if item not in self.consider_msgs_cache:
374                 providers_list = []
375                 for fn in eligible:
376                     providers_list.append(dataCache.pkg_fn[fn])
377                 bb.msg.note(1, bb.msg.domain.Provider, "multiple providers are available for %s (%s);" % (item, ", ".join(providers_list)))
378                 bb.msg.note(1, bb.msg.domain.Provider, "consider defining PREFERRED_PROVIDER_%s" % item)
379                 bb.event.fire(bb.event.MultipleProviders(item, providers_list, cfgData))
380             self.consider_msgs_cache.append(item)
381
382         for fn in eligible:
383             fnid = self.getfn_id(fn)
384             if fnid in self.failed_fnids:
385                 continue
386             bb.msg.debug(2, bb.msg.domain.Provider, "adding %s to satisfy %s" % (fn, item))
387             self.add_build_target(fn, item)
388             self.add_tasks(fn, dataCache)
389
390
391             #item = dataCache.pkg_fn[fn]
392
393     def add_rprovider(self, cfgData, dataCache, item):
394         """
395         Add the runtime providers of item to the task data
396         (takes item names from RDEPENDS/PACKAGES namespace)
397         """
398
399         if item in dataCache.ignored_dependencies:
400             return
401
402         if self.have_runtime_target(item):
403             return
404
405         all_p = bb.providers.getRuntimeProviders(dataCache, item)
406
407         if not all_p:
408             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))
409             bb.event.fire(bb.event.NoProvider(item, cfgData, runtime=True))
410             raise bb.providers.NoRProvider(item)
411
412         eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache)
413
414         for p in eligible:
415             fnid = self.getfn_id(p)
416             if fnid in self.failed_fnids:
417                 eligible.remove(p)
418
419         if not eligible:
420             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))
421             bb.event.fire(bb.event.NoProvider(item, cfgData, runtime=True))
422             raise bb.providers.NoRProvider(item)
423
424         if len(eligible) > 1 and numberPreferred == 0:
425             if item not in self.consider_msgs_cache:
426                 providers_list = []
427                 for fn in eligible:
428                     providers_list.append(dataCache.pkg_fn[fn])
429                 bb.msg.note(2, bb.msg.domain.Provider, "multiple providers are available for runtime %s (%s);" % (item, ", ".join(providers_list)))
430                 bb.msg.note(2, bb.msg.domain.Provider, "consider defining a PREFERRED_PROVIDER entry to match runtime %s" % item)
431                 bb.event.fire(bb.event.MultipleProviders(item,providers_list, cfgData, runtime=True))
432             self.consider_msgs_cache.append(item)
433
434         if numberPreferred > 1:
435             if item not in self.consider_msgs_cache:
436                 providers_list = []
437                 for fn in eligible:
438                     providers_list.append(dataCache.pkg_fn[fn])
439                 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)))
440                 bb.msg.note(2, bb.msg.domain.Provider, "consider defining only one PREFERRED_PROVIDER entry to match runtime %s" % item)
441                 bb.event.fire(bb.event.MultipleProviders(item,providers_list, cfgData, runtime=True))
442             self.consider_msgs_cache.append(item)
443
444         # run through the list until we find one that we can build
445         for fn in eligible:
446             fnid = self.getfn_id(fn)
447             if fnid in self.failed_fnids:
448                 continue
449             bb.msg.debug(2, bb.msg.domain.Provider, "adding '%s' to satisfy runtime '%s'" % (fn, item))
450             self.add_runtime_target(fn, item)
451             self.add_tasks(fn, dataCache)
452
453     def fail_fnid(self, fnid, missing_list = []):
454         """
455         Mark a file as failed (unbuildable)
456         Remove any references from build and runtime provider lists
457
458         missing_list, A list of missing requirements for this target
459         """
460         if fnid in self.failed_fnids:
461             return
462         if not missing_list:
463             missing_list = [fnid]
464         bb.msg.debug(1, bb.msg.domain.Provider, "File '%s' is unbuildable, removing..." % self.fn_index[fnid])
465         self.failed_fnids.append(fnid)
466         for target in self.build_targets:
467             if fnid in self.build_targets[target]:
468                 self.build_targets[target].remove(fnid)
469                 if len(self.build_targets[target]) == 0:
470                     self.remove_buildtarget(target, missing_list)
471         for target in self.run_targets:
472             if fnid in self.run_targets[target]:
473                 self.run_targets[target].remove(fnid)
474                 if len(self.run_targets[target]) == 0:
475                     self.remove_runtarget(target, missing_list)
476
477     def remove_buildtarget(self, targetid, missing_list = []):
478         """
479         Mark a build target as failed (unbuildable)
480         Trigger removal of any files that have this as a dependency
481         """
482         if not missing_list:
483             missing_list = [self.build_names_index[targetid]]
484         else:
485             missing_list = [self.build_names_index[targetid]] + missing_list
486         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))
487         self.failed_deps.append(targetid)
488         dependees = self.get_dependees(targetid)
489         for fnid in dependees:
490             self.fail_fnid(fnid, missing_list)
491         if self.abort and targetid in self.external_targets:
492             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))
493             raise bb.providers.NoProvider
494
495     def remove_runtarget(self, targetid, missing_list = []):
496         """
497         Mark a run target as failed (unbuildable)
498         Trigger removal of any files that have this as a dependency
499         """
500         if not missing_list:
501             missing_list = [self.run_names_index[targetid]]
502         else:
503             missing_list = [self.run_names_index[targetid]] + missing_list
504
505         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))
506         self.failed_rdeps.append(targetid)
507         dependees = self.get_rdependees(targetid)
508         for fnid in dependees:
509             self.fail_fnid(fnid, missing_list)
510
511     def add_unresolved(self, cfgData, dataCache):
512         """
513         Resolve all unresolved build and runtime targets
514         """
515         bb.msg.note(1, bb.msg.domain.TaskData, "Resolving any missing task queue dependencies")
516         while 1:
517             added = 0
518             for target in self.get_unresolved_build_targets(dataCache):
519                 try:
520                     self.add_provider_internal(cfgData, dataCache, target)
521                     added = added + 1
522                 except bb.providers.NoProvider:
523                     targetid = self.getbuild_id(target)
524                     if self.abort and targetid in self.external_targets:
525                         bb.msg.error(bb.msg.domain.Provider, "Nothing PROVIDES '%s' (but '%s' DEPENDS on or otherwise requires it)" % (target, self.get_dependees_str(target)))
526                         raise
527                     self.remove_buildtarget(targetid)
528             for target in self.get_unresolved_run_targets(dataCache):
529                 try:
530                     self.add_rprovider(cfgData, dataCache, target)
531                     added = added + 1
532                 except bb.providers.NoRProvider:
533                     self.remove_runtarget(self.getrun_id(target))
534             bb.msg.debug(1, bb.msg.domain.TaskData, "Resolved " + str(added) + " extra dependecies")
535             if added == 0:
536                 break
537         # self.dump_data()
538
539     def dump_data(self):
540         """
541         Dump some debug information on the internal data structures
542         """
543         bb.msg.debug(3, bb.msg.domain.TaskData, "build_names:")
544         bb.msg.debug(3, bb.msg.domain.TaskData, ", ".join(self.build_names_index))
545
546         bb.msg.debug(3, bb.msg.domain.TaskData, "run_names:")
547         bb.msg.debug(3, bb.msg.domain.TaskData, ", ".join(self.run_names_index))
548
549         bb.msg.debug(3, bb.msg.domain.TaskData, "build_targets:")
550         for buildid in range(len(self.build_names_index)):
551             target = self.build_names_index[buildid]
552             targets = "None"
553             if buildid in self.build_targets:
554                 targets = self.build_targets[buildid]
555             bb.msg.debug(3, bb.msg.domain.TaskData, " (%s)%s: %s" % (buildid, target, targets))
556
557         bb.msg.debug(3, bb.msg.domain.TaskData, "run_targets:")
558         for runid in range(len(self.run_names_index)):
559             target = self.run_names_index[runid]
560             targets = "None"
561             if runid in self.run_targets:
562                 targets = self.run_targets[runid]
563             bb.msg.debug(3, bb.msg.domain.TaskData, " (%s)%s: %s" % (runid, target, targets))
564
565         bb.msg.debug(3, bb.msg.domain.TaskData, "tasks:")
566         for task in range(len(self.tasks_name)):
567             bb.msg.debug(3, bb.msg.domain.TaskData, " (%s)%s - %s: %s" % (
568                 task, 
569                 self.fn_index[self.tasks_fnid[task]], 
570                 self.tasks_name[task], 
571                 self.tasks_tdepends[task]))
572
573         bb.msg.debug(3, bb.msg.domain.TaskData, "dependency ids (per fn):")
574         for fnid in self.depids:
575             bb.msg.debug(3, bb.msg.domain.TaskData, " %s %s: %s" % (fnid, self.fn_index[fnid], self.depids[fnid]))
576
577         bb.msg.debug(3, bb.msg.domain.TaskData, "runtime dependency ids (per fn):")
578         for fnid in self.rdepids:
579             bb.msg.debug(3, bb.msg.domain.TaskData, " %s %s: %s" % (fnid, self.fn_index[fnid], self.rdepids[fnid]))
580
581