1package workflow
2
3import (
4 "fmt"
5
6 "tangled.sh/tangled.sh/core/api/tangled"
7)
8
9type Compiler struct {
10 Trigger tangled.Pipeline_TriggerMetadata
11 Diagnostics Diagnostics
12}
13
14type Diagnostics struct {
15 Errors []error
16 Warnings []Warning
17}
18
19func (d *Diagnostics) Combine(o Diagnostics) {
20 d.Errors = append(d.Errors, o.Errors...)
21 d.Warnings = append(d.Warnings, o.Warnings...)
22}
23
24func (d *Diagnostics) AddWarning(path string, kind WarningKind, reason string) {
25 d.Warnings = append(d.Warnings, Warning{path, kind, reason})
26}
27
28func (d *Diagnostics) AddError(err error) {
29 d.Errors = append(d.Errors, err)
30}
31
32func (d Diagnostics) IsErr() bool {
33 return len(d.Errors) != 0
34}
35
36type Warning struct {
37 Path string
38 Type WarningKind
39 Reason string
40}
41
42type WarningKind string
43
44var (
45 WorkflowSkipped WarningKind = "workflow skipped"
46 InvalidConfiguration WarningKind = "invalid configuration"
47)
48
49// convert a repositories' workflow files into a fully compiled pipeline that runners accept
50func (compiler *Compiler) Compile(p Pipeline) tangled.Pipeline {
51 cp := tangled.Pipeline{
52 TriggerMetadata: &compiler.Trigger,
53 }
54
55 for _, w := range p {
56 cw := compiler.compileWorkflow(w)
57
58 // empty workflows are not added to the pipeline
59 if len(cw.Steps) == 0 {
60 continue
61 }
62
63 cp.Workflows = append(cp.Workflows, &cw)
64 }
65
66 return cp
67}
68
69func (compiler *Compiler) compileWorkflow(w Workflow) tangled.Pipeline_Workflow {
70 cw := tangled.Pipeline_Workflow{}
71
72 if !w.Match(compiler.Trigger) {
73 compiler.Diagnostics.AddWarning(
74 w.Name,
75 WorkflowSkipped,
76 fmt.Sprintf("did not match trigger %s", compiler.Trigger.Kind),
77 )
78 return cw
79 }
80
81 if len(w.Steps) == 0 {
82 compiler.Diagnostics.AddWarning(
83 w.Name,
84 WorkflowSkipped,
85 "empty workflow",
86 )
87 return cw
88 }
89
90 // validate clone options
91 compiler.analyzeCloneOptions(w)
92
93 cw.Name = w.Name
94 cw.Dependencies = w.Dependencies.AsRecord()
95 for _, s := range w.Steps {
96 step := tangled.Pipeline_Step{
97 Command: s.Command,
98 Name: s.Name,
99 }
100 for k, v := range s.Environment {
101 e := &tangled.Pipeline_Step_Environment_Elem{
102 Key: k,
103 Value: v,
104 }
105 step.Environment = append(step.Environment, e)
106 }
107 cw.Steps = append(cw.Steps, &step)
108 }
109 for k, v := range w.Environment {
110 e := &tangled.Pipeline_Workflow_Environment_Elem{
111 Key: k,
112 Value: v,
113 }
114 cw.Environment = append(cw.Environment, e)
115 }
116
117 o := w.CloneOpts.AsRecord()
118 cw.Clone = &o
119
120 return cw
121}
122
123func (compiler *Compiler) analyzeCloneOptions(w Workflow) {
124 if w.CloneOpts.Skip && w.CloneOpts.IncludeSubmodules {
125 compiler.Diagnostics.AddWarning(
126 w.Name,
127 InvalidConfiguration,
128 "cannot apply `clone.skip` and `clone.submodules`",
129 )
130 }
131
132 if w.CloneOpts.Skip && w.CloneOpts.Depth > 0 {
133 compiler.Diagnostics.AddWarning(
134 w.Name,
135 InvalidConfiguration,
136 "cannot apply `clone.skip` and `clone.depth`",
137 )
138 }
139}