@@ -71,6 +71,8 @@ func newIncludeLoader(root *Config) (*includeLoader, error) {
7171
7272func (l * includeLoader ) load () (* Config , error ) {
7373 l .root .Namespaces = make (map [string ]string )
74+ l .root .NamespaceDirs = make (map [string ]string )
75+ l .root .NamespaceVars = make (map [string ]map [string ]Var )
7476
7577 // Process flatten files first (their tasks merge into the root namespace).
7678 // Order vs. includes doesn't matter for correctness because both honor
@@ -167,7 +169,7 @@ func (l *includeLoader) loadInclude(req includeRequest) error {
167169 }
168170 }
169171
170- l .mergeVars (included .Vars )
172+ l .mergeVars (included .Namespace , included . Dir , included . Vars )
171173 l .mergeSourcePresets (included .Sources )
172174 l .mergeSecrets (included .Secrets )
173175 return l .mergeTasks (included )
@@ -199,6 +201,7 @@ func (l *includeLoader) parseInclude(req includeRequest) (*includedConfig, error
199201 Parent : req ,
200202 }
201203 l .root .Namespaces [childDir ] = req .namespace
204+ l .root .NamespaceDirs [req .namespace ] = childDir
202205 return included , nil
203206}
204207
@@ -303,7 +306,7 @@ func (l *includeLoader) loadFlatten(req flattenRequest) error {
303306 }
304307 }
305308
306- l .mergeVars (flattened .Vars )
309+ l .mergeVars (req . namespace , req . ancestorDir , flattened .Vars )
307310 l .mergeSourcePresets (flattened .Sources )
308311 return l .mergeFlattenedTasks (flattened , req .namespace , req .ancestorDir )
309312}
@@ -315,17 +318,40 @@ func namespaceJoin(prefix, name string) string {
315318 return prefix + ":" + name
316319}
317320
318- // mergeVars merges child global vars into the root. Root vars win conflicts.
319- func (l * includeLoader ) mergeVars (vars map [string ]Var ) {
321+ // mergeVars merges vars declared by an included or flattened file into the
322+ // scope that owns them. Vars from the root or root-level flatten files
323+ // (namespace "") land in Config.Vars and are visible everywhere; vars from
324+ // an include land in Config.NamespaceVars[namespace] and are visible only
325+ // to tasks that live at or below that namespace — so two sibling includes
326+ // can each declare their own LDFLAGS/IMAGE_NAME without leaking values
327+ // into each other. "First defined wins" still holds within each scope,
328+ // matching the prior root-merge precedence.
329+ func (l * includeLoader ) mergeVars (namespace , dir string , vars map [string ]Var ) {
320330 if len (vars ) == 0 {
321331 return
322332 }
323- if l .root .Vars == nil {
324- l .root .Vars = make (map [string ]Var )
333+ if namespace == "" {
334+ if l .root .Vars == nil {
335+ l .root .Vars = make (map [string ]Var )
336+ }
337+ for k , v := range vars {
338+ if _ , exists := l .root .Vars [k ]; ! exists {
339+ l .root .Vars [k ] = v
340+ }
341+ }
342+ return
343+ }
344+ if _ , ok := l .root .NamespaceDirs [namespace ]; ! ok {
345+ l .root .NamespaceDirs [namespace ] = dir
346+ }
347+ scoped := l .root .NamespaceVars [namespace ]
348+ if scoped == nil {
349+ scoped = make (map [string ]Var )
350+ l .root .NamespaceVars [namespace ] = scoped
325351 }
326352 for k , v := range vars {
327- if _ , exists := l . root . Vars [k ]; ! exists {
328- l . root . Vars [k ] = v
353+ if _ , exists := scoped [k ]; ! exists {
354+ scoped [k ] = v
329355 }
330356 }
331357}
0 commit comments