Repository: docker/compose

Issue:
Add `docker compose build --check`
### Description

I noticed when running `docker compose build` on a project that some warnings were printed in the output

```
 => WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 4) 
```

I found that the base `docker` command supports

```
docker build --check .
```

- https://docs.docker.com/build/checks/
- https://docs.docker.com/reference/build-checks/

And this will flag and fail if there are any warnings present. This would be very valuable to have in CI, and something that Compose could make convenient for Compose projects:

```
docker compose build --check
```

This would output the same as `docker build --check .` for each `service` that is built and return a failing status code if any do not pass the checking.

Discussion:

Solution patch:
diff --git a/cmd/compose/build.go b/cmd/compose/build.go
index 58636e0d..f31fbd28 100644
--- a/cmd/compose/build.go
+++ b/cmd/compose/build.go
@@ -45,6 +45,7 @@ type buildOptions struct {
 	builder string
 	deps    bool
 	print   bool
+	check   bool
 }
 
 func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
@@ -79,6 +80,7 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
 		Deps:     opts.deps,
 		Memory:   int64(opts.memory),
 		Print:    opts.print,
+		Check:    opts.check,
 		SSHs:     SSHKeys,
 		Builder:  builderName,
 	}, nil
@@ -135,6 +137,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
 	flags.StringVar(&p.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
 	flags.MarkHidden("progress") //nolint:errcheck
 	flags.BoolVar(&opts.print, "print", false, "Print equivalent bake file")
+	flags.BoolVar(&opts.check, "check", false, "Check build configuration")
 
 	return cmd
 }
diff --git a/docs/reference/compose_build.md b/docs/reference/compose_build.md
index 98d573e4..5589a469 100644
--- a/docs/reference/compose_build.md
+++ b/docs/reference/compose_build.md
@@ -17,6 +17,7 @@ run `docker compose build` to rebuild it.
 |:----------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------|
 | `--build-arg`         | `stringArray` |         | Set build-time variables for services                                                                       |
 | `--builder`           | `string`      |         | Set builder to use                                                                                          |
+| `--check`             | `bool`        |         | Check build configuration                                                                                   |
 | `--dry-run`           | `bool`        |         | Execute command in dry run mode                                                                             |
 | `-m`, `--memory`      | `bytes`       | `0`     | Set memory limit for the build container. Not supported by BuildKit.                                        |
 | `--no-cache`          | `bool`        |         | Do not use cache when building the image                                                                    |
diff --git a/docs/reference/docker_compose_build.yaml b/docs/reference/docker_compose_build.yaml
index 3f53dcf7..1197d531 100644
--- a/docs/reference/docker_compose_build.yaml
+++ b/docs/reference/docker_compose_build.yaml
@@ -33,6 +33,16 @@ options:
       experimentalcli: false
       kubernetes: false
       swarm: false
+    - option: check
+      value_type: bool
+      default_value: "false"
+      description: Check build configuration
+      deprecated: false
+      hidden: false
+      experimental: false
+      experimentalcli: false
+      kubernetes: false
+      swarm: false
     - option: compress
       value_type: bool
       default_value: "true"
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 6745991d..0b159b22 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -157,6 +157,8 @@ type BuildOptions struct {
 	Builder string
 	// Print don't actually run builder but print equivalent build config
 	Print bool
+	// Check let builder validate build configuration
+	Check bool
 }
 
 // Apply mutates project according to build options
diff --git a/pkg/compose/build_bake.go b/pkg/compose/build_bake.go
index 589ed540..31864cef 100644
--- a/pkg/compose/build_bake.go
+++ b/pkg/compose/build_bake.go
@@ -176,12 +176,16 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
 			privileged = true
 		}
 
-		var output string
+		var outputs []string
+		var call string
 		push := options.Push && service.Image != ""
-		if len(service.Build.Platforms) > 1 {
-			output = fmt.Sprintf("type=image,push=%t", push)
-		} else {
-			output = fmt.Sprintf("type=docker,load=true,push=%t", push)
+		switch {
+		case options.Check:
+			call = "lint"
+		case len(service.Build.Platforms) > 1:
+			outputs = []string{fmt.Sprintf("type=image,push=%t", push)}
+		default:
+			outputs = []string{fmt.Sprintf("type=docker,load=true,push=%t", push)}
 		}
 
 		read = append(read, build.Context)
@@ -212,7 +216,9 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
 			ShmSize:      build.ShmSize,
 			Ulimits:      toBakeUlimits(build.Ulimits),
 			Entitlements: entitlements,
-			Outputs:      []string{output},
+
+			Outputs: outputs,
+			Call:    call,
 		}
 		group.Targets = append(group.Targets, serviceName)
 	}
@@ -284,7 +290,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
 		return nil, err
 	}
 
-	var errMessage string
+	var errMessage []string
 	scanner := bufio.NewScanner(pipe)
 	scanner.Split(bufio.ScanLines)
 
@@ -300,7 +306,9 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
 		err := decoder.Decode(&status)
 		if err != nil {
 			if strings.HasPrefix(line, "ERROR: ") {
-				errMessage = line[7:]
+				errMessage = append(errMessage, line[7:])
+			} else {
+				errMessage = append(errMessage, line)
 			}
 			continue
 		}
@@ -310,8 +318,8 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
 
 	err = eg.Wait()
 	if err != nil {
-		if errMessage != "" {
-			return nil, errors.New(errMessage)
+		if len(errMessage) > 0 {
+			return nil, errors.New(strings.Join(errMessage, "\n"))
 		}
 		return nil, fmt.Errorf("failed to execute bake: %w", err)
 	}



Test patch:
diff --git a/pkg/e2e/build_test.go b/pkg/e2e/build_test.go
index 1b31f6fb..6061ee10 100644
--- a/pkg/e2e/build_test.go
+++ b/pkg/e2e/build_test.go
@@ -300,8 +300,8 @@ func TestBuildImageDependencies(t *testing.T) {
 			"DOCKER_BUILDKIT=1", "COMPOSE_BAKE=1",
 			"COMPOSE_FILE=./fixtures/build-dependencies/compose.yaml",
 		))
-		doTest(t, cli, "build")
-		doTest(t, cli, "build", "service")
+		doTest(t, cli, "--verbose", "build")
+		doTest(t, cli, "--verbose", "build", "service")
 	})
 }