Eine aufbereitete Darstellung der Quelle

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

Benutzer

Quelle  ninja.go   Sprache: unbekannt

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

// Copyright 2018 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 status

import (
 "bufio"
 "fmt"
 "io"
 "os"
 "regexp"
 "runtime"
 "strings"
 "syscall"
 "time"

 "google.golang.org/protobuf/proto"

 "android/soong/ui/logger"
 "android/soong/ui/status/ninja_frontend"
)

// NewNinjaReader reads the protobuf frontend format from ninja and translates it
// into calls on the ToolStatus API.
func NewNinjaReader(ctx logger.Logger, status ToolStatus, fifo string, sigNumFunc func() os.Signal) *NinjaReader {
 os.Remove(fifo)

 if err := syscall.Mkfifo(fifo, 0666); err != nil {
  ctx.Fatalf("Failed to mkfifo(%q): %v", fifo, err)
 }

 n := &NinjaReader{
  status:     status,
  fifo:       fifo,
  sigNumFunc: sigNumFunc,
  forceClose: make(chan bool),
  done:       make(chan bool),
  cancelOpen: make(chan bool),
  running:    make(map[uint32]*Action),
 }

 go n.run()

 return n
}

type NinjaReader struct {
 status       ToolStatus
 fifo         string
 sigNumFunc   func() os.Signal
 forceClose   chan bool
 done         chan bool
 cancelOpen   chan bool
 running      map[uint32]*Action
 hasAnyOutput bool
}

const NINJA_READER_CLOSE_TIMEOUT = 5 * time.Second

// Close waits for NinjaReader to finish reading from the fifo, or 5 seconds.
func (n *NinjaReader) Close() {
 // Signal the goroutine to stop if it is blocking opening the fifo.
 close(n.cancelOpen)

 closed := false

 // Ninja should already have exited or been killed, wait 5 seconds for the FIFO to be closed and any
 // remaining messages to be processed through the NinjaReader.run goroutine.
 timeoutCh := time.After(NINJA_READER_CLOSE_TIMEOUT)
 select {
 case <-n.done:
  closed = true
 case <-timeoutCh:
  // Channel is not closed yet
 }

 if !closed {
  n.status.Error(fmt.Sprintf("ninja fifo didn't finish after %s", NINJA_READER_CLOSE_TIMEOUT.String()))

  // Force close the reader even if the FIFO didn't close.
  close(n.forceClose)

  // Wait again for the reader thread to acknowledge the close before giving up and assuming it isn't going
  // to send anything else.
  timeoutCh = time.After(NINJA_READER_CLOSE_TIMEOUT)
  select {
  case <-n.done:
   closed = true
  case <-timeoutCh:
   // Channel is not closed yet
  }
 }

 if !closed {
  n.status.Verbose(fmt.Sprintf("ninja fifo didn't finish even after force closing after %s", NINJA_READER_CLOSE_TIMEOUT.String()))
 }

 if n.sigNumFunc() != os.Interrupt {
  err := fmt.Errorf("error: action cancelled when ninja exited")
  for _, action := range n.running {
   n.status.FinishAction(ActionResult{
    Action: action,
    Output: err.Error(),
    Error:  err,
   })
  }
 }
}

