Merge forward paren-matching and python 2.4 compatibility. There is still one significant issue with python 2.4 compatibility and process waiting, but that will be a different patch.
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 26 Feb 2009 11:12:34 -0500
changeset 183 aeb76d986c599190a3b82f646e9d573e41d36ef7
parent 182 d0cbe562ed8e3070e9656bfe41a072d85a377539 (current diff)
parent 180 79707812e43244a0dc9af3771cea26838aaf4ba8 (diff)
child 184 6bc02006435cacef53fac7ca087d542b7c8d6e54
push id105
push userbsmedberg@mozilla.com
push dateThu, 26 Feb 2009 16:12:41 +0000
Merge forward paren-matching and python 2.4 compatibility. There is still one significant issue with python 2.4 compatibility and process waiting, but that will be a different patch.
make.py
pymake/data.py
pymake/functions.py
pymake/parser.py
pymake/parserdata.py
pymake/process.py
pymake/util.py
--- a/make.py
+++ b/make.py
@@ -1,9 +1,9 @@
-#!/usr/bin/env python
+#!/usr/bin/env python2.4
 
 """
 make.py
 
 A drop-in or mostly drop-in replacement for GNU make.
 """
 
 import sys, os
--- a/pymake/command.py
+++ b/pymake/command.py
@@ -56,17 +56,17 @@ This is free software; see the source fo
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 DEALINGS IN THE SOFTWARE."""
 
-log = logging.getLogger('pymake.execution')
+_log = logging.getLogger('pymake.execution')
 
 def main(args, env, cwd, context, cb):
     try:
         makelevel = int(env.get('MAKELEVEL', '0'))
 
         op = OptionParser()
         op.add_option('-f', '--file', '--makefile',
                       action='append',
@@ -116,25 +116,25 @@ def main(args, env, cwd, context, cb):
 
         shortflags.append('j%i' % (options.jobcount,))
 
         makeflags = ''.join(shortflags) + ' ' + ' '.join(longflags)
 
         logging.basicConfig(level=loglevel, **logkwargs)
 
         if context is not None and context.jcount > 1 and options.jobcount == 1:
-            log.debug("-j1 specified, creating new serial execution context")
+            _log.debug("-j1 specified, creating new serial execution context")
             context = process.getcontext(options.jobcount)
             subcontext = True
         elif context is None:
-            log.debug("Creating new execution context, jobcount %s" % options.jobcount)
+            _log.debug("Creating new execution context, jobcount %s", options.jobcount)
             context = process.getcontext(options.jobcount)
             subcontext = True
         else:
-            log.debug("Using parent execution context")
+            _log.debug("Using parent execution context")
             subcontext = False
 
         if options.printdir:
             print "make.py[%i]: Entering directory '%s'" % (makelevel, workdir)
             sys.stdout.flush()
 
         if len(options.makefiles) == 0:
             if os.path.exists(os.path.join(workdir, 'Makefile')):
@@ -166,18 +166,18 @@ def main(args, env, cwd, context, cb):
                                                       realtargets=realtargets, tstack=tstack, i=i+1, firsterror=firsterror)
 
                 makefile.gettarget(realtargets[i]).make(makefile, tstack, [], cb=deferredmake)
                                                                                   
 
         def remakecb(remade, restarts, makefile):
             if remade:
                 if restarts > 0:
-                    log.info("make.py[%i]: Restarting makefile parsing" % (makelevel,))
-                makefile = data.Makefile(restarts=restarts, make='%s %s' % (sys.executable, makepypath),
+                    _log.info("make.py[%i]: Restarting makefile parsing", makelevel)
+                makefile = data.Makefile(restarts=restarts, make='%s %s' % (sys.executable.replace('\\', '/'), makepypath.replace('\\', '/')),
                                          makeflags=makeflags, makelevel=makelevel, workdir=workdir,
                                          context=context, env=env)
 
                 try:
                     overrides.execute(makefile)
                     for f in options.makefiles:
                         makefile.include(f)
                     makefile.finishparsing()
@@ -191,16 +191,17 @@ def main(args, env, cwd, context, cb):
                 return
 
             if len(targets) == 0:
                 if makefile.defaulttarget is None:
                     print "No target specified and no default target found."
                     context.defer(cb, 2)
                     return
 
+                _log.info("Making default target %s", makefile.defaulttarget)
                 realtargets = [makefile.defaulttarget]
                 tstack = ['<default-target>']
             else:
                 realtargets = targets
                 tstack = ['<command-line>']
 
             deferredmake = process.makedeferrable(makecb, makefile=makefile,
                                                   realtargets=realtargets, tstack=tstack, i=1, firsterror=None)
--- a/pymake/data.py
+++ b/pymake/data.py
@@ -1,22 +1,18 @@
 """
 A representation of makefile data structures.
 """
 
 import logging, re, os
-import parserdata
-import pymake.parser
-import pymake.functions
-import pymake.process
-import pymake.util
+import parserdata, parser, functions, process, util
 
-log = logging.getLogger('pymake.data')
+_log = logging.getLogger('pymake.data')
 
-class DataError(pymake.util.MakeError):
+class DataError(util.MakeError):
     pass
 
 class ResolutionError(DataError):
     """
     Raised when dependency resolution fails, either due to recursion or to missing
     prerequisites.This is separately catchable so that implicit rule search can try things
     without having to commit.
     """
@@ -42,28 +38,24 @@ def mtimeislater(deptime, targettime):
 
 def getmtime(path):
     try:
         s = os.stat(path)
         return s.st_mtime
     except OSError:
         return None
 
-_ws = re.compile(r'\s+')
-
-def splitwords(s):
-    """Split string s into words delimited by whitespace."""
+def stripdotslash(s):
+    if s.startswith('./'):
+        return s[2:]
+    return s
 
-    words = _ws.split(s)
-    for i in (0, -1):
-        if len(words) == 0:
-            break
-        if words[i] == '':
-            del words[i]
-    return words
+def stripdotslashes(sl):
+    for s in sl:
+        yield stripdotslash(s)
 
 def getindent(stack):
     return ''.ljust(len(stack) - 1)
 
 def _if_else(c, t, f):
     if c:
         return t()
     return f()
@@ -79,18 +71,23 @@ class Expansion(object):
         self.loc = loc
 
     @staticmethod
     def fromstring(s):
         e = Expansion()
         e.append(s)
         return e
 
+    def clone(self):
+        e = Expansion()
+        e._elements = list(self._elements)
+        return e
+
     def append(self, object):
-        if not isinstance(object, (str, pymake.functions.Function)):
+        if not isinstance(object, (str, functions.Function)):
             raise DataError("Expansions can contain only strings or functions, got %s" % (type(object),))
 
         if object == '':
             return
 
         if len(self) and isinstance(object, str) and isinstance(self[-1], str):
             self[-1] += object
         else:
@@ -129,18 +126,25 @@ class Expansion(object):
         @param setting (Variable instance) the variable currently
                being set, if any. Setting variables must avoid self-referential
                loops.
         """
         assert isinstance(makefile, Makefile)
         assert isinstance(variables, Variables)
         assert isinstance(setting, list)
 
-        return ''.join( (_if_else(isinstance(i, str), lambda: i, lambda: i.resolve(makefile, variables, setting))
-                         for i in self._elements) )
+        for i in self._elements:
+            if isinstance(i, str):
+                yield i
+            else:
+                for j in i.resolve(makefile, variables, setting):
+                    yield j
+                    
+    def resolvestr(self, makefile, variables, setting=[]):
+        return ''.join(self.resolve(makefile, variables, setting))
 
     def __len__(self):
         return len(self._elements)
 
     def __getitem__(self, key):
         return self._elements[key]
 
     def __setitem__(self, key, v):
@@ -166,34 +170,34 @@ class Variables(object):
     SOURCE_COMMANDLINE = 1
     SOURCE_MAKEFILE = 2
     SOURCE_ENVIRONMENT = 3
     SOURCE_AUTOMATIC = 4
     # I have no intention of supporting builtin rules or variables that go with them
     # SOURCE_IMPLICIT = 5
 
     def __init__(self, parent=None):
-        self._map = {}
+        self._map = {} # vname -> flavor, source, valuestr, valueexp, expansionerror
         self.parent = parent
 
     def readfromenvironment(self, env):
         for k, v in env.iteritems():
             self.set(k, self.FLAVOR_SIMPLE, self.SOURCE_ENVIRONMENT, v)
 
     def get(self, name, expand=True):
         """
         Get the value of a named variable. Returns a tuple (flavor, source, value)
 
         If the variable is not present, returns (None, None, None)
 
         @param expand If true, the value will be returned as an expansion. If false,
         it will be returned as an unexpanded string.
         """
         if name in self._map:
-            flavor, source, valuestr = self._map[name]
+            flavor, source, valuestr, valueexp, expansionerror = self._map[name]
             if flavor == self.FLAVOR_APPEND:
                 if self.parent:
                     pflavor, psource, pvalue = self.parent.get(name, expand)
                 else:
                     pflavor, psource, pvalue = None, None, None
 
                 if pvalue is None:
                     flavor = self.FLAVOR_RECURSIVE
@@ -201,30 +205,37 @@ class Variables(object):
                 else:
                     if source > psource:
                         # TODO: log a warning?
                         return pflavor, psource, pvalue
 
                     if not expand:
                         return pflavor, psource, pvalue + ' ' + valuestr
 
-                    d = pymake.parser.Data.fromstring(valuestr, parserdata.Location("Expansion of variable '%s'" % (name,), 1, 0))
-                    appende, t, o = pymake.parser.parsemakesyntax(d, 0, (), pymake.parser.iterdata)
+                    if expansionerror is not None:
+                        raise expansionerror
 
+                    
+                    d = parser.Data.fromstring(valuestr, parserdata.Location("Expansion of variable '%s'" % (name,), 1, 0))
+                    appende, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
+
+                    pvalue = pvalue.clone()
                     pvalue.append(' ')
-                    pvalue.concat(appende)
+                    pvalue.concat(valueexp)
 
                     return pflavor, psource, pvalue
                     
             if not expand:
                 return flavor, source, valuestr
 
             if flavor == self.FLAVOR_RECURSIVE:
-                d = pymake.parser.Data.fromstring(valuestr, parserdata.Location("Expansion of variable '%s'" % (name,), 1, 0))
-                val, t, o = pymake.parser.parsemakesyntax(d, 0, (), pymake.parser.iterdata)
+                if expansionerror is not None:
+                    raise expansionerror
+
+                val = valueexp
             else:
                 val = Expansion.fromstring(valuestr)
 
             return flavor, source, val
 
         if self.parent is not None:
             return self.parent.get(name, expand)
 
@@ -233,49 +244,82 @@ class Variables(object):
     def set(self, name, flavor, source, value):
         assert flavor in (self.FLAVOR_RECURSIVE, self.FLAVOR_SIMPLE)
         assert source in (self.SOURCE_OVERRIDE, self.SOURCE_COMMANDLINE, self.SOURCE_MAKEFILE, self.SOURCE_ENVIRONMENT, self.SOURCE_AUTOMATIC)
         assert isinstance(value, str), "expected str, got %s" % type(value)
 
         prevflavor, prevsource, prevvalue = self.get(name)
         if prevsource is not None and source > prevsource:
             # TODO: give a location for this warning
-            log.warning("not setting variable '%s', set by higher-priority source to value '%s'" % (name, prevvalue))
+            _log.info("not setting variable '%s', set by higher-priority source to value '%s'" % (name, prevvalue))
             return
 
-        self._map[name] = (flavor, source, value)
+        if flavor == self.FLAVOR_SIMPLE:
+            valueexp = None
+            expansionerror = None
+        else:
+            try:
+                d = parser.Data.fromstring(value, parserdata.Location("Expansion of variable '%s'" % (name,), 1, 0))
+                valueexp, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
+                expansionerror = None
+            except parser.SyntaxError, e:
+                valueexp = None
+                expansionerror = e
+
+        self._map[name] = (flavor, source, value, valueexp, expansionerror)
 
     def append(self, name, source, value, variables, makefile):
         assert source in (self.SOURCE_OVERRIDE, self.SOURCE_MAKEFILE, self.SOURCE_AUTOMATIC)
         assert isinstance(value, str)
+
+        def expand():
+            try:
+                d = parser.Data.fromstring(value, parserdata.Location("Expansion of variable '%s'" % (name,), 1, 0))
+                valueexp, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
+                return valueexp, None
+            except parser.SyntaxError, e:
+                return None, e
         
-        if name in self._map:
-            prevflavor, prevsource, prevvalue = self._map[name]
-            if source > prevsource:
-                # TODO: log a warning?
-                return
+        if name not in self._map:
+            exp, err = expand()
+            self._map[name] = self.FLAVOR_APPEND, source, value, exp, err
+            return
+
+        prevflavor, prevsource, prevvalue, valueexp, err = self._map[name]
+        if source > prevsource:
+            # TODO: log a warning?
+            return
 
-            if prevflavor == self.FLAVOR_SIMPLE:
-                d = pymake.parser.Data.fromstring(value, parserdata.Location("Expansion of variable '%s'" % (name,), 1, 0))
-                e, t, o = pymake.parser.parsemakesyntax(d, 0, (), pymake.parser.iterdata)
-                val = e.resolve(makefile, variables, [name])
-            else:
-                val = value
+        if prevflavor == self.FLAVOR_SIMPLE:
+            exp, err = expand()
+            if err is not None:
+                raise err
+
+            val = exp.resolvestr(makefile, variables, [name])
+            self._map[name] = prevflavor, prevsource, prevvalue + ' ' + val, None, None
+            return
 
-            self._map[name] = prevflavor, prevsource, prevvalue + ' ' + val
-        else:
-            self._map[name] = self.FLAVOR_APPEND, source, value
+        newvalue = prevvalue + ' ' + value
+        try:
+            d = parser.Data.fromstring(newvalue, parserdata.Location("Expansion of variable '%s'" % (name,), 1, 0))
+            valueexp, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
+            err = None
+        except parser.SyntaxError, e:
+            valueexp = None
+            err = e
+
+        self._map[name] = prevflavor, prevsource, newvalue, valueexp, err
 
     def merge(self, other):
         assert isinstance(other, Variables)
         for k, flavor, source, value in other:
             self.set(k, flavor, source, value)
 
     def __iter__(self):
-        for k, (flavor, source, value) in self._map.iteritems():
+        for k, (flavor, source, value, valueexp, expansionerr) in self._map.iteritems():
             yield k, flavor, source, value
 
     def __contains__(self, item):
         return item in self._map
 
 class Pattern(object):
     """
     A pattern is a string, possibly with a % substitution character. From the GNU make manual:
@@ -449,33 +493,31 @@ class Target(object):
     def resolveimplicitrule(self, makefile, targetstack, rulestack):
         """
         Try to resolve an implicit rule to build this target.
         """
         # The steps in the GNU make manual Implicit-Rule-Search.html are very detailed. I hope they can be trusted.
 
         indent = getindent(targetstack)
 
-        log.info(indent + "Trying to find implicit rule to make '%s'" % (self.target,))
+        _log.info("%sSearching for implicit rule to make '%s'", indent, self.target)
 
-        dir, s, file = pymake.util.strrpartition(self.target, '/')
+        dir, s, file = util.strrpartition(self.target, '/')
         dir = dir + s
 
         candidates = [] # list of PatternRuleInstance
 
-        hasmatch = pymake.util.any((r.hasspecificmatch(file) for r in makefile.implicitrules))
-        log.debug("Does any implicit rule match '%s'? %s" % (self.target, hasmatch))
+        hasmatch = util.any((r.hasspecificmatch(file) for r in makefile.implicitrules))
 
         for r in makefile.implicitrules:
             if r in rulestack:
-                log.info(indent + " %s: Avoiding implicit rule recursion" % (r.loc,))
+                _log.info("%s %s: Avoiding implicit rule recursion", indent, r.loc)
                 continue
 
             if not len(r.commands):
-                log.info(indent + " %s: Has no commands" % (r.loc,))
                 continue
 
             for ri in r.matchesfor(dir, file, hasmatch):
                 candidates.append(ri)
             
         newcandidates = []
 
         for r in candidates:
@@ -484,22 +526,22 @@ class Target(object):
                 t = makefile.gettarget(p)
                 t.resolvevpath(makefile)
                 if not t.explicit and t.mtime is None:
                     depfailed = p
                     break
 
             if depfailed is not None:
                 if r.doublecolon:
-                    log.info(indent + " Terminal rule at %s doesn't match: prerequisite '%s' not mentioned and doesn't exist." % (r.loc, depfailed))
+                    _log.info("%s Terminal rule at %s doesn't match: prerequisite '%s' not mentioned and doesn't exist.", indent, r.loc, depfailed)
                 else:
                     newcandidates.append(r)
                 continue
 
-            log.info(indent + "Found implicit rule at %s for target '%s'" % (r.loc, self.target))
+            _log.info("%sFound implicit rule at %s for target '%s'", indent, r.loc, self.target)
             self.rules.append(r)
             return
 
         # Try again, but this time with chaining and without terminal (double-colon) rules
 
         for r in newcandidates:
             newrulestack = rulestack + [r.prule]
 
@@ -508,24 +550,24 @@ class Target(object):
                 t = makefile.gettarget(p)
                 try:
                     t.resolvedeps(makefile, targetstack, newrulestack, True)
                 except ResolutionError:
                     depfailed = p
                     break
 
             if depfailed is not None:
-                log.info(indent + " Rule at %s doesn't match: prerequisite '%s' could not be made." % (r.loc, depfailed))
+                _log.info("%s Rule at %s doesn't match: prerequisite '%s' could not be made.", indent, r.loc, depfailed)
                 continue
 
-            log.info(indent + "Found implicit rule at %s for target '%s'" % (r.loc, self.target))
+            _log.info("%sFound implicit rule at %s for target '%s'", indent, r.loc, self.target)
             self.rules.append(r)
             return
 
-        log.info(indent + "Couldn't find implicit rule to remake '%s'" % (self.target,))
+        _log.info("%sCouldn't find implicit rule to remake '%s'", indent, self.target)
 
     def ruleswithcommands(self):
         "The number of rules with commands"
         return reduce(lambda i, rule: i + (len(rule.commands) > 0), self.rules, 0)
 
     def resolvedeps(self, makefile, targetstack, rulestack, required):
         """
         Resolve the actual path of this target, using vpath if necessary.
@@ -547,17 +589,17 @@ class Target(object):
         if self.target in targetstack:
             raise ResolutionError("Recursive dependency: %s -> %s" % (
                     " -> ".join(targetstack), self.target))
 
         targetstack = targetstack + [self.target]
         
         indent = getindent(targetstack)
 
-        log.info(indent + "Considering target '%s'" % (self.target,))
+        _log.info("%sConsidering target '%s'", indent, self.target)
 
         self.resolvevpath(makefile)
 
         # Sanity-check our rules. If we're single-colon, only one rule should have commands
         ruleswithcommands = self.ruleswithcommands()
         if len(self.rules) and not self.isdoublecolon():
             if ruleswithcommands > 1:
                 # In GNU make this is a warning, not an error. I'm going to be stricter.
@@ -567,24 +609,23 @@ class Target(object):
         if ruleswithcommands == 0:
             self.resolveimplicitrule(makefile, targetstack, rulestack)
 
         # If a target is mentioned, but doesn't exist, has no commands and no
         # prerequisites, it is special and exists just to say that targets which
         # depend on it are always out of date. This is like .FORCE but more
         # compatible with other makes.
         # Otherwise, we don't know how to make it.
-        if not len(self.rules) and self.mtime is None and not pymake.util.any((len(rule.prerequisites) > 0
-                                                                               for rule in self.rules)):
+        if not len(self.rules) and self.mtime is None and not util.any((len(rule.prerequisites) > 0
+                                                                        for rule in self.rules)):
             if required:
                 raise ResolutionError("No rule to make target '%s' needed by %r" % (self.target,
                                                                                     targetstack))
 
         for r in self.rules:
-            log.info(indent + "Will remake target '%s' using rule at %s" % (self.target, r.loc))
             newrulestack = rulestack + [r]
             for d in r.prerequisites:
                 dt = makefile.gettarget(d)
                 if dt.explicit:
                     continue
 
                 dt.resolvedeps(makefile, targetstack, newrulestack, True)
 
@@ -599,47 +640,47 @@ class Target(object):
             self.vpathtarget = self.target
             self.mtime = None
             return
 
         if self.target.startswith('-l'):
             stem = self.target[2:]
             f, s, e = makefile.variables.get('.LIBPATTERNS')
             if e is not None:
-                libpatterns = map(Pattern, splitwords(e.resolve(makefile, makefile.variables)))
+                libpatterns = [Pattern(stripdotslash(s)) for s in e.resolvestr(makefile, makefile.variables).split()]
                 if len(libpatterns):
                     searchdirs = ['']
                     searchdirs.extend(makefile.getvpath(self.target))
 
                     for lp in libpatterns:
                         if not lp.ispattern():
                             raise DataError('.LIBPATTERNS contains a non-pattern')
 
                         libname = lp.resolve('', stem)
 
                         for dir in searchdirs:
-                            libpath = os.path.join(dir, libname)
+                            libpath = os.path.join(dir, libname).replace('\\', '/')
                             fspath = os.path.join(makefile.workdir, libpath)
                             mtime = getmtime(fspath)
                             if mtime is not None:
                                 self.vpathtarget = libpath
                                 self.mtime = mtime
                                 return
 
                     self.vpathtarget = self.target
                     self.mtime = None
                     return
 
         search = [self.target]
         if not os.path.isabs(self.target):
-            search += [os.path.join(dir, self.target)
+            search += [os.path.join(dir, self.target).replace('\\', '/')
                        for dir in makefile.getvpath(self.target)]
 
         for t in search:
-            fspath = os.path.join(makefile.workdir, t)
+            fspath = os.path.join(makefile.workdir, t).replace('\\', '/')
             mtime = getmtime(fspath)
             if mtime is not None:
                 self.vpathtarget = t
                 self.mtime = mtime
                 return
 
         self.vpathtarget = self.target
         self.mtime = None
@@ -650,31 +691,27 @@ class Target(object):
 
         We store our old mtime so that $? can calculate out-of-date prerequisites.
         """
         self.realmtime = self.mtime
         self.mtime = None
         self.vpathtarget = self.target
 
     def _notifyerror(self, makefile, e):
-        log.debug("Making target '%s' failed with error %s" % (self.target, e))
-
         if self._state == MAKESTATE_FINISHED:
             # multiple callbacks failed. The first one already finished us, so we ignore this one
             return
 
         self._state = MAKESTATE_FINISHED
         self._makeerror = e
         for cb in self._callbacks:
             makefile.context.defer(cb, error=e, didanything=None)
         del self._callbacks 
 
     def _notifysuccess(self, makefile, didanything):
-        log.debug("Making target '%s' succeeded" % (self.target,))
-
         self._state = MAKESTATE_FINISHED
         self._makeerror = None
         self._didanything = didanything
 
         for cb in self._callbacks:
             makefile.context.defer(cb, error=None, didanything=didanything)
 
         del self._callbacks
@@ -691,57 +728,53 @@ class Target(object):
         * execute each command (asynchronous, makeself.commandcb)
 
         @param cb A callback function to notify when remaking is finished. It is called
                thusly: callback(error=exception/None, didanything=True/False/None)
                If there is no asynchronous activity to perform, the callback may be called directly.
         """
         if self._state == MAKESTATE_FINISHED:
             if self._makeerror is not None:
-                log.debug("Already made target '%s', got error %s" % (self.target, self._makeerror))
-                cb(error=self._makeerror)
+                cb(error=self._makeerror, didanything=False) #XXX?
             else:
-                log.debug("Already made target '%s'" % (self.target,))
                 cb(error=None, didanything=self._didanything)
             return
             
         if self._state == MAKESTATE_WORKING:
-            log.debug("Already making target '%s', adding callback. targetstack %r" % (self.target, targetstack))
             self._callbacks.append(cb)
             return
 
         assert self._state == MAKESTATE_NONE
-        log.debug("Starting to make target '%s', targetstack %r" % (self.target, targetstack))
 
         self._state = MAKESTATE_WORKING
         self._callbacks = [cb]
 
         indent = getindent(targetstack)
 
         # this object exists solely as a container to subvert python's read-only closures
-        o = pymake.util.makeobject(('unmadedeps', 'didanything', 'error'))
+        o = util.makeobject(('unmadedeps', 'didanything', 'error'))
 
         def iterdeps():
             for r, deps in _resolvedrules:
                 for d in deps:
                     yield d
 
         def startdep():
             try:
                 d = depiterator.next()
             except StopIteration:
                 notifyfinished()
                 return
 
             if o.error is not None:
                 notifyfinished()
-
-            o.unmadedeps += 1
-            d.make(makefile, targetstack, [], cb=depfinished)
-            makefile.context.defer(startdep)
+            else:
+                o.unmadedeps += 1
+                d.make(makefile, targetstack, [], cb=depfinished)
+                makefile.context.defer(startdep)
 
         def notifyfinished():
             o.unmadedeps -= 1
             if o.unmadedeps == 0:
                 if o.error:
                     self._notifyerror(makefile, o.error)
                 else:
                     makeself()
@@ -765,49 +798,49 @@ class Target(object):
             commands = []
             if len(self.rules) == 0:
                 pass
             elif self.isdoublecolon():
                 for r, deps in _resolvedrules:
                     remake = False
                     if len(deps) == 0:
                         if avoidremakeloop:
-                            log.info(indent + "Not remaking %s using rule at %s because it would introduce an infinite loop." % (self.target, r.loc))
+                            _log.info("%sNot remaking %s using rule at %s because it would introduce an infinite loop.", indent, self.target, r.loc)
                         else:
-                            log.info(indent + "Remaking %s using rule at %s because there are no prerequisites listed for a double-colon rule." % (self.target, r.loc))
+                            _log.info("%sRemaking %s using rule at %s because there are no prerequisites listed for a double-colon rule.", indent, self.target, r.loc)
                             remake = True
                     else:
                         if self.mtime is None:
-                            log.info(indent + "Remaking %s using rule at %s because it doesn't exist or is a forced target" % (self.target, r.loc))
+                            _log.info("%sRemaking %s using rule at %s because it doesn't exist or is a forced target", indent, self.target, r.loc)
                             remake = True
                         else:
                             for d in deps:
                                 if mtimeislater(d.mtime, self.mtime):
-                                    log.info(indent + "Remaking %s using rule at %s because %s is newer." % (self.target, r.loc, d.target))
+                                    _log.info("%sRemaking %s using rule at %s because %s is newer.", indent, self.target, r.loc, d.target)
                                     remake = True
                                     break
                     if remake:
                         self._beingremade()
                         commands.extend(r.getcommands(self, makefile))
             else:
                 commandrule = None
                 remake = False
                 if self.mtime is None:
-                    log.info(indent + "Remaking %s because it doesn't exist or is a forced target" % (self.target,))
+                    _log.info("%sRemaking %s because it doesn't exist or is a forced target", indent, self.target)
                     remake = True
 
                 for r, deps in _resolvedrules:
                     if len(r.commands):
                         assert commandrule is None, "Two command rules for a single-colon target?"
                         commandrule = r
 
                     if not remake:
                         for d in deps:
                             if mtimeislater(d.mtime, self.mtime):
-                                log.info(indent + "Remaking %s because %s is newer" % (self.target, d.target))
+                                _log.info("%sRemaking %s because %s is newer", indent, self.target, d.target)
                                 remake = True
 
                 if remake:
                     self._beingremade()
                     if commandrule is not None:
                         commands.extend(commandrule.getcommands(self, makefile))
 
             def commandcb(error):
@@ -822,39 +855,38 @@ class Target(object):
 
             commandcb(None)
                     
         try:
             self.resolvedeps(makefile, targetstack, rulestack, required)
             assert self.vpathtarget is not None, "Target was never resolved!"
 
             _resolvedrules = [(r, [makefile.gettarget(p) for p in r.prerequisites]) for r in self.rules]
-            log.debug("resolvedrules for %r: %r" % (self.target, _resolvedrules))
 
             targetstack = targetstack + [self.target]
 
             o.didanything = False
             o.unmadedeps = 1
             o.error = None
 
             depiterator = iterdeps()
             startdep()
 
-        except pymake.util.MakeError, e:
+        except util.MakeError, e:
             self._notifyerror(makefile, e)
 
 def dirpart(p):
-    d, s, f = pymake.util.strrpartition(p, '/')
+    d, s, f = util.strrpartition(p, '/')
     if d == '':
         return '.'
 
     return d
 
 def filepart(p):
-    d, s, f = pymake.util.strrpartition(p, '/')
+    d, s, f = util.strrpartition(p, '/')
     return f
 
 def setautomatic(v, name, plist):
     v.set(name, Variables.FLAVOR_SIMPLE, Variables.SOURCE_AUTOMATIC, ' '.join(plist))
     v.set(name + 'D', Variables.FLAVOR_SIMPLE, Variables.SOURCE_AUTOMATIC, ' '.join((dirpart(p) for p in plist)))
     v.set(name + 'F', Variables.FLAVOR_SIMPLE, Variables.SOURCE_AUTOMATIC, ' '.join((filepart(p) for p in plist)))
 
 def setautomaticvariables(v, makefile, target, prerequisites):
@@ -897,33 +929,19 @@ def findmodifiers(command):
     Find any of +-@ prefixed on the command.
     @returns (command, isHidden, isRecursive, ignoreErrors)
     """
 
     isHidden = False
     isRecursive = False
     ignoreErrors = False
 
-    while len(command):
-        c = command[0]
-        if c == '@' and not isHidden:
-            command = command[1:]
-            isHidden = True
-        elif c == '+' and not isRecursive:
-            command = command[1:]
-            isRecursive = True
-        elif c == '-' and not ignoreErrors:
-            command = command[1:]
-            ignoreErrors = True
-        elif c.isspace():
-            command = command[1:]
-        else:
-            break
-
-    return command, isHidden, isRecursive, ignoreErrors
+    realcommand = command.lstrip(' \t\n@+-')
+    modset = set(command[:-len(realcommand)])
+    return realcommand, '@' in modset, '+' in modset, '-' in modset
 
 class CommandWrapper(object):
     def __init__(self, cline, ignoreErrors, loc, context, **kwargs):
         self.ignoreErrors = ignoreErrors
         self.loc = loc
         self.cline = cline
         self.kwargs = kwargs
         self.context = context
@@ -931,28 +949,28 @@ class CommandWrapper(object):
     def _cb(self, res):
         if res != 0 and not self.ignoreErrors:
             self.usercb(error=DataError("command '%s' failed, return code %s" % (self.cline, res), self.loc))
         else:
             self.usercb(error=None)
 
     def __call__(self, cb):
         self.usercb = cb
-        pymake.process.call(self.cline, loc=self.loc, cb=self._cb, context=self.context, **self.kwargs)
+        process.call(self.cline, loc=self.loc, cb=self._cb, context=self.context, **self.kwargs)
 
 def getcommandsforrule(rule, target, makefile, prerequisites, stem):
     v = Variables(parent=target.variables)
     setautomaticvariables(v, makefile, target, prerequisites)
     if stem is not None:
         setautomatic(v, '*', [stem])
 
     env = makefile.getsubenvironment(v)
 
     for c in rule.commands:
-        cstring = c.resolve(makefile, v)
+        cstring = c.resolvestr(makefile, v)
         for cline in splitcommand(cstring):
             cline, isHidden, isRecursive, ignoreErrors = findmodifiers(cline)
             if isHidden:
                 echo = None
             else:
                 echo = "%s$ %s" % (c.loc, cline)
             yield CommandWrapper(cline, ignoreErrors=ignoreErrors, env=env, cwd=makefile.workdir, loc=c.loc, context=makefile.context,
                                  echo=echo)
@@ -1019,17 +1037,17 @@ class PatternRule(object):
         self.loc = loc
         self.commands = []
 
     def addcommand(self, c):
         assert isinstance(c, Expansion)
         self.commands.append(c)
 
     def ismatchany(self):
-        return pymake.util.any((t.ismatchany() for t in self.targetpatterns))
+        return util.any((t.ismatchany() for t in self.targetpatterns))
 
     def hasspecificmatch(self, file):
         for p in self.targetpatterns:
             if not p.ismatchany() and p.match(file) is not None:
                 return True
 
         return False
 
@@ -1079,17 +1097,17 @@ class Makefile(object):
 
         self._patternvpaths = [] # of (pattern, [dir, ...])
 
         if workdir is None:
             workdir = os.getcwd()
         workdir = os.path.realpath(workdir)
         self.workdir = workdir
         self.variables.set('CURDIR', Variables.FLAVOR_SIMPLE,
-                           Variables.SOURCE_AUTOMATIC, workdir)
+                           Variables.SOURCE_AUTOMATIC, workdir.replace('\\','/'))
 
         # the list of included makefiles, whether or not they existed
         self.included = []
 
         self.variables.set('MAKE_RESTARTS', Variables.FLAVOR_SIMPLE,
                            Variables.SOURCE_AUTOMATIC, restarts > 0 and str(restarts) or '')
 
         if make is not None:
@@ -1159,81 +1177,77 @@ class Makefile(object):
         Various activities, such as "eval", are not allowed after parsing is
         finished. In addition, various warnings and errors can only be issued
         after the parsing data model is complete. All dependency resolution
         and rule execution requires that parsing be finished.
         """
         self.parsingfinished = True
 
         flavor, source, value = self.variables.get('GPATH')
-        if value is not None and value.resolve(self, self.variables, ['GPATH']).strip() != '':
+        if value is not None and value.resolvestr(self, self.variables, ['GPATH']).strip() != '':
             raise DataError('GPATH was set: pymake does not support GPATH semantics')
 
         flavor, source, value = self.variables.get('VPATH')
         if value is None:
             self._vpath = []
         else:
-            self._vpath = filter(lambda e: e != '', re.split('[:\s]+', value.resolve(self, self.variables, ['VPATH'])))
+            self._vpath = filter(lambda e: e != '', re.split('[:\s]+', value.resolvestr(self, self.variables, ['VPATH'])))
 
         targets = list(self._targets.itervalues())
         for t in targets:
             t.explicit = True
             for r in t.rules:
                 for p in r.prerequisites:
                     self.gettarget(p).explicit = True
 
     def include(self, path, required=True, loc=None):
         """
         Include the makefile at `path`.
         """
         self.included.append(path)
 
         fspath = os.path.join(self.workdir, path)
         if os.path.exists(fspath):
-            stmts = pymake.parser.parsefile(fspath)
+            stmts = parser.parsefile(fspath)
             self.variables.append('MAKEFILE_LIST', Variables.SOURCE_AUTOMATIC, path, None, self)
             stmts.execute(self)
             self.gettarget(path).explicit = True
         elif required:
             raise DataError("Attempting to include file '%s' which doesn't exist." % (path,), loc)
 
     def addvpath(self, pattern, dirs):
         """
         Add a directory to the vpath search for the given pattern.
         """
-        log.info("Adding vpath directive: pattern '%s' directories %r" % (pattern, dirs))
         self._patternvpaths.append((pattern, dirs))
 
     def clearvpath(self, pattern):
         """
         Clear vpaths for the given pattern.
         """
-        log.info("Clearing vpath directives for pattern '%s'" % (pattern,))
-
         self._patternvpaths = [(p, dirs)
                                for p, dirs in self._patternvpaths
                                if not p.match(pattern)]
 
     def clearallvpaths(self):
-        log.info("Clearing all vpath directives")
         self._patternvpaths = []
 
     def getvpath(self, target):
         vp = list(self._vpath)
         for p, dirs in self._patternvpaths:
             if p.match(target):
                 vp.extend(dirs)
 
         return withoutdups(vp)
 
     def remakemakefiles(self, cb):
         reparse = False
 
-        o = pymake.util.makeobject(('remadecount',),
-                                   remadecount = 0)
+        o = util.makeobject(('remadecount',),
+                            remadecount = 0)
 
         def remakecb(error, didanything):
             if error is not None:
                 print "Error remaking makefiles (ignored): ", error
 
             o.remadecount += 1
             if o.remadecount == len(self.included):
                 assert len(mlist) == len(self.included)
@@ -1258,24 +1272,24 @@ class Makefile(object):
 
     def getsubenvironment(self, variables):
         env = dict(self.env)
         for vname in self.exportedvars:
             flavor, source, val = variables.get(vname)
             if val is None:
                 strval = ''
             else:
-                strval = val.resolve(self, variables, [vname])
+                strval = val.resolvestr(self, variables, [vname])
             env[vname] = strval
 
         makeflags = ''
 
         flavor, source, val = variables.get('MAKEFLAGS')
         if val is not None:
-            flagsval = val.resolve(self, variables, ['MAKEFLAGS'])
+            flagsval = val.resolvestr(self, variables, ['MAKEFLAGS'])
             if flagsval != '':
                 makeflags = flagsval
 
         makeflags += ' -- '
         makeflags += ' '.join((self.flagescape.sub(r'\\\1', o) for o in self.overrides))
 
         env['MAKEFLAGS'] = makeflags
 
--- a/pymake/functions.py
+++ b/pymake/functions.py
@@ -14,17 +14,17 @@ class Function(object):
     An object that represents a function call. This class is always subclassed
     with the following methods and attributes:
 
     minargs = minimum # of arguments
     maxargs = maximum # of arguments (0 means unlimited)
 
     def resolve(self, makefile, variables, setting)
         Calls the function
-        @returns string
+        @yields strings
     """
     def __init__(self, loc):
         self._arguments = []
         self.loc = loc
         assert self.minargs > 0
 
     def __getitem__(self, key):
         return self._arguments[key]
@@ -49,215 +49,215 @@ class VariableRef(Function):
         self.loc = loc
         assert isinstance(vname, data.Expansion)
         self.vname = vname
         
     def setup(self):
         assert False, "Shouldn't get here"
 
     def resolve(self, makefile, variables, setting):
-        vname = self.vname.resolve(makefile, variables, setting)
+        vname = self.vname.resolvestr(makefile, variables, setting)
         if vname in setting:
             raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
 
         flavor, source, value = variables.get(vname)
         if value is None:
             log.debug("%s: variable '%s' was not set" % (self.loc, vname))
-            return ''
+            return
 
-        return value.resolve(makefile, variables, setting + [vname])
+        for j in value.resolve(makefile, variables, setting + [vname]):
+            yield j
 
 class SubstitutionRef(Function):
     """$(VARNAME:.c=.o) and $(VARNAME:%.c=%.o)"""
     def __init__(self, loc, varname, substfrom, substto):
         self.loc = loc
         self.vname = varname
         self.substfrom = substfrom
         self.substto = substto
 
     def setup(self):
         assert False, "Shouldn't get here"
 
     def resolve(self, makefile, variables, setting):
-        vname = self.vname.resolve(makefile, variables, setting)
+        vname = self.vname.resolvestr(makefile, variables, setting)
         if vname in setting:
             raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
 
-        substfrom = self.substfrom.resolve(makefile, variables, setting)
-        substto = self.substto.resolve(makefile, variables, setting)
+        substfrom = self.substfrom.resolvestr(makefile, variables, setting)
+        substto = self.substto.resolvestr(makefile, variables, setting)
 
         flavor, source, value = variables.get(vname)
         if value is None:
             log.debug("%s: variable '%s' was not set" % (self.loc, vname))
-            return ''
+            return
 
-        evalue = value.resolve(makefile, variables, setting + [vname])
-        words = data.splitwords(evalue)
+        evalue = value.resolvestr(makefile, variables, setting + [vname])
 
         f = data.Pattern(substfrom)
         if not f.ispattern():
             f = data.Pattern('%' + substfrom)
             substto = '%' + substto
 
-        return " ".join((f.subst(substto, word, False)
-                         for word in words))
+        yield " ".join((f.subst(substto, word, False)
+                        for word in evalue.split()))
 
 class SubstFunction(Function):
     name = 'subst'
     minargs = 3
     maxargs = 3
 
     def resolve(self, makefile, variables, setting):
-        s = self._arguments[0].resolve(makefile, variables, setting)
-        r = self._arguments[1].resolve(makefile, variables, setting)
-        d = self._arguments[2].resolve(makefile, variables, setting)
-        return d.replace(s, r)
+        s = self._arguments[0].resolvestr(makefile, variables, setting)
+        r = self._arguments[1].resolvestr(makefile, variables, setting)
+        d = self._arguments[2].resolvestr(makefile, variables, setting)
+        yield d.replace(s, r)
 
 class PatSubstFunction(Function):
     name = 'patsubst'
     minargs = 3
     maxargs = 3
 
     def resolve(self, makefile, variables, setting):
-        s = self._arguments[0].resolve(makefile, variables, setting)
-        r = self._arguments[1].resolve(makefile, variables, setting)
-        d = self._arguments[2].resolve(makefile, variables, setting)
+        s = self._arguments[0].resolvestr(makefile, variables, setting)
+        r = self._arguments[1].resolvestr(makefile, variables, setting)
+        d = self._arguments[2].resolvestr(makefile, variables, setting)
 
         p = data.Pattern(s)
-        return ' '.join((p.subst(r, word, False)
-                         for word in data.splitwords(d)))
+        yield ' '.join((p.subst(r, word, False)
+                        for word in d.split()))
 
 class StripFunction(Function):
     name = 'strip'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join(data.splitwords(self._arguments[0].resolve(makefile, variables, setting)))
+        yield ' '.join(self._arguments[0].resolvestr(makefile, variables, setting).split())
 
 class FindstringFunction(Function):
     name = 'findstring'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        s = self._arguments[0].resolve(makefile, variables, setting)
-        r = self._arguments[1].resolve(makefile, variables, setting)
+        s = self._arguments[0].resolvestr(makefile, variables, setting)
+        r = self._arguments[1].resolvestr(makefile, variables, setting)
         if r.find(s) == -1:
-            return ''
-        return s
+            return
+        yield s
 
 class FilterFunction(Function):
     name = 'filter'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        ps = self._arguments[0].resolve(makefile, variables, setting)
-        d = self._arguments[1].resolve(makefile, variables, setting)
-        plist = [data.Pattern(p) for p in data.splitwords(ps)]
+        ps = self._arguments[0].resolvestr(makefile, variables, setting)
+        d = self._arguments[1].resolvestr(makefile, variables, setting)
+        plist = [data.Pattern(p) for p in ps.split()]
         r = []
-        for w in data.splitwords(d):
+        for w in d.split():
             if util.any((p.match(w) for p in plist)):
                 r.append(w)
                 
-        return ' '.join(r)
+        yield ' '.join(r)
 
 class FilteroutFunction(Function):
     name = 'filter-out'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        ps = self._arguments[0].resolve(makefile, variables, setting)
-        d = self._arguments[1].resolve(makefile, variables, setting)
-        plist = [data.Pattern(p) for p in data.splitwords(ps)]
+        ps = self._arguments[0].resolvestr(makefile, variables, setting)
+        d = self._arguments[1].resolvestr(makefile, variables, setting)
+        plist = [data.Pattern(p) for p in ps.split()]
         r = []
-        for w in data.splitwords(d):
+        for w in d.split():
             found = False
             if not util.any((p.match(w) for p in plist)):
                 r.append(w)
 
-        return ' '.join(r)
+        yield ' '.join(r)
 
 class SortFunction(Function):
     name = 'sort'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        d = self._arguments[0].resolve(makefile, variables, setting)
-        w = data.splitwords(d)
+        d = self._arguments[0].resolvestr(makefile, variables, setting)
+        w = d.split()
         w.sort()
-        return ' '.join((w for w in data.withoutdups(w)))
+        yield ' '.join((w for w in data.withoutdups(w)))
 
 class WordFunction(Function):
     name = 'word'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        n = self._arguments[0].resolve(makefile, variables, setting)
+        n = self._arguments[0].resolvestr(makefile, variables, setting)
         # TODO: provide better error if this doesn't convert
         n = int(n)
-        words = data.splitwords(self._arguments[1].resolve(makefile, variables, setting))
+        words = self._arguments[1].resolvestr(makefile, variables, setting).split()
         if n < 1 or n > len(words):
-            return ''
-        return words[n - 1]
+            return
+        yield words[n - 1]
 
 class WordlistFunction(Function):
     name = 'wordlist'
     minargs = 3
     maxargs = 3
 
     def resolve(self, makefile, variables, setting):
-        nfrom = self._arguments[0].resolve(makefile, variables, setting)
-        nto = self._arguments[1].resolve(makefile, variables, setting)
+        nfrom = self._arguments[0].resolvestr(makefile, variables, setting)
+        nto = self._arguments[1].resolvestr(makefile, variables, setting)
         # TODO: provide better errors if this doesn't convert
         nfrom = int(nfrom)
         nto = int(nto)
 
-        words = data.splitwords(self._arguments[2].resolve(makefile, variables, setting))
+        words = self._arguments[2].resolvestr(makefile, variables, setting).split()
 
         if nfrom < 1:
             nfrom = 1
         if nto < 1:
             nto = 1
 
-        return ' '.join(words[nfrom - 1:nto])
+        yield ' '.join(words[nfrom - 1:nto])
 
 class WordsFunction(Function):
     name = 'words'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        return str(len(data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
+        yield str(len(self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class FirstWordFunction(Function):
     name = 'firstword'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        wl = data.splitwords(self._arguments[0].resolve(makefile, variables, setting))
+        wl = self._arguments[0].resolvestr(makefile, variables, setting).split()
         if len(wl) == 0:
-            return ''
-        return wl[0]
+            return
+        yield wl[0]
 
 class LastWordFunction(Function):
     name = 'lastword'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        wl = data.splitwords(self._arguments[0].resolve(makefile, variables, setting))
+        wl = self._arguments[0].resolvestr(makefile, variables, setting).split()
         if len(wl) == 0:
-            return ''
-        return wl[0]
+            return
+        yield wl[0]
 
 def pathsplit(path, default='./'):
     """
     Splits a path into dirpart, filepart on the last slash. If there is no slash, dirpart
     is ./
     """
     dir, slash, file = util.strrpartition(path, '/')
     if dir == '':
@@ -266,43 +266,43 @@ def pathsplit(path, default='./'):
     return dir + slash, file
 
 class DirFunction(Function):
     name = 'dir'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join((pathsplit(path)[0]
-                         for path in data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
+        yield ' '.join((pathsplit(path)[0]
+                        for path in self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class NotDirFunction(Function):
     name = 'notdir'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join((pathsplit(path)[1]
-                         for path in data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
+        yield ' '.join((pathsplit(path)[1]
+                        for path in self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class SuffixFunction(Function):
     name = 'suffix'
     minargs = 1
     maxargs = 1
 
     @staticmethod
     def suffixes(words):
         for w in words:
             dir, file = pathsplit(w)
             base, dot, suffix = util.strrpartition(file, '.')
             if base != '':
                 yield dot + suffix
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join(self.suffixes(data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
+        yield ' '.join(self.suffixes(self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class BasenameFunction(Function):
     name = 'basename'
     minargs = 1
     maxargs = 1
 
     @staticmethod
     def basenames(words):
@@ -310,312 +310,311 @@ class BasenameFunction(Function):
             dir, file = pathsplit(w, '')
             base, dot, suffix = util.strrpartition(file, '.')
             if dot == '':
                 base = suffix
 
             yield dir + base
 
     def resolve(self, makefile, variables, setting):
-        return ' '.join(self.basenames(data.splitwords(self._arguments[0].resolve(makefile, variables, setting))))
+        yield ' '.join(self.basenames(self._arguments[0].resolvestr(makefile, variables, setting).split()))
 
 class AddSuffixFunction(Function):
     name = 'addprefix'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        suffix = self._arguments[0].resolve(makefile, variables, setting)
+        suffix = self._arguments[0].resolvestr(makefile, variables, setting)
 
-        return ' '.join((w + suffix for w in data.splitwords(self._arguments[1].resolve(makefile, variables, setting))))
+        yield ' '.join((w + suffix for w in self._arguments[1].resolvestr(makefile, variables, setting).split()))
 
 class AddPrefixFunction(Function):
     name = 'addsuffix'
     minargs = 2
     maxargs = 2
 
     def resolve(self, makefile, variables, setting):
-        prefix = self._arguments[0].resolve(makefile, variables, setting)
+        prefix = self._arguments[0].resolvestr(makefile, variables, setting)
 
-        return ' '.join((prefix + w for w in data.splitwords(self._arguments[1].resolve(makefile, variables, setting))))
+        yield ' '.join((prefix + w for w in self._arguments[1].resolvestr(makefile, variables, setting).split()))
 
 class JoinFunction(Function):
     name = 'join'
     minargs = 2
     maxargs = 2
 
     @staticmethod
     def iterjoin(l1, l2):
         for i in xrange(0, max(len(l1), len(l2))):
             i1 = i < len(l1) and l1[i] or ''
             i2 = i < len(l2) and l2[i] or ''
             yield i1 + i2
 
     def resolve(self, makefile, variables, setting):
-        list1 = data.splitwords(self._arguments[0].resolve(makefile, variables, setting))
-        list2 = data.splitwords(self._arguments[1].resolve(makefile, variables, setting))
+        list1 = self._arguments[0].resolvestr(makefile, variables, setting).split()
+        list2 = self._arguments[1].resolvestr(makefile, variables, setting).split()
 
-        return ' '.join(self.iterjoin(list1, list2))
+        yield ' '.join(self.iterjoin(list1, list2))
 
 class WildcardFunction(Function):
     name = 'wildcard'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        # TODO: will need work when we support -C without actually changing the OS cwd
-        patterns = data.splitwords(self._arguments[0].resolve(makefile, variables, setting))
+        patterns = self._arguments[0].resolvestr(makefile, variables, setting).split()
 
         r = []
         for p in patterns:
-            r.extend(glob(makefile.workdir, p))
-        return ' '.join(r)
+            r.extend([x.replace('\\','/') for x in glob(makefile.workdir, p)])
+        yield ' '.join(r)
 
 class RealpathFunction(Function):
     name = 'realpath'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        paths = data.splitwords(self._arguments[0].resolve(makefile, variables, setting))
+        paths = self._arguments[0].resolvestr(makefile, variables, setting).split()
         fspaths = [os.path.join(makefile.workdir, path) for path in paths]
-        realpaths = [os.path.realpath(path) for path in fspaths]
-        return ' '.join(realpaths)
+        realpaths = [os.path.realpath(path).replace('\\','/') for path in fspaths]
+        yield ' '.join(realpaths)
 
 class AbspathFunction(Function):
     name = 'abspath'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
         assert os.path.isabs(makefile.workdir)
-        paths = data.splitwords(self._arguments[0].resolve(makefile, variables, setting))
-        fspaths = [os.path.join(makefile.workdir, path) for path in paths]
-        return ' '.join(fspaths)
+        paths = self._arguments[0].resolvestr(makefile, variables, setting).split()
+        fspaths = [os.path.join(makefile.workdir, path).replace('\\','/') for path in paths]
+        yield ' '.join(fspaths)
 
 class IfFunction(Function):
     name = 'if'
     minargs = 1
     maxargs = 3
 
     def setup(self):
         Function.setup(self)
         self._arguments[0].lstrip()
         self._arguments[0].rstrip()
 
     def resolve(self, makefile, variables, setting):
-        condition = self._arguments[0].resolve(makefile, variables, setting)
+        condition = self._arguments[0].resolvestr(makefile, variables, setting)
         if len(condition):
-            return self._arguments[1].resolve(makefile, variables, setting)
-
-        if len(self._arguments) > 2:
-            return self._arguments[2].resolve(makefile, variables, setting)
-
-        return ''
+            for j in self._arguments[1].resolvestr(makefile, variables, setting):
+                yield j
+        elif len(self._arguments) > 2:
+            for j in self._arguments[2].resolvestr(makefile, variables, setting):
+                yield j
 
 class OrFunction(Function):
     name = 'or'
     minargs = 1
     maxargs = 0
 
     def resolve(self, makefile, variables, setting):
         for arg in self._arguments:
-            r = arg.resolve(makefile, variables, setting)
+            r = arg.resolvestr(makefile, variables, setting)
             if r != '':
-                return r
-
-        return ''
+                yield r
+                return
 
 class AndFunction(Function):
     name = 'and'
     minargs = 1
     maxargs = 0
 
     def resolve(self, makefile, variables, setting):
         r = ''
 
         for arg in self._arguments:
-            r = arg.resolve(makefile, variables, setting)
+            r = arg.resolvestr(makefile, variables, setting)
             if r == '':
-                return ''
+                return
 
-        return r
+        yield r
 
 class ForEachFunction(Function):
     name = 'foreach'
     minargs = 3
     maxargs = 3
 
     def resolve(self, makefile, variables, setting):
-        vname = self._arguments[0].resolve(makefile, variables, setting)
+        vname = self._arguments[0].resolvestr(makefile, variables, setting)
+        words = self._arguments[1].resolvestr(makefile, variables, setting).split()
 
-        words = data.splitwords(self._arguments[1].resolve(makefile, variables, setting))
         e = self._arguments[2]
 
-        results = []
+        v = data.Variables(parent=variables)
+        for i in xrange(0, len(words)):
+            w = words[i]
+            if i > 0:
+                yield ' '
 
-        v = data.Variables(parent=variables)
-        for w in words:
             v.set(vname, data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, w)
-            results.append(e.resolve(makefile, v, setting))
-
-        return ' '.join(results)
+            for j in e.resolve(makefile, v, setting):
+                yield j
 
 class CallFunction(Function):
     name = 'call'
     minargs = 1
     maxargs = 0
 
     def resolve(self, makefile, variables, setting):
-        vname = self._arguments[0].resolve(makefile, variables, setting)
+        vname = self._arguments[0].resolvestr(makefile, variables, setting)
         if vname in setting:
             raise data.DataError("Recursively setting variable '%s'" % (vname,))
 
         v = data.Variables(parent=variables)
         v.set('0', data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, vname)
         for i in xrange(1, len(self._arguments)):
-            param = self._arguments[i].resolve(makefile, variables, setting)
+            param = self._arguments[i].resolvestr(makefile, variables, setting)
             v.set(str(i), data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, param)
 
         flavor, source, e = variables.get(vname)
         if e is None:
-            return ''
+            return
 
         if flavor == data.Variables.FLAVOR_SIMPLE:
             log.warning("%s: calling variable '%s' which is simply-expanded" % (self.loc, vname))
 
         # but we'll do it anyway
-        return e.resolve(makefile, v, setting + [vname])
+        for j in e.resolve(makefile, v, setting + [vname]):
+            yield j
 
 class ValueFunction(Function):
     name = 'value'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        varname = self._arguments[0].resolve(makefile, variables, setting)
+        varname = self._arguments[0].resolvestr(makefile, variables, setting)
 
         flavor, source, value = variables.get(varname, expand=False)
         if value is None:
-            return ''
+            return
 
-        return value
+        yield value
 
 class EvalFunction(Function):
     name = 'eval'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
         if makefile.parsingfinished:
             # GNU make allows variables to be set by recursive expansion during
             # command execution. This seems really dumb to me, so I don't!
             raise data.DataError("$(eval) not allowed via recursive expansion after parsing is finished", self.loc)
 
-        text = StringIO(self._arguments[0].resolve(makefile, variables, setting))
+        text = StringIO(self._arguments[0].resolvestr(makefile, variables, setting))
         stmts = parser.parsestream(text, 'evaluation from %s' % self.loc)
         stmts.execute(makefile)
-        return ''
+        return
+        yield None
 
 class OriginFunction(Function):
     name = 'origin'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        vname = self._arguments[0].resolve(makefile, variables, setting)
+        vname = self._arguments[0].resolvestr(makefile, variables, setting)
 
         flavor, source, value = variables.get(vname)
         if source is None:
-            return 'undefined'
-
-        if source == data.Variables.SOURCE_OVERRIDE:
-            return 'override'
-
-        if source == data.Variables.SOURCE_MAKEFILE:
-            return 'file'
-
-        if source == data.Variables.SOURCE_ENVIRONMENT:
-            return 'environment'
-
-        if source == data.Variables.SOURCE_COMMANDLINE:
-            return 'command line'
-
-        if source == data.Variables.SOURCE_AUTOMATIC:
-            return 'automatic'
-
-        assert False, "Unexpected source value: %s" % source
+            yield 'undefined'
+        elif source == data.Variables.SOURCE_OVERRIDE:
+            yield 'override'
+        elif source == data.Variables.SOURCE_MAKEFILE:
+            yield 'file'
+        elif source == data.Variables.SOURCE_ENVIRONMENT:
+            yield 'environment'
+        elif source == data.Variables.SOURCE_COMMANDLINE:
+            yield 'command line'
+        elif source == data.Variables.SOURCE_AUTOMATIC:
+            yield 'automatic'
+        else:
+            assert False, "Unexpected source value: %s" % source
 
 class FlavorFunction(Function):
     name = 'flavor'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        varname = self._arguments[0].resolve(makefile, variables, setting)
+        varname = self._arguments[0].resolvestr(makefile, variables, setting)
         
         flavor, source, value = variables.get(varname)
         if flavor is None:
-            return 'undefined'
-
-        if flavor == data.Variables.FLAVOR_RECURSIVE:
-            return 'recursive'
+            yield 'undefined'
+        elif flavor == data.Variables.FLAVOR_RECURSIVE:
+            yield 'recursive'
         elif flavor == data.Variables.FLAVOR_SIMPLE:
-            return 'simple'
-
-        assert False, "Neither simple nor recursive?"
+            yield 'simple'
+        else:
+            assert False, "Neither simple nor recursive?"
 
 class ShellFunction(Function):
     name = 'shell'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        cline = self._arguments[0].resolve(makefile, variables, setting)
+        #TODO: call this once up-front somewhere and save the result?
+        shell, msys = util.checkmsyscompat()
+        cline = self._arguments[0].resolvestr(makefile, variables, setting)
 
         log.debug("%s: running shell command '%s'" % (self.loc, cline))
-
-        p = subprocess.Popen(cline, shell=True, stdout=subprocess.PIPE, cwd=makefile.workdir)
+        if msys:
+            cline = [shell, "-c", cline]
+        p = subprocess.Popen(cline, shell=not msys, stdout=subprocess.PIPE, cwd=makefile.workdir)
         stdout, stderr = p.communicate()
 
         stdout = stdout.replace('\r\n', '\n')
         if len(stdout) > 1 and stdout[-1] == '\n':
             stdout = stdout[:-1]
         stdout = stdout.replace('\n', ' ')
 
-        return stdout
+        yield stdout
 
 class ErrorFunction(Function):
     name = 'error'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        v = self._arguments[0].resolve(makefile, variables, setting)
+        v = self._arguments[0].resolvestr(makefile, variables, setting)
         raise data.DataError(v, self.loc)
 
 class WarningFunction(Function):
     name = 'warning'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        v = self._arguments[0].resolve(makefile, variables, setting)
+        v = self._arguments[0].resolvestr(makefile, variables, setting)
         log.warning(v)
-        return ''
+        return
+        yield None
 
 class InfoFunction(Function):
     name = 'info'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
-        v = self._arguments[0].resolve(makefile, variables, setting)
+        v = self._arguments[0].resolvestr(makefile, variables, setting)
         log.info(v)
-        return ''
+        return
+        yield None
 
 functionmap = {
     'subst': SubstFunction,
     'patsubst': PatSubstFunction,
     'strip': StripFunction,
     'findstring': FindstringFunction,
     'filter': FilterFunction,
     'filter-out': FilteroutFunction,
--- a/pymake/parser.py
+++ b/pymake/parser.py
@@ -19,17 +19,17 @@ nest parenthesized syntax.
 
 This file parses into the data structures defined in the parserdata module. Those classes are what actually
 do the dirty work of "executing" the parsed data into a Makefile data structure.
 """
 
 import logging, re, os
 import data, functions, util, parserdata
 
-log = logging.getLogger('pymake.parser')
+_log = logging.getLogger('pymake.parser')
 
 class SyntaxError(util.MakeError):
     pass
 
 def findlast(func, iterable):
     for i in iterable:
         if func(i):
             f = i
@@ -361,28 +361,16 @@ def ensureend(d, offset, msg, ifunc=iter
     """
     Ensure that only whitespace remains in this data.
     """
 
     for c, t, o, oo in ifunc(d, offset, emptytokenlist):
         if c != '' and not c.isspace():
             raise SyntaxError(msg, d.getloc(o))
 
-def iterlines(fd):
-    """Yield (lineno, line) for each line in fd"""
-
-    lineno = 0
-    for line in fd:
-        lineno += 1
-
-        if line.endswith('\r\n'):
-            line = line[:-2] + '\n'
-
-        yield (lineno, line)
-
 eqargstokenlist = TokenList.get(('(', "'", '"'))
 
 def ifeq(d, offset):
     # the variety of formats for this directive is rather maddening
     token, offset = d.findtoken(offset, eqargstokenlist, False)
     if token is None:
         raise SyntaxError("No arguments after conditional", d.getloc(offset))
 
@@ -454,36 +442,36 @@ def parsefile(pathname):
     pathname = os.path.realpath(pathname)
 
     mtime = os.path.getmtime(pathname)
 
     if pathname in _parsecache:
         oldmtime, stmts = _parsecache[pathname]
 
         if mtime == oldmtime:
-            log.debug("Using '%s' from the parser cache.", pathname)
+            _log.debug("Using '%s' from the parser cache.", pathname)
             return stmts
 
-        log.debug("Not using '%s' from the parser cache, mtimes don't match: was %s, now %s" % (pathname, oldmtime, mtime))
+        _log.debug("Not using '%s' from the parser cache, mtimes don't match: was %s, now %s", pathname, oldmtime, mtime)
 
-    stmts = parsestream(open(pathname), pathname)
+    stmts = parsestream(open(pathname, "rU"), pathname)
     _parsecache[pathname] = mtime, stmts
     return stmts
 
 def parsestream(fd, filename):
     """
     Parse a stream of makefile into a parser data structure.
 
     @param fd A file-like object containing the makefile data.
     """
 
     currule = False
     condstack = [parserdata.StatementList()]
 
-    fdlines = iterlines(fd)
+    fdlines = enumerate(fd)
 
     while True:
         assert len(condstack) > 0
 
         d = DynamicData(fdlines, filename)
         if not d.readline():
             break
 
@@ -534,28 +522,30 @@ def parsestream(fd, filename):
                 continue
 
             if kword == 'endef':
                 raise SyntaxError("Unmatched endef", d.getloc(offset))
 
             if kword == 'define':
                 currule = False
                 vname, t, i = parsemakesyntax(d, offset, (), itermakefilechars)
+                vname.rstrip()
 
                 d = DynamicData(fdlines, filename)
                 d.readline()
 
                 value = _iterflatten(iterdefinechars, d, 0)
                 condstack[-1].append(parserdata.SetVariable(vname, value=value, valueloc=d.getloc(0), token='=', targetexp=None))
                 continue
 
             if kword in ('include', '-include'):
                 currule = False
                 incfile, t, offset = parsemakesyntax(d, offset, (), itermakefilechars)
                 condstack[-1].append(parserdata.Include(incfile, kword == 'include'))
+
                 continue
 
             if kword == 'vpath':
                 currule = False
                 e, t, offset = parsemakesyntax(d, offset, (), itermakefilechars)
                 condstack[-1].append(parserdata.VPathDirective(e))
                 continue
 
@@ -821,17 +811,17 @@ def parsemakesyntax(d, startat, stopon, 
                 stacktop.substfrom = stacktop.expansion
                 stacktop.parsestate = PARSESTATE_SUBSTTO
                 stacktop.expansion = data.Expansion()
                 stacktop.tokenlist = TokenList.get((stacktop.openbrace, stacktop.closebrace, '$'))
             elif token in (')', '}'):
                 # A substitution of the form $(VARNAME:.ee) is probably a mistake, but make
                 # parses it. Issue a warning. Combine the varname and substfrom expansions to
                 # make the compatible varname. See tests/var-substitutions.mk SIMPLE3SUBSTNAME
-                log.warning("%s: Variable reference looks like substitution without =" % (stacktop.loc, ))
+                _log.warning("%s: Variable reference looks like substitution without =", stacktop.loc)
                 stacktop.varname.append(':')
                 stacktop.varname.concat(stacktop.expansion)
                 stack.pop()
                 stack[-1].expansion.append(functions.VariableRef(stacktop.loc, stacktop.varname))
             else:
                 assert False, "Not reached, PARSESTATE_SUBSTFROM"
         elif stacktop.parsestate == PARSESTATE_SUBSTTO:
             assert token in  (')','}'), "Not reached, PARSESTATE_SUBSTTO"
--- a/pymake/parserdata.py
+++ b/pymake/parserdata.py
@@ -1,26 +1,16 @@
 import logging, re
 import data, functions, util, parser
 from cStringIO import StringIO
 from pymake.globrelative import hasglob, glob
 
 _log = logging.getLogger('pymake.data')
 _tabwidth = 4
 
-def _charlocation(start, char):
-    """
-    Return the column position after processing a perhaps-tab character.
-    This function is meant to be used with reduce().
-    """
-    if char != '\t':
-        return start + 1
-
-    return start + _tabwidth - start % _tabwidth
-
 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.
     """
@@ -31,20 +21,32 @@ class Location(object):
         self.line = line
         self.column = column
 
     def __add__(self, data):
         """
         Returns a new location on the same line offset by
         the specified string.
         """
-        newcol = reduce(_charlocation, data, self.column)
-        if newcol == self.column:
+        column = self.column
+        i = 0
+        while True:
+            j = data.find('\t', i)
+            if j == -1:
+                column += len(data) - i
+                break
+
+            column += j - i
+            column += _tabwidth
+            column -= column % _tabwidth
+            i = j + 1
+
+        if column == self.column:
             return self
-        return Location(self.path, self.line, newcol)
+        return Location(self.path, self.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
@@ -96,42 +98,41 @@ class Override(Statement):
     def execute(self, makefile, context):
         makefile.overrides.append(self.s)
 
     def dump(self, fd, indent):
         print >>fd, indent, "Override: %r" % (self.s,)
 
 class DummyRule(object):
     def addcommand(self, r):
-        _log.debug("Discarding rule at %s" % (r.loc,))
         pass
 
 class Rule(Statement):
     def __init__(self, targetexp, depexp, doublecolon):
         assert isinstance(targetexp, data.Expansion)
         assert isinstance(depexp, data.Expansion)
         
         self.targetexp = targetexp
         self.depexp = depexp
         self.doublecolon = doublecolon
 
     def execute(self, makefile, context):
-        atargets = data.splitwords(self.targetexp.resolve(makefile, makefile.variables))
+        atargets = data.stripdotslashes(self.targetexp.resolvestr(makefile, makefile.variables).split())
         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 = [p for p in _expandwildcards(makefile, data.splitwords(self.depexp.resolve(makefile, makefile.variables)))]
+        deps = [p for p in _expandwildcards(makefile, data.stripdotslashes(self.depexp.resolvestr(makefile, makefile.variables).split()))]
         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)
             for t in targets:
                 makefile.gettarget(t.gettarget()).addrule(rule)
             makefile.foundtarget(targets[0].gettarget())
@@ -148,28 +149,28 @@ class StaticPatternRule(Statement):
         assert isinstance(depexp, data.Expansion)
 
         self.targetexp = targetexp
         self.patternexp = patternexp
         self.depexp = depexp
         self.doublecolon = doublecolon
 
     def execute(self, makefile, context):
-        targets = list(_expandwildcards(makefile, data.splitwords(self.targetexp.resolve(makefile, makefile.variables))))
+        targets = list(_expandwildcards(makefile, data.stripdotslashes(self.targetexp.resolvestr(makefile, makefile.variables).split())))
 
         if not len(targets):
             context.currule = DummyRule()
             return
 
-        patterns = data.splitwords(self.patternexp.resolve(makefile, makefile.variables))
+        patterns = list(data.stripdotslashes(self.patternexp.resolvestr(makefile, makefile.variables).split()))
         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.splitwords(self.depexp.resolve(makefile, makefile.variables)))]
+        deps = [data.Pattern(p) for p in _expandwildcards(makefile, data.stripdotslashes(self.depexp.resolvestr(makefile, makefile.variables).split()))]
 
         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:
@@ -206,26 +207,26 @@ class SetVariable(Statement):
         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.resolve(makefile, makefile.variables)
+        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.splitwords(self.targetexp.resolve(makefile, makefile.variables))]
+            targets = [data.Pattern(t) for t in data.stripdotslashes(self.targetexp.resolvestr(makefile, makefile.variables).split())]
             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 == '+=':
@@ -242,17 +243,17 @@ class SetVariable(Statement):
                 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.resolve(makefile, makefile.variables)
+                value = e.resolvestr(makefile, makefile.variables)
 
             v.set(vname, flavor, self.source, value)
 
     def dump(self, fd, indent):
         print >>fd, indent, "SetVariable %r value=%r" % (self.vnameexp, self.value)
 
 class Condition(object):
     """
@@ -267,36 +268,34 @@ class EqCondition(Condition):
     def __init__(self, exp1, exp2):
         assert isinstance(exp1, data.Expansion)
         assert isinstance(exp2, data.Expansion)
 
         self.exp1 = exp1
         self.exp2 = exp2
 
     def evaluate(self, makefile):
-        r1 = self.exp1.resolve(makefile, makefile.variables)
-        r2 = self.exp2.resolve(makefile, makefile.variables)
+        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) %r %r" % (self.expected, self.exp1, self.exp2)
 
 class IfdefCondition(Condition):
     expected = True
 
     def __init__(self, exp):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
 
     def evaluate(self, makefile):
-        vname = self.exp.resolve(makefile, makefile.variables)
+        vname = self.exp.resolvestr(makefile, makefile.variables)
         flavor, source, value = makefile.variables.get(vname, expand=False)
 
-        _log.debug("ifdef at %s: vname: %r value is %r" % (self.exp.loc, vname, value))
-
         if value is None:
             return not self.expected
 
         return (len(value) > 0) == self.expected
 
     def __str__(self):
         return "ifdef (expected=%s) %r" % (self.expected, self.exp)
 
@@ -329,17 +328,17 @@ class ConditionBlock(Statement):
 
     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))
