0.2 Release push
This commit is contained in:
@@ -17,4 +17,5 @@ struct Target
|
||||
string[] prerequisites; /// Normal prerequisites (trigger rebuild)
|
||||
string[] recipe; /// Shell commands to build this target
|
||||
string[] orderOnlyPrereqs; /// Order-only prerequisites (| — must exist, no rebuild trigger)
|
||||
string stem; /// Pattern/suffix rule stem for $* expansion
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ module antelope.cli.args;
|
||||
|
||||
import std.algorithm.searching : startsWith;
|
||||
import std.conv : to;
|
||||
import std.string : strip;
|
||||
import std.string : strip, indexOf;
|
||||
import std.ascii : isDigit;
|
||||
|
||||
/// Available subcommands.
|
||||
@@ -54,6 +54,10 @@ struct CliConfig
|
||||
|
||||
/// Show version (--version).
|
||||
bool showVersion;
|
||||
|
||||
/// Variable overrides from the command line (VAR=value style).
|
||||
/// GNU Make passes these with highest precedence.
|
||||
string[string] varOverrides;
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
@@ -229,6 +233,16 @@ CliConfig parseArgs(string[] args)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Variable override: VAR=value (GNU Make command-line var)
|
||||
auto eqPos = indexOf(arg, '=');
|
||||
if (eqPos > 0)
|
||||
{
|
||||
string varName = arg[0 .. eqPos];
|
||||
string varValue = arg[eqPos + 1 .. $];
|
||||
config.varOverrides[varName] = varValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Everything else is a target
|
||||
config.targets ~= arg;
|
||||
}
|
||||
|
||||
@@ -117,6 +117,35 @@ int runBuild(CliConfig config)
|
||||
env.set("MAKECMDGOALS", config.targets.join(" "));
|
||||
}
|
||||
|
||||
// Parse MAKEFLAGS for variable overrides propagated from parent
|
||||
// antelope processes via recursive $(MAKE). These are serialized
|
||||
// in MAKEFLAGS as "VAR=value" tokens.
|
||||
if (env.hasKey("MAKEFLAGS"))
|
||||
{
|
||||
import std.string : split, indexOf;
|
||||
import std.algorithm : startsWith;
|
||||
auto mkFlags = env.get("MAKEFLAGS");
|
||||
foreach (word; mkFlags.split(" "))
|
||||
{
|
||||
auto eqPos = indexOf(word, '=');
|
||||
if (eqPos > 0 && !word.startsWith("-"))
|
||||
{
|
||||
string varName = word[0 .. eqPos];
|
||||
string varValue = word[eqPos + 1 .. $];
|
||||
config.varOverrides[varName] = varValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply command-line variable overrides (VAR=value args) BEFORE
|
||||
// Makefile evaluation so they take precedence over Makefile
|
||||
// assignments. GNU Make semantics: command-line vars beat
|
||||
// everything except `override` directives.
|
||||
foreach (varName, varValue; config.varOverrides)
|
||||
{
|
||||
env.setCmdOverride(varName, varValue);
|
||||
}
|
||||
|
||||
// Evaluate AST → populate env + build graph
|
||||
auto graph = new DependencyGraph();
|
||||
|
||||
@@ -229,9 +258,26 @@ int runBuild(CliConfig config)
|
||||
{
|
||||
if (!graph.hasTarget(targetName))
|
||||
{
|
||||
log(LogLevel.normal, "antelope: *** No rule to make target '" ~
|
||||
targetName ~ "'. Stop.");
|
||||
return 1;
|
||||
// Target not explicitly defined — try to create it from
|
||||
// implicit rules (suffix rules, pattern rules). Autotools
|
||||
// Makefiles invoke $(MAKE) with targets like "be.gmo" that
|
||||
// are only defined via suffix rules (e.g., .po.gmo:).
|
||||
import antelope.build.target;
|
||||
Target stub;
|
||||
stub.name = targetName;
|
||||
stub.kind = TargetKind.file;
|
||||
graph.addTarget(stub);
|
||||
|
||||
import antelope.evaluator.evaluator : resolveImplicitRules;
|
||||
resolveImplicitRules(*graph, env);
|
||||
|
||||
if (!graph.hasTarget(targetName) ||
|
||||
graph.findTarget(targetName).recipe.length == 0)
|
||||
{
|
||||
log(LogLevel.normal, "antelope: *** No rule to make target '" ~
|
||||
targetName ~ "'. Stop.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve dependencies and check what needs building
|
||||
@@ -263,7 +309,7 @@ int runBuild(CliConfig config)
|
||||
{
|
||||
// Expand variables in the recipe
|
||||
string expanded = expand(recipeLine, env, t.name,
|
||||
t.prerequisites);
|
||||
t.prerequisites, t.stem);
|
||||
|
||||
// Print the command unless silent (@ prefix)
|
||||
import std.string : stripLeft;
|
||||
|
||||
@@ -40,11 +40,17 @@ struct SubMakeConfig
|
||||
/// -n — dry run
|
||||
/// -P — POSIX conformance mode
|
||||
/// -d — debug output
|
||||
/// VAR=val — command-line variable overrides
|
||||
///
|
||||
/// Returns: a space-delimited MAKEFLAGS string, or "" if no flags are active.
|
||||
string serializeMakeFlags(CliConfig config)
|
||||
{
|
||||
string flags;
|
||||
|
||||
// Variable overrides first (parsed back by parseArgs in sub-make)
|
||||
foreach (varName, varValue; config.varOverrides)
|
||||
flags ~= " " ~ varName ~ "=" ~ varValue;
|
||||
|
||||
if (config.jobs > 1)
|
||||
flags ~= " -j" ~ config.jobs.to!string;
|
||||
if (config.dryRun)
|
||||
|
||||
@@ -129,6 +129,7 @@ size_t resolveImplicitRules(ref DependencyGraph graph, Environment* env = null)
|
||||
if (prereqSatisfiable(match.resolvedPrereq, graph))
|
||||
{
|
||||
tp.recipe = match.rule.recipe;
|
||||
tp.stem = match.stem; // for $* expansion in recipes
|
||||
if (match.resolvedPrereq.length > 0)
|
||||
{
|
||||
bool alreadyPresent;
|
||||
@@ -171,9 +172,100 @@ size_t resolveImplicitRules(ref DependencyGraph graph, Environment* env = null)
|
||||
if (userMatches.length > 0)
|
||||
{
|
||||
auto um = userMatches[$ - 1];
|
||||
resolveUserPatternRule(tp, um, graph, env);
|
||||
resolved++;
|
||||
applied = true;
|
||||
// Skip pattern rules with no recipe — GNU Make treats these
|
||||
// as "cancellation" rules that remove implicit rules rather
|
||||
// than providing new ones. Applying an empty recipe would
|
||||
// leave the target unresolved (recipe.length==0), causing
|
||||
// infinite re-resolution every pass.
|
||||
if (um.rule.recipe.length > 0)
|
||||
{
|
||||
resolveUserPatternRule(tp, um, graph, env);
|
||||
resolved++;
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If still no match, try suffix rules (e.g., .c.o:).
|
||||
// GNU Make suffix rules provide recipes for targets based on
|
||||
// the file extensions of the target and its prerequisites.
|
||||
if (!applied)
|
||||
{
|
||||
import std.string : lastIndexOf;
|
||||
import std.file : exists;
|
||||
auto tDot = lastIndexOf(tp.name, '.');
|
||||
if (tDot >= 0)
|
||||
{
|
||||
string tSuffix = tp.name[tDot .. $]; // e.g. ".o"
|
||||
|
||||
// First pass: match against existing prerequisites.
|
||||
if (tp.prerequisites.length > 0)
|
||||
{
|
||||
foreach (prereq; tp.prerequisites)
|
||||
{
|
||||
auto pDot = lastIndexOf(prereq, '.');
|
||||
if (pDot >= 0)
|
||||
{
|
||||
string pSuffix = prereq[pDot .. $];
|
||||
string ruleName = pSuffix ~ tSuffix;
|
||||
auto sr = graph.findTarget(ruleName);
|
||||
// Only apply if the source prerequisite actually
|
||||
// exists on disk (matching GNU Make semantics).
|
||||
if (sr !is null && sr.recipe.length > 0 && exists(prereq))
|
||||
{
|
||||
tp.recipe = sr.recipe;
|
||||
// Stem for $*: everything before the target suffix
|
||||
tp.stem = tp.name[0 .. tDot];
|
||||
resolved++;
|
||||
applied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: no existing prereqs — try to discover
|
||||
// a source file by matching suffix rules. For target
|
||||
// "be.gmo", find suffix rules ending in ".gmo" (e.g.,
|
||||
// ".po.gmo"), construct the source name "be.po", and
|
||||
// check if it exists on disk.
|
||||
if (!applied)
|
||||
{
|
||||
string stem = tp.name[0 .. tDot]; // e.g. "be"
|
||||
// Scan all graph targets for suffix rules matching tSuffix
|
||||
foreach (ref gt; graph.targets)
|
||||
{
|
||||
if (gt.name.length > tSuffix.length &&
|
||||
gt.name[$ - tSuffix.length .. $] == tSuffix &&
|
||||
gt.name[0] == '.')
|
||||
{
|
||||
// gt.name is e.g. ".po.gmo" — source suffix is
|
||||
// everything before tSuffix: ".po"
|
||||
string srcSuffix = gt.name[0 .. $ - tSuffix.length];
|
||||
if (srcSuffix.length == 0 || srcSuffix[0] != '.')
|
||||
continue;
|
||||
string srcFile = stem ~ srcSuffix; // e.g. "be.po"
|
||||
if (exists(srcFile) && gt.recipe.length > 0)
|
||||
{
|
||||
tp.recipe = gt.recipe;
|
||||
tp.stem = stem; // for $* expansion
|
||||
// Add source as a prerequisite
|
||||
bool alreadyPresent;
|
||||
foreach (p; tp.prerequisites)
|
||||
if (p == srcFile) { alreadyPresent = true; break; }
|
||||
if (!alreadyPresent)
|
||||
{
|
||||
string[] np = [srcFile];
|
||||
np ~= tp.prerequisites;
|
||||
tp.prerequisites = np;
|
||||
}
|
||||
resolved++;
|
||||
applied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +285,7 @@ private void resolveUserPatternRule(Target* tp, PatternMatch match,
|
||||
ref DependencyGraph graph, Environment* env)
|
||||
{
|
||||
tp.recipe = match.rule.recipe;
|
||||
tp.stem = match.stem; // for $* expansion in recipes
|
||||
// Clear existing prereqs and set from pattern match
|
||||
tp.prerequisites = [];
|
||||
foreach (prereq; match.resolvedPrereqs)
|
||||
@@ -337,7 +430,11 @@ private void handleRule(AstNode node, Environment* env, DependencyGraph* graph)
|
||||
foreach (ref p; prereqs)
|
||||
{
|
||||
string expanded = expand(p, env);
|
||||
foreach (word; expanded.split(" "))
|
||||
// Split on ALL whitespace (not just spaces). Autotools
|
||||
// Makefiles use tab-indented line continuations inside
|
||||
// variable values, so a naive space-split leaves leading
|
||||
// tabs that prevent prerequisite names from matching.
|
||||
foreach (word; expanded.split())
|
||||
{
|
||||
if (word.length > 0)
|
||||
expandedPrereqs ~= word;
|
||||
@@ -387,12 +484,35 @@ private void handleRule(AstNode node, Environment* env, DependencyGraph* graph)
|
||||
t.orderOnlyPrereqs = prereqSplit.orderOnly;
|
||||
t.recipe = recipe;
|
||||
|
||||
// Materialize missing prerequisites as stub Targets in the graph.
|
||||
// Without this, prerequisites that have no explicit rule (e.g., foo.o
|
||||
// when only "all: foo.o" appears) never become graph targets, so
|
||||
// resolveImplicitRules() can't give them recipes and the dependency
|
||||
// resolver treats them as external/always-satisfied.
|
||||
// Skip pattern prereqs containing '%' — they're templates, not real files.
|
||||
if (graph)
|
||||
{
|
||||
import std.file : exists;
|
||||
import std.string : indexOf;
|
||||
foreach (prereq; prereqSplit.normal ~ prereqSplit.orderOnly)
|
||||
{
|
||||
if (indexOf(prereq, '%') >= 0) continue; // pattern prereq, not a real file
|
||||
if (!graph.hasTarget(prereq) && !exists(prereq))
|
||||
{
|
||||
Target stub;
|
||||
stub.name = prereq;
|
||||
stub.kind = TargetKind.file;
|
||||
graph.addTarget(stub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle multi-target rules: when target expands to multiple words,
|
||||
// create separate targets for each (same prereqs + recipe).
|
||||
import std.string;
|
||||
import std.algorithm : filter;
|
||||
import std.array : array;
|
||||
auto expandedTargets = targetName.split(" ").filter!(s => s.length > 0).array;
|
||||
auto expandedTargets = targetName.split().filter!(s => s.length > 0).array;
|
||||
if (expandedTargets.length > 1)
|
||||
{
|
||||
foreach (tn; expandedTargets)
|
||||
@@ -561,7 +681,7 @@ private void handleDirective(AstNode node, Environment* env, DependencyGraph* gr
|
||||
import std.string : split;
|
||||
VPathEntry entry;
|
||||
entry.pattern = pattern;
|
||||
foreach (dir; dirs.split(" "))
|
||||
foreach (dir; dirs.split())
|
||||
if (dir.length > 0) entry.directories ~= dir;
|
||||
// Store in env for later use by the resolver
|
||||
if (env !is null)
|
||||
@@ -667,14 +787,21 @@ private void handleInclude(string dirName, string data, ptrdiff_t space,
|
||||
{
|
||||
if (space < 0) return;
|
||||
|
||||
string rawPath = data[space + 1 .. $].strip;
|
||||
string rawPath = data[space + 1 .. $];
|
||||
// Strip inline comment (# to end of logical line).
|
||||
// Autotools-generated Makefiles append " # am--include-marker"
|
||||
// as a comment after include directives.
|
||||
auto hashPos = indexOf(rawPath, '#');
|
||||
if (hashPos >= 0)
|
||||
rawPath = rawPath[0 .. hashPos];
|
||||
rawPath = rawPath.strip;
|
||||
if (rawPath.length == 0) return;
|
||||
|
||||
// Expand variable references in the include path (e.g., $(DEP_FILES)
|
||||
// commonly expands to "./.deps/a.Po ./.deps/b.Po" in autotools projects).
|
||||
import antelope.evaluator.expansion;
|
||||
import std.string : split;
|
||||
auto paths = expand(rawPath, env).split(" ");
|
||||
auto paths = expand(rawPath, env).split();
|
||||
|
||||
foreach (path; paths)
|
||||
{
|
||||
@@ -812,20 +939,23 @@ unittest
|
||||
|
||||
evaluate(root, &env, &graph);
|
||||
|
||||
assert(graph.targets.length == 1,
|
||||
"graph should contain exactly 1 target");
|
||||
assert(graph.targets[0].name == "program",
|
||||
"target name should be 'program', got: " ~ graph.targets[0].name);
|
||||
assert(graph.targets[0].prerequisites.length == 2,
|
||||
// With prerequisite materialization, main.o + util.o become stub targets
|
||||
assert(graph.targets.length == 3,
|
||||
"graph should contain program + 2 prereq stubs");
|
||||
auto prog = graph.findTarget("program");
|
||||
assert(prog !is null, "program should be in the graph");
|
||||
assert(prog.name == "program",
|
||||
"target name should be 'program', got: " ~ prog.name);
|
||||
assert(prog.prerequisites.length == 2,
|
||||
"target should have 2 prerequisites, got: " ~
|
||||
to!string(graph.targets[0].prerequisites.length));
|
||||
assert(graph.targets[0].prerequisites[0] == "main.o",
|
||||
to!string(prog.prerequisites.length));
|
||||
assert(prog.prerequisites[0] == "main.o",
|
||||
"first prereq should be 'main.o', got: " ~
|
||||
graph.targets[0].prerequisites[0]);
|
||||
assert(graph.targets[0].prerequisites[1] == "util.o",
|
||||
prog.prerequisites[0]);
|
||||
assert(prog.prerequisites[1] == "util.o",
|
||||
"second prereq should be 'util.o', got: " ~
|
||||
graph.targets[0].prerequisites[1]);
|
||||
assert(graph.targets[0].recipe.length == 1,
|
||||
prog.prerequisites[1]);
|
||||
assert(prog.recipe.length == 1,
|
||||
"target should have 1 recipe line");
|
||||
}
|
||||
|
||||
@@ -1130,9 +1260,11 @@ unittest
|
||||
|
||||
assert(env.get("CC") == "gcc");
|
||||
assert(env.get("CFLAGS") == "-Wall");
|
||||
assert(graph.targets.length == 2);
|
||||
assert(graph.targets[0].name == "foo.o");
|
||||
assert(graph.targets[1].name == "bar.o");
|
||||
// foo.o: foo.c → foo.c stub, bar.o: → no prereqs
|
||||
assert(graph.targets.length == 3);
|
||||
assert(graph.hasTarget("foo.o"), "foo.o should be in graph");
|
||||
assert(graph.hasTarget("bar.o"), "bar.o should be in graph");
|
||||
assert(graph.hasTarget("foo.c"), "foo.c stub should be in graph");
|
||||
}
|
||||
|
||||
///
|
||||
@@ -1395,13 +1527,15 @@ unittest
|
||||
evaluate(root, &env, &graph);
|
||||
|
||||
// Target should exist with only real prereqs.
|
||||
// Target should exist with only real prereqs.
|
||||
assert(graph.targets.length == 1);
|
||||
assert(graph.targets[0].prerequisites.length == 2,
|
||||
// dep1.o + dep2.o stubs also materialized.
|
||||
assert(graph.targets.length == 3);
|
||||
auto myprog = graph.findTarget("my_prog");
|
||||
assert(myprog !is null);
|
||||
assert(myprog.prerequisites.length == 2,
|
||||
"should have 2 real prereqs, got: " ~
|
||||
graph.targets[0].prerequisites.length.to!string);
|
||||
assert(graph.targets[0].prerequisites[0] == "dep1.o");
|
||||
assert(graph.targets[0].prerequisites[1] == "dep2.o");
|
||||
myprog.prerequisites.length.to!string);
|
||||
assert(myprog.prerequisites[0] == "dep1.o");
|
||||
assert(myprog.prerequisites[1] == "dep2.o");
|
||||
|
||||
// Scoped variable should be stored.
|
||||
assert(env.getScoped("LDFLAGS", "my_prog") == "-lm",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# I'll use edit tool instead
|
||||
@@ -770,6 +770,7 @@ bool isBuiltinFunction(string word)
|
||||
case "wildcard": case "realpath": case "abspath":
|
||||
case "shell": case "error": case "warning": case "info":
|
||||
case "foreach": case "call": case "value": case "origin": case "flavor":
|
||||
case "or": case "and":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -835,6 +836,8 @@ private bool isSelfExpanding(BuiltinFunction bf)
|
||||
switch (bf)
|
||||
{
|
||||
case BuiltinFunction.foreach_:
|
||||
case BuiltinFunction.or_:
|
||||
case BuiltinFunction.and_:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -878,6 +881,8 @@ BuiltinFunction builtinFromName(string name)
|
||||
case "value": return BuiltinFunction.value;
|
||||
case "origin": return BuiltinFunction.origin;
|
||||
case "flavor": return BuiltinFunction.flavor;
|
||||
case "or": return BuiltinFunction.or_;
|
||||
case "and": return BuiltinFunction.and_;
|
||||
default: return BuiltinFunction.info;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,6 +568,36 @@ string evaluateFunction(BuiltinFunction func, string[] args, Environment* env =
|
||||
if (args.length < 1)
|
||||
return "undefined";
|
||||
return env && env.hasKey(args[0]) ? "recursive" : "undefined";
|
||||
|
||||
// --- Logical operators ---
|
||||
case BuiltinFunction.or_:
|
||||
// $(or arg1,arg2,...) — returns the first non-empty argument.
|
||||
// Short-circuit: arguments are not expanded until needed.
|
||||
foreach (arg; args)
|
||||
{
|
||||
// For self-expanding functions, args are raw — expand now.
|
||||
import antelope.evaluator.expansion;
|
||||
string expanded = expand(arg, env);
|
||||
if (expanded.length > 0)
|
||||
return expanded;
|
||||
}
|
||||
return "";
|
||||
|
||||
case BuiltinFunction.and_:
|
||||
// $(and arg1,arg2,...) — returns empty if any argument is empty,
|
||||
// otherwise returns the last (expanded) argument.
|
||||
{
|
||||
import antelope.evaluator.expansion;
|
||||
string last;
|
||||
foreach (arg; args)
|
||||
{
|
||||
string expanded = expand(arg, env);
|
||||
if (expanded.length == 0)
|
||||
return "";
|
||||
last = expanded;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ enum BuiltinFunction
|
||||
value,
|
||||
origin,
|
||||
flavor,
|
||||
or_,
|
||||
and_,
|
||||
}
|
||||
|
||||
/// A parsed function call.
|
||||
|
||||
@@ -41,12 +41,20 @@ struct Token
|
||||
}
|
||||
|
||||
/// Returns true if `c` is a valid character inside an identifier.
|
||||
///
|
||||
/// GNU Make identifiers are extremely permissive — almost any printable
|
||||
/// character that isn't a whitespace or operator can appear in variable
|
||||
/// names, values, and prerequisite names. Missing any character here
|
||||
/// causes an infinite loop in the lexer (the identifier-accumulation
|
||||
/// code breaks without advancing `pos`, so nextToken() returns the same
|
||||
/// empty-token forever).
|
||||
private static bool isIdentChar(char c)
|
||||
{
|
||||
import std.ascii : isAlphaNum;
|
||||
switch (c)
|
||||
{
|
||||
case '-', '_', '.', '/', '+', '?', '%', '*', '~', '\\', '@', '<', '^':
|
||||
case '-', '_', '.', '/', '+', '?', '%', '*', '~', '\\', '@', '<', '^',
|
||||
'\'', '"', '`', '!', '&', '[', ']':
|
||||
return true;
|
||||
default:
|
||||
return isAlphaNum(c);
|
||||
@@ -274,6 +282,17 @@ struct Lexer
|
||||
break;
|
||||
}
|
||||
|
||||
// Safety net: if nothing was accumulated (the first character isn't
|
||||
// a valid identifier char and we broke immediately), eat the
|
||||
// unrecognised byte and return it as a single-char identifier.
|
||||
// Without this, the lexer loops forever because `pos` never advances.
|
||||
if (buf.length == 0 && pos < input.length)
|
||||
{
|
||||
buf ~= input[pos];
|
||||
pos++;
|
||||
column++;
|
||||
}
|
||||
|
||||
string value = buf.idup;
|
||||
return Token(TokenType.identifier, value, startLine, startCol);
|
||||
}
|
||||
|
||||
@@ -474,19 +474,32 @@ private struct Parser
|
||||
prereq.data = accumulatePrereq();
|
||||
// Merge adjacent tokens into a single prerequisite
|
||||
// when the first token was a $ reference, but stop
|
||||
// at standalone $ refs like $(OBJS) ${LIBS}
|
||||
// at standalone $ refs like $(OBJS) ${LIBS}.
|
||||
// Also stop if identifier text has been merged after
|
||||
// the initial $() — a subsequent $ is a new independent
|
||||
// reference (e.g., "$(srcdir)/foo $(BAR)" should be two
|
||||
// prereqs, not one concatenation).
|
||||
bool startsWithDollar = (prereq.data.length > 0 && prereq.data[0] == '$');
|
||||
if (startsWithDollar)
|
||||
{
|
||||
bool mergedIdents;
|
||||
while (check(TokenType.identifier) || check(TokenType.dollar))
|
||||
{
|
||||
// Don't merge separate standalone $ refs
|
||||
if (check(TokenType.dollar) && (prereq.data[$-1] == ')' || prereq.data[$-1] == '}'))
|
||||
break;
|
||||
// Once any identifier has been merged after the $()
|
||||
// reference (e.g., /POTFILES.in after $(srcdir)),
|
||||
// subsequent tokens are new independent prereqs.
|
||||
if (mergedIdents)
|
||||
break;
|
||||
if (check(TokenType.dollar))
|
||||
prereq.data ~= accumulatePrereq();
|
||||
else
|
||||
{
|
||||
prereq.data ~= advance().value;
|
||||
mergedIdents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
rule.children ~= prereq;
|
||||
|
||||
@@ -13,6 +13,7 @@ struct Environment
|
||||
{
|
||||
private string[string] vars;
|
||||
private bool[string] exported;
|
||||
private bool[string] cmdOverride; // set by command-line VAR=value
|
||||
private ScopedVariable[] scopedVars;
|
||||
|
||||
/// Get a variable value.
|
||||
@@ -29,9 +30,22 @@ struct Environment
|
||||
return vars.keys;
|
||||
}
|
||||
|
||||
/// Set a variable value.
|
||||
/// Set a variable value (Makefile or environment).
|
||||
/// Command-line overrides are NOT overwritten by this method.
|
||||
void set(string key, string value)
|
||||
{
|
||||
// Command-line overrides have highest precedence —
|
||||
// only `override` directive can change them (not yet implemented).
|
||||
if (key in cmdOverride)
|
||||
return;
|
||||
vars[key] = value;
|
||||
}
|
||||
|
||||
/// Set a command-line variable override (VAR=value on CLI).
|
||||
/// These take precedence over all Makefile assignments.
|
||||
void setCmdOverride(string key, string value)
|
||||
{
|
||||
cmdOverride[key] = true;
|
||||
vars[key] = value;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user