File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/workflow/matrix.go
178 lines · 4.2kb · master
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
package workflow
133728e barerepo 1mo
2
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"fmt"
133728e barerepo 1mo
5
"sort"
133728e barerepo 1mo
6
"strings"
133728e barerepo 1mo
7
)
133728e barerepo 1mo
8
133728e barerepo 1mo
9
// combo is one set of matrix values, which becomes one job. Chapter 15A.
133728e barerepo 1mo
10
type combo map[string]string
133728e barerepo 1mo
11
133728e barerepo 1mo
12
// expand turns a matrix into the combinations it names, in a stable order.
133728e barerepo 1mo
13
func expand(m map[string]any) ([]combo, []string) {
133728e barerepo 1mo
14
var notes []string
133728e barerepo 1mo
15
keys := make([]string, 0, len(m))
133728e barerepo 1mo
16
for k := range m {
133728e barerepo 1mo
17
// include and exclude are not axes, they change the product the axes make.
133728e barerepo 1mo
18
if k == "include" || k == "exclude" {
133728e barerepo 1mo
19
continue
133728e barerepo 1mo
20
}
133728e barerepo 1mo
21
keys = append(keys, k)
133728e barerepo 1mo
22
}
133728e barerepo 1mo
23
sort.Strings(keys)
133728e barerepo 1mo
24
133728e barerepo 1mo
25
// One empty combination, widened by each axis in turn, which is the product.
133728e barerepo 1mo
26
out := []combo{{}}
133728e barerepo 1mo
27
for _, k := range keys {
133728e barerepo 1mo
28
values := stringList(m[k])
133728e barerepo 1mo
29
if len(values) == 0 {
133728e barerepo 1mo
30
notes = append(notes, "matrix axis "+k+" has no values barerepo can read")
133728e barerepo 1mo
31
continue
133728e barerepo 1mo
32
}
133728e barerepo 1mo
33
var next []combo
133728e barerepo 1mo
34
for _, c := range out {
133728e barerepo 1mo
35
for _, v := range values {
133728e barerepo 1mo
36
widened := combo{}
133728e barerepo 1mo
37
for ck, cv := range c {
133728e barerepo 1mo
38
widened[ck] = cv
133728e barerepo 1mo
39
}
133728e barerepo 1mo
40
widened[k] = v
133728e barerepo 1mo
41
next = append(next, widened)
133728e barerepo 1mo
42
}
133728e barerepo 1mo
43
}
133728e barerepo 1mo
44
out = next
133728e barerepo 1mo
45
}
133728e barerepo 1mo
46
133728e barerepo 1mo
47
out = applyExclude(out, m["exclude"])
133728e barerepo 1mo
48
if _, has := m["include"]; has {
133728e barerepo 1mo
49
// include can add keys and whole combinations, and guessing at it would run the wrong builds.
133728e barerepo 1mo
50
notes = append(notes, "matrix include is not applied, so only the listed combinations run")
133728e barerepo 1mo
51
}
133728e barerepo 1mo
52
if len(out) == 1 && len(out[0]) == 0 {
133728e barerepo 1mo
53
return nil, notes
133728e barerepo 1mo
54
}
133728e barerepo 1mo
55
if len(out) == 0 {
133728e barerepo 1mo
56
// The axes made combinations and exclude removed every one, so there is nothing to build.
133728e barerepo 1mo
57
notes = append(notes, "excludes every combination its matrix makes")
133728e barerepo 1mo
58
}
133728e barerepo 1mo
59
return out, notes
133728e barerepo 1mo
60
}
133728e barerepo 1mo
61
133728e barerepo 1mo
62
// applyExclude drops the combinations a matrix said not to build.
133728e barerepo 1mo
63
func applyExclude(in []combo, raw any) []combo {
133728e barerepo 1mo
64
rules, ok := raw.([]any)
133728e barerepo 1mo
65
if !ok {
133728e barerepo 1mo
66
return in
133728e barerepo 1mo
67
}
133728e barerepo 1mo
68
out := in[:0]
133728e barerepo 1mo
69
for _, c := range in {
133728e barerepo 1mo
70
if !excluded(c, rules) {
133728e barerepo 1mo
71
out = append(out, c)
133728e barerepo 1mo
72
}
133728e barerepo 1mo
73
}
133728e barerepo 1mo
74
return out
133728e barerepo 1mo
75
}
133728e barerepo 1mo
76
133728e barerepo 1mo
77
// excluded reports whether one combination matches any exclude rule, which needs every key to match.
133728e barerepo 1mo
78
func excluded(c combo, rules []any) bool {
133728e barerepo 1mo
79
for _, raw := range rules {
133728e barerepo 1mo
80
rule, ok := raw.(map[string]any)
133728e barerepo 1mo
81
if !ok || len(rule) == 0 {
133728e barerepo 1mo
82
continue
133728e barerepo 1mo
83
}
133728e barerepo 1mo
84
all := true
133728e barerepo 1mo
85
for k, v := range rule {
133728e barerepo 1mo
86
if c[k] != scalar(v) {
133728e barerepo 1mo
87
all = false
133728e barerepo 1mo
88
break
133728e barerepo 1mo
89
}
133728e barerepo 1mo
90
}
133728e barerepo 1mo
91
if all {
133728e barerepo 1mo
92
return true
133728e barerepo 1mo
93
}
133728e barerepo 1mo
94
}
133728e barerepo 1mo
95
return false
133728e barerepo 1mo
96
}
133728e barerepo 1mo
97
133728e barerepo 1mo
98
// suffix names one combination, so two jobs from one matrix are told apart in the runs list.
133728e barerepo 1mo
99
func (c combo) suffix() string {
133728e barerepo 1mo
100
if len(c) == 0 {
133728e barerepo 1mo
101
return ""
133728e barerepo 1mo
102
}
133728e barerepo 1mo
103
keys := make([]string, 0, len(c))
133728e barerepo 1mo
104
for k := range c {
133728e barerepo 1mo
105
keys = append(keys, k)
133728e barerepo 1mo
106
}
133728e barerepo 1mo
107
sort.Strings(keys)
133728e barerepo 1mo
108
parts := make([]string, 0, len(keys))
133728e barerepo 1mo
109
for _, k := range keys {
133728e barerepo 1mo
110
// The axis is named, because a reader of the runs list cannot guess which 1.26 this is.
133728e barerepo 1mo
111
parts = append(parts, k+" "+c[k])
133728e barerepo 1mo
112
}
133728e barerepo 1mo
113
return " (" + strings.Join(parts, ", ") + ")"
133728e barerepo 1mo
114
}
133728e barerepo 1mo
115
133728e barerepo 1mo
116
// fill replaces the matrix expressions in one string, which is how runs-on and a run step get values.
133728e barerepo 1mo
117
func (c combo) fill(s string) string {
133728e barerepo 1mo
118
if len(c) == 0 || !strings.Contains(s, "${{") {
133728e barerepo 1mo
119
return s
133728e barerepo 1mo
120
}
133728e barerepo 1mo
121
var b strings.Builder
133728e barerepo 1mo
122
rest := s
133728e barerepo 1mo
123
for {
133728e barerepo 1mo
124
before, after, found := strings.Cut(rest, "${{")
133728e barerepo 1mo
125
if !found {
133728e barerepo 1mo
126
b.WriteString(rest)
133728e barerepo 1mo
127
return b.String()
133728e barerepo 1mo
128
}
133728e barerepo 1mo
129
b.WriteString(before)
133728e barerepo 1mo
130
inner, tail, closed := strings.Cut(after, "}}")
133728e barerepo 1mo
131
if !closed {
133728e barerepo 1mo
132
b.WriteString("${{")
133728e barerepo 1mo
133
b.WriteString(after)
133728e barerepo 1mo
134
return b.String()
133728e barerepo 1mo
135
}
133728e barerepo 1mo
136
name := strings.TrimSpace(inner)
133728e barerepo 1mo
137
if key, ok := strings.CutPrefix(name, "matrix."); ok {
133728e barerepo 1mo
138
if v, known := c[key]; known {
133728e barerepo 1mo
139
b.WriteString(v)
133728e barerepo 1mo
140
rest = tail
133728e barerepo 1mo
141
continue
133728e barerepo 1mo
142
}
133728e barerepo 1mo
143
}
133728e barerepo 1mo
144
b.WriteString("${{" + inner + "}}")
133728e barerepo 1mo
145
rest = tail
133728e barerepo 1mo
146
}
133728e barerepo 1mo
147
}
133728e barerepo 1mo
148
133728e barerepo 1mo
149
// vars exports the combination, so a step reading $MATRIX_OS works the way a step reading env does.
133728e barerepo 1mo
150
func (c combo) vars() string {
133728e barerepo 1mo
151
if len(c) == 0 {
133728e barerepo 1mo
152
return ""
133728e barerepo 1mo
153
}
133728e barerepo 1mo
154
keys := make([]string, 0, len(c))
133728e barerepo 1mo
155
for k := range c {
133728e barerepo 1mo
156
keys = append(keys, k)
133728e barerepo 1mo
157
}
133728e barerepo 1mo
158
sort.Strings(keys)
133728e barerepo 1mo
159
var b strings.Builder
133728e barerepo 1mo
160
for _, k := range keys {
133728e barerepo 1mo
161
fmt.Fprintf(&b, "export MATRIX_%s=%s\n", envName(k), shellQuote(c[k]))
133728e barerepo 1mo
162
}
133728e barerepo 1mo
163
return b.String()
133728e barerepo 1mo
164
}
133728e barerepo 1mo
165
133728e barerepo 1mo
166
// envName turns a matrix key into a shell name, since go-version is not a usable variable.
133728e barerepo 1mo
167
func envName(k string) string {
133728e barerepo 1mo
168
var b strings.Builder
133728e barerepo 1mo
169
for _, r := range strings.ToUpper(k) {
133728e barerepo 1mo
170
switch {
133728e barerepo 1mo
171
case r >= 'A' && r <= 'Z', r >= '0' && r <= '9':
133728e barerepo 1mo
172
b.WriteRune(r)
133728e barerepo 1mo
173
default:
133728e barerepo 1mo
174
b.WriteByte('_')
133728e barerepo 1mo
175
}
133728e barerepo 1mo
176
}
133728e barerepo 1mo
177
return b.String()
133728e barerepo 1mo
178
}
history · rawbarerepo 0.1.0