diff --git a/cmd/compose/build.go b/cmd/compose/build.go index 4b0e59bfd81..ca9e0f25932 100644 --- a/cmd/compose/build.go +++ b/cmd/compose/build.go @@ -68,17 +68,16 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, uiMode = "rawjson" } return api.BuildOptions{ - Pull: opts.pull, - Push: opts.push, - Progress: uiMode, - Args: types.NewMappingWithEquals(opts.args), - NoCache: opts.noCache, - Quiet: opts.quiet, - Services: services, - Deps: opts.deps, - SSHs: SSHKeys, - Builder: builderName, - Compatibility: opts.Compatibility, + Pull: opts.pull, + Push: opts.push, + Progress: uiMode, + Args: types.NewMappingWithEquals(opts.args), + NoCache: opts.noCache, + Quiet: opts.quiet, + Services: services, + Deps: opts.deps, + SSHs: SSHKeys, + Builder: builderName, }, nil } diff --git a/go.mod b/go.mod index 654daf7be95..b288db103c6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/buger/goterm v1.0.4 - github.com/compose-spec/compose-go/v2 v2.4.7 + github.com/compose-spec/compose-go/v2 v2.4.8-0.20250130174723-77ab539e4f3f github.com/containerd/containerd/v2 v2.0.2 github.com/containerd/platforms v1.0.0-rc.1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc diff --git a/go.sum b/go.sum index b701cc843b0..977494a3264 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.4.7 h1:WNpz5bIbKG+G+w9pfu72B1ZXr+Og9jez8TMEo8ecXPk= -github.com/compose-spec/compose-go/v2 v2.4.7/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc= +github.com/compose-spec/compose-go/v2 v2.4.8-0.20250130174723-77ab539e4f3f h1:turCjSVHj+0P+G6kuRsJfhhYzp1ULfTv7GVzv1dgIHQ= +github.com/compose-spec/compose-go/v2 v2.4.8-0.20250130174723-77ab539e4f3f/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= diff --git a/pkg/api/api.go b/pkg/api/api.go index 2495665a004..e73bfcc9ef1 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -155,8 +155,6 @@ type BuildOptions struct { Memory int64 // Builder name passed in the command line Builder string - // Compatibility let compose run with best backward compatibility - Compatibility bool } // Apply mutates project according to build options diff --git a/pkg/compose/build.go b/pkg/compose/build.go index af38c05edc8..3e842abf795 100644 --- a/pkg/compose/build.go +++ b/pkg/compose/build.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "os" + "strings" "github.com/compose-spec/compose-go/v2/types" "github.com/containerd/platforms" @@ -71,7 +72,33 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti if options.Deps { policy = types.IncludeDependencies } - err := project.ForEachService(options.Services, func(serviceName string, service *types.ServiceConfig) error { + + serviceDeps := false + project, err := project.WithServicesTransform(func(serviceName string, service types.ServiceConfig) (types.ServiceConfig, error) { + if service.Build != nil { + for _, c := range service.Build.AdditionalContexts { + if t, found := strings.CutPrefix(c, types.ServicePrefix); found { + serviceDeps = true + if service.DependsOn == nil { + service.DependsOn = map[string]types.ServiceDependency{} + } + service.DependsOn[t] = types.ServiceDependency{ + Condition: "build", // non-canonical, but will force dependency graph ordering + } + } + } + } + return service, nil + }) + if err != nil { + return imageIDs, err + } + + if serviceDeps { + logrus.Infof(`additional_context with "service:"" is better supported when delegating build go bake. Set COMPOSE_BAKE=true`) + } + + err = project.ForEachService(options.Services, func(serviceName string, service *types.ServiceConfig) error { if service.Build == nil { return nil } @@ -536,6 +563,11 @@ func getImageBuildLabels(project *types.Project, service types.ServiceConfig) ty func toBuildContexts(additionalContexts types.Mapping) map[string]build.NamedContext { namedContexts := map[string]build.NamedContext{} for name, contextPath := range additionalContexts { + if _, found := strings.CutPrefix(contextPath, types.ServicePrefix); found { + // image we depend on has been build previously, as we run in dependency order. + // this assumes use of docker engine builder, so that build can access local images + continue + } namedContexts[name] = build.NamedContext{Path: contextPath} } return namedContexts diff --git a/pkg/compose/build_bake.go b/pkg/compose/build_bake.go index 2a4c2be5ddb..5c7fabc4e56 100644 --- a/pkg/compose/build_bake.go +++ b/pkg/compose/build_bake.go @@ -105,14 +105,17 @@ type bakeTarget struct { Tags []string `json:"tags,omitempty"` CacheFrom []string `json:"cache-from,omitempty"` CacheTo []string `json:"cache-to,omitempty"` + Target string `json:"target,omitempty"` Secrets []string `json:"secret,omitempty"` SSH []string `json:"ssh,omitempty"` Platforms []string `json:"platforms,omitempty"` - Target string `json:"target,omitempty"` Pull bool `json:"pull,omitempty"` NoCache bool `json:"no-cache,omitempty"` + NetworkMode string `json:"network,omitempty"` + NoCacheFilter []string `json:"no-cache-filter,omitempty"` ShmSize types.UnitBytes `json:"shm-size,omitempty"` Ulimits []string `json:"ulimits,omitempty"` + Call string `json:"call,omitempty"` Entitlements []string `json:"entitlements,omitempty"` Outputs []string `json:"output,omitempty"` } @@ -124,11 +127,6 @@ type buildStatus struct { } func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo - cw := progress.ContextWriter(ctx) - for name := range serviceToBeBuild { - cw.Event(progress.BuildingEvent(name)) - } - eg := errgroup.Group{} ch := make(chan *client.SolveStatus) display, err := progressui.NewDisplay(os.Stdout, progressui.DisplayMode(options.Progress)) @@ -191,7 +189,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project cfg.Targets[serviceName] = bakeTarget{ Context: build.Context, - Contexts: additionalContexts(build.AdditionalContexts, service.DependsOn, options.Compatibility), + Contexts: additionalContexts(build.AdditionalContexts), Dockerfile: dockerFilePath(build.Context, build.Dockerfile), DockerfileInline: build.DockerfileInline, Args: args, @@ -221,7 +219,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project return nil, err } - logrus.Debugf("bake config:\n%s", string(b)) + logrus.Debugf("bake build config:\n%s", string(b)) metadata, err := os.CreateTemp(os.TempDir(), "compose") if err != nil { @@ -320,6 +318,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project return nil, err } + cw := progress.ContextWriter(ctx) results := map[string]string{} for name, m := range md { results[name] = m.Digest @@ -328,14 +327,12 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project return results, nil } -func additionalContexts(contexts types.Mapping, dependencies types.DependsOnConfig, compatibility bool) map[string]string { +func additionalContexts(contexts types.Mapping) map[string]string { ac := map[string]string{} - if compatibility { - for name := range dependencies { - ac[name] = "target:" + name - } - } for k, v := range contexts { + if target, found := strings.CutPrefix(v, types.ServicePrefix); found { + v = "target:" + target + } ac[k] = v } return ac diff --git a/pkg/e2e/build_test.go b/pkg/e2e/build_test.go index 3459a86168f..9231ab1ec9a 100644 --- a/pkg/e2e/build_test.go +++ b/pkg/e2e/build_test.go @@ -271,18 +271,34 @@ func TestBuildImageDependencies(t *testing.T) { t.Run("ClassicBuilder", func(t *testing.T) { cli := NewCLI(t, WithEnv( "DOCKER_BUILDKIT=0", - "COMPOSE_FILE=./fixtures/build-dependencies/compose.yaml", + "COMPOSE_FILE=./fixtures/build-dependencies/classic.yaml", + )) + doTest(t, cli) + }) + + t.Run("BuildKit by dependency order", func(t *testing.T) { + cli := NewCLI(t, WithEnv( + "DOCKER_BUILDKIT=1", + "COMPOSE_FILE=./fixtures/build-dependencies/classic.yaml", )) doTest(t, cli) }) - t.Run("BuildKit", func(t *testing.T) { + t.Run("BuildKit by additional contexts", func(t *testing.T) { cli := NewCLI(t, WithEnv( "DOCKER_BUILDKIT=1", "COMPOSE_FILE=./fixtures/build-dependencies/compose.yaml", )) doTest(t, cli) }) + + t.Run("Bake by additional contexts", func(t *testing.T) { + cli := NewCLI(t, WithEnv( + "DOCKER_BUILDKIT=1", "COMPOSE_BAKE=1", + "COMPOSE_FILE=./fixtures/build-dependencies/compose.yaml", + )) + doTest(t, cli) + }) } func TestBuildPlatformsWithCorrectBuildxConfig(t *testing.T) { diff --git a/pkg/e2e/fixtures/build-dependencies/classic.yaml b/pkg/e2e/fixtures/build-dependencies/classic.yaml new file mode 100644 index 00000000000..b0dbbaad0a1 --- /dev/null +++ b/pkg/e2e/fixtures/build-dependencies/classic.yaml @@ -0,0 +1,14 @@ +services: + base: + image: base + init: true + build: + context: . + dockerfile: base.dockerfile + service: + init: true + depends_on: + - base + build: + context: . + dockerfile: service.dockerfile diff --git a/pkg/e2e/fixtures/build-dependencies/compose.yaml b/pkg/e2e/fixtures/build-dependencies/compose.yaml index b0dbbaad0a1..1c914b2b4e5 100644 --- a/pkg/e2e/fixtures/build-dependencies/compose.yaml +++ b/pkg/e2e/fixtures/build-dependencies/compose.yaml @@ -7,8 +7,8 @@ services: dockerfile: base.dockerfile service: init: true - depends_on: - - base build: context: . + additional_contexts: + base: "service:base" dockerfile: service.dockerfile