-
Notifications
You must be signed in to change notification settings - Fork 3
/
build.go
188 lines (157 loc) · 4.75 KB
/
build.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package build
import (
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/hashicorp/actions-go-build/internal/zipper"
"github.com/hashicorp/composite-action-framework-go/pkg/fs"
"github.com/hashicorp/composite-action-framework-go/pkg/json"
)
// Build represents the build of a single binary.
// It could be a primary build or a verification build, this Build doesn't
// need to know.
type Build interface {
Env() []string
Config() Config
CachedResult() (Result, bool, error)
Steps() []Step
ChangeRoot(string) error
ChangeToVerificationRoot() error
IsVerification() bool
Dirs() TempDirs
}
func New(name string, cfg Config, options ...Option) (Build, error) {
return newCore(name, cfg, options...)
}
type core struct {
Settings
config Config
}
func newCore(name string, cfg Config, options ...Option) (*core, error) {
s, err := newSettings(options)
if err != nil {
return nil, err
}
if s.cleanOnly && cfg.Product.IsDirty() {
return nil, fmt.Errorf("build config indicates a dirty worktree but clean-only is set to true")
}
return &core{
Settings: s,
config: cfg,
}, nil
}
func (b *core) Config() Config {
return b.config
}
func (b *core) IsVerification() bool { return b.isVerification }
func (b *core) Dirs() TempDirs {
return newDirsFromConfig(b.config, b.isVerification)
}
func (b *core) ChangeRoot(dir string) error {
b.Debug("changing root to %s", dir)
var err error
b.config, err = b.config.ChangeRoot(dir)
return err
}
func (b *core) ChangeToVerificationRoot() error {
return b.ChangeRoot(b.config.VerificationRoot())
}
func (b *core) ChangeToPrimaryRoot() error {
return b.ChangeRoot(b.config.RemotePrimaryRoot())
}
// UpdateBuildRoot updates the build root for this build depending
// whether it's a primary or verification build.
func (b *core) UpdateBuildRoot() error {
if b.isVerification {
return b.ChangeToVerificationRoot()
}
return b.ChangeToPrimaryRoot()
}
func (b *core) CachedResult() (Result, bool, error) {
var r Result
path := b.config.BuildResultCachePath(b.isVerification)
exists, err := fs.FileExists(path)
if err != nil {
b.Debug("Cache read error: %s", err)
return r, false, err
}
if !exists {
b.Debug("Cache miss: %s", path)
return r, false, nil
}
b.Debug("Cache hit: %s", path)
r, err = json.ReadFile[Result](path)
r.loadedFromCache = true
return r, err == nil, err
}
func newStep(desc string, action StepFunc) Step {
return Step{desc: desc, action: action}
}
func (b *core) Steps() []Step {
var productRevisionTimestamp time.Time
return []Step{
newStep("validating inputs", func() error {
var err error
productRevisionTimestamp, err = b.Config().Product.RevisionTimestamp()
return err
}),
newStep("creating output directories", b.createDirectories),
newStep("running build instructions", b.runInstructions),
newStep("asserting executable written", b.assertExecutableWritten),
newStep("setting mtimes", func() error {
return fs.SetMtimes(b.Config().Paths.TargetDir(), productRevisionTimestamp)
}),
newStep(fmt.Sprintf("creating zip file %q", b.Config().Paths.ZipPath), func() error {
return zipper.ZipToFile(b.Config().Paths.TargetDir(), b.Config().Paths.ZipPath, b.Settings.Log)
}),
}
}
func (b *core) createDirectories() error {
c := b.config
return fs.Mkdirs(c.Paths.TargetDir(), c.Paths.ZipDir(), c.Paths.MetaDir)
}
func (b *core) assertExecutableWritten() error {
binExists, err := b.executableWasWritten()
if err != nil {
return err
}
if !binExists {
return fmt.Errorf("no file written to BIN_PATH %q", b.config.Paths.BinPath)
}
return nil
}
func (b *core) executableWasWritten() (bool, error) {
return fs.FileExists(b.config.Paths.BinPath)
}
func (b *core) newCommand(name string, args ...string) *exec.Cmd {
cmd := exec.CommandContext(b.Settings.context, name, args...)
cmd.Dir = b.config.Paths.WorkDir
cmd.Stdout = b.Settings.stdout
cmd.Stderr = b.Settings.stderr
return cmd
}
func (b *core) runCommand(name string, args ...string) error {
return b.newCommand(name, args...).Run()
}
func (b *core) runInstructions() error {
path, err := b.writeInstructions()
if err != nil {
return err
}
c := b.newCommand(b.Settings.bash, path)
c.Env = b.Env()
b.Log("Build environment determined by config:\n%s", strings.Join(c.Env, "\n"))
c.Env = append(os.Environ(), c.Env...)
b.Debug("Full build environment:\n%s", strings.Join(c.Env, "\n"))
return c.Run()
}
// writeInstructions writes the build instructions to a temporary file
// and returns its path, or an error if writing fails.
func (b *core) writeInstructions() (path string, err error) {
b.Log("Build instructions:\n%s", b.config.Parameters.Instructions)
return fs.WriteTempFile("actions-go-build.instructions", b.config.Parameters.Instructions)
}