Back to index

enigmail  1.4.3
data.py
Go to the documentation of this file.
00001 """
00002 A representation of makefile data structures.
00003 """
00004 
00005 import logging, re, os, sys
00006 import parserdata, parser, functions, process, util, implicit
00007 from cStringIO import StringIO
00008 
00009 _log = logging.getLogger('pymake.data')
00010 
00011 class DataError(util.MakeError):
00012     pass
00013 
00014 class ResolutionError(DataError):
00015     """
00016     Raised when dependency resolution fails, either due to recursion or to missing
00017     prerequisites.This is separately catchable so that implicit rule search can try things
00018     without having to commit.
00019     """
00020     pass
00021 
00022 def withoutdups(it):
00023     r = set()
00024     for i in it:
00025         if not i in r:
00026             r.add(i)
00027             yield i
00028 
00029 def mtimeislater(deptime, targettime):
00030     """
00031     Is the mtime of the dependency later than the target?
00032     """
00033 
00034     if deptime is None:
00035         return True
00036     if targettime is None:
00037         return False
00038     return deptime > targettime
00039 
00040 def getmtime(path):
00041     try:
00042         s = os.stat(path)
00043         return s.st_mtime
00044     except OSError:
00045         return None
00046 
00047 def stripdotslash(s):
00048     if s.startswith('./'):
00049         st = s[2:]
00050         return st if st != '' else '.'
00051     return s
00052 
00053 def stripdotslashes(sl):
00054     for s in sl:
00055         yield stripdotslash(s)
00056 
00057 def getindent(stack):
00058     return ''.ljust(len(stack) - 1)
00059 
00060 def _if_else(c, t, f):
00061     if c:
00062         return t()
00063     return f()
00064 
00065 class StringExpansion(object):
00066     __slots__ = ('loc', 's',)
00067     simple = True
00068     
00069     def __init__(self, s, loc):
00070         assert isinstance(s, str)
00071         self.s = s
00072         self.loc = loc
00073 
00074     def lstrip(self):
00075         self.s = self.s.lstrip()
00076 
00077     def rstrip(self):
00078         self.s = self.s.rstrip()
00079 
00080     def isempty(self):
00081         return self.s == ''
00082 
00083     def resolve(self, i, j, fd, k=None):
00084         fd.write(self.s)
00085 
00086     def resolvestr(self, i, j, k=None):
00087         return self.s
00088 
00089     def resolvesplit(self, i, j, k=None):
00090         return self.s.split()
00091 
00092     def clone(self):
00093         e = Expansion(self.loc)
00094         e.appendstr(self.s)
00095         return e
00096 
00097     def __len__(self):
00098         return 1
00099 
00100     def __getitem__(self, i):
00101         assert i == 0
00102         return self.s, False
00103 
00104     def __str__(self):
00105         return "Exp<%s>(%r)" % (self.loc, self.s)
00106 
00107 class Expansion(list):
00108     """
00109     A representation of expanded data, such as that for a recursively-expanded variable, a command, etc.
00110     """
00111 
00112     __slots__ = ('loc', 'hasfunc')
00113     simple = False
00114 
00115     def __init__(self, loc=None):
00116         # A list of (element, isfunc) tuples
00117         # element is either a string or a function
00118         self.loc = loc
00119         self.hasfunc = False
00120 
00121     @staticmethod
00122     def fromstring(s, path):
00123         return StringExpansion(s, parserdata.Location(path, 1, 0))
00124 
00125     def clone(self):
00126         e = Expansion()
00127         e.extend(self)
00128         return e
00129 
00130     def appendstr(self, s):
00131         assert isinstance(s, str)
00132         if s == '':
00133             return
00134 
00135         self.append((s, False))
00136 
00137     def appendfunc(self, func):
00138         assert isinstance(func, functions.Function)
00139         self.append((func, True))
00140         self.hasfunc = True
00141 
00142     def concat(self, o):
00143         """Concatenate the other expansion on to this one."""
00144         if o.simple:
00145             self.appendstr(o.s)
00146         else:
00147             self.extend(o)
00148             self.hasfunc = self.hasfunc or o.hasfunc
00149 
00150     def isempty(self):
00151         return (not len(self)) or self[0] == ('', False)
00152 
00153     def lstrip(self):
00154         """Strip leading literal whitespace from this expansion."""
00155         while True:
00156             i, isfunc = self[0]
00157             if isfunc:
00158                 return
00159 
00160             i = i.lstrip()
00161             if i != '':
00162                 self[0] = i, False
00163                 return
00164 
00165             del self[0]
00166 
00167     def rstrip(self):
00168         """Strip trailing literal whitespace from this expansion."""
00169         while True:
00170             i, isfunc = self[-1]
00171             if isfunc:
00172                 return
00173 
00174             i = i.rstrip()
00175             if i != '':
00176                 self[-1] = i, False
00177                 return
00178 
00179             del self[-1]
00180 
00181     def finish(self):
00182         if self.hasfunc:
00183             return self
00184 
00185         return StringExpansion(''.join([i for i, isfunc in self]), self.loc)
00186 
00187     def resolve(self, makefile, variables, fd, setting=[]):
00188         """
00189         Resolve this variable into a value, by interpolating the value
00190         of other variables.
00191 
00192         @param setting (Variable instance) the variable currently
00193                being set, if any. Setting variables must avoid self-referential
00194                loops.
00195         """
00196         assert isinstance(makefile, Makefile)
00197         assert isinstance(variables, Variables)
00198         assert isinstance(setting, list)
00199 
00200         for e, isfunc in self:
00201             if isfunc:
00202                 e.resolve(makefile, variables, fd, setting)
00203             else:
00204                 assert isinstance(e, str)
00205                 fd.write(e)
00206                     
00207     def resolvestr(self, makefile, variables, setting=[]):
00208         fd = StringIO()
00209         self.resolve(makefile, variables, fd, setting)
00210         return fd.getvalue()
00211 
00212     def resolvesplit(self, makefile, variables, setting=[]):
00213         return self.resolvestr(makefile, variables, setting).split()
00214 
00215     def __repr__(self):
00216         return "<Expansion with elements: %r>" % ([e for e, isfunc in self],)
00217 
00218 class Variables(object):
00219     """
00220     A mapping from variable names to variables. Variables have flavor, source, and value. The value is an 
00221     expansion object.
00222     """
00223 
00224     __slots__ = ('parent', '_map')
00225 
00226     FLAVOR_RECURSIVE = 0
00227     FLAVOR_SIMPLE = 1
00228     FLAVOR_APPEND = 2
00229 
00230     SOURCE_OVERRIDE = 0
00231     SOURCE_COMMANDLINE = 1
00232     SOURCE_MAKEFILE = 2
00233     SOURCE_ENVIRONMENT = 3
00234     SOURCE_AUTOMATIC = 4
00235     SOURCE_IMPLICIT = 5
00236 
00237     def __init__(self, parent=None):
00238         self._map = {} # vname -> flavor, source, valuestr, valueexp
00239         self.parent = parent
00240 
00241     def readfromenvironment(self, env):
00242         for k, v in env.iteritems():
00243             self.set(k, self.FLAVOR_SIMPLE, self.SOURCE_ENVIRONMENT, v)
00244 
00245     def get(self, name, expand=True):
00246         """
00247         Get the value of a named variable. Returns a tuple (flavor, source, value)
00248 
00249         If the variable is not present, returns (None, None, None)
00250 
00251         @param expand If true, the value will be returned as an expansion. If false,
00252         it will be returned as an unexpanded string.
00253         """
00254         flavor, source, valuestr, valueexp = self._map.get(name, (None, None, None, None))
00255         if flavor is not None:
00256             if expand and flavor != self.FLAVOR_SIMPLE and valueexp is None:
00257                 d = parser.Data.fromstring(valuestr, parserdata.Location("Expansion of variables '%s'" % (name,), 1, 0))
00258                 valueexp, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
00259                 self._map[name] = flavor, source, valuestr, valueexp
00260 
00261             if flavor == self.FLAVOR_APPEND:
00262                 if self.parent:
00263                     pflavor, psource, pvalue = self.parent.get(name, expand)
00264                 else:
00265                     pflavor, psource, pvalue = None, None, None
00266 
00267                 if pvalue is None:
00268                     flavor = self.FLAVOR_RECURSIVE
00269                     # fall through
00270                 else:
00271                     if source > psource:
00272                         # TODO: log a warning?
00273                         return pflavor, psource, pvalue
00274 
00275                     if not expand:
00276                         return pflavor, psource, pvalue + ' ' + valuestr
00277 
00278                     pvalue = pvalue.clone()
00279                     pvalue.appendstr(' ')
00280                     pvalue.concat(valueexp)
00281 
00282                     return pflavor, psource, pvalue
00283                     
00284             if not expand:
00285                 return flavor, source, valuestr
00286 
00287             if flavor == self.FLAVOR_RECURSIVE:
00288                 val = valueexp
00289             else:
00290                 val = Expansion.fromstring(valuestr, "Expansion of variable '%s'" % (name,))
00291 
00292             return flavor, source, val
00293 
00294         if self.parent is not None:
00295             return self.parent.get(name, expand)
00296 
00297         return (None, None, None)
00298 
00299     def set(self, name, flavor, source, value):
00300         assert flavor in (self.FLAVOR_RECURSIVE, self.FLAVOR_SIMPLE)
00301         assert source in (self.SOURCE_OVERRIDE, self.SOURCE_COMMANDLINE, self.SOURCE_MAKEFILE, self.SOURCE_ENVIRONMENT, self.SOURCE_AUTOMATIC, self.SOURCE_IMPLICIT)
00302         assert isinstance(value, str), "expected str, got %s" % type(value)
00303 
00304         prevflavor, prevsource, prevvalue = self.get(name)
00305         if prevsource is not None and source > prevsource:
00306             # TODO: give a location for this warning
00307             _log.info("not setting variable '%s', set by higher-priority source to value '%s'" % (name, prevvalue))
00308             return
00309 
00310         self._map[name] = flavor, source, value, None
00311 
00312     def append(self, name, source, value, variables, makefile):
00313         assert source in (self.SOURCE_OVERRIDE, self.SOURCE_MAKEFILE, self.SOURCE_AUTOMATIC)
00314         assert isinstance(value, str)
00315 
00316         if name not in self._map:
00317             self._map[name] = self.FLAVOR_APPEND, source, value, None
00318             return
00319 
00320         prevflavor, prevsource, prevvalue, valueexp = self._map[name]
00321         if source > prevsource:
00322             # TODO: log a warning?
00323             return
00324 
00325         if prevflavor == self.FLAVOR_SIMPLE:
00326             d = parser.Data.fromstring(value, parserdata.Location("Expansion of variables '%s'" % (name,), 1, 0))
00327             valueexp, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
00328 
00329             val = valueexp.resolvestr(makefile, variables, [name])
00330             self._map[name] = prevflavor, prevsource, prevvalue + ' ' + val, None
00331             return
00332 
00333         newvalue = prevvalue + ' ' + value
00334         self._map[name] = prevflavor, prevsource, newvalue, None
00335 
00336     def merge(self, other):
00337         assert isinstance(other, Variables)
00338         for k, flavor, source, value in other:
00339             self.set(k, flavor, source, value)
00340 
00341     def __iter__(self):
00342         for k, (flavor, source, value, valueexp) in self._map.iteritems():
00343             yield k, flavor, source, value
00344 
00345     def __contains__(self, item):
00346         return item in self._map
00347 
00348 class Pattern(object):
00349     """
00350     A pattern is a string, possibly with a % substitution character. From the GNU make manual:
00351 
00352     '%' characters in pattern rules can be quoted with precending backslashes ('\'). Backslashes that
00353     would otherwise quote '%' charcters can be quoted with more backslashes. Backslashes that
00354     quote '%' characters or other backslashes are removed from the pattern before it is compared t
00355     file names or has a stem substituted into it. Backslashes that are not in danger of quoting '%'
00356     characters go unmolested. For example, the pattern the\%weird\\%pattern\\ has `the%weird\' preceding
00357     the operative '%' character, and 'pattern\\' following it. The final two backslashes are left alone
00358     because they cannot affect any '%' character.
00359 
00360     This insane behavior probably doesn't matter, but we're compatible just for shits and giggles.
00361     """
00362 
00363     __slots__ = ('data')
00364 
00365     def __init__(self, s):
00366         r = []
00367         i = 0
00368         while i < len(s):
00369             c = s[i]
00370             if c == '\\':
00371                 nc = s[i + 1]
00372                 if nc == '%':
00373                     r.append('%')
00374                     i += 1
00375                 elif nc == '\\':
00376                     r.append('\\')
00377                     i += 1
00378                 else:
00379                     r.append(c)
00380             elif c == '%':
00381                 self.data = (''.join(r), s[i+1:])
00382                 return
00383             else:
00384                 r.append(c)
00385             i += 1
00386 
00387         # This is different than (s,) because \% and \\ have been unescaped. Parsing patterns is
00388         # context-sensitive!
00389         self.data = (''.join(r),)
00390 
00391     def ismatchany(self):
00392         return self.data == ('','')
00393 
00394     def ispattern(self):
00395         return len(self.data) == 2
00396 
00397     def __hash__(self):
00398         return self.data.__hash__()
00399 
00400     def __eq__(self, o):
00401         assert isinstance(o, Pattern)
00402         return self.data == o.data
00403 
00404     def gettarget(self):
00405         assert not self.ispattern()
00406         return self.data[0]
00407 
00408     def hasslash(self):
00409         return self.data[0].find('/') != -1 or self.data[1].find('/') != -1
00410 
00411     def match(self, word):
00412         """
00413         Match this search pattern against a word (string).
00414 
00415         @returns None if the word doesn't match, or the matching stem.
00416                       If this is a %-less pattern, the stem will always be ''
00417         """
00418         d = self.data
00419         if len(d) == 1:
00420             if word == d[0]:
00421                 return word
00422             return None
00423 
00424         d0, d1 = d
00425         l1 = len(d0)
00426         l2 = len(d1)
00427         if len(word) >= l1 + l2 and word.startswith(d0) and word.endswith(d1):
00428             if l2 == 0:
00429                 return word[l1:]
00430             return word[l1:-l2]
00431 
00432         return None
00433 
00434     def resolve(self, dir, stem):
00435         if self.ispattern():
00436             return dir + self.data[0] + stem + self.data[1]
00437 
00438         return self.data[0]
00439 
00440     def subst(self, replacement, word, mustmatch):
00441         """
00442         Given a word, replace the current pattern with the replacement pattern, a la 'patsubst'
00443 
00444         @param mustmatch If true and this pattern doesn't match the word, throw a DataError. Otherwise
00445                          return word unchanged.
00446         """
00447         assert isinstance(replacement, str)
00448 
00449         stem = self.match(word)
00450         if stem is None:
00451             if mustmatch:
00452                 raise DataError("target '%s' doesn't match pattern" % (word,))
00453             return word
00454 
00455         if not self.ispattern():
00456             # if we're not a pattern, the replacement is not parsed as a pattern either
00457             return replacement
00458 
00459         return Pattern(replacement).resolve('', stem)
00460 
00461     def __repr__(self):
00462         return "<Pattern with data %r>" % (self.data,)
00463 
00464     _backre = re.compile(r'[%\\]')
00465     def __str__(self):
00466         if not self.ispattern():
00467             return self._backre.sub(r'\\\1', self.data[0])
00468 
00469         return self._backre.sub(r'\\\1', self.data[0]) + '%' + self.data[1]
00470 
00471 class RemakeTargetSerially(object):
00472     __slots__ = ('target', 'makefile', 'indent', 'rlist')
00473 
00474     def __init__(self, target, makefile, indent, rlist):
00475         self.target = target
00476         self.makefile = makefile
00477         self.indent = indent
00478         self.rlist = rlist
00479         self.commandscb(False)
00480 
00481     def resolvecb(self, error, didanything):
00482         assert error in (True, False)
00483 
00484         if didanything:
00485             self.target.didanything = True
00486 
00487         if error:
00488             self.target.error = True
00489             self.makefile.error = True
00490             if not self.makefile.keepgoing:
00491                 self.target.notifydone(self.makefile)
00492                 return
00493             else:
00494                 # don't run the commands!
00495                 del self.rlist[0]
00496                 self.commandscb(error=False)
00497         else:
00498             self.rlist.pop(0).runcommands(self.indent, self.commandscb)
00499 
00500     def commandscb(self, error):
00501         assert error in (True, False)
00502 
00503         if error:
00504             self.target.error = True
00505             self.makefile.error = True
00506 
00507         if self.target.error and not self.makefile.keepgoing:
00508             self.target.notifydone(self.makefile)
00509             return
00510 
00511         if not len(self.rlist):
00512             self.target.notifydone(self.makefile)
00513         else:
00514             self.rlist[0].resolvedeps(True, self.resolvecb)
00515 
00516 class RemakeTargetParallel(object):
00517     __slots__ = ('target', 'makefile', 'indent', 'rlist', 'rulesremaining', 'currunning')
00518 
00519     def __init__(self, target, makefile, indent, rlist):
00520         self.target = target
00521         self.makefile = makefile
00522         self.indent = indent
00523         self.rlist = rlist
00524 
00525         self.rulesremaining = len(rlist)
00526         self.currunning = False
00527 
00528         for r in rlist:
00529             makefile.context.defer(self.doresolve, r)
00530 
00531     def doresolve(self, r):
00532         if self.makefile.error and not self.makefile.keepgoing:
00533             r.error = True
00534             self.resolvecb(True, False)
00535         else:
00536             r.resolvedeps(False, self.resolvecb)
00537 
00538     def resolvecb(self, error, didanything):
00539         assert error in (True, False)
00540 
00541         if error:
00542             self.target.error = True
00543 
00544         if didanything:
00545             self.target.didanything = True
00546 
00547         self.rulesremaining -= 1
00548 
00549         # commandscb takes care of the details if we're currently building
00550         # something
00551         if self.currunning:
00552             return
00553 
00554         self.runnext()
00555 
00556     def runnext(self):
00557         assert not self.currunning
00558 
00559         if self.makefile.error and not self.makefile.keepgoing:
00560             self.rlist = []
00561         else:
00562             while len(self.rlist) and self.rlist[0].error:
00563                 del self.rlist[0]
00564 
00565         if not len(self.rlist):
00566             if not self.rulesremaining:
00567                 self.target.notifydone(self.makefile)
00568             return
00569 
00570         if self.rlist[0].depsremaining != 0:
00571             return
00572 
00573         self.currunning = True
00574         self.rlist.pop(0).runcommands(self.indent, self.commandscb)
00575 
00576     def commandscb(self, error):
00577         assert error in (True, False)
00578         if error:
00579             self.target.error = True
00580             self.makefile.error = True
00581 
00582         assert self.currunning
00583         self.currunning = False
00584         self.runnext()
00585 
00586 class RemakeRuleContext(object):
00587     def __init__(self, target, makefile, rule, deps,
00588                  targetstack, avoidremakeloop):
00589         self.target = target
00590         self.makefile = makefile
00591         self.rule = rule
00592         self.deps = deps
00593         self.targetstack = targetstack
00594         self.avoidremakeloop = avoidremakeloop
00595 
00596         self.running = False
00597         self.error = False
00598         self.depsremaining = len(deps) + 1
00599         self.remake = False
00600 
00601     def resolvedeps(self, serial, cb):
00602         self.resolvecb = cb
00603         self.didanything = False
00604         if serial:
00605             self._resolvedepsserial()
00606         else:
00607             self._resolvedepsparallel()
00608 
00609     def _weakdepfinishedserial(self, error, didanything):
00610         if error:
00611             self.remake = True
00612         self._depfinishedserial(False, didanything)
00613 
00614     def _depfinishedserial(self, error, didanything):
00615         assert error in (True, False)
00616 
00617         if didanything:
00618             self.didanything = True
00619 
00620         if error:
00621             self.error = True
00622             if not self.makefile.keepgoing:
00623                 self.resolvecb(error=True, didanything=self.didanything)
00624                 return
00625         
00626         if len(self.resolvelist):
00627             dep, weak = self.resolvelist.pop(0)
00628             self.makefile.context.defer(dep.make,
00629                                         self.makefile, self.targetstack, weak and self._weakdepfinishedserial or self._depfinishedserial)
00630         else:
00631             self.resolvecb(error=self.error, didanything=self.didanything)
00632 
00633     def _resolvedepsserial(self):
00634         self.resolvelist = list(self.deps)
00635         self._depfinishedserial(False, False)
00636 
00637     def _startdepparallel(self, d):
00638         if self.makefile.error:
00639             depfinished(True, False)
00640         else:
00641             dep, weak = d
00642             dep.make(self.makefile, self.targetstack, weak and self._weakdepfinishedparallel or self._depfinishedparallel)
00643 
00644     def _weakdepfinishedparallel(self, error, didanything):
00645         if error:
00646             self.remake = True
00647         self._depfinishedparallel(False, didanything)
00648 
00649     def _depfinishedparallel(self, error, didanything):
00650         assert error in (True, False)
00651 
00652         if error:
00653             print "<%s>: Found error" % self.target.target
00654             self.error = True
00655         if didanything:
00656             self.didanything = True
00657 
00658         self.depsremaining -= 1
00659         if self.depsremaining == 0:
00660             self.resolvecb(error=self.error, didanything=self.didanything)
00661 
00662     def _resolvedepsparallel(self):
00663         self.depsremaining -= 1
00664         if self.depsremaining == 0:
00665             self.resolvecb(error=self.error, didanything=self.didanything)
00666             return
00667 
00668         self.didanything = False
00669 
00670         for d in self.deps:
00671             self.makefile.context.defer(self._startdepparallel, d)
00672 
00673     def _commandcb(self, error):
00674         assert error in (True, False)
00675 
00676         if error:
00677             self.runcb(error=True)
00678             return
00679 
00680         if len(self.commands):
00681             self.commands.pop(0)(self._commandcb)
00682         else:
00683             self.runcb(error=False)
00684 
00685     def runcommands(self, indent, cb):
00686         assert not self.running
00687         self.running = True
00688 
00689         self.runcb = cb
00690 
00691         if self.rule is None or not len(self.rule.commands):
00692             if self.target.mtime is None:
00693                 self.target.beingremade()
00694             else:
00695                 for d, weak in self.deps:
00696                     if mtimeislater(d.mtime, self.target.mtime):
00697                         self.target.beingremade()
00698                         break
00699             cb(error=False)
00700             return
00701 
00702         if self.rule.doublecolon:
00703             if len(self.deps) == 0:
00704                 if self.avoidremakeloop:
00705                     _log.info("%sNot remaking %s using rule at %s because it would introduce an infinite loop.", indent, self.target.target, self.rule.loc)
00706                     cb(error=False)
00707                     return
00708 
00709         remake = self.remake
00710         if remake:
00711             _log.info("%sRemaking %s using rule at %s: weak dependency was not found.", indent, self.target.target, self.rule.loc)
00712         else:
00713             if self.target.mtime is None:
00714                 remake = True
00715                 _log.info("%sRemaking %s using rule at %s: target doesn't exist or is a forced target", indent, self.target.target, self.rule.loc)
00716 
00717         if not remake:
00718             if self.rule.doublecolon:
00719                 if len(self.deps) == 0:
00720                     _log.info("%sRemaking %s using rule at %s because there are no prerequisites listed for a double-colon rule.", indent, self.target.target, self.rule.loc)
00721                     remake = True
00722 
00723         if not remake:
00724             for d, weak in self.deps:
00725                 if mtimeislater(d.mtime, self.target.mtime):
00726                     _log.info("%sRemaking %s using rule at %s because %s is newer.", indent, self.target.target, self.rule.loc, d.target)
00727                     remake = True
00728                     break
00729 
00730         if remake:
00731             self.target.beingremade()
00732             self.target.didanything = True
00733             try:
00734                 self.commands = [c for c in self.rule.getcommands(self.target, self.makefile)]
00735             except util.MakeError, e:
00736                 print e
00737                 sys.stdout.flush()
00738                 cb(error=True)
00739                 return
00740 
00741             self._commandcb(False)
00742         else:
00743             cb(error=False)
00744 
00745 MAKESTATE_NONE = 0
00746 MAKESTATE_FINISHED = 1
00747 MAKESTATE_WORKING = 2
00748 
00749 class Target(object):
00750     """
00751     An actual (non-pattern) target.
00752 
00753     It holds target-specific variables and a list of rules. It may also point to a parent
00754     PatternTarget, if this target is being created by an implicit rule.
00755 
00756     The rules associated with this target may be Rule instances or, in the case of static pattern
00757     rules, PatternRule instances.
00758     """
00759 
00760     wasremade = False
00761 
00762     def __init__(self, target, makefile):
00763         assert isinstance(target, str)
00764         self.target = target
00765         self.vpathtarget = None
00766         self.rules = []
00767         self.variables = Variables(makefile.variables)
00768         self.explicit = False
00769         self._state = MAKESTATE_NONE
00770 
00771     def addrule(self, rule):
00772         assert isinstance(rule, (Rule, PatternRuleInstance))
00773         if len(self.rules) and rule.doublecolon != self.rules[0].doublecolon:
00774             raise DataError("Cannot have single- and double-colon rules for the same target. Prior rule location: %s" % self.rules[0].loc, rule.loc)
00775 
00776         if isinstance(rule, PatternRuleInstance):
00777             if len(rule.prule.targetpatterns) != 1:
00778                 raise DataError("Static pattern rules must only have one target pattern", rule.prule.loc)
00779             if rule.prule.targetpatterns[0].match(self.target) is None:
00780                 raise DataError("Static pattern rule doesn't match target '%s'" % self.target, rule.loc)
00781 
00782         self.rules.append(rule)
00783 
00784     def isdoublecolon(self):
00785         return self.rules[0].doublecolon
00786 
00787     def isphony(self, makefile):
00788         """Is this a phony target? We don't check for existence of phony targets."""
00789         return makefile.gettarget('.PHONY').hasdependency(self.target)
00790 
00791     def hasdependency(self, t):
00792         for rule in self.rules:
00793             if t in rule.prerequisites:
00794                 return True
00795 
00796         return False
00797 
00798     def resolveimplicitrule(self, makefile, targetstack, rulestack):
00799         """
00800         Try to resolve an implicit rule to build this target.
00801         """
00802         # The steps in the GNU make manual Implicit-Rule-Search.html are very detailed. I hope they can be trusted.
00803 
00804         indent = getindent(targetstack)
00805 
00806         _log.info("%sSearching for implicit rule to make '%s'", indent, self.target)
00807 
00808         dir, s, file = util.strrpartition(self.target, '/')
00809         dir = dir + s
00810 
00811         candidates = [] # list of PatternRuleInstance
00812 
00813         hasmatch = util.any((r.hasspecificmatch(file) for r in makefile.implicitrules))
00814 
00815         for r in makefile.implicitrules:
00816             if r in rulestack:
00817                 _log.info("%s %s: Avoiding implicit rule recursion", indent, r.loc)
00818                 continue
00819 
00820             if not len(r.commands):
00821                 continue
00822 
00823             for ri in r.matchesfor(dir, file, hasmatch):
00824                 candidates.append(ri)
00825             
00826         newcandidates = []
00827 
00828         for r in candidates:
00829             depfailed = None
00830             for p in r.prerequisites:
00831                 t = makefile.gettarget(p)
00832                 t.resolvevpath(makefile)
00833                 if not t.explicit and t.mtime is None:
00834                     depfailed = p
00835                     break
00836 
00837             if depfailed is not None:
00838                 if r.doublecolon:
00839                     _log.info("%s Terminal rule at %s doesn't match: prerequisite '%s' not mentioned and doesn't exist.", indent, r.loc, depfailed)
00840                 else:
00841                     newcandidates.append(r)
00842                 continue
00843 
00844             _log.info("%sFound implicit rule at %s for target '%s'", indent, r.loc, self.target)
00845             self.rules.append(r)
00846             return
00847 
00848         # Try again, but this time with chaining and without terminal (double-colon) rules
00849 
00850         for r in newcandidates:
00851             newrulestack = rulestack + [r.prule]
00852 
00853             depfailed = None
00854             for p in r.prerequisites:
00855                 t = makefile.gettarget(p)
00856                 try:
00857                     t.resolvedeps(makefile, targetstack, newrulestack, True)
00858                 except ResolutionError:
00859                     depfailed = p
00860                     break
00861 
00862             if depfailed is not None:
00863                 _log.info("%s Rule at %s doesn't match: prerequisite '%s' could not be made.", indent, r.loc, depfailed)
00864                 continue
00865 
00866             _log.info("%sFound implicit rule at %s for target '%s'", indent, r.loc, self.target)
00867             self.rules.append(r)
00868             return
00869 
00870         _log.info("%sCouldn't find implicit rule to remake '%s'", indent, self.target)
00871 
00872     def ruleswithcommands(self):
00873         "The number of rules with commands"
00874         return reduce(lambda i, rule: i + (len(rule.commands) > 0), self.rules, 0)
00875 
00876     def resolvedeps(self, makefile, targetstack, rulestack, recursive):
00877         """
00878         Resolve the actual path of this target, using vpath if necessary.
00879 
00880         Recursively resolve dependencies of this target. This means finding implicit
00881         rules which match the target, if appropriate.
00882 
00883         Figure out whether this target needs to be rebuild, and set self.outofdate
00884         appropriately.
00885 
00886         @param targetstack is the current stack of dependencies being resolved. If
00887                this target is already in targetstack, bail to prevent infinite
00888                recursion.
00889         @param rulestack is the current stack of implicit rules being used to resolve
00890                dependencies. A rule chain cannot use the same implicit rule twice.
00891         """
00892         assert makefile.parsingfinished
00893 
00894         if self.target in targetstack:
00895             raise ResolutionError("Recursive dependency: %s -> %s" % (
00896                     " -> ".join(targetstack), self.target))
00897 
00898         targetstack = targetstack + [self.target]
00899         
00900         indent = getindent(targetstack)
00901 
00902         _log.info("%sConsidering target '%s'", indent, self.target)
00903 
00904         self.resolvevpath(makefile)
00905 
00906         # Sanity-check our rules. If we're single-colon, only one rule should have commands
00907         ruleswithcommands = self.ruleswithcommands()
00908         if len(self.rules) and not self.isdoublecolon():
00909             if ruleswithcommands > 1:
00910                 # In GNU make this is a warning, not an error. I'm going to be stricter.
00911                 # TODO: provide locations
00912                 raise DataError("Target '%s' has multiple rules with commands." % self.target)
00913 
00914         if ruleswithcommands == 0:
00915             self.resolveimplicitrule(makefile, targetstack, rulestack)
00916 
00917         # If a target is mentioned, but doesn't exist, has no commands and no
00918         # prerequisites, it is special and exists just to say that targets which
00919         # depend on it are always out of date. This is like .FORCE but more
00920         # compatible with other makes.
00921         # Otherwise, we don't know how to make it.
00922         if not len(self.rules) and self.mtime is None and not util.any((len(rule.prerequisites) > 0
00923                                                                         for rule in self.rules)):
00924             raise ResolutionError("No rule to make target '%s' needed by %r" % (self.target,
00925                                                                                 targetstack))
00926 
00927         if recursive:
00928             for r in self.rules:
00929                 newrulestack = rulestack + [r]
00930                 for d in r.prerequisites:
00931                     dt = makefile.gettarget(d)
00932                     if dt.explicit:
00933                         continue
00934 
00935                     dt.resolvedeps(makefile, targetstack, newrulestack, True)
00936 
00937         for v in makefile.getpatternvariablesfor(self.target):
00938             self.variables.merge(v)
00939 
00940     def resolvevpath(self, makefile):
00941         if self.vpathtarget is not None:
00942             return
00943 
00944         if self.isphony(makefile):
00945             self.vpathtarget = self.target
00946             self.mtime = None
00947             return
00948 
00949         if self.target.startswith('-l'):
00950             stem = self.target[2:]
00951             f, s, e = makefile.variables.get('.LIBPATTERNS')
00952             if e is not None:
00953                 libpatterns = [Pattern(stripdotslash(s)) for s in e.resolvesplit(makefile, makefile.variables)]
00954                 if len(libpatterns):
00955                     searchdirs = ['']
00956                     searchdirs.extend(makefile.getvpath(self.target))
00957 
00958                     for lp in libpatterns:
00959                         if not lp.ispattern():
00960                             raise DataError('.LIBPATTERNS contains a non-pattern')
00961 
00962                         libname = lp.resolve('', stem)
00963 
00964                         for dir in searchdirs:
00965                             libpath = util.normaljoin(dir, libname).replace('\\', '/')
00966                             fspath = util.normaljoin(makefile.workdir, libpath)
00967                             mtime = getmtime(fspath)
00968                             if mtime is not None:
00969                                 self.vpathtarget = libpath
00970                                 self.mtime = mtime
00971                                 return
00972 
00973                     self.vpathtarget = self.target
00974                     self.mtime = None
00975                     return
00976 
00977         search = [self.target]
00978         if not os.path.isabs(self.target):
00979             search += [util.normaljoin(dir, self.target).replace('\\', '/')
00980                        for dir in makefile.getvpath(self.target)]
00981 
00982         for t in search:
00983             fspath = util.normaljoin(makefile.workdir, t).replace('\\', '/')
00984             mtime = getmtime(fspath)
00985 #            _log.info("Searching %s ... checking %s ... mtime %r" % (t, fspath, mtime))
00986             if mtime is not None:
00987                 self.vpathtarget = t
00988                 self.mtime = mtime
00989                 return
00990 
00991         self.vpathtarget = self.target
00992         self.mtime = None
00993         
00994     def beingremade(self):
00995         """
00996         When we remake ourself, we need to reset our mtime and vpathtarget.
00997 
00998         We store our old mtime so that $? can calculate out-of-date prerequisites.
00999         """
01000         self.realmtime = self.mtime
01001         self.mtime = None
01002         self.vpathtarget = self.target
01003         self.wasremade = True
01004 
01005     def notifydone(self, makefile):
01006         assert self._state == MAKESTATE_WORKING, "State was %s" % self._state
01007 
01008         self._state = MAKESTATE_FINISHED
01009         for cb in self._callbacks:
01010             makefile.context.defer(cb, error=self.error, didanything=self.didanything)
01011         del self._callbacks 
01012 
01013     def make(self, makefile, targetstack, cb, avoidremakeloop=False, printerror=True):
01014         """
01015         If we are out of date, asynchronously make ourself. This is a multi-stage process, mostly handled
01016         by the helper objects RemakeTargetSerially, RemakeTargetParallel,
01017         RemakeRuleContext. These helper objects should keep us from developing
01018         any cyclical dependencies.
01019 
01020         * resolve dependencies (synchronous)
01021         * gather a list of rules to execute and related dependencies (synchronous)
01022         * for each rule (in parallel)
01023         ** remake dependencies (asynchronous)
01024         ** build list of commands to execute (synchronous)
01025         ** execute each command (asynchronous)
01026         * asynchronously notify when all rules are complete
01027 
01028         @param cb A callback function to notify when remaking is finished. It is called
01029                thusly: callback(error=True/False, didanything=True/False)
01030                If there is no asynchronous activity to perform, the callback may be called directly.
01031         """
01032 
01033         serial = makefile.context.jcount == 1
01034         
01035         if self._state == MAKESTATE_FINISHED:
01036             cb(error=self.error, didanything=self.didanything)
01037             return
01038             
01039         if self._state == MAKESTATE_WORKING:
01040             assert not serial
01041             self._callbacks.append(cb)
01042             return
01043 
01044         assert self._state == MAKESTATE_NONE
01045 
01046         self._state = MAKESTATE_WORKING
01047         self._callbacks = [cb]
01048         self.error = False
01049         self.didanything = False
01050 
01051         indent = getindent(targetstack)
01052 
01053         try:
01054             self.resolvedeps(makefile, targetstack, [], False)
01055         except util.MakeError, e:
01056             if printerror:
01057                 print e
01058             self.error = True
01059             self.notifydone(makefile)
01060             return
01061 
01062         assert self.vpathtarget is not None, "Target was never resolved!"
01063         if not len(self.rules):
01064             self.notifydone(makefile)
01065             return
01066 
01067         if self.isdoublecolon():
01068             rulelist = [RemakeRuleContext(self, makefile, r, [(makefile.gettarget(p), False) for p in r.prerequisites], targetstack, avoidremakeloop) for r in self.rules]
01069         else:
01070             alldeps = []
01071 
01072             commandrule = None
01073             for r in self.rules:
01074                 rdeps = [(makefile.gettarget(p), r.weakdeps) for p in r.prerequisites]
01075                 if len(r.commands):
01076                     assert commandrule is None
01077                     commandrule = r
01078                     # The dependencies of the command rule are resolved before other dependencies,
01079                     # no matter the ordering of the other no-command rules
01080                     alldeps[0:0] = rdeps
01081                 else:
01082                     alldeps.extend(rdeps)
01083 
01084             rulelist = [RemakeRuleContext(self, makefile, commandrule, alldeps, targetstack, avoidremakeloop)]
01085 
01086         targetstack = targetstack + [self.target]
01087 
01088         if serial:
01089             RemakeTargetSerially(self, makefile, indent, rulelist)
01090         else:
01091             RemakeTargetParallel(self, makefile, indent, rulelist)
01092 
01093 def dirpart(p):
01094     d, s, f = util.strrpartition(p, '/')
01095     if d == '':
01096         return '.'
01097 
01098     return d
01099 
01100 def filepart(p):
01101     d, s, f = util.strrpartition(p, '/')
01102     return f
01103 
01104 def setautomatic(v, name, plist):
01105     v.set(name, Variables.FLAVOR_SIMPLE, Variables.SOURCE_AUTOMATIC, ' '.join(plist))
01106     v.set(name + 'D', Variables.FLAVOR_SIMPLE, Variables.SOURCE_AUTOMATIC, ' '.join((dirpart(p) for p in plist)))
01107     v.set(name + 'F', Variables.FLAVOR_SIMPLE, Variables.SOURCE_AUTOMATIC, ' '.join((filepart(p) for p in plist)))
01108 
01109 def setautomaticvariables(v, makefile, target, prerequisites):
01110     prtargets = [makefile.gettarget(p) for p in prerequisites]
01111     prall = [pt.vpathtarget for pt in prtargets]
01112     proutofdate = [pt.vpathtarget for pt in withoutdups(prtargets)
01113                    if target.realmtime is None or mtimeislater(pt.mtime, target.realmtime)]
01114     
01115     setautomatic(v, '@', [target.vpathtarget])
01116     if len(prall):
01117         setautomatic(v, '<', [prall[0]])
01118 
01119     setautomatic(v, '?', proutofdate)
01120     setautomatic(v, '^', list(withoutdups(prall)))
01121     setautomatic(v, '+', prall)
01122 
01123 def splitcommand(command):
01124     """
01125     Using the esoteric rules, split command lines by unescaped newlines.
01126     """
01127     start = 0
01128     i = 0
01129     while i < len(command):
01130         c = command[i]
01131         if c == '\\':
01132             i += 1
01133         elif c == '\n':
01134             yield command[start:i]
01135             i += 1
01136             start = i
01137             continue
01138 
01139         i += 1
01140 
01141     if i > start:
01142         yield command[start:i]
01143 
01144 def findmodifiers(command):
01145     """
01146     Find any of +-@% prefixed on the command.
01147     @returns (command, isHidden, isRecursive, ignoreErrors, isNative)
01148     """
01149 
01150     isHidden = False
01151     isRecursive = False
01152     ignoreErrors = False
01153     isNative = False
01154 
01155     realcommand = command.lstrip(' \t\n@+-%')
01156     modset = set(command[:-len(realcommand)])
01157     return realcommand, '@' in modset, '+' in modset, '-' in modset, '%' in modset
01158 
01159 class _CommandWrapper(object):
01160     def __init__(self, cline, ignoreErrors, loc, context, **kwargs):
01161         self.ignoreErrors = ignoreErrors
01162         self.loc = loc
01163         self.cline = cline
01164         self.kwargs = kwargs
01165         self.context = context
01166 
01167     def _cb(self, res):
01168         if res != 0 and not self.ignoreErrors:
01169             print "%s: command '%s' failed, return code %i" % (self.loc, self.cline, res)
01170             self.usercb(error=True)
01171         else:
01172             self.usercb(error=False)
01173 
01174     def __call__(self, cb):
01175         self.usercb = cb
01176         process.call(self.cline, loc=self.loc, cb=self._cb, context=self.context, **self.kwargs)
01177 
01178 class _NativeWrapper(_CommandWrapper):
01179     def __init__(self, cline, ignoreErrors, loc, context,
01180                  pycommandpath, **kwargs):
01181         _CommandWrapper.__init__(self, cline, ignoreErrors, loc, context,
01182                                  **kwargs)
01183         # get the module and method to call
01184         parts, badchar = process.clinetoargv(cline)
01185         if parts is None:
01186             raise DataError("native command '%s': shell metacharacter '%s' in command line" % (cline, badchar), self.loc)
01187         if len(parts) < 2:
01188             raise DataError("native command '%s': no method name specified" % cline, self.loc)
01189         if pycommandpath:
01190             self.pycommandpath = re.split('[%s\s]+' % os.pathsep,
01191                                           pycommandpath)
01192         else:
01193             self.pycommandpath = None
01194         self.module = parts[0]
01195         self.method = parts[1]
01196         self.cline_list = parts[2:]
01197 
01198     def __call__(self, cb):
01199         self.usercb = cb
01200         process.call_native(self.module, self.method, self.cline_list,
01201                             loc=self.loc, cb=self._cb, context=self.context,
01202                             pycommandpath=self.pycommandpath, **self.kwargs)
01203 
01204 def getcommandsforrule(rule, target, makefile, prerequisites, stem):
01205     v = Variables(parent=target.variables)
01206     setautomaticvariables(v, makefile, target, prerequisites)
01207     if stem is not None:
01208         setautomatic(v, '*', [stem])
01209 
01210     env = makefile.getsubenvironment(v)
01211 
01212     for c in rule.commands:
01213         cstring = c.resolvestr(makefile, v)
01214         for cline in splitcommand(cstring):
01215             cline, isHidden, isRecursive, ignoreErrors, isNative = findmodifiers(cline)
01216             if (isHidden or makefile.silent) and not makefile.justprint:
01217                 echo = None
01218             else:
01219                 echo = "%s$ %s" % (c.loc, cline)
01220             if not isNative:
01221                 yield _CommandWrapper(cline, ignoreErrors=ignoreErrors, env=env, cwd=makefile.workdir, loc=c.loc, context=makefile.context,
01222                                       echo=echo, justprint=makefile.justprint)
01223             else:
01224                 f, s, e = v.get("PYCOMMANDPATH", True)
01225                 if e:
01226                     e = e.resolvestr(makefile, v, ["PYCOMMANDPATH"])
01227                 yield _NativeWrapper(cline, ignoreErrors=ignoreErrors,
01228                                      env=env, cwd=makefile.workdir,
01229                                      loc=c.loc, context=makefile.context,
01230                                      echo=echo, justprint=makefile.justprint,
01231                                      pycommandpath=e)
01232 
01233 class Rule(object):
01234     """
01235     A rule contains a list of prerequisites and a list of commands. It may also
01236     contain rule-specific variables. This rule may be associated with multiple targets.
01237     """
01238 
01239     def __init__(self, prereqs, doublecolon, loc, weakdeps):
01240         self.prerequisites = prereqs
01241         self.doublecolon = doublecolon
01242         self.commands = []
01243         self.loc = loc
01244         self.weakdeps = weakdeps
01245 
01246     def addcommand(self, c):
01247         assert isinstance(c, (Expansion, StringExpansion))
01248         self.commands.append(c)
01249 
01250     def getcommands(self, target, makefile):
01251         assert isinstance(target, Target)
01252 
01253         return getcommandsforrule(self, target, makefile, self.prerequisites, stem=None)
01254         # TODO: $* in non-pattern rules?
01255 
01256 class PatternRuleInstance(object):
01257     weakdeps = False
01258 
01259     """
01260     A pattern rule instantiated for a particular target. It has the same API as Rule, but
01261     different internals, forwarding most information on to the PatternRule.
01262     """
01263     def __init__(self, prule, dir, stem, ismatchany):
01264         assert isinstance(prule, PatternRule)
01265 
01266         self.dir = dir
01267         self.stem = stem
01268         self.prule = prule
01269         self.prerequisites = prule.prerequisitesforstem(dir, stem)
01270         self.doublecolon = prule.doublecolon
01271         self.loc = prule.loc
01272         self.ismatchany = ismatchany
01273         self.commands = prule.commands
01274 
01275     def getcommands(self, target, makefile):
01276         assert isinstance(target, Target)
01277         return getcommandsforrule(self, target, makefile, self.prerequisites, stem=self.dir + self.stem)
01278 
01279     def __str__(self):
01280         return "Pattern rule at %s with stem '%s', matchany: %s doublecolon: %s" % (self.loc,
01281                                                                                     self.dir + self.stem,
01282                                                                                     self.ismatchany,
01283                                                                                     self.doublecolon)
01284 
01285 class PatternRule(object):
01286     """
01287     An implicit rule or static pattern rule containing target patterns, prerequisite patterns,
01288     and a list of commands.
01289     """
01290 
01291     def __init__(self, targetpatterns, prerequisites, doublecolon, loc):
01292         self.targetpatterns = targetpatterns
01293         self.prerequisites = prerequisites
01294         self.doublecolon = doublecolon
01295         self.loc = loc
01296         self.commands = []
01297 
01298     def addcommand(self, c):
01299         assert isinstance(c, (Expansion, StringExpansion))
01300         self.commands.append(c)
01301 
01302     def ismatchany(self):
01303         return util.any((t.ismatchany() for t in self.targetpatterns))
01304 
01305     def hasspecificmatch(self, file):
01306         for p in self.targetpatterns:
01307             if not p.ismatchany() and p.match(file) is not None:
01308                 return True
01309 
01310         return False
01311 
01312     def matchesfor(self, dir, file, skipsinglecolonmatchany):
01313         """
01314         Determine all the target patterns of this rule that might match target t.
01315         @yields a PatternRuleInstance for each.
01316         """
01317 
01318         for p in self.targetpatterns:
01319             matchany = p.ismatchany()
01320             if matchany:
01321                 if skipsinglecolonmatchany and not self.doublecolon:
01322                     continue
01323 
01324                 yield PatternRuleInstance(self, dir, file, True)
01325             else:
01326                 stem = p.match(dir + file)
01327                 if stem is not None:
01328                     yield PatternRuleInstance(self, '', stem, False)
01329                 else:
01330                     stem = p.match(file)
01331                     if stem is not None:
01332                         yield PatternRuleInstance(self, dir, stem, False)
01333 
01334     def prerequisitesforstem(self, dir, stem):
01335         return [p.resolve(dir, stem) for p in self.prerequisites]
01336 
01337 class _RemakeContext(object):
01338     def __init__(self, makefile, cb):
01339         self.makefile = makefile
01340         self.included = [(makefile.gettarget(f), required)
01341                          for f, required in makefile.included]
01342         self.toremake = list(self.included)
01343         self.cb = cb
01344 
01345         self.remakecb(error=False, didanything=False)
01346 
01347     def remakecb(self, error, didanything):
01348         assert error in (True, False)
01349 
01350         if error and self.required:
01351             print "Error remaking makefiles (ignored)"
01352 
01353         if len(self.toremake):
01354             target, self.required = self.toremake.pop(0)
01355             target.make(self.makefile, [], avoidremakeloop=True, cb=self.remakecb, printerror=False)
01356         else:
01357             for t, required in self.included:
01358                 if t.wasremade:
01359                     _log.info("Included file %s was remade, restarting make", t.target)
01360                     self.cb(remade=True)
01361                     return
01362                 elif required and t.mtime is None:
01363                     self.cb(remade=False, error=DataError("No rule to remake missing include file %s" % t.target))
01364                     return
01365 
01366             self.cb(remade=False)
01367 
01368 class Makefile(object):
01369     """
01370     The top-level data structure for makefile execution. It holds Targets, implicit rules, and other
01371     state data.
01372     """
01373 
01374     def __init__(self, workdir=None, env=None, restarts=0, make=None,
01375                  makeflags='', makeoverrides='',
01376                  makelevel=0, context=None, targets=(), keepgoing=False,
01377                  silent=False, justprint=False):
01378         self.defaulttarget = None
01379 
01380         if env is None:
01381             env = os.environ
01382         self.env = env
01383 
01384         self.variables = Variables()
01385         self.variables.readfromenvironment(env)
01386 
01387         self.context = context
01388         self.exportedvars = {}
01389         self._targets = {}
01390         self.keepgoing = keepgoing
01391         self.silent = silent
01392         self.justprint = justprint
01393         self._patternvariables = [] # of (pattern, variables)
01394         self.implicitrules = []
01395         self.parsingfinished = False
01396 
01397         self._patternvpaths = [] # of (pattern, [dir, ...])
01398 
01399         if workdir is None:
01400             workdir = os.getcwd()
01401         workdir = os.path.realpath(workdir)
01402         self.workdir = workdir
01403         self.variables.set('CURDIR', Variables.FLAVOR_SIMPLE,
01404                            Variables.SOURCE_AUTOMATIC, workdir.replace('\\','/'))
01405 
01406         # the list of included makefiles, whether or not they existed
01407         self.included = []
01408 
01409         self.variables.set('MAKE_RESTARTS', Variables.FLAVOR_SIMPLE,
01410                            Variables.SOURCE_AUTOMATIC, restarts > 0 and str(restarts) or '')
01411 
01412         self.variables.set('.PYMAKE', Variables.FLAVOR_SIMPLE,
01413                            Variables.SOURCE_MAKEFILE, "1")
01414         if make is not None:
01415             self.variables.set('MAKE', Variables.FLAVOR_SIMPLE,
01416                                Variables.SOURCE_MAKEFILE, make)
01417 
01418         if makeoverrides != '':
01419             self.variables.set('-*-command-variables-*-', Variables.FLAVOR_SIMPLE,
01420                                Variables.SOURCE_AUTOMATIC, makeoverrides)
01421             makeflags += ' -- $(MAKEOVERRIDES)'
01422 
01423         self.variables.set('MAKEOVERRIDES', Variables.FLAVOR_RECURSIVE,
01424                            Variables.SOURCE_ENVIRONMENT,
01425                            '${-*-command-variables-*-}')
01426 
01427         self.variables.set('MAKEFLAGS', Variables.FLAVOR_RECURSIVE,
01428                            Variables.SOURCE_MAKEFILE, makeflags)
01429         self.exportedvars['MAKEFLAGS'] = True
01430 
01431         self.makelevel = makelevel
01432         self.variables.set('MAKELEVEL', Variables.FLAVOR_SIMPLE,
01433                            Variables.SOURCE_MAKEFILE, str(makelevel))
01434 
01435         self.variables.set('MAKECMDGOALS', Variables.FLAVOR_SIMPLE,
01436                            Variables.SOURCE_AUTOMATIC, ' '.join(targets))
01437 
01438         for vname, val in implicit.variables.iteritems():
01439             self.variables.set(vname,
01440                                Variables.FLAVOR_SIMPLE,
01441                                Variables.SOURCE_IMPLICIT, val)
01442 
01443     def foundtarget(self, t):
01444         """
01445         Inform the makefile of a target which is a candidate for being the default target,
01446         if there isn't already a default target.
01447         """
01448         if self.defaulttarget is None and t != '.PHONY':
01449             self.defaulttarget = t
01450 
01451     def getpatternvariables(self, pattern):
01452         assert isinstance(pattern, Pattern)
01453 
01454         for p, v in self._patternvariables:
01455             if p == pattern:
01456                 return v
01457 
01458         v = Variables()
01459         self._patternvariables.append( (pattern, v) )
01460         return v
01461 
01462     def getpatternvariablesfor(self, target):
01463         for p, v in self._patternvariables:
01464             if p.match(target):
01465                 yield v
01466 
01467     def hastarget(self, target):
01468         return target in self._targets
01469 
01470     def gettarget(self, target):
01471         assert isinstance(target, str)
01472 
01473         target = target.rstrip('/')
01474 
01475         assert target != '', "empty target?"
01476 
01477         if target.find('*') != -1 or target.find('?') != -1 or target.find('[') != -1:
01478             raise DataError("wildcards should have been expanded by the parser: '%s'" % (target,))
01479 
01480         t = self._targets.get(target, None)
01481         if t is None:
01482             t = Target(target, self)
01483             self._targets[target] = t
01484         return t
01485 
01486     def appendimplicitrule(self, rule):
01487         assert isinstance(rule, PatternRule)
01488         self.implicitrules.append(rule)
01489 
01490     def finishparsing(self):
01491         """
01492         Various activities, such as "eval", are not allowed after parsing is
01493         finished. In addition, various warnings and errors can only be issued
01494         after the parsing data model is complete. All dependency resolution
01495         and rule execution requires that parsing be finished.
01496         """
01497         self.parsingfinished = True
01498 
01499         flavor, source, value = self.variables.get('GPATH')
01500         if value is not None and value.resolvestr(self, self.variables, ['GPATH']).strip() != '':
01501             raise DataError('GPATH was set: pymake does not support GPATH semantics')
01502 
01503         flavor, source, value = self.variables.get('VPATH')
01504         if value is None:
01505             self._vpath = []
01506         else:
01507             self._vpath = filter(lambda e: e != '',
01508                                  re.split('[%s\s]+' % os.pathsep,
01509                                           value.resolvestr(self, self.variables, ['VPATH'])))
01510 
01511         targets = list(self._targets.itervalues())
01512         for t in targets:
01513             t.explicit = True
01514             for r in t.rules:
01515                 for p in r.prerequisites:
01516                     self.gettarget(p).explicit = True
01517 
01518         np = self.gettarget('.NOTPARALLEL')
01519         if len(np.rules):
01520             self.context = process.getcontext(1)
01521 
01522         self.error = False
01523 
01524     def include(self, path, required=True, weak=False, loc=None):
01525         """
01526         Include the makefile at `path`.
01527         """
01528         self.included.append((path, required))
01529         fspath = util.normaljoin(self.workdir, path)
01530         if os.path.exists(fspath):
01531             stmts = parser.parsefile(fspath)
01532             self.variables.append('MAKEFILE_LIST', Variables.SOURCE_AUTOMATIC, path, None, self)
01533             stmts.execute(self, weak=weak)
01534             self.gettarget(path).explicit = True
01535 
01536     def addvpath(self, pattern, dirs):
01537         """
01538         Add a directory to the vpath search for the given pattern.
01539         """
01540         self._patternvpaths.append((pattern, dirs))
01541 
01542     def clearvpath(self, pattern):
01543         """
01544         Clear vpaths for the given pattern.
01545         """
01546         self._patternvpaths = [(p, dirs)
01547                                for p, dirs in self._patternvpaths
01548                                if not p.match(pattern)]
01549 
01550     def clearallvpaths(self):
01551         self._patternvpaths = []
01552 
01553     def getvpath(self, target):
01554         vp = list(self._vpath)
01555         for p, dirs in self._patternvpaths:
01556             if p.match(target):
01557                 vp.extend(dirs)
01558 
01559         return withoutdups(vp)
01560 
01561     def remakemakefiles(self, cb):
01562         mlist = []
01563         for f, required in self.included:
01564             t = self.gettarget(f)
01565             t.explicit = True
01566             t.resolvevpath(self)
01567             oldmtime = t.mtime
01568 
01569             mlist.append((t, oldmtime))
01570 
01571         _RemakeContext(self, cb)
01572 
01573     def getsubenvironment(self, variables):
01574         env = dict(self.env)
01575         for vname, v in self.exportedvars.iteritems():
01576             if v:
01577                 flavor, source, val = variables.get(vname)
01578                 if val is None:
01579                     strval = ''
01580                 else:
01581                     strval = val.resolvestr(self, variables, [vname])
01582                 env[vname] = strval
01583             else:
01584                 env.pop(vname, None)
01585 
01586         makeflags = ''
01587 
01588         env['MAKELEVEL'] = str(self.makelevel + 1)
01589         return env