}
}
- sh := work.NewShell("", fmt.Print)
+ sh := work.NewShell("", &load.TextPrinter{Writer: os.Stdout})
if cleanCache {
dir, _ := cache.DefaultDir()
return
}
- sh := work.NewShell("", fmt.Print)
+ sh := work.NewShell("", &load.TextPrinter{Writer: os.Stdout})
packageFile := map[string]bool{}
if p.Name != "main" {
all := PackageList(pkgs)
for _, p := range all {
if p.Error != nil {
- base.Errorf("%v", p.Error)
+ DefaultPrinter().Errorf(p, "%v", p.Error)
}
}
}
--- /dev/null
+// 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"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "sync"
+)
+
+// A Printer reports output about a Package.
+type Printer interface {
+ // Output reports output from building pkg. The arguments are of the form
+ // expected by fmt.Print.
+ //
+ // 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.
+ Output(pkg *Package, 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 {
+ // TODO: This will return a JSON printer once that's an option.
+ 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) Output(_ *Package, args ...any) {
+ fmt.Fprint(p.Writer, args...)
+}
+
+func (p *TextPrinter) Errorf(_ *Package, format string, args ...any) {
+ fmt.Fprint(p.Writer, ensureNewline(fmt.Sprintf(format, args...)))
+ base.SetExitStatus(1)
+}
package work
import (
- "fmt"
"internal/testenv"
"io/fs"
"os"
// of `(*Shell).ShowCmd` afterwards as a sanity check.
cfg.BuildX = true
var cmdBuf strings.Builder
- sh := NewShell("", func(a ...any) (int, error) {
- return cmdBuf.WriteString(fmt.Sprint(a...))
- })
+ sh := NewShell("", &load.TextPrinter{Writer: &cmdBuf})
setgiddir := t.TempDir()
if a.Package != nil && (!errors.As(err, &ipe) || ipe.ImportPath() != a.Package.ImportPath) {
err = fmt.Errorf("%s: %v", a.Package.ImportPath, err)
}
- base.Errorf("%s", err)
+ sh := b.Shell(a)
+ sh.Errorf("%s", err)
}
a.Failed = true
}
workDir string // $WORK, immutable
printLock sync.Mutex
- printFunc func(args ...any) (int, error)
+ printer load.Printer
scriptDir string // current directory in printed script
mkdirCache par.Cache[string, error] // a cache of created directories
// NewShell returns a new Shell.
//
-// Shell will internally serialize calls to the print function.
-// If print is nil, it defaults to printing to stderr.
-func NewShell(workDir string, print func(a ...any) (int, error)) *Shell {
- if print == nil {
- print = func(a ...any) (int, error) {
- return fmt.Fprint(os.Stderr, a...)
- }
+// Shell will internally serialize calls to the printer.
+// If printer is nil, it uses load.DefaultPrinter.
+func NewShell(workDir string, printer load.Printer) *Shell {
+ if printer == nil {
+ printer = load.DefaultPrinter()
}
shared := &shellShared{
- workDir: workDir,
- printFunc: print,
+ workDir: workDir,
+ printer: printer,
}
return &Shell{shellShared: shared}
}
+func (sh *Shell) pkg() *load.Package {
+ if sh.action == nil {
+ return nil
+ }
+ return sh.action.Package
+}
+
// Print emits a to this Shell's output stream, formatting it like fmt.Print.
// It is safe to call concurrently.
func (sh *Shell) Print(a ...any) {
sh.printLock.Lock()
defer sh.printLock.Unlock()
- sh.printFunc(a...)
+ sh.printer.Output(sh.pkg(), a...)
}
func (sh *Shell) printLocked(a ...any) {
- sh.printFunc(a...)
+ sh.printer.Output(sh.pkg(), a...)
+}
+
+// Errorf reports an error on sh's package and sets the process exit status to 1.
+func (sh *Shell) Errorf(format string, a ...any) {
+ sh.printLock.Lock()
+ defer sh.printLock.Unlock()
+ sh.printer.Errorf(sh.pkg(), format, a...)
}
// WithAction returns a Shell identical to sh, but bound to Action a.