siggen.py: better print for task hash comparison
[bitbake.git] / lib / bb / siggen.py
1 import hashlib
2 import logging
3 import re
4 import bb.data
5
6 logger = logging.getLogger('BitBake.SigGen')
7
8 try:
9     import cPickle as pickle
10 except ImportError:
11     import pickle
12     logger.info('Importing cPickle failed.  Falling back to a very slow implementation.')
13
14 def init(d):
15     siggens = [obj for obj in globals().itervalues()
16                       if type(obj) is type and issubclass(obj, SignatureGenerator)]
17
18     desired = bb.data.getVar("BB_SIGNATURE_HANDLER", d, True) or "noop"
19     for sg in siggens:
20         if desired == sg.name:
21             return sg(d)
22             break
23     else:
24         logger.error("Invalid signature generator '%s', using default 'noop'\n"
25                      "Available generators: %s",
26                      ', '.join(obj.name for obj in siggens))
27         return SignatureGenerator(d)
28
29 class SignatureGenerator(object):
30     """
31     """
32     name = "noop"
33
34     def __init__(self, data):
35         return
36
37     def finalise(self, fn, d, varient):
38         return
39
40     def get_taskhash(self, fn, task, deps, dataCache):
41         return 0
42
43     def set_taskdata(self, hashes, deps):
44         return
45
46     def stampfile(self, stampbase, file_name, taskname, extrainfo):
47         return ("%s.%s.%s" % (stampbase, taskname, extrainfo)).rstrip('.')
48
49 class SignatureGeneratorBasic(SignatureGenerator):
50     """
51     """
52     name = "basic"
53
54     def __init__(self, data):
55         self.basehash = {}
56         self.taskhash = {}
57         self.taskdeps = {}
58         self.runtaskdeps = {}
59         self.gendeps = {}
60         self.lookupcache = {}
61         self.basewhitelist = set((data.getVar("BB_HASHBASE_WHITELIST", True) or "").split())
62         self.taskwhitelist = data.getVar("BB_HASHTASK_WHITELIST", True) or None
63
64         if self.taskwhitelist:
65             self.twl = re.compile(self.taskwhitelist)
66         else:
67             self.twl = None
68
69     def _build_data(self, fn, d):
70
71         tasklist, gendeps = bb.data.generate_dependencies(d)
72
73         taskdeps = {}
74         basehash = {}
75         lookupcache = {}
76
77         for task in tasklist:
78             data = d.getVar(task, False)
79             lookupcache[task] = data
80
81             newdeps = gendeps[task]
82             seen = set()
83             while newdeps:
84                 nextdeps = newdeps
85                 seen |= nextdeps
86                 newdeps = set()
87                 for dep in nextdeps:
88                     if dep in self.basewhitelist:
89                         continue
90                     newdeps |= gendeps[dep]
91                 newdeps -= seen
92
93             alldeps = seen - self.basewhitelist
94
95             for dep in sorted(alldeps):
96                 if dep in lookupcache:
97                     var = lookupcache[dep]
98                 else:
99                     var = d.getVar(dep, False)
100                     lookupcache[dep] = var
101                 if var:
102                     data = data + var
103             if data is None:
104                 bb.error("Task %s from %s seems to be empty?!" % (task, fn))
105             self.basehash[fn + "." + task] = hashlib.md5(data).hexdigest()
106             taskdeps[task] = sorted(alldeps)
107
108         self.taskdeps[fn] = taskdeps
109         self.gendeps[fn] = gendeps
110         self.lookupcache[fn] = lookupcache
111
112         return taskdeps
113
114     def finalise(self, fn, d, variant):
115
116         if variant:
117             fn = "virtual:" + variant + ":" + fn
118
119         taskdeps = self._build_data(fn, d)
120
121         #Slow but can be useful for debugging mismatched basehashes
122         #for task in self.taskdeps[fn]:
123         #    self.dump_sigtask(fn, task, d.getVar("STAMP", True), False)
124
125         for task in taskdeps:
126             d.setVar("BB_BASEHASH_task-%s" % task, self.basehash[fn + "." + task])
127
128     def get_taskhash(self, fn, task, deps, dataCache):
129         k = fn + "." + task
130         data = dataCache.basetaskhash[k]
131         self.runtaskdeps[k] = []
132         for dep in sorted(deps):
133             # We only manipulate the dependencies for packages not in the whitelist
134             if self.twl and not self.twl.search(dataCache.pkg_fn[fn]):
135                 # then process the actual dependencies
136                 dep_fn = re.search("(?P<fn>.*)\..*", dep).group('fn')
137                 if self.twl.search(dataCache.pkg_fn[dep_fn]):
138                     continue
139             if dep not in self.taskhash:
140                 bb.fatal("%s is not in taskhash, caller isn't calling in dependency order?", dep)
141             data = data + self.taskhash[dep]
142             self.runtaskdeps[k].append(dep)
143         h = hashlib.md5(data).hexdigest()
144         self.taskhash[k] = h
145         #d.setVar("BB_TASKHASH_task-%s" % task, taskhash[task])
146         return h
147
148     def set_taskdata(self, hashes, deps):
149         self.runtaskdeps = deps
150         self.taskhash = hashes
151
152     def dump_sigtask(self, fn, task, stampbase, runtime):
153         k = fn + "." + task
154         if runtime == "customfile":
155             sigfile = stampbase
156         elif runtime:
157             sigfile = stampbase + "." + task + ".sigdata" + "." + self.taskhash[k]
158         else:
159             sigfile = stampbase + "." + task + ".sigbasedata" + "." + self.basehash[k]
160
161         bb.utils.mkdirhier(os.path.dirname(sigfile))
162
163         data = {}
164         data['basewhitelist'] = self.basewhitelist
165         data['taskwhitelist'] = self.taskwhitelist
166         data['taskdeps'] = self.taskdeps[fn][task]
167         data['basehash'] = self.basehash[k]
168         data['gendeps'] = {}
169         data['varvals'] = {}
170         data['varvals'][task] = self.lookupcache[fn][task]
171         for dep in self.taskdeps[fn][task]:
172             if dep in self.basewhitelist:
173                 continue
174             data['gendeps'][dep] = self.gendeps[fn][dep]
175             data['varvals'][dep] = self.lookupcache[fn][dep]
176
177         if runtime:
178             data['runtaskdeps'] = self.runtaskdeps[k]
179             data['runtaskhashes'] = {}
180             for dep in data['runtaskdeps']:
181                 data['runtaskhashes'][dep] = self.taskhash[dep]
182
183         p = pickle.Pickler(file(sigfile, "wb"), -1)
184         p.dump(data)
185
186     def dump_sigs(self, dataCache):
187         for fn in self.taskdeps:
188             for task in self.taskdeps[fn]:
189                 k = fn + "." + task
190                 if k not in self.taskhash:
191                     continue
192                 if dataCache.basetaskhash[k] != self.basehash[k]:
193                     bb.error("Bitbake's cached basehash does not match the one we just generated (%s)!" % k)
194                     bb.error("The mismatched hashes were %s and %s" % (dataCache.basetaskhash[k], self.basehash[k]))
195                 self.dump_sigtask(fn, task, dataCache.stamp[fn], True)
196
197 class SignatureGeneratorBasicHash(SignatureGeneratorBasic):
198     name = "basichash"
199
200     def stampfile(self, stampbase, fn, taskname, extrainfo):
201         if taskname != "do_setscene" and taskname.endswith("_setscene"):
202             k = fn + "." + taskname[:-9]
203         else:
204             k = fn + "." + taskname
205         h = self.taskhash[k]
206         return ("%s.%s.%s.%s" % (stampbase, taskname, h, extrainfo)).rstrip('.')
207
208 def dump_this_task(outfile, d):
209     import bb.parse
210     fn = d.getVar("BB_FILENAME", True)
211     task = "do_" + d.getVar("BB_CURRENTTASK", True)
212     bb.parse.siggen.dump_sigtask(fn, task, outfile, "customfile")
213
214 def compare_sigfiles(a, b):
215     p1 = pickle.Unpickler(file(a, "rb"))
216     a_data = p1.load()
217     p2 = pickle.Unpickler(file(b, "rb"))
218     b_data = p2.load()
219
220     def dict_diff(a, b):
221         sa = set(a.keys())
222         sb = set(b.keys())
223         common = sa & sb
224         changed = set()
225         for i in common:
226             if a[i] != b[i]:
227                 changed.add(i)
228         added = sa - sb
229         removed = sb - sa
230         return changed, added, removed
231
232     if 'basewhitelist' in a_data and a_data['basewhitelist'] != b_data['basewhitelist']:
233         print "basewhitelist changed from %s to %s" % (a_data['basewhitelist'], b_data['basewhitelist'])
234
235     if 'taskwhitelist' in a_data and a_data['taskwhitelist'] != b_data['taskwhitelist']:
236         print "taskwhitelist changed from %s to %s" % (a_data['taskwhitelist'], b_data['taskwhitelist'])
237
238     if a_data['taskdeps'] != b_data['taskdeps']:
239         print "Task dependencies changed from %s to %s" % (sorted(a_data['taskdeps']), sorted(b_data['taskdeps']))
240
241     if a_data['basehash'] != b_data['basehash']:
242         print "basehash changed from %s to %s" % (a_data['basehash'], b_data['basehash'])
243
244     changed, added, removed = dict_diff(a_data['gendeps'], b_data['gendeps'])
245     if changed:
246         for dep in changed:
247             print "List of dependencies for variable %s changed from %s to %s" % (dep, a_data['gendeps'][dep], b_data['gendeps'][dep])
248     if added:
249         for dep in added:
250             print "Dependency on variable %s was added" % (dep)
251     if removed:
252         for dep in removed:
253             print "Dependency on Variable %s was removed" % (dep)
254
255
256     changed, added, removed = dict_diff(a_data['varvals'], b_data['varvals'])
257     if changed:
258         for dep in changed:
259             print "Variable %s value changed from %s to %s" % (dep, a_data['varvals'][dep], b_data['varvals'][dep])
260
261     if 'runtaskhashes' in a_data and 'runtaskhashes' in b_data:
262         changed, added, removed = dict_diff(a_data['runtaskhashes'], b_data['runtaskhashes'])
263         if added:
264             for dep in added:
265                 print "Dependency on task %s was added" % (dep)
266         if removed:
267             for dep in removed:
268                 print "Dependency on task %s was removed" % (dep)
269         if changed:
270             for dep in changed:
271                 print "Hash for dependent task %s changed from %s to %s" % (dep, a_data['runtaskhashes'][dep], b_data['runtaskhashes'][dep])
272     elif 'runtaskdeps' in a_data and 'runtaskdeps' in b_data and sorted(a_data['runtaskdeps']) != sorted(b_data['runtaskdeps']):
273         print "Tasks this task depends on changed from %s to %s" % (sorted(a_data['runtaskdeps']), sorted(b_data['runtaskdeps']))
274
275 def dump_sigfile(a):
276     p1 = pickle.Unpickler(file(a, "rb"))
277     a_data = p1.load()
278
279     print "basewhitelist: %s" % (a_data['basewhitelist'])
280
281     print "taskwhitelist: %s" % (a_data['taskwhitelist'])
282
283     print "Task dependencies: %s" % (sorted(a_data['taskdeps']))
284
285     print "basehash: %s" % (a_data['basehash'])
286
287     for dep in a_data['gendeps']:
288         print "List of dependencies for variable %s is %s" % (dep, a_data['gendeps'][dep])
289
290     for dep in a_data['varvals']:
291         print "Variable %s value is %s" % (dep, a_data['varvals'][dep])
292
293     if 'runtaskdeps' in a_data:
294         print "Tasks this task depends on: %s" % (a_data['runtaskdeps'])
295
296     if 'runtaskhashes' in a_data:
297         for dep in a_data['runtaskhashes']:
298             print "Hash for dependent task %s is %s" % (dep, a_data['runtaskhashes'][dep])