+                _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, indent, "ConditionBlock"
 
@@ -353,30 +352,30 @@ class ConditionBlock(Statement):
 
 class Include(Statement):
     def __init__(self, exp, required):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
         self.required = required
 
     def execute(self, makefile, context):
-        files = data.splitwords(self.exp.resolve(makefile, makefile.variables))
+        files = self.exp.resolvestr(makefile, makefile.variables).split()
         for f in files:
             makefile.include(f, self.required, loc=self.exp.loc)
 
     def dump(self, fd, indent):
         print >>fd, indent, "Include %r" % (self.exp,)
 
 class VPathDirective(Statement):
     def __init__(self, exp):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
 
     def execute(self, makefile, context):
-        words = data.splitwords(self.exp.resolve(makefile, makefile.variables))
+        words = list(data.stripdotslashes(self.exp.resolvestr(makefile, makefile.variables).split()))
         if len(words) == 0:
             makefile.clearallvpaths()
         else:
             pattern = data.Pattern(words[0])
             mpaths = words[1:]
 
             if len(mpaths) == 0:
                 makefile.clearvpath(pattern)
@@ -394,35 +393,35 @@ class VPathDirective(Statement):
 class ExportDirective(Statement):
     def __init__(self, exp, single):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
         self.single = single
 
     def execute(self, makefile, context):
         if self.single:
