From: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
To: "Reyna, David" <david.reyna@windriver.com>,
"bitbake-devel@lists.openembedded.org"
<bitbake-devel@lists.openembedded.org>
Subject: RE: [bitbake-devel] [PATCH 1/1] bitbake: ncurses version of taskexp.py
Date: Wed, 1 Jun 2022 16:53:58 +0000 [thread overview]
Message-ID: <272a5c37865549099f7bd088df32f675@axis.com> (raw)
In-Reply-To: <af83d503f04b2064c8ad0f005fa526185e231619.1653802954.git.David.Reyna@windriver.com>
> -----Original Message-----
> From: bitbake-devel@lists.openembedded.org <bitbake-
> devel@lists.openembedded.org> On Behalf Of Reyna, David
> Sent: den 29 maj 2022 08:16
> To: bitbake-devel@lists.openembedded.org
> Subject: [bitbake-devel] [PATCH 1/1] bitbake: ncurses version of taskexp.py
Please don't prefix the commit subject with "bitbake: ". combo-layer will
add that prefix when it cherry-picks the commit to the poky repo.
//Peter
>
> From: David Reyna <David.Reyna@windriver.com>
>
> * Create an ncurses version of the GTK app "taskexp.py".
> * Add these additional features:
> - Sort tasks in recipes by their dependency order
> - Print individual and/or recipe-wide dependencies to a file
> - Add a wild card filter
> - Show the target recipes on BOLD
> - Add a seft-test
>
> [YOCTO #14814]
>
> Signed-off-by: David Reyna <David.Reyna@windriver.com>
> ---
> lib/bb/ui/taskexp_tty.py | 1400 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 1400 insertions(+)
> create mode 100755 bitbake/lib/bb/ui/taskexp_tty.py
>
> diff --git a/lib/bb/ui/taskexp_tty.py b/lib/bb/ui/taskexp_tty.py
> new file mode 100755
> index 0000000000..6b08f3ff21
> --- /dev/null
> +++ b/lib/bb/ui/taskexp_tty.py
> @@ -0,0 +1,1400 @@
> +#
> +# 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 David Reyna
> +#
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +
> +#
> +# Execution example:
> +# $ bitbake -g -u taskexp_tty acl zlib
> +#
> +# Self-test example:
> +# $ TASK_EXP_UNIT_TEST=1 bitbake -g -u taskexp_tty zlib acl
> +# ...
> +# $ echo $?
> +# 0
> +# $ TASK_EXP_UNIT_TEST=1 bitbake -g -u taskexp_tty zlib acl foo
> +# ERROR: Nothing PROVIDES 'foo'. Close matches:
> +# ofono
> +# $ echo $?
> +# 1
> +#
> +# 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: capture intermediate information to a log
> +trace_file = 'ncurses.log'
> +try:
> + # Clean trace per run
> + os.remove(trace_file)
> +except:
> + pass
> +def _log(msg):
> + f1=open(trace_file, 'a')
> + f1.write(msg + '\n' )
> + f1.close()
> +
> +# 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_fetch',
> + '# Default selected task in deps',
> + 'tst_entry=<TAB>',
> + 'tst_selected=',
> + '# Default selected task in rdeps',
> + 'tst_entry=<TAB>',
> + 'tst_selected=<TASK>.do_prepare_recipe_sysroot',
> + "# Test 'select' back to primary box",
> + 'tst_entry=<CR>',
> + '#tst_entry=<DOWN>', # optional injected error
> + 'tst_selected=<TASK>.do_prepare_recipe_sysroot',
> + '# Check filter',
> + 'tst_entry=/uilt-nativ/',
> + 'tst_selected=quilt-native.do_fetch',
> + '# 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'" % (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)
> +
> +#################################################
> +### 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)
> + # 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:
> + _log("CURSOR_UPDATE:I=%3d,M=%3d,L=%3d" %
> (self.cursor_index,len(self.task_list),len(self.task_list[TASK_NAME])))
> + 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.pkg_model = []
> + self.depends_model = []
> + self.dep_sort_map = {}
> + self.bb_sort_map = {}
> + self.filter_str = ''
> + self.filter_prev = 'deadbeef'
> +
> + 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))
> + 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)
> + _log("ERROR:DEP_SIEVE_NO_CHANGE:%s,%s" %
> (str(dep_table[task_group])))
> + 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
> +
> +
> +#################################################
> +### 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
> +
> + # 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):
> + # 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)
> +
> + #
> + # Instantiate the presentation layer
> + #
> +
> + dep = DepExplorer(screen)
> +
> + #
> + # Main bitbake loop
> + #
> +
> + try:
> + params.updateToServer(server, os.environ.copy())
> + params.updateFromServer(server)
> + cmdline = params.parseActions()
> + if not cmdline:
> + curses_off(screen)
> + print("ERROR: nothing to do. Use 'bitbake world' to build
> everything, or run 'bitbake --help' for usage information.")
> + return 1
> + if 'msg' in cmdline and cmdline['msg']:
> + curses_off(screen)
> + print('ERROR: ' + cmdline['msg'])
> + return 1
> + cmdline = cmdline['action']
> + if not cmdline or cmdline[0] != "generateDotGraph":
> + curses_off(screen)
> + print("ERROR: This UI requires the -g option")
> + return 1
> + ret, error = server.runCommand(["generateDepTreeEvent",
> cmdline[1], cmdline[2]])
> + if error:
> + curses_off(screen)
> + print("ERROR: running command '%s': %s" % (cmdline, error))
> + return 1
> + elif not ret:
> + curses_off(screen)
> + print("ERROR: running command '%s': returned %s" % (cmdline,
> ret))
> + return 1
> + except client.Fault as x:
> + curses_off(screen)
> + print("ERROR: XMLRPC Fault getting commandline:\n %s" % x)
> + return 1
> + except Exception as e:
> + curses_off(screen)
> + print("ERROR: in startup:\n %s" % traceback.format_exc())
> + return 1
> +
> + # Prepare to receive the data from bitbake
> + progress_total = 0
> + load_bitbake = True
> + # 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)
> + if unit_test:
> + alert('UNIT_TEST',screen)
> +
> + # Catch any errors and safely display them out of ncursor mode
> + try:
> + while load_bitbake:
> + try:
> + event = eventHandler.waitEvent(0.25)
> + if quit:
> + _, error = server.runCommand(["stateForceShutdown"])
> + curses_off(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
> + dep.progress_view.progress('Loading
> Cache',0,progress_total)
> + continue
> +
> + if isinstance(event, bb.event.CacheLoadProgress):
> + x = event.current
> + dep.progress_view.progress('',x,progress_total)
> + continue
> +
> + if isinstance(event, bb.event.CacheLoadCompleted):
> + dep.progress_view.clear()
> + dep.progress_view.progress('Bitbake... ',1,2)
> + continue
> +
> + if isinstance(event, bb.event.ParseStarted):
> + progress_total = event.total
> + if progress_total == 0:
> + continue
> + dep.progress_view.progress('Processing
> recipes',0,progress_total)
> +
> + if isinstance(event, bb.event.ParseProgress):
> + x = event.current
> + dep.progress_view.progress('',x,progress_total)
> + continue
> +
> + if isinstance(event, bb.event.ParseCompleted):
> + dep.progress_view.progress('Generating dependency
> tree',0,3)
> + continue
> +
> + if isinstance(event, bb.event.DepTreeGenerated):
> + dep.progress_view.progress('Generating dependency
> tree',1,3)
> + dep.parse(event._depgraph)
> + dep.progress_view.progress('Generating dependency
> tree',2,3)
> +
> + if isinstance(event, bb.command.CommandCompleted):
> + load_bitbake = False
> + dep.progress_view.progress('Generating dependency
> tree',3,3)
> + dep.progress_view.clear()
> + dep.help_bar_view.show_help(True)
> + continue
> +
> + if isinstance(event, bb.event.NoProvider):
> + curses_off(screen)
> + print('ERROR: %s' % event)
> +
> + _, error = server.runCommand(["stateShutdown"])
> + if error:
> + print('ERROR: Unable to cleanly shutdown: %s' %
> error)
> + return 1
> +
> + if isinstance(event, bb.command.CommandFailed):
> + curses_off(screen)
> + print('ERROR: ' + str(event))
> + return event.exitcode
> +
> + if isinstance(event, bb.command.CommandExit):
> + curses_off(screen)
> + return event.exitcode
> +
> + 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:
> + curses_off(screen)
> + print("\nThird Keyboard Interrupt, exit.\n")
> + break
> + if shutdown == 1:
> + curses_off(screen)
> + print("\nSecond Keyboard Interrupt, stopping...\n")
> + _, error = server.runCommand(["stateForceShutdown"])
> + if error:
> + print('Unable to cleanly stop: %s' % error)
> + if shutdown == 0:
> + curses_off(screen)
> + print("\nKeyboard Interrupt, closing down...\n")
> + _, error = server.runCommand(["stateShutdown"])
> + if error:
> + print('Unable to cleanly shutdown: %s' % error)
> + shutdown = shutdown + 1
> + pass
> +
> + #
> + # Main user loop
> + #
> +
> + dep.help_bar_view.show_help(True)
> + active_package = dep.package_view
> + active_package.cursor_on(True)
> + dep.select(primary_packages[0]+'.')
> +
> + # 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
> +
> + 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("")
> --
> 2.35.1
prev parent reply other threads:[~2022-06-01 16:54 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-05-29 6:15 [PATCH 0/1][V2] bitbake: ncurses version of taskexp.py David Reyna
2022-05-29 6:15 ` [PATCH 1/1] " David Reyna
2022-05-30 8:41 ` [bitbake-devel] " Jacob Kroon
2022-05-31 1:38 ` Reyna, David
2022-06-01 16:53 ` Peter Kjellerstedt [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=272a5c37865549099f7bd088df32f675@axis.com \
--to=peter.kjellerstedt@axis.com \
--cc=bitbake-devel@lists.openembedded.org \
--cc=david.reyna@windriver.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox