blob: 9d14ecefaf008ad33ca538fb1e12b8b90577cdc2 [file] [log] [blame]
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001#
2# BitBake Graphical GTK based Dependency Explorer
3#
4# Copyright (C) 2007 Ross Burton
5# Copyright (C) 2007 - 2008 Richard Purdie
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20import sys
21import gi
22gi.require_version('Gtk', '3.0')
23from gi.repository import Gtk, Gdk, GObject
24from multiprocessing import Queue
25import threading
26from xmlrpc import client
27import time
28import bb
29import bb.event
30
31# Package Model
32(COL_PKG_NAME) = (0)
33
34# Dependency Model
35(TYPE_DEP, TYPE_RDEP) = (0, 1)
36(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2)
37
38
39class PackageDepView(Gtk.TreeView):
40 def __init__(self, model, dep_type, label):
41 Gtk.TreeView.__init__(self)
42 self.current = None
43 self.dep_type = dep_type
44 self.filter_model = model.filter_new()
45 self.filter_model.set_visible_func(self._filter, data=None)
46 self.set_model(self.filter_model)
47 self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PACKAGE))
48
49 def _filter(self, model, iter, data):
50 this_type = model[iter][COL_DEP_TYPE]
51 package = model[iter][COL_DEP_PARENT]
52 if this_type != self.dep_type: return False
53 return package == self.current
54
55 def set_current_package(self, package):
56 self.current = package
57 self.filter_model.refilter()
58
59
60class PackageReverseDepView(Gtk.TreeView):
61 def __init__(self, model, label):
62 Gtk.TreeView.__init__(self)
63 self.current = None
64 self.filter_model = model.filter_new()
65 self.filter_model.set_visible_func(self._filter)
66 self.set_model(self.filter_model)
67 self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PARENT))
68
69 def _filter(self, model, iter, data):
70 package = model[iter][COL_DEP_PACKAGE]
71 return package == self.current
72
73 def set_current_package(self, package):
74 self.current = package
75 self.filter_model.refilter()
76
77
78class DepExplorer(Gtk.Window):
79 def __init__(self):
80 Gtk.Window.__init__(self)
81 self.set_title("Task Dependency Explorer")
82 self.set_default_size(500, 500)
83 self.connect("delete-event", Gtk.main_quit)
84
85 # Create the data models
86 self.pkg_model = Gtk.ListStore(GObject.TYPE_STRING)
87 self.pkg_model.set_sort_column_id(COL_PKG_NAME, Gtk.SortType.ASCENDING)
88 self.depends_model = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_STRING)
89 self.depends_model.set_sort_column_id(COL_DEP_PACKAGE, Gtk.SortType.ASCENDING)
90
91 pane = Gtk.HPaned()
92 pane.set_position(250)
93 self.add(pane)
94
95 # The master list of packages
96 scrolled = Gtk.ScrolledWindow()
97 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
98 scrolled.set_shadow_type(Gtk.ShadowType.IN)
99
100 self.pkg_treeview = Gtk.TreeView(self.pkg_model)
101 self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed)
102 column = Gtk.TreeViewColumn("Package", Gtk.CellRendererText(), text=COL_PKG_NAME)
103 self.pkg_treeview.append_column(column)
104 pane.add1(scrolled)
105 scrolled.add(self.pkg_treeview)
106
107 box = Gtk.VBox(homogeneous=True, spacing=4)
108
109 # Task Depends
110 scrolled = Gtk.ScrolledWindow()
111 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
112 scrolled.set_shadow_type(Gtk.ShadowType.IN)
113 self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Dependencies")
114 self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
115 scrolled.add(self.dep_treeview)
116 box.add(scrolled)
117 pane.add2(box)
118
119 # Reverse Task Depends
120 scrolled = Gtk.ScrolledWindow()
121 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
122 scrolled.set_shadow_type(Gtk.ShadowType.IN)
123 self.revdep_treeview = PackageReverseDepView(self.depends_model, "Dependent Tasks")
124 self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
125 scrolled.add(self.revdep_treeview)
126 box.add(scrolled)
127 pane.add2(box)
128
129 self.show_all()
130
131 def on_package_activated(self, treeview, path, column, data_col):
132 model = treeview.get_model()
133 package = model.get_value(model.get_iter(path), data_col)
134
135 pkg_path = []
136 def finder(model, path, iter, needle):
137 package = model.get_value(iter, COL_PKG_NAME)
138 if package == needle:
139 pkg_path.append(path)
140 return True
141 else:
142 return False
143 self.pkg_model.foreach(finder, package)
144 if pkg_path:
145 self.pkg_treeview.get_selection().select_path(pkg_path[0])
146 self.pkg_treeview.scroll_to_cell(pkg_path[0])
147
148 def on_cursor_changed(self, selection):
149 (model, it) = selection.get_selected()
150 if it is None:
151 current_package = None
152 else:
153 current_package = model.get_value(it, COL_PKG_NAME)
154 self.dep_treeview.set_current_package(current_package)
155 self.revdep_treeview.set_current_package(current_package)
156
157
158 def parse(self, depgraph):
159 for task in depgraph["tdepends"]:
160 self.pkg_model.insert(0, (task,))
161 for depend in depgraph["tdepends"][task]:
162 self.depends_model.insert (0, (TYPE_DEP, task, depend))
163
164
165class gtkthread(threading.Thread):
166 quit = threading.Event()
167 def __init__(self, shutdown):
168 threading.Thread.__init__(self)
169 self.setDaemon(True)
170 self.shutdown = shutdown
171 if not Gtk.init_check()[0]:
172 sys.stderr.write("Gtk+ init failed. Make sure DISPLAY variable is set.\n")
173 gtkthread.quit.set()
174
175 def run(self):
176 GObject.threads_init()
177 Gdk.threads_init()
178 Gtk.main()
179 gtkthread.quit.set()
180
181
182def main(server, eventHandler, params):
183 shutdown = 0
184
185 gtkgui = gtkthread(shutdown)
186 gtkgui.start()
187
188 try:
189 params.updateFromServer(server)
190 cmdline = params.parseActions()
191 if not cmdline:
192 print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
193 return 1
194 if 'msg' in cmdline and cmdline['msg']:
195 print(cmdline['msg'])
196 return 1
197 cmdline = cmdline['action']
198 if not cmdline or cmdline[0] != "generateDotGraph":
199 print("This UI requires the -g option")
200 return 1
201 ret, error = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
202 if error:
203 print("Error running command '%s': %s" % (cmdline, error))
204 return 1
205 elif ret != True:
206 print("Error running command '%s': returned %s" % (cmdline, ret))
207 return 1
208 except client.Fault as x:
209 print("XMLRPC Fault getting commandline:\n %s" % x)
210 return
211
212 if gtkthread.quit.isSet():
213 return
214
215 Gdk.threads_enter()
216 dep = DepExplorer()
217 bardialog = Gtk.Dialog(parent=dep,
218 flags=Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT)
219 bardialog.set_default_size(400, 50)
220 box = bardialog.get_content_area()
221 pbar = Gtk.ProgressBar()
222 box.pack_start(pbar, True, True, 0)
223 bardialog.show_all()
224 bardialog.connect("delete-event", Gtk.main_quit)
225 Gdk.threads_leave()
226
227 progress_total = 0
228 while True:
229 try:
230 event = eventHandler.waitEvent(0.25)
231 if gtkthread.quit.isSet():
232 _, error = server.runCommand(["stateForceShutdown"])
233 if error:
234 print('Unable to cleanly stop: %s' % error)
235 break
236
237 if event is None:
238 continue
239
240 if isinstance(event, bb.event.CacheLoadStarted):
241 progress_total = event.total
242 Gdk.threads_enter()
243 bardialog.set_title("Loading Cache")
244 pbar.set_fraction(0.0)
245 Gdk.threads_leave()
246
247 if isinstance(event, bb.event.CacheLoadProgress):
248 x = event.current
249 Gdk.threads_enter()
250 pbar.set_fraction(x * 1.0 / progress_total)
251 Gdk.threads_leave()
252 continue
253
254 if isinstance(event, bb.event.CacheLoadCompleted):
255 continue
256
257 if isinstance(event, bb.event.ParseStarted):
258 progress_total = event.total
259 if progress_total == 0:
260 continue
261 Gdk.threads_enter()
262 pbar.set_fraction(0.0)
263 bardialog.set_title("Processing recipes")
264 Gdk.threads_leave()
265
266 if isinstance(event, bb.event.ParseProgress):
267 x = event.current
268 Gdk.threads_enter()
269 pbar.set_fraction(x * 1.0 / progress_total)
270 Gdk.threads_leave()
271 continue
272
273 if isinstance(event, bb.event.ParseCompleted):
274 Gdk.threads_enter()
275 bardialog.set_title("Generating dependency tree")
276 Gdk.threads_leave()
277 continue
278
279 if isinstance(event, bb.event.DepTreeGenerated):
280 Gdk.threads_enter()
281 bardialog.hide()
282 dep.parse(event._depgraph)
283 Gdk.threads_leave()
284
285 if isinstance(event, bb.command.CommandCompleted):
286 continue
287
288 if isinstance(event, bb.event.NoProvider):
289 if event._runtime:
290 r = "R"
291 else:
292 r = ""
293
294 extra = ''
295 if not event._reasons:
296 if event._close_matches:
297 extra = ". Close matches:\n %s" % '\n '.join(event._close_matches)
298
299 if event._dependees:
300 print("Nothing %sPROVIDES '%s' (but %s %sDEPENDS on or otherwise requires it)%s" % (r, event._item, ", ".join(event._dependees), r, extra))
301 else:
302 print("Nothing %sPROVIDES '%s'%s" % (r, event._item, extra))
303 if event._reasons:
304 for reason in event._reasons:
305 print(reason)
306
307 _, error = server.runCommand(["stateShutdown"])
308 if error:
309 print('Unable to cleanly shutdown: %s' % error)
310 break
311
312 if isinstance(event, bb.command.CommandFailed):
313 print("Command execution failed: %s" % event.error)
314 return event.exitcode
315
316 if isinstance(event, bb.command.CommandExit):
317 return event.exitcode
318
319 if isinstance(event, bb.cooker.CookerExit):
320 break
321
322 continue
323 except EnvironmentError as ioerror:
324 # ignore interrupted io
325 if ioerror.args[0] == 4:
326 pass
327 except KeyboardInterrupt:
328 if shutdown == 2:
329 print("\nThird Keyboard Interrupt, exit.\n")
330 break
331 if shutdown == 1:
332 print("\nSecond Keyboard Interrupt, stopping...\n")
333 _, error = server.runCommand(["stateForceShutdown"])
334 if error:
335 print('Unable to cleanly stop: %s' % error)
336 if shutdown == 0:
337 print("\nKeyboard Interrupt, closing down...\n")
338 _, error = server.runCommand(["stateShutdown"])
339 if error:
340 print('Unable to cleanly shutdown: %s' % error)
341 shutdown = shutdown + 1
342 pass