-            vlist = [self.exp.resolve(makefile, makefile.variables)]
+            vlist = [self.exp.resolvestr(makefile, makefile.variables)]
         else:
-            vlist = data.splitwords(self.exp.resolve(makefile, makefile.variables))
+            vlist = self.exp.resolvestr(makefile, makefile.variables).split()
             if not len(vlist):
                 raise data.DataError("Exporting all variables is not supported", self.exp.loc)
 
         for v in vlist:
             makefile.exportedvars.add(v)
 
     def dump(self, fd, indent):
         print >>fd, indent, "Export (single=%s) %r" % (self.single, self.exp)
 
 class EmptyDirective(Statement):
     def __init__(self, exp):
         assert isinstance(exp, data.Expansion)
         self.exp = exp
 
     def execute(self, makefile, context):
-        v = self.exp.resolve(makefile, makefile.variables)
+        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, indent, "EmptyDirective: %r" % self.exp
 
 class StatementList(list):
     def append(self, statement):
--- a/pymake/process.py
+++ b/pymake/process.py
@@ -1,58 +1,81 @@
 """
 Skipping shell invocations is good, when possible. This wrapper around subprocess does dirty work of
 parsing command lines into argv and making sure that no shell magic is being used.
 """
 
-import subprocess, shlex, re, logging, sys, traceback, os, util
-import command
+import subprocess, shlex, re, logging, sys, traceback, os
+import command, util
+if sys.platform=='win32':
+    import win32process
 
 _log = logging.getLogger('pymake.process')
 
-blacklist = re.compile(r'[=\\$><;*?[{~`|&]')
+_blacklist = re.compile(r'[=\\$><;*?[{~`|&]')
 def clinetoargv(cline):
     """
     If this command line can safely skip the shell, return an argv array.
+    @returns argv, badchar
     """
 
-    if blacklist.search(cline) is not None:
-        return None
+    m = _blacklist.search(cline)
+    if m is not None:
+        return None, m.group(0)
 
-    return shlex.split(cline, comments=True)
+    return shlex.split(cline, comments=True), None
 
 shellwords = (':', '.', 'break', 'cd', 'continue', 'exec', 'exit', 'export',
               'getopts', 'hash', 'pwd', 'readonly', 'return', 'shift', 
               'test', 'times', 'trap', 'umask', 'unset', 'alias',
               'set', 'bind', 'builtin', 'caller', 'command', 'declare',
               'echo', 'enable', 'help', 'let', 'local', 'logout', 
               'printf', 'read', 'shopt', 'source', 'type', 'typeset',
               'ulimit', 'unalias', 'set')
 
 def call(cline, env, cwd, loc, cb, context, echo):
-    argv = clinetoargv(cline)
-    if argv is None or (len(argv) and argv[0] in shellwords):
-        _log.debug("%s: Running command through shell because of shell metacharacters" % (loc,))
-        context.call(cline, shell=True, env=env, cwd=cwd, cb=cb, echo=echo)
+    #TODO: call this once up-front somewhere and save the result?
+    shell, msys = util.checkmsyscompat()
+
+    shellreason = None
+    if msys and cline.startswith('/'):
+        shellreason = "command starts with /"
+    else:
+        argv, badchar = clinetoargv(cline)
+        if argv is None:
+            shellreason = "command contains shell-special character '%s'" % (badchar,)
+        elif len(argv) and argv[0] in shellwords:
+            shellreason = "command starts with shell primitive '%s'" % (argv[0],)
+
+    if shellreason is not None:
+        _log.debug("%s: using shell: %s: '%s'", loc, shellreason, cline)
+        if msys:
+            cline = [shell, "-c", cline]
+        context.call(cline, shell=not msys, env=env, cwd=cwd, cb=cb, echo=echo)
         return
 
     if not len(argv):
         cb(res=0)
         return
 
     if argv[0] == command.makepypath:
         command.main(argv[1:], env, cwd, context, cb)
         return
 
