blob: 94e822c485f35002d89493cedb57ef8b2276d47f [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001#!/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 TaskEntry:
41 def __init__(self):
42 self.tdepends = []
43 self.idepends = []
44 self.irdepends = []
45
46class TaskData:
47 """
48 BitBake Task Data implementation
49 """
50 def __init__(self, abort = True, skiplist = None, allowincomplete = False):
51 self.build_targets = {}
52 self.run_targets = {}
53
54 self.external_targets = []
55
56 self.seenfns = []
57 self.taskentries = {}
58
59 self.depids = {}
60 self.rdepids = {}
61
62 self.consider_msgs_cache = []
63
64 self.failed_deps = []
65 self.failed_rdeps = []
66 self.failed_fns = []
67
68 self.abort = abort
69 self.allowincomplete = allowincomplete
70
71 self.skiplist = skiplist
72
73 self.mcdepends = []
74
75 def add_tasks(self, fn, dataCache):
76 """
77 Add tasks for a given fn to the database
78 """
79
80 task_deps = dataCache.task_deps[fn]
81
82 if fn in self.failed_fns:
83 bb.msg.fatal("TaskData", "Trying to re-add a failed file? Something is broken...")
84
85 # Check if we've already seen this fn
86 if fn in self.seenfns:
87 return
88
89 self.seenfns.append(fn)
90
91 self.add_extra_deps(fn, dataCache)
92
93 def add_mcdepends(task):
94 for dep in task_deps['mcdepends'][task].split():
95 if len(dep.split(':')) != 5:
96 bb.msg.fatal("TaskData", "Error for %s:%s[%s], multiconfig dependency %s does not contain exactly four ':' characters.\n Task '%s' should be specified in the form 'multiconfig:fromMC:toMC:packagename:task'" % (fn, task, 'mcdepends', dep, 'mcdepends'))
97 if dep not in self.mcdepends:
98 self.mcdepends.append(dep)
99
100 # Common code for dep_name/depends = 'depends'/idepends and 'rdepends'/irdepends
101 def handle_deps(task, dep_name, depends, seen):
102 if dep_name in task_deps and task in task_deps[dep_name]:
103 ids = []
104 for dep in task_deps[dep_name][task].split():
105 if dep:
106 parts = dep.split(":")
107 if len(parts) != 2:
108 bb.msg.fatal("TaskData", "Error for %s:%s[%s], dependency %s in '%s' does not contain exactly one ':' character.\n Task '%s' should be specified in the form 'packagename:task'" % (fn, task, dep_name, dep, task_deps[dep_name][task], dep_name))
109 ids.append((parts[0], parts[1]))
110 seen(parts[0])
111 depends.extend(ids)
112
113 for task in task_deps['tasks']:
114
115 tid = "%s:%s" % (fn, task)
116 self.taskentries[tid] = TaskEntry()
117
118 # Work out task dependencies
119 parentids = []
120 for dep in task_deps['parents'][task]:
121 if dep not in task_deps['tasks']:
122 bb.debug(2, "Not adding dependency of %s on %s since %s does not exist" % (task, dep, dep))
123 continue
124 parentid = "%s:%s" % (fn, dep)
125 parentids.append(parentid)
126 self.taskentries[tid].tdepends.extend(parentids)
127
128
129 # Touch all intertask dependencies
130 handle_deps(task, 'depends', self.taskentries[tid].idepends, self.seen_build_target)
131 handle_deps(task, 'rdepends', self.taskentries[tid].irdepends, self.seen_run_target)
132
133 if 'mcdepends' in task_deps and task in task_deps['mcdepends']:
134 add_mcdepends(task)
135
136 # Work out build dependencies
137 if not fn in self.depids:
138 dependids = set()
139 for depend in dataCache.deps[fn]:
140 dependids.add(depend)
141 self.depids[fn] = list(dependids)
142 logger.debug(2, "Added dependencies %s for %s", str(dataCache.deps[fn]), fn)
143
144 # Work out runtime dependencies
145 if not fn in self.rdepids:
146 rdependids = set()
147 rdepends = dataCache.rundeps[fn]
148 rrecs = dataCache.runrecs[fn]
149 rdependlist = []
150 rreclist = []
151 for package in rdepends:
152 for rdepend in rdepends[package]:
153 rdependlist.append(rdepend)
154 rdependids.add(rdepend)
155 for package in rrecs:
156 for rdepend in rrecs[package]:
157 rreclist.append(rdepend)
158 rdependids.add(rdepend)
159 if rdependlist:
160 logger.debug(2, "Added runtime dependencies %s for %s", str(rdependlist), fn)
161 if rreclist:
162 logger.debug(2, "Added runtime recommendations %s for %s", str(rreclist), fn)
163 self.rdepids[fn] = list(rdependids)
164
165 for dep in self.depids[fn]:
166 self.seen_build_target(dep)
167 if dep in self.failed_deps:
168 self.fail_fn(fn)
169 return
170 for dep in self.rdepids[fn]:
171 self.seen_run_target(dep)
172 if dep in self.failed_rdeps:
173 self.fail_fn(fn)
174 return
175
176 def add_extra_deps(self, fn, dataCache):
177 func = dataCache.extradepsfunc.get(fn, None)
178 if func:
179 bb.providers.buildWorldTargetList(dataCache)
180 pn = dataCache.pkg_fn[fn]
181 params = {'deps': dataCache.deps[fn],
182 'world_target': dataCache.world_target,
183 'pkg_pn': dataCache.pkg_pn,
184 'self_pn': pn}
185 funcname = '_%s_calculate_extra_depends' % pn.replace('-', '_')
186 paramlist = ','.join(params.keys())
187 func = 'def %s(%s):\n%s\n\n%s(%s)' % (funcname, paramlist, func, funcname, paramlist)
188 bb.utils.better_exec(func, params)
189
190
191 def have_build_target(self, target):
192 """
193 Have we a build target matching this name?
194 """
195 if target in self.build_targets and self.build_targets[target]:
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 if target in self.run_targets and self.run_targets[target]:
204 return True
205 return False
206
207 def seen_build_target(self, name):
208 """
209 Maintain a list of build targets
210 """
211 if name not in self.build_targets:
212 self.build_targets[name] = []
213
214 def add_build_target(self, fn, item):
215 """
216 Add a build target.
217 If already present, append the provider fn to the list
218 """
219 if item in self.build_targets:
220 if fn in self.build_targets[item]:
221 return
222 self.build_targets[item].append(fn)
223 return
224 self.build_targets[item] = [fn]
225
226 def seen_run_target(self, name):
227 """
228 Maintain a list of runtime build targets
229 """
230 if name not in self.run_targets:
231 self.run_targets[name] = []
232
233 def add_runtime_target(self, fn, item):
234 """
235 Add a runtime target.
236 If already present, append the provider fn to the list
237 """
238 if item in self.run_targets:
239 if fn in self.run_targets[item]:
240 return
241 self.run_targets[item].append(fn)
242 return
243 self.run_targets[item] = [fn]
244
245 def mark_external_target(self, target):
246 """
247 Mark a build target as being externally requested
248 """
249 if target not in self.external_targets:
250 self.external_targets.append(target)
251
252 def get_unresolved_build_targets(self, dataCache):
253 """
254 Return a list of build targets who's providers
255 are unknown.
256 """
257 unresolved = []
258 for target in self.build_targets:
259 if re_match_strings(target, dataCache.ignored_dependencies):
260 continue
261 if target in self.failed_deps:
262 continue
263 if not self.build_targets[target]:
264 unresolved.append(target)
265 return unresolved
266
267 def get_unresolved_run_targets(self, dataCache):
268 """
269 Return a list of runtime targets who's providers
270 are unknown.
271 """
272 unresolved = []
273 for target in self.run_targets:
274 if re_match_strings(target, dataCache.ignored_dependencies):
275 continue
276 if target in self.failed_rdeps:
277 continue
278 if not self.run_targets[target]:
279 unresolved.append(target)
280 return unresolved
281
282 def get_provider(self, item):
283 """
284 Return a list of providers of item
285 """
286 return self.build_targets[item]
287
288 def get_dependees(self, item):
289 """
290 Return a list of targets which depend on item
291 """
292 dependees = []
293 for fn in self.depids:
294 if item in self.depids[fn]:
295 dependees.append(fn)
296 return dependees
297
298 def get_rdependees(self, item):
299 """
300 Return a list of targets which depend on runtime item
301 """
302 dependees = []
303 for fn in self.rdepids:
304 if item in self.rdepids[fn]:
305 dependees.append(fn)
306 return dependees
307
308 def get_reasons(self, item, runtime=False):
309 """
310 Get the reason(s) for an item not being provided, if any
311 """
312 reasons = []
313 if self.skiplist:
314 for fn in self.skiplist:
315 skipitem = self.skiplist[fn]
316 if skipitem.pn == item:
317 reasons.append("%s was skipped: %s" % (skipitem.pn, skipitem.skipreason))
318 elif runtime and item in skipitem.rprovides:
319 reasons.append("%s RPROVIDES %s but was skipped: %s" % (skipitem.pn, item, skipitem.skipreason))
320 elif not runtime and item in skipitem.provides:
321 reasons.append("%s PROVIDES %s but was skipped: %s" % (skipitem.pn, item, skipitem.skipreason))
322 return reasons
323
324 def get_close_matches(self, item, provider_list):
325 import difflib
326 if self.skiplist:
327 skipped = []
328 for fn in self.skiplist:
329 skipped.append(self.skiplist[fn].pn)
330 full_list = provider_list + skipped
331 else:
332 full_list = provider_list
333 return difflib.get_close_matches(item, full_list, cutoff=0.7)
334
335 def add_provider(self, cfgData, dataCache, item):
336 try:
337 self.add_provider_internal(cfgData, dataCache, item)
338 except bb.providers.NoProvider:
339 if self.abort:
340 raise
341 self.remove_buildtarget(item)
342
343 self.mark_external_target(item)
344
345 def add_provider_internal(self, cfgData, dataCache, item):
346 """
347 Add the providers of item to the task data
348 Mark entries were specifically added externally as against dependencies
349 added internally during dependency resolution
350 """
351
352 if re_match_strings(item, dataCache.ignored_dependencies):
353 return
354
355 if not item in dataCache.providers:
356 close_matches = self.get_close_matches(item, list(dataCache.providers.keys()))
357 # Is it in RuntimeProviders ?
358 all_p = bb.providers.getRuntimeProviders(dataCache, item)
359 for fn in all_p:
360 new = dataCache.pkg_fn[fn] + " RPROVIDES " + item
361 if new not in close_matches:
362 close_matches.append(new)
363 bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees(item), reasons=self.get_reasons(item), close_matches=close_matches), cfgData)
364 raise bb.providers.NoProvider(item)
365
366 if self.have_build_target(item):
367 return
368
369 all_p = dataCache.providers[item]
370
371 eligible, foundUnique = bb.providers.filterProviders(all_p, item, cfgData, dataCache)
372 eligible = [p for p in eligible if not p in self.failed_fns]
373
374 if not eligible:
375 bb.event.fire(bb.event.NoProvider(item, dependees=self.get_dependees(item), reasons=["No eligible PROVIDERs exist for '%s'" % item]), cfgData)
376 raise bb.providers.NoProvider(item)
377
378 if len(eligible) > 1 and foundUnique == False:
379 if item not in self.consider_msgs_cache:
380 providers_list = []
381 for fn in eligible:
382 providers_list.append(dataCache.pkg_fn[fn])
383 bb.event.fire(bb.event.MultipleProviders(item, providers_list), cfgData)
384 self.consider_msgs_cache.append(item)
385
386 for fn in eligible:
387 if fn in self.failed_fns:
388 continue
389 logger.debug(2, "adding %s to satisfy %s", fn, item)
390 self.add_build_target(fn, item)
391 self.add_tasks(fn, dataCache)
392
393
394 #item = dataCache.pkg_fn[fn]
395
396 def add_rprovider(self, cfgData, dataCache, item):
397 """
398 Add the runtime providers of item to the task data
399 (takes item names from RDEPENDS/PACKAGES namespace)
400 """
401
402 if re_match_strings(item, dataCache.ignored_dependencies):
403 return
404
405 if self.have_runtime_target(item):
406 return
407
408 all_p = bb.providers.getRuntimeProviders(dataCache, item)
409
410 if not all_p:
411 bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees(item), reasons=self.get_reasons(item, True)), cfgData)
412 raise bb.providers.NoRProvider(item)
413
414 eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache)
415 eligible = [p for p in eligible if not p in self.failed_fns]
416
417 if not eligible:
418 bb.event.fire(bb.event.NoProvider(item, runtime=True, dependees=self.get_rdependees(item), reasons=["No eligible RPROVIDERs exist for '%s'" % item]), cfgData)
419 raise bb.providers.NoRProvider(item)
420
421 if len(eligible) > 1 and numberPreferred == 0:
422 if item not in self.consider_msgs_cache:
423 providers_list = []
424 for fn in eligible:
425 providers_list.append(dataCache.pkg_fn[fn])
426 bb.event.fire(bb.event.MultipleProviders(item, providers_list, runtime=True), cfgData)
427 self.consider_msgs_cache.append(item)
428
429 if numberPreferred > 1:
430 if item not in self.consider_msgs_cache:
431 providers_list = []
432 for fn in eligible:
433 providers_list.append(dataCache.pkg_fn[fn])
434 bb.event.fire(bb.event.MultipleProviders(item, providers_list, runtime=True), cfgData)
435 self.consider_msgs_cache.append(item)
436 raise bb.providers.MultipleRProvider(item)
437
438 # run through the list until we find one that we can build
439 for fn in eligible:
440 if fn in self.failed_fns:
441 continue
442 logger.debug(2, "adding '%s' to satisfy runtime '%s'", fn, item)
443 self.add_runtime_target(fn, item)
444 self.add_tasks(fn, dataCache)
445
446 def fail_fn(self, fn, missing_list=None):
447 """
448 Mark a file as failed (unbuildable)
449 Remove any references from build and runtime provider lists
450
451 missing_list, A list of missing requirements for this target
452 """
453 if fn in self.failed_fns:
454 return
455 if not missing_list:
456 missing_list = []
457 logger.debug(1, "File '%s' is unbuildable, removing...", fn)
458 self.failed_fns.append(fn)
459 for target in self.build_targets:
460 if fn in self.build_targets[target]:
461 self.build_targets[target].remove(fn)
462 if len(self.build_targets[target]) == 0:
463 self.remove_buildtarget(target, missing_list)
464 for target in self.run_targets:
465 if fn in self.run_targets[target]:
466 self.run_targets[target].remove(fn)
467 if len(self.run_targets[target]) == 0:
468 self.remove_runtarget(target, missing_list)
469
470 def remove_buildtarget(self, target, missing_list=None):
471 """
472 Mark a build target as failed (unbuildable)
473 Trigger removal of any files that have this as a dependency
474 """
475 if not missing_list:
476 missing_list = [target]
477 else:
478 missing_list = [target] + missing_list
479 logger.verbose("Target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", target, missing_list)
480 self.failed_deps.append(target)
481 dependees = self.get_dependees(target)
482 for fn in dependees:
483 self.fail_fn(fn, missing_list)
484 for tid in self.taskentries:
485 for (idepend, idependtask) in self.taskentries[tid].idepends:
486 if idepend == target:
487 fn = tid.rsplit(":",1)[0]
488 self.fail_fn(fn, missing_list)
489
490 if self.abort and target in self.external_targets:
491 logger.error("Required build target '%s' has no buildable providers.\nMissing or unbuildable dependency chain was: %s", target, missing_list)
492 raise bb.providers.NoProvider(target)
493
494 def remove_runtarget(self, target, missing_list=None):
495 """
496 Mark a run target as failed (unbuildable)
497 Trigger removal of any files that have this as a dependency
498 """
499 if not missing_list:
500 missing_list = [target]
501 else:
502 missing_list = [target] + missing_list
503
504 logger.info("Runtime target '%s' is unbuildable, removing...\nMissing or unbuildable dependency chain was: %s", target, missing_list)
505 self.failed_rdeps.append(target)
506 dependees = self.get_rdependees(target)
507 for fn in dependees:
508 self.fail_fn(fn, missing_list)
509 for tid in self.taskentries:
510 for (idepend, idependtask) in self.taskentries[tid].irdepends:
511 if idepend == target:
512 fn = tid.rsplit(":",1)[0]
513 self.fail_fn(fn, missing_list)
514
515 def add_unresolved(self, cfgData, dataCache):
516 """
517 Resolve all unresolved build and runtime targets
518 """
519 logger.info("Resolving any missing task queue dependencies")
520 while True:
521 added = 0
522 for target in self.get_unresolved_build_targets(dataCache):
523 try:
524 self.add_provider_internal(cfgData, dataCache, target)
525 added = added + 1
526 except bb.providers.NoProvider:
527 if self.abort and target in self.external_targets and not self.allowincomplete:
528 raise
529 if not self.allowincomplete:
530 self.remove_buildtarget(target)
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, bb.providers.MultipleRProvider):
536 self.remove_runtarget(target)
537 logger.debug(1, "Resolved " + str(added) + " extra dependencies")
538 if added == 0:
539 break
540 # self.dump_data()
541
542 def get_providermap(self, prefix=None):
543 provmap = {}
544 for name in self.build_targets:
545 if prefix and not name.startswith(prefix):
546 continue
547 if self.have_build_target(name):
548 provider = self.get_provider(name)
549 if provider:
550 provmap[name] = provider[0]
551 return provmap
552
553 def get_mcdepends(self):
554 return self.mcdepends
555
556 def dump_data(self):
557 """
558 Dump some debug information on the internal data structures
559 """
560 logger.debug(3, "build_names:")
561 logger.debug(3, ", ".join(self.build_targets))
562
563 logger.debug(3, "run_names:")
564 logger.debug(3, ", ".join(self.run_targets))
565
566 logger.debug(3, "build_targets:")
567 for target in self.build_targets:
568 targets = "None"
569 if target in self.build_targets:
570 targets = self.build_targets[target]
571 logger.debug(3, " %s: %s", target, targets)
572
573 logger.debug(3, "run_targets:")
574 for target in self.run_targets:
575 targets = "None"
576 if target in self.run_targets:
577 targets = self.run_targets[target]
578 logger.debug(3, " %s: %s", target, targets)
579
580 logger.debug(3, "tasks:")
581 for tid in self.taskentries:
582 logger.debug(3, " %s: %s %s %s",
583 tid,
584 self.taskentries[tid].idepends,
585 self.taskentries[tid].irdepends,
586 self.taskentries[tid].tdepends)
587
588 logger.debug(3, "dependency ids (per fn):")
589 for fn in self.depids:
590 logger.debug(3, " %s: %s", fn, self.depids[fn])
591
592 logger.debug(3, "runtime dependency ids (per fn):")
593 for fn in self.rdepids:
594 logger.debug(3, " %s: %s", fn, self.rdepids[fn])