diff --git a/README.md b/README.md index 11b7c8736fcb..48a261e17e1c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![asciicinema example](https://asciinema.org/a/gPEIEo1NzmDTUu2bEPsUboqmU.png)](https://asciinema.org/a/gPEIEo1NzmDTUu2bEPsUboqmU) -# BuildKit +# BuildKit [![GoDoc](https://godoc.org/github.com/moby/buildkit?status.svg)](https://godoc.org/github.com/moby/buildkit/client/llb) [![Build Status](https://github.com/moby/buildkit/workflows/build/badge.svg)](https://github.com/moby/buildkit/actions?query=workflow%3Abuild) @@ -45,11 +45,11 @@ Join `#buildkit` channel on [Docker Community Slack](http://dockr.ly/slack) - [Used by](#used-by) - [Quick start](#quick-start) - - [Starting the `buildkitd` daemon:](#starting-the-buildkitd-daemon) + - [Starting the `buildkitd` daemon](#starting-the-buildkitd-daemon) - [Exploring LLB](#exploring-llb) - [Exploring Dockerfiles](#exploring-dockerfiles) - [Building a Dockerfile with `buildctl`](#building-a-dockerfile-with-buildctl) - - [Building a Dockerfile using external frontend:](#building-a-dockerfile-using-external-frontend) + - [Building a Dockerfile using external frontend](#building-a-dockerfile-using-external-frontend) - [Output](#output) - [Image/Registry](#imageregistry) - [Local directory](#local-directory) @@ -76,6 +76,8 @@ Join `#buildkit` channel on [Docker Community Slack](http://dockr.ly/slack) - [Opentracing support](#opentracing-support) - [Running BuildKit without root privileges](#running-buildkit-without-root-privileges) - [Building multi-platform images](#building-multi-platform-images) + - [Configuring `buildctl`](#configuring-buildctl) + - [Color Output Controls](#color-output-controls) - [Contributing](#contributing) @@ -121,7 +123,7 @@ $ brew install buildkit To build BuildKit from source, see [`.github/CONTRIBUTING.md`](./.github/CONTRIBUTING.md). -### Starting the `buildkitd` daemon: +### Starting the `buildkitd` daemon You need to run `buildkitd` as the root user on the host. @@ -193,7 +195,7 @@ buildctl build \ `--local` exposes local source files from client to the builder. `context` and `dockerfile` are the names Dockerfile frontend looks for build context and Dockerfile location. -#### Building a Dockerfile using external frontend: +#### Building a Dockerfile using external frontend External versions of the Dockerfile frontend are pushed to https://hub.docker.com/r/docker/dockerfile-upstream and https://hub.docker.com/r/docker/dockerfile and can be used with the gateway frontend. The source for the external frontend is currently located in `./frontend/dockerfile/cmd/dockerfile-frontend` but will move out of this repository in the future ([#163](https://github.com/moby/buildkit/issues/163)). For automatic build from master branch of this repository `docker/dockerfile-upstream:master` or `docker/dockerfile-upstream:master-labs` image can be used. @@ -669,6 +671,16 @@ Please refer to [`docs/rootless.md`](docs/rootless.md). Please refer to [`docs/multi-platform.md`](docs/multi-platform.md). +### Configuring `buildctl` + +#### Color Output Controls + +`buildctl` has support for modifying the colors that are used to output information to the terminal. You can set the environment variable `BUILDKIT_COLORS` to something like `run=blue;cancel=yellow;warn=orange;error=123,12,0` to set the colors that you would like to use. Setting `NO_COLOR` to anything will disable any colorized output as recommended by [no-color.org](https://no-color.org/). + +Parsing errors will be reported but ignored. This will result in default color values being used where needed. + +- [The list of pre-defined colors](https://github.com/moby/buildkit/blob/master/util/progress/progressui/colors.go). + ## Contributing Want to contribute to BuildKit? Awesome! You can find information about contributing to this project in the [CONTRIBUTING.md](/.github/CONTRIBUTING.md) diff --git a/util/progress/progressui/colors.go b/util/progress/progressui/colors.go new file mode 100644 index 000000000000..bfa340bd14a8 --- /dev/null +++ b/util/progress/progressui/colors.go @@ -0,0 +1,135 @@ +package progressui + +import ( + "encoding/csv" + "errors" + "strconv" + "strings" + + "github.com/morikuni/aec" + "github.com/sirupsen/logrus" +) + +var termColorMap = map[string]aec.ANSI{ + "default": aec.DefaultF, + + "black": aec.BlackF, + "blue": aec.BlueF, + "cyan": aec.CyanF, + "green": aec.GreenF, + "magenta": aec.MagentaF, + "red": aec.RedF, + "white": aec.WhiteF, + "yellow": aec.YellowF, + + "light-black": aec.LightBlackF, + "light-blue": aec.LightBlueF, + "light-cyan": aec.LightCyanF, + "light-green": aec.LightGreenF, + "light-magenta": aec.LightMagentaF, + "light-red": aec.LightRedF, + "light-white": aec.LightWhiteF, + "light-yellow": aec.LightYellowF, +} + +func setUserDefinedTermColors(colorsEnv string) { + fields := readBuildkitColorsEnv(colorsEnv) + if fields == nil { + return + } + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 || strings.Contains(parts[1], "=") { + err := errors.New("A valid entry must have exactly two fields") + logrus.WithError(err).Warnf("Could not parse BUILDKIT_COLORS component: %s", field) + continue + } + k := strings.ToLower(parts[0]) + v := parts[1] + if c, ok := termColorMap[strings.ToLower(v)]; ok { + parseKeys(k, c) + } else if strings.Contains(v, ",") { + if c := readRGB(v); c != nil { + parseKeys(k, c) + } + } else { + err := errors.New("Colors must be a name from the pre-defined list or a valid 3-part RGB value") + logrus.WithError(err).Warnf("Unknown color value found in BUILDKIT_COLORS: %s=%s", k, v) + } + } +} + +func readBuildkitColorsEnv(colorsEnv string) []string { + csvReader := csv.NewReader(strings.NewReader(colorsEnv)) + csvReader.Comma = ':' + fields, err := csvReader.Read() + if err != nil { + logrus.WithError(err).Warnf("Could not parse BUILDKIT_COLORS. Falling back to defaults.") + return nil + } + return fields +} + +func readRGB(v string) aec.ANSI { + csvReader := csv.NewReader(strings.NewReader(v)) + fields, err := csvReader.Read() + if err != nil { + logrus.WithError(err).Warnf("Could not parse value %s as valid comma-separated RGB color. Ignoring.", v) + return nil + } + if len(fields) != 3 { + err = errors.New("A valid RGB color must have three fields") + logrus.WithError(err).Warnf("Could not parse value %s as valid RGB color. Ignoring.", v) + return nil + } + ok := isValidRGB(fields) + if ok { + p1, _ := strconv.Atoi(fields[0]) + p2, _ := strconv.Atoi(fields[1]) + p3, _ := strconv.Atoi(fields[2]) + c := aec.Color8BitF(aec.NewRGB8Bit(uint8(p1), uint8(p2), uint8(p3))) + return c + } + return nil +} + +func parseKeys(k string, c aec.ANSI) { + key := strings.ToLower(k) + switch key { + case "run": + colorRun = c + case "cancel": + colorCancel = c + case "error": + colorError = c + case "warning": + colorWarning = c + default: + logrus.Warnf("Unknown key found in BUILDKIT_COLORS (expected: run, cancel, error, or warning): %s", k) + } +} + +func isValidRGB(s []string) bool { + for _, n := range s { + num, err := strconv.Atoi(n) + if err != nil { + logrus.Warnf("A field in BUILDKIT_COLORS appears to contain an RGB value that is not an integer: %s", strings.Join(s, ",")) + return false + } + ok := isValidRGBValue(num) + if ok { + continue + } else { + logrus.Warnf("A field in BUILDKIT_COLORS appears to contain an RGB value that is not within the valid range of 0-255: %s", strings.Join(s, ",")) + return false + } + } + return true +} + +func isValidRGBValue(i int) bool { + if (i >= 0) && (i <= 255) { + return true + } + return false +} diff --git a/util/progress/progressui/display.go b/util/progress/progressui/display.go index 2d4ccd153e2d..edbdaaa75e59 100644 --- a/util/progress/progressui/display.go +++ b/util/progress/progressui/display.go @@ -108,6 +108,7 @@ type job struct { name string status string hasError bool + hasWarning bool // This is currently unused, but it's here for future use. isCanceled bool vertex *vertex showTerm bool @@ -829,8 +830,13 @@ func (disp *display) print(d displayInfo, width, height int, all bool) { color = colorCancel } else if j.hasError { color = colorError + } else if j.hasWarning { + // This is currently unused, but it's here for future use. + color = colorWarning + } + if color != nil { + out = aec.Apply(out, color) } - out = aec.Apply(out, color) } fmt.Fprint(disp.c, out) lineCount++ diff --git a/util/progress/progressui/init.go b/util/progress/progressui/init.go new file mode 100644 index 000000000000..f21072214825 --- /dev/null +++ b/util/progress/progressui/init.go @@ -0,0 +1,37 @@ +package progressui + +import ( + "os" + "runtime" + + "github.com/morikuni/aec" +) + +var colorRun aec.ANSI +var colorCancel aec.ANSI +var colorWarning aec.ANSI +var colorError aec.ANSI + +func init() { + // As recommended on https://no-color.org/ + if _, ok := os.LookupEnv("NO_COLOR"); ok { + // nil values will result in no ANSI color codes being emitted. + return + } else if runtime.GOOS == "windows" { + colorRun = termColorMap["cyan"] + colorCancel = termColorMap["yellow"] + colorWarning = termColorMap["yellow"] + colorError = termColorMap["red"] + } else { + colorRun = termColorMap["blue"] + colorCancel = termColorMap["yellow"] + colorWarning = termColorMap["yellow"] + colorError = termColorMap["red"] + } + + // Loosely based on the standard set by Linux LS_COLORS. + if _, ok := os.LookupEnv("BUILDKIT_COLORS"); ok { + envColorString := os.Getenv("BUILDKIT_COLORS") + setUserDefinedTermColors(envColorString) + } +} diff --git a/util/progress/progressui/term.go b/util/progress/progressui/term.go deleted file mode 100644 index 08f1b8e4d17a..000000000000 --- a/util/progress/progressui/term.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !windows -// +build !windows - -package progressui - -import "github.com/morikuni/aec" - -var ( - colorRun = aec.BlueF - colorCancel = aec.YellowF - colorError = aec.RedF -) diff --git a/util/progress/progressui/term_windows.go b/util/progress/progressui/term_windows.go deleted file mode 100644 index c8ce914a0765..000000000000 --- a/util/progress/progressui/term_windows.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build windows -// +build windows - -package progressui - -import "github.com/morikuni/aec" - -var ( - colorRun = aec.CyanF - colorCancel = aec.YellowF - colorError = aec.RedF -)