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