select services implicitly declared by a service:xx build dependency

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2025-04-28 08:59:07 +02:00 committed by Guillaume Lours
parent 9c998a934f
commit fc8c56b407
4 changed files with 41 additions and 31 deletions

View File

@ -27,7 +27,6 @@ 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"
@ -141,13 +140,11 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
}
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
if err != nil {
return err
}
services = addBuildDependencies(services, project)
if err := applyPlatforms(project, false); err != nil {
return err
}
@ -159,21 +156,3 @@ 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()
}

View File

@ -85,7 +85,16 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
policy = types.IncludeDependencies
}
err := project.ForEachService(options.Services, func(serviceName string, service *types.ServiceConfig) error {
if len(options.Services) > 0 {
// As user requested some services to be built, also include those used as additional_contexts
options.Services = addBuildDependencies(options.Services, project)
}
project, err := project.WithSelectedServices(options.Services)
if err != nil {
return nil, err
}
err = project.ForEachService(options.Services, func(serviceName string, service *types.ServiceConfig) error {
if service.Build == nil {
return nil
}
@ -613,3 +622,21 @@ func parsePlatforms(service types.ServiceConfig) ([]specs.Platform, error) {
return ret, nil
}
func addBuildDependencies(services []string, project *types.Project) []string {
servicesWithDependencies := utils.NewSet(services...)
for _, service := range services {
b := project.Services[service].Build
if b != nil {
for _, target := range b.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()
}

View File

@ -117,14 +117,14 @@ func TestLocalComposeBuild(t *testing.T) {
})
t.Run(env+" rebuild when up --build", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--workdir", "fixtures/build-test", "up", "-d", "--build")
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d", "--build")
res.Assert(t, icmd.Expected{Out: "COPY static /usr/share/nginx/html"})
res.Assert(t, icmd.Expected{Out: "COPY static2 /usr/share/nginx/html"})
})
t.Run(env+" build --push ignored for unnamed images", func(t *testing.T) {
res := c.RunDockerComposeCmd(t, "--workdir", "fixtures/build-test", "build", "--push", "nginx")
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "build", "--push", "nginx")
assert.Assert(t, !strings.Contains(res.Stdout(), "failed to push"), res.Stdout())
})
@ -232,7 +232,7 @@ func TestBuildTags(t *testing.T) {
}
func TestBuildImageDependencies(t *testing.T) {
doTest := func(t *testing.T, cli *CLI) {
doTest := func(t *testing.T, cli *CLI, args ...string) {
resetState := func() {
cli.RunDockerComposeCmd(t, "down", "--rmi=all", "-t=0")
res := cli.RunDockerOrExitError(t, "image", "rm", "build-dependencies-service")
@ -250,7 +250,7 @@ func TestBuildImageDependencies(t *testing.T) {
Err: "No such image: build-dependencies-service",
})
res = cli.RunDockerComposeCmd(t, "build")
res = cli.RunDockerComposeCmd(t, args...)
t.Log(res.Combined())
res = cli.RunDockerCmd(t,
@ -273,7 +273,8 @@ func TestBuildImageDependencies(t *testing.T) {
"DOCKER_BUILDKIT=0",
"COMPOSE_FILE=./fixtures/build-dependencies/classic.yaml",
))
doTest(t, cli)
doTest(t, cli, "build")
doTest(t, cli, "build", "--with-dependencies", "service")
})
t.Run("BuildKit by dependency order", func(t *testing.T) {
@ -281,7 +282,8 @@ func TestBuildImageDependencies(t *testing.T) {
"DOCKER_BUILDKIT=1",
"COMPOSE_FILE=./fixtures/build-dependencies/classic.yaml",
))
doTest(t, cli)
doTest(t, cli, "build")
doTest(t, cli, "build", "--with-dependencies", "service")
})
t.Run("BuildKit by additional contexts", func(t *testing.T) {
@ -289,7 +291,8 @@ func TestBuildImageDependencies(t *testing.T) {
"DOCKER_BUILDKIT=1",
"COMPOSE_FILE=./fixtures/build-dependencies/compose.yaml",
))
doTest(t, cli)
doTest(t, cli, "build")
doTest(t, cli, "build", "service")
})
t.Run("Bake by additional contexts", func(t *testing.T) {
@ -297,7 +300,8 @@ func TestBuildImageDependencies(t *testing.T) {
"DOCKER_BUILDKIT=1", "COMPOSE_BAKE=1",
"COMPOSE_FILE=./fixtures/build-dependencies/compose.yaml",
))
doTest(t, cli)
doTest(t, cli, "build")
doTest(t, cli, "build", "service")
})
}