Repository: docker/compose

Issue:
[BUG] `docker compose build <service_name>` fails with "invalid reference format" when using `additional_contexts`
### Description

When attempting to build a specific service using `docker compose build <service_name>`, and that service is defined using `additional_contexts` to stage another service image, the build process fails with either an "invalid reference format" error or, when using COMPOSE_BAKE=true, a "failed to find target" error.

### Steps To Reproduce

Create a docker-compose.yaml file with the following content :
```yaml
services:
  base:
    build:
      context: .
      dockerfile_inline: |
        FROM alpine
  my-service:
    build:
      context: .
      dockerfile_inline: |
        FROM base
    additional_contexts:
      base: service:base
```

Run `docker compose build` to confirm both services build successfully.
```
$ docker compose build 

[+] Building 2/2
 ✔ base        Built
 ✔ my-service  Built 
```

Attempt to build only the dependent service:
```
$ docker compose build my-service

failed to solve: invalid reference format
```
Alternatively, try building with `COMPOSE_BAKE=true`:
```
$ COMPOSE_BAKE=true docker compose build my-service

failed to find target base
```

For reference, building the base service alone is done without issues:
```
$ docker compose build base

[+] Building 1/1
 ✔ base  Built
```


### Compose Version

```Text
Docker Compose version v2.34.0
```

### Docker Environment

```Text
Client: Docker Engine - Community
 Version:    28.0.4
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.22.0
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.34.0
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 5
  Running: 0
  Paused: 0
  Stopped: 5
 Images: 63
 Server Version: 28.0.4
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 nvidia runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 753481ec61c7c8955a23d6ff7bc8e4daed455734
 runc version: v1.2.5-0-g59923ef
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: builtin
  userns
  cgroupns
 Kernel Version: 6.11.0-19-generic
 Operating System: Ubuntu 24.04.2 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 24
 Total Memory: 62.4GiB
 Name: valhalla
 ID: 17c74018-ea57-48af-8c04-23a98ae5ab7e
 Docker Root Dir: /var/lib/docker/231072.231072
 Debug Mode: false
 Experimental: false
 Insecure Registries:
  ::1/128
  127.0.0.0/8
 Live Restore Enabled: false
```

### Anything else?

_No response_

Discussion:
@ndeloof: This indeed implies `base` MUST also be built, I'll look into this

Solution patch:
diff --git a/cmd/compose/build.go b/cmd/compose/build.go
index 95144921..bd3f10b0 100644
--- a/cmd/compose/build.go
+++ b/cmd/compose/build.go
@@ -27,6 +27,7 @@ import (
 	"github.com/docker/cli/cli/command"
 	cliopts "github.com/docker/cli/opts"
 	ui "github.com/docker/compose/v2/pkg/progress"
+	"github.com/docker/compose/v2/pkg/utils"
 	buildkit "github.com/moby/buildkit/util/progress/progressui"
 	"github.com/spf13/cobra"
 
@@ -145,6 +146,8 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
 		return err
 	}
 
+	services = addBuildDependencies(services, project)
+
 	if err := applyPlatforms(project, false); err != nil {
 		return err
 	}
@@ -156,3 +159,21 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
 
 	return backend.Build(ctx, project, apiBuildOptions)
 }
+
+func addBuildDependencies(services []string, project *types.Project) []string {
+	servicesWithDependencies := utils.NewSet(services...)
+	for _, service := range services {
+		build := project.Services[service].Build
+		if build != nil {
+			for _, target := range build.AdditionalContexts {
+				if s, found := strings.CutPrefix(target, types.ServicePrefix); found {
+					servicesWithDependencies.Add(s)
+				}
+			}
+		}
+	}
+	if len(servicesWithDependencies) > len(services) {
+		return addBuildDependencies(servicesWithDependencies.Elements(), project)
+	}
+	return servicesWithDependencies.Elements()
+}



Test patch:
diff --git a/cmd/compose/build_test.go b/cmd/compose/build_test.go
new file mode 100644
index 00000000..fa0a9e2c
--- /dev/null
+++ b/cmd/compose/build_test.go
@@ -0,0 +1,57 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package compose
+
+import (
+	"slices"
+	"testing"
+
+	"github.com/compose-spec/compose-go/v2/types"
+	"gotest.tools/v3/assert"
+)
+
+func Test_addBuildDependencies(t *testing.T) {
+	project := &types.Project{Services: types.Services{
+		"test": types.ServiceConfig{
+			Build: &types.BuildConfig{
+				AdditionalContexts: map[string]string{
+					"foo": "service:foo",
+					"bar": "service:bar",
+				},
+			},
+		},
+		"foo": types.ServiceConfig{
+			Build: &types.BuildConfig{
+				AdditionalContexts: map[string]string{
+					"zot": "service:zot",
+				},
+			},
+		},
+		"bar": types.ServiceConfig{
+			Build: &types.BuildConfig{},
+		},
+		"zot": types.ServiceConfig{
+			Build: &types.BuildConfig{},
+		},
+	}}
+
+	services := addBuildDependencies([]string{"test"}, project)
+	expected := []string{"test", "foo", "bar", "zot"}
+	slices.Sort(services)
+	slices.Sort(expected)
+	assert.DeepEqual(t, services, expected)
+}