Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  smart_status.go   Sprache: unbekannt

 
Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// Copyright 2019 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package terminal

import (
 "fmt"
 "io"
 "os"
 "os/signal"
 "strconv"
 "strings"
 "sync"
 "syscall"
 "time"

 "android/soong/ui/status"
)

const tableHeightEnVar = "SOONG_UI_TABLE_HEIGHT"

type actionTableEntry struct {
 action    *status.Action
 startTime time.Time
}

type smartStatusOutput struct {
 writer         io.Writer
 formatter      formatter
 suppressOutput bool

 lock sync.Mutex

 haveBlankLine bool

 tableMode             bool
 tableHeight           int
 requestedTableHeight  int
 termWidth, termHeight int

 runningActions  []actionTableEntry
 ticker          *time.Ticker
 done            chan bool
 sigwinch        chan os.Signal
 sigwinchHandled chan bool

 // Once there is a failure, we stop printing command output so the error
 // is easier to find
 haveFailures bool
 // If we are dropping errors, then at the end, we report a message to go
 // look in the verbose log if you want that command output.
 postFailureActionCount int
}

// NewSmartStatusOutput returns a StatusOutput that represents the
// current build status similarly to Ninja's built-in terminal
// output.
func NewSmartStatusOutput(w io.Writer,
 formatter formatter,
 suppressOutput bool) status.StatusOutput {
 s := &smartStatusOutput{
  writer:         w,
  formatter:      formatter,
  suppressOutput: suppressOutput,

  haveBlankLine: true,

  tableMode: true,

  done:     make(chan bool),
  sigwinch: make(chan os.Signal),
 }

 if env, ok := os.LookupEnv(tableHeightEnVar); ok {
  h, _ := strconv.Atoi(env)
  s.tableMode = h > 0
  s.requestedTableHeight = h
 }

 if w, h, ok := termSize(s.writer); ok {
  s.termWidth, s.termHeight = w, h
  s.computeTableHeight()
 } else {
  s.tableMode = false
 }

 if s.tableMode {
  // Add empty lines at the bottom of the screen to scroll back the existing history
  // and make room for the action table.
  // TODO: read the cursor position to see if the empty lines are necessary?
  for i := 0; i < s.tableHeight; i++ {
   fmt.Fprintln(w)
  }

  // Hide the cursor to prevent seeing it bouncing around
  fmt.Fprintf(s.writer, ansi.hideCursor())

  // Configure the empty action table
  s.actionTable()

  // Start a tick to update the action table periodically
  s.startActionTableTick()
 }

 s.startSigwinch()

 return s
}

func (s *smartStatusOutput) Message(level status.MsgLevel, message string) {
 if level < status.StatusLvl {
  return
 }

 str := s.formatter.message(level, message)

 s.lock.Lock()
 defer s.lock.Unlock()

 if level > status.StatusLvl {
  s.print(str)
 } else {
  s.statusLine(str)
 }
}

func (s *smartStatusOutput) StartAction(action *status.Action, counts status.Counts) {
 startTime := time.Now()

 str := action.Description
 if str == "" {
  str = action.Command
 }

 progress := s.formatter.progress(counts)

 s.lock.Lock()
 defer s.lock.Unlock()

 s.runningActions = append(s.runningActions, actionTableEntry{
  action:    action,
  startTime: startTime,
 })

 s.statusLine(progress + str)
}

func (s *smartStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
 str := result.Description
 if str == "" {
  str = result.Command
 }

 progress := s.formatter.progress(counts) + str

 output := s.formatter.result(result)

 s.lock.Lock()
 defer s.lock.Unlock()

 for i, runningAction := range s.runningActions {
  if runningAction.action == result.Action {
   s.runningActions = append(s.runningActions[:i], s.runningActions[i+1:]...)
   break
  }
 }

 s.statusLine(progress)

 // Stop printing when there are failures, but don't skip actions that also have their own errors.
 if output != "" {
  if (!s.haveFailures && !s.suppressOutput) || result.Error != nil {
   s.requestLine()
   s.print(output)
  } else {
   s.postFailureActionCount++
  }
 }

 if result.Error != nil {
  s.haveFailures = true
 }
}

