
Blender and FFmpeg were run in the same way, using copy-pasted code. This is now abstracted away into the CLI runner, which in turn is moved into its own subpackage. No functional changes.
104 lines
2.5 KiB
Go
104 lines
2.5 KiB
Go
package cli_runner
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
// The buffer size used to read stdout/stderr output from subprocesses.
|
|
// Effectively this determines the maximum line length that can be handled.
|
|
const StdoutBufferSize = 40 * 1024
|
|
|
|
// CLIRunner is a wrapper around exec.CommandContext() to allow mocking.
|
|
type CLIRunner struct {
|
|
}
|
|
|
|
func NewCLIRunner() *CLIRunner {
|
|
return &CLIRunner{}
|
|
}
|
|
|
|
func (cli *CLIRunner) CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
|
|
return exec.CommandContext(ctx, name, arg...)
|
|
}
|
|
|
|
// RunWithTextOutput runs a command and sends its output line-by-line to the
|
|
// lineChannel. Stdout and stderr are combined.
|
|
func (cli *CLIRunner) RunWithTextOutput(
|
|
ctx context.Context,
|
|
logger zerolog.Logger,
|
|
execCmd *exec.Cmd,
|
|
logChunker LogChunker,
|
|
lineChannel chan<- string,
|
|
) error {
|
|
outPipe, err := execCmd.StdoutPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
execCmd.Stderr = execCmd.Stdout // Redirect stderr to stdout.
|
|
|
|
if err := execCmd.Start(); err != nil {
|
|
logger.Error().Err(err).Msg("error starting CLI execution")
|
|
return err
|
|
}
|
|
|
|
blenderPID := execCmd.Process.Pid
|
|
logger = logger.With().Int("pid", blenderPID).Logger()
|
|
|
|
reader := bufio.NewReaderSize(outPipe, StdoutBufferSize)
|
|
|
|
for {
|
|
lineBytes, isPrefix, readErr := reader.ReadLine()
|
|
if readErr == io.EOF {
|
|
break
|
|
}
|
|
if readErr != nil {
|
|
logger.Error().Err(err).Msg("error reading stdout/err")
|
|
return err
|
|
}
|
|
|
|
line := string(lineBytes)
|
|
if isPrefix {
|
|
logger.Warn().
|
|
Str("line", fmt.Sprintf("%s...", line[:256])).
|
|
Int("lineLength", len(line)).
|
|
Msg("unexpectedly long line read, truncating")
|
|
}
|
|
|
|
logger.Debug().Msg(line)
|
|
if lineChannel != nil {
|
|
lineChannel <- line
|
|
}
|
|
|
|
if err := logChunker.Append(ctx, fmt.Sprintf("pid=%d > %s", blenderPID, line)); err != nil {
|
|
return fmt.Errorf("appending log entry to log chunker: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := logChunker.Flush(ctx); err != nil {
|
|
return fmt.Errorf("flushing log chunker: %w", err)
|
|
}
|
|
|
|
if err := execCmd.Wait(); err != nil {
|
|
logger.Error().Err(err).Msg("error in CLI execution")
|
|
return err
|
|
}
|
|
|
|
if execCmd.ProcessState.Success() {
|
|
logger.Info().Msg("command exited succesfully")
|
|
} else {
|
|
logger.Error().
|
|
Int("exitCode", execCmd.ProcessState.ExitCode()).
|
|
Msg("command exited abnormally")
|
|
return fmt.Errorf("command exited abnormally with code %d", execCmd.ProcessState.ExitCode())
|
|
}
|
|
|
|
return nil
|
|
}
|