Skip to content

Commit e78aa98

Browse files
committed
build: refactor some of the build functions into smaller utility functions
Signed-off-by: Jonathan A. Sternberg <[email protected]>
1 parent e6ff731 commit e78aa98

File tree

1 file changed

+152
-92
lines changed

1 file changed

+152
-92
lines changed

build/build.go

Lines changed: 152 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -138,75 +138,68 @@ func filterAvailableNodes(nodes []builder.Node) ([]builder.Node, error) {
138138
return nil, err
139139
}
140140

141-
func toRepoOnly(in string) (string, error) {
142-
m := map[string]struct{}{}
143-
p := strings.Split(in, ",")
144-
for _, pp := range p {
145-
n, err := reference.ParseNormalizedNamed(pp)
146-
if err != nil {
147-
return "", err
141+
// findNonMobyDriver returns the first non-moby based driver.
142+
func findNonMobyDriver(nodes []builder.Node) *driver.DriverHandle {
143+
for _, n := range nodes {
144+
if !n.Driver.IsMobyDriver() {
145+
return n.Driver
148146
}
149-
m[n.Name()] = struct{}{}
150-
}
151-
out := make([]string, 0, len(m))
152-
for k := range m {
153-
out = append(out, k)
154147
}
155-
return strings.Join(out, ","), nil
156-
}
157-
158-
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
159-
return BuildWithResultHandler(ctx, nodes, opts, docker, cfg, w, nil)
148+
return nil
160149
}
161150

162-
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) {
163-
if len(nodes) == 0 {
164-
return nil, errors.Errorf("driver required for build")
151+
// warnOnNoOutput will check if the given nodes and options would result in an output
152+
// and prints a warning if it would not.
153+
func warnOnNoOutput(ctx context.Context, nodes []builder.Node, opts map[string]Options) {
154+
// Return immediately if default load is explicitly disabled or a call
155+
// function is used.
156+
if noDefaultLoad() || !noCallFunc(opts) {
157+
return
165158
}
166159

167-
nodes, err = filterAvailableNodes(nodes)
168-
if err != nil {
169-
return nil, errors.Wrapf(err, "no valid drivers found")
160+
// Find the first non-moby driver and return if it either doesn't exist
161+
// or if the driver has default load enabled.
162+
noMobyDriver := findNonMobyDriver(nodes)
163+
if noMobyDriver == nil || noMobyDriver.Features(ctx)[driver.DefaultLoad] {
164+
return
170165
}
171166

172-
var noMobyDriver *driver.DriverHandle
173-
for _, n := range nodes {
174-
if !n.Driver.IsMobyDriver() {
175-
noMobyDriver = n.Driver
176-
break
167+
// Produce a warning describing the targets affected.
168+
var noOutputTargets []string
169+
for name, opt := range opts {
170+
if !opt.Linked && len(opt.Exports) == 0 {
171+
noOutputTargets = append(noOutputTargets, name)
177172
}
178173
}
179174

180-
if noMobyDriver != nil && !noDefaultLoad() && noCallFunc(opts) {
181-
var noOutputTargets []string
182-
for name, opt := range opts {
183-
if noMobyDriver.Features(ctx)[driver.DefaultLoad] {
184-
continue
185-
}
186-
187-
if !opt.Linked && len(opt.Exports) == 0 {
188-
noOutputTargets = append(noOutputTargets, name)
189-
}
190-
}
191-
if len(noOutputTargets) > 0 {
192-
var warnNoOutputBuf bytes.Buffer
193-
warnNoOutputBuf.WriteString("No output specified ")
194-
if len(noOutputTargets) == 1 && noOutputTargets[0] == "default" {
195-
warnNoOutputBuf.WriteString(fmt.Sprintf("with %s driver", noMobyDriver.Factory().Name()))
196-
} else {
197-
warnNoOutputBuf.WriteString(fmt.Sprintf("for %s target(s) with %s driver", strings.Join(noOutputTargets, ", "), noMobyDriver.Factory().Name()))
198-
}
199-
logrus.Warnf("%s. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load", warnNoOutputBuf.String())
200-
}
175+
if len(noOutputTargets) == 0 {
176+
return
201177
}
202178

203-
drivers, err := resolveDrivers(ctx, nodes, opts, w)
204-
if err != nil {
205-
return nil, err
179+
var warnNoOutputBuf bytes.Buffer
180+
warnNoOutputBuf.WriteString("No output specified ")
181+
if len(noOutputTargets) == 1 && noOutputTargets[0] == "default" {
182+
warnNoOutputBuf.WriteString(fmt.Sprintf("with %s driver", noMobyDriver.Factory().Name()))
183+
} else {
184+
warnNoOutputBuf.WriteString(fmt.Sprintf("for %s target(s) with %s driver", strings.Join(noOutputTargets, ", "), noMobyDriver.Factory().Name()))
206185
}
186+
logrus.Warnf("%s. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load", warnNoOutputBuf.String())
187+
}
207188

189+
func newBuildRequests(ctx context.Context, docker *dockerutil.Client, cfg *confutil.Config, drivers map[string][]*resolvedNode, w progress.Writer, opts map[string]Options) (_ map[string][]*reqForNode, _ func(), retErr error) {
208190
reqForNodes := make(map[string][]*reqForNode)
209-
eg, ctx := errgroup.WithContext(ctx)
191+
192+
var releasers []func()
193+
releaseAll := func() {
194+
for _, fn := range releasers {
195+
fn()
196+
}
197+
}
198+
defer func() {
199+
if retErr != nil {
200+
releaseAll()
201+
}
202+
}()
210203

211204
for k, opt := range opts {
212205
multiDriver := len(drivers[k]) > 1
@@ -226,17 +219,17 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
226219
opt.Platforms = np.platforms
227220
gatewayOpts, err := np.BuildOpts(ctx)
228221
if err != nil {
229-
return nil, err
222+
return nil, nil, err
230223
}
231224
localOpt := opt
232225
so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, &localOpt, gatewayOpts, cfg, w, docker)
233226
opts[k] = localOpt
234227
if err != nil {
235-
return nil, err
228+
return nil, nil, err
236229
}
237-
defer release()
230+
releasers = append(releasers, release)
238231
if err := saveLocalState(so, k, opt, np.Node(), cfg); err != nil {
239-
return nil, err
232+
return nil, nil, err
240233
}
241234
addGitAttrs(so)
242235
reqn = append(reqn, &reqForNode{
@@ -261,15 +254,17 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
261254
for _, e := range np.so.Exports {
262255
if e.Type == "moby" {
263256
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
264-
return nil, errors.Errorf("multi-node push can't currently be performed with the docker driver, please switch to a different driver")
257+
return nil, nil, errors.Errorf("multi-node push can't currently be performed with the docker driver, please switch to a different driver")
265258
}
266259
}
267260
}
268261
}
269262
}
270263
}
264+
return reqForNodes, releaseAll, nil
265+
}
271266

272-
// validate that all links between targets use same drivers
267+
func validateTargetLinks(reqForNodes map[string][]*reqForNode, drivers map[string][]*resolvedNode, opts map[string]Options) error {
273268
for name := range opts {
274269
dps := reqForNodes[name]
275270
for i, dp := range dps {
@@ -279,8 +274,9 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
279274
k2 := strings.TrimPrefix(v, "target:")
280275
dps2, ok := drivers[k2]
281276
if !ok {
282-
return nil, errors.Errorf("failed to find target %s for context %s", k2, strings.TrimPrefix(k, "context:")) // should be validated before already
277+
return errors.Errorf("failed to find target %s for context %s", k2, strings.TrimPrefix(k, "context:")) // should be validated before already
283278
}
279+
284280
var found bool
285281
for _, dp2 := range dps2 {
286282
if dp2.driverIndex == dp.driverIndex {
@@ -289,12 +285,63 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
289285
}
290286
}
291287
if !found {
292-
return nil, errors.Errorf("failed to use %s as context %s for %s because targets build with different drivers", k2, strings.TrimPrefix(k, "context:"), name)
288+
return errors.Errorf("failed to use %s as context %s for %s because targets build with different drivers", k2, strings.TrimPrefix(k, "context:"), name)
293289
}
294290
}
295291
}
296292
}
297293
}
294+
return nil
295+
}
296+
297+
func toRepoOnly(in string) (string, error) {
298+
m := map[string]struct{}{}
299+
p := strings.Split(in, ",")
300+
for _, pp := range p {
301+
n, err := reference.ParseNormalizedNamed(pp)
302+
if err != nil {
303+
return "", err
304+
}
305+
m[n.Name()] = struct{}{}
306+
}
307+
out := make([]string, 0, len(m))
308+
for k := range m {
309+
out = append(out, k)
310+
}
311+
return strings.Join(out, ","), nil
312+
}
313+
314+
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
315+
return BuildWithResultHandler(ctx, nodes, opts, docker, cfg, w, nil)
316+
}
317+
318+
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer, resultHandleFunc func(driverIdx int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) {
319+
if len(nodes) == 0 {
320+
return nil, errors.Errorf("driver required for build")
321+
}
322+
323+
nodes, err = filterAvailableNodes(nodes)
324+
if err != nil {
325+
return nil, errors.Wrapf(err, "no valid drivers found")
326+
}
327+
warnOnNoOutput(ctx, nodes, opts)
328+
329+
drivers, err := resolveDrivers(ctx, nodes, opts, w)
330+
if err != nil {
331+
return nil, err
332+
}
333+
334+
eg, ctx := errgroup.WithContext(ctx)
335+
reqForNodes, release, err := newBuildRequests(ctx, docker, cfg, drivers, w, opts)
336+
if err != nil {
337+
return nil, err
338+
}
339+
defer release()
340+
341+
// validate that all links between targets use same drivers
342+
if err := validateTargetLinks(reqForNodes, drivers, opts); err != nil {
343+
return nil, err
344+
}
298345

299346
sharedSessions, err := detectSharedMounts(ctx, reqForNodes)
300347
if err != nil {
@@ -311,7 +358,6 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
311358

312359
for k, opt := range opts {
313360
err := func(k string) (err error) {
314-
opt := opt
315361
dps := drivers[k]
316362
multiDriver := len(drivers[k]) > 1
317363

@@ -441,51 +487,27 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
441487
req.FrontendOpt["requestid"] = "frontend." + opt.CallFunc.Name
442488
}
443489

444-
res, err := c.Solve(ctx, req)
490+
res, err := solve(ctx, c, req)
445491
if err != nil {
446-
req, ok := fallbackPrintError(err, req)
447-
if ok {
448-
res2, err2 := c.Solve(ctx, req)
449-
if err2 != nil {
450-
return nil, err
451-
}
452-
res = res2
453-
} else {
454-
return nil, err
455-
}
492+
return nil, err
456493
}
494+
457495
if opt.CallFunc != nil {
458496
callRes = res.Metadata
459497
}
460498

461499
rKey := resultKey(dp.driverIndex, k)
462500
results.Set(rKey, res)
463501

464-
if children, ok := childTargets[rKey]; ok && len(children) > 0 {
465-
// wait for the child targets to register their LLB before evaluating
466-
_, err := results.Get(ctx, children...)
467-
if err != nil {
468-
return nil, err
469-
}
470-
// we need to wait until the child targets have completed before we can release
471-
eg, ctx := errgroup.WithContext(ctx)
472-
eg.Go(func() error {
473-
return res.EachRef(func(ref gateway.Reference) error {
474-
return ref.Evaluate(ctx)
475-
})
476-
})
477-
eg.Go(func() error {
478-
_, err := results.Get(ctx, children...)
479-
return err
480-
})
481-
if err := eg.Wait(); err != nil {
502+
if children := childTargets[rKey]; len(children) > 0 {
503+
if err := waitForChildren(ctx, res, results, children); err != nil {
482504
return nil, err
483505
}
484506
}
485-
486507
return res, nil
487508
}
488509
buildRef := fmt.Sprintf("%s/%s/%s", node.Builder, node.Name, so.Ref)
510+
489511
var rr *client.SolveResponse
490512
if resultHandleFunc != nil {
491513
var resultHandle *ResultHandle
@@ -496,6 +518,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
496518
rr, err = c.Build(ctx, *so, "buildx", buildFunc, ch)
497519
tracing.FinishWithError(span, err)
498520
}
521+
499522
if !so.Internal && desktop.BuildBackendEnabled() && node.Driver.HistoryAPISupported(ctx) {
500523
if err != nil {
501524
return &desktop.ErrorWithBuildRef{
@@ -1146,3 +1169,40 @@ func ReadSourcePolicy() (*spb.Policy, error) {
11461169

11471170
return &pol, nil
11481171
}
1172+
1173+
func solve(ctx context.Context, c gateway.Client, req gateway.SolveRequest) (*gateway.Result, error) {
1174+
res, err := c.Solve(ctx, req)
1175+
if err != nil {
1176+
req, ok := fallbackPrintError(err, req)
1177+
if ok {
1178+
res2, err2 := c.Solve(ctx, req)
1179+
if err2 != nil {
1180+
return nil, err
1181+
}
1182+
res = res2
1183+
} else {
1184+
return nil, err
1185+
}
1186+
}
1187+
return res, nil
1188+
}
1189+
1190+
func waitForChildren(ctx context.Context, res *gateway.Result, results *waitmap.Map, children []string) error {
1191+
// wait for the child targets to register their LLB before evaluating
1192+
_, err := results.Get(ctx, children...)
1193+
if err != nil {
1194+
return err
1195+
}
1196+
// we need to wait until the child targets have completed before we can release
1197+
eg, ctx := errgroup.WithContext(ctx)
1198+
eg.Go(func() error {
1199+
return res.EachRef(func(ref gateway.Reference) error {
1200+
return ref.Evaluate(ctx)
1201+
})
1202+
})
1203+
eg.Go(func() error {
1204+
_, err := results.Get(ctx, children...)
1205+
return err
1206+
})
1207+
return eg.Wait()
1208+
}

0 commit comments

Comments
 (0)