func (n *NinjaReader) run() {
 defer close(n.done)

 // Opening the fifo can block forever if ninja never opens the write end, do it in a goroutine so this
 // method can exit on cancel.
 fileCh := make(chan *os.File)
 go func() {
  f, err := os.Open(n.fifo)
  if err != nil {
   n.status.Error(fmt.Sprintf("Failed to open fifo: %v", err))
   close(fileCh)
   return
  }
  fileCh <- f
 }()

 var f *os.File

 select {
 case f = <-fileCh:
  // Nothing
 case <-n.cancelOpen:
  return
 }

 defer f.Close()

 r := bufio.NewReader(f)

 msgChan := make(chan *ninja_frontend.Status)

 // Read from the ninja fifo and decode the protobuf in a goroutine so the main NinjaReader.run goroutine
 // can listen
 go func() {
  defer close(msgChan)
  for {
   size, err := readVarInt(r)
   if err != nil {
    if err != io.EOF {
     n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
    }
    return
   }

   buf := make([]byte, size)
   _, err = io.ReadFull(r, buf)
   if err != nil {
    if err == io.EOF {
     n.status.Print(fmt.Sprintf("Missing message of size %d from ninja\n", size))
    } else {
     n.status.Error(fmt.Sprintf("Got error reading from ninja: %s", err))
    }
    return
   }

   msg := &ninja_frontend.Status{}
   err = proto.Unmarshal(buf, msg)
   if err != nil {
    n.status.Print(fmt.Sprintf("Error reading message from ninja: %v", err))
    continue
   }

   msgChan <- msg
  }
 }()

 for {
  var msg *ninja_frontend.Status
  var msgOk bool
  select {
  case <-n.forceClose:
   // Close() has been called, but the reader goroutine didn't get EOF after 5 seconds
   break
  case msg, msgOk = <-msgChan:
   // msg is ready or closed
  }

  if !msgOk {
   // msgChan is closed
   break
  }

  if msg.BuildStarted != nil {
   parallelism := uint32(runtime.NumCPU())
   if msg.BuildStarted.GetParallelism() > 0 {
    parallelism = msg.BuildStarted.GetParallelism()
   }
   // It is estimated from total time / parallelism assumming the build is packing enough.
   estimatedDurationFromTotal := time.Duration(msg.BuildStarted.GetEstimatedTotalTime()/parallelism) * time.Millisecond
   // It is estimated from critical path time which is useful for small size build.
   estimatedDurationFromCriticalPath := time.Duration(msg.BuildStarted.GetCriticalPathTime()) * time.Millisecond
   // Select the longer one.
   estimatedDuration := max(estimatedDurationFromTotal, estimatedDurationFromCriticalPath)

   if estimatedDuration > 0 {
    n.status.SetEstimatedTime(time.Now().Add(estimatedDuration))
    n.status.Verbose(fmt.Sprintf("parallelism: %d, estimated from total time: %s, critical path time: %s",
     parallelism,
     estimatedDurationFromTotal,
     estimatedDurationFromCriticalPath))

   }
  }
  if msg.TotalEdges != nil {
   n.status.SetTotalActions(int(msg.TotalEdges.GetTotalEdges()))
  }
  if msg.EdgeStarted != nil {
   action := &Action{
    Description:   msg.EdgeStarted.GetDesc(),
    Outputs:       msg.EdgeStarted.Outputs,
    Inputs:        msg.EdgeStarted.Inputs,
    Command:       msg.EdgeStarted.GetCommand(),
    ChangedInputs: msg.EdgeStarted.ChangedInputs,
   }
   n.status.StartAction(action)
   n.running[msg.EdgeStarted.GetId()] = action
  }
  if msg.EdgeFinished != nil {
   if started, ok := n.running[msg.EdgeFinished.GetId()]; ok {
    delete(n.running, msg.EdgeFinished.GetId())

    if !msg.EdgeFinished.GetCanceled() {
     var err error
     exitCode := int(msg.EdgeFinished.GetStatus())
     if exitCode != 0 {
      err = fmt.Errorf("exited with code: %d", exitCode)
     }

     rawOutput := msg.EdgeFinished.GetOutput()
     outputWithErrorHint := errorHintGenerator.GetOutputWithErrorHint(rawOutput, exitCode)
     n.status.FinishAction(ActionResult{
      Action: started,
      Output: outputWithErrorHint,
      Error:  err,
      Stats: ActionResultStats{
       UserTime:                   msg.EdgeFinished.GetUserTime(),
       SystemTime:                 msg.EdgeFinished.GetSystemTime(),
       MaxRssKB:                   msg.EdgeFinished.GetMaxRssKb(),
       MinorPageFaults:            msg.EdgeFinished.GetMinorPageFaults(),
       MajorPageFaults:            msg.EdgeFinished.GetMajorPageFaults(),
       IOInputKB:                  msg.EdgeFinished.GetIoInputKb(),
       IOOutputKB:                 msg.EdgeFinished.GetIoOutputKb(),
       VoluntaryContextSwitches:   msg.EdgeFinished.GetVoluntaryContextSwitches(),
       InvoluntaryContextSwitches: msg.EdgeFinished.GetInvoluntaryContextSwitches(),
       Tags:                       msg.EdgeFinished.GetTags(),
      },
     })

     n.hasAnyOutput = n.hasAnyOutput || len(rawOutput) > 0
    }
   }
  }
  if msg.Message != nil {
   message := "ninja: " + msg.Message.GetMessage()
   switch msg.Message.GetLevel() {
   case ninja_frontend.Status_Message_INFO:
    n.status.Status(message)
   case ninja_frontend.Status_Message_WARNING:
    n.status.Print("warning: " + message)
   case ninja_frontend.Status_Message_ERROR:
    n.status.Error(message)
   case ninja_frontend.Status_Message_DEBUG:
    n.status.Verbose(message)
   default:
    n.status.Print(message)
   }
  }
  if msg.BuildFinished != nil {
   n.status.Finish()
  }
 }
}

