| // 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) |
| } |