diff --git a/bake/bake.go b/bake/bake.go index f9c1976fa..e50fda4dc 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -376,10 +376,13 @@ func ParseFiles(files []File, defaults, vars map[string]string, opts ...ParseOpt } if len(composeFiles) > 0 { - cfg, cmperr := parseComposeFilesWithBase(composeFiles, vars, frel) + cfg, cmperr := ParseComposeFiles(composeFiles, vars) if cmperr != nil { return nil, nil, errors.Wrap(cmperr, "failed to parse compose file") } + if frel { + setComposeContextBase(cfg, composeFiles) + } c = mergeConfig(c, *cfg) c = dedupeConfig(c) } diff --git a/bake/compose.go b/bake/compose.go index 0a604849e..c6eabcb31 100644 --- a/bake/compose.go +++ b/bake/compose.go @@ -22,38 +22,19 @@ import ( ) func ParseComposeFiles(fs []File, envOverrides map[string]string, opts ...ParseOpt) (*Config, error) { - frel := fileRelativePaths(opts) - cfg, err := parseComposeFilesWithBase(fs, envOverrides, frel) - if err != nil { - return nil, err - } - if frel { - rebaseContextPaths(cfg) - } - return cfg, nil -} - -func parseComposeFilesWithBase(fs []File, envOverrides map[string]string, withBase bool) (*Config, error) { envs, err := composeEnv(envOverrides) if err != nil { return nil, err } - - if withBase && len(fs) > 0 { - var c Config - for _, f := range fs { - cfg, err := parseComposeFiles([]File{f}, envs) - if err != nil { - return nil, err - } - setComposeContextBase(cfg, f.Name) - c = mergeConfig(c, *cfg) - c = dedupeConfig(c) - } - return &c, nil + cfg, err := parseComposeFiles(fs, envs) + if err != nil { + return nil, err } - - return parseComposeFiles(fs, envs) + if fileRelativePaths(opts) { + setComposeContextBase(cfg, fs) + rebaseContextPaths(cfg) + } + return cfg, nil } func parseComposeFiles(fs []File, envs map[string]string) (*Config, error) { @@ -67,8 +48,14 @@ func parseComposeFiles(fs []File, envs map[string]string) (*Config, error) { return ParseCompose(cfgs, envs) } -func setComposeContextBase(c *Config, name string) { - base, _ := localFileDir(name) +func setComposeContextBase(c *Config, files []File) { + if len(files) == 0 { + return + } + base, ok := localFileDir(files[0].Name) + if !ok { + return + } for _, t := range c.Targets { t.defaultContextBase = base t.hasDefaultContextBase = true diff --git a/bake/compose_test.go b/bake/compose_test.go index da8ba2cfa..3782e2eea 100644 --- a/bake/compose_test.go +++ b/bake/compose_test.go @@ -174,6 +174,45 @@ services: require.Equal(t, "webapp", *c.Targets[1].Target) } +func TestComposeProjectBase(t *testing.T) { + fp := File{ + Name: filepath.Join("project", "compose.yml"), + Data: []byte(` +services: + app: + build: + context: ./app +`), + } + fp2 := File{ + Name: filepath.Join("overrides", "compose.yml"), + Data: []byte(` +services: + app: + build: + additional_contexts: + shared: ./shared + other: + build: + context: ./other +`), + } + + c, err := ParseComposeFiles([]File{fp, fp2}, nil, ParseOpt{ + FileRelativePaths: true, + }) + require.NoError(t, err) + + targets := map[string]*Target{} + for _, t := range c.Targets { + targets[t.Name] = t + } + + require.Equal(t, filepath.ToSlash(filepath.Clean("project/app")), *targets["app"].Context) + require.Equal(t, filepath.ToSlash(filepath.Clean("project/shared")), targets["app"].Contexts["shared"]) + require.Equal(t, filepath.ToSlash(filepath.Clean("project/other")), *targets["other"].Context) +} + func TestBuildArgEnvCompose(t *testing.T) { dt := []byte(` version: "3.8" diff --git a/docs/bake-reference.md b/docs/bake-reference.md index 07594d2a7..800e0abbc 100644 --- a/docs/bake-reference.md +++ b/docs/bake-reference.md @@ -417,9 +417,10 @@ target "app" { This resolves to the current working directory (`"."`) by default. Set `BUILDX_BAKE_FILE_RELATIVE_PATHS=1` to resolve local directory paths in -`target.context` and `target.contexts` relative to the Bake or Compose file -that defines each path. Use `cwd://` for paths that should remain relative to -the current working directory when this opt-in is enabled. +`target.context` and `target.contexts` relative to the Bake file that defines +each path. Compose files use the first Compose file directory as the base, which +matches Compose project directory semantics. Use `cwd://` for paths that should +remain relative to the current working directory when this opt-in is enabled. ```console $ docker buildx bake --print -f - <<< 'target "default" {}' diff --git a/docs/reference/buildx_bake.md b/docs/reference/buildx_bake.md index 5edb3fdb9..683a06c6f 100644 --- a/docs/reference/buildx_bake.md +++ b/docs/reference/buildx_bake.md @@ -155,9 +155,11 @@ Multiple definitions can be specified by separating them with the system's path By default, local directory build contexts in Bake files are resolved from the current working directory. To opt in to resolving local directory build contexts -from the Bake or Compose file that defines each path, set -`BUILDX_BAKE_FILE_RELATIVE_PATHS=1`. Use the `cwd://` prefix for paths that -should remain relative to the current working directory. +from the Bake file that defines each path, set +`BUILDX_BAKE_FILE_RELATIVE_PATHS=1`. Compose files use the first Compose file +directory as the base, which matches Compose project directory semantics. Use +the `cwd://` prefix for paths that should remain relative to the current working +directory. You can pass the names of the targets to build, to build only specific target(s). The following example builds the `db` and `webapp-release` targets that are diff --git a/tests/bake.go b/tests/bake.go index 90faaac6a..032d962c1 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -715,6 +715,49 @@ services: require.FileExists(t, filepath.Join(dirDest, "shared-marker")) }) + t.Run("compose project", func(t *testing.T) { + composefile := []byte(` +services: + app: + build: + context: ./app + dockerfile_inline: | + FROM scratch + COPY marker /marker + COPY --from=shared shared-marker /shared-marker +`) + overridefile := []byte(` +services: + app: + build: + additional_contexts: + shared: ./shared +`) + + dir := tmpdir( + t, + fstest.CreateDir("project", 0700), + fstest.CreateDir("project/app", 0700), + fstest.CreateDir("project/shared", 0700), + fstest.CreateDir("overrides", 0700), + fstest.CreateFile("project/compose.yml", composefile, 0600), + fstest.CreateFile("project/app/marker", []byte("marker"), 0600), + fstest.CreateFile("project/shared/shared-marker", []byte("shared"), 0600), + fstest.CreateFile("overrides/compose.yml", overridefile, 0600), + ) + dirDest := t.TempDir() + + out, err := bakeCmd( + sb, + withDir(dir), + withArgs("--file", "project/compose.yml", "--file", "overrides/compose.yml", "--set", "app.output=type=local,dest="+dirDest), + withEnv("BUILDX_BAKE_FILE_RELATIVE_PATHS=1"), + ) + require.NoError(t, err, out) + require.FileExists(t, filepath.Join(dirDest, "marker")) + require.FileExists(t, filepath.Join(dirDest, "shared-marker")) + }) + t.Run("default context", func(t *testing.T) { bakefile := []byte(` target "default" {