Back to index

enigmail  1.4.3
parserdata.py
Go to the documentation of this file.
00001 import logging, re, os
00002 import data, parser, functions, util
00003 from cStringIO import StringIO
00004 from pymake.globrelative import hasglob, glob
00005 
00006 _log = logging.getLogger('pymake.data')
00007 _tabwidth = 4
00008 
00009 class Location(object):
00010     """
00011     A location within a makefile.
00012 
00013     For the moment, locations are just path/line/column, but in the future
00014     they may reference parent locations for more accurate "included from"
00015     or "evaled at" error reporting.
00016     """
00017     __slots__ = ('path', 'line', 'column')
00018 
00019     def __init__(self, path, line, column):
00020         self.path = path
00021         self.line = line
00022         self.column = column
00023 
00024     def offset(self, s, start, end):
00025         """
00026         Returns a new location offset by
00027         the specified string.
00028         """
00029 
00030         if start == end:
00031             return self
00032         
00033         skiplines = s.count('\n', start, end)
00034         line = self.line + skiplines
00035         if skiplines:
00036             lastnl = s.rfind('\n', start, end)
00037             assert lastnl != -1
00038             start = lastnl + 1
00039             column = 0
00040         else:
00041             column = self.column
00042 
00043         while True:
00044             j = s.find('\t', start, end)
00045             if j == -1:
00046                 column += end - start
00047                 break
00048 
00049             column += j - start
00050             column += _tabwidth
00051             column -= column % _tabwidth
00052             start = j + 1
00053 
00054         return Location(self.path, line, column)
00055 
00056     def __str__(self):
00057         return "%s:%s:%s" % (self.path, self.line, self.column)
00058 
00059 def _expandwildcards(makefile, tlist):
00060     for t in tlist:
00061         if not hasglob(t):
00062             yield t
00063         else:
00064             l = glob(makefile.workdir, t)
00065             for r in l:
00066                 yield r
00067 
00068 _flagescape = re.compile(r'([\s\\])')
00069 
00070 def parsecommandlineargs(args):
00071     """
00072     Given a set of arguments from a command-line invocation of make,
00073     parse out the variable definitions and return (stmts, arglist, overridestr)
00074     """
00075 
00076     overrides = []
00077     stmts = StatementList()
00078     r = []
00079     for i in xrange(0, len(args)):
00080         a = args[i]
00081 
00082         vname, t, val = util.strpartition(a, ':=')
00083         if t == '':
00084             vname, t, val = util.strpartition(a, '=')
00085         if t != '':
00086             overrides.append(_flagescape.sub(r'\\\1', a))
00087 
00088             vname = vname.strip()
00089             vnameexp = data.Expansion.fromstring(vname, "Command-line argument")
00090 
00091             stmts.append(SetVariable(vnameexp, token=t,
00092                                      value=val, valueloc=Location('<command-line>', i, len(vname) + len(t)),
00093                                      targetexp=None, source=data.Variables.SOURCE_COMMANDLINE))
00094         else:
00095             r.append(a)
00096 
00097     return stmts, r, ' '.join(overrides)
00098 
00099 class Statement(object):
00100     """
00101     A statement is an abstract object representing a single "chunk" of makefile syntax. Subclasses
00102     must implement the following method:
00103 
00104     def execute(self, makefile, context)
00105     """
00106 
00107 class DummyRule(object):
00108     __slots__ = ()
00109 
00110     def addcommand(self, r):
00111         pass
00112 
00113 class Rule(Statement):
00114     __slots__ = ('targetexp', 'depexp', 'doublecolon')
00115 
00116     def __init__(self, targetexp, depexp, doublecolon):
00117         assert isinstance(targetexp, (data.Expansion, data.StringExpansion))
00118         assert isinstance(depexp, (data.Expansion, data.StringExpansion))
00119         
00120         self.targetexp = targetexp
00121         self.depexp = depexp
00122         self.doublecolon = doublecolon
00123 
00124     def execute(self, makefile, context):
00125         atargets = data.stripdotslashes(self.targetexp.resolvesplit(makefile, makefile.variables))
00126         targets = [data.Pattern(p) for p in _expandwildcards(makefile, atargets)]
00127 
00128         if not len(targets):
00129             context.currule = DummyRule()
00130             return
00131 
00132         ispatterns = set((t.ispattern() for t in targets))
00133         if len(ispatterns) == 2:
00134             raise data.DataError("Mixed implicit and normal rule", self.targetexp.loc)
00135         ispattern, = ispatterns
00136 
00137         if ispattern and context.weak:
00138             raise data.DataError("Pattern rules not allowed in includedeps", self.targetexp.loc)
00139 
00140         deps = [p for p in _expandwildcards(makefile, data.stripdotslashes(self.depexp.resolvesplit(makefile, makefile.variables)))]
00141         if ispattern:
00142             rule = data.PatternRule(targets, map(data.Pattern, deps), self.doublecolon, loc=self.targetexp.loc)
00143             makefile.appendimplicitrule(rule)
00144         else:
00145             rule = data.Rule(deps, self.doublecolon, loc=self.targetexp.loc, weakdeps=context.weak)
00146             for t in targets:
00147                 makefile.gettarget(t.gettarget()).addrule(rule)
00148 
00149             makefile.foundtarget(targets[0].gettarget())
00150 
00151         context.currule = rule
00152 
00153     def dump(self, fd, indent):
00154         print >>fd, "%sRule %s: %s" % (indent, self.targetexp, self.depexp)
00155 
00156 class StaticPatternRule(Statement):
00157     __slots__ = ('targetexp', 'patternexp', 'depexp', 'doublecolon')
00158 
00159     def __init__(self, targetexp, patternexp, depexp, doublecolon):
00160         assert isinstance(targetexp, (data.Expansion, data.StringExpansion))
00161         assert isinstance(patternexp, (data.Expansion, data.StringExpansion))
00162         assert isinstance(depexp, (data.Expansion, data.StringExpansion))
00163 
00164         self.targetexp = targetexp
00165         self.patternexp = patternexp
00166         self.depexp = depexp
00167         self.doublecolon = doublecolon
00168 
00169     def execute(self, makefile, context):
00170         if context.weak:
00171             raise data.DataError("Static pattern rules not allowed in includedeps", self.targetexp.loc)
00172 
00173         targets = list(_expandwildcards(makefile, data.stripdotslashes(self.targetexp.resolvesplit(makefile, makefile.variables))))
00174 
00175         if not len(targets):
00176             context.currule = DummyRule()
00177             return
00178 
00179         patterns = list(data.stripdotslashes(self.patternexp.resolvesplit(makefile, makefile.variables)))
00180         if len(patterns) != 1:
00181             raise data.DataError("Static pattern rules must have a single pattern", self.patternexp.loc)
00182         pattern = data.Pattern(patterns[0])
00183 
00184         deps = [data.Pattern(p) for p in _expandwildcards(makefile, data.stripdotslashes(self.depexp.resolvesplit(makefile, makefile.variables)))]
00185 
00186         rule = data.PatternRule([pattern], deps, self.doublecolon, loc=self.targetexp.loc)
00187 
00188         for t in targets:
00189             if data.Pattern(t).ispattern():
00190                 raise data.DataError("Target '%s' of a static pattern rule must not be a pattern" % (t,), self.targetexp.loc)
00191             stem = pattern.match(t)
00192             if stem is None:
00193                 raise data.DataError("Target '%s' does not match the static pattern '%s'" % (t, pattern), self.targetexp.loc)
00194             makefile.gettarget(t).addrule(data.PatternRuleInstance(rule, '', stem, pattern.ismatchany()))
00195 
00196         makefile.foundtarget(targets[0])
00197         context.currule = rule
00198 
00199     def dump(self, fd, indent):
00200         print >>fd, "%sStaticPatternRule %s: %s: %s" % (indent, self.targetexp, self.patternexp, self.depexp)
00201 
00202 class Command(Statement):
00203     __slots__ = ('exp',)
00204 
00205     def __init__(self, exp):
00206         assert isinstance(exp, (data.Expansion, data.StringExpansion))
00207         self.exp = exp
00208 
00209     def execute(self, makefile, context):
00210         assert context.currule is not None
00211         if context.weak:
00212             raise data.DataError("rules not allowed in includedeps", self.exp.loc)
00213 
00214         context.currule.addcommand(self.exp)
00215 
00216     def dump(self, fd, indent):
00217         print >>fd, "%sCommand %s" % (indent, self.exp,)
00218 
00219 class SetVariable(Statement):
00220     __slots__ = ('vnameexp', 'token', 'value', 'valueloc', 'targetexp', 'source')
00221 
00222     def __init__(self, vnameexp, token, value, valueloc, targetexp, source=None):
00223         assert isinstance(vnameexp, (data.Expansion, data.StringExpansion))
00224         assert isinstance(value, str)
00225         assert targetexp is None or isinstance(targetexp, (data.Expansion, data.StringExpansion))
00226 
00227         if source is None:
00228             source = data.Variables.SOURCE_MAKEFILE
00229 
00230         self.vnameexp = vnameexp
00231         self.token = token
00232         self.value = value
00233         self.valueloc = valueloc
00234         self.targetexp = targetexp
00235         self.source = source
00236 
00237     def execute(self, makefile, context):
00238         vname = self.vnameexp.resolvestr(makefile, makefile.variables)
00239         if len(vname) == 0:
00240             raise data.DataError("Empty variable name", self.vnameexp.loc)
00241 
00242         if self.targetexp is None:
00243             setvariables = [makefile.variables]
00244         else:
00245             setvariables = []
00246 
00247             targets = [data.Pattern(t) for t in data.stripdotslashes(self.targetexp.resolvesplit(makefile, makefile.variables))]
00248             for t in targets:
00249                 if t.ispattern():
00250                     setvariables.append(makefile.getpatternvariables(t))
00251                 else:
00252                     setvariables.append(makefile.gettarget(t.gettarget()).variables)
00253 
00254         for v in setvariables:
00255             if self.token == '+=':
00256                 v.append(vname, self.source, self.value, makefile.variables, makefile)
00257                 continue
00258 
00259             if self.token == '?=':
00260                 flavor = data.Variables.FLAVOR_RECURSIVE
00261                 oldflavor, oldsource, oldval = v.get(vname, expand=False)
00262                 if oldval is not None:
00263                     continue
00264                 value = self.value
00265             elif self.token == '=':
00266                 flavor = data.Variables.FLAVOR_RECURSIVE
00267                 value = self.value
00268             else:
00269                 assert self.token == ':='
00270 
00271                 flavor = data.Variables.FLAVOR_SIMPLE
00272                 d = parser.Data.fromstring(self.value, self.valueloc)
00273                 e, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
00274                 value = e.resolvestr(makefile, makefile.variables)
00275 
00276             v.set(vname, flavor, self.source, value)
00277 
00278     def dump(self, fd, indent):
00279         print >>fd, "%sSetVariable<%s> %s %s\n%s %r" % (indent, self.valueloc, self.vnameexp, self.token, indent, self.value)
00280 
00281 class Condition(object):
00282     """
00283     An abstract "condition", either ifeq or ifdef, perhaps negated. Subclasses must implement:
00284 
00285     def evaluate(self, makefile)
00286     """
00287 
00288 class EqCondition(Condition):
00289     __slots__ = ('exp1', 'exp2', 'expected')
00290 
00291     def __init__(self, exp1, exp2):
00292         assert isinstance(exp1, (data.Expansion, data.StringExpansion))
00293         assert isinstance(exp2, (data.Expansion, data.StringExpansion))
00294 
00295         self.expected = True
00296         self.exp1 = exp1
00297         self.exp2 = exp2
00298 
00299     def evaluate(self, makefile):
00300         r1 = self.exp1.resolvestr(makefile, makefile.variables)
00301         r2 = self.exp2.resolvestr(makefile, makefile.variables)
00302         return (r1 == r2) == self.expected
00303 
00304     def __str__(self):
00305         return "ifeq (expected=%s) %s %s" % (self.expected, self.exp1, self.exp2)
00306 
00307 class IfdefCondition(Condition):
00308     __slots__ = ('exp', 'expected')
00309 
00310     def __init__(self, exp):
00311         assert isinstance(exp, (data.Expansion, data.StringExpansion))
00312         self.exp = exp
00313         self.expected = True
00314 
00315     def evaluate(self, makefile):
00316         vname = self.exp.resolvestr(makefile, makefile.variables)
00317         flavor, source, value = makefile.variables.get(vname, expand=False)
00318 
00319         if value is None:
00320             return not self.expected
00321 
00322         return (len(value) > 0) == self.expected
00323 
00324     def __str__(self):
00325         return "ifdef (expected=%s) %s" % (self.expected, self.exp)
00326 
00327 class ElseCondition(Condition):
00328     __slots__ = ()
00329 
00330     def evaluate(self, makefile):
00331         return True
00332 
00333     def __str__(self):
00334         return "else"
00335 
00336 class ConditionBlock(Statement):
00337     """
00338     A list of conditions: each condition has an associated list of statements.
00339     """
00340     __slots__ = ('loc', '_groups')
00341 
00342     def __init__(self, loc, condition):
00343         self.loc = loc
00344         self._groups = []
00345         self.addcondition(loc, condition)
00346 
00347     def getloc(self):
00348         return self.loc
00349 
00350     def addcondition(self, loc, condition):
00351         assert isinstance(condition, Condition)
00352         condition.loc = loc
00353 
00354         if len(self._groups) and isinstance(self._groups[-1][0], ElseCondition):
00355             raise parser.SyntaxError("Multiple else conditions for block starting at %s" % self.loc, loc)
00356 
00357         self._groups.append((condition, StatementList()))
00358 
00359     def append(self, statement):
00360         self._groups[-1][1].append(statement)
00361 
00362     def execute(self, makefile, context):
00363         i = 0
00364         for c, statements in self._groups:
00365             if c.evaluate(makefile):
00366                 _log.debug("Condition at %s met by clause #%i", self.loc, i)
00367                 statements.execute(makefile, context)
00368                 return
00369 
00370             i += 1
00371 
00372     def dump(self, fd, indent):
00373         print >>fd, "%sConditionBlock" % (indent,)
00374 
00375         indent2 = indent + '  '
00376         for c, statements in self._groups:
00377             print >>fd, "%s Condition %s" % (indent, c)
00378             statements.dump(fd, indent2)
00379             print >>fd, "%s ~Condition" % (indent,)
00380         print >>fd, "%s~ConditionBlock" % (indent,)
00381 
00382     def __iter__(self):
00383         return iter(self._groups)
00384 
00385     def __len__(self):
00386         return len(self._groups)
00387 
00388     def __getitem__(self, i):
00389         return self._groups[i]
00390 
00391 class Include(Statement):
00392     __slots__ = ('exp', 'required', 'deps')
00393 
00394     def __init__(self, exp, required, weak):
00395         assert isinstance(exp, (data.Expansion, data.StringExpansion))
00396         self.exp = exp
00397         self.required = required
00398         self.weak = weak
00399 
00400     def execute(self, makefile, context):
00401         files = self.exp.resolvesplit(makefile, makefile.variables)
00402         for f in files:
00403             makefile.include(f, self.required, loc=self.exp.loc, weak=self.weak)
00404 
00405     def dump(self, fd, indent):
00406         print >>fd, "%sInclude %s" % (indent, self.exp)
00407 
00408 class VPathDirective(Statement):
00409     __slots__ = ('exp',)
00410 
00411     def __init__(self, exp):
00412         assert isinstance(exp, (data.Expansion, data.StringExpansion))
00413         self.exp = exp
00414 
00415     def execute(self, makefile, context):
00416         words = list(data.stripdotslashes(self.exp.resolvesplit(makefile, makefile.variables)))
00417         if len(words) == 0:
00418             makefile.clearallvpaths()
00419         else:
00420             pattern = data.Pattern(words[0])
00421             mpaths = words[1:]
00422 
00423             if len(mpaths) == 0:
00424                 makefile.clearvpath(pattern)
00425             else:
00426                 dirs = []
00427                 for mpath in mpaths:
00428                     dirs.extend((dir for dir in mpath.split(os.pathsep)
00429                                  if dir != ''))
00430                 if len(dirs):
00431                     makefile.addvpath(pattern, dirs)
00432 
00433     def dump(self, fd, indent):
00434         print >>fd, "%sVPath %s" % (indent, self.exp)
00435 
00436 class ExportDirective(Statement):
00437     __slots__ = ('exp', 'single')
00438 
00439     def __init__(self, exp, single):
00440         assert isinstance(exp, (data.Expansion, data.StringExpansion))
00441         self.exp = exp
00442         self.single = single
00443 
00444     def execute(self, makefile, context):
00445         if self.single:
00446             vlist = [self.exp.resolvestr(makefile, makefile.variables)]
00447         else:
00448             vlist = list(self.exp.resolvesplit(makefile, makefile.variables))
00449             if not len(vlist):
00450                 raise data.DataError("Exporting all variables is not supported", self.exp.loc)
00451 
00452         for v in vlist:
00453             makefile.exportedvars[v] = True
00454 
00455     def dump(self, fd, indent):
00456         print >>fd, "%sExport (single=%s) %s" % (indent, self.single, self.exp)
00457 
00458 class UnexportDirective(Statement):
00459     __slots__ = ('exp',)
00460 
00461     def __init__(self, exp):
00462         self.exp = exp
00463 
00464     def execute(self, makefile, context):
00465         vlist = list(self.exp.resolvesplit(makefile, makefile.variables))
00466         for v in vlist:
00467             makefile.exportedvars[v] = False
00468 
00469 class EmptyDirective(Statement):
00470     __slots__ = ('exp',)
00471 
00472     def __init__(self, exp):
00473         assert isinstance(exp, (data.Expansion, data.StringExpansion))
00474         self.exp = exp
00475 
00476     def execute(self, makefile, context):
00477         v = self.exp.resolvestr(makefile, makefile.variables)
00478         if v.strip() != '':
00479             raise data.DataError("Line expands to non-empty value", self.exp.loc)
00480 
00481     def dump(self, fd, indent):
00482         print >>fd, "%sEmptyDirective: %s" % (indent, self.exp)
00483 
00484 class _EvalContext(object):
00485     __slots__ = ('currule', 'weak')
00486 
00487     def __init__(self, weak):
00488         self.weak = weak
00489 
00490 class StatementList(list):
00491     __slots__ = ('mtime',)
00492 
00493     def append(self, statement):
00494         assert isinstance(statement, Statement)
00495         list.append(self, statement)
00496 
00497     def execute(self, makefile, context=None, weak=False):
00498         if context is None:
00499             context = _EvalContext(weak=weak)
00500 
00501         for s in self:
00502             s.execute(makefile, context)
00503 
00504     def dump(self, fd, indent):
00505         for s in self:
00506             s.dump(fd, indent)
00507 
00508     def __str__(self):
00509         fd = StringIO()
00510         self.dump(fd, '')
00511         return fd.getvalue()
00512 
00513 def iterstatements(stmts):
00514     for s in stmts:
00515         yield s
00516         if isinstance(s, ConditionBlock):
00517             for c, sl in s:
00518                 for s2 in iterstatments(sl): yield s2