blob: 0e8e9d4cf89cf949e355f4a054facf071e24d7ae [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)
Brad Bishopd7bf8c12018-02-25 22:55:05 -050066 self.sort_model = self.filter_model.sort_new_with_model()
67 self.sort_model.set_sort_column_id(COL_DEP_PARENT, Gtk.SortType.ASCENDING)
68 self.set_model(self.sort_model)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050069 self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PARENT))
70
71 def _filter(self, model, iter, data):
72 package = model[iter][COL_DEP_PACKAGE]
73 return package == self.current
74
75 def set_current_package(self, package):
76 self.current = package
77 self.filter_model.refilter()
78
79
80class DepExplorer(Gtk.Window):
81 def __init__(self):
82 Gtk.Window.__init__(self)
83 self.set_title("Task Dependency Explorer")
84 self.set_default_size(500, 500)
85 self.connect("delete-event", Gtk.main_quit)
86
87 # Create the data models
88 self.pkg_model = Gtk.ListStore(GObject.TYPE_STRING)
89 self.pkg_model.set_sort_column_id(COL_PKG_NAME, Gtk.SortType.ASCENDING)
90 self.depends_model = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_STRING)
91 self.depends_model.set_sort_column_id(COL_DEP_PACKAGE, Gtk.SortType.ASCENDING)
92
93 pane = Gtk.HPaned()
94 pane.set_position(250)
95 self.add(pane)
96
97 # The master list of packages
98 scrolled = Gtk.ScrolledWindow()
99 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
100 scrolled.set_shadow_type(Gtk.ShadowType.IN)
101
102 self.pkg_treeview = Gtk.TreeView(self.pkg_model)
103 self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed)
104 column = Gtk.TreeViewColumn("Package", Gtk.CellRendererText(), text=COL_PKG_NAME)
105 self.pkg_treeview.append_column(column)
106 pane.add1(scrolled)
107 scrolled.add(self.pkg_treeview)
108
109 box = Gtk.VBox(homogeneous=True, spacing=4)
110
111 # Task Depends
112 scrolled = Gtk.ScrolledWindow()
113 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
114 scrolled.set_shadow_type(Gtk.ShadowType.IN)
115 self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Dependencies")
116 self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
117 scrolled.add(self.dep_treeview)
118 box.add(scrolled)
119 pane.add2(box)
120
121 # Reverse Task Depends
122 scrolled = Gtk.ScrolledWindow()
123 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
124 scrolled.set_shadow_type(Gtk.ShadowType.IN)
125 self.revdep_treeview = PackageReverseDepView(self.depends_model, "Dependent Tasks")
126 self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
127 scrolled.add(self.revdep_treeview)
128 box.add(scrolled)
129 pane.add2(box)
130
131 self.show_all()
132
133 def on_package_activated(self, treeview, path, column, data_col):
134 model = treeview.get_model()
135 package = model.get_value(model.get_iter(path), data_col)
136
137 pkg_path = []
138 def finder(model, path, iter, needle):
139 package = model.get_value(iter, COL_PKG_NAME)
140 if package == needle:
141 pkg_path.append(path)
142 return True
143 else:
144 return False
145 self.pkg_model.foreach(finder, package)
146 if pkg_path:
147 self.pkg_treeview.get_selection().select_path(pkg_path[0])
148 self.pkg_treeview.scroll_to_cell(pkg_path[0])
149
150 def on_cursor_changed(self, selection):
151 (model, it) = selection.get_selected()
152 if it is None:
153 current_package = None
154 else:
155 current_package = model.get_value(it, COL_PKG_NAME)
156 self.dep_treeview.set_current_package(current_package)
157 self.revdep_treeview.set_current_package(current_package)
158
159
160 def parse(self, depgraph):
161 for task in depgraph["tdepends"]:
162 self.pkg_model.insert(0, (task,))
163 for depend in depgraph["tdepends"][task]:
164 self.depends_model.insert (0, (TYPE_DEP, task, depend))
165
166
167class gtkthread(threading.Thread):
168 quit = threading.Event()
169 def __init__(self, shutdown):
170 threading.Thread.__init__(self)
171 self.setDaemon(True)
172 self.shutdown = shutdown
173 if not Gtk.init_check()[0]:
174 sys.stderr.write("Gtk+ init failed. Make sure DISPLAY variable is set.\n")
175 gtkthread.quit.set()
176
177 def run(self):
178 GObject.threads_init()
179 Gdk.threads_init()
180 Gtk.main()
181 gtkthread.quit.set()
182
183
184def main(server, eventHandler, params):
185 shutdown = 0
186
187 gtkgui = gtkthread(shutdown)
188 gtkgui.start()
189
190 try:
191 params.updateFromServer(server)
192 cmdline = params.parseActions()
193 if not cmdline:
194 print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
195 return 1
196 if 'msg' in cmdline and cmdline['msg']:
197 print(cmdline['msg'])
198 return 1
199 cmdline = cmdline['action']
200 if not cmdline or cmdline[0] != "generateDotGraph":
201 print("This UI requires the -g option")
202 return 1
203 ret, error = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
204 if error:
205 print("Error running command '%s': %s" % (cmdline, error))
206 return 1
207 elif ret != True:
208 print("Error running command '%s': returned %s" % (cmdline, ret))
209 return 1
210 except client.Fault as x:
211 print("XMLRPC Fault getting commandline:\n %s" % x)
212 return
213
214 if gtkthread.quit.isSet():
215 return
216
217 Gdk.threads_enter()
218 dep = DepExplorer()
219 bardialog = Gtk.Dialog(parent=dep,
220 flags=Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT)
221 bardialog.set_default_size(400, 50)
222 box = bardialog.get_content_area()
223 pbar = Gtk.ProgressBar()
224 box.pack_start(pbar, True, True, 0)
225 bardialog.show_all()
226 bardialog.connect("delete-event", Gtk.main_quit)
227 Gdk.threads_leave()
228
229 progress_total = 0
230 while True:
231 try:
232 event = eventHandler.waitEvent(0.25)
233 if gtkthread.quit.isSet():
234 _, error = server.runCommand(["stateForceShutdown"])
235 if error:
236 print('Unable to cleanly stop: %s' % error)
237 break
238
239 if event is None:
240 continue
241
242 if isinstance(event, bb.event.CacheLoadStarted):
243 progress_total = event.total
244 Gdk.threads_enter()
245 bardialog.set_title("Loading Cache")
246 pbar.set_fraction(0.0)
247 Gdk.threads_leave()
248
249 if isinstance(event, bb.event.CacheLoadProgress):
250 x = event.current
251 Gdk.threads_enter()
252 pbar.set_fraction(x * 1.0 / progress_total)
253 Gdk.threads_leave()
254 continue
255
256 if isinstance(event, bb.event.CacheLoadCompleted):
257 continue
258
259 if isinstance(event, bb.event.ParseStarted):
260 progress_total = event.total
261 if progress_total == 0:
262 continue
263 Gdk.threads_enter()
264 pbar.set_fraction(0.0)
265 bardialog.set_title("Processing recipes")
266 Gdk.threads_leave()
267
268 if isinstance(event, bb.event.ParseProgress):
269 x = event.current
270 Gdk.threads_enter()
271 pbar.set_fraction(x * 1.0 / progress_total)
272 Gdk.threads_leave()
273 continue
274
275 if isinstance(event, bb.event.ParseCompleted):
276 Gdk.threads_enter()
277 bardialog.set_title("Generating dependency tree")
278 Gdk.threads_leave()
279 continue
280
281 if isinstance(event, bb.event.DepTreeGenerated):
282 Gdk.threads_enter()
283 bardialog.hide()
284 dep.parse(event._depgraph)
285 Gdk.threads_leave()
286
287 if isinstance(event, bb.command.CommandCompleted):
288 continue
289
290 if isinstance(event, bb.event.NoProvider):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500291 print(str(event))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500292
293 _, error = server.runCommand(["stateShutdown"])
294 if error:
295 print('Unable to cleanly shutdown: %s' % error)
296 break
297
298 if isinstance(event, bb.command.CommandFailed):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500299 print(str(event))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500300 return event.exitcode
301
302 if isinstance(event, bb.command.CommandExit):
303 return event.exitcode
304
305 if isinstance(event, bb.cooker.CookerExit):
306 break
307
308 continue
309 except EnvironmentError as ioerror:
310 # ignore interrupted io
311 if ioerror.args[0] == 4:
312 pass
313 except KeyboardInterrupt:
314 if shutdown == 2:
315 print("\nThird Keyboard Interrupt, exit.\n")
316 break
317 if shutdown == 1:
318 print("\nSecond Keyboard Interrupt, stopping...\n")
319 _, error = server.runCommand(["stateForceShutdown"])
320 if error:
321 print('Unable to cleanly stop: %s' % error)
322 if shutdown == 0:
323 print("\nKeyboard Interrupt, closing down...\n")
324 _, error = server.runCommand(["stateShutdown"])
325 if error:
326 print('Unable to cleanly shutdown: %s' % error)
327 shutdown = shutdown + 1
328 pass