From 18226d345b024a068ab305bd364d4a970c3ba697 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 4 Sep 2026 11:17:32 +0000 Subject: [PATCH] WIP: fix export/override prefix and ?= first-wins in make variable parser Two defects in the lint-once resolver's make variable handling: - The assignment pattern anchored at the start of the name, so `export FMT := script/fmt-check` was never collected and `@$(FMT)` went unresolved. An optional `export `/`override ` prefix is now allowed. - `?=` assigns only when the name is unset, so the first assignment wins. The parser called .set() unconditionally, letting a later `FMT ?= script/build` overwrite an earlier `FMT := script/fmt-check` and resolve to a command make never runs. Tests pinning both to follow. --- test/packaging/lint-once.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/packaging/lint-once.test.ts b/test/packaging/lint-once.test.ts index 9694a61..8137ccc 100644 --- a/test/packaging/lint-once.test.ts +++ b/test/packaging/lint-once.test.ts @@ -159,7 +159,7 @@ const MAKE_CONDITIONAL = /^\s*(?:ifeq|ifneq|ifdef|ifndef|else|endif)\b/; // not happen. An unresolved reference is left standing verbatim instead, which // is the same dead end as any other unfollowed edge rather than a wrong answer. const MAKE_ASSIGNMENT = new RegExp( - `^([A-Za-z_][A-Za-z0-9_]*)\\s*(?::=|\\?=|=)\\s*(.*)$`, + `^(?:(?:export|override)\\s+)*([A-Za-z_][A-Za-z0-9_]*)\\s*(:=|\\?=|=)\\s*(.*)$`, ); const MAKE_VARIABLE_REFERENCE = /\$[({]([A-Za-z_][A-Za-z0-9_]*)[)}]/g; @@ -181,8 +181,13 @@ const makeVariables = (text: string): Map => { if (inDefine || raw.startsWith("\t")) continue; const assignment = MAKE_ASSIGNMENT.exec(raw.replace(/#.*$/, "").trim()); if (assignment === null) continue; - const [, name, value] = assignment; - if (name === undefined || value === undefined) continue; + const [, name, operator, value] = assignment; + if (name === undefined || operator === undefined) continue; + if (value === undefined) continue; + // `?=` assigns only when the name has no value yet, so the first one + // wins where `:=` and `=` let the last one win. Overwriting here would + // resolve the reference to a string make never uses. + if (operator === "?=" && variables.has(name)) continue; // A value that is itself a reference is the recursive case, and is not // resolved. Recording it would hand the substitution below a string it // cannot finish expanding.