Fix static pattern stems... argh, I never wrote a passing test for static patterns, only a failing test. Dumb dumb dumb.
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 12 Feb 2009 14:46:55 -0500
changeset 105 1a5a35701f9388d9d3216f01c21cc565a3d2ff11
parent 104 b60ef841a8113faefe5d31a713988d91743a74c2
child 107 a7a2fc544d28ce23977b83704cee6ef9b66ad9b6
push id56
push userbsmedberg@mozilla.com
push dateThu, 12 Feb 2009 19:47:10 +0000
Fix static pattern stems... argh, I never wrote a passing test for static patterns, only a failing test. Dumb dumb dumb. Also add indents to the log output, so that it's clear how dependencies chain.
make.py
pymake/data.py
pymake/functions.py
pymake/parser.py
tests/static-pattern2.mk
--- a/make.py
+++ b/make.py
@@ -128,19 +128,23 @@ try:
 
         break
 
     if len(targets) == 0:
         if m.defaulttarget is None:
             print "No target specified and no default target found."
             sys.exit(2)
         targets = [m.defaulttarget]
+        tstack = ['<default-target>']
+    else:
+        tstack = ['<command-line>']
+
 
     for t in targets:
-        m.gettarget(t).make(m, [], [])
+        m.gettarget(t).make(m, ['<command-line>'], [])
 
 except (DataError, SyntaxError, subprocess.CalledProcessError), e:
     print e
     print "make.py[%i]: Leaving directory '%s'" % (makelevel, os.getcwd())
     sys.stdout.flush()
     sys.exit(2)
 
 print "make.py[%i]: Leaving directory '%s'" % (makelevel, os.getcwd())
--- a/pymake/data.py
+++ b/pymake/data.py
@@ -57,16 +57,19 @@ def splitwords(s):
     words = _ws.split(s)
     for i in (0, -1):
         if len(words) == 0:
             break
         if words[i] == '':
             del words[i]
     return words
 
+def getindent(stack):
+    return ''.ljust(len(stack) - 1)
+
 def _if_else(c, t, f):
     if c:
         return t()
     return f()
 
 class Expansion(object):
     """
     A representation of expanded data, such as that for a recursively-expanded variable, a command, etc.
@@ -444,31 +447,33 @@ class Target(object):
         return False
 
     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.
 
-        log.info("Trying to find implicit rule to make '%s'" % self.target)
+        indent = getindent(targetstack)
+
+        log.info(indent + "Trying to find implicit rule to make '%s'" % (self.target,))
 
         dir, s, file = self.target.rpartition('/')
         dir = dir + s
 
         candidates = [] # list of PatternRuleInstance
 
         hasmatch = any((r.hasmatch(file) for r in makefile.implicitrules))
         for r in makefile.implicitrules:
             if r in rulestack:
-                log.info("%s: Avoiding implicit rule recursion" % (r.loc,))
+                log.info(indent + " %s: Avoiding implicit rule recursion" % (r.loc,))
                 continue
 
             if not len(r.commands):
-                log.info("%s: Has no commands" % (r.loc,))
+                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:
@@ -477,22 +482,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("  Terminal rule at %s doesn't match: prerequisite '%s' not mentioned and doesn't exist." % (r.loc, depfailed))
+                    log.info(indent + " Terminal rule at %s doesn't match: prerequisite '%s' not mentioned and doesn't exist." % (r.loc, depfailed))
                 else:
                     newcandidates.append(r)
                 continue
 
-            log.info("Found implicit rule at %s for target '%s'" % (r.loc, self.target))
+            log.info(indent + "Found implicit rule at %s for target '%s'" % (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]
 
@@ -501,24 +506,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("  Rule at %s doesn't match: prerequisite '%s' could not be made." % (r.loc, depfailed))
+                log.info(indent + " Rule at %s doesn't match: prerequisite '%s' could not be made." % (r.loc, depfailed))
                 continue
 
-            log.info("Found implicit rule at %s for target '%s'" % (r.loc, self.target))
+            log.info(indent + "Found implicit rule at %s for target '%s'" % (r.loc, self.target))
             self.rules.append(r)
             return
 
-        log.info("Couldn't find implicit rule to remake '%s'" % (self.target,))
+        log.info(indent + "Couldn't find implicit rule to remake '%s'" % (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.
@@ -536,19 +541,21 @@ class Target(object):
                dependencies. A rule chain cannot use the same implicit rule twice.
         """
         assert makefile.parsingfinished
 
         if self.target in targetstack:
             raise ResolutionError("Recursive dependency: %s -> %s" % (
                     " -> ".join(targetstack), self.target))
 
