From aac040df56d7a5d5ba6b7e13c2aca952ee5f8028 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:01:07 +0200 Subject: [PATCH] bake: fix file-relative target references Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- bake/bake.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++- bake/bake_test.go | 24 +++++++++++++++++++++ tests/bake.go | 42 ++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/bake/bake.go b/bake/bake.go index 2ccb66e95..9a661c3b6 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -437,7 +437,22 @@ func fileRelativePaths(opts []ParseOpt) bool { } func rebaseContextPaths(c *Config) { + targets := make(map[string]*Target, len(c.Targets)) for _, t := range c.Targets { + targets[t.Name] = t + } + + for _, t := range c.Targets { + if ref := targets[t.contextBaseRef]; ref != nil { + switch { + case ref.hasContextBase: + t.contextBase = ref.contextBase + t.hasContextBase = true + case ref.hasDefaultContextBase: + t.contextBase = ref.defaultContextBase + t.hasContextBase = true + } + } t.rebaseContextPaths() } } @@ -854,6 +869,7 @@ type Target struct { useDefaultContextBase bool contextBase string hasContextBase bool + contextBaseRef string contextsBase map[string]string } @@ -922,9 +938,10 @@ func (t *Target) SetBlockSource(block *hcl.Block) { if diags.HasErrors() { return } - if _, ok := content.Attributes["context"]; ok { + if attr, ok := content.Attributes["context"]; ok { t.contextBase = base t.hasContextBase = true + t.contextBaseRef = targetContextRef(attr.Expr) } if _, ok := content.Attributes["contexts"]; ok { t.setContextsBase(base) @@ -943,6 +960,41 @@ func (t *Target) setContextsBase(base string) { } } +func targetContextRef(expr hcl.Expression) string { + traversal, diags := hcl.AbsTraversalForExpr(expr) + if diags.HasErrors() || len(traversal) != 3 { + return "" + } + root, ok := traversal[0].(hcl.TraverseRoot) + if !ok || root.Name != "target" { + return "" + } + target, ok := traversalStepName(traversal[1]) + if !ok { + return "" + } + field, ok := traversal[2].(hcl.TraverseAttr) + if !ok || field.Name != "context" { + return "" + } + return target +} + +func traversalStepName(step hcl.Traverser) (string, bool) { + switch step := step.(type) { + case hcl.TraverseAttr: + return step.Name, true + case hcl.TraverseIndex: + key, err := convert.Convert(step.Key, cty.String) + if err != nil || key.IsNull() || !key.IsKnown() { + return "", false + } + return key.AsString(), true + default: + return "", false + } +} + func (t *Target) normalize() { t.Annotations = removeDupesStr(t.Annotations) t.Attest = t.Attest.Normalize() @@ -982,6 +1034,7 @@ func (t *Target) Merge(t2 *Target) { t.Context = t2.Context t.contextBase = t2.contextBase t.hasContextBase = t2.hasContextBase + t.contextBaseRef = t2.contextBaseRef } if t2.Dockerfile != nil { t.Dockerfile = t2.Dockerfile diff --git a/bake/bake_test.go b/bake/bake_test.go index 49cd0e242..627198725 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -918,6 +918,30 @@ target "app" { }) } +func TestTargetReferenceContextRebase(t *testing.T) { + fp1 := File{ + Name: filepath.Join("one", "docker-bake.hcl"), + Data: []byte(` +target "base" { + context = "basectx" +}`), + } + fp2 := File{ + Name: filepath.Join("two", "docker-bake.hcl"), + Data: []byte(` +target "app" { + context = target.base.context +}`), + } + + m, _, err := ReadTargets(context.TODO(), []File{fp1, fp2}, []string{"app"}, nil, nil, nil, &EntitlementConf{}, ParseOpt{ + FileRelativePaths: true, + }) + require.NoError(t, err) + + require.Equal(t, filepath.ToSlash(filepath.Clean("one/basectx")), *m["app"].Context) +} + func TestOverridesNotRebased(t *testing.T) { fp := File{ Name: filepath.Join("subdir", "docker-bake.hcl"), diff --git a/tests/bake.go b/tests/bake.go index 032d962c1..0b1d44330 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -786,6 +786,48 @@ EOT require.FileExists(t, filepath.Join(dirDest, "marker")) }) + t.Run("hcl target reference", func(t *testing.T) { + baseBakefile := []byte(` +target "base" { + context = "basectx" +} +`) + appBakefile := []byte(` +target "app" { + context = target.base.context + dockerfile-inline = <