-    if argv[0:2] == [sys.executable, command.makepypath]:
+    if argv[0:2] == [sys.executable.replace('\\', '/'),
+                     command.makepypath.replace('\\', '/')]:
         command.main(argv[2:], env, cwd, context, cb)
         return
 
-    _log.debug("%s: skipping shell, no metacharacters found" % (loc,))
-    context.call(argv, shell=False, env=env, cwd=cwd, cb=cb, echo=echo)
+    if argv[0].find('/') != -1:
+        executable = os.path.join(cwd, argv[0])
+    else:
+        executable = None
+
+    context.call(argv, executable=executable, shell=False, env=env, cwd=cwd, cb=cb, echo=echo)
 
 def statustoresult(status):
     """
     Convert the status returned from waitpid into a prettier numeric result.
     """
     sig = status & 0xFF
     if sig:
         return -sig
@@ -81,59 +104,80 @@ class ParallelContext(object):
 
     def finish(self):
         assert len(self.pending) == 0 and len(self.running) == 0, "pending: %i running: %i" % (len(self.pending), len(self.running))
         self._allcontexts.remove(self)
 
     def run(self):
         while len(self.pending) and len(self.running) < self.jcount:
             cb, args, kwargs = self.pending.pop(0)
-            _log.debug("Running callback %r with args %r kwargs %r" % (cb, args, kwargs))
             cb(*args, **kwargs)
 
     def defer(self, cb, *args, **kwargs):
         self.pending.append((cb, args, kwargs))
 
-    def _docall(self, argv, shell, env, cwd, cb, echo):
+    def _docall(self, argv, executable, shell, env, cwd, cb, echo):
             if echo is not None:
                 print echo
-            p = subprocess.Popen(argv, shell=shell, env=env, cwd=cwd)
+            try:
+                p = subprocess.Popen(argv, executable=executable, shell=shell, env=env, cwd=cwd)
+            except OSError, e:
+                print >>sys.stderr, e
+                cb(-127)
+                return
+
             self.running.append((p, cb))
 
-    def call(self, argv, shell, env, cwd, cb, echo):
+    def call(self, argv, shell, env, cwd, cb, echo, executable=None):
         """
         Asynchronously call the process
         """
 
-        self.defer(self._docall, argv, shell, env, cwd, cb, echo)
+        self.defer(self._docall, argv, executable, shell, env, cwd, cb, echo)
+
+    if sys.platform == 'win32':
+        @staticmethod
+        def _waitany():
+            return win32process.WaitForAnyProcess([p for c in ParallelContext._allcontexts for p, cb in c.running])
+
+        @staticmethod
+        def _comparepid(pid, process):
+            return pid == process
+
+    else:
+        @staticmethod
+        def _waitany():
+            return os.waitpid(-1, 0)
+
+        @staticmethod
+        def _comparepid(pid, process):
+            return pid == process.pid
 
     @staticmethod
     def spin():
         """
         Spin the 'event loop', and never return.
         """
 
-        _log.debug("Spinning the event loop")
-
         while True:
             clist = list(ParallelContext._allcontexts)
             for c in clist:
                 c.run()
 
             dowait = util.any((len(c.running) for c in ParallelContext._allcontexts))
 
             if dowait:
-                pid, status = os.waitpid(-1, 0)
+                pid, status = ParallelContext._waitany()
                 result = statustoresult(status)
 
                 found = False
                 for c in ParallelContext._allcontexts:
                     for i in xrange(0, len(c.running)):
                         p, cb = c.running[i]
-                        if p.pid == pid:
+                        if ParallelContext._comparepid(pid, p):
                             del c.running[i]
                             cb(result)
                             found = True
                             break
 
                     if found: break
 
 def makedeferrable(usercb, **userkwargs):
--- a/pymake/util.py
+++ b/pymake/util.py
@@ -1,8 +1,10 @@
+import os
+
 def makeobject(proplist, **kwargs):
     class P(object):
         __slots__ = proplist
 
     p = P()
     for k, v in kwargs.iteritems():
         setattr(p, k, v)
     return p
@@ -14,16 +16,34 @@ class MakeError(Exception):
 
     def __str__(self):
         locstr = ''
         if self.loc is not None:
             locstr = "%s:" % (self.loc,)
 
         return "%s%s" % (locstr, self.message)
 
+def checkmsyscompat():
+    """For msys compatibility on windows, honor the SHELL environment variable,
+    and if $MSYSTEM == MINGW32, run commands through $SHELL -c instead of
+    letting Python use the system shell."""
+    if 'SHELL' in os.environ:
+        shell = os.environ['SHELL']
+    elif 'COMSPEC' in os.environ:
+        shell = os.environ['COMSPEC']
+    else:
+        raise DataError("Can't find a suitable shell!")
+
+    msys = False
+    if 'MSYSTEM' in os.environ and os.environ['MSYSTEM'] == 'MINGW32':
+        msys = True
+        if not shell.lower().endswith(".exe"):
+            shell += ".exe"
+    return (shell, msys)
+
 if hasattr(str, 'partition'):
     def strpartition(str, token):
         return str.partition(token)
 
     def strrpartition(str, token):
         return str.rpartition(token)
 
 else:
new file mode 100644
--- /dev/null
+++ b/pymake/win32process.py
@@ -0,0 +1,28 @@
+from ctypes import windll, POINTER, byref, WinError
+from ctypes.wintypes import WINFUNCTYPE, HANDLE, DWORD, BOOL
+
+INFINITE = -1
+WAIT_FAILED = 0xFFFFFFFF
+
+LPDWORD = POINTER(DWORD)
+_GetExitCodeProcessProto = WINFUNCTYPE(BOOL, HANDLE, LPDWORD)
+_GetExitCodeProcess = _GetExitCodeProcessProto(("GetExitCodeProcess", windll.kernel32))
+def GetExitCodeProcess(h):
+    exitcode = DWORD()
+    r = _GetExitCodeProcess(h, byref(exitcode))
+    if r is 0:
+        raise WinError()
+    return exitcode.value
+
+_WaitForMultipleObjectsProto = WINFUNCTYPE(DWORD, DWORD, POINTER(HANDLE), BOOL, DWORD)
+_WaitForMultipleObjects = _WaitForMultipleObjectsProto(("WaitForMultipleObjects", windll.kernel32))
+
+def WaitForAnyProcess(processes):
+    arrtype = HANDLE * len(processes)
+    harray = arrtype(*(int(p._handle) for p in processes))
+
+    r = _WaitForMultipleObjects(len(processes), harray, False, INFINITE)
+    if r == WAIT_FAILED:
+        raise WinError()
+
+    return processes[r], GetExitCodeProcess(int(processes[r]._handle)) <<8
--- a/tests/automatic-variables.mk
+++ b/tests/automatic-variables.mk
@@ -1,18 +1,19 @@
 $(shell \
-mkdir -p src/subd subd; \
+mkdir -p src/subd; \
+mkdir subd; \
 touch dummy; \
-sleep 1; \
+sleep 2; \
 touch subd/test.out src/subd/test.in2; \
-sleep 1; \
+sleep 2; \
 touch subd/test.out2 src/subd/test.in; \
-sleep 1; \
+sleep 2; \
 touch subd/host_test.out subd/host_test.out2; \
-sleep 1; \
+sleep 2; \
 touch host_prog; \
 )
 
 VPATH = src
 
 all: prog host_prog prog dir/
 	test "$@" = "all"
 	test "$<" = "prog"
@@ -39,16 +40,17 @@ dir/:
 	test "$(@F)" = "dir"
 	mkdir $@
 
 prog: subd/test.out subd/test.out2
 	test "$@" = "prog"
 	test "$<" = "subd/test.out"
 	test "$^" = "subd/test.out subd/test.out2" # ^
 	test "$?" = "subd/test.out subd/test.out2" # ?
+	cat $<
 	test "$$(cat $<)" = "remade"
 	test "$$(cat $(word 2,$^))" = ""
 
 host_prog: subd/host_test.out subd/host_test.out2
 	@echo TEST-FAIL No need to remake
 
 %.out: %.in dummy
 	test "$@" = "subd/test.out"
