internal/control/buildlog.go

f8b976a97290a20d552056a999511f5d27d8e8ec
gitbay/internal/control/buildlog.go history · blame · raw

91 lines · 2354 bytes

 1package control
 2
 3import "strings"
 4
 5// LogSection is one part of a build log: the setup before the first
 6// step (N 0), or one step and its output.
 7type LogSection struct {
 8	N    int    // 0 for the setup, else the 1-based step
 9	Step string // the step's command; "" for the setup
10	Text string
11}
12
13// SplitBuildLog cuts a log at the "$ <step>" line the runner writes
14// before each step, matching the build's steps in order and only at a
15// line start. Output before the first step is the setup section, left
16// out when empty. A step with no line in the log — the build stopped
17// before it — has no section, and neither has any step after it.
18func SplitBuildLog(log string, steps []string) []LogSection {
19	var out []LogSection
20	cur := LogSection{}
21	start := 0
22	for i, step := range steps {
23		marker := "$ " + step + "\n"
24		at := findLine(log, marker, start)
25		if at < 0 {
26			break
27		}
28		cur.Text = log[start:at]
29		if cur.N > 0 || cur.Text != "" {
30			out = append(out, cur)
31		}
32		cur = LogSection{N: i + 1, Step: step}
33		start = at + len(marker)
34	}
35	cur.Text = log[start:]
36	if cur.N > 0 || cur.Text != "" {
37		out = append(out, cur)
38	}
39	return out
40}
41
42// findLine is the index of line in log at or after from where it starts
43// a line, or -1.
44func findLine(log, line string, from int) int {
45	for i := from; i <= len(log)-len(line); {
46		j := strings.Index(log[i:], line)
47		if j < 0 {
48			return -1
49		}
50		at := i + j
51		if at == 0 || log[at-1] == '\n' {
52			return at
53		}
54		i = at + 1
55	}
56	return -1
57}
58
59// FailedSection is the index of the section a failed build stopped in:
60// the step the runner named, or the last section when it named none (an
61// older runner, or a failure the runner could not tie to a step). -1
62// when the build did not fail or its log is empty.
63func FailedSection(sections []LogSection, status string, failedStep int) int {
64	if status != "failure" || len(sections) == 0 {
65		return -1
66	}
67	for i, s := range sections {
68		if failedStep > 0 && s.N == failedStep {
69			return i
70		}
71	}
72	return len(sections) - 1
73}
74
75// tailLines is the last n lines of b; a final newline ends the last line
76// rather than starting another.
77func tailLines(b []byte, n int) []byte {
78	end := len(b)
79	if end > 0 && b[end-1] == '\n' {
80		end--
81	}
82	for i := end - 1; i >= 0; i-- {
83		if b[i] == '\n' {
84			n--
85			if n == 0 {
86				return b[i+1:]
87			}
88		}
89	}
90	return b
91}