Source file src/cmd/go/internal/load/printer.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package load
     6  
     7  import (
     8  	"cmd/go/internal/base"
     9  	"cmd/go/internal/cfg"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"strings"
    15  	"sync"
    16  )
    17  
    18  // A Printer reports output about a Package.
    19  type Printer interface {
    20  	// Printf reports output from building pkg. The arguments are of the form
    21  	// expected by [fmt.Printf].
    22  	//
    23  	// pkg may be nil if this output is not associated with the build of a
    24  	// particular package.
    25  	//
    26  	// The caller is responsible for checking if printing output is appropriate,
    27  	// for example by checking cfg.BuildN or cfg.BuildV.
    28  	Printf(pkg *Package, format string, args ...any)
    29  
    30  	// Errorf prints output in the form of `log.Errorf` and reports that
    31  	// building pkg failed.
    32  	//
    33  	// This ensures the output is terminated with a new line if there's any
    34  	// output, but does not do any other formatting. Callers should generally
    35  	// use a higher-level output abstraction, such as (*Shell).reportCmd.
    36  	//
    37  	// pkg may be nil if this output is not associated with the build of a
    38  	// particular package.
    39  	//
    40  	// This sets the process exit status to 1.
    41  	Errorf(pkg *Package, format string, args ...any)
    42  }
    43  
    44  // DefaultPrinter returns the default Printer.
    45  func DefaultPrinter() Printer {
    46  	return defaultPrinter()
    47  }
    48  
    49  var defaultPrinter = sync.OnceValue(func() Printer {
    50  	if cfg.BuildJSON {
    51  		return NewJSONPrinter(os.Stdout)
    52  	}
    53  	return &TextPrinter{os.Stderr}
    54  })
    55  
    56  func ensureNewline(s string) string {
    57  	if s == "" {
    58  		return ""
    59  	}
    60  	if strings.HasSuffix(s, "\n") {
    61  		return s
    62  	}
    63  	return s + "\n"
    64  }
    65  
    66  // A TextPrinter emits text format output to Writer.
    67  type TextPrinter struct {
    68  	Writer io.Writer
    69  }
    70  
    71  func (p *TextPrinter) Printf(_ *Package, format string, args ...any) {
    72  	fmt.Fprintf(p.Writer, format, args...)
    73  }
    74  
    75  func (p *TextPrinter) Errorf(_ *Package, format string, args ...any) {
    76  	fmt.Fprint(p.Writer, ensureNewline(fmt.Sprintf(format, args...)))
    77  	base.SetExitStatus(1)
    78  }
    79  
    80  // A JSONPrinter emits output about a build in JSON format.
    81  type JSONPrinter struct {
    82  	enc *json.Encoder
    83  }
    84  
    85  func NewJSONPrinter(w io.Writer) *JSONPrinter {
    86  	return &JSONPrinter{json.NewEncoder(w)}
    87  }
    88  
    89  type jsonBuildEvent struct {
    90  	ImportPath string
    91  	Action     string
    92  	Output     string `json:",omitempty"` // Non-empty if Action == “build-output”
    93  }
    94  
    95  func (p *JSONPrinter) Printf(pkg *Package, format string, args ...any) {
    96  	ev := &jsonBuildEvent{
    97  		Action: "build-output",
    98  		Output: fmt.Sprintf(format, args...),
    99  	}
   100  	if ev.Output == "" {
   101  		// There's no point in emitting a completely empty output event.
   102  		return
   103  	}
   104  	if pkg != nil {
   105  		ev.ImportPath = pkg.Desc()
   106  	}
   107  	p.enc.Encode(ev)
   108  }
   109  
   110  func (p *JSONPrinter) Errorf(pkg *Package, format string, args ...any) {
   111  	s := ensureNewline(fmt.Sprintf(format, args...))
   112  	// For clarity, emit each line as a separate output event.
   113  	for len(s) > 0 {
   114  		i := strings.IndexByte(s, '\n')
   115  		p.Printf(pkg, "%s", s[:i+1])
   116  		s = s[i+1:]
   117  	}
   118  	ev := &jsonBuildEvent{
   119  		Action: "build-fail",
   120  	}
   121  	if pkg != nil {
   122  		ev.ImportPath = pkg.Desc()
   123  	}
   124  	p.enc.Encode(ev)
   125  	base.SetExitStatus(1)
   126  }
   127  

View as plain text