new file mode 100644
--- /dev/null
+++ b/tests/commandmodifiers.mk
@@ -0,0 +1,21 @@
+define COMMAND
+$(1)
+ 	$(1)
+
+endef
+
+all:
+	$(call COMMAND,@true #TEST-FAIL)
+	$(call COMMAND,-exit 4)
+	$(call COMMAND,@-exit 1 # TEST-FAIL)
+	$(call COMMAND,-@exit 1 # TEST-FAIL)
+	$(call COMMAND,+exit 0)
+	$(call COMMAND,+-exit 1)
+	$(call COMMAND,@+exit 0 # TEST-FAIL)
+	$(call COMMAND,+@exit 0 # TEST-FAIL)
+	$(call COMMAND,-+@exit 1 # TEST-FAIL)
+	$(call COMMAND,+-@exit 1 # TEST-FAIL)
+	$(call COMMAND,@+-exit 1 # TEST-FAIL)
+	$(call COMMAND,@+-@+-exit 1 # TEST-FAIL)
+	$(call COMMAND,@@++exit 0 # TEST-FAIL)
+	@echo TEST-PASS
--- a/tests/datatests.py
+++ b/tests/datatests.py
@@ -5,32 +5,32 @@ import re
 class SplitWordsTest(unittest.TestCase):
     testdata = (
         (' test test.c test.o ', ['test', 'test.c', 'test.o']),
         ('\ttest\t  test.c \ntest.o', ['test', 'test.c', 'test.o']),
     )
 
     def runTest(self):
         for s, e in self.testdata:
-            w = pymake.data.splitwords(s)
+            w = s.split()
             self.assertEqual(w, e, 'splitwords(%r)' % (s,))
 
 class GetPatSubstTest(unittest.TestCase):
     testdata = (
         ('%.c', '%.o', ' test test.c test.o ', 'test test.o test.o'),
         ('%', '%.o', ' test.c test.o ', 'test.c.o test.o.o'),
         ('foo', 'bar', 'test foo bar', 'test bar bar'),
         ('foo', '%bar', 'test foo bar', 'test %bar bar'),
         ('%', 'perc_%', 'path', 'perc_path'),
         ('\\%', 'sub%', 'p %', 'p sub%'),
         ('%.c', '\\%%.o', 'foo.c bar.o baz.cpp', '%foo.o bar.o baz.cpp'),
     )
 
     def runTest(self):
         for s, r, d, e in self.testdata:
-            words = pymake.data.splitwords(d)
+            words = d.split()
             p = pymake.data.Pattern(s)
             a = ' '.join((p.subst(r, word, False)
                           for word in words))
             self.assertEqual(a, e, 'Pattern(%r).subst(%r, %r)' % (s, r, d))
 
 if __name__ == '__main__':
     unittest.main()
--- a/tests/define-directive.mk
+++ b/tests/define-directive.mk
@@ -3,16 +3,20 @@ shellvar=hello
 test "$$shellvar" != "hello"
 endef
 
 define COMMANDS2
 shellvar=hello; \
   test "$$shellvar" = "hello"
 endef
 
+define VARWITHCOMMENT # comment
+value
+endef
+
 define TEST3
   whitespace
 endef
 
 define TEST4
 define TEST5
 random
 endef
@@ -44,11 +48,12 @@ endif
 define TEST9
 value \
 endef
 endef
 
 all:
 	$(COMMANDS)
 	$(COMMANDS2)
+	test '$(VARWITHCOMMENT)' = 'value'
 	test '$(COMMANDS2)' = 'shellvar=hello; test "$$shellvar" = "hello"'
 	test "$(TEST3)" = "  whitespace"
 	@echo TEST-PASS
new file mode 100644
--- /dev/null
+++ b/tests/depfailed.mk
@@ -0,0 +1,4 @@
+#T returncode: 2
+
+all: foo.out foo.in
+	@echo TEST-PASS
new file mode 100644
--- /dev/null
+++ b/tests/depfailedj.mk
@@ -0,0 +1,10 @@
+#T returncode: 2
+#T commandline: ['-j4']
+
+$(shell touch foo.in)
+
+all: foo.in foo.out missing
+	@echo TEST-PASS
+
+%.out: %.in
+	cp $< $@
new file mode 100644
--- /dev/null
+++ b/tests/dotslash.mk
@@ -0,0 +1,9 @@
+$(shell touch foo.in)
+
+all: foo.out
+	test "$(wildcard ./*.in)" = "./foo.in"
+	@echo TEST-PASS
+
+./%.out: %.in
+	test "$@" = "foo.out"
+	cp $< $@
new file mode 100644
--- /dev/null
+++ b/tests/exit-code.mk
@@ -0,0 +1,5 @@
+#T returncode: 2
+
+all:
+	exit 1
+	@echo TEST-PASS
new file mode 100644
--- /dev/null
+++ b/tests/file-functions-symlinks.mk
@@ -0,0 +1,22 @@
+#T returncode-on: {'win32': 2}
+$(shell \
+touch test.file; \
+ln -s test.file test.symlink; \
+ln -s test.missing missing.symlink; \
+touch .testhidden; \
+mkdir foo; \
+touch foo/testfile; \
+ln -s foo symdir; \
+)
+
+all:
+	test "$(abspath test.file test.symlink)" = "$(CURDIR)/test.file $(CURDIR)/test.symlink"
+	test "$(realpath test.file test.symlink)" = "$(CURDIR)/test.file $(CURDIR)/test.file"
+	test "$(sort $(wildcard *))" = "foo symdir test.file test.symlink"
+	test "$(sort $(wildcard .*))" = ". .. .testhidden"
+	test "$(sort $(wildcard test*))" = "test.file test.symlink"
+	test "$(sort $(wildcard foo/*))" = "foo/testfile"
+	test "$(sort $(wildcard ./*))" = "./foo ./symdir ./test.file ./test.symlink"
+	test "$(sort $(wildcard f?o/*))" = "foo/testfile"
+	test "$(sort $(wildcard */*))" = "foo/testfile symdir/testfile"
+	@echo TEST-PASS
--- a/tests/file-functions.mk
+++ b/tests/file-functions.mk
@@ -1,21 +1,19 @@
 $(shell \
 touch test.file; \
-ln -s test.file test.symlink; \
-ln -s test.missing missing.symlink; \
 touch .testhidden; \
 mkdir foo; \
 touch foo/testfile; \
-ln -s foo symdir; \
 )
 
 all:
-	test "$(abspath test.file test.symlink)" = "$(CURDIR)/test.file $(CURDIR)/test.symlink"
-	test "$(realpath test.file test.symlink)" = "$(CURDIR)/test.file $(CURDIR)/test.file"
-	test "$(sort $(wildcard *))" = "foo symdir test.file test.symlink"
-	test "$(sort $(wildcard .*))" = ". .. .testhidden"
-	test "$(sort $(wildcard test*))" = "test.file test.symlink"
+	test "$(abspath test.file)" = "$(CURDIR)/test.file"
+	test "$(realpath test.file)" = "$(CURDIR)/test.file"
+	test "$(sort $(wildcard *))" = "foo test.file"
+# commented out because GNU make matches . and .. while python doesn't, and I don't
+# care enough
+#	test "$(sort $(wildcard .*))" = ". .. .testhidden"
+	test "$(sort $(wildcard test*))" = "test.file"
 	test "$(sort $(wildcard foo/*))" = "foo/testfile"
-	test "$(sort $(wildcard ./*))" = "./foo ./symdir ./test.file ./test.symlink"
+	test "$(sort $(wildcard ./*))" = "./foo ./test.file"
 	test "$(sort $(wildcard f?o/*))" = "foo/testfile"
-	test "$(sort $(wildcard */*))" = "foo/testfile symdir/testfile"
 	@echo TEST-PASS
--- a/tests/include-dynamic.mk
+++ b/tests/include-dynamic.mk
@@ -1,12 +1,12 @@
 $(shell \
 if ! test -f include-dynamic.inc; then \
   echo "TESTVAR = oldval" > include-dynamic.inc; \
-  sleep 1; \
+  sleep 2; \
   echo "TESTVAR = newval" > include-dynamic.inc.in; \
 fi \
 )
 
 # before running the 'all' rule, we should be rebuilding include-dynamic.inc,
 # because there is a rule to do so
 
 all:
--- a/tests/include-notfound.mk
+++ b/tests/include-notfound.mk
@@ -1,13 +1,19 @@
-ifneq ($(strip $(MAKEFILE_LIST)),$(TESTPATH)/include-notfound.mk)
-$(error MAKEFILE_LIST incorrect: '$(MAKEFILE_LIST)')
+ifdef __WIN32__
+PS:=\\#
+else
+PS:=/
+endif
+
+ifneq ($(strip $(MAKEFILE_LIST)),$(NATIVE_TESTPATH)$(PS)include-notfound.mk)
+$(error MAKEFILE_LIST incorrect: '$(MAKEFILE_LIST)' (expected '$(NATIVE_TESTPATH)$(PS)include-notfound.mk'))
 endif
 
 -include notfound.inc-dummy
 
-ifneq ($(strip $(MAKEFILE_LIST)),$(TESTPATH)/include-notfound.mk)
-$(error MAKEFILE_LIST incorrect: '$(MAKEFILE_LIST)')
+ifneq ($(strip $(MAKEFILE_LIST)),$(NATIVE_TESTPATH)$(PS)include-notfound.mk)
+$(error MAKEFILE_LIST incorrect: '$(MAKEFILE_LIST)' (expected '$(NATIVE_TESTPATH)$(PS)include-notfound.mk'))
 endif
 
 all:
 	@echo TEST-PASS
 
new file mode 100644
--- /dev/null
+++ b/tests/nosuchfile.mk
@@ -0,0 +1,4 @@
+#T returncode: 2
+
+all:
+	reallythereisnosuchcommand
new file mode 100644
--- /dev/null
+++ b/tests/parallel-waiting.mk
@@ -0,0 +1,21 @@
+#T commandline: ['-j2']
+
+EXPECTED = target1:before:target2:1:target2:2:target2:3:target1:after
+
+all:: target1 target2
+	cat results
+	test "$$(cat results)" = "$(EXPECTED)"
+	@echo TEST-PASS
+
+target1:
+	printf "$@:before:" >>results
+	sleep 4
+	printf "$@:after" >>results
+
+target2:
+	sleep 0.2
+	printf "$@:1:" >>results
+	sleep 0.1
+	printf "$@:2:" >>results
+	sleep 0.1
+	printf "$@:3:" >>results
--- a/tests/parsertests.py
+++ b/tests/parsertests.py
@@ -125,17 +125,17 @@ endef not what you think!"""
 endef
 endef\n""",
             "value endef"
         ),
     }
 
     def runSingle(self, ifunc, idata, expected):
         fd = StringIO(idata)
-        lineiter = pymake.parser.iterlines(fd)
+        lineiter = enumerate(fd)
 
         d = pymake.parser.DynamicData(lineiter, 'PlainIterTest-data')
         d.readline()
 
         actual = ''.join( (c for c, t, o, oo in ifunc(d, 0, pymake.parser.emptytokenlist)) )
         self.assertEqual(actual, expected)
 
         self.assertRaises(StopIteration, lambda: fd.next())
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -5,16 +5,17 @@ walk the directory for files named .mk a
 For each test, we run gmake -f test.mk. By default, make must exit with an exit code of 0, and must print 'TEST-PASS'.
 
 Each test is run in an empty directory.
 
 The test file may contain lines at the beginning to alter the default behavior. These are all evaluated as python:
 
 #T commandline: ['extra', 'params', 'here']
 #T returncode: 2
+#T returncode-on: {'win32': 2}
 """
 
 from subprocess import Popen, PIPE, STDOUT
 from optparse import OptionParser
 import os, re, sys, shutil
 
 thisdir = os.path.dirname(os.path.abspath(__file__))
 
@@ -36,38 +37,51 @@ for a in args:
         for path, dirnames, filenames in os.walk(a):
             for f in filenames:
                 if f.endswith('.mk'):
                     makefiles.append('%s/%s' % (path, f))
     else:
         print >>sys.stderr, "Error: Unknown file on command line"
         sys.exit(1)
 
-tre = re.compile('^#T ([a-z]+): (.*)$')
+tre = re.compile('^#T ([a-z-]+): (.*)$')
 
 for makefile in makefiles:
     print "Testing: %s" % makefile,
 
     if os.path.exists(opts.tempdir): shutil.rmtree(opts.tempdir)
     os.mkdir(opts.tempdir, 0755)
 
-    cline = [opts.make, '-C', opts.tempdir, '-f', os.path.abspath(makefile), 'TESTPATH=%s' % thisdir]
+    # For some reason, MAKEFILE_LIST uses native paths in GNU make on Windows
+    # (even in MSYS!) so we pass both TESTPATH and NATIVE_TESTPATH
+    cline = [opts.make, '-C', opts.tempdir, '-f', os.path.abspath(makefile), 'TESTPATH=%s' % thisdir.replace('\\','/'), 'NATIVE_TESTPATH=%s' % thisdir]
+    if sys.platform == 'win32':
+        #XXX: hack to run pymake on windows
+        if opts.make.endswith('.py'):
+            cline = [sys.executable] + cline
+        #XXX: hack so we can specialize the separator character on windows.
+        # we really shouldn't need this, but y'know
+        cline += ['__WIN32__=1']
+        
     returncode = 0
 
     mdata = open(makefile)
     for line in mdata:
         m = tre.search(line)
         if m is None:
             break
         key, data = m.group(1, 2)
         data = eval(data)
         if key == 'commandline':
             cline.extend(data)
         elif key == 'returncode':
             returncode = data
+        elif key == 'returncode-on':
+            if sys.platform in data:
+                returncode = data[sys.platform]
         else:
             print >>sys.stderr, "Unexpected #T key: %s" % key
             sys.exit(1)
 
     mdata.close()
 
     p = Popen(cline, stdout=PIPE, stderr=STDOUT)
     stdout, d = p.communicate()
--- a/tests/vpath.mk
+++ b/tests/vpath.mk
@@ -1,14 +1,14 @@
 VPATH = foo bar
 
 $(shell \
 mkdir foo; touch foo/tfile1; \
 mkdir bar; touch bar/tfile2 bar/tfile3 bar/test.objtest; \
-sleep 1; \
+sleep 2; \
 touch bar/test.source; \
 )
 
 all: tfile1 tfile2 tfile3 test.objtest test.source
 	test "$^" = "foo/tfile1 bar/tfile2 tfile3 test.objtest bar/test.source"
 	@echo TEST-PASS
 
 tfile3: test.objtest
--- a/tests/wildcards.mk
+++ b/tests/wildcards.mk
@@ -1,18 +1,19 @@
 $(shell \
 mkdir foo; \
 touch a.c b.c c.out foo/d.c; \
-sleep 1; \
+sleep 2; \
 touch c.in; \
 )
 
 VPATH = foo
 
 all: c.out prog
+	cat $<
 	test "$$(cat $<)" = "remadec.out"
 	@echo TEST-PASS
 
 *.out: %.out: %.in
 	test "$@" = c.out
 	test "$<" = c.in
 	printf "remade$@" >$@