internal/control/buildlog.go
91 lines · 2354 bytes
5 symbols in this file
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}