// Returns true if any command run by ninja had any output to stdout/stderr. Be sure to only
// call this after close() to ensure all commands have been seen.
func (n *NinjaReader) HasAnyOutput() bool {
 return n.hasAnyOutput
}

func readVarInt(r *bufio.Reader) (int, error) {
 ret := 0
 shift := uint(0)

 for {
  b, err := r.ReadByte()
  if err != nil {
   return 0, err
  }

  ret += int(b&0x7f) << (shift * 7)
  if b&0x80 == 0 {
   break
  }
  shift += 1
  if shift > 4 {
   return 0, fmt.Errorf("Expected varint32 length-delimited message")
  }
 }

 return ret, nil
}

// key is pattern in stdout/stderr
// value is error hint
var allErrorHints = map[string]string{
 "Read-only file system": `Write to a read-only file system detected. Possible fixes include
1. Generate file directly to out/ which is ReadWrite, #recommend solution
2. BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST := <my/path/1> <my/path/2> #discouraged, subset of source tree will be RW
3. BUILD_BROKEN_SRC_DIR_IS_WRITABLE := true #highly discouraged, entire source tree will be RW
`,
}
var errorHintGenerator = *newErrorHintGenerator(allErrorHints)

type ErrorHintGenerator struct {
 allErrorHints                map[string]string
 allErrorHintPatternsCompiled *regexp.Regexp
}

func newErrorHintGenerator(allErrorHints map[string]string) *ErrorHintGenerator {
 var allErrorHintPatterns []string
 for errorHintPattern, _ := range allErrorHints {
  allErrorHintPatterns = append(allErrorHintPatterns, errorHintPattern)
 }
 allErrorHintPatternsRegex := strings.Join(allErrorHintPatterns[:], "|")
 re := regexp.MustCompile(allErrorHintPatternsRegex)
 return &ErrorHintGenerator{
  allErrorHints:                allErrorHints,
  allErrorHintPatternsCompiled: re,
 }
}

func (errorHintGenerator *ErrorHintGenerator) GetOutputWithErrorHint(rawOutput string, buildExitCode int) string {
 if buildExitCode == 0 {
  return rawOutput
 }
 errorHint := errorHintGenerator.getErrorHint(rawOutput)
 if errorHint == nil {
  return rawOutput
 }
 return rawOutput + *errorHint
}

// Returns the error hint corresponding to the FIRST match in raw output
func (errorHintGenerator *ErrorHintGenerator) getErrorHint(rawOutput string) *string {
 firstMatch := errorHintGenerator.allErrorHintPatternsCompiled.FindString(rawOutput)
 if _, found := errorHintGenerator.allErrorHints[firstMatch]; found {
  errorHint := errorHintGenerator.allErrorHints[firstMatch]
  return &errorHint
 }
 return nil
}

[Dauer der Verarbeitung: 0.24 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