diff --git a/go.mod b/go.mod index 11a5e114..c5b73773 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/bitrise-io/bitrise v0.0.0-20230707121919-a5b9e2d27ea9 github.com/bitrise-io/go-steputils/v2 v2.0.0-alpha.40 github.com/bitrise-io/go-utils v1.0.13 - github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.26 + github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.28.0.20260129135042-916e3b7a8f35 github.com/bitrise-io/go-xcode/v2 v2.0.0-alpha.70 github.com/bitrise-steplib/steps-deploy-to-bitrise-io v0.0.0-20250707145051-550b658b019b github.com/hashicorp/go-version v1.7.0 diff --git a/go.sum b/go.sum index 01d56f30..41c826b7 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/bitrise-io/go-steputils/v2 v2.0.0-alpha.40/go.mod h1:UNKPd7zsUF7gtOpW github.com/bitrise-io/go-utils v1.0.1/go.mod h1:ZY1DI+fEpZuFpO9szgDeICM4QbqoWVt0RSY3tRI1heY= github.com/bitrise-io/go-utils v1.0.13 h1:1QENhTS/JlKH9F7+/nB+TtbTcor6jGrE6cQ4CJWfp5U= github.com/bitrise-io/go-utils v1.0.13/go.mod h1:ZY1DI+fEpZuFpO9szgDeICM4QbqoWVt0RSY3tRI1heY= -github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.26 h1:meDTxqONXlQv2JmOcEbJj5Wx7WcuwpHRsP5MUob1NCQ= -github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.26/go.mod h1:3XUplo0dOWc3DqT2XA2SeHToDSg7+j1y1HTHibT2H68= +github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.28.0.20260129135042-916e3b7a8f35 h1:oefgtDJxTwjZ+YGlHn9ROo71IZTXkAK1/Vt7mR8b2K8= +github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.28.0.20260129135042-916e3b7a8f35/go.mod h1:6/2xllOblqANQitGXDwazMX87peiEhXRSZE/dkZCYyk= github.com/bitrise-io/go-xcode v1.3.0 h1:QB8Vyr2oZQro/ocs9DJai80rlYL1hU1kwjHqdGslFLo= github.com/bitrise-io/go-xcode v1.3.0/go.mod h1:9OwsvrhZ4A2JxHVoEY7CPcABAKA+OE7FQqFfBfvbFuY= github.com/bitrise-io/go-xcode/v2 v2.0.0-alpha.70 h1:yx7xxWLoBetwiLof++8o+hTELevEq2NJgft+go/Fhuk= diff --git a/step/step.go b/step/step.go index ea56bb72..1c3bf945 100644 --- a/step/step.go +++ b/step/step.go @@ -11,7 +11,7 @@ import ( "github.com/bitrise-io/go-steputils/v2/stepconf" "github.com/bitrise-io/go-steputils/v2/testquarantine" "github.com/bitrise-io/go-utils/colorstring" - "github.com/bitrise-io/go-utils/progress" + "github.com/bitrise-io/go-utils/v2/progress" "github.com/bitrise-io/go-utils/sliceutil" "github.com/bitrise-io/go-utils/v2/command" "github.com/bitrise-io/go-utils/v2/log" diff --git a/vendor/github.com/bitrise-io/go-utils/v2/log/log.go b/vendor/github.com/bitrise-io/go-utils/v2/log/log.go index 771c67aa..95d6b26f 100644 --- a/vendor/github.com/bitrise-io/go-utils/v2/log/log.go +++ b/vendor/github.com/bitrise-io/go-utils/v2/log/log.go @@ -34,6 +34,7 @@ type logger struct { enableDebugLog bool timestampLayout string stdout io.Writer + prefix string } // NewLogger ... @@ -71,7 +72,16 @@ func WithOutput(w io.Writer) LoggerOptions { } } +// WithPrefix adds a prefix to each log line. Prefix is added before the timestamp if timestamps are enabled. +// It does not add any extra spaces, so if you want a space after the prefix, include it in the prefix string. +func WithPrefix(prefix string) LoggerOptions { + return func(l *logger) { + l.prefix = prefix + } +} + // EnableDebugLog ... +// Deprecated: use WithDebugLog option instead func (l *logger) EnableDebugLog(enable bool) { l.enableDebugLog = enable } @@ -160,6 +170,9 @@ func (l *logger) createLogMsg(severity Severity, withTime bool, format string, v if withTime { message = l.prefixCurrentTime(message) } + if l.prefix != "" { + message = l.prefix + message + } return message } diff --git a/vendor/github.com/bitrise-io/go-utils/v2/progress/spinner.go b/vendor/github.com/bitrise-io/go-utils/v2/progress/spinner.go new file mode 100644 index 00000000..985552e3 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/v2/progress/spinner.go @@ -0,0 +1,127 @@ +package progress + +import ( + "fmt" + "io" + "os" + "sync" + "time" + "unicode/utf8" +) + +// Sleeper defines the interface for sleeping operations, allowing for dependency injection in tests. +type Sleeper interface { + Sleep(d time.Duration) +} + +// DefaultSleeper implements Sleeper using time.Sleep. +type DefaultSleeper struct{} + +// Sleep calls time.Sleep with the given duration. +func (s DefaultSleeper) Sleep(d time.Duration) { + time.Sleep(d) +} + +// Spinner displays an animated progress indicator. +type Spinner struct { + message string + chars []string + delay time.Duration + writer io.Writer + sleeper Sleeper + + mu sync.Mutex + active bool + lastOutput string + stopChan chan bool +} + +// NewSpinner creates a new Spinner with the given parameters. +func NewSpinner(message string, chars []string, delay time.Duration, writer io.Writer) *Spinner { + return NewSpinnerWithSleeper(message, chars, delay, writer, DefaultSleeper{}) +} + +// NewSpinnerWithSleeper creates a new Spinner with a custom Sleeper for testing. +func NewSpinnerWithSleeper(message string, chars []string, delay time.Duration, writer io.Writer, sleeper Sleeper) *Spinner { + return &Spinner{ + message: message, + chars: chars, + delay: delay, + writer: writer, + sleeper: sleeper, + + active: false, + stopChan: make(chan bool), + } +} + +// NewDefaultSpinner creates a Spinner with default animation characters and timing, writing to stdout. +func NewDefaultSpinner(message string) *Spinner { + return NewDefaultSpinnerWithOutput(message, os.Stdout) +} + +// NewDefaultSpinnerWithOutput creates a Spinner with default animation characters and timing. +func NewDefaultSpinnerWithOutput(message string, output io.Writer) *Spinner { + chars := []string{"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"} + delay := 100 * time.Millisecond + return NewSpinner(message, chars, delay, output) +} + +// Start begins the spinner animation in a background goroutine. +func (s *Spinner) Start() { + s.mu.Lock() + if s.active { + s.mu.Unlock() + return + } + s.active = true + s.mu.Unlock() + + go func() { + for { + for i := 0; i < len(s.chars); i++ { + select { + case <-s.stopChan: + return + default: + s.mu.Lock() + s.erase() + + out := fmt.Sprintf("%s %s", s.message, s.chars[i]) + if _, err := fmt.Fprint(s.writer, out); err != nil { + fmt.Printf("failed to update progress, error: %s\n", err) + } + s.lastOutput = out + s.mu.Unlock() + + s.sleeper.Sleep(s.delay) + } + } + } + }() +} + +// Stop terminates the spinner animation and clears the output. +func (s *Spinner) Stop() { + s.mu.Lock() + if s.active { + s.active = false + s.erase() + s.mu.Unlock() + s.stopChan <- true + } else { + s.mu.Unlock() + } +} + +func (s *Spinner) erase() { + n := utf8.RuneCountInString(s.lastOutput) + for _, c := range []string{"\b", " ", "\b"} { + for i := 0; i < n; i++ { + if _, err := fmt.Fprint(s.writer, c); err != nil { + fmt.Printf("failed to update progress, error: %s\n", err) + } + } + } + s.lastOutput = "" +} diff --git a/vendor/github.com/bitrise-io/go-utils/v2/progress/wrapper.go b/vendor/github.com/bitrise-io/go-utils/v2/progress/wrapper.go new file mode 100644 index 00000000..5a7efd5d --- /dev/null +++ b/vendor/github.com/bitrise-io/go-utils/v2/progress/wrapper.go @@ -0,0 +1,61 @@ +package progress + +import ( + "fmt" + "io" + "os" + "strings" + + "golang.org/x/crypto/ssh/terminal" //nolint:staticcheck // Keep using this for Go 1.21 compatibility +) + +// Wrapper wraps an action with progress indication. +type Wrapper struct { + spinner *Spinner + interactiveMode bool +} + +// NewWrapper creates a Wrapper with the given spinner and interactive mode setting. +func NewWrapper(spinner *Spinner, interactiveMode bool) Wrapper { + return Wrapper{ + spinner: spinner, + interactiveMode: interactiveMode, + } +} + +// NewDefaultWrapper creates a Wrapper with default spinner configuration. +func NewDefaultWrapper(message string) Wrapper { + spinner := NewDefaultSpinner(message) + interactiveMode := OutputDeviceIsTerminal() + return NewWrapper(spinner, interactiveMode) +} + +// NewDefaultWrapperWithOutput creates a Wrapper with default spinner configuration. +func NewDefaultWrapperWithOutput(message string, output io.Writer) Wrapper { + spinner := NewDefaultSpinnerWithOutput(message, output) + interactiveMode := OutputDeviceIsTerminal() + return NewWrapper(spinner, interactiveMode) +} + +// WrapAction executes the given action with progress indication. +func (w Wrapper) WrapAction(action func()) { + if w.interactiveMode { + w.spinner.Start() + action() + w.spinner.Stop() + } else { + message := w.spinner.message + if !strings.HasSuffix(message, ".") { + message = message + "..." + } + if _, err := fmt.Fprintln(w.spinner.writer, message); err != nil { + fmt.Printf("failed to print message: %s, error: %s", message, err) + } + action() + } +} + +// OutputDeviceIsTerminal returns true if stdout is connected to a terminal. +func OutputDeviceIsTerminal() bool { + return terminal.IsTerminal(int(os.Stdout.Fd())) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8f5c4f5c..290d00e9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -36,7 +36,7 @@ github.com/bitrise-io/go-utils/retry github.com/bitrise-io/go-utils/sliceutil github.com/bitrise-io/go-utils/stringutil github.com/bitrise-io/go-utils/ziputil -# github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.26 +# github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.28.0.20260129135042-916e3b7a8f35 ## explicit; go 1.17 github.com/bitrise-io/go-utils/v2/command github.com/bitrise-io/go-utils/v2/env @@ -45,6 +45,7 @@ github.com/bitrise-io/go-utils/v2/fileutil github.com/bitrise-io/go-utils/v2/log github.com/bitrise-io/go-utils/v2/log/colorstring github.com/bitrise-io/go-utils/v2/pathutil +github.com/bitrise-io/go-utils/v2/progress # github.com/bitrise-io/go-xcode v1.3.0 ## explicit; go 1.20 github.com/bitrise-io/go-xcode/xcodeproject/serialized