blob: d698a78aa2d92be2948a7064fe37e0ed137d60df [file] [log] [blame] [edit]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package load
import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"sync"
)
// A Printer reports output about a Package.
type Printer interface {
// Printf reports output from building pkg. The arguments are of the form
// expected by [fmt.Printf].
//
// pkg may be nil if this output is not associated with the build of a
// particular package.
//
// The caller is responsible for checking if printing output is appropriate,
// for example by checking cfg.BuildN or cfg.BuildV.
Printf(pkg *Package, format string, args ...any)
// Errorf prints output in the form of `log.Errorf` and reports that
// building pkg failed.
//
// This ensures the output is terminated with a new line if there's any
// output, but does not do any other formatting. Callers should generally
// use a higher-level output abstraction, such as (*Shell).reportCmd.
//
// pkg may be nil if this output is not associated with the build of a
// particular package.
//
// This sets the process exit status to 1.
Errorf(pkg *Package, format string, args ...any)
}
// DefaultPrinter returns the default Printer.
func DefaultPrinter() Printer {
return defaultPrinter()
}
var defaultPrinter = sync.OnceValue(func() Printer {
if cfg.BuildJSON {
return NewJSONPrinter(os.Stdout)
}
return &TextPrinter{os.Stderr}
})
func ensureNewline(s string) string {
if s == "" {
return ""
}
if strings.HasSuffix(s, "\n") {
return s
}
return s + "\n"
}
// A TextPrinter emits text format output to Writer.
type TextPrinter struct {
Writer io.Writer
}
func (p *TextPrinter) Printf(_ *Package, format string, args ...any) {
fmt.Fprintf(p.Writer, format, args...)
}
func (p *TextPrinter) Errorf(_ *Package, format string, args ...any) {
fmt.Fprint(p.Writer, ensureNewline(fmt.Sprintf(format, args...)))
base.SetExitStatus(1)
}
// A JSONPrinter emits output about a build in JSON format.
type JSONPrinter struct {
enc *json.Encoder
}
func NewJSONPrinter(w io.Writer) *JSONPrinter {
return &JSONPrinter{json.NewEncoder(w)}
}
type jsonBuildEvent struct {
ImportPath string
Action string
Output string `json:",omitempty"` // Non-empty if Action == “build-output”
}
func (p *JSONPrinter) Printf(pkg *Package, format string, args ...any) {
ev := &jsonBuildEvent{
Action: "build-output",
Output: fmt.Sprintf(format, args...),
}
if ev.Output == "" {
// There's no point in emitting a completely empty output event.
return
}
if pkg != nil {
ev.ImportPath = pkg.Desc()
}
p.enc.Encode(ev)
}
func (p *JSONPrinter) Errorf(pkg *Package, format string, args ...any) {
s := ensureNewline(fmt.Sprintf(format, args...))
// For clarity, emit each line as a separate output event.
for len(s) > 0 {
i := strings.IndexByte(s, '\n')
p.Printf(pkg, "%s", s[:i+1])
s = s[i+1:]
}
ev := &jsonBuildEvent{
Action: "build-fail",
}
if pkg != nil {
ev.ImportPath = pkg.Desc()
}
p.enc.Encode(ev)
base.SetExitStatus(1)
}