func (s *smartStatusOutput) Flush() {
 if s.tableMode {
  // Stop the action table tick outside of the lock to avoid lock ordering issues between s.done and
  // s.lock, the goroutine in startActionTableTick can get blocked on the lock and be unable to read
  // from the channel.
  s.stopActionTableTick()
 }

 s.lock.Lock()
 defer s.lock.Unlock()

 s.stopSigwinch()

 if s.postFailureActionCount > 0 {
  s.requestLine()
  if s.postFailureActionCount == 1 {
   s.print(fmt.Sprintf("There was 1 action that completed after the action that failed. See verbose.log.gz for its output."))
  } else {
   s.print(fmt.Sprintf("There were %d actions that completed after the action that failed. See verbose.log.gz for their output.", s.postFailureActionCount))
  }
 }

 s.requestLine()

 s.runningActions = nil

 if s.tableMode {
  // Update the table after clearing runningActions to clear it
  s.actionTable()

  // Reset the scrolling region to the whole terminal
  fmt.Fprintf(s.writer, ansi.resetScrollingMargins())
  _, height, _ := termSize(s.writer)
  // Move the cursor to the top of the now-blank, previously non-scrolling region
  fmt.Fprintf(s.writer, ansi.setCursor(height-s.tableHeight, 1))
  // Turn the cursor back on
  fmt.Fprintf(s.writer, ansi.showCursor())
 }
}

func (s *smartStatusOutput) Write(p []byte) (int, error) {
 s.lock.Lock()
 defer s.lock.Unlock()
 s.print(string(p))
 return len(p), nil
}

func (s *smartStatusOutput) requestLine() {
 if !s.haveBlankLine {
  fmt.Fprintln(s.writer)
  s.haveBlankLine = true
 }
}

func (s *smartStatusOutput) print(str string) {
 if !s.haveBlankLine {
  fmt.Fprint(s.writer, "\r", ansi.clearToEndOfLine())
  s.haveBlankLine = true
 }
 fmt.Fprint(s.writer, str)
 if len(str) == 0 || str[len(str)-1] != '\n' {
  fmt.Fprint(s.writer, "\n")
 }
}

func (s *smartStatusOutput) statusLine(str string) {
 idx := strings.IndexRune(str, '\n')
 if idx != -1 {
  str = str[0:idx]
 }

 // Limit line width to the terminal width, otherwise we'll wrap onto
 // another line and we won't delete the previous line.
 str = elide(str, s.termWidth)

 // Move to the beginning on the line, turn on bold, print the output,
 // turn off bold, then clear the rest of the line.
 start := "\r" + ansi.bold()
 end := ansi.regular() + ansi.clearToEndOfLine()
 fmt.Fprint(s.writer, start, str, end)
 s.haveBlankLine = false
}

func elide(str string, width int) string {
 if width > 0 && len(str) > width {
  // TODO: Just do a max. Ninja elides the middle, but that's
  // more complicated and these lines aren't that important.
  str = str[:width]
 }

 return str
}

func (s *smartStatusOutput) startActionTableTick() {
 s.ticker = time.NewTicker(time.Second)
 go func() {
  for {
   select {
   case <-s.ticker.C:
    s.lock.Lock()
    s.actionTable()
    s.lock.Unlock()
   case <-s.done:
    return
   }
  }
 }()
}

func (s *smartStatusOutput) stopActionTableTick() {
 s.ticker.Stop()
 s.done <- true
}

func (s *smartStatusOutput) startSigwinch() {
 signal.Notify(s.sigwinch, syscall.SIGWINCH)
 go func() {
  for _ = range s.sigwinch {
   s.lock.Lock()
   s.updateTermSize()
   if s.tableMode {
    s.actionTable()
   }
   s.lock.Unlock()
   if s.sigwinchHandled != nil {
    s.sigwinchHandled <- true
   }
  }
 }()
}

func (s *smartStatusOutput) stopSigwinch() {
 signal.Stop(s.sigwinch)
 close(s.sigwinch)
}

