@@ -4,12 +4,16 @@ import (
44 "bytes"
55 "cmp"
66 "context"
7+ "crypto/sha256"
8+ "encoding/hex"
79 "encoding/json"
810 "fmt"
911 "io"
1012 "os"
1113 "slices"
14+ "sort"
1215 "strings"
16+ "sync"
1317 "text/tabwriter"
1418
1519 "github.com/containerd/console"
@@ -26,13 +30,15 @@ import (
2630 "github.com/docker/buildx/util/confutil"
2731 "github.com/docker/buildx/util/desktop"
2832 "github.com/docker/buildx/util/dockerutil"
33+ "github.com/docker/buildx/util/osutil"
2934 "github.com/docker/buildx/util/progress"
3035 "github.com/docker/buildx/util/tracing"
3136 "github.com/docker/cli/cli/command"
3237 "github.com/moby/buildkit/identity"
3338 "github.com/moby/buildkit/util/progress/progressui"
3439 "github.com/pkg/errors"
3540 "github.com/spf13/cobra"
41+ "go.opentelemetry.io/otel/attribute"
3642)
3743
3844type bakeOptions struct {
@@ -52,6 +58,8 @@ type bakeOptions struct {
5258}
5359
5460func runBake (ctx context.Context , dockerCli command.Cli , targets []string , in bakeOptions , cFlags commonFlags ) (err error ) {
61+ mp := dockerCli .MeterProvider ()
62+
5563 ctx , end , err := tracing .TraceCurrentCommand (ctx , "bake" )
5664 if err != nil {
5765 return err
@@ -60,22 +68,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
6068 end (err )
6169 }()
6270
63- var url string
64- cmdContext := "cwd://"
65-
66- if len (targets ) > 0 {
67- if build .IsRemoteURL (targets [0 ]) {
68- url = targets [0 ]
69- targets = targets [1 :]
70- if len (targets ) > 0 {
71- if build .IsRemoteURL (targets [0 ]) {
72- cmdContext = targets [0 ]
73- targets = targets [1 :]
74- }
75- }
76- }
77- }
78-
71+ url , cmdContext , targets := bakeArgs (targets )
7972 if len (targets ) == 0 {
8073 targets = []string {"default" }
8174 }
@@ -116,6 +109,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
116109 var progressConsoleDesc , progressTextDesc string
117110
118111 // instance only needed for reading remote bake files or building
112+ var driverType string
119113 if url != "" || ! in .printOnly {
120114 b , err := builder .New (dockerCli ,
121115 builder .WithName (in .builder ),
@@ -133,17 +127,20 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
133127 }
134128 progressConsoleDesc = fmt .Sprintf ("%s:%s" , b .Driver , b .Name )
135129 progressTextDesc = fmt .Sprintf ("building with %q instance using %s driver" , b .Name , b .Driver )
130+ driverType = b .Driver
136131 }
137132
138133 var term bool
139134 if _ , err := console .ConsoleFromFile (os .Stderr ); err == nil {
140135 term = true
141136 }
137+ attributes := bakeMetricAttributes (dockerCli , driverType , url , cmdContext , targets , & in )
142138
143139 progressMode := progressui .DisplayMode (cFlags .progress )
144140 var printer * progress.Printer
145141 printer , err = progress .NewPrinter (ctx2 , os .Stderr , progressMode ,
146142 progress .WithDesc (progressTextDesc , progressConsoleDesc ),
143+ progress .WithMetrics (mp , attributes ),
147144 progress .WithOnClose (func () {
148145 printWarnings (os .Stderr , printer .Warnings (), progressMode )
149146 }),
@@ -241,12 +238,18 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
241238 return err
242239 }
243240
241+ done := timeBuildCommand (mp , attributes )
244242 resp , retErr := build .Build (ctx , nodes , bo , dockerutil .NewClient (dockerCli ), confutil .ConfigDir (dockerCli ), printer )
245243 if err := printer .Wait (); retErr == nil {
246244 retErr = err
247245 }
248246 if retErr != nil {
249- return wrapBuildError (retErr , true )
247+ err = wrapBuildError (retErr , true )
248+ }
249+ done (err )
250+
251+ if err != nil {
252+ return err
250253 }
251254
252255 if progressMode != progressui .QuietMode && progressMode != progressui .RawJSONMode {
@@ -268,7 +271,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
268271 }
269272
270273 var callFormatJSON bool
271- var jsonResults = map [string ]map [string ]any {}
274+ jsonResults : = map [string ]map [string ]any {}
272275 if callFunc != nil {
273276 callFormatJSON = callFunc .Format == "json"
274277 }
@@ -456,6 +459,21 @@ func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string
456459 })
457460}
458461
462+ // bakeArgs will retrieve the remote url, command context, and targets
463+ // from the command line arguments.
464+ func bakeArgs (args []string ) (url , cmdContext string , targets []string ) {
465+ cmdContext , targets = "cwd://" , args
466+ if len (targets ) == 0 || ! build .IsRemoteURL (targets [0 ]) {
467+ return url , cmdContext , targets
468+ }
469+ url , targets = targets [0 ], targets [1 :]
470+ if len (targets ) == 0 || ! build .IsRemoteURL (targets [0 ]) {
471+ return url , cmdContext , targets
472+ }
473+ cmdContext , targets = targets [0 ], targets [1 :]
474+ return url , cmdContext , targets
475+ }
476+
459477func readBakeFiles (ctx context.Context , nodes []builder.Node , url string , names []string , stdin io.Reader , pw progress.Writer ) (files []bake.File , inp * bake.Input , err error ) {
460478 var lnames []string // local
461479 var rnames []string // remote
@@ -570,3 +588,67 @@ func printTargetList(w io.Writer, cfg *bake.Config) error {
570588
571589 return nil
572590}
591+
592+ func bakeMetricAttributes (dockerCli command.Cli , driverType , url , cmdContext string , targets []string , options * bakeOptions ) attribute.Set {
593+ return attribute .NewSet (
594+ commandNameAttribute .String ("bake" ),
595+ attribute .Stringer (string (commandOptionsHash ), & bakeOptionsHash {
596+ bakeOptions : options ,
597+ configDir : confutil .ConfigDir (dockerCli ),
598+ url : url ,
599+ cmdContext : cmdContext ,
600+ targets : targets ,
601+ }),
602+ driverNameAttribute .String (options .builder ),
603+ driverTypeAttribute .String (driverType ),
604+ )
605+ }
606+
607+ type bakeOptionsHash struct {
608+ * bakeOptions
609+ configDir string
610+ url string
611+ cmdContext string
612+ targets []string
613+ result string
614+ resultOnce sync.Once
615+ }
616+
617+ func (o * bakeOptionsHash ) String () string {
618+ o .resultOnce .Do (func () {
619+ url := o .url
620+ cmdContext := o .cmdContext
621+ if cmdContext == "cwd://" {
622+ // Resolve the directory if the cmdContext is the current working directory.
623+ cmdContext = osutil .GetWd ()
624+ }
625+
626+ // Sort the inputs for files and targets since the ordering
627+ // doesn't matter, but avoid modifying the original slice.
628+ files := immutableSort (o .files )
629+ targets := immutableSort (o .targets )
630+
631+ joinedFiles := strings .Join (files , "," )
632+ joinedTargets := strings .Join (targets , "," )
633+ salt := confutil .TryNodeIdentifier (o .configDir )
634+
635+ h := sha256 .New ()
636+ for _ , s := range []string {url , cmdContext , joinedFiles , joinedTargets , salt } {
637+ _ , _ = io .WriteString (h , s )
638+ h .Write ([]byte {0 })
639+ }
640+ o .result = hex .EncodeToString (h .Sum (nil ))
641+ })
642+ return o .result
643+ }
644+
645+ // immutableSort will sort the entries in s without modifying the original slice.
646+ func immutableSort (s []string ) []string {
647+ if ! sort .StringsAreSorted (s ) {
648+ cpy := make ([]string , len (s ))
649+ copy (cpy , s )
650+ sort .Strings (cpy )
651+ return cpy
652+ }
653+ return s
654+ }
0 commit comments