| 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 |