Openembedded Bitbake Development
 help / color / mirror / Atom feed
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



      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