Skip to content

Commit f896fed

Browse files
committed
build: display build details link
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
1 parent 7cef021 commit f896fed

8 files changed

Lines changed: 152 additions & 4 deletions

File tree

build/build.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/docker/buildx/builder"
2727
"github.com/docker/buildx/driver"
2828
"github.com/docker/buildx/localstate"
29+
"github.com/docker/buildx/util/desktop"
2930
"github.com/docker/buildx/util/dockerutil"
3031
"github.com/docker/buildx/util/imagetools"
3132
"github.com/docker/buildx/util/progress"
@@ -822,6 +823,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
822823

823824
for i, dp := range dps {
824825
i, dp, so := i, dp, *dp.so
826+
node := nodes[dp.driverIndex]
825827
if multiDriver {
826828
for i, e := range so.Exports {
827829
switch e.Type {
@@ -940,6 +942,16 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
940942
} else {
941943
rr, err = c.Build(ctx, so, "buildx", buildFunc, ch)
942944
}
945+
if node.Driver.Features(ctx)[driver.HistoryAPI] && desktop.BuildBackendEnabled() {
946+
buildRef := fmt.Sprintf("%s/%s/%s", node.Builder, node.Name, so.Ref)
947+
if err != nil {
948+
return &desktop.ErrorWithBuildRef{
949+
Ref: buildRef,
950+
Err: err,
951+
}
952+
}
953+
progress.WriteBuildRef(w, k, buildRef)
954+
}
943955
if err != nil {
944956
return err
945957
}

cmd/buildx/main.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"fmt"
55
"os"
66

7+
"github.com/containerd/console"
78
"github.com/docker/buildx/commands"
9+
"github.com/docker/buildx/util/desktop"
810
"github.com/docker/buildx/version"
911
"github.com/docker/cli/cli"
1012
"github.com/docker/cli/cli-plugins/manager"
@@ -81,10 +83,20 @@ func main() {
8183
for _, s := range errdefs.Sources(err) {
8284
s.Print(cmd.Err())
8385
}
86+
87+
var bdOutput string
88+
if ebr, ok := err.(*desktop.ErrorWithBuildRef); ok {
89+
var term bool
90+
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
91+
term = true
92+
}
93+
bdOutput = fmt.Sprintf("\n%s", desktop.BuildDetailsOutput(map[string]string{"default": ebr.Ref}, term))
94+
}
95+
8496
if debug.IsEnabled() {
85-
fmt.Fprintf(cmd.Err(), "ERROR: %+v", stack.Formatter(err))
97+
fmt.Fprintf(cmd.Err(), "ERROR: %+v%s", stack.Formatter(err), bdOutput)
8698
} else {
87-
fmt.Fprintf(cmd.Err(), "ERROR: %v\n", err)
99+
fmt.Fprintf(cmd.Err(), "ERROR: %v\n%s", err, bdOutput)
88100
}
89101

90102
os.Exit(1)

commands/bake.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import (
66
"fmt"
77
"os"
88

9+
"github.com/containerd/console"
910
"github.com/containerd/containerd/platforms"
1011
"github.com/docker/buildx/bake"
1112
"github.com/docker/buildx/build"
1213
"github.com/docker/buildx/builder"
1314
"github.com/docker/buildx/util/buildflags"
1415
"github.com/docker/buildx/util/cobrautil/completion"
1516
"github.com/docker/buildx/util/confutil"
17+
"github.com/docker/buildx/util/desktop"
1618
"github.com/docker/buildx/util/dockerutil"
1719
"github.com/docker/buildx/util/progress"
1820
"github.com/docker/buildx/util/tracing"
@@ -117,8 +119,19 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
117119
progressTextDesc = fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver)
118120
}
119121

120-
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, cFlags.progress,
122+
var term bool
123+
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
124+
term = true
125+
}
126+
127+
var printer *progress.Printer
128+
printer, err = progress.NewPrinter(ctx2, os.Stderr, os.Stderr, cFlags.progress,
121129
progress.WithDesc(progressTextDesc, progressConsoleDesc),
130+
progress.WithOnClose(func() {
131+
if cFlags.progress != progress.PrinterModeQuiet {
132+
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
133+
}
134+
}),
122135
)
123136
if err != nil {
124137
return err

commands/build.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/docker/buildx/store"
2727
"github.com/docker/buildx/store/storeutil"
2828
"github.com/docker/buildx/util/buildflags"
29+
"github.com/docker/buildx/util/desktop"
2930
"github.com/docker/buildx/util/ioset"
3031
"github.com/docker/buildx/util/progress"
3132
"github.com/docker/buildx/util/tracing"
@@ -238,6 +239,11 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
238239
return err
239240
}
240241

242+
var term bool
243+
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
244+
term = true
245+
}
246+
241247
ctx2, cancel := context.WithCancel(context.TODO())
242248
defer cancel()
243249
progressMode, err := options.toProgress()
@@ -252,6 +258,9 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
252258
),
253259
progress.WithOnClose(func() {
254260
printWarnings(os.Stderr, printer.Warnings(), progressMode)
261+
if progressMode != progress.PrinterModeQuiet {
262+
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
263+
}
255264
}),
256265
)
257266
if err != nil {
@@ -270,7 +279,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
270279
retErr = err
271280
}
272281
if retErr != nil {
273-
return retErr
282+
return wrapBuildError(retErr, false)
274283
}
275284

