|
@@ -0,0 +1,1511 @@
|
|
|
+#
|
|
|
+# BitBake Graphical ncurses-based Dependency Explorer
|
|
|
+# * Based on the GTK implementation
|
|
|
+# * Intended to run on any Linux host
|
|
|
+#
|
|
|
+# Copyright (C) 2007 Ross Burton
|
|
|
+# Copyright (C) 2007 - 2008 Richard Purdie
|
|
|
+# Copyright (C) 2022 - 2024 David Reyna
|
|
|
+#
|
|
|
+# SPDX-License-Identifier: GPL-2.0-only
|
|
|
+#
|
|
|
+
|
|
|
+#
|
|
|
+# Execution example:
|
|
|
+# $ bitbake -g -u taskexp_ncurses.py acl zlib
|
|
|
+#
|
|
|
+# Self-test example (executes a script of GUI actions):
|
|
|
+# $ TASK_EXP_UNIT_TEST=1 bitbake -g -u taskexp_ncurses zlib acl
|
|
|
+# ...
|
|
|
+# $ echo $?
|
|
|
+# 0
|
|
|
+# $ TASK_EXP_UNIT_TEST=1 bitbake -g -u taskexp_ncurses zlib acl foo
|
|
|
+# ERROR: Nothing PROVIDES 'foo'. Close matches:
|
|
|
+# ofono
|
|
|
+# $ echo $?
|
|
|
+# 1
|
|
|
+#
|
|
|
+# Self-test with no terminal example (only tests dependency fetch from bitbake):
|
|
|
+# $ TASK_EXP_UNIT_TEST_NOTERM=1 bitbake -g -u taskexp_ncurses quilt
|
|
|
+# $ echo $?
|
|
|
+# 0
|
|
|
+#
|
|
|
+# Features:
|
|
|
+# * Ncurses is used for the presentation layer. Only the 'curses'
|
|
|
+# library is used (none of the extension libraries), plus only
|
|
|
+# one main screen is used (no sub-windows)
|
|
|
+# * Uses the 'generateDepTreeEvent' bitbake event to fetch the
|
|
|
+# dynamic dependency data based on passed recipes
|
|
|
+# * Computes and provides reverse dependencies
|
|
|
+# * Supports task sorting on:
|
|
|
+# (a) Task dependency order within each recipe
|
|
|
+# (b) Pure alphabetical order
|
|
|
+# (c) Provisions for third sort order (bitbake order?)
|
|
|
+# * The 'Filter' does a "*string*" wildcard filter on tasks in the
|
|
|
+# main window, dynamically re-ordering and re-centering the content
|
|
|
+# * A 'Print' function exports the selected task or its whole recipe
|
|
|
+# task set to the default file "taskdep.txt"
|
|
|
+# * Supports a progress bar for bitbake loads and file printing
|
|
|
+# * Line art for box drawing supported, ASCII art an alernative
|
|
|
+# * No horizontal scrolling support. Selected task's full name
|
|
|
+# shown in bottom bar
|
|
|
+# * Dynamically catches terminals that are (or become) too small
|
|
|
+# * Exception to insure return to normal terminal on errors
|
|
|
+# * Debugging support, self test option
|
|
|
+#
|
|
|
+
|
|
|
+import sys
|
|
|
+import traceback
|
|
|
+import curses
|
|
|
+import re
|
|
|
+import time
|
|
|
+
|
|
|
+# Bitbake server support
|
|
|
+import threading
|
|
|
+from xmlrpc import client
|
|
|
+import bb
|
|
|
+import bb.event
|
|
|
+
|
|
|
+# Dependency indexes (depends_model)
|
|
|
+(TYPE_DEP, TYPE_RDEP) = (0, 1)
|
|
|
+DEPENDS_TYPE = 0
|
|
|
+DEPENDS_TASK = 1
|
|
|
+DEPENDS_DEPS = 2
|
|
|
+# Task indexes (task_list)
|
|
|
+TASK_NAME = 0
|
|
|
+TASK_PRIMARY = 1
|
|
|
+TASK_SORT_ALPHA = 2
|
|
|
+TASK_SORT_DEPS = 3
|
|
|
+TASK_SORT_BITBAKE = 4
|
|
|
+# Sort options (default is SORT_DEPS)
|
|
|
+SORT_ALPHA = 0
|
|
|
+SORT_DEPS = 1
|
|
|
+SORT_BITBAKE_ENABLE = False # NOTE: future sort
|
|
|
+SORT_BITBAKE = 2
|
|
|
+sort_model = SORT_DEPS
|
|
|
+# Print options
|
|
|
+PRINT_MODEL_1 = 0
|
|
|
+PRINT_MODEL_2 = 1
|
|
|
+print_model = PRINT_MODEL_2
|
|
|
+print_file_name = "taskdep_print.log"
|
|
|
+print_file_backup_name = "taskdep_print_backup.log"
|
|
|
+is_printed = False
|
|
|
+is_filter = False
|
|
|
+
|
|
|
+# Standard (and backup) key mappings
|
|
|
+CHAR_NUL = 0 # Used as self-test nop char
|
|
|
+CHAR_BS_H = 8 # Alternate backspace key
|
|
|
+CHAR_TAB = 9
|
|
|
+CHAR_RETURN = 10
|
|
|
+CHAR_ESCAPE = 27
|
|
|
+CHAR_UP = ord('{') # Used as self-test ASCII char
|
|
|
+CHAR_DOWN = ord('}') # Used as self-test ASCII char
|
|
|
+
|
|
|
+# Color_pair IDs
|
|
|
+CURSES_NORMAL = 0
|
|
|
+CURSES_HIGHLIGHT = 1
|
|
|
+CURSES_WARNING = 2
|
|
|
+
|
|
|
+
|
|
|
+#################################################
|
|
|
+### Debugging support
|
|
|
+###
|
|
|
+
|
|
|
+verbose = False
|
|
|
+
|
|
|
+# Debug: message display slow-step through display update issues
|
|
|
+def alert(msg,screen):
|
|
|
+ if msg:
|
|
|
+ screen.addstr(0, 10, '[%-4s]' % msg)
|
|
|
+ screen.refresh();
|
|
|
+ curses.napms(2000)
|
|
|
+ else:
|
|
|
+ if do_line_art:
|
|
|
+ for i in range(10, 24):
|
|
|
+ screen.addch(0, i, curses.ACS_HLINE)
|
|
|
+ else:
|
|
|
+ screen.addstr(0, 10, '-' * 14)
|
|
|
+ screen.refresh();
|
|
|
+
|
|
|
+# Debug: display edge conditions on frame movements
|
|
|
+def debug_frame(nbox_ojb):
|
|
|
+ if verbose:
|
|
|
+ nbox_ojb.screen.addstr(0, 50, '[I=%2d,O=%2d,S=%3s,H=%2d,M=%4d]' % (
|
|
|
+ nbox_ojb.cursor_index,
|
|
|
+ nbox_ojb.cursor_offset,
|
|
|
+ nbox_ojb.scroll_offset,
|
|
|
+ nbox_ojb.inside_height,
|
|
|
+ len(nbox_ojb.task_list),
|
|
|
+ ))
|
|
|
+ nbox_ojb.screen.refresh();
|
|
|
+
|
|
|
+#
|
|
|
+# Unit test (assumes that 'quilt-native' is always present)
|
|
|
+#
|
|
|
+
|
|
|
+unit_test = os.environ.get('TASK_EXP_UNIT_TEST')
|
|
|
+unit_test_cmnds=[
|
|
|
+ '# Default selected task in primary box',
|
|
|
+ 'tst_selected=<TASK>.do_recipe_qa',
|
|
|
+ '# Default selected task in deps',
|
|
|
+ 'tst_entry=<TAB>',
|
|
|
+ 'tst_selected=',
|
|
|
+ '# Default selected task in rdeps',
|
|
|
+ 'tst_entry=<TAB>',
|
|
|
+ 'tst_selected=<TASK>.do_fetch',
|
|
|
+ "# Test 'select' back to primary box",
|
|
|
+ 'tst_entry=<CR>',
|
|
|
+ '#tst_entry=<DOWN>', # optional injected error
|
|
|
+ 'tst_selected=<TASK>.do_fetch',
|
|
|
+ '# Check filter',
|
|
|
+ 'tst_entry=/uilt-nativ/',
|
|
|
+ 'tst_selected=quilt-native.do_recipe_qa',
|
|
|
+ '# Check print',
|
|
|
+ 'tst_entry=p',
|
|
|
+ 'tst_printed=quilt-native.do_fetch',
|
|
|
+ '#tst_printed=quilt-foo.do_nothing', # optional injected error
|
|
|
+ '# Done!',
|
|
|
+ 'tst_entry=q',
|
|
|
+]
|
|
|
+unit_test_idx=0
|
|
|
+unit_test_command_chars=''
|
|
|
+unit_test_results=[]
|
|
|
+def unit_test_action(active_package):
|
|
|
+ global unit_test_idx
|
|
|
+ global unit_test_command_chars
|
|
|
+ global unit_test_results
|
|
|
+ ret = CHAR_NUL
|
|
|
+ if unit_test_command_chars:
|
|
|
+ ch = unit_test_command_chars[0]
|
|
|
+ unit_test_command_chars = unit_test_command_chars[1:]
|
|
|
+ time.sleep(0.5)
|
|
|
+ ret = ord(ch)
|
|
|
+ else:
|
|
|
+ line = unit_test_cmnds[unit_test_idx]
|
|
|
+ unit_test_idx += 1
|
|
|
+ line = re.sub('#.*', '', line).strip()
|
|
|
+ line = line.replace('<TASK>',active_package.primary[0])
|
|
|
+ line = line.replace('<TAB>','\t').replace('<CR>','\n')
|
|
|
+ line = line.replace('<UP>','{').replace('<DOWN>','}')
|
|
|
+ if not line: line = 'nop=nop'
|
|
|
+ cmnd,value = line.split('=')
|
|
|
+ if cmnd == 'tst_entry':
|
|
|
+ unit_test_command_chars = value
|
|
|
+ elif cmnd == 'tst_selected':
|
|
|
+ active_selected = active_package.get_selected()
|
|
|
+ if active_selected != value:
|
|
|
+ unit_test_results.append("ERROR:SELFTEST:expected '%s' but got '%s' (NOTE:bitbake may have changed)" % (value,active_selected))
|
|
|
+ ret = ord('Q')
|
|
|
+ else:
|
|
|
+ unit_test_results.append("Pass:SELFTEST:found '%s'" % (value))
|
|
|
+ elif cmnd == 'tst_printed':
|
|
|
+ result = os.system('grep %s %s' % (value,print_file_name))
|
|
|
+ if result:
|
|
|
+ unit_test_results.append("ERROR:PRINTTEST:expected '%s' in '%s'" % (value,print_file_name))
|
|
|
+ ret = ord('Q')
|
|
|
+ else:
|
|
|
+ unit_test_results.append("Pass:PRINTTEST:found '%s'" % (value))
|
|
|
+ # Return the action (CHAR_NUL for no action til next round)
|
|
|
+ return(ret)
|
|
|
+
|
|
|
+# Unit test without an interative terminal (e.g. ptest)
|
|
|
+unit_test_noterm = os.environ.get('TASK_EXP_UNIT_TEST_NOTERM')
|
|
|
+
|
|
|
+
|
|
|
+#################################################
|
|
|
+### Window frame rendering
|
|
|
+###
|
|
|
+### By default, use the normal line art. Since
|
|
|
+### these extended characters are not ASCII, one
|
|
|
+### must use the ncursus API to render them
|
|
|
+### The alternate ASCII line art set is optionally
|
|
|
+### available via the 'do_line_art' flag
|
|
|
+
|
|
|
+# By default, render frames using line art
|
|
|
+do_line_art = True
|
|
|
+
|
|
|
+# ASCII render set option
|
|
|
+CHAR_HBAR = '-'
|
|
|
+CHAR_VBAR = '|'
|
|
|
+CHAR_UL_CORNER = '/'
|
|
|
+CHAR_UR_CORNER = '\\'
|
|
|
+CHAR_LL_CORNER = '\\'
|
|
|
+CHAR_LR_CORNER = '/'
|
|
|
+
|
|
|
+# Box frame drawing with line-art
|
|
|
+def line_art_frame(box):
|
|
|
+ x = box.base_x
|
|
|
+ y = box.base_y
|
|
|
+ w = box.width
|
|
|
+ h = box.height + 1
|
|
|
+
|
|
|
+ if do_line_art:
|
|
|
+ for i in range(1, w - 1):
|
|
|
+ box.screen.addch(y, x + i, curses.ACS_HLINE, box.color)
|
|
|
+ box.screen.addch(y + h - 1, x + i, curses.ACS_HLINE, box.color)
|
|
|
+ body_line = "%s" % (' ' * (w - 2))
|
|
|
+ for i in range(1, h - 1):
|
|
|
+ box.screen.addch(y + i, x, curses.ACS_VLINE, box.color)
|
|
|
+ box.screen.addstr(y + i, x + 1, body_line, box.color)
|
|
|
+ box.screen.addch(y + i, x + w - 1, curses.ACS_VLINE, box.color)
|
|
|
+ box.screen.addch(y, x, curses.ACS_ULCORNER, box.color)
|
|
|
+ box.screen.addch(y, x + w - 1, curses.ACS_URCORNER, box.color)
|
|
|
+ box.screen.addch(y + h - 1, x, curses.ACS_LLCORNER, box.color)
|
|
|
+ box.screen.addch(y + h - 1, x + w - 1, curses.ACS_LRCORNER, box.color)
|
|
|
+ else:
|
|
|
+ top_line = "%s%s%s" % (CHAR_UL_CORNER,CHAR_HBAR * (w - 2),CHAR_UR_CORNER)
|
|
|
+ body_line = "%s%s%s" % (CHAR_VBAR,' ' * (w - 2),CHAR_VBAR)
|
|
|
+ bot_line = "%s%s%s" % (CHAR_UR_CORNER,CHAR_HBAR * (w - 2),CHAR_UL_CORNER)
|
|
|
+ tag_line = "%s%s%s" % ('[',CHAR_HBAR * (w - 2),']')
|
|
|
+ # Top bar
|
|
|
+ box.screen.addstr(y, x, top_line)
|
|
|
+ # Middle frame
|
|
|
+ for i in range(1, (h - 1)):
|
|
|
+ box.screen.addstr(y+i, x, body_line)
|
|
|
+ # Bottom bar
|
|
|
+ box.screen.addstr(y + (h - 1), x, bot_line)
|
|
|
+
|
|
|
+# Connect the separate boxes
|
|
|
+def line_art_fixup(box):
|
|
|
+ if do_line_art:
|
|
|
+ box.screen.addch(box.base_y+2, box.base_x, curses.ACS_LTEE, box.color)
|
|
|
+ box.screen.addch(box.base_y+2, box.base_x+box.width-1, curses.ACS_RTEE, box.color)
|
|
|
+
|
|
|
+
|
|
|
+#################################################
|
|
|
+### Ncurses box object : box frame object to display
|
|
|
+### and manage a sub-window's display elements
|
|
|
+### using basic ncurses
|
|
|
+###
|
|
|
+### Supports:
|
|
|
+### * Frame drawing, content (re)drawing
|
|
|
+### * Content scrolling via ArrowUp, ArrowDn, PgUp, PgDN,
|
|
|
+### * Highlighting for active selected item
|
|
|
+### * Content sorting based on selected sort model
|
|
|
+###
|
|
|
+
|
|
|
+class NBox():
|
|
|
+ def __init__(self, screen, label, primary, base_x, base_y, width, height):
|
|
|
+ # Box description
|
|
|
+ self.screen = screen
|
|
|
+ self.label = label
|
|
|
+ self.primary = primary
|
|
|
+ self.color = curses.color_pair(CURSES_NORMAL) if screen else None
|
|
|
+ # Box boundaries
|
|
|
+ self.base_x = base_x
|
|
|
+ self.base_y = base_y
|
|
|
+ self.width = width
|
|
|
+ self.height = height
|
|
|
+ # Cursor/scroll management
|
|
|
+ self.cursor_enable = False
|
|
|
+ self.cursor_index = 0 # Absolute offset
|
|
|
+ self.cursor_offset = 0 # Frame centric offset
|
|
|
+ self.scroll_offset = 0 # Frame centric offset
|
|
|
+ # Box specific content
|
|
|
+ # Format of each entry is [package_name,is_primary_recipe,alpha_sort_key,deps_sort_key]
|
|
|
+ self.task_list = []
|
|
|
+
|
|
|
+ @property
|
|
|
+ def inside_width(self):
|
|
|
+ return(self.width-2)
|
|
|
+
|
|
|
+ @property
|
|
|
+ def inside_height(self):
|
|
|
+ return(self.height-2)
|
|
|
+
|
|
|
+ # Populate the box's content, include the sort mappings and is_primary flag
|
|
|
+ def task_list_append(self,task_name,dep):
|
|
|
+ task_sort_alpha = task_name
|
|
|
+ task_sort_deps = dep.get_dep_sort(task_name)
|
|
|
+ is_primary = False
|
|
|
+ for primary in self.primary:
|
|
|
+ if task_name.startswith(primary+'.'):
|
|
|
+ is_primary = True
|
|
|
+ if SORT_BITBAKE_ENABLE:
|
|
|
+ task_sort_bitbake = dep.get_bb_sort(task_name)
|
|
|
+ self.task_list.append([task_name,is_primary,task_sort_alpha,task_sort_deps,task_sort_bitbake])
|
|
|
+ else:
|
|
|
+ self.task_list.append([task_name,is_primary,task_sort_alpha,task_sort_deps])
|
|
|
+
|
|
|
+ def reset(self):
|
|
|
+ self.task_list = []
|
|
|
+ self.cursor_index = 0 # Absolute offset
|
|
|
+ self.cursor_offset = 0 # Frame centric offset
|
|
|
+ self.scroll_offset = 0 # Frame centric offset
|
|
|
+
|
|
|
+ # Sort the box's content based on the current sort model
|
|
|
+ def sort(self):
|
|
|
+ if SORT_ALPHA == sort_model:
|
|
|
+ self.task_list.sort(key = lambda x: x[TASK_SORT_ALPHA])
|
|
|
+ elif SORT_DEPS == sort_model:
|
|
|
+ self.task_list.sort(key = lambda x: x[TASK_SORT_DEPS])
|
|
|
+ elif SORT_BITBAKE == sort_model:
|
|
|
+ self.task_list.sort(key = lambda x: x[TASK_SORT_BITBAKE])
|
|
|
+
|
|
|
+ # The target package list (to hightlight), from the command line
|
|
|
+ def set_primary(self,primary):
|
|
|
+ self.primary = primary
|
|
|
+
|
|
|
+ # Draw the box's outside frame
|
|
|
+ def draw_frame(self):
|
|
|
+ line_art_frame(self)
|
|
|
+ # Title
|
|
|
+ self.screen.addstr(self.base_y,
|
|
|
+ (self.base_x + (self.width//2))-((len(self.label)+2)//2),
|
|
|
+ '['+self.label+']')
|
|
|
+ self.screen.refresh()
|
|
|
+
|
|
|
+ # Draw the box's inside text content
|
|
|
+ def redraw(self):
|
|
|
+ task_list_len = len(self.task_list)
|
|
|
+ # Middle frame
|
|
|
+ body_line = "%s" % (' ' * (self.inside_width-1) )
|
|
|
+ for i in range(0,self.inside_height+1):
|
|
|
+ if i < (task_list_len + self.scroll_offset):
|
|
|
+ str_ctl = "%%-%ss" % (self.width-3)
|
|
|
+ # Safety assert
|
|
|
+ if (i + self.scroll_offset) >= task_list_len:
|
|
|
+ alert("REDRAW:%2d,%4d,%4d" % (i,self.scroll_offset,task_list_len),self.screen)
|
|
|
+ break
|
|
|
+
|
|
|
+ task_obj = self.task_list[i + self.scroll_offset]
|
|
|
+ task = task_obj[TASK_NAME][:self.inside_width-1]
|
|
|
+ task_primary = task_obj[TASK_PRIMARY]
|
|
|
+
|
|
|
+ if task_primary:
|
|
|
+ line = str_ctl % task[:self.inside_width-1]
|
|
|
+ self.screen.addstr(self.base_y+1+i, self.base_x+2, line, curses.A_BOLD)
|
|
|
+ else:
|
|
|
+ line = str_ctl % task[:self.inside_width-1]
|
|
|
+ self.screen.addstr(self.base_y+1+i, self.base_x+2, line)
|
|
|
+ else:
|
|
|
+ line = "%s" % (' ' * (self.inside_width-1) )
|
|
|
+ self.screen.addstr(self.base_y+1+i, self.base_x+2, line)
|
|
|
+ self.screen.refresh()
|
|
|
+
|
|
|
+ # Show the current selected task over the bottom of the frame
|
|
|
+ def show_selected(self,selected_task):
|
|
|
+ if not selected_task:
|
|
|
+ selected_task = self.get_selected()
|
|
|
+ tag_line = "%s%s%s" % ('[',CHAR_HBAR * (self.width-2),']')
|
|
|
+ self.screen.addstr(self.base_y + self.height, self.base_x, tag_line)
|
|
|
+ self.screen.addstr(self.base_y + self.height,
|
|
|
+ (self.base_x + (self.width//2))-((len(selected_task)+2)//2),
|
|
|
+ '['+selected_task+']')
|
|
|
+ self.screen.refresh()
|
|
|
+
|
|
|
+ # Load box with new table of content
|
|
|
+ def update_content(self,task_list):
|
|
|
+ self.task_list = task_list
|
|
|
+ if self.cursor_enable:
|
|
|
+ cursor_update(turn_on=False)
|
|
|
+ self.cursor_index = 0
|
|
|
+ self.cursor_offset = 0
|
|
|
+ self.scroll_offset = 0
|
|
|
+ self.redraw()
|
|
|
+ if self.cursor_enable:
|
|
|
+ cursor_update(turn_on=True)
|
|
|
+
|
|
|
+ # Manage the box's highlighted task and blinking cursor character
|
|
|
+ def cursor_on(self,is_on):
|
|
|
+ self.cursor_enable = is_on
|
|
|
+ self.cursor_update(is_on)
|
|
|
+
|
|
|
+ # High-light the current pointed package, normal for released packages
|
|
|
+ def cursor_update(self,turn_on=True):
|
|
|
+ str_ctl = "%%-%ss" % (self.inside_width-1)
|
|
|
+ try:
|
|
|
+ if len(self.task_list):
|
|
|
+ task_obj = self.task_list[self.cursor_index]
|
|
|
+ task = task_obj[TASK_NAME][:self.inside_width-1]
|
|
|
+ task_primary = task_obj[TASK_PRIMARY]
|
|
|
+ task_font = curses.A_BOLD if task_primary else 0
|
|
|
+ else:
|
|
|
+ task = ''
|
|
|
+ task_font = 0
|
|
|
+ except Exception as e:
|
|
|
+ alert("CURSOR_UPDATE:%s" % (e),self.screen)
|
|
|
+ return
|
|
|
+ if turn_on:
|
|
|
+ self.screen.addstr(self.base_y+1+self.cursor_offset,self.base_x+1,">", curses.color_pair(CURSES_HIGHLIGHT) | curses.A_BLINK)
|
|
|
+ self.screen.addstr(self.base_y+1+self.cursor_offset,self.base_x+2,str_ctl % task, curses.color_pair(CURSES_HIGHLIGHT) | task_font)
|
|
|
+ else:
|
|
|
+ self.screen.addstr(self.base_y+1+self.cursor_offset,self.base_x+1," ")
|
|
|
+ self.screen.addstr(self.base_y+1+self.cursor_offset,self.base_x+2,str_ctl % task, task_font)
|
|
|
+
|
|
|
+ # Down arrow
|
|
|
+ def line_down(self):
|
|
|
+ if len(self.task_list) <= (self.cursor_index+1):
|
|
|
+ return
|
|
|
+ self.cursor_update(turn_on=False)
|
|
|
+ self.cursor_index += 1
|
|
|
+ self.cursor_offset += 1
|
|
|
+ if self.cursor_offset > (self.inside_height):
|
|
|
+ self.cursor_offset -= 1
|
|
|
+ self.scroll_offset += 1
|
|
|
+ self.redraw()
|
|
|
+ self.cursor_update(turn_on=True)
|
|
|
+ debug_frame(self)
|
|
|
+
|
|
|
+ # Up arrow
|
|
|
+ def line_up(self):
|
|
|
+ if 0 > (self.cursor_index-1):
|
|
|
+ return
|
|
|
+ self.cursor_update(turn_on=False)
|
|
|
+ self.cursor_index -= 1
|
|
|
+ self.cursor_offset -= 1
|
|
|
+ if self.cursor_offset < 0:
|
|
|
+ self.cursor_offset += 1
|
|
|
+ self.scroll_offset -= 1
|
|
|
+ self.redraw()
|
|
|
+ self.cursor_update(turn_on=True)
|
|
|
+ debug_frame(self)
|
|
|
+
|
|
|
+ # Page down
|
|
|
+ def page_down(self):
|
|
|
+ max_task = len(self.task_list)-1
|
|
|
+ if max_task < self.inside_height:
|
|
|
+ return
|
|
|
+ self.cursor_update(turn_on=False)
|
|
|
+ self.cursor_index += 10
|
|
|
+ self.cursor_index = min(self.cursor_index,max_task)
|
|
|
+ self.cursor_offset = min(self.inside_height,self.cursor_index)
|
|
|
+ self.scroll_offset = self.cursor_index - self.cursor_offset
|
|
|
+ self.redraw()
|
|
|
+ self.cursor_update(turn_on=True)
|
|
|
+ debug_frame(self)
|
|
|
+
|
|
|
+ # Page up
|
|
|
+ def page_up(self):
|
|
|
+ max_task = len(self.task_list)-1
|
|
|
+ if max_task < self.inside_height:
|
|
|
+ return
|
|
|
+ self.cursor_update(turn_on=False)
|
|
|
+ self.cursor_index -= 10
|
|
|
+ self.cursor_index = max(self.cursor_index,0)
|
|
|
+ self.cursor_offset = max(0, self.inside_height - (max_task - self.cursor_index))
|
|
|
+ self.scroll_offset = self.cursor_index - self.cursor_offset
|
|
|
+ self.redraw()
|
|
|
+ self.cursor_update(turn_on=True)
|
|
|
+ debug_frame(self)
|
|
|
+
|
|
|
+ # Return the currently selected task name for this box
|
|
|
+ def get_selected(self):
|
|
|
+ if self.task_list:
|
|
|
+ return(self.task_list[self.cursor_index][TASK_NAME])
|
|
|
+ else:
|
|
|
+ return('')
|
|
|
+
|
|
|
+#################################################
|
|
|
+### The helper sub-windows
|
|
|
+###
|
|
|
+
|
|
|
+# Show persistent help at the top of the screen
|
|
|
+class HelpBarView(NBox):
|
|
|
+ def __init__(self, screen, label, primary, base_x, base_y, width, height):
|
|
|
+ super(HelpBarView, self).__init__(screen, label, primary, base_x, base_y, width, height)
|
|
|
+
|
|
|
+ def show_help(self,show):
|
|
|
+ self.screen.addstr(self.base_y,self.base_x, "%s" % (' ' * self.inside_width))
|
|
|
+ if show:
|
|
|
+ help = "Help='?' Filter='/' NextBox=<Tab> Select=<Enter> Print='p','P' Quit='q'"
|
|
|
+ bar_size = self.inside_width - 5 - len(help)
|
|
|
+ self.screen.addstr(self.base_y,self.base_x+((self.inside_width-len(help))//2), help)
|
|
|
+ self.screen.refresh()
|
|
|
+
|
|
|
+# Pop up a detailed Help box
|
|
|
+class HelpBoxView(NBox):
|
|
|
+ def __init__(self, screen, label, primary, base_x, base_y, width, height, dep):
|
|
|
+ super(HelpBoxView, self).__init__(screen, label, primary, base_x, base_y, width, height)
|
|
|
+ self.x_pos = 0
|
|
|
+ self.y_pos = 0
|
|
|
+ self.dep = dep
|
|
|
+
|
|
|
+ # Instantial the pop-up help box
|
|
|
+ def show_help(self,show):
|
|
|
+ self.x_pos = self.base_x + 4
|
|
|
+ self.y_pos = self.base_y + 2
|
|
|
+
|
|
|
+ def add_line(line):
|
|
|
+ if line:
|
|
|
+ self.screen.addstr(self.y_pos,self.x_pos,line)
|
|
|
+ self.y_pos += 1
|
|
|
+
|
|
|
+ # Gather some statisics
|
|
|
+ dep_count = 0
|
|
|
+ rdep_count = 0
|
|
|
+ for task_obj in self.dep.depends_model:
|
|
|
+ if TYPE_DEP == task_obj[DEPENDS_TYPE]:
|
|
|
+ dep_count += 1
|
|
|
+ elif TYPE_RDEP == task_obj[DEPENDS_TYPE]:
|
|
|
+ rdep_count += 1
|
|
|
+
|
|
|
+ self.draw_frame()
|
|
|
+ line_art_fixup(self.dep)
|
|
|
+ add_line("Quit : 'q' ")
|
|
|
+ add_line("Filter task names : '/'")
|
|
|
+ add_line("Tab to next box : <Tab>")
|
|
|
+ add_line("Select a task : <Enter>")
|
|
|
+ add_line("Print task's deps : 'p'")
|
|
|
+ add_line("Print recipe's deps : 'P'")
|
|
|
+ add_line(" -> '%s'" % print_file_name)
|
|
|
+ add_line("Sort toggle : 's'")
|
|
|
+ add_line(" %s Recipe inner-depends order" % ('->' if (SORT_DEPS == sort_model) else '- '))
|
|
|
+ add_line(" %s Alpha-numeric order" % ('->' if (SORT_ALPHA == sort_model) else '- '))
|
|
|
+ if SORT_BITBAKE_ENABLE:
|
|
|
+ add_line(" %s Bitbake order" % ('->' if (TASK_SORT_BITBAKE == sort_model) else '- '))
|
|
|
+ add_line("Alternate backspace : <CTRL-H>")
|
|
|
+ add_line("")
|
|
|
+ add_line("Primary recipes = %s" % ','.join(self.primary))
|
|
|
+ add_line("Task count = %4d" % len(self.dep.pkg_model))
|
|
|
+ add_line("Deps count = %4d" % dep_count)
|
|
|
+ add_line("RDeps count = %4d" % rdep_count)
|
|
|
+ add_line("")
|
|
|
+ self.screen.addstr(self.y_pos,self.x_pos+7,"<Press any key>", curses.color_pair(CURSES_HIGHLIGHT))
|
|
|
+ self.screen.refresh()
|
|
|
+ c = self.screen.getch()
|
|
|
+
|
|
|
+# Show a progress bar
|
|
|
+class ProgressView(NBox):
|
|
|
+ def __init__(self, screen, label, primary, base_x, base_y, width, height):
|
|
|
+ super(ProgressView, self).__init__(screen, label, primary, base_x, base_y, width, height)
|
|
|
+
|
|
|
+ def progress(self,title,current,max):
|
|
|
+ if title:
|
|
|
+ self.label = title
|
|
|
+ else:
|
|
|
+ title = self.label
|
|
|
+ if max <=0: max = 10
|
|
|
+ bar_size = self.width - 7 - len(title)
|
|
|
+ bar_done = int( (float(current)/float(max)) * float(bar_size) )
|
|
|
+ self.screen.addstr(self.base_y,self.base_x, " %s:[%s%s]" % (title,'*' * bar_done,' ' * (bar_size-bar_done)))
|
|
|
+ self.screen.refresh()
|
|
|
+ return(current+1)
|
|
|
+
|
|
|
+ def clear(self):
|
|
|
+ self.screen.addstr(self.base_y,self.base_x, "%s" % (' ' * self.width))
|
|
|
+ self.screen.refresh()
|
|
|
+
|
|
|
+# Implement a task filter bar
|
|
|
+class FilterView(NBox):
|
|
|
+ SEARCH_NOP = 0
|
|
|
+ SEARCH_GO = 1
|
|
|
+ SEARCH_CANCEL = 2
|
|
|
+
|
|
|
+ def __init__(self, screen, label, primary, base_x, base_y, width, height):
|
|
|
+ super(FilterView, self).__init__(screen, label, primary, base_x, base_y, width, height)
|
|
|
+ self.do_show = False
|
|
|
+ self.filter_str = ""
|
|
|
+
|
|
|
+ def clear(self,enable_show=True):
|
|
|
+ self.filter_str = ""
|
|
|
+
|
|
|
+ def show(self,enable_show=True):
|
|
|
+ self.do_show = enable_show
|
|
|
+ if self.do_show:
|
|
|
+ self.screen.addstr(self.base_y,self.base_x, "[ Filter: %-25s ] '/'=cancel, format='abc' " % self.filter_str[0:25])
|
|
|
+ else:
|
|
|
+ self.screen.addstr(self.base_y,self.base_x, "%s" % (' ' * self.width))
|
|
|
+ self.screen.refresh()
|
|
|
+
|
|
|
+ def show_prompt(self):
|
|
|
+ self.screen.addstr(self.base_y,self.base_x + 10 + len(self.filter_str), " ")
|
|
|
+ self.screen.addstr(self.base_y,self.base_x + 10 + len(self.filter_str), "")
|
|
|
+
|
|
|
+ # Keys specific to the filter box (start/stop filter keys are in the main loop)
|
|
|
+ def input(self,c,ch):
|
|
|
+ ret = self.SEARCH_GO
|
|
|
+ if c in (curses.KEY_BACKSPACE,CHAR_BS_H):
|
|
|
+ # Backspace
|
|
|
+ if self.filter_str:
|
|
|
+ self.filter_str = self.filter_str[0:-1]
|
|
|
+ self.show()
|
|
|
+ elif ((ch >= 'a') and (ch <= 'z')) or ((ch >= 'A') and (ch <= 'Z')) or ((ch >= '0') and (ch <= '9')) or (ch in (' ','_','.','-')):
|
|
|
+ # The isalnum() acts strangly with keypad(True), so explicit bounds
|
|
|
+ self.filter_str += ch
|
|
|
+ self.show()
|
|
|
+ else:
|
|
|
+ ret = self.SEARCH_NOP
|
|
|
+ return(ret)
|
|
|
+
|
|
|
+
|
|
|
+#################################################
|
|
|
+### The primary dependency windows
|
|
|
+###
|
|
|
+
|
|
|
+# The main list of package tasks
|
|
|
+class PackageView(NBox):
|
|
|
+ def __init__(self, screen, label, primary, base_x, base_y, width, height):
|
|
|
+ super(PackageView, self).__init__(screen, label, primary, base_x, base_y, width, height)
|
|
|
+
|
|
|
+ # Find and verticaly center a selected task (from filter or from dependent box)
|
|
|
+ # The 'task_filter_str' can be a full or a partial (filter) task name
|
|
|
+ def find(self,task_filter_str):
|
|
|
+ found = False
|
|
|
+ max = self.height-2
|
|
|
+ if not task_filter_str:
|
|
|
+ return(found)
|
|
|
+ for i,task_obj in enumerate(self.task_list):
|
|
|
+ task = task_obj[TASK_NAME]
|
|
|
+ if task.startswith(task_filter_str):
|
|
|
+ self.cursor_on(False)
|
|
|
+ self.cursor_index = i
|
|
|
+
|
|
|
+ # Position selected at vertical center
|
|
|
+ vcenter = self.inside_height // 2
|
|
|
+ if self.cursor_index <= vcenter:
|
|
|
+ self.scroll_offset = 0
|
|
|
+ self.cursor_offset = self.cursor_index
|
|
|
+ elif self.cursor_index >= (len(self.task_list) - vcenter - 1):
|
|
|
+ self.cursor_offset = self.inside_height-1
|
|
|
+ self.scroll_offset = self.cursor_index - self.cursor_offset
|
|
|
+ else:
|
|
|
+ self.cursor_offset = vcenter
|
|
|
+ self.scroll_offset = self.cursor_index - self.cursor_offset
|
|
|
+
|
|
|
+ self.redraw()
|
|
|
+ self.cursor_on(True)
|
|
|
+ found = True
|
|
|
+ break
|
|
|
+ return(found)
|
|
|
+
|
|
|
+# The view of dependent packages
|
|
|
+class PackageDepView(NBox):
|
|
|
+ def __init__(self, screen, label, primary, base_x, base_y, width, height):
|
|
|
+ super(PackageDepView, self).__init__(screen, label, primary, base_x, base_y, width, height)
|
|
|
+
|
|
|
+# The view of reverse-dependent packages
|
|
|
+class PackageReverseDepView(NBox):
|
|
|
+ def __init__(self, screen, label, primary, base_x, base_y, width, height):
|
|
|
+ super(PackageReverseDepView, self).__init__(screen, label, primary, base_x, base_y, width, height)
|
|
|
+
|
|
|
+
|
|
|
+#################################################
|
|
|
+### DepExplorer : The parent frame and object
|
|
|
+###
|
|
|
+
|
|
|
+class DepExplorer(NBox):
|
|
|
+ def __init__(self,screen):
|
|
|
+ title = "Task Dependency Explorer"
|
|
|
+ super(DepExplorer, self).__init__(screen, 'Task Dependency Explorer','',0,0,80,23)
|
|
|
+
|
|
|
+ self.screen = screen
|
|
|
+ self.pkg_model = []
|
|
|
+ self.depends_model = []
|
|
|
+ self.dep_sort_map = {}
|
|
|
+ self.bb_sort_map = {}
|
|
|
+ self.filter_str = ''
|
|
|
+ self.filter_prev = 'deadbeef'
|
|
|
+
|
|
|
+ if self.screen:
|
|
|
+ self.help_bar_view = HelpBarView(screen, "Help",'',1,1,79,1)
|
|
|
+ self.help_box_view = HelpBoxView(screen, "Help",'',0,2,40,20,self)
|
|
|
+ self.progress_view = ProgressView(screen, "Progress",'',2,1,76,1)
|
|
|
+ self.filter_view = FilterView(screen, "Filter",'',2,1,76,1)
|
|
|
+ self.package_view = PackageView(screen, "Package",'alpha', 0,2,40,20)
|
|
|
+ self.dep_view = PackageDepView(screen, "Dependencies",'beta',40,2,40,10)
|
|
|
+ self.reverse_view = PackageReverseDepView(screen, "Dependent Tasks",'gamma',40,13,40,9)
|
|
|
+ self.draw_frames()
|
|
|
+
|
|
|
+ # Draw this main window's frame and all sub-windows
|
|
|
+ def draw_frames(self):
|
|
|
+ self.draw_frame()
|
|
|
+ self.package_view.draw_frame()
|
|
|
+ self.dep_view.draw_frame()
|
|
|
+ self.reverse_view.draw_frame()
|
|
|
+ if is_filter:
|
|
|
+ self.filter_view.show(True)
|
|
|
+ self.filter_view.show_prompt()
|
|
|
+ else:
|
|
|
+ self.help_bar_view.show_help(True)
|
|
|
+ self.package_view.redraw()
|
|
|
+ self.dep_view.redraw()
|
|
|
+ self.reverse_view.redraw()
|
|
|
+ self.show_selected(self.package_view.get_selected())
|
|
|
+ line_art_fixup(self)
|
|
|
+
|
|
|
+ # Parse the bitbake dependency event object
|
|
|
+ def parse(self, depgraph):
|
|
|
+ for task in depgraph["tdepends"]:
|
|
|
+ self.pkg_model.insert(0, task)
|
|
|
+ for depend in depgraph["tdepends"][task]:
|
|
|
+ self.depends_model.insert (0, (TYPE_DEP, task, depend))
|
|
|
+ self.depends_model.insert (0, (TYPE_RDEP, depend, task))
|
|
|
+ if self.screen:
|
|
|
+ self.dep_sort_prep()
|
|
|
+
|
|
|
+ # Prepare the dependency sort order keys
|
|
|
+ # This method creates sort keys per recipe tasks in
|
|
|
+ # the order of each recipe's internal dependecies
|
|
|
+ # Method:
|
|
|
+ # Filter the tasks in dep order in dep_sort_map = {}
|
|
|
+ # (a) Find a task that has no dependecies
|
|
|
+ # Ignore non-recipe specific tasks
|
|
|
+ # (b) Add it to the sort mapping dict with
|
|
|
+ # key of "<task_group>_<order>"
|
|
|
+ # (c) Remove it as a dependency from the other tasks
|
|
|
+ # (d) Repeat till all tasks are mapped
|
|
|
+ # Use placeholders to insure each sub-dict is instantiated
|
|
|
+ def dep_sort_prep(self):
|
|
|
+ self.progress_view.progress('DepSort',0,4)
|
|
|
+ # Init the task base entries
|
|
|
+ self.progress_view.progress('DepSort',1,4)
|
|
|
+ dep_table = {}
|
|
|
+ bb_index = 0
|
|
|
+ for task in self.pkg_model:
|
|
|
+ # First define the incoming bitbake sort order
|
|
|
+ self.bb_sort_map[task] = "%04d" % (bb_index)
|
|
|
+ bb_index += 1
|
|
|
+ task_group = task[0:task.find('.')]
|
|
|
+ if task_group not in dep_table:
|
|
|
+ dep_table[task_group] = {}
|
|
|
+ dep_table[task_group]['-'] = {} # Placeholder
|
|
|
+ if task not in dep_table[task_group]:
|
|
|
+ dep_table[task_group][task] = {}
|
|
|
+ dep_table[task_group][task]['-'] = {} # Placeholder
|
|
|
+ # Add the task dependecy entries
|
|
|
+ self.progress_view.progress('DepSort',2,4)
|
|
|
+ for task_obj in self.depends_model:
|
|
|
+ if task_obj[DEPENDS_TYPE] != TYPE_DEP:
|
|
|
+ continue
|
|
|
+ task = task_obj[DEPENDS_TASK]
|
|
|
+ task_dep = task_obj[DEPENDS_DEPS]
|
|
|
+ task_group = task[0:task.find('.')]
|
|
|
+ # Only track depends within same group
|
|
|
+ if task_dep.startswith(task_group+'.'):
|
|
|
+ dep_table[task_group][task][task_dep] = 1
|
|
|
+ self.progress_view.progress('DepSort',3,4)
|
|
|
+ for task_group in dep_table:
|
|
|
+ dep_index = 0
|
|
|
+ # Whittle down the tasks of each group
|
|
|
+ this_pass = 1
|
|
|
+ do_loop = True
|
|
|
+ while (len(dep_table[task_group]) > 1) and do_loop:
|
|
|
+ this_pass += 1
|
|
|
+ is_change = False
|
|
|
+ delete_list = []
|
|
|
+ for task in dep_table[task_group]:
|
|
|
+ if '-' == task:
|
|
|
+ continue
|
|
|
+ if 1 == len(dep_table[task_group][task]):
|
|
|
+ is_change = True
|
|
|
+ # No more deps, so collect this task...
|
|
|
+ self.dep_sort_map[task] = "%s_%04d" % (task_group,dep_index)
|
|
|
+ dep_index += 1
|
|
|
+ # ... remove it from other lists as resolved ...
|
|
|
+ for dep_task in dep_table[task_group]:
|
|
|
+ if task in dep_table[task_group][dep_task]:
|
|
|
+ del dep_table[task_group][dep_task][task]
|
|
|
+ # ... and remove it from from the task group
|
|
|
+ delete_list.append(task)
|
|
|
+ for task in delete_list:
|
|
|
+ del dep_table[task_group][task]
|
|
|
+ if not is_change:
|
|
|
+ alert("ERROR:DEP_SIEVE_NO_CHANGE:%s" % task_group,self.screen)
|
|
|
+ do_loop = False
|
|
|
+ continue
|
|
|
+ self.progress_view.progress('',4,4)
|
|
|
+ self.progress_view.clear()
|
|
|
+ self.help_bar_view.show_help(True)
|
|
|
+ if len(self.dep_sort_map) != len(self.pkg_model):
|
|
|
+ alert("ErrorDepSort:%d/%d" % (len(self.dep_sort_map),len(self.pkg_model)),self.screen)
|
|
|
+
|
|
|
+ # Look up a dep sort order key
|
|
|
+ def get_dep_sort(self,key):
|
|
|
+ if key in self.dep_sort_map:
|
|
|
+ return(self.dep_sort_map[key])
|
|
|
+ else:
|
|
|
+ return(key)
|
|
|
+
|
|
|
+ # Look up a bitbake sort order key
|
|
|
+ def get_bb_sort(self,key):
|
|
|
+ if key in self.bb_sort_map:
|
|
|
+ return(self.bb_sort_map[key])
|
|
|
+ else:
|
|
|
+ return(key)
|
|
|
+
|
|
|
+ # Find the selected package in the main frame, update the dependency frames content accordingly
|
|
|
+ def select(self, package_name, only_update_dependents=False):
|
|
|
+ if not package_name:
|
|
|
+ package_name = self.package_view.get_selected()
|
|
|
+ # alert("SELECT:%s:" % package_name,self.screen)
|
|
|
+
|
|
|
+ if self.filter_str != self.filter_prev:
|
|
|
+ self.package_view.cursor_on(False)
|
|
|
+ # Fill of the main package task list using new filter
|
|
|
+ self.package_view.task_list = []
|
|
|
+ for package in self.pkg_model:
|
|
|
+ if self.filter_str:
|
|
|
+ if self.filter_str in package:
|
|
|
+ self.package_view.task_list_append(package,self)
|
|
|
+ else:
|
|
|
+ self.package_view.task_list_append(package,self)
|
|
|
+ self.package_view.sort()
|
|
|
+ self.filter_prev = self.filter_str
|
|
|
+
|
|
|
+ # Old position is lost, assert new position of previous task (if still filtered in)
|
|
|
+ self.package_view.cursor_index = 0
|
|
|
+ self.package_view.cursor_offset = 0
|
|
|
+ self.package_view.scroll_offset = 0
|
|
|
+ self.package_view.redraw()
|
|
|
+ self.package_view.cursor_on(True)
|
|
|
+
|
|
|
+ # Make sure the selected package is in view, with implicit redraw()
|
|
|
+ if (not only_update_dependents):
|
|
|
+ self.package_view.find(package_name)
|
|
|
+ # In case selected name change (i.e. filter removed previous)
|
|
|
+ package_name = self.package_view.get_selected()
|
|
|
+
|
|
|
+ # Filter the package's dependent list to the dependent view
|
|
|
+ self.dep_view.reset()
|
|
|
+ for package_def in self.depends_model:
|
|
|
+ if (package_def[DEPENDS_TYPE] == TYPE_DEP) and (package_def[DEPENDS_TASK] == package_name):
|
|
|
+ self.dep_view.task_list_append(package_def[DEPENDS_DEPS],self)
|
|
|
+ self.dep_view.sort()
|
|
|
+ self.dep_view.redraw()
|
|
|
+ # Filter the package's dependent list to the reverse dependent view
|
|
|
+ self.reverse_view.reset()
|
|
|
+ for package_def in self.depends_model:
|
|
|
+ if (package_def[DEPENDS_TYPE] == TYPE_RDEP) and (package_def[DEPENDS_TASK] == package_name):
|
|
|
+ self.reverse_view.task_list_append(package_def[DEPENDS_DEPS],self)
|
|
|
+ self.reverse_view.sort()
|
|
|
+ self.reverse_view.redraw()
|
|
|
+ self.show_selected(package_name)
|
|
|
+ self.screen.refresh()
|
|
|
+
|
|
|
+ # The print-to-file method
|
|
|
+ def print_deps(self,whole_group=False):
|
|
|
+ global is_printed
|
|
|
+ # Print the selected deptree(s) to a file
|
|
|
+ if not is_printed:
|
|
|
+ try:
|
|
|
+ # Move to backup any exiting file before first write
|
|
|
+ if os.path.isfile(print_file_name):
|
|
|
+ os.system('mv -f %s %s' % (print_file_name,print_file_backup_name))
|
|
|
+ except Exception as e:
|
|
|
+ alert(e,self.screen)
|
|
|
+ alert('',self.screen)
|
|
|
+ print_list = []
|
|
|
+ selected_task = self.package_view.get_selected()
|
|
|
+ if not selected_task:
|
|
|
+ return
|
|
|
+ if not whole_group:
|
|
|
+ print_list.append(selected_task)
|
|
|
+ else:
|
|
|
+ # Use the presorted task_group order from 'package_view'
|
|
|
+ task_group = selected_task[0:selected_task.find('.')+1]
|
|
|
+ for task_obj in self.package_view.task_list:
|
|
|
+ task = task_obj[TASK_NAME]
|
|
|
+ if task.startswith(task_group):
|
|
|
+ print_list.append(task)
|
|
|
+ with open(print_file_name, "a") as fd:
|
|
|
+ print_max = len(print_list)
|
|
|
+ print_count = 1
|
|
|
+ self.progress_view.progress('Write "%s"' % print_file_name,0,print_max)
|
|
|
+ for task in print_list:
|
|
|
+ print_count = self.progress_view.progress('',print_count,print_max)
|
|
|
+ self.select(task)
|
|
|
+ self.screen.refresh();
|
|
|
+ # Utilize the current print output model
|
|
|
+ if print_model == PRINT_MODEL_1:
|
|
|
+ print("=== Dependendency Snapshot ===",file=fd)
|
|
|
+ print(" = Package =",file=fd)
|
|
|
+ print(' '+task,file=fd)
|
|
|
+ # Fill in the matching dependencies
|
|
|
+ print(" = Dependencies =",file=fd)
|
|
|
+ for task_obj in self.dep_view.task_list:
|
|
|
+ print(' '+ task_obj[TASK_NAME],file=fd)
|
|
|
+ print(" = Dependent Tasks =",file=fd)
|
|
|
+ for task_obj in self.reverse_view.task_list:
|
|
|
+ print(' '+ task_obj[TASK_NAME],file=fd)
|
|
|
+ if print_model == PRINT_MODEL_2:
|
|
|
+ print("=== Dependendency Snapshot ===",file=fd)
|
|
|
+ dep_count = len(self.dep_view.task_list) - 1
|
|
|
+ for i,task_obj in enumerate(self.dep_view.task_list):
|
|
|
+ print('%s%s' % ("Dep =" if (i==dep_count) else " ",task_obj[TASK_NAME]),file=fd)
|
|
|
+ if not self.dep_view.task_list:
|
|
|
+ print('Dep =',file=fd)
|
|
|
+ print("Package=%s" % task,file=fd)
|
|
|
+ for i,task_obj in enumerate(self.reverse_view.task_list):
|
|
|
+ print('%s%s' % ("RDep =" if (i==0) else " ",task_obj[TASK_NAME]),file=fd)
|
|
|
+ if not self.reverse_view.task_list:
|
|
|
+ print('RDep =',file=fd)
|
|
|
+ curses.napms(2000)
|
|
|
+ self.progress_view.clear()
|
|
|
+ self.help_bar_view.show_help(True)
|
|
|
+ print('',file=fd)
|
|
|
+ # Restore display to original selected task
|
|
|
+ self.select(selected_task)
|
|
|
+ is_printed = True
|
|
|
+
|
|
|
+#################################################
|
|
|
+### Load bitbake data
|
|
|
+###
|
|
|
+
|
|
|
+def bitbake_load(server, eventHandler, params, dep, curses_off, screen):
|
|
|
+ global bar_len_old
|
|
|
+ bar_len_old = 0
|
|
|
+
|
|
|
+ # Support no screen
|
|
|
+ def progress(msg,count,max):
|
|
|
+ global bar_len_old
|
|
|
+ if screen:
|
|
|
+ dep.progress_view.progress(msg,count,max)
|
|
|
+ else:
|
|
|
+ if msg:
|
|
|
+ if bar_len_old:
|
|
|
+ bar_len_old = 0
|
|
|
+ print("\n")
|
|
|
+ print(f"{msg}: ({count} of {max})")
|
|
|
+ else:
|
|
|
+ bar_len = int((count*40)/max)
|
|
|
+ if bar_len_old != bar_len:
|
|
|
+ print(f"{'*' * (bar_len-bar_len_old)}",end='',flush=True)
|
|
|
+ bar_len_old = bar_len
|
|
|
+ def clear():
|
|
|
+ if screen:
|
|
|
+ dep.progress_view.clear()
|
|
|
+ def clear_curses(screen):
|
|
|
+ if screen:
|
|
|
+ curses_off(screen)
|
|
|
+
|
|
|
+ #
|
|
|
+ # Trigger bitbake "generateDepTreeEvent"
|
|
|
+ #
|
|
|
+
|
|
|
+ cmdline = ''
|
|
|
+ try:
|
|
|
+ params.updateToServer(server, os.environ.copy())
|
|
|
+ params.updateFromServer(server)
|
|
|
+ cmdline = params.parseActions()
|
|
|
+ if not cmdline:
|
|
|
+ clear_curses(screen)
|
|
|
+ print("ERROR: nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
|
|
|
+ return 1,cmdline
|
|
|
+ if 'msg' in cmdline and cmdline['msg']:
|
|
|
+ clear_curses(screen)
|
|
|
+ print('ERROR: ' + cmdline['msg'])
|
|
|
+ return 1,cmdline
|
|
|
+ cmdline = cmdline['action']
|
|
|
+ if not cmdline or cmdline[0] != "generateDotGraph":
|
|
|
+ clear_curses(screen)
|
|
|
+ print("ERROR: This UI requires the -g option")
|
|
|
+ return 1,cmdline
|
|
|
+ ret, error = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
|
|
|
+ if error:
|
|
|
+ clear_curses(screen)
|
|
|
+ print("ERROR: running command '%s': %s" % (cmdline, error))
|
|
|
+ return 1,cmdline
|
|
|
+ elif not ret:
|
|
|
+ clear_curses(screen)
|
|
|
+ print("ERROR: running command '%s': returned %s" % (cmdline, ret))
|
|
|
+ return 1,cmdline
|
|
|
+ except client.Fault as x:
|
|
|
+ clear_curses(screen)
|
|
|
+ print("ERROR: XMLRPC Fault getting commandline:\n %s" % x)
|
|
|
+ return 1,cmdline
|
|
|
+ except Exception as e:
|
|
|
+ clear_curses(screen)
|
|
|
+ print("ERROR: in startup:\n %s" % traceback.format_exc())
|
|
|
+ return 1,cmdline
|
|
|
+
|
|
|
+ #
|
|
|
+ # Receive data from bitbake
|
|
|
+ #
|
|
|
+
|
|
|
+ progress_total = 0
|
|
|
+ load_bitbake = True
|
|
|
+ quit = False
|
|
|
+ try:
|
|
|
+ while load_bitbake:
|
|
|
+ try:
|
|
|
+ event = eventHandler.waitEvent(0.25)
|
|
|
+ if quit:
|
|
|
+ _, error = server.runCommand(["stateForceShutdown"])
|
|
|
+ clear_curses(screen)
|
|
|
+ if error:
|
|
|
+ print('Unable to cleanly stop: %s' % error)
|
|
|
+ break
|
|
|
+
|
|
|
+ if event is None:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if isinstance(event, bb.event.CacheLoadStarted):
|
|
|
+ progress_total = event.total
|
|
|
+ progress('Loading Cache',0,progress_total)
|
|
|
+ continue
|
|
|
+
|
|
|
+ if isinstance(event, bb.event.CacheLoadProgress):
|
|
|
+ x = event.current
|
|
|
+ progress('',x,progress_total)
|
|
|
+ continue
|
|
|
+
|
|
|
+ if isinstance(event, bb.event.CacheLoadCompleted):
|
|
|
+ clear()
|
|
|
+ progress('Bitbake... ',1,2)
|
|
|
+ continue
|
|
|
+
|
|
|
+ if isinstance(event, bb.event.ParseStarted):
|
|
|
+ progress_total = event.total
|
|
|
+ progress('Processing recipes',0,progress_total)
|
|
|
+ if progress_total == 0:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if isinstance(event, bb.event.ParseProgress):
|
|
|
+ x = event.current
|
|
|
+ progress('',x,progress_total)
|
|
|
+ continue
|
|
|
+
|
|
|
+ if isinstance(event, bb.event.ParseCompleted):
|
|
|
+ progress('Generating dependency tree',0,3)
|
|
|
+ continue
|
|
|
+
|
|
|
+ if isinstance(event, bb.event.DepTreeGenerated):
|
|
|
+ progress('Generating dependency tree',1,3)
|
|
|
+ dep.parse(event._depgraph)
|
|
|
+ progress('Generating dependency tree',2,3)
|
|
|
+
|
|
|
+ if isinstance(event, bb.command.CommandCompleted):
|
|
|
+ load_bitbake = False
|
|
|
+ progress('Generating dependency tree',3,3)
|
|
|
+ clear()
|
|
|
+ if screen:
|
|
|
+ dep.help_bar_view.show_help(True)
|
|
|
+ continue
|
|
|
+
|
|
|
+ if isinstance(event, bb.event.NoProvider):
|
|
|
+ clear_curses(screen)
|
|
|
+ print('ERROR: %s' % event)
|
|
|
+
|
|
|
+ _, error = server.runCommand(["stateShutdown"])
|
|
|
+ if error:
|
|
|
+ print('ERROR: Unable to cleanly shutdown: %s' % error)
|
|
|
+ return 1,cmdline
|
|
|
+
|
|
|
+ if isinstance(event, bb.command.CommandFailed):
|
|
|
+ clear_curses(screen)
|
|
|
+ print('ERROR: ' + str(event))
|
|
|
+ return event.exitcode,cmdline
|
|
|
+
|
|
|
+ if isinstance(event, bb.command.CommandExit):
|
|
|
+ clear_curses(screen)
|
|
|
+ return event.exitcode,cmdline
|
|
|
+
|
|
|
+ if isinstance(event, bb.cooker.CookerExit):
|
|
|
+ break
|
|
|
+
|
|
|
+ continue
|
|
|
+ except EnvironmentError as ioerror:
|
|
|
+ # ignore interrupted io
|
|
|
+ if ioerror.args[0] == 4:
|
|
|
+ pass
|
|
|
+ except KeyboardInterrupt:
|
|
|
+ if shutdown == 2:
|
|
|
+ clear_curses(screen)
|
|
|
+ print("\nThird Keyboard Interrupt, exit.\n")
|
|
|
+ break
|
|
|
+ if shutdown == 1:
|
|
|
+ clear_curses(screen)
|
|
|
+ print("\nSecond Keyboard Interrupt, stopping...\n")
|
|
|
+ _, error = server.runCommand(["stateForceShutdown"])
|
|
|
+ if error:
|
|
|
+ print('Unable to cleanly stop: %s' % error)
|
|
|
+ if shutdown == 0:
|
|
|
+ clear_curses(screen)
|
|
|
+ print("\nKeyboard Interrupt, closing down...\n")
|
|
|
+ _, error = server.runCommand(["stateShutdown"])
|
|
|
+ if error:
|
|
|
+ print('Unable to cleanly shutdown: %s' % error)
|
|
|
+ shutdown = shutdown + 1
|
|
|
+ pass
|
|
|
+ except Exception as e:
|
|
|
+ # Safe exit on error
|
|
|
+ clear_curses(screen)
|
|
|
+ print("Exception : %s" % e)
|
|
|
+ print("Exception in startup:\n %s" % traceback.format_exc())
|
|
|
+
|
|
|
+ return 0,cmdline
|
|
|
+
|
|
|
+#################################################
|
|
|
+### main
|
|
|
+###
|
|
|
+
|
|
|
+SCREEN_COL_MIN = 83
|
|
|
+SCREEN_ROW_MIN = 26
|
|
|
+
|
|
|
+def main(server, eventHandler, params):
|
|
|
+ global verbose
|
|
|
+ global sort_model
|
|
|
+ global print_model
|
|
|
+ global is_printed
|
|
|
+ global is_filter
|
|
|
+ global screen_too_small
|
|
|
+
|
|
|
+ shutdown = 0
|
|
|
+ screen_too_small = False
|
|
|
+ quit = False
|
|
|
+
|
|
|
+ # Unit test with no terminal?
|
|
|
+ if unit_test_noterm:
|
|
|
+ # Load bitbake, test that there is valid dependency data, then exit
|
|
|
+ screen = None
|
|
|
+ print("* UNIT TEST:START")
|
|
|
+ dep = DepExplorer(screen)
|
|
|
+ print("* UNIT TEST:BITBAKE FETCH")
|
|
|
+ ret,cmdline = bitbake_load(server, eventHandler, params, dep, None, screen)
|
|
|
+ if ret:
|
|
|
+ print("* UNIT TEST: BITBAKE FAILED")
|
|
|
+ return ret
|
|
|
+ # Test the acquired dependency data
|
|
|
+ quilt_native_deps = 0
|
|
|
+ quilt_native_rdeps = 0
|
|
|
+ quilt_deps = 0
|
|
|
+ quilt_rdeps = 0
|
|
|
+ for i,task_obj in enumerate(dep.depends_model):
|
|
|
+ if TYPE_DEP == task_obj[0]:
|
|
|
+ task = task_obj[1]
|
|
|
+ if task.startswith('quilt-native'):
|
|
|
+ quilt_native_deps += 1
|
|
|
+ elif task.startswith('quilt'):
|
|
|
+ quilt_deps += 1
|
|
|
+ elif TYPE_RDEP == task_obj[0]:
|
|
|
+ task = task_obj[1]
|
|
|
+ if task.startswith('quilt-native'):
|
|
|
+ quilt_native_rdeps += 1
|
|
|
+ elif task.startswith('quilt'):
|
|
|
+ quilt_rdeps += 1
|
|
|
+ # Print results
|
|
|
+ failed = False
|
|
|
+ if 0 < len(dep.depends_model):
|
|
|
+ print(f"Pass:Bitbake dependency count = {len(dep.depends_model)}")
|
|
|
+ else:
|
|
|
+ failed = True
|
|
|
+ print(f"FAIL:Bitbake dependency count = 0")
|
|
|
+ if quilt_native_deps:
|
|
|
+ print(f"Pass:Quilt-native depends count = {quilt_native_deps}")
|
|
|
+ else:
|
|
|
+ failed = True
|
|
|
+ print(f"FAIL:Quilt-native depends count = 0")
|
|
|
+ if quilt_native_rdeps:
|
|
|
+ print(f"Pass:Quilt-native rdepends count = {quilt_native_rdeps}")
|
|
|
+ else:
|
|
|
+ failed = True
|
|
|
+ print(f"FAIL:Quilt-native rdepends count = 0")
|
|
|
+ if quilt_deps:
|
|
|
+ print(f"Pass:Quilt depends count = {quilt_deps}")
|
|
|
+ else:
|
|
|
+ failed = True
|
|
|
+ print(f"FAIL:Quilt depends count = 0")
|
|
|
+ if quilt_rdeps:
|
|
|
+ print(f"Pass:Quilt rdepends count = {quilt_rdeps}")
|
|
|
+ else:
|
|
|
+ failed = True
|
|
|
+ print(f"FAIL:Quilt rdepends count = 0")
|
|
|
+ print("* UNIT TEST:STOP")
|
|
|
+ return failed
|
|
|
+
|
|
|
+ # Help method to dynamically test parent window too small
|
|
|
+ def check_screen_size(dep, active_package):
|
|
|
+ global screen_too_small
|
|
|
+ rows, cols = screen.getmaxyx()
|
|
|
+ if (rows >= SCREEN_ROW_MIN) and (cols >= SCREEN_COL_MIN):
|
|
|
+ if screen_too_small:
|
|
|
+ # Now big enough, remove error message and redraw screen
|
|
|
+ dep.draw_frames()
|
|
|
+ active_package.cursor_on(True)
|
|
|
+ screen_too_small = False
|
|
|
+ return True
|
|
|
+ # Test on App init
|
|
|
+ if not dep:
|
|
|
+ # Do not start this app if screen not big enough
|
|
|
+ curses.endwin()
|
|
|
+ print("")
|
|
|
+ print("ERROR(Taskexp_cli): Mininal screen size is %dx%d" % (SCREEN_COL_MIN,SCREEN_ROW_MIN))
|
|
|
+ print("Current screen is Cols=%s,Rows=%d" % (cols,rows))
|
|
|
+ return False
|
|
|
+ # First time window too small
|
|
|
+ if not screen_too_small:
|
|
|
+ active_package.cursor_on(False)
|
|
|
+ dep.screen.addstr(0,2,'[BIGGER WINDOW PLEASE]', curses.color_pair(CURSES_WARNING) | curses.A_BLINK)
|
|
|
+ screen_too_small = True
|
|
|
+ return False
|
|
|
+
|
|
|
+ # Helper method to turn off curses mode
|
|
|
+ def curses_off(screen):
|
|
|
+ if not screen: return
|
|
|
+ # Safe error exit
|
|
|
+ screen.keypad(False)
|
|
|
+ curses.echo()
|
|
|
+ curses.curs_set(1)
|
|
|
+ curses.endwin()
|
|
|
+
|
|
|
+ if unit_test_results:
|
|
|
+ print('\nUnit Test Results:')
|
|
|
+ for line in unit_test_results:
|
|
|
+ print(" %s" % line)
|
|
|
+
|
|
|
+ #
|
|
|
+ # Initialize the ncurse environment
|
|
|
+ #
|
|
|
+
|
|
|
+ screen = curses.initscr()
|
|
|
+ try:
|
|
|
+ if not check_screen_size(None, None):
|
|
|
+ exit(1)
|
|
|
+ try:
|
|
|
+ curses.start_color()
|
|
|
+ curses.use_default_colors();
|
|
|
+ curses.init_pair(0xFF, curses.COLOR_BLACK, curses.COLOR_WHITE);
|
|
|
+ curses.init_pair(CURSES_NORMAL, curses.COLOR_WHITE, curses.COLOR_BLACK)
|
|
|
+ curses.init_pair(CURSES_HIGHLIGHT, curses.COLOR_WHITE, curses.COLOR_BLUE)
|
|
|
+ curses.init_pair(CURSES_WARNING, curses.COLOR_WHITE, curses.COLOR_RED)
|
|
|
+ except:
|
|
|
+ curses.endwin()
|
|
|
+ print("")
|
|
|
+ print("ERROR(Taskexp_cli): Requires 256 colors. Please use this or the equivalent:")
|
|
|
+ print(" $ export TERM='xterm-256color'")
|
|
|
+ exit(1)
|
|
|
+
|
|
|
+ screen.keypad(True)
|
|
|
+ curses.noecho()
|
|
|
+ curses.curs_set(0)
|
|
|
+ screen.refresh();
|
|
|
+ except Exception as e:
|
|
|
+ # Safe error exit
|
|
|
+ curses_off(screen)
|
|
|
+ print("Exception : %s" % e)
|
|
|
+ print("Exception in startup:\n %s" % traceback.format_exc())
|
|
|
+ exit(1)
|
|
|
+
|
|
|
+ try:
|
|
|
+ #
|
|
|
+ # Instantiate the presentation layers
|
|
|
+ #
|
|
|
+
|
|
|
+ dep = DepExplorer(screen)
|
|
|
+
|
|
|
+ #
|
|
|
+ # Prepare bitbake
|
|
|
+ #
|
|
|
+
|
|
|
+ # Fetch bitbake dependecy data
|
|
|
+ ret,cmdline = bitbake_load(server, eventHandler, params, dep, curses_off, screen)
|
|
|
+ if ret: return ret
|
|
|
+
|
|
|
+ #
|
|
|
+ # Preset the views
|
|
|
+ #
|
|
|
+
|
|
|
+ # Cmdline example = ['generateDotGraph', ['acl', 'zlib'], 'build']
|
|
|
+ primary_packages = cmdline[1]
|
|
|
+ dep.package_view.set_primary(primary_packages)
|
|
|
+ dep.dep_view.set_primary(primary_packages)
|
|
|
+ dep.reverse_view.set_primary(primary_packages)
|
|
|
+ dep.help_box_view.set_primary(primary_packages)
|
|
|
+ dep.help_bar_view.show_help(True)
|
|
|
+ active_package = dep.package_view
|
|
|
+ active_package.cursor_on(True)
|
|
|
+ dep.select(primary_packages[0]+'.')
|
|
|
+ if unit_test:
|
|
|
+ alert('UNIT_TEST',screen)
|
|
|
+
|
|
|
+ # Help method to start/stop the filter feature
|
|
|
+ def filter_mode(new_filter_status):
|
|
|
+ global is_filter
|
|
|
+ if is_filter == new_filter_status:
|
|
|
+ # Ignore no changes
|
|
|
+ return
|
|
|
+ if not new_filter_status:
|
|
|
+ # Turn off
|
|
|
+ curses.curs_set(0)
|
|
|
+ #active_package.cursor_on(False)
|
|
|
+ active_package = dep.package_view
|
|
|
+ active_package.cursor_on(True)
|
|
|
+ is_filter = False
|
|
|
+ dep.help_bar_view.show_help(True)
|
|
|
+ dep.filter_str = ''
|
|
|
+ dep.select('')
|
|
|
+ else:
|
|
|
+ # Turn on
|
|
|
+ curses.curs_set(1)
|
|
|
+ dep.help_bar_view.show_help(False)
|
|
|
+ dep.filter_view.clear()
|
|
|
+ dep.filter_view.show(True)
|
|
|
+ dep.filter_view.show_prompt()
|
|
|
+ is_filter = True
|
|
|
+
|
|
|
+ #
|
|
|
+ # Main user loop
|
|
|
+ #
|
|
|
+
|
|
|
+ while not quit:
|
|
|
+ if is_filter:
|
|
|
+ dep.filter_view.show_prompt()
|
|
|
+ if unit_test:
|
|
|
+ c = unit_test_action(active_package)
|
|
|
+ else:
|
|
|
+ c = screen.getch()
|
|
|
+ ch = chr(c)
|
|
|
+
|
|
|
+ # Do not draw if window now too small
|
|
|
+ if not check_screen_size(dep,active_package):
|
|
|
+ continue
|
|
|
+
|
|
|
+ if verbose:
|
|
|
+ if c == CHAR_RETURN:
|
|
|
+ screen.addstr(0, 4, "|%3d,CR |" % (c))
|
|
|
+ else:
|
|
|
+ screen.addstr(0, 4, "|%3d,%3s|" % (c,chr(c)))
|
|
|
+
|
|
|
+ # pre-map alternate filter close keys
|
|
|
+ if is_filter and (c == CHAR_ESCAPE):
|
|
|
+ # Alternate exit from filter
|
|
|
+ ch = '/'
|
|
|
+ c = ord(ch)
|
|
|
+
|
|
|
+ # Filter and non-filter mode command keys
|
|
|
+ # https://docs.python.org/3/library/curses.html
|
|
|
+ if c in (curses.KEY_UP,CHAR_UP):
|
|
|
+ active_package.line_up()
|
|
|
+ if active_package == dep.package_view:
|
|
|
+ dep.select('',only_update_dependents=True)
|
|
|
+ elif c in (curses.KEY_DOWN,CHAR_DOWN):
|
|
|
+ active_package.line_down()
|
|
|
+ if active_package == dep.package_view:
|
|
|
+ dep.select('',only_update_dependents=True)
|
|
|
+ elif curses.KEY_PPAGE == c:
|
|
|
+ active_package.page_up()
|
|
|
+ if active_package == dep.package_view:
|
|
|
+ dep.select('',only_update_dependents=True)
|
|
|
+ elif curses.KEY_NPAGE == c:
|
|
|
+ active_package.page_down()
|
|
|
+ if active_package == dep.package_view:
|
|
|
+ dep.select('',only_update_dependents=True)
|
|
|
+ elif CHAR_TAB == c:
|
|
|
+ # Tab between boxes
|
|
|
+ active_package.cursor_on(False)
|
|
|
+ if active_package == dep.package_view:
|
|
|
+ active_package = dep.dep_view
|
|
|
+ elif active_package == dep.dep_view:
|
|
|
+ active_package = dep.reverse_view
|
|
|
+ else:
|
|
|
+ active_package = dep.package_view
|
|
|
+ active_package.cursor_on(True)
|
|
|
+ elif curses.KEY_BTAB == c:
|
|
|
+ # Shift-Tab reverse between boxes
|
|
|
+ active_package.cursor_on(False)
|
|
|
+ if active_package == dep.package_view:
|
|
|
+ active_package = dep.reverse_view
|
|
|
+ elif active_package == dep.reverse_view:
|
|
|
+ active_package = dep.dep_view
|
|
|
+ else:
|
|
|
+ active_package = dep.package_view
|
|
|
+ active_package.cursor_on(True)
|
|
|
+ elif (CHAR_RETURN == c):
|
|
|
+ # CR to select
|
|
|
+ selected = active_package.get_selected()
|
|
|
+ if selected:
|
|
|
+ active_package.cursor_on(False)
|
|
|
+ active_package = dep.package_view
|
|
|
+ filter_mode(False)
|
|
|
+ dep.select(selected)
|
|
|
+ else:
|
|
|
+ filter_mode(False)
|
|
|
+ dep.select(primary_packages[0]+'.')
|
|
|
+
|
|
|
+ elif '/' == ch: # Enter/exit dep.filter_view
|
|
|
+ if is_filter:
|
|
|
+ filter_mode(False)
|
|
|
+ else:
|
|
|
+ filter_mode(True)
|
|
|
+ elif is_filter:
|
|
|
+ # If in filter mode, re-direct all these other keys to the filter box
|
|
|
+ result = dep.filter_view.input(c,ch)
|
|
|
+ dep.filter_str = dep.filter_view.filter_str
|
|
|
+ dep.select('')
|
|
|
+
|
|
|
+ # Non-filter mode command keys
|
|
|
+ elif 'p' == ch:
|
|
|
+ dep.print_deps(whole_group=False)
|
|
|
+ elif 'P' == ch:
|
|
|
+ dep.print_deps(whole_group=True)
|
|
|
+ elif 'w' == ch:
|
|
|
+ # Toggle the print model
|
|
|
+ if print_model == PRINT_MODEL_1:
|
|
|
+ print_model = PRINT_MODEL_2
|
|
|
+ else:
|
|
|
+ print_model = PRINT_MODEL_1
|
|
|
+ elif 's' == ch:
|
|
|
+ # Toggle the sort model
|
|
|
+ if sort_model == SORT_DEPS:
|
|
|
+ sort_model = SORT_ALPHA
|
|
|
+ elif sort_model == SORT_ALPHA:
|
|
|
+ if SORT_BITBAKE_ENABLE:
|
|
|
+ sort_model = TASK_SORT_BITBAKE
|
|
|
+ else:
|
|
|
+ sort_model = SORT_DEPS
|
|
|
+ else:
|
|
|
+ sort_model = SORT_DEPS
|
|
|
+ active_package.cursor_on(False)
|
|
|
+ current_task = active_package.get_selected()
|
|
|
+ dep.package_view.sort()
|
|
|
+ dep.dep_view.sort()
|
|
|
+ dep.reverse_view.sort()
|
|
|
+ active_package = dep.package_view
|
|
|
+ active_package.cursor_on(True)
|
|
|
+ dep.select(current_task)
|
|
|
+ # Announce the new sort model
|
|
|
+ alert("SORT=%s" % ("ALPHA" if (sort_model == SORT_ALPHA) else "DEPS"),screen)
|
|
|
+ alert('',screen)
|
|
|
+
|
|
|
+ elif 'q' == ch:
|
|
|
+ quit = True
|
|
|
+ elif ch in ('h','?'):
|
|
|
+ dep.help_box_view.show_help(True)
|
|
|
+ dep.select(active_package.get_selected())
|
|
|
+
|
|
|
+ #
|
|
|
+ # Debugging commands
|
|
|
+ #
|
|
|
+
|
|
|
+ elif 'V' == ch:
|
|
|
+ verbose = not verbose
|
|
|
+ alert('Verbose=%s' % str(verbose),screen)
|
|
|
+ alert('',screen)
|
|
|
+ elif 'R' == ch:
|
|
|
+ screen.refresh()
|
|
|
+ elif 'B' == ch:
|
|
|
+ # Progress bar unit test
|
|
|
+ dep.progress_view.progress('Test',0,40)
|
|
|
+ curses.napms(1000)
|
|
|
+ dep.progress_view.progress('',10,40)
|
|
|
+ curses.napms(1000)
|
|
|
+ dep.progress_view.progress('',20,40)
|
|
|
+ curses.napms(1000)
|
|
|
+ dep.progress_view.progress('',30,40)
|
|
|
+ curses.napms(1000)
|
|
|
+ dep.progress_view.progress('',40,40)
|
|
|
+ curses.napms(1000)
|
|
|
+ dep.progress_view.clear()
|
|
|
+ dep.help_bar_view.show_help(True)
|
|
|
+ elif 'Q' == ch:
|
|
|
+ # Simulated error
|
|
|
+ curses_off(screen)
|
|
|
+ print('ERROR: simulated error exit')
|
|
|
+ return 1
|
|
|
+
|
|
|
+ # Safe exit
|
|
|
+ curses_off(screen)
|
|
|
+ except Exception as e:
|
|
|
+ # Safe exit on error
|
|
|
+ curses_off(screen)
|
|
|
+ print("Exception : %s" % e)
|
|
|
+ print("Exception in startup:\n %s" % traceback.format_exc())
|
|
|
+
|
|
|
+ # Reminder to pick up your printed results
|
|
|
+ if is_printed:
|
|
|
+ print("")
|
|
|
+ print("You have output ready!")
|
|
|
+ print(" * Your printed dependency file is: %s" % print_file_name)
|
|
|
+ print(" * Your previous results saved in: %s" % print_file_backup_name)
|
|
|
+ print("")
|