Implement automatic wildcard expansion in targets and prerequisites. I hate this, but NSS uses it, and I hate NSS more.
authorBenjamin Smedberg <benjamin@smedbergs.us>
Tue, 17 Feb 2009 14:46:34 -0500
changeset 123 17169ca68e03
parent 122 1995f94b1c2f
child 124 2545f3385285
push id69
push userbsmedberg@mozilla.com
push date2009-02-17 20:06 +0000
Implement automatic wildcard expansion in targets and prerequisites. I hate this, but NSS uses it, and I hate NSS more.
pymake/data.py
pymake/functions.py
pymake/parser.py
tests/wildcards.mk
--- a/pymake/data.py
+++ b/pymake/data.py
@@ -1015,17 +1015,17 @@ class Makefile(object):
     def gettarget(self, target):
         assert isinstance(target, str)
 
         target = target.rstrip('/')
 
         assert target != '', "empty target?"
 
         if target.find('*') != -1 or target.find('?') != -1 or target.find('[') != -1:
-            raise DataError("pymake doesn't implement wildcards in targets/prerequisites.")
+            raise DataError("wildcards should have been expanded by the parser: '%s'" % (target,))
 
         t = self._targets.get(target, None)
         if t is None:
             t = Target(target, self)
             self._targets[target] = t
         return t
 
     def appendimplicitrule(self, rule):
--- a/pymake/functions.py
+++ b/pymake/functions.py
@@ -358,17 +358,20 @@ class JoinFunction(Function):
 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
         pattern = self._arguments[0].resolve(makefile, variables, setting)
-        return ' '.join(glob.glob(pattern))
+
+        r = glob.glob(pattern)
+        r.sort()
+        return ' '.join(r)
 
 class RealpathFunction(Function):
     name = 'realpath'
     minargs = 1
     maxargs = 1
 
     def resolve(self, makefile, variables, setting):
         # TODO: will need work when we support -C without actually changing the OS cwd
--- a/pymake/parser.py
+++ b/pymake/parser.py
@@ -13,17 +13,17 @@ Lines with command syntax do not condens
 
 Lines with an initial tab are commands if they can be (there is a rule or a command immediately preceding).
 Otherwise, they are parsed as makefile syntax.
 
 After splitting data into parseable chunks, we use a recursive-descent parser to
 nest parenthesized syntax.
 """
 
-import logging, re
+import logging, re, glob
 from pymake import data, functions
 from cStringIO import StringIO
 
 tabwidth = 4
 
 log = logging.getLogger('pymake.parser')
 
 class SyntaxError(Exception):
@@ -554,16 +554,26 @@ class Condition(object):
         if self.everactive:
             self.active = False
             return
 
         self.active = active
         if active:
             self.everactive = True
 
+def expandwildcards(makefile, tlist):
+    for t in tlist:
+        if t.find('*') == -1 and t.find('?') == -1 and t.find('[') == -1:
+            yield t
+        else:
+            l = glob.glob(t)
+            l.sort()
+            for r in l:
+                yield r
+
 conditiontokens = tuple(conditionkeywords.iterkeys())
 directivestokenlist = TokenList.get(conditiontokens + \
     ('else', 'endif', 'define', 'endef', 'override', 'include', '-include', 'vpath', 'export', 'unexport'))
 conditionkeywordstokenlist = TokenList.get(conditiontokens)
 
 varsettokens = (':=', '+=', '?=', '=')
 
 def parsestream(fd, filename, makefile):
@@ -741,31 +751,32 @@ def parsestream(fd, filename, makefile):
                 # `e` is targets or target patterns, which can end up as
                 # * a rule
                 # * an implicit rule
                 # * a static pattern rule
                 # * a target-specific variable definition
                 # * a pattern-specific variable definition
                 # any of the rules may have order-only prerequisites
                 # delimited by |, and a command delimited by ;
-                targets = map(data.Pattern, data.splitwords(e.resolve(makefile, makefile.variables)))
+                targets = data.splitwords(e.resolve(makefile, makefile.variables))
+                targets = [data.Pattern(p) for p in expandwildcards(makefile, targets)]
 
                 if len(targets):
                     ispatterns = set((t.ispattern() for t in targets))
                     if len(ispatterns) == 2:
                         raise SyntaxError("Mixed implicit and normal rule", d.getloc(offset))
                     ispattern, = ispatterns
                 else:
                     ispattern = False
 
                 e, token, offset = parsemakesyntax(d, offset,
                                                    varsettokens + (':', '|', ';'),
                                                    itermakefilechars)
                 if token in (None, ';'):
-                    prereqs = data.splitwords(e.resolve(makefile, makefile.variables))
+                    prereqs = [p for p in expandwildcards(makefile, data.splitwords(e.resolve(makefile, makefile.variables)))]
                     if ispattern:
                         currule = data.PatternRule(targets, map(data.Pattern, prereqs), doublecolon, loc=d.getloc(0))
                         makefile.appendimplicitrule(currule)
                     else:
                         currule = data.Rule(prereqs, doublecolon, loc=d.getloc(0))
                         for t in targets:
                             makefile.gettarget(t.gettarget()).addrule(currule)
                         if len(targets):
@@ -801,17 +812,17 @@ def parsestream(fd, filename, makefile):
                     patstr = e.resolve(makefile, makefile.variables)
                     patterns = data.splitwords(patstr)
                     if len(patterns) != 1:
                         raise SyntaxError("A static pattern rule may have only one pattern", d.getloc(offset))
 
                     pattern = data.Pattern(patterns[0])
 
                     e, token, offset = parsemakesyntax(d, offset, (';',), itermakefilechars)
-                    prereqs = map(data.Pattern, data.splitwords(e.resolve(makefile, makefile.variables)))
+                    prereqs = [data.Pattern(p) for p in expandwildcards(makefile, data.splitwords(e.resolve(makefile, makefile.variables)))]
                     currule = data.PatternRule([pattern], prereqs, doublecolon, loc=d.getloc(0))
 
                     for t in targets:
                         tname = t.gettarget()
                         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())
new file mode 100644
--- /dev/null
+++ b/tests/wildcards.mk
@@ -0,0 +1,21 @@
+$(shell \
+mkdir foo; \
+touch a.c b.c c.out foo/d.c; \
+sleep 1; \
+touch c.in; \
+)
+
+VPATH = foo
+
+all: c.out prog
+	test "$$(cat $<)" = "remadec.out"
+	@echo TEST-PASS
+
+*.out: %.out: %.in
+	test "$@" = c.out
+	test "$<" = c.in
+	printf "remade$@" >$@
+
+prog: *.c
+	test "$^" = "a.c b.c"
+	touch $@