Bound retained Go command diagnostics

This commit is contained in:
2026-11-02 03:31:59 +00:00
parent 2b23574ece
commit 9cb48a70b7
5 changed files with 230 additions and 45 deletions

View File

@@ -95,7 +95,7 @@ func New(ctx context.Context, directory string) (*Tool, error) {
// local Go toolchain. It preserves caller module-network settings and caches,
// while disabling workspace discovery and automatic toolchain downloads.
func PrepareBuildDependencies(ctx context.Context, directory string) error {
captured := &combinedWriter{}
captured := &combinedWriter{mode: captureStatus}
err := process.Run(ctx, process.Invocation{
Args: []string{"go", "mod", "download", "-modfile="},
Dir: directory,
@@ -107,7 +107,8 @@ func PrepareBuildDependencies(ctx context.Context, directory string) error {
Stderr: captured,
})
if err != nil {
if output := strings.TrimSpace(string(captured.take())); output != "" {
omitted := captured.omitted()
if output := diagnosticText(captured.take(), omitted); output != "" {
return fmt.Errorf("go mod download: %w: %s", err, output)
}
return fmt.Errorf("go mod download: %w", err)
@@ -140,7 +141,11 @@ func (tool *Tool) execute(ctx context.Context, directory string, mode Mode, targ
return nil, errors.New("Go command requires arguments")
}
environment := tool.environment(mode, target)
captured := &combinedWriter{output: output}
modeCapture := captureStatus
if keepOutput {
modeCapture = captureFull
}
captured := &combinedWriter{output: output, mode: modeCapture}
commandArgs := append([]string{"go"}, args...)
err := process.Run(ctx, process.Invocation{
Args: commandArgs,
@@ -152,16 +157,28 @@ func (tool *Tool) execute(ctx context.Context, directory string, mode Mode, targ
if forwardErr := captured.forwardingError(); forwardErr != nil {
err = errors.Join(err, fmt.Errorf("forward Go output: %w", forwardErr))
}
if err == nil && !keepOutput {
return nil, nil
}
omitted := captured.omitted()
contents := captured.take()
if err != nil {
if len(contents) != 0 {
return outputBytes(contents, keepOutput), fmt.Errorf("go %s: %w: %s", strings.Join(args, " "), err, strings.TrimSpace(string(contents)))
if diagnostic := diagnosticText(contents, omitted); diagnostic != "" {
return outputBytes(contents, keepOutput), fmt.Errorf("go %s: %w: %s", strings.Join(args, " "), err, diagnostic)
}
return outputBytes(contents, keepOutput), fmt.Errorf("go %s: %w", strings.Join(args, " "), err)
}
return outputBytes(contents, keepOutput), nil
}
func diagnosticText(contents []byte, omitted bool) string {
text := strings.TrimSpace(string(contents))
if omitted {
return "[earlier Go output omitted]\n" + text
}
return text
}
func outputBytes(contents []byte, keep bool) []byte {
if keep {
return contents
@@ -232,17 +249,63 @@ type combinedWriter struct {
mu sync.Mutex
output io.Writer
buffer bytes.Buffer
mode captureMode
tail []byte
head int
truncated bool
forwardErr error
}
type captureMode uint8
const (
captureFull captureMode = iota
captureStatus
statusDiagnosticLimit = 1 << 20
)
func (writer *combinedWriter) capture(contents []byte) {
if writer.mode == captureFull {
_, _ = writer.buffer.Write(contents)
return
}
if len(contents) >= statusDiagnosticLimit {
writer.truncated = writer.truncated || len(writer.tail) != 0 || len(contents) > statusDiagnosticLimit
if cap(writer.tail) < statusDiagnosticLimit {
writer.tail = make([]byte, statusDiagnosticLimit)
} else {
writer.tail = writer.tail[:statusDiagnosticLimit]
}
copy(writer.tail, contents[len(contents)-statusDiagnosticLimit:])
writer.head = 0
return
}
if available := statusDiagnosticLimit - len(writer.tail); available > 0 {
n := min(available, len(contents))
if needed := len(writer.tail) + n; needed > cap(writer.tail) {
capacity := min(statusDiagnosticLimit, max(needed, 2*cap(writer.tail)))
grown := make([]byte, len(writer.tail), capacity)
copy(grown, writer.tail)
writer.tail = grown
}
writer.tail = append(writer.tail, contents[:n]...)
contents = contents[n:]
}
if len(contents) == 0 {
return
}
writer.truncated = true
n := copy(writer.tail[writer.head:], contents)
copy(writer.tail, contents[n:])
writer.head = (writer.head + len(contents)) % statusDiagnosticLimit
}
func (writer *combinedWriter) Write(contents []byte) (int, error) {
writer.mu.Lock()
defer writer.mu.Unlock()
writer.capture(contents)
if writer.output == nil {
return writer.buffer.Write(contents)
}
if _, err := writer.buffer.Write(contents); err != nil {
return 0, err
return len(contents), nil
}
written, err := writer.output.Write(contents)
if err == nil && written != len(contents) {
@@ -254,6 +317,12 @@ func (writer *combinedWriter) Write(contents []byte) (int, error) {
return written, err
}
func (writer *combinedWriter) omitted() bool {
writer.mu.Lock()
defer writer.mu.Unlock()
return writer.truncated
}
func (writer *combinedWriter) forwardingError() error {
writer.mu.Lock()
defer writer.mu.Unlock()
@@ -265,5 +334,18 @@ func (writer *combinedWriter) forwardingError() error {
func (writer *combinedWriter) take() []byte {
writer.mu.Lock()
defer writer.mu.Unlock()
return writer.buffer.Bytes()
if writer.mode == captureFull {
contents := writer.buffer.Bytes()
writer.buffer = bytes.Buffer{}
return contents
}
contents := writer.tail
if writer.head != 0 {
contents = make([]byte, 0, len(writer.tail))
contents = append(contents, writer.tail[writer.head:]...)
contents = append(contents, writer.tail[:writer.head]...)
}
writer.tail = nil
writer.head = 0
return contents
}