// computeTableHeight recomputes s.tableHeight based on s.termHeight and s.requestedTableHeight.
func (s *smartStatusOutput) computeTableHeight() {
 tableHeight := s.requestedTableHeight
 if tableHeight == 0 {
  tableHeight = s.termHeight / 4
  if tableHeight < 1 {
   tableHeight = 1
  } else if tableHeight > 10 {
   tableHeight = 10
  }
 }
 if tableHeight > s.termHeight-1 {
  tableHeight = s.termHeight - 1
 }
 s.tableHeight = tableHeight
}

// updateTermSize recomputes the table height after a SIGWINCH and pans any existing text if
// necessary.
func (s *smartStatusOutput) updateTermSize() {
 if w, h, ok := termSize(s.writer); ok {
  oldScrollingHeight := s.termHeight - s.tableHeight

  s.termWidth, s.termHeight = w, h

  if s.tableMode {
   s.computeTableHeight()

   scrollingHeight := s.termHeight - s.tableHeight

   // If the scrolling region has changed, attempt to pan the existing text so that it is
   // not overwritten by the table.
   if scrollingHeight < oldScrollingHeight {
    pan := oldScrollingHeight - scrollingHeight
    if pan > s.tableHeight {
     pan = s.tableHeight
    }
    fmt.Fprint(s.writer, ansi.panDown(pan))
   }
  }
 }
}

func (s *smartStatusOutput) actionTable() {
 scrollingHeight := s.termHeight - s.tableHeight

 // Update the scrolling region in case the height of the terminal changed

 fmt.Fprint(s.writer, ansi.setScrollingMargins(1, scrollingHeight))

 // Write as many status lines as fit in the table
 for tableLine := 0; tableLine < s.tableHeight; tableLine++ {
  if tableLine >= s.tableHeight {
   break
  }
  // Move the cursor to the correct line of the non-scrolling region
  fmt.Fprint(s.writer, ansi.setCursor(scrollingHeight+1+tableLine, 1))

  if tableLine < len(s.runningActions) {
   runningAction := s.runningActions[tableLine]

   seconds := int(time.Since(runningAction.startTime).Round(time.Second).Seconds())

   desc := runningAction.action.Description
   if desc == "" {
    desc = runningAction.action.Command
   }

   color := ""
   if seconds >= 60 {
    color = ansi.red() + ansi.bold()
   } else if seconds >= 30 {
    color = ansi.yellow() + ansi.bold()
   }

   durationStr := fmt.Sprintf("   %2d:%02d ", seconds/60, seconds%60)
   desc = elide(desc, s.termWidth-len(durationStr))
   durationStr = color + durationStr + ansi.regular()
   fmt.Fprint(s.writer, durationStr, desc)
  }
  fmt.Fprint(s.writer, ansi.clearToEndOfLine())
 }

 // Move the cursor back to the last line of the scrolling region
 fmt.Fprint(s.writer, ansi.setCursor(scrollingHeight, 1))
}

var ansi = ansiImpl{}

type ansiImpl struct{}

func (ansiImpl) clearToEndOfLine() string {
 return "\x1b[K"
}

func (ansiImpl) setCursor(row, column int) string {
 // Direct cursor address
 return fmt.Sprintf("\x1b[%d;%dH", row, column)
}

func (ansiImpl) setScrollingMargins(top, bottom int) string {
 // Set Top and Bottom Margins DECSTBM
 return fmt.Sprintf("\x1b[%d;%dr", top, bottom)
}

func (ansiImpl) resetScrollingMargins() string {
 // Set Top and Bottom Margins DECSTBM
 return fmt.Sprintf("\x1b[r")
}

func (ansiImpl) red() string {
 return "\x1b[31m"
}

func (ansiImpl) yellow() string {
 return "\x1b[33m"
}

func (ansiImpl) bold() string {
 return "\x1b[1m"
}

func (ansiImpl) regular() string {
 return "\x1b[0m"
}

func (ansiImpl) showCursor() string {
 return "\x1b[?25h"
}

func (ansiImpl) hideCursor() string {
 return "\x1b[?25l"
}

func (ansiImpl) panDown(lines int) string {
 return fmt.Sprintf("\x1b[%dS", lines)
}

func (ansiImpl) panUp(lines int) string {
 return fmt.Sprintf("\x1b[%dT", lines)
}

[Dauer der Verarbeitung: 0.3 Sekunden, vorverarbeitet 2026-06-28]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik