blob: 50a943cd050a7ca7a5a38f9f873b192a98556517 [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
11import gi
12gi.require_version('Gtk', '3.0')
13from gi.repository import Gtk, Gdk, GObject
14from multiprocessing import Queue
15import threading
16from xmlrpc import client
17import time
18import bb
19import bb.event
20
21# Package Model
22(COL_PKG_NAME) = (0)
23
24# Dependency Model
25(TYPE_DEP, TYPE_RDEP) = (0, 1)
26(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2)
27
28
29class PackageDepView(Gtk.TreeView):
30 def __init__(self, model, dep_type, label):
31 Gtk.TreeView.__init__(self)
32 self.current = None
33 self.dep_type = dep_type
34 self.filter_model = model.filter_new()
35 self.filter_model.set_visible_func(self._filter, data=None)
36 self.set_model(self.filter_model)
37 self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PACKAGE))
38
39 def _filter(self, model, iter, data):
40 this_type = model[iter][COL_DEP_TYPE]
41 package = model[iter][COL_DEP_PARENT]
42 if this_type != self.dep_type: return False
43 return package == self.current
44
45 def set_current_package(self, package):
46 self.current = package
47 self.filter_model.refilter()
48
49
50class PackageReverseDepView(Gtk.TreeView):
51 def __init__(self, model, label):
52 Gtk.TreeView.__init__(self)
53 self.current = None
54 self.filter_model = model.filter_new()
55 self.filter_model.set_visible_func(self._filter)
Brad Bishopd7bf8c12018-02-25 22:55:05 -050056 self.sort_model = self.filter_model.sort_new_with_model()
57 self.sort_model.set_sort_column_id(COL_DEP_PARENT, Gtk.SortType.ASCENDING)
58 self.set_model(self.sort_model)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050059 self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PARENT))
60
61 def _filter(self, model, iter, data):
62 package = model[iter][COL_DEP_PACKAGE]
63 return package == self.current
64
65 def set_current_package(self, package):
66 self.current = package
67 self.filter_model.refilter()
68
69
70class DepExplorer(Gtk.Window):
71 def __init__(self):
72 Gtk.Window.__init__(self)
73 self.set_title("Task Dependency Explorer")
74 self.set_default_size(500, 500)
75 self.connect("delete-event", Gtk.main_quit)
76
77 # Create the data models
78 self.pkg_model = Gtk.ListStore(GObject.TYPE_STRING)
79 self.pkg_model.set_sort_column_id(COL_PKG_NAME, Gtk.SortType.ASCENDING)
80 self.depends_model = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_STRING)
81 self.depends_model.set_sort_column_id(COL_DEP_PACKAGE, Gtk.SortType.ASCENDING)
82
83 pane = Gtk.HPaned()
84 pane.set_position(250)
85 self.add(pane)
86
87 # The master list of packages
88 scrolled = Gtk.ScrolledWindow()
89 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
90 scrolled.set_shadow_type(Gtk.ShadowType.IN)
91
92 self.pkg_treeview = Gtk.TreeView(self.pkg_model)
93 self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed)
94 column = Gtk.TreeViewColumn("Package", Gtk.CellRendererText(), text=COL_PKG_NAME)
95 self.pkg_treeview.append_column(column)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050096 scrolled.add(self.pkg_treeview)
97
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080098 self.search_entry = Gtk.SearchEntry.new()
99 self.pkg_treeview.set_search_entry(self.search_entry)
100
101 left_panel = Gtk.VPaned()
102 left_panel.add(self.search_entry)
103 left_panel.add(scrolled)
104 pane.add1(left_panel)
105
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500106 box = Gtk.VBox(homogeneous=True, spacing=4)
107
108 # Task Depends
109 scrolled = Gtk.ScrolledWindow()
110 scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
111 scrolled.set_shadow_type(Gtk.ShadowType.IN)
112 self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Dependencies")
113 self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
114 scrolled.add(self.dep_treeview)
115 box.add(scrolled)
116 pane.add2(box)
117
118 # Reverse 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.revdep_treeview = PackageReverseDepView(self.depends_model, "Dependent Tasks")
123 self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
124 scrolled.add(self.revdep_treeview)
125 box.add(scrolled)
126 pane.add2(box)
127
128 self.show_all()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800129 self.search_entry.grab_focus()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500130
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):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500289 print(str(event))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500290
291 _, error = server.runCommand(["stateShutdown"])
292 if error:
293 print('Unable to cleanly shutdown: %s' % error)
294 break
295
296 if isinstance(event, bb.command.CommandFailed):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500297 print(str(event))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500298 return event.exitcode
299
300 if isinstance(event, bb.command.CommandExit):
301 return event.exitcode
302
303 if isinstance(event, bb.cooker.CookerExit):
304 break
305
306 continue
307 except EnvironmentError as ioerror:
308 # ignore interrupted io
309 if ioerror.args[0] == 4:
310 pass
311 except KeyboardInterrupt:
312 if shutdown == 2:
313 print("\nThird Keyboard Interrupt, exit.\n")
314 break
315 if shutdown == 1:
316 print("\nSecond Keyboard Interrupt, stopping...\n")
317 _, error = server.runCommand(["stateForceShutdown"])
318 if error:
319 print('Unable to cleanly stop: %s' % error)
320 if shutdown == 0:
321 print("\nKeyboard Interrupt, closing down...\n")
322 _, error = server.runCommand(["stateShutdown"])
323 if error:
324 print('Unable to cleanly shutdown: %s' % error)
325 shutdown = shutdown + 1
326 pass