-        log.info("Considering target '%s'" % (self.target,))
+        targetstack = targetstack + [self.target]
+        
+        indent = getindent(targetstack)
 
-        targetstack = targetstack + [self.target]
+        log.info(indent + "Considering target '%s'" % (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.
@@ -561,20 +568,21 @@ class Target(object):
         # 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 any((len(rule.prerequisites) > 0
                                                                    for rule in self.rules)):
             if required:
-                raise ResolutionError("No rule to make target '%s' needed by %s" % (self.target,
-                    ' -> '.join(targetstack[:-1])))
+                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)
 
@@ -650,52 +658,56 @@ class Target(object):
         @returns True if anything was done to remake this target
         """
         if self.remade is not None:
             return self.remade
 
         self.resolvedeps(makefile, targetstack, rulestack, required)
         assert self.vpathtarget is not None, "Target was never resolved!"
 
+        targetstack = targetstack + [self.target]
+
+        indent = getindent(targetstack)
+
         didanything = False
 
         if len(self.rules) == 0:
             pass
         elif self.isdoublecolon():
             for r in self.rules:
                 remake = False
                 if self.mtime is None:
                     log.info("Remaking %s using rule at %s because it doesn't exist or is a forced target" % (self.target, r.loc))
                     remake = True
                 for p in r.prerequisites:
                     dep = makefile.gettarget(p)
-                    didanything = dep.make(makefile, [], []) or didanything
+                    didanything = dep.make(makefile, targetstack, []) or didanything
                     if not remake and mtimeislater(dep.mtime, self.mtime):
-                        log.info("Remaking %s using rule at %s because %s is newer." % (self.target, r.loc, p))
+                        log.info(indent + "Remaking %s using rule at %s because %s is newer." % (self.target, r.loc, p))
                         remake = True
                 if remake:
                     self.remake()
                     r.execute(self, makefile)
                     didanything = True
         else:
             commandrule = None
             remake = False
             if self.mtime is None:
-                log.info("Remaking %s because it doesn't exist or is a forced target" % (self.target,))
+                log.info(indent + "Remaking %s because it doesn't exist or is a forced target" % (self.target,))
                 remake = True
 
             for r in self.rules:
                 if len(r.commands):
                     assert commandrule is None, "Two command rules for a single-colon target?"
                     commandrule = r
                 for p in r.prerequisites:
                     dep = makefile.gettarget(p)
-                    didanything = dep.make(makefile, [], []) or didanything
+                    didanything = dep.make(makefile, targetstack, []) or didanything
                     if not remake and mtimeislater(dep.mtime, self.mtime):
-                        log.info("Remaking %s because %s is newer" % (self.target, p))
+                        log.info(indent + "Remaking %s because %s is newer" % (self.target, p))
                         remake = True
 
             if remake:
                 self.remake()
                 if commandrule is not None:
                     commandrule.execute(self, makefile)
                 didanything = True
 
@@ -1026,28 +1038,28 @@ class Makefile(object):
 
         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):
+    def include(self, path, required=True, loc=None):
         """
         Include the makefile at `path`.
         """
         self.included.append(path)
         if os.path.exists(path):
             fd = open(path)
             self.variables.append('MAKEFILE_LIST', Variables.SOURCE_AUTOMATIC, path, None)
             pymake.parser.parsestream(fd, path, self)
             self.gettarget(path).explicit = True
         elif required:
-            raise DataError("Attempting to include file which doesn't exist.")
+            raise DataError("Attempting to include file '%s' which doesn't exist." % (path,), loc)
 
     def remakemakefiles(self):
         reparse = False
 
         for f in self.included:
             t = self.gettarget(f)
             t.explicit = True
             t.resolvevpath(self)
--- a/pymake/functions.py
+++ b/pymake/functions.py
@@ -53,17 +53,17 @@ class VariableRef(Function):
 
     def resolve(self, variables, setting):
         vname = self.vname.resolve(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.info("%s: variable '%s' was not set" % (self.loc, vname))
+            log.debug("%s: variable '%s' was not set" % (self.loc, vname))
             return ''
 
         return value.resolve(variables, setting + [vname])
 
 class SubstitutionRef(Function):
     """$(VARNAME:.c=.o) and $(VARNAME:%.c=%.o)"""
     def __init__(self, loc, varname, substfrom, substto):
         self.loc = loc
@@ -79,17 +79,17 @@ class SubstitutionRef(Function):
         if vname in setting:
             raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
 
         substfrom = self.substfrom.resolve(variables, setting)
         substto = self.substto.resolve(variables, setting)
 
         flavor, source, value = variables.get(vname)
         if value is None:
-            log.info("%s: variable '%s' was not set" % (self.loc, vname))
+            log.debug("%s: variable '%s' was not set" % (self.loc, vname))
             return ''
 
         evalue = value.resolve(variables, setting + [vname])
         words = data.splitwords(evalue)
 
         f = data.Pattern(substfrom)
         if not f.ispattern():
             f = data.Pattern('%' + substfrom)
--- a/pymake/parser.py
+++ b/pymake/parser.py
@@ -513,17 +513,17 @@ def parsestream(fd, filename, makefile):
 
     while True:
         d = Data(fdlines, filename)
         if not d.readline():
             break
 
         if len(d.data) > 0 and d.data[0] == '\t' and currule is not None:
             if any((not c.active for c in condstack)):
-                log.info('%s: skipping line because of active conditions' % (d.getloc(0),))
+                log.debug('%s: skipping line because of active conditions' % (d.getloc(0),))
                 continue
 
             e, t, o = parsemakesyntax(d, 1, (), itercommandchars)
             assert t == None
             currule.addcommand(e)
         else:
             # To parse Makefile syntax, we first strip leading whitespace and
             # look for initial keywords. If there are no keywords, it's either
@@ -560,17 +560,17 @@ def parsestream(fd, filename, makefile):
                 continue
 
             if kword in conditionkeywords:
                 m = conditionkeywords[kword](d, offset, makefile)
                 condstack.append(Condition(m, d.getloc(offset)))
                 continue
 
             if any((not c.active for c in condstack)):
-                log.info('%s: skipping line because of active conditions' % (d.getloc(0),))
+                log.debug('%s: skipping line because of active conditions' % (d.getloc(0),))
                 for c in itermakefilechars(d, offset):
                     pass
                 continue
 
             if kword == 'endef':
                 raise SyntaxError("Unmatched endef", d.getloc(offset))
 
             if kword == 'define':
@@ -585,17 +585,17 @@ def parsestream(fd, filename, makefile):
                             skipwhitespace=False)
 
                 continue
 
             if kword in ('include', '-include'):
                 incfile, t, offset = parsemakesyntax(d, offset, (), itermakefilechars)
                 files = data.splitwords(incfile.resolve(makefile.variables))
                 for f in files:
-                    makefile.include(f, kword == 'include')
+                    makefile.include(f, kword == 'include', loc=d.getloc(offset))
                 continue
 
             if kword == 'override':
                 e, token, offset = parsemakesyntax(d, offset, varsettokens, itermakefilechars)
                 e.lstrip()
                 e.rstrip()
 
                 if token is None:
@@ -718,17 +718,20 @@ def parsestream(fd, filename, makefile):
                     pattern = data.Pattern(patterns[0])
 
                     e, token, offset = parsemakesyntax(d, offset, (';',), itermakefilechars)
                     prereqs = map(data.Pattern, data.splitwords(e.resolve(makefile.variables)))
                     currule = data.PatternRule([pattern], prereqs, doublecolon, loc=d.getloc(0))
 
                     for t in targets:
                         tname = t.gettarget()
-                        pinstance = data.PatternRuleInstance(currule, '', tname, pattern.ismatchany())
+                        stem = pattern.match(tname)
+                        if stem is None:
+                            raise SyntaxError("Target '%s' of static pattern rule does not match pattern '%s'" % (tname, pattern), d.getloc(0))
+                        pinstance = data.PatternRuleInstance(currule, '', stem, pattern.ismatchany())
                         makefile.gettarget(tname).addrule(pinstance)
 
                     if len(targets):
                         makefile.foundtarget(targets[0].gettarget())
 
                     if token == ';':
                         offset = d.skipwhitespace(offset)
                         e, token, offset = parsemakesyntax(d, offset, (), itercommandchars)
new file mode 100644
--- /dev/null
+++ b/tests/static-pattern2.mk
@@ -0,0 +1,10 @@
+all: foo.out
+	test -f $^
+	@echo TEST-PASS
+
+foo.out: %.out: %.in
+	test "$*" = "foo"
+	cp $^ $@
+
+foo.in:
+	touch $@