blob: 7b2e5443d58b3d6c633e94ed2a9830935bdbb22d [file] [log] [blame]
import logging, re, os
import data, parser, functions, util
from cStringIO import StringIO
from pymake.globrelative import hasglob, glob
_log = logging.getLogger('pymake.data')
_tabwidth = 4
class Location(object):
"""
A location within a makefile.
For the moment, locations are just path/line/column, but in the future
they may reference parent locations for more accurate "included from"
or "evaled at" error reporting.
"""
__slots__ = ('path', 'line', 'column')
def __init__(self, path, line, column):
self.path = path
self.line = line
self.column = column
def offset(self, s, start, end):
"""
Returns a new location offset by
the specified string.
"""
if start == end:
return self
skiplines = s.count('\n', start, end)
line = self.line + skiplines
if skiplines:
lastnl = s.rfind('\n', start, end)
assert lastnl != -1
start = lastnl + 1
column = 0
else:
column = self.column
while True:
j = s.find('\t', start, end)
if j == -1:
column += end - start
break
column += j - start
column += _tabwidth
column -= column % _tabwidth
start = j + 1
return Location(self.path, line, column)
def __str__(self):
return "%s:%s:%s" % (self.path, self.line, self.column)
def _expandwildcards(makefile, tlist):
for t in tlist:
if not hasglob(t):
yield t
else:
l = glob(makefile.workdir, t)
for r in l:
yield r
_flagescape = re.compile(r'([\s\\])')
def parsecommandlineargs(args):
"""
Given a set of arguments from a command-line invocation of make,
parse out the variable definitions and return (stmts, arglist, overridestr)
"""
overrides = []
stmts = StatementList()
r = []
for i in xrange(0, len(args)):
a = args[i]
vname, t, val = util.strpartition(a, ':=')
if t == '':
vname, t, val = util.strpartition(a, '=')
if t != '':
overrides.append(_flagescape.sub(r'\\\1', a))
vname = vname.strip()
vnameexp = data.Expansion.fromstring(vname, "Command-line argument")
stmts.append(ExportDirective(vnameexp, concurrent_set=True))
stmts.append(SetVariable(vnameexp, token=t,
value=val, valueloc=Location('<command-line>', i, len(vname) + len(t)),
targetexp=None, source=data.Variables.SOURCE_COMMANDLINE))
else:
r.append(data.stripdotslash(a))
return stmts, r, ' '.join(overrides)
class Statement(object):
"""
Represents parsed make file syntax.
This is an abstract base class. Child classes are expected to implement
basic methods defined below.
"""
def execute(self, makefile, context):
"""Executes this Statement within a make file execution context."""
raise Exception("%s must implement execute()." % self.__class__)
def to_source(self):
"""Obtain the make file "source" representation of the Statement.
This converts an individual Statement back to a string that can again
be parsed into this Statement.
"""
raise Exception("%s must implement to_source()." % self.__class__)
def __eq__(self, other):
raise Exception("%s must implement __eq__." % self.__class__)
def __ne__(self, other):
return self.__eq__(other)
class DummyRule(object):
__slots__ = ()
def addcommand(self, r):
pass
class Rule(Statement):
"""
Rules represent how to make specific targets.
See https://www.gnu.org/software/make/manual/make.html#Rules.
An individual rule is composed of a target, dependencies, and a recipe.
This class only contains references to the first 2. The recipe will be
contained in Command classes which follow this one in a stream of Statement
instances.
Instances also contain a boolean property `doublecolon` which says whether
this is a doublecolon rule. Doublecolon rules are rules that are always
executed, if they are evaluated. Normally, rules are only executed if their
target is out of date.
"""
__slots__ = ('targetexp', 'depexp', 'doublecolon')
def __init__(self, targetexp, depexp, doublecolon):
assert isinstance(targetexp, (data.Expansion, data.StringExpansion))
assert isinstance(depexp, (data.Expansion, data.StringExpansion))
self.targetexp = targetexp
self.depexp = depexp
self.doublecolon = doublecolon
def execute(self, makefile, context):
if context.weak:
self._executeweak(makefile, context)
else:
self._execute(makefile, context)
def _executeweak(self, makefile, context):
"""
If the context is weak (we're just handling dependencies) we can make a number of assumptions here.
This lets us go really fast and is generally good.
"""
assert context.weak
deps = self.depexp.resolvesplit(makefile, makefile.variables)
# Skip targets with no rules and no dependencies
if not deps:
return
targets = data.stripdotslashes(self.targetexp.resolvesplit(makefile, makefile.variables))
rule = data.Rule(list(data.stripdotslashes(deps)), self.doublecolon, loc=self.targetexp.loc, weakdeps=True)
for target in targets:
makefile.gettarget(target).addrule(rule)
makefile.foundtarget(target)
context.currule = rule
def _execute(self, makefile, context):
assert not context.weak
atargets = data.stripdotslashes(self.targetexp.resolvesplit(makefile, makefile.variables))
targets = [data.Pattern(p) for p in _expandwildcards(makefile, atargets)]
if not len(targets):
context.currule = DummyRule()
return
ispatterns = set((t.ispattern() for t in targets))
if len(ispatterns) == 2:
raise data.DataError("Mixed implicit and normal rule", self.targetexp.loc)
ispattern, = ispatterns
deps = list(_expandwildcards(makefile, data.stripdotslashes(self.depexp.resolvesplit(makefile, makefile.variables))))
if ispattern:
rule = data.PatternRule(targets, map(data.Pattern, deps), self.doublecolon, loc=self.targetexp.loc)
makefile.appendimplicitrule(rule)
else:
rule = data.Rule(deps, self.doublecolon, loc=self.targetexp.loc, weakdeps=False)
for t in targets:
makefile.gettarget(t.gettarget()).addrule(rule)
makefile.foundtarget(targets[0].gettarget())
context.currule = rule
def dump(self, fd, indent):
print >>fd, "%sRule %s: %s" % (indent, self.targetexp, self.depexp)
def to_source(self):
sep = ':'
if self.doublecolon:
sep = '::'
deps = self.depexp.to_source()
if len(deps) > 0 and not deps[0].isspace():
sep += ' '
return '\n%s%s%s' % (
self.targetexp.to_source(escape_variables=True),
sep,
deps)
def __eq__(self, other):
if not isinstance(other, Rule):
return False
return self.targetexp == other.targetexp \
and self.depexp == other.depexp \
and self.doublecolon == other.doublecolon
class StaticPatternRule(Statement):
"""
Static pattern rules are rules which specify multiple targets based on a
string pattern.
See https://www.gnu.org/software/make/manual/make.html#Static-Pattern
They are like `Rule` instances except an added property, `patternexp` is
present. It contains the Expansion which represents the rule pattern.
"""
__slots__ = ('targetexp', 'patternexp', 'depexp', 'doublecolon')
def __init__(self, targetexp, patternexp, depexp, doublecolon):
assert isinstance(targetexp, (data.Expansion, data.StringExpansion))
assert isinstance(patternexp, (data.Expansion, data.StringExpansion))
assert isinstance(depexp, (data.Expansion, data.StringExpansion))
self.targetexp = targetexp
self.patternexp = patternexp
self.depexp = depexp
self.doublecolon = doublecolon
def execute(self, makefile, context):
if context.weak:
raise data.DataError("Static pattern rules not allowed in includedeps", self.targetexp.loc)
targets = list(_expandwildcards(makefile, data.stripdotslashes(self.targetexp.resolvesplit(makefile, makefile.variables))))
if not len(targets):
context.currule = DummyRule()
return
patterns = list(data.stripdotslashes(self.patternexp.resolvesplit(makefile, makefile.variables)))
if len(patterns) != 1:
raise data.DataError("Static pattern rules must have a single pattern", self.patternexp.loc)
pattern = data.Pattern(patterns[0])
deps = [data.Pattern(p) for p in _expandwildcards(makefile, data.stripdotslashes(self.depexp.resolvesplit(makefile, makefile.variables)))]
rule = data.PatternRule([pattern], deps, self.doublecolon, loc=self.targetexp.loc)
for t in targets:
if data.Pattern(t).ispattern():
raise data.DataError("Target '%s' of a static pattern rule must not be a pattern" % (t,), self.targetexp.loc)
stem = pattern.match(t)
if stem is None:
raise data.DataError("Target '%s' does not match the static pattern '%s'" % (t, pattern), self.targetexp.loc)
makefile.gettarget(t).addrule(data.PatternRuleInstance(rule, '', stem, pattern.ismatchany()))
makefile.foundtarget(targets[0])
context.currule = rule
def dump(self, fd, indent):
print >>fd, "%sStaticPatternRule %s: %s: %s" % (indent, self.targetexp, self.patternexp, self.depexp)
def to_source(self):
sep = ':'
if self.doublecolon:
sep = '::'
pattern = self.patternexp.to_source()
deps = self.depexp.to_source()
if len(pattern) > 0 and pattern[0] not in (' ', '\t'):
sep += ' '
return '\n%s%s%s:%s' % (
self.targetexp.to_source(escape_variables=True),
sep,
pattern,
deps)
def __eq__(self, other):
if not isinstance(other, StaticPatternRule):
return False
return self.targetexp == other.targetexp \
and self.patternexp == other.patternexp \
and self.depexp == other.depexp \
and self.doublecolon == other.doublecolon
class Command(Statement):
"""
Commands are things that get executed by a rule.
A rule's recipe is composed of 0 or more Commands.
A command is simply an expansion. Commands typically represent strings to
be executed in a shell (e.g. via system()). Although, since make files
allow arbitrary shells to be used for command execution, this isn't a
guarantee.
"""
__slots__ = ('exp',)
def __init__(self, exp):
assert isinstance(exp, (data.Expansion, data.StringExpansion))
self.exp = exp
def execute(self, makefile, context):
assert context.currule is not None
if context.weak:
raise data.DataError("rules not allowed in includedeps", self.exp.loc)
context.currule.addcommand(self.exp)
def dump(self, fd, indent):
print >>fd, "%sCommand %s" % (indent, self.exp,)
def to_source(self):
# Commands have some interesting quirks when it comes to source
# formatting. First, they can be multi-line. Second, a tab needs to be
# inserted at the beginning of every line. Finally, there might be
# variable references inside the command. This means we need to escape
# variable references inside command strings. Luckily, this is handled
# by the Expansion.
s = self.exp.to_source(escape_variables=True)
return '\n'.join(['\t%s' % line for line in s.split('\n')])
def __eq__(self, other):
if not isinstance(other, Command):
return False
return self.exp == other.exp
class SetVariable(Statement):
"""
Represents a variable assignment.
Variable assignment comes in two different flavors.
Simple assignment has the form:
<Expansion> <Assignment Token> <string>
e.g. FOO := bar
These correspond to the fields `vnameexp`, `token`, and `value`. In
addition, `valueloc` will be a Location and `source` will be a
pymake.data.Variables.SOURCE_* constant.
There are also target-specific variables. These are variables that only
apply in the context of a specific target. They are like the aforementioned
assignment except the `targetexp` field is set to an Expansion representing
the target they apply to.
"""
__slots__ = ('vnameexp', 'token', 'value', 'valueloc', 'targetexp', 'source')
def __init__(self, vnameexp, token, value, valueloc, targetexp, source=None):
assert isinstance(vnameexp, (data.Expansion, data.StringExpansion))
assert isinstance(value, str)
assert targetexp is None or isinstance(targetexp, (data.Expansion, data.StringExpansion))
if source is None:
source = data.Variables.SOURCE_MAKEFILE
self.vnameexp = vnameexp
self.token = token
self.value = value
self.valueloc = valueloc
self.targetexp = targetexp
self.source = source
def execute(self, makefile, context):
vname = self.vnameexp.resolvestr(makefile, makefile.variables)
if len(vname) == 0:
raise data.DataError("Empty variable name", self.vnameexp.loc)
if self.targetexp is None:
setvariables = [makefile.variables]
else:
setvariables = []
targets = [data.Pattern(t) for t in data.stripdotslashes(self.targetexp.resolvesplit(makefile, makefile.variables))]
for t in targets:
if t.ispattern():
setvariables.append(makefile.getpatternvariables(t))
else:
setvariables.append(makefile.gettarget(t.gettarget()).variables)
for v in setvariables:
if self.token == '+=':
v.append(vname, self.source, self.value, makefile.variables, makefile)
continue
if self.token == '?=':
flavor = data.Variables.FLAVOR_RECURSIVE
oldflavor, oldsource, oldval = v.get(vname, expand=False)
if oldval is not None:
continue
value = self.value
elif self.token == '=':
flavor = data.Variables.FLAVOR_RECURSIVE
value = self.value
else:
assert self.token == ':='
flavor = data.Variables.FLAVOR_SIMPLE
d = parser.Data.fromstring(self.value, self.valueloc)
e, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
value = e.resolvestr(makefile, makefile.variables)
v.set(vname, flavor, self.source, value)
def dump(self, fd, indent):
print >>fd, "%sSetVariable<%s> %s %s\n%s %r" % (indent, self.valueloc, self.vnameexp, self.token, indent, self.value)
def __eq__(self, other):
if not isinstance(other, SetVariable):
return False
return self.vnameexp == other.vnameexp \
and self.token == other.token \
and self.value == other.value \
and self.targetexp == other.targetexp \
and self.source == other.source
def to_source(self):
chars = []
for i in xrange(0, len(self.value)):
c = self.value[i]
# Literal # is escaped in variable assignment otherwise it would be
# a comment.
if c == '#':
# If a backslash precedes this, we need to escape it as well.
if i > 0 and self.value[i-1] == '\\':
chars.append('\\')
chars.append('\\#')
continue
chars.append(c)
value = ''.join(chars)
prefix = ''
if self.source == data.Variables.SOURCE_OVERRIDE:
prefix = 'override '
# SetVariable come in two flavors: simple and target-specific.
# We handle the target-specific syntax first.
if self.targetexp is not None:
return '%s: %s %s %s' % (
self.targetexp.to_source(),
self.vnameexp.to_source(),
self.token,
value)
# The variable could be multi-line or have leading whitespace. For
# regular variable assignment, whitespace after the token but before
# the value is ignored. If we see leading whitespace in the value here,
# the variable must have come from a define.
if value.count('\n') > 0 or (len(value) and value[0].isspace()):
# The parser holds the token in vnameexp for whatever reason.
return '%sdefine %s\n%s\nendef' % (
prefix,
self.vnameexp.to_source(),
value)
return '%s%s %s %s' % (
prefix,
self.vnameexp.to_source(),
self.token,
value)
class Condition(object):
"""
An abstract "condition", either ifeq or ifdef, perhaps negated.
See https://www.gnu.org/software/make/manual/make.html#Conditional-Syntax
Subclasses must implement:
def evaluate(self, makefile)
"""
def __eq__(self, other):
raise Exception("%s must implement __eq__." % __class__)
def __ne__(self, other):
return not self.__eq__(other)
class EqCondition(Condition):
"""
Represents an ifeq or ifneq conditional directive.
This directive consists of two Expansions which are compared for equality.
The `expected` field is a bool indicating what the condition must evaluate
to in order for its body to be executed. If True, this is an "ifeq"
conditional directive. If False, an "ifneq."
"""
__slots__ = ('exp1', 'exp2', 'expected')
def __init__(self, exp1, exp2):
assert isinstance(exp1, (data.Expansion, data.StringExpansion))
assert isinstance(exp2, (data.Expansion, data.StringExpansion))
self.expected = True
self.exp1 = exp1
self.exp2 = exp2
def evaluate(self, makefile):
r1 = self.exp1.resolvestr(makefile, makefile.variables)
r2 = self.exp2.resolvestr(makefile, makefile.variables)
return (r1 == r2) == self.expected
def __str__(self):
return "ifeq (expected=%s) %s %s" % (self.expected, self.exp1, self.exp2)
def __eq__(self, other):
if not isinstance(other, EqCondition):
return False
return self.exp1 == other.exp1 \
and self.exp2 == other.exp2 \
and self.expected == other.expected
class IfdefCondition(Condition):
"""
Represents an ifdef or ifndef conditional directive.
This directive consists of a single expansion which represents the name of
a variable (without the leading '$') which will be checked for definition.
The `expected` field is a bool and has the same behavior as EqCondition.
If it is True, this represents a "ifdef" conditional. If False, "ifndef."
"""
__slots__ = ('exp', 'expected')
def __init__(self, exp):
assert isinstance(exp, (data.Expansion, data.StringExpansion))
self.exp = exp
self.expected = True
def evaluate(self, makefile):
vname = self.exp.resolvestr(makefile, makefile.variables)
flavor, source, value = makefile.variables.get(vname, expand=False)
if value is None:
return not self.expected
return (len(value) > 0) == self.expected
def __str__(self):
return "ifdef (expected=%s) %s" % (self.expected, self.exp)
def __eq__(self, other):
if not isinstance(other, IfdefCondition):
return False
return self.exp == other.exp and self.expected == other.expected
class ElseCondition(Condition):
"""
Represents the transition between branches in a ConditionBlock.
"""
__slots__ = ()
def evaluate(self, makefile):
return True
def __str__(self):
return "else"
def __eq__(self, other):
return isinstance(other, ElseCondition)
class ConditionBlock(Statement):
"""
A set of related Conditions.
This is essentially a list of 2-tuples of (Condition, list(Statement)).
The parser creates a ConditionBlock for all statements related to the same
conditional group. If iterating over the parser's output, where you think
you would see an ifeq, you will see a ConditionBlock containing an IfEq. In
other words, the parser collapses separate statements into this container
class.
ConditionBlock instances may exist within other ConditionBlock if the
conditional logic is multiple levels deep.
"""
__slots__ = ('loc', '_groups')
def __init__(self, loc, condition):
self.loc = loc
self._groups = []
self.addcondition(loc, condition)
def getloc(self):
return self.loc
def addcondition(self, loc, condition):
assert isinstance(condition, Condition)
condition.loc = loc
if len(self._groups) and isinstance(self._groups[-1][0], ElseCondition):
raise parser.SyntaxError("Multiple else conditions for block starting at %s" % self.loc, loc)
self._groups.append((condition, StatementList()))
def append(self, statement):
self._groups[-1][1].append(statement)
def execute(self, makefile, context):
i = 0
for c, statements in self._groups:
if c.evaluate(makefile):
_log.debug("Condition at %s met by clause #%i", self.loc, i)
statements.execute(makefile, context)
return
i += 1
def dump(self, fd, indent):
print >>fd, "%sConditionBlock" % (indent,)
indent2 = indent + ' '
for c, statements in self._groups:
print >>fd, "%s Condition %s" % (indent, c)
statements.dump(fd, indent2)
print >>fd, "%s ~Condition" % (indent,)
print >>fd, "%s~ConditionBlock" % (indent,)
def to_source(self):
lines = []
index = 0
for condition, statements in self:
lines.append(ConditionBlock.condition_source(condition, index))
index += 1
for statement in statements:
lines.append(statement.to_source())
lines.append('endif')
return '\n'.join(lines)
def __eq__(self, other):
if not isinstance(other, ConditionBlock):
return False
if len(self) != len(other):
return False
for i in xrange(0, len(self)):
our_condition, our_statements = self[i]
other_condition, other_statements = other[i]
if our_condition != other_condition:
return False
if our_statements != other_statements:
return False
return True
@staticmethod
def condition_source(statement, index):
"""Convert a condition to its source representation.
The index argument defines the index of this condition inside a
ConditionBlock. If it is greater than 0, an "else" will be prepended
to the result, if necessary.
"""
prefix = ''
if isinstance(statement, (EqCondition, IfdefCondition)) and index > 0:
prefix = 'else '
if isinstance(statement, IfdefCondition):
s = statement.exp.s
if statement.expected:
return '%sifdef %s' % (prefix, s)
return '%sifndef %s' % (prefix, s)
if isinstance(statement, EqCondition):
args = [
statement.exp1.to_source(escape_comments=True),
statement.exp2.to_source(escape_comments=True)]
use_quotes = False
single_quote_present = False
double_quote_present = False
for i, arg in enumerate(args):
if len(arg) > 0 and (arg[0].isspace() or arg[-1].isspace()):
use_quotes = True
if "'" in arg:
single_quote_present = True
if '"' in arg:
double_quote_present = True
# Quote everything if needed.
if single_quote_present and double_quote_present:
raise Exception('Cannot format condition with multiple quotes.')
if use_quotes:
for i, arg in enumerate(args):
# Double to single quotes.
if single_quote_present:
args[i] = '"' + arg + '"'
else:
args[i] = "'" + arg + "'"
body = None
if use_quotes:
body = ' '.join(args)
else:
body = '(%s)' % ','.join(args)
if statement.expected:
return '%sifeq %s' % (prefix, body)
return '%sifneq %s' % (prefix, body)
if isinstance(statement, ElseCondition):
return 'else'
raise Exception('Unhandled Condition statement: %s' %
statement.__class__)
def __iter__(self):
return iter(self._groups)
def __len__(self):
return len(self._groups)
def __getitem__(self, i):
return self._groups[i]
class Include(Statement):
"""
Represents the include directive.
See https://www.gnu.org/software/make/manual/make.html#Include
The file to be included is represented by the Expansion defined in the
field `exp`. `required` is a bool indicating whether execution should fail
if the specified file could not be processed.
"""
__slots__ = ('exp', 'required', 'deps')
def __init__(self, exp, required, weak):
assert isinstance(exp, (data.Expansion, data.StringExpansion))
self.exp = exp
self.required = required
self.weak = weak
def execute(self, makefile, context):
files = self.exp.resolvesplit(makefile, makefile.variables)
for f in files:
makefile.include(f, self.required, loc=self.exp.loc, weak=self.weak)
def dump(self, fd, indent):
print >>fd, "%sInclude %s" % (indent, self.exp)
def to_source(self):
prefix = ''
if not self.required:
prefix = '-'
return '%sinclude %s' % (prefix, self.exp.to_source())
def __eq__(self, other):
if not isinstance(other, Include):
return False
return self.exp == other.exp and self.required == other.required
class VPathDirective(Statement):
"""
Represents the vpath directive.
See https://www.gnu.org/software/make/manual/make.html#Selective-Search
"""
__slots__ = ('exp',)
def __init__(self, exp):
assert isinstance(exp, (data.Expansion, data.StringExpansion))
self.exp = exp
def execute(self, makefile, context):
words = list(data.stripdotslashes(self.exp.resolvesplit(makefile, makefile.variables)))
if len(words) == 0:
makefile.clearallvpaths()
else:
pattern = data.Pattern(words[0])
mpaths = words[1:]
if len(mpaths) == 0:
makefile.clearvpath(pattern)
else:
dirs = []
for mpath in mpaths:
dirs.extend((dir for dir in mpath.split(os.pathsep)
if dir != ''))
if len(dirs):
makefile.addvpath(pattern, dirs)
def dump(self, fd, indent):
print >>fd, "%sVPath %s" % (indent, self.exp)
def to_source(self):
return 'vpath %s' % self.exp.to_source()
def __eq__(self, other):
if not isinstance(other, VPathDirective):
return False
return self.exp == other.exp
class ExportDirective(Statement):
"""
Represents the "export" directive.
This is used to control exporting variables to sub makes.
See https://www.gnu.org/software/make/manual/make.html#Variables_002fRecursion
The `concurrent_set` field defines whether this statement occurred with or
without a variable assignment. If False, no variable assignment was
present. If True, the SetVariable immediately following this statement
originally came from this export directive (the parser splits it into
multiple statements).
"""
__slots__ = ('exp', 'concurrent_set')
def __init__(self, exp, concurrent_set):
assert isinstance(exp, (data.Expansion, data.StringExpansion))
self.exp = exp
self.concurrent_set = concurrent_set
def execute(self, makefile, context):
if self.concurrent_set:
vlist = [self.exp.resolvestr(makefile, makefile.variables)]
else:
vlist = list(self.exp.resolvesplit(makefile, makefile.variables))
if not len(vlist):
raise data.DataError("Exporting all variables is not supported", self.exp.loc)
for v in vlist:
makefile.exportedvars[v] = True
def dump(self, fd, indent):
print >>fd, "%sExport (single=%s) %s" % (indent, self.single, self.exp)
def to_source(self):
return ('export %s' % self.exp.to_source()).rstrip()
def __eq__(self, other):
if not isinstance(other, ExportDirective):
return False
# single is irrelevant because it just says whether the next Statement
# contains a variable definition.
return self.exp == other.exp
class UnexportDirective(Statement):
"""
Represents the "unexport" directive.
This is the opposite of ExportDirective.
"""
__slots__ = ('exp',)
def __init__(self, exp):
self.exp = exp
def execute(self, makefile, context):
vlist = list(self.exp.resolvesplit(makefile, makefile.variables))
for v in vlist:
makefile.exportedvars[v] = False
def dump(self, fd, indent):
print >>fd, "%sUnexport %s" % (indent, self.exp)
def to_source(self):
return 'unexport %s' % self.exp.to_source()
def __eq__(self, other):
if not isinstance(other, UnexportDirective):
return False
return self.exp == other.exp
class EmptyDirective(Statement):
"""
Represents a standalone statement, usually an Expansion.
You will encounter EmptyDirective instances if there is a function
or similar at the top-level of a make file (e.g. outside of a rule or
variable assignment). You can also find them as the bodies of
ConditionBlock branches.
"""
__slots__ = ('exp',)
def __init__(self, exp):
assert isinstance(exp, (data.Expansion, data.StringExpansion))
self.exp = exp
def execute(self, makefile, context):
v = self.exp.resolvestr(makefile, makefile.variables)
if v.strip() != '':
raise data.DataError("Line expands to non-empty value", self.exp.loc)
def dump(self, fd, indent):
print >>fd, "%sEmptyDirective: %s" % (indent, self.exp)
def to_source(self):
return self.exp.to_source()
def __eq__(self, other):
if not isinstance(other, EmptyDirective):
return False
return self.exp == other.exp
class _EvalContext(object):
__slots__ = ('currule', 'weak')
def __init__(self, weak):
self.weak = weak
class StatementList(list):
"""
A list of Statement instances.
This is what is generated by the parser when a make file is parsed.
Consumers can iterate over all Statement instances in this collection to
statically inspect (and even modify) make files before they are executed.
"""
__slots__ = ('mtime',)
def append(self, statement):
assert isinstance(statement, Statement)
list.append(self, statement)
def execute(self, makefile, context=None, weak=False):
if context is None:
context = _EvalContext(weak=weak)
for s in self:
s.execute(makefile, context)
def dump(self, fd, indent):
for s in self:
s.dump(fd, indent)
def __str__(self):
fd = StringIO()
self.dump(fd, '')
return fd.getvalue()
def to_source(self):
return '\n'.join([s.to_source() for s in self])
def iterstatements(stmts):
for s in stmts:
yield s
if isinstance(s, ConditionBlock):
for c, sl in s:
for s2 in iterstatments(sl): yield s2