blob: 2b246710caeeb5ec1c8881fd69eff8425be15341 [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#
Brad Bishopc342db32019-05-15 21:57:59 -04007# SPDX-License-Identifier: GPL-2.0-only
Brad Bishop6e60e8b2018-02-01 10:27:11 -05008#
Brad Bishop6e60e8b2018-02-01 10:27:11 -05009
10import sys
Andrew Geissler475cb722020-07-10 16:00:51 -050011
12try:
13 import gi
14 gi.require_version('Gtk', '3.0')
15 from gi.repository import Gtk, Gdk, GObject
16except ValueError:
17 sys.exit("FATAL: Gtk version needs to be 3.0")
18except ImportError:
19 sys.exit("FATAL: Gtk ui could not load the required gi python module")
20
Brad Bishop6e60e8b2018-02-01 10:27:11 -050021import threading
22from xmlrpc import client
Brad Bishop6e60e8b2018-02-01 10:27:11 -050023import bb
24import bb.event
25
26# Package Model
27(COL_PKG_NAME) = (0)
28
29# Dependency Model
30(TYPE_DEP, TYPE_RDEP) = (0, 1)
31(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2)
32
33
34class PackageDepView(Gtk.TreeView):
35 def __init__(self, model, dep_type, label):
36 Gtk.TreeView.__init__(self)
37 self.current = None
38 self.dep_type = dep_type
39 self.filter_model = model.filter_new()
40 self.filter_model.set_visible_func(self._filter, data=None)
41 self.set_model(self.filter_model)
42 self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PACKAGE))
43
44 def _filter(self, model, iter, data):
45 this_type = model[iter][COL_DEP_TYPE]
46 package = model[iter][COL_DEP_PARENT]
47 if this_type != self.dep_type: return False
48 return package == self.current
49
50 def set_current_package(self, package):
51 self.current = package
52 self.filter_model.refilter()
53
54
55class PackageReverseDepView(Gtk.TreeView):
56 def __init__(self, model, label):
57 Gtk.TreeView.__init__(self)
58 self.current = None
59 self.filter_model = model.filter_new()
60 self.filter_model.set_visible_func(self._filter)
Andrew Geisslerc3d88e42020-10-02 09:45:00 -050061 # The introspected API was fixed but we can't rely on a pygobject that hides this.
62 # https://gitlab.gnome.org/GNOME/pygobject/-/commit/9cdbc56fbac4db2de78dc080934b8f0a7efc892a
63 if hasattr(Gtk.TreeModelSort, "new_with_model"):
64 self.sort_model = Gtk.TreeModelSort.new_with_model(self.filter_model)
65 else:
66 self.sort_model = self.filter_model.sort_new_with_model()
Brad Bishopd7bf8c12018-02-25 22:55:05 -050067 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)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500106 scrolled.add(self.pkg_treeview)
107
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800108 self.search_entry = Gtk.SearchEntry.new()
109 self.pkg_treeview.set_search_entry(self.search_entry)
110
111 left_panel = Gtk.VPaned()
112 left_panel.add(self.search_entry)
113 left_panel.add(scrolled)
114 pane.add1(left_panel)
115
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500116 box = Gtk.VBox(homogeneous=True, spacing=4)
117
118 # Task Depends
119 scrolled = Gtk.ScrolledWindow()
120 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
121 scrolled.set_shadow_type(Gtk.ShadowType.IN)
122 self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Dependencies")
123 self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
124 scrolled.add(self.dep_treeview)
125 box.add(scrolled)
126 pane.add2(box)
127
128 # Reverse Task Depends
129 scrolled = Gtk.ScrolledWindow()
130 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
131 scrolled.set_shadow_type(Gtk.ShadowType.IN)
132 self.revdep_treeview = PackageReverseDepView(self.depends_model, "Dependent Tasks")
133 self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
134 scrolled.add(self.revdep_treeview)
135 box.add(scrolled)
136 pane.add2(box)
137
138 self.show_all()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800139 self.search_entry.grab_focus()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500140
141 def on_package_activated(self, treeview, path, column, data_col):
142 model = treeview.get_model()
143 package = model.get_value(model.get_iter(path), data_col)
144
145 pkg_path = []
146 def finder(model, path, iter, needle):
147 package = model.get_value(iter, COL_PKG_NAME)
148 if package == needle:
149 pkg_path.append(path)
150 return True
151 else:
152 return False
153 self.pkg_model.foreach(finder, package)
154 if pkg_path:
155 self.pkg_treeview.get_selection().select_path(pkg_path[0])
156 self.pkg_treeview.scroll_to_cell(pkg_path[0])
157
158 def on_cursor_changed(self, selection):
159 (model, it) = selection.get_selected()
160 if it is None:
161 current_package = None
162 else:
163 current_package = model.get_value(it, COL_PKG_NAME)
164 self.dep_treeview.set_current_package(current_package)
165 self.revdep_treeview.set_current_package(current_package)
166
167
168 def parse(self, depgraph):
169 for task in depgraph["tdepends"]:
170 self.pkg_model.insert(0, (task,))
171 for depend in depgraph["tdepends"][task]:
172 self.depends_model.insert (0, (TYPE_DEP, task, depend))
173
174
175class gtkthread(threading.Thread):
176 quit = threading.Event()
177 def __init__(self, shutdown):
178 threading.Thread.__init__(self)
179 self.setDaemon(True)
180 self.shutdown = shutdown
181 if not Gtk.init_check()[0]:
182 sys.stderr.write("Gtk+ init failed. Make sure DISPLAY variable is set.\n")
183 gtkthread.quit.set()
184
185 def run(self):
186 GObject.threads_init()
187 Gdk.threads_init()
188 Gtk.main()
189 gtkthread.quit.set()
190
191
192def main(server, eventHandler, params):
193 shutdown = 0
194
195 gtkgui = gtkthread(shutdown)
196 gtkgui.start()
197
198 try:
199 params.updateFromServer(server)
200 cmdline = params.parseActions()
201 if not cmdline:
202 print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
203 return 1
204 if 'msg' in cmdline and cmdline['msg']:
205 print(cmdline['msg'])
206 return 1
207 cmdline = cmdline['action']
208 if not cmdline or cmdline[0] != "generateDotGraph":
209 print("This UI requires the -g option")
210 return 1
211 ret, error = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
212 if error:
213 print("Error running command '%s': %s" % (cmdline, error))
214 return 1
Andrew Geissler82c905d2020-04-13 13:39:40 -0500215 elif not ret:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500216 print("Error running command '%s': returned %s" % (cmdline, ret))
217 return 1
218 except client.Fault as x:
219 print("XMLRPC Fault getting commandline:\n %s" % x)
220 return
221
222 if gtkthread.quit.isSet():
223 return
224
225 Gdk.threads_enter()
226 dep = DepExplorer()
227 bardialog = Gtk.Dialog(parent=dep,
228 flags=Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT)
229 bardialog.set_default_size(400, 50)
230 box = bardialog.get_content_area()
231 pbar = Gtk.ProgressBar()
232 box.pack_start(pbar, True, True, 0)
233 bardialog.show_all()
234 bardialog.connect("delete-event", Gtk.main_quit)
235 Gdk.threads_leave()
236
237 progress_total = 0
238 while True:
239 try:
240 event = eventHandler.waitEvent(0.25)
241 if gtkthread.quit.isSet():
242 _, error = server.runCommand(["stateForceShutdown"])
243 if error:
244 print('Unable to cleanly stop: %s' % error)
245 break
246
247 if event is None:
248 continue
249
250 if isinstance(event, bb.event.CacheLoadStarted):
251 progress_total = event.total
252 Gdk.threads_enter()
253 bardialog.set_title("Loading Cache")
254 pbar.set_fraction(0.0)
255 Gdk.threads_leave()
256
257 if isinstance(event, bb.event.CacheLoadProgress):
258 x = event.current
259 Gdk.threads_enter()
260 pbar.set_fraction(x * 1.0 / progress_total)
261 Gdk.threads_leave()
262 continue
263
264 if isinstance(event, bb.event.CacheLoadCompleted):
265 continue
266
267 if isinstance(event, bb.event.ParseStarted):
268 progress_total = event.total
269 if progress_total == 0:
270 continue
271 Gdk.threads_enter()
272 pbar.set_fraction(0.0)
273 bardialog.set_title("Processing recipes")
274 Gdk.threads_leave()
275
276 if isinstance(event, bb.event.ParseProgress):
277 x = event.current
278 Gdk.threads_enter()
279 pbar.set_fraction(x * 1.0 / progress_total)
280 Gdk.threads_leave()
281 continue
282
283 if isinstance(event, bb.event.ParseCompleted):
284 Gdk.threads_enter()
285 bardialog.set_title("Generating dependency tree")
286 Gdk.threads_leave()
287 continue
288
289 if isinstance(event, bb.event.DepTreeGenerated):
290 Gdk.threads_enter()
291 bardialog.hide()
292 dep.parse(event._depgraph)
293 Gdk.threads_leave()
294
295 if isinstance(event, bb.command.CommandCompleted):
296 continue
297
298 if isinstance(event, bb.event.NoProvider):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500299 print(str(event))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500300
301 _, error = server.runCommand(["stateShutdown"])
302 if error:
303 print('Unable to cleanly shutdown: %s' % error)
304 break
305
306 if isinstance(event, bb.command.CommandFailed):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500307 print(str(event))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500308 return event.exitcode
309
310 if isinstance(event, bb.command.CommandExit):
311 return event.exitcode
312
313 if isinstance(event, bb.cooker.CookerExit):
314 break
315
316 continue
317 except EnvironmentError as ioerror:
318 # ignore interrupted io
319 if ioerror.args[0] == 4:
320 pass
321 except KeyboardInterrupt:
322 if shutdown == 2:
323 print("\nThird Keyboard Interrupt, exit.\n")
324 break
325 if shutdown == 1:
326 print("\nSecond Keyboard Interrupt, stopping...\n")
327 _, error = server.runCommand(["stateForceShutdown"])
328 if error:
329 print('Unable to cleanly stop: %s' % error)
330 if shutdown == 0:
331 print("\nKeyboard Interrupt, closing down...\n")
332 _, error = server.runCommand(["stateShutdown"])
333 if error:
334 print('Unable to cleanly shutdown: %s' % error)
335 shutdown = shutdown + 1
336 pass