From c5da849a2df0860b2eac18625b98210fbd9acceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Fri, 3 Oct 2025 13:30:04 +0200 Subject: [PATCH 01/11] Separate closing functions --- logio/pipe_wiring.go | 45 ++++++++++---------------------------- xcodecommand/xcbeautify.go | 6 ++++- xcodecommand/xcpretty.go | 8 +++++-- xcpretty/xcpretty.go | 6 ++++- 4 files changed, 28 insertions(+), 37 deletions(-) diff --git a/logio/pipe_wiring.go b/logio/pipe_wiring.go index 2a210966..8a9c5dd8 100644 --- a/logio/pipe_wiring.go +++ b/logio/pipe_wiring.go @@ -2,8 +2,6 @@ package logio import ( "bytes" - "errors" - "fmt" "io" "os" "regexp" @@ -21,15 +19,18 @@ type PipeWiring struct { ToolStdout io.WriteCloser ToolStderr io.WriteCloser - closer func() error + toolPipeW *io.PipeWriter + filter *PrefixFilter } -// Close closes the PipeWiring instances that needs to be closing as part of this instance. -// -// In reality it can only close the filter and the tool input as everything else is -// managed by a command or the os. -func (i *PipeWiring) Close() error { - return i.closer() +// CloseToolInput... +func (p *PipeWiring) CloseToolInput() error { + return p.toolPipeW.Close() +} + +// CloseFilter... +func (p *PipeWiring) CloseFilter() error { + return p.filter.Close() } // SetupPipeWiring creates a new PipeWiring instance that contains the usual @@ -62,30 +63,8 @@ func SetupPipeWiring(filter *regexp.Regexp) *PipeWiring { ToolStdin: toolPipeR, ToolStdout: os.Stdout, ToolStderr: os.Stderr, - closer: func() error { - // XcbuildRawout - no need to close - // XcbuildStdout - Multiwriter, meaning we need to close the subwriters - // XcbuildStderr - Multiwriter, meaning we need to close the subwriters - // ToolStdout - We are not closing stdout - // ToolSterr - We are not closing stderr - - var errStr string - - if err := bitrisePrefixFilter.Close(); err != nil { - errStr += fmt.Sprintf("failed to close log filter, error: %s", err.Error()) - } - if err := toolPipeW.Close(); err != nil { - if len(errStr) > 0 { - errStr += ", " - } - errStr += fmt.Sprintf("failed to close xcodebuild-xcpretty pipe, error: %s", err.Error()) - } - - if len(errStr) > 0 { - return errors.New(errStr) - } - return nil - }, + toolPipeW: toolPipeW, + filter: bitrisePrefixFilter, } } diff --git a/xcodecommand/xcbeautify.go b/xcodecommand/xcbeautify.go index 3517c7dd..ae493079 100644 --- a/xcodecommand/xcbeautify.go +++ b/xcodecommand/xcbeautify.go @@ -53,13 +53,17 @@ func (c *XcbeautifyRunner) Run(workDir string, xcodebuildArgs []string, xcbeauti }) defer func() { - if err := loggingIO.Close(); err != nil { + if err := loggingIO.CloseToolInput(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } if err := beautifyCmd.Wait(); err != nil { c.logger.Warnf("xcbeautify command failed: %s", err) } + + if err := loggingIO.CloseFilter(); err != nil { + c.logger.Warnf("logging IO failure, error: %s", err) + } }() c.logger.TPrintf("$ set -o pipefail && %s | %s", buildCmd.PrintableCommandArgs(), beautifyCmd.PrintableCommandArgs()) diff --git a/xcodecommand/xcpretty.go b/xcodecommand/xcpretty.go index b7868e94..4fe074f8 100644 --- a/xcodecommand/xcpretty.go +++ b/xcodecommand/xcpretty.go @@ -59,12 +59,16 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp }) defer func() { - if err := loggingIO.Close(); err != nil { + if err := loggingIO.CloseToolInput(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } if err := prettyCmd.Wait(); err != nil { - c.logger.Warnf("xcpretty command failed: %s", err) + c.logger.Warnf("xcbeautify command failed: %s", err) + } + + if err := loggingIO.CloseFilter(); err != nil { + c.logger.Warnf("logging IO failure, error: %s", err) } }() diff --git a/xcpretty/xcpretty.go b/xcpretty/xcpretty.go index 3221a3d3..86836ce3 100644 --- a/xcpretty/xcpretty.go +++ b/xcpretty/xcpretty.go @@ -67,13 +67,17 @@ func (c CommandModel) Run() (string, error) { // Always close xcpretty outputs defer func() { - if err := loggingIO.Close(); err != nil { + if err := loggingIO.CloseToolInput(); err != nil { fmt.Printf("logging IO failure, error: %s", err) } if err := prettyCmd.Wait(); err != nil { fmt.Printf("xcpretty command failed, error: %s", err) } + + if err := loggingIO.CloseFilter(); err != nil { + fmt.Printf("logging IO failure, error: %s", err) + } }() // Run From f9f8c724ec4cbd83e55917c96763322b36a8cb27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Fri, 3 Oct 2025 13:48:00 +0200 Subject: [PATCH 02/11] Move raw xcbuild logs after filter to ensure waiting for them --- logio/pipe_wiring.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/logio/pipe_wiring.go b/logio/pipe_wiring.go index 8a9c5dd8..e272a9ea 100644 --- a/logio/pipe_wiring.go +++ b/logio/pipe_wiring.go @@ -45,7 +45,7 @@ func SetupPipeWiring(filter *regexp.Regexp) *PipeWiring { // Add a buffer before stdout bufferedStdout := NewSink(os.Stdout) // Add a buffer before tool input - xcbuildLogs := NewSink(toolPipeW) + xcbuildLogs := NewSink(io.MultiWriter(&rawXcbuild, toolPipeW)) // Create a filter for [Bitrise ...] prefixes bitrisePrefixFilter := NewPrefixFilter( filter, @@ -53,13 +53,10 @@ func SetupPipeWiring(filter *regexp.Regexp) *PipeWiring { xcbuildLogs, ) - // Send raw xcbuild out to raw out and filter - rawInputDuplication := io.MultiWriter(&rawXcbuild, bitrisePrefixFilter) - return &PipeWiring{ XcbuildRawout: rawXcbuild, - XcbuildStdout: rawInputDuplication, - XcbuildStderr: rawInputDuplication, + XcbuildStdout: bitrisePrefixFilter, + XcbuildStderr: bitrisePrefixFilter, ToolStdin: toolPipeR, ToolStdout: os.Stdout, ToolStderr: os.Stderr, From d6776b8330ad71486cf6abac76e6644b48099ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Fri, 3 Oct 2025 14:22:53 +0200 Subject: [PATCH 03/11] Flush sinks after closing filter --- logio/pipe_wiring.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/logio/pipe_wiring.go b/logio/pipe_wiring.go index e272a9ea..d78cedb1 100644 --- a/logio/pipe_wiring.go +++ b/logio/pipe_wiring.go @@ -19,8 +19,10 @@ type PipeWiring struct { ToolStdout io.WriteCloser ToolStderr io.WriteCloser - toolPipeW *io.PipeWriter - filter *PrefixFilter + toolPipeW *io.PipeWriter + bufferedStdout *Sink + xcbuildLogs *Sink + filter *PrefixFilter } // CloseToolInput... @@ -30,7 +32,11 @@ func (p *PipeWiring) CloseToolInput() error { // CloseFilter... func (p *PipeWiring) CloseFilter() error { - return p.filter.Close() + err := p.filter.Close() + _ = p.xcbuildLogs.Close() + _ = p.bufferedStdout.Close() + + return err } // SetupPipeWiring creates a new PipeWiring instance that contains the usual @@ -61,7 +67,9 @@ func SetupPipeWiring(filter *regexp.Regexp) *PipeWiring { ToolStdout: os.Stdout, ToolStderr: os.Stderr, - toolPipeW: toolPipeW, - filter: bitrisePrefixFilter, + toolPipeW: toolPipeW, + bufferedStdout: bufferedStdout, + xcbuildLogs: xcbuildLogs, + filter: bitrisePrefixFilter, } } From 5ab68568d8da206324d431d1059d27319fa4e4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Fri, 3 Oct 2025 14:29:47 +0200 Subject: [PATCH 04/11] Swap order of closing the filter and the outputs --- xcodecommand/xcbeautify.go | 8 ++++---- xcodecommand/xcpretty.go | 8 ++++---- xcpretty/xcpretty.go | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/xcodecommand/xcbeautify.go b/xcodecommand/xcbeautify.go index ae493079..dcdd51f1 100644 --- a/xcodecommand/xcbeautify.go +++ b/xcodecommand/xcbeautify.go @@ -53,6 +53,10 @@ func (c *XcbeautifyRunner) Run(workDir string, xcodebuildArgs []string, xcbeauti }) defer func() { + if err := loggingIO.CloseFilter(); err != nil { + c.logger.Warnf("logging IO failure, error: %s", err) + } + if err := loggingIO.CloseToolInput(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } @@ -60,10 +64,6 @@ func (c *XcbeautifyRunner) Run(workDir string, xcodebuildArgs []string, xcbeauti if err := beautifyCmd.Wait(); err != nil { c.logger.Warnf("xcbeautify command failed: %s", err) } - - if err := loggingIO.CloseFilter(); err != nil { - c.logger.Warnf("logging IO failure, error: %s", err) - } }() c.logger.TPrintf("$ set -o pipefail && %s | %s", buildCmd.PrintableCommandArgs(), beautifyCmd.PrintableCommandArgs()) diff --git a/xcodecommand/xcpretty.go b/xcodecommand/xcpretty.go index 4fe074f8..dd446aef 100644 --- a/xcodecommand/xcpretty.go +++ b/xcodecommand/xcpretty.go @@ -59,6 +59,10 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp }) defer func() { + if err := loggingIO.CloseFilter(); err != nil { + c.logger.Warnf("logging IO failure, error: %s", err) + } + if err := loggingIO.CloseToolInput(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } @@ -66,10 +70,6 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp if err := prettyCmd.Wait(); err != nil { c.logger.Warnf("xcbeautify command failed: %s", err) } - - if err := loggingIO.CloseFilter(); err != nil { - c.logger.Warnf("logging IO failure, error: %s", err) - } }() c.logger.TPrintf("$ set -o pipefail && %s | %s", buildCmd.PrintableCommandArgs(), prettyCmd.PrintableCommandArgs()) diff --git a/xcpretty/xcpretty.go b/xcpretty/xcpretty.go index 86836ce3..a4b4a158 100644 --- a/xcpretty/xcpretty.go +++ b/xcpretty/xcpretty.go @@ -67,6 +67,10 @@ func (c CommandModel) Run() (string, error) { // Always close xcpretty outputs defer func() { + if err := loggingIO.CloseFilter(); err != nil { + fmt.Printf("logging IO failure, error: %s", err) + } + if err := loggingIO.CloseToolInput(); err != nil { fmt.Printf("logging IO failure, error: %s", err) } @@ -74,10 +78,6 @@ func (c CommandModel) Run() (string, error) { if err := prettyCmd.Wait(); err != nil { fmt.Printf("xcpretty command failed, error: %s", err) } - - if err := loggingIO.CloseFilter(); err != nil { - fmt.Printf("logging IO failure, error: %s", err) - } }() // Run From fba628fd39a3b2b99343a78a07e97921af63815b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Fri, 3 Oct 2025 14:38:31 +0200 Subject: [PATCH 05/11] Wait for filter done when closing sinks --- logio/pipe_wiring.go | 1 + 1 file changed, 1 insertion(+) diff --git a/logio/pipe_wiring.go b/logio/pipe_wiring.go index d78cedb1..c7af9556 100644 --- a/logio/pipe_wiring.go +++ b/logio/pipe_wiring.go @@ -33,6 +33,7 @@ func (p *PipeWiring) CloseToolInput() error { // CloseFilter... func (p *PipeWiring) CloseFilter() error { err := p.filter.Close() + <-p.filter.Done() _ = p.xcbuildLogs.Close() _ = p.bufferedStdout.Close() From 5b80c6a4c54b97f650ab36d1e4e86a7cfb8e156d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Thu, 9 Oct 2025 11:34:28 +0200 Subject: [PATCH 06/11] Use buffer pointer for xcoderaw --- logio/pipe_wiring.go | 23 ++++++++++------------- logio/pipe_wiring_test.go | 30 ++++++++++++++++++++++++++++++ logio/prefix_filter.go | 4 ++-- logio/prefix_filter_test.go | 2 +- xcodecommand/xcbeautify.go | 6 +----- xcodecommand/xcpretty.go | 6 +----- xcpretty/xcpretty.go | 6 +----- 7 files changed, 46 insertions(+), 31 deletions(-) create mode 100644 logio/pipe_wiring_test.go diff --git a/logio/pipe_wiring.go b/logio/pipe_wiring.go index c7af9556..c2254f34 100644 --- a/logio/pipe_wiring.go +++ b/logio/pipe_wiring.go @@ -12,7 +12,7 @@ import ( // users responsibility to choose between this and manual hooking of the in/outputs. // It also provides a convenient Close() method that only closes things that can/should be closed. type PipeWiring struct { - XcbuildRawout bytes.Buffer + XcbuildRawout *bytes.Buffer XcbuildStdout io.Writer XcbuildStderr io.Writer ToolStdin io.ReadCloser @@ -21,20 +21,16 @@ type PipeWiring struct { toolPipeW *io.PipeWriter bufferedStdout *Sink - xcbuildLogs *Sink + toolInSink *Sink filter *PrefixFilter } -// CloseToolInput... -func (p *PipeWiring) CloseToolInput() error { - return p.toolPipeW.Close() -} - -// CloseFilter... -func (p *PipeWiring) CloseFilter() error { +// Close ... +func (p *PipeWiring) Close() error { err := p.filter.Close() <-p.filter.Done() - _ = p.xcbuildLogs.Close() + _ = p.toolInSink.Close() + _ = p.toolPipeW.Close() _ = p.bufferedStdout.Close() return err @@ -45,14 +41,15 @@ func (p *PipeWiring) CloseFilter() error { // using a logging filter. func SetupPipeWiring(filter *regexp.Regexp) *PipeWiring { // Create a buffer to store raw xcbuild output - var rawXcbuild bytes.Buffer + rawXcbuild := bytes.NewBuffer(nil) // Pipe filtered logs to tool toolPipeR, toolPipeW := io.Pipe() // Add a buffer before stdout bufferedStdout := NewSink(os.Stdout) // Add a buffer before tool input - xcbuildLogs := NewSink(io.MultiWriter(&rawXcbuild, toolPipeW)) + toolInSink := NewSink(toolPipeW) + xcbuildLogs := io.MultiWriter(rawXcbuild, toolInSink) // Create a filter for [Bitrise ...] prefixes bitrisePrefixFilter := NewPrefixFilter( filter, @@ -70,7 +67,7 @@ func SetupPipeWiring(filter *regexp.Regexp) *PipeWiring { toolPipeW: toolPipeW, bufferedStdout: bufferedStdout, - xcbuildLogs: xcbuildLogs, + toolInSink: toolInSink, filter: bitrisePrefixFilter, } } diff --git a/logio/pipe_wiring_test.go b/logio/pipe_wiring_test.go new file mode 100644 index 00000000..91d3a98c --- /dev/null +++ b/logio/pipe_wiring_test.go @@ -0,0 +1,30 @@ +package logio_test + +import ( + "io" + "regexp" + "testing" + + "github.com/bitrise-io/go-xcode/v2/logio" + "github.com/stretchr/testify/assert" +) + +func TestPipeWiring(t *testing.T) { + sut := logio.SetupPipeWiring(regexp.MustCompile(`^\[Bitrise.*\].*`)) + + out := NewChanWriterCloser() + go func() { + _, _ = io.Copy(out, sut.ToolStdin) + _ = out.Close() + }() + + _, _ = sut.XcbuildStdout.Write([]byte(msg1)) + _, _ = sut.XcbuildStdout.Write([]byte(msg2)) + _, _ = sut.XcbuildStdout.Write([]byte(msg3)) + _, _ = sut.XcbuildStdout.Write([]byte(msg4)) + + _ = sut.Close() + + assert.Equal(t, msg1+msg4, sut.XcbuildRawout.String()) + assert.Equal(t, msg1+msg4, out.Messages()) +} diff --git a/logio/prefix_filter.go b/logio/prefix_filter.go index 2eb30720..18e1c97c 100644 --- a/logio/prefix_filter.go +++ b/logio/prefix_filter.go @@ -20,7 +20,7 @@ type PrefixFilter struct { pipeW *io.PipeWriter Matching *Sink - Filtered *Sink + Filtered io.Writer // closing closeOnce sync.Once @@ -48,7 +48,7 @@ func (p *PrefixFilter) ScannerError() <-chan error { return p.scannerError } // NewPrefixFilter returns a new PrefixFilter. Writes are based on line prefix. // // Note: Callers are responsible for closing intercepted and target writers that implement io.Closer -func NewPrefixFilter(prefixRegexp *regexp.Regexp, matching, filtered *Sink) *PrefixFilter { +func NewPrefixFilter(prefixRegexp *regexp.Regexp, matching *Sink, filtered io.Writer) *PrefixFilter { // This is the backing field of the bufio.ReadWriter pipeR, pipeW := io.Pipe() messageLost := make(chan error, 1) diff --git a/logio/prefix_filter_test.go b/logio/prefix_filter_test.go index ab56897e..38086a97 100644 --- a/logio/prefix_filter_test.go +++ b/logio/prefix_filter_test.go @@ -12,7 +12,7 @@ import ( const ( msg1 = "Log message without prefix\n" - msg2 = "[Bitrise Analytics] Log message with prefixs\n" + msg2 = "[Bitrise Analytics] Log message with prefix\n" msg3 = "[Bitrise Build Cache] Log message with prefix\n" msg4 = "Stuff [Bitrise Build Cache] Log message without prefix\n" ) diff --git a/xcodecommand/xcbeautify.go b/xcodecommand/xcbeautify.go index dcdd51f1..3517c7dd 100644 --- a/xcodecommand/xcbeautify.go +++ b/xcodecommand/xcbeautify.go @@ -53,11 +53,7 @@ func (c *XcbeautifyRunner) Run(workDir string, xcodebuildArgs []string, xcbeauti }) defer func() { - if err := loggingIO.CloseFilter(); err != nil { - c.logger.Warnf("logging IO failure, error: %s", err) - } - - if err := loggingIO.CloseToolInput(); err != nil { + if err := loggingIO.Close(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } diff --git a/xcodecommand/xcpretty.go b/xcodecommand/xcpretty.go index dd446aef..9a20ed18 100644 --- a/xcodecommand/xcpretty.go +++ b/xcodecommand/xcpretty.go @@ -59,11 +59,7 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp }) defer func() { - if err := loggingIO.CloseFilter(); err != nil { - c.logger.Warnf("logging IO failure, error: %s", err) - } - - if err := loggingIO.CloseToolInput(); err != nil { + if err := loggingIO.Close(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } diff --git a/xcpretty/xcpretty.go b/xcpretty/xcpretty.go index a4b4a158..3221a3d3 100644 --- a/xcpretty/xcpretty.go +++ b/xcpretty/xcpretty.go @@ -67,11 +67,7 @@ func (c CommandModel) Run() (string, error) { // Always close xcpretty outputs defer func() { - if err := loggingIO.CloseFilter(); err != nil { - fmt.Printf("logging IO failure, error: %s", err) - } - - if err := loggingIO.CloseToolInput(); err != nil { + if err := loggingIO.Close(); err != nil { fmt.Printf("logging IO failure, error: %s", err) } From 1adf4228ef087942e855ea5511cf06c107f3ca81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Mon, 13 Oct 2025 09:07:28 +0200 Subject: [PATCH 07/11] Wait for filtering and formatter to close before using the raw output --- xcodecommand/xcbeautify.go | 18 ++++++++---------- xcodecommand/xcpretty.go | 18 ++++++++---------- xcpretty/xcpretty.go | 19 ++++++++----------- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/xcodecommand/xcbeautify.go b/xcodecommand/xcbeautify.go index 3517c7dd..b42991e0 100644 --- a/xcodecommand/xcbeautify.go +++ b/xcodecommand/xcbeautify.go @@ -52,16 +52,6 @@ func (c *XcbeautifyRunner) Run(workDir string, xcodebuildArgs []string, xcbeauti Env: unbufferedIOEnv, }) - defer func() { - if err := loggingIO.Close(); err != nil { - c.logger.Warnf("logging IO failure, error: %s", err) - } - - if err := beautifyCmd.Wait(); err != nil { - c.logger.Warnf("xcbeautify command failed: %s", err) - } - }() - c.logger.TPrintf("$ set -o pipefail && %s | %s", buildCmd.PrintableCommandArgs(), beautifyCmd.PrintableCommandArgs()) err := buildCmd.Start() @@ -82,6 +72,14 @@ func (c *XcbeautifyRunner) Run(workDir string, xcodebuildArgs []string, xcbeauti } } + if err := loggingIO.Close(); err != nil { + c.logger.Warnf("logging IO failure, error: %s", err) + } + + if err := beautifyCmd.Wait(); err != nil { + c.logger.Warnf("xcbeautify command failed: %s", err) + } + return Output{ RawOut: loggingIO.XcbuildRawout.Bytes(), ExitCode: exitCode, diff --git a/xcodecommand/xcpretty.go b/xcodecommand/xcpretty.go index 9a20ed18..0eeaae52 100644 --- a/xcodecommand/xcpretty.go +++ b/xcodecommand/xcpretty.go @@ -58,16 +58,6 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp Stderr: loggingIO.ToolStderr, }) - defer func() { - if err := loggingIO.Close(); err != nil { - c.logger.Warnf("logging IO failure, error: %s", err) - } - - if err := prettyCmd.Wait(); err != nil { - c.logger.Warnf("xcbeautify command failed: %s", err) - } - }() - c.logger.TPrintf("$ set -o pipefail && %s | %s", buildCmd.PrintableCommandArgs(), prettyCmd.PrintableCommandArgs()) err := buildCmd.Start() @@ -88,6 +78,14 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp } } + if err := loggingIO.Close(); err != nil { + c.logger.Warnf("logging IO failure, error: %s", err) + } + + if err := prettyCmd.Wait(); err != nil { + c.logger.Warnf("xcbeautify command failed: %s", err) + } + return Output{ RawOut: loggingIO.XcbuildRawout.Bytes(), ExitCode: exitCode, diff --git a/xcpretty/xcpretty.go b/xcpretty/xcpretty.go index 3221a3d3..b2addbb4 100644 --- a/xcpretty/xcpretty.go +++ b/xcpretty/xcpretty.go @@ -65,17 +65,6 @@ func (c CommandModel) Run() (string, error) { Stderr: loggingIO.ToolStderr, }) - // Always close xcpretty outputs - defer func() { - if err := loggingIO.Close(); err != nil { - fmt.Printf("logging IO failure, error: %s", err) - } - - if err := prettyCmd.Wait(); err != nil { - fmt.Printf("xcpretty command failed, error: %s", err) - } - }() - // Run if err := xcodebuildCmd.Start(); err != nil { out := loggingIO.XcbuildRawout.String() @@ -91,6 +80,14 @@ func (c CommandModel) Run() (string, error) { return out, err } + if err := loggingIO.Close(); err != nil { + fmt.Printf("logging IO failure, error: %s", err) + } + + if err := prettyCmd.Wait(); err != nil { + fmt.Printf("xcpretty command failed, error: %s", err) + } + return loggingIO.XcbuildRawout.String(), nil } From 3163cb208120fbe9aab59b5420a29c38d2c11603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Mon, 13 Oct 2025 09:25:40 +0200 Subject: [PATCH 08/11] Add comments explaining the sync closes without defer --- xcodecommand/xcbeautify.go | 1 + xcodecommand/xcpretty.go | 1 + xcpretty/xcpretty.go | 1 + 3 files changed, 3 insertions(+) diff --git a/xcodecommand/xcbeautify.go b/xcodecommand/xcbeautify.go index b42991e0..52453123 100644 --- a/xcodecommand/xcbeautify.go +++ b/xcodecommand/xcbeautify.go @@ -72,6 +72,7 @@ func (c *XcbeautifyRunner) Run(workDir string, xcodebuildArgs []string, xcbeauti } } + // Wait for the filtering to finish if err := loggingIO.Close(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } diff --git a/xcodecommand/xcpretty.go b/xcodecommand/xcpretty.go index 0eeaae52..5739569e 100644 --- a/xcodecommand/xcpretty.go +++ b/xcodecommand/xcpretty.go @@ -78,6 +78,7 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp } } + // Wait for the filtering to finish if err := loggingIO.Close(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } diff --git a/xcpretty/xcpretty.go b/xcpretty/xcpretty.go index b2addbb4..4d30d774 100644 --- a/xcpretty/xcpretty.go +++ b/xcpretty/xcpretty.go @@ -80,6 +80,7 @@ func (c CommandModel) Run() (string, error) { return out, err } + // Wait for the filtering to finish if err := loggingIO.Close(); err != nil { fmt.Printf("logging IO failure, error: %s", err) } From 2d02e15ff513c5935c129ef5a75d998371c7b647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Mon, 13 Oct 2025 10:58:18 +0200 Subject: [PATCH 09/11] Separate filter closing and flushing from closing the rest of the piping --- logio/pipe_wiring.go | 28 ++++++++++++++++++++++------ xcodecommand/xcbeautify.go | 18 ++++++++++++------ xcodecommand/xcpretty.go | 18 ++++++++++++------ xcpretty/xcpretty.go | 32 +++++++++++++++++++++++++------- 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/logio/pipe_wiring.go b/logio/pipe_wiring.go index c2254f34..722de65b 100644 --- a/logio/pipe_wiring.go +++ b/logio/pipe_wiring.go @@ -2,9 +2,11 @@ package logio import ( "bytes" + "errors" "io" "os" "regexp" + "sync" ) // PipeWiring is a helper struct to define the setup and binding of tools and @@ -23,17 +25,29 @@ type PipeWiring struct { bufferedStdout *Sink toolInSink *Sink filter *PrefixFilter + + closeFilterOnce sync.Once +} + +// CloseFilter closes the filter and waits for it to finish +func (p *PipeWiring) CloseFilter() error { + err := error(nil) + p.closeFilterOnce.Do(func() { + err = p.filter.Close() + <-p.filter.Done() + + }) + return err } // Close ... func (p *PipeWiring) Close() error { - err := p.filter.Close() - <-p.filter.Done() - _ = p.toolInSink.Close() - _ = p.toolPipeW.Close() - _ = p.bufferedStdout.Close() + filterErr := p.CloseFilter() + toolSinkErr := p.toolInSink.Close() + pipeWErr := p.toolPipeW.Close() + bufferedStdoutErr := p.bufferedStdout.Close() - return err + return errors.Join(filterErr, toolSinkErr, pipeWErr, bufferedStdoutErr) } // SetupPipeWiring creates a new PipeWiring instance that contains the usual @@ -69,5 +83,7 @@ func SetupPipeWiring(filter *regexp.Regexp) *PipeWiring { bufferedStdout: bufferedStdout, toolInSink: toolInSink, filter: bitrisePrefixFilter, + + closeFilterOnce: sync.Once{}, } } diff --git a/xcodecommand/xcbeautify.go b/xcodecommand/xcbeautify.go index 52453123..7e98486d 100644 --- a/xcodecommand/xcbeautify.go +++ b/xcodecommand/xcbeautify.go @@ -52,6 +52,16 @@ func (c *XcbeautifyRunner) Run(workDir string, xcodebuildArgs []string, xcbeauti Env: unbufferedIOEnv, }) + defer func() { + if err := loggingIO.Close(); err != nil { + c.logger.Warnf("logging IO failure, error: %s", err) + } + + if err := beautifyCmd.Wait(); err != nil { + c.logger.Warnf("xcbeautify command failed: %s", err) + } + }() + c.logger.TPrintf("$ set -o pipefail && %s | %s", buildCmd.PrintableCommandArgs(), beautifyCmd.PrintableCommandArgs()) err := buildCmd.Start() @@ -72,15 +82,11 @@ func (c *XcbeautifyRunner) Run(workDir string, xcodebuildArgs []string, xcbeauti } } - // Wait for the filtering to finish - if err := loggingIO.Close(); err != nil { + // Closing the filter to ensure all output is flushed and processed + if err := loggingIO.CloseFilter(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } - if err := beautifyCmd.Wait(); err != nil { - c.logger.Warnf("xcbeautify command failed: %s", err) - } - return Output{ RawOut: loggingIO.XcbuildRawout.Bytes(), ExitCode: exitCode, diff --git a/xcodecommand/xcpretty.go b/xcodecommand/xcpretty.go index 5739569e..66610b4f 100644 --- a/xcodecommand/xcpretty.go +++ b/xcodecommand/xcpretty.go @@ -58,6 +58,16 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp Stderr: loggingIO.ToolStderr, }) + defer func() { + if err := loggingIO.Close(); err != nil { + c.logger.Warnf("logging IO failure, error: %s", err) + } + + if err := prettyCmd.Wait(); err != nil { + c.logger.Warnf("xcbeautify command failed: %s", err) + } + }() + c.logger.TPrintf("$ set -o pipefail && %s | %s", buildCmd.PrintableCommandArgs(), prettyCmd.PrintableCommandArgs()) err := buildCmd.Start() @@ -78,15 +88,11 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp } } - // Wait for the filtering to finish - if err := loggingIO.Close(); err != nil { + // Closing the filter to ensure all output is flushed and processed + if err := loggingIO.CloseFilter(); err != nil { c.logger.Warnf("logging IO failure, error: %s", err) } - if err := prettyCmd.Wait(); err != nil { - c.logger.Warnf("xcbeautify command failed: %s", err) - } - return Output{ RawOut: loggingIO.XcbuildRawout.Bytes(), ExitCode: exitCode, diff --git a/xcpretty/xcpretty.go b/xcpretty/xcpretty.go index 4d30d774..ff0abc7b 100644 --- a/xcpretty/xcpretty.go +++ b/xcpretty/xcpretty.go @@ -65,30 +65,48 @@ func (c CommandModel) Run() (string, error) { Stderr: loggingIO.ToolStderr, }) + // Wait for the filtering to finish + defer func() { + if err := loggingIO.Close(); err != nil { + fmt.Printf("logging IO failure, error: %s", err) + } + + if err := prettyCmd.Wait(); err != nil { + fmt.Printf("xcpretty command failed, error: %s", err) + } + }() + // Run if err := xcodebuildCmd.Start(); err != nil { + // Closing the filter to ensure all output is flushed and processed + if err := loggingIO.CloseFilter(); err != nil { + fmt.Printf("logging IO failure, error: %s", err) + } out := loggingIO.XcbuildRawout.String() return out, err } if err := prettyCmd.Start(); err != nil { + // Closing the filter to ensure all output is flushed and processed + if err := loggingIO.CloseFilter(); err != nil { + fmt.Printf("logging IO failure, error: %s", err) + } out := loggingIO.XcbuildRawout.String() return out, err } if err := xcodebuildCmd.Wait(); err != nil { + // Closing the filter to ensure all output is flushed and processed + if err := loggingIO.CloseFilter(); err != nil { + fmt.Printf("logging IO failure, error: %s", err) + } out := loggingIO.XcbuildRawout.String() return out, err } - // Wait for the filtering to finish - if err := loggingIO.Close(); err != nil { + // Closing the filter to ensure all output is flushed and processed + if err := loggingIO.CloseFilter(); err != nil { fmt.Printf("logging IO failure, error: %s", err) } - - if err := prettyCmd.Wait(); err != nil { - fmt.Printf("xcpretty command failed, error: %s", err) - } - return loggingIO.XcbuildRawout.String(), nil } From 687b1f2c5325324f6ce4c04f34511c2b29ed8f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Mon, 13 Oct 2025 11:00:16 +0200 Subject: [PATCH 10/11] Wrap logging and error checking on filter close --- xcpretty/xcpretty.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/xcpretty/xcpretty.go b/xcpretty/xcpretty.go index ff0abc7b..5a3de912 100644 --- a/xcpretty/xcpretty.go +++ b/xcpretty/xcpretty.go @@ -76,37 +76,32 @@ func (c CommandModel) Run() (string, error) { } }() - // Run - if err := xcodebuildCmd.Start(); err != nil { + closeAndFlushFilter := func() { // Closing the filter to ensure all output is flushed and processed if err := loggingIO.CloseFilter(); err != nil { fmt.Printf("logging IO failure, error: %s", err) } + } + + // Run + if err := xcodebuildCmd.Start(); err != nil { + closeAndFlushFilter() out := loggingIO.XcbuildRawout.String() return out, err } if err := prettyCmd.Start(); err != nil { - // Closing the filter to ensure all output is flushed and processed - if err := loggingIO.CloseFilter(); err != nil { - fmt.Printf("logging IO failure, error: %s", err) - } + closeAndFlushFilter() out := loggingIO.XcbuildRawout.String() return out, err } if err := xcodebuildCmd.Wait(); err != nil { - // Closing the filter to ensure all output is flushed and processed - if err := loggingIO.CloseFilter(); err != nil { - fmt.Printf("logging IO failure, error: %s", err) - } + closeAndFlushFilter() out := loggingIO.XcbuildRawout.String() return out, err } - // Closing the filter to ensure all output is flushed and processed - if err := loggingIO.CloseFilter(); err != nil { - fmt.Printf("logging IO failure, error: %s", err) - } + closeAndFlushFilter() return loggingIO.XcbuildRawout.String(), nil } From 0b9991a70057e50d4daf9d7201dc9f4c7f2e16ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bala=CC=81zs=20Hajagos?= Date: Mon, 13 Oct 2025 11:01:44 +0200 Subject: [PATCH 11/11] Fix copy paste in xcpretty logs --- xcodecommand/xcpretty.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcodecommand/xcpretty.go b/xcodecommand/xcpretty.go index 66610b4f..1013eb42 100644 --- a/xcodecommand/xcpretty.go +++ b/xcodecommand/xcpretty.go @@ -64,7 +64,7 @@ func (c *XcprettyCommandRunner) Run(workDir string, xcodebuildArgs []string, xcp } if err := prettyCmd.Wait(); err != nil { - c.logger.Warnf("xcbeautify command failed: %s", err) + c.logger.Warnf("xcpretty command failed: %s", err) } }()