Transfer completed Go output without copies

This commit is contained in:
2026-10-09 01:44:33 +00:00
parent 39196a525c
commit 50d3573257
7 changed files with 226 additions and 21 deletions

View File

@@ -105,7 +105,7 @@ func PrepareBuildDependencies(ctx context.Context, directory string) error {
Stderr: captured,
})
if err != nil {
if output := strings.TrimSpace(string(captured.Bytes())); output != "" {
if output := strings.TrimSpace(string(captured.take())); output != "" {
return fmt.Errorf("go mod download: %w: %s", err, output)
}
return fmt.Errorf("go mod download: %w", err)
@@ -121,8 +121,19 @@ func (tool *Tool) HostTarget() Target {
// Run executes a Go command with ReleaseKit's shared offline baseline. Native
// commands use the host target and effective cgo configuration. Static commands
// use target and disable cgo. Output, when supplied, receives stdout and stderr
// as the command runs; the returned bytes retain both streams for diagnostics.
func (tool *Tool) Run(ctx context.Context, directory string, mode Mode, target Target, output io.Writer, args ...string) ([]byte, error) {
// as the command runs.
func (tool *Tool) Run(ctx context.Context, directory string, mode Mode, target Target, output io.Writer, args ...string) error {
_, err := tool.execute(ctx, directory, mode, target, output, false, args...)
return err
}
// Output executes a Go command and returns its combined stdout and stderr.
// Output, when supplied, also receives the bytes as the command runs.
func (tool *Tool) Output(ctx context.Context, directory string, mode Mode, target Target, output io.Writer, args ...string) ([]byte, error) {
return tool.execute(ctx, directory, mode, target, output, true, args...)
}
func (tool *Tool) execute(ctx context.Context, directory string, mode Mode, target Target, output io.Writer, keepOutput bool, args ...string) ([]byte, error) {
if len(args) == 0 {
return nil, errors.New("Go command requires arguments")
}
@@ -136,14 +147,24 @@ func (tool *Tool) Run(ctx context.Context, directory string, mode Mode, target T
Stdout: captured,
Stderr: captured,
})
contents := captured.Bytes()
if forwardErr := captured.forwardingError(); forwardErr != nil {
err = errors.Join(err, fmt.Errorf("forward Go output: %w", forwardErr))
}
contents := captured.take()
if err != nil {
if len(contents) != 0 {
return contents, fmt.Errorf("go %s: %w: %s", strings.Join(args, " "), err, strings.TrimSpace(string(contents)))
return outputBytes(contents, keepOutput), fmt.Errorf("go %s: %w: %s", strings.Join(args, " "), err, strings.TrimSpace(string(contents)))
}
return contents, fmt.Errorf("go %s: %w", strings.Join(args, " "), err)
return outputBytes(contents, keepOutput), fmt.Errorf("go %s: %w", strings.Join(args, " "), err)
}
return contents, nil
return outputBytes(contents, keepOutput), nil
}
func outputBytes(contents []byte, keep bool) []byte {
if keep {
return contents
}
return nil
}
func (tool *Tool) environment(mode Mode, target Target) map[string]string {
@@ -206,9 +227,10 @@ func discover(ctx context.Context, directory string, environment map[string]stri
}
type combinedWriter struct {
mu sync.Mutex
output io.Writer
buffer bytes.Buffer
mu sync.Mutex
output io.Writer
buffer bytes.Buffer
forwardErr error
}
func (writer *combinedWriter) Write(contents []byte) (int, error) {
@@ -217,11 +239,29 @@ func (writer *combinedWriter) Write(contents []byte) (int, error) {
if writer.output == nil {
return writer.buffer.Write(contents)
}
return io.MultiWriter(&writer.buffer, writer.output).Write(contents)
if _, err := writer.buffer.Write(contents); err != nil {
return 0, err
}
written, err := writer.output.Write(contents)
if err == nil && written != len(contents) {
err = io.ErrShortWrite
}
if err != nil && writer.forwardErr == nil {
writer.forwardErr = err
}
return written, err
}
func (writer *combinedWriter) Bytes() []byte {
func (writer *combinedWriter) forwardingError() error {
writer.mu.Lock()
defer writer.mu.Unlock()
return append([]byte(nil), writer.buffer.Bytes()...)
return writer.forwardErr
}
// take transfers the completed capture to the caller. It must only be used
// after the child process has stopped writing.
func (writer *combinedWriter) take() []byte {
writer.mu.Lock()
defer writer.mu.Unlock()
return writer.buffer.Bytes()
}