276285
if options.quiet {

controller/pb/progress.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ func (w *writer) Write(status *client.SolveStatus) {
1919
w.ch <- ToControlStatus(status)
2020
}
2121

22+
func (w *writer) WriteBuildRef(target string, ref string) {
23+
return
24+
}
25+
2226
func (w *writer) ValidateLogSource(digest.Digest, interface{}) bool {
2327
return true
2428
}

util/desktop/desktop.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package desktop
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"os"
8+
"path/filepath"
9+
"sync"
10+
)
11+
12+
var (
13+
bbEnabledOnce sync.Once
14+
bbEnabled bool
15+
)
16+
17+
func BuildBackendEnabled() bool {
18+
bbEnabledOnce.Do(func() {
19+
home, err := os.UserHomeDir()
20+
if err != nil {
21+
return
22+
}
23+
_, err = os.Stat(filepath.Join(home, ".docker", "desktop-build", ".lastaccess"))
24+
bbEnabled = err == nil
25+
})
26+
return bbEnabled
27+
}
28+
29+
func BuildDetailsOutput(refs map[string]string, term bool) string {
30+
if len(refs) == 0 {
31+
return ""
32+
}
33+
refURL := func(ref string) string {
34+
return fmt.Sprintf("docker-desktop://dashboard/build/%s", ref)
35+
}
36+
var out bytes.Buffer
37+
out.WriteString("View build details: ")
38+
multiTargets := len(refs) > 1
39+
for target, ref := range refs {
40+
if multiTargets {
41+
out.WriteString(fmt.Sprintf("\n %s: ", target))
42+
}
43+
if term {
44+
out.WriteString(hyperlink(refURL(ref)))
45+
} else {
46+
out.WriteString(refURL(ref))
47+
}
48+
}
49+
return out.String()
50+
}
51+
52+
func PrintBuildDetails(w io.Writer, refs map[string]string, term bool) {
53+
if out := BuildDetailsOutput(refs, term); out != "" {
54+
fmt.Fprintf(w, "\n%s\n", out)
55+
}
56+
}
57+
58+
func hyperlink(url string) string {
59+
// create an escape sequence using the OSC 8 format: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
60+
return fmt.Sprintf("\033]8;;%s\033\\%s\033]8;;\033\\", url, url)
61+
}
62+
63+
type ErrorWithBuildRef struct {
64+
Ref string
65+
Err error
66+
Msg string
67+
}
68+
69+
func (e *ErrorWithBuildRef) Error() string {
70+
return e.Err.Error()
71+
}
72+
73+
func (e *ErrorWithBuildRef) Unwrap() error {
74+
return e.Err
75+
}

util/progress/printer.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ type Printer struct {
3333
warnings []client.VertexWarning
3434
logMu sync.Mutex
3535
logSourceMap map[digest.Digest]interface{}
36+
37+
// TODO: remove once we can use result context to pass build ref
38+
// see https://github.com/docker/buildx/pull/1861
39+
buildRefsMu sync.Mutex
40+
buildRefs map[string]string
3641
}
3742

3843
func (p *Printer) Wait() error {
@@ -143,6 +148,19 @@ func NewPrinter(ctx context.Context, w io.Writer, out console.File, mode string,
143148
return pw, nil
144149
}
145150

151+
func (p *Printer) WriteBuildRef(target string, ref string) {
152+
p.buildRefsMu.Lock()
153+
defer p.buildRefsMu.Unlock()
154+
if p.buildRefs == nil {
155+
p.buildRefs = map[string]string{}
156+
}
157+
p.buildRefs[target] = ref
158+
}
159+
160+
func (p *Printer) BuildRefs() map[string]string {
161+
return p.buildRefs
162+
}
163+
146164
type printerOpts struct {
147165
displayOpts []progressui.DisplaySolveStatusOpt
148166

util/progress/writer.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
type Writer interface {
1212
Write(*client.SolveStatus)
13+
WriteBuildRef(string, string)
1314
ValidateLogSource(digest.Digest, interface{}) bool
1415
ClearLogSource(interface{})
1516
}
@@ -41,6 +42,10 @@ func Write(w Writer, name string, f func() error) {
4142
})
4243
}
4344

45+
func WriteBuildRef(w Writer, target string, ref string) {
46+
w.WriteBuildRef(target, ref)
47+
}
48+
4449
func NewChannel(w Writer) (chan *client.SolveStatus, chan struct{}) {
4550
ch := make(chan *client.SolveStatus)
4651
done := make(chan struct{})

0 commit comments

Comments
 (0)