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