~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to mozilla/build/pymake/pymake/data.py

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
A representation of makefile data structures.
 
3
"""
 
4
 
 
5
import logging, re, os, sys
 
6
import parserdata, parser, functions, process, util, implicit
 
7
from cStringIO import StringIO
 
8
 
 
9
_log = logging.getLogger('pymake.data')
 
10
 
 
11
class DataError(util.MakeError):
 
12
    pass
 
13
 
 
14
class ResolutionError(DataError):
 
15
    """
 
16
    Raised when dependency resolution fails, either due to recursion or to missing
 
17
    prerequisites.This is separately catchable so that implicit rule search can try things
 
18
    without having to commit.
 
19
    """
 
20
    pass
 
21
 
 
22
def withoutdups(it):
 
23
    r = set()
 
24
    for i in it:
 
25
        if not i in r:
 
26
            r.add(i)
 
27
            yield i
 
28
 
 
29
def mtimeislater(deptime, targettime):
 
30
    """
 
31
    Is the mtime of the dependency later than the target?
 
32
    """
 
33
 
 
34
    if deptime is None:
 
35
        return True
 
36
    if targettime is None:
 
37
        return False
 
38
    return deptime > targettime
 
39
 
 
40
def getmtime(path):
 
41
    try:
 
42
        s = os.stat(path)
 
43
        return s.st_mtime
 
44
    except OSError:
 
45
        return None
 
46
 
 
47
def stripdotslash(s):
 
48
    if s.startswith('./'):
 
49
        st = s[2:]
 
50
        return st if st != '' else '.'
 
51
    return s
 
52
 
 
53
def stripdotslashes(sl):
 
54
    for s in sl:
 
55
        yield stripdotslash(s)
 
56
 
 
57
def getindent(stack):
 
58
    return ''.ljust(len(stack) - 1)
 
59
 
 
60
def _if_else(c, t, f):
 
61
    if c:
 
62
        return t()
 
63
    return f()
 
64
 
 
65
 
 
66
class BaseExpansion(object):
 
67
    """Base class for expansions.
 
68
 
 
69
    A make expansion is the parsed representation of a string, which may
 
70
    contain references to other elements.
 
71
    """
 
72
 
 
73
    @property
 
74
    def is_static_string(self):
 
75
        """Returns whether the expansion is composed of static string content.
 
76
 
 
77
        This is always True for StringExpansion. It will be True for Expansion
 
78
        only if all elements of that Expansion are static strings.
 
79
        """
 
80
        raise Exception('Must be implemented in child class.')
 
81
 
 
82
    def functions(self, descend=False):
 
83
        """Obtain all functions inside this expansion.
 
84
 
 
85
        This is a generator for pymake.functions.Function instances.
 
86
 
 
87
        By default, this only returns functions existing as the primary
 
88
        elements of this expansion. If `descend` is True, it will descend into
 
89
        child expansions and extract all functions in the tree.
 
90
        """
 
91
        # An empty generator. Yeah, it's weird.
 
92
        for x in []:
 
93
            yield x
 
94
 
 
95
    def variable_references(self, descend=False):
 
96
        """Obtain all variable references in this expansion.
 
97
 
 
98
        This is a generator for pymake.functionsVariableRef instances.
 
99
 
 
100
        To retrieve the names of variables, simply query the `vname` field on
 
101
        the returned instances. Most of the time these will be StringExpansion
 
102
        instances.
 
103
        """
 
104
        for f in self.functions(descend=descend):
 
105
            if not isinstance(f, functions.VariableRef):
 
106
                continue
 
107
 
 
108
            yield f
 
109
 
 
110
    @property
 
111
    def is_filesystem_dependent(self):
 
112
        """Whether this expansion may query the filesystem for evaluation.
 
113
 
 
114
        This effectively asks "is any function in this expansion dependent on
 
115
        the filesystem.
 
116
        """
 
117
        for f in self.functions(descend=True):
 
118
            if f.is_filesystem_dependent:
 
119
                return True
 
120
 
 
121
        return False
 
122
 
 
123
    @property
 
124
    def is_shell_dependent(self):
 
125
        """Whether this expansion may invoke a shell for evaluation."""
 
126
 
 
127
        for f in self.functions(descend=True):
 
128
            if isinstance(f, functions.ShellFunction):
 
129
                return True
 
130
 
 
131
        return False
 
132
 
 
133
 
 
134
class StringExpansion(BaseExpansion):
 
135
    """An Expansion representing a static string.
 
136
 
 
137
    This essentially wraps a single str instance.
 
138
    """
 
139
 
 
140
    __slots__ = ('loc', 's',)
 
141
    simple = True
 
142
 
 
143
    def __init__(self, s, loc):
 
144
        assert isinstance(s, str)
 
145
        self.s = s
 
146
        self.loc = loc
 
147
 
 
148
    def lstrip(self):
 
149
        self.s = self.s.lstrip()
 
150
 
 
151
    def rstrip(self):
 
152
        self.s = self.s.rstrip()
 
153
 
 
154
    def isempty(self):
 
155
        return self.s == ''
 
156
 
 
157
    def resolve(self, i, j, fd, k=None):
 
158
        fd.write(self.s)
 
159
 
 
160
    def resolvestr(self, i, j, k=None):
 
161
        return self.s
 
162
 
 
163
    def resolvesplit(self, i, j, k=None):
 
164
        return self.s.split()
 
165
 
 
166
    def clone(self):
 
167
        e = Expansion(self.loc)
 
168
        e.appendstr(self.s)
 
169
        return e
 
170
 
 
171
    @property
 
172
    def is_static_string(self):
 
173
        return True
 
174
 
 
175
    def __len__(self):
 
176
        return 1
 
177
 
 
178
    def __getitem__(self, i):
 
179
        assert i == 0
 
180
        return self.s, False
 
181
 
 
182
    def __repr__(self):
 
183
        return "Exp<%s>(%r)" % (self.loc, self.s)
 
184
 
 
185
    def __eq__(self, other):
 
186
        """We only compare the string contents."""
 
187
        return self.s == other
 
188
 
 
189
    def __ne__(self, other):
 
190
        return not self.__eq__(other)
 
191
 
 
192
    def to_source(self, escape_variables=False, escape_comments=False):
 
193
        s = self.s
 
194
 
 
195
        if escape_comments:
 
196
            s = s.replace('#', '\\#')
 
197
 
 
198
        if escape_variables:
 
199
            return s.replace('$', '$$')
 
200
 
 
201
        return s
 
202
 
 
203
 
 
204
class Expansion(BaseExpansion, list):
 
205
    """A representation of expanded data.
 
206
 
 
207
    This is effectively an ordered list of StringExpansion and
 
208
    pymake.function.Function instances. Every item in the collection appears in
 
209
    the same context in a make file.
 
210
    """
 
211
 
 
212
    __slots__ = ('loc',)
 
213
    simple = False
 
214
 
 
215
    def __init__(self, loc=None):
 
216
        # A list of (element, isfunc) tuples
 
217
        # element is either a string or a function
 
218
        self.loc = loc
 
219
 
 
220
    @staticmethod
 
221
    def fromstring(s, path):
 
222
        return StringExpansion(s, parserdata.Location(path, 1, 0))
 
223
 
 
224
    def clone(self):
 
225
        e = Expansion()
 
226
        e.extend(self)
 
227
        return e
 
228
 
 
229
    def appendstr(self, s):
 
230
        assert isinstance(s, str)
 
231
        if s == '':
 
232
            return
 
233
 
 
234
        self.append((s, False))
 
235
 
 
236
    def appendfunc(self, func):
 
237
        assert isinstance(func, functions.Function)
 
238
        self.append((func, True))
 
239
 
 
240
    def concat(self, o):
 
241
        """Concatenate the other expansion on to this one."""
 
242
        if o.simple:
 
243
            self.appendstr(o.s)
 
244
        else:
 
245
            self.extend(o)
 
246
 
 
247
    def isempty(self):
 
248
        return (not len(self)) or self[0] == ('', False)
 
249
 
 
250
    def lstrip(self):
 
251
        """Strip leading literal whitespace from this expansion."""
 
252
        while True:
 
253
            i, isfunc = self[0]
 
254
            if isfunc:
 
255
                return
 
256
 
 
257
            i = i.lstrip()
 
258
            if i != '':
 
259
                self[0] = i, False
 
260
                return
 
261
 
 
262
            del self[0]
 
263
 
 
264
    def rstrip(self):
 
265
        """Strip trailing literal whitespace from this expansion."""
 
266
        while True:
 
267
            i, isfunc = self[-1]
 
268
            if isfunc:
 
269
                return
 
270
 
 
271
            i = i.rstrip()
 
272
            if i != '':
 
273
                self[-1] = i, False
 
274
                return
 
275
 
 
276
            del self[-1]
 
277
 
 
278
    def finish(self):
 
279
        # Merge any adjacent literal strings:
 
280
        strings = []
 
281
        elements = []
 
282
        for (e, isfunc) in self:
 
283
            if isfunc:
 
284
                if strings:
 
285
                    s = ''.join(strings)
 
286
                    if s:
 
287
                        elements.append((s, False))
 
288
                    strings = []
 
289
                elements.append((e, True))
 
290
            else:
 
291
                strings.append(e)
 
292
 
 
293
        if not elements:
 
294
            # This can only happen if there were no function elements.
 
295
            return StringExpansion(''.join(strings), self.loc)
 
296
 
 
297
        if strings:
 
298
            s = ''.join(strings)
 
299
            if s:
 
300
                elements.append((s, False))
 
301
 
 
302
        if len(elements) < len(self):
 
303
            self[:] = elements
 
304
 
 
305
        return self
 
306
 
 
307
    def resolve(self, makefile, variables, fd, setting=[]):
 
308
        """
 
309
        Resolve this variable into a value, by interpolating the value
 
310
        of other variables.
 
311
 
 
312
        @param setting (Variable instance) the variable currently
 
313
               being set, if any. Setting variables must avoid self-referential
 
314
               loops.
 
315
        """
 
316
        assert isinstance(makefile, Makefile)
 
317
        assert isinstance(variables, Variables)
 
318
        assert isinstance(setting, list)
 
319
 
 
320
        for e, isfunc in self:
 
321
            if isfunc:
 
322
                e.resolve(makefile, variables, fd, setting)
 
323
            else:
 
324
                assert isinstance(e, str)
 
325
                fd.write(e)
 
326
                    
 
327
    def resolvestr(self, makefile, variables, setting=[]):
 
328
        fd = StringIO()
 
329
        self.resolve(makefile, variables, fd, setting)
 
330
        return fd.getvalue()
 
331
 
 
332
    def resolvesplit(self, makefile, variables, setting=[]):
 
333
        return self.resolvestr(makefile, variables, setting).split()
 
334
 
 
335
    @property
 
336
    def is_static_string(self):
 
337
        """An Expansion is static if all its components are strings, not
 
338
        functions."""
 
339
        for e, is_func in self:
 
340
            if is_func:
 
341
                return False
 
342
 
 
343
        return True
 
344
 
 
345
    def functions(self, descend=False):
 
346
        for e, is_func in self:
 
347
            if is_func:
 
348
                yield e
 
349
 
 
350
            if descend:
 
351
                for exp in e.expansions(descend=True):
 
352
                    for f in exp.functions(descend=True):
 
353
                        yield f
 
354
 
 
355
    def __repr__(self):
 
356
        return "<Expansion with elements: %r>" % ([e for e, isfunc in self],)
 
357
 
 
358
    def to_source(self, escape_variables=False, escape_comments=False):
 
359
        parts = []
 
360
        for e, is_func in self:
 
361
            if is_func:
 
362
                parts.append(e.to_source())
 
363
                continue
 
364
 
 
365
            if escape_variables:
 
366
                parts.append(e.replace('$', '$$'))
 
367
                continue
 
368
 
 
369
            parts.append(e)
 
370
 
 
371
        return ''.join(parts)
 
372
 
 
373
    def __eq__(self, other):
 
374
        if not isinstance(other, (Expansion, StringExpansion)):
 
375
            return False
 
376
 
 
377
        # Expansions are equivalent if adjacent string literals normalize to
 
378
        # the same value. So, we must normalize before any comparisons are
 
379
        # made.
 
380
        a = self.clone().finish()
 
381
 
 
382
        if isinstance(other, StringExpansion):
 
383
            if isinstance(a, StringExpansion):
 
384
                return a == other
 
385
 
 
386
            # A normalized Expansion != StringExpansion.
 
387
            return False
 
388
 
 
389
        b = other.clone().finish()
 
390
 
 
391
        # b could be a StringExpansion now.
 
392
        if isinstance(b, StringExpansion):
 
393
            if isinstance(a, StringExpansion):
 
394
                return a == b
 
395
 
 
396
            # Our normalized Expansion != normalized StringExpansion.
 
397
            return False
 
398
 
 
399
        if len(a) != len(b):
 
400
            return False
 
401
 
 
402
        for i in xrange(len(self)):
 
403
            e1, is_func1 = a[i]
 
404
            e2, is_func2 = b[i]
 
405
 
 
406
            if is_func1 != is_func2:
 
407
                return False
 
408
 
 
409
            if type(e1) != type(e2):
 
410
                return False
 
411
 
 
412
            if e1 != e2:
 
413
                return False
 
414
 
 
415
        return True
 
416
 
 
417
    def __ne__(self, other):
 
418
        return not self.__eq__(other)
 
419
 
 
420
class Variables(object):
 
421
    """
 
422
    A mapping from variable names to variables. Variables have flavor, source, and value. The value is an 
 
423
    expansion object.
 
424
    """
 
425
 
 
426
    __slots__ = ('parent', '_map')
 
427
 
 
428
    FLAVOR_RECURSIVE = 0
 
429
    FLAVOR_SIMPLE = 1
 
430
    FLAVOR_APPEND = 2
 
431
 
 
432
    SOURCE_OVERRIDE = 0
 
433
    SOURCE_COMMANDLINE = 1
 
434
    SOURCE_MAKEFILE = 2
 
435
    SOURCE_ENVIRONMENT = 3
 
436
    SOURCE_AUTOMATIC = 4
 
437
    SOURCE_IMPLICIT = 5
 
438
 
 
439
    def __init__(self, parent=None):
 
440
        self._map = {} # vname -> flavor, source, valuestr, valueexp
 
441
        self.parent = parent
 
442
 
 
443
    def readfromenvironment(self, env):
 
444
        for k, v in env.iteritems():
 
445
            self.set(k, self.FLAVOR_SIMPLE, self.SOURCE_ENVIRONMENT, v)
 
446
 
 
447
    def get(self, name, expand=True):
 
448
        """
 
449
        Get the value of a named variable. Returns a tuple (flavor, source, value)
 
450
 
 
451
        If the variable is not present, returns (None, None, None)
 
452
 
 
453
        @param expand If true, the value will be returned as an expansion. If false,
 
454
        it will be returned as an unexpanded string.
 
455
        """
 
456
        flavor, source, valuestr, valueexp = self._map.get(name, (None, None, None, None))
 
457
        if flavor is not None:
 
458
            if expand and flavor != self.FLAVOR_SIMPLE and valueexp is None:
 
459
                d = parser.Data.fromstring(valuestr, parserdata.Location("Expansion of variables '%s'" % (name,), 1, 0))
 
460
                valueexp, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
 
461
                self._map[name] = flavor, source, valuestr, valueexp
 
462
 
 
463
            if flavor == self.FLAVOR_APPEND:
 
464
                if self.parent:
 
465
                    pflavor, psource, pvalue = self.parent.get(name, expand)
 
466
                else:
 
467
                    pflavor, psource, pvalue = None, None, None
 
468
 
 
469
                if pvalue is None:
 
470
                    flavor = self.FLAVOR_RECURSIVE
 
471
                    # fall through
 
472
                else:
 
473
                    if source > psource:
 
474
                        # TODO: log a warning?
 
475
                        return pflavor, psource, pvalue
 
476
 
 
477
                    if not expand:
 
478
                        return pflavor, psource, pvalue + ' ' + valuestr
 
479
 
 
480
                    pvalue = pvalue.clone()
 
481
                    pvalue.appendstr(' ')
 
482
                    pvalue.concat(valueexp)
 
483
 
 
484
                    return pflavor, psource, pvalue
 
485
                    
 
486
            if not expand:
 
487
                return flavor, source, valuestr
 
488
 
 
489
            if flavor == self.FLAVOR_RECURSIVE:
 
490
                val = valueexp
 
491
            else:
 
492
                val = Expansion.fromstring(valuestr, "Expansion of variable '%s'" % (name,))
 
493
 
 
494
            return flavor, source, val
 
495
 
 
496
        if self.parent is not None:
 
497
            return self.parent.get(name, expand)
 
498
 
 
499
        return (None, None, None)
 
500
 
 
501
    def set(self, name, flavor, source, value):
 
502
        assert flavor in (self.FLAVOR_RECURSIVE, self.FLAVOR_SIMPLE)
 
503
        assert source in (self.SOURCE_OVERRIDE, self.SOURCE_COMMANDLINE, self.SOURCE_MAKEFILE, self.SOURCE_ENVIRONMENT, self.SOURCE_AUTOMATIC, self.SOURCE_IMPLICIT)
 
504
        assert isinstance(value, str), "expected str, got %s" % type(value)
 
505
 
 
506
        prevflavor, prevsource, prevvalue = self.get(name)
 
507
        if prevsource is not None and source > prevsource:
 
508
            # TODO: give a location for this warning
 
509
            _log.info("not setting variable '%s', set by higher-priority source to value '%s'" % (name, prevvalue))
 
510
            return
 
511
 
 
512
        self._map[name] = flavor, source, value, None
 
513
 
 
514
    def append(self, name, source, value, variables, makefile):
 
515
        assert source in (self.SOURCE_OVERRIDE, self.SOURCE_MAKEFILE, self.SOURCE_AUTOMATIC)
 
516
        assert isinstance(value, str)
 
517
 
 
518
        if name not in self._map:
 
519
            self._map[name] = self.FLAVOR_APPEND, source, value, None
 
520
            return
 
521
 
 
522
        prevflavor, prevsource, prevvalue, valueexp = self._map[name]
 
523
        if source > prevsource:
 
524
            # TODO: log a warning?
 
525
            return
 
526
 
 
527
        if prevflavor == self.FLAVOR_SIMPLE:
 
528
            d = parser.Data.fromstring(value, parserdata.Location("Expansion of variables '%s'" % (name,), 1, 0))
 
529
            valueexp, t, o = parser.parsemakesyntax(d, 0, (), parser.iterdata)
 
530
 
 
531
            val = valueexp.resolvestr(makefile, variables, [name])
 
532
            self._map[name] = prevflavor, prevsource, prevvalue + ' ' + val, None
 
533
            return
 
534
 
 
535
        newvalue = prevvalue + ' ' + value
 
536
        self._map[name] = prevflavor, prevsource, newvalue, None
 
537
 
 
538
    def merge(self, other):
 
539
        assert isinstance(other, Variables)
 
540
        for k, flavor, source, value in other:
 
541
            self.set(k, flavor, source, value)
 
542
 
 
543
    def __iter__(self):
 
544
        for k, (flavor, source, value, valueexp) in self._map.iteritems():
 
545
            yield k, flavor, source, value
 
546
 
 
547
    def __contains__(self, item):
 
548
        return item in self._map
 
549
 
 
550
class Pattern(object):
 
551
    """
 
552
    A pattern is a string, possibly with a % substitution character. From the GNU make manual:
 
553
 
 
554
    '%' characters in pattern rules can be quoted with precending backslashes ('\'). Backslashes that
 
555
    would otherwise quote '%' charcters can be quoted with more backslashes. Backslashes that
 
556
    quote '%' characters or other backslashes are removed from the pattern before it is compared t
 
557
    file names or has a stem substituted into it. Backslashes that are not in danger of quoting '%'
 
558
    characters go unmolested. For example, the pattern the\%weird\\%pattern\\ has `the%weird\' preceding
 
559
    the operative '%' character, and 'pattern\\' following it. The final two backslashes are left alone
 
560
    because they cannot affect any '%' character.
 
561
 
 
562
    This insane behavior probably doesn't matter, but we're compatible just for shits and giggles.
 
563
    """
 
564
 
 
565
    __slots__ = ('data')
 
566
 
 
567
    def __init__(self, s):
 
568
        r = []
 
569
        i = 0
 
570
        while i < len(s):
 
571
            c = s[i]
 
572
            if c == '\\':
 
573
                nc = s[i + 1]
 
574
                if nc == '%':
 
575
                    r.append('%')
 
576
                    i += 1
 
577
                elif nc == '\\':
 
578
                    r.append('\\')
 
579
                    i += 1
 
580
                else:
 
581
                    r.append(c)
 
582
            elif c == '%':
 
583
                self.data = (''.join(r), s[i+1:])
 
584
                return
 
585
            else:
 
586
                r.append(c)
 
587
            i += 1
 
588
 
 
589
        # This is different than (s,) because \% and \\ have been unescaped. Parsing patterns is
 
590
        # context-sensitive!
 
591
        self.data = (''.join(r),)
 
592
 
 
593
    def ismatchany(self):
 
594
        return self.data == ('','')
 
595
 
 
596
    def ispattern(self):
 
597
        return len(self.data) == 2
 
598
 
 
599
    def __hash__(self):
 
600
        return self.data.__hash__()
 
601
 
 
602
    def __eq__(self, o):
 
603
        assert isinstance(o, Pattern)
 
604
        return self.data == o.data
 
605
 
 
606
    def gettarget(self):
 
607
        assert not self.ispattern()
 
608
        return self.data[0]
 
609
 
 
610
    def hasslash(self):
 
611
        return self.data[0].find('/') != -1 or self.data[1].find('/') != -1
 
612
 
 
613
    def match(self, word):
 
614
        """
 
615
        Match this search pattern against a word (string).
 
616
 
 
617
        @returns None if the word doesn't match, or the matching stem.
 
618
                      If this is a %-less pattern, the stem will always be ''
 
619
        """
 
620
        d = self.data
 
621
        if len(d) == 1:
 
622
            if word == d[0]:
 
623
                return word
 
624
            return None
 
625
 
 
626
        d0, d1 = d
 
627
        l1 = len(d0)
 
628
        l2 = len(d1)
 
629
        if len(word) >= l1 + l2 and word.startswith(d0) and word.endswith(d1):
 
630
            if l2 == 0:
 
631
                return word[l1:]
 
632
            return word[l1:-l2]
 
633
 
 
634
        return None
 
635
 
 
636
    def resolve(self, dir, stem):
 
637
        if self.ispattern():
 
638
            return dir + self.data[0] + stem + self.data[1]
 
639
 
 
640
        return self.data[0]
 
641
 
 
642
    def subst(self, replacement, word, mustmatch):
 
643
        """
 
644
        Given a word, replace the current pattern with the replacement pattern, a la 'patsubst'
 
645
 
 
646
        @param mustmatch If true and this pattern doesn't match the word, throw a DataError. Otherwise
 
647
                         return word unchanged.
 
648
        """
 
649
        assert isinstance(replacement, str)
 
650
 
 
651
        stem = self.match(word)
 
652
        if stem is None:
 
653
            if mustmatch:
 
654
                raise DataError("target '%s' doesn't match pattern" % (word,))
 
655
            return word
 
656
 
 
657
        if not self.ispattern():
 
658
            # if we're not a pattern, the replacement is not parsed as a pattern either
 
659
            return replacement
 
660
 
 
661
        return Pattern(replacement).resolve('', stem)
 
662
 
 
663
    def __repr__(self):
 
664
        return "<Pattern with data %r>" % (self.data,)
 
665
 
 
666
    _backre = re.compile(r'[%\\]')
 
667
    def __str__(self):
 
668
        if not self.ispattern():
 
669
            return self._backre.sub(r'\\\1', self.data[0])
 
670
 
 
671
        return self._backre.sub(r'\\\1', self.data[0]) + '%' + self.data[1]
 
672
 
 
673
class RemakeTargetSerially(object):
 
674
    __slots__ = ('target', 'makefile', 'indent', 'rlist')
 
675
 
 
676
    def __init__(self, target, makefile, indent, rlist):
 
677
        self.target = target
 
678
        self.makefile = makefile
 
679
        self.indent = indent
 
680
        self.rlist = rlist
 
681
        self.commandscb(False)
 
682
 
 
683
    def resolvecb(self, error, didanything):
 
684
        assert error in (True, False)
 
685
 
 
686
        if didanything:
 
687
            self.target.didanything = True
 
688
 
 
689
        if error:
 
690
            self.target.error = True
 
691
            self.makefile.error = True
 
692
            if not self.makefile.keepgoing:
 
693
                self.target.notifydone(self.makefile)
 
694
                return
 
695
            else:
 
696
                # don't run the commands!
 
697
                del self.rlist[0]
 
698
                self.commandscb(error=False)
 
699
        else:
 
700
            self.rlist.pop(0).runcommands(self.indent, self.commandscb)
 
701
 
 
702
    def commandscb(self, error):
 
703
        assert error in (True, False)
 
704
 
 
705
        if error:
 
706
            self.target.error = True
 
707
            self.makefile.error = True
 
708
 
 
709
        if self.target.error and not self.makefile.keepgoing:
 
710
            self.target.notifydone(self.makefile)
 
711
            return
 
712
 
 
713
        if not len(self.rlist):
 
714
            self.target.notifydone(self.makefile)
 
715
        else:
 
716
            self.rlist[0].resolvedeps(True, self.resolvecb)
 
717
 
 
718
class RemakeTargetParallel(object):
 
719
    __slots__ = ('target', 'makefile', 'indent', 'rlist', 'rulesremaining', 'currunning')
 
720
 
 
721
    def __init__(self, target, makefile, indent, rlist):
 
722
        self.target = target
 
723
        self.makefile = makefile
 
724
        self.indent = indent
 
725
        self.rlist = rlist
 
726
 
 
727
        self.rulesremaining = len(rlist)
 
728
        self.currunning = False
 
729
 
 
730
        for r in rlist:
 
731
            makefile.context.defer(self.doresolve, r)
 
732
 
 
733
    def doresolve(self, r):
 
734
        if self.makefile.error and not self.makefile.keepgoing:
 
735
            r.error = True
 
736
            self.resolvecb(True, False)
 
737
        else:
 
738
            r.resolvedeps(False, self.resolvecb)
 
739
 
 
740
    def resolvecb(self, error, didanything):
 
741
        assert error in (True, False)
 
742
 
 
743
        if error:
 
744
            self.target.error = True
 
745
 
 
746
        if didanything:
 
747
            self.target.didanything = True
 
748
 
 
749
        self.rulesremaining -= 1
 
750
 
 
751
        # commandscb takes care of the details if we're currently building
 
752
        # something
 
753
        if self.currunning:
 
754
            return
 
755
 
 
756
        self.runnext()
 
757
 
 
758
    def runnext(self):
 
759
        assert not self.currunning
 
760
 
 
761
        if self.makefile.error and not self.makefile.keepgoing:
 
762
            self.rlist = []
 
763
        else:
 
764
            while len(self.rlist) and self.rlist[0].error:
 
765
                del self.rlist[0]
 
766
 
 
767
        if not len(self.rlist):
 
768
            if not self.rulesremaining:
 
769
                self.target.notifydone(self.makefile)
 
770
            return
 
771
 
 
772
        if self.rlist[0].depsremaining != 0:
 
773
            return
 
774
 
 
775
        self.currunning = True
 
776
        self.rlist.pop(0).runcommands(self.indent, self.commandscb)
 
777
 
 
778
    def commandscb(self, error):
 
779
        assert error in (True, False)
 
780
        if error:
 
781
            self.target.error = True
 
782
            self.makefile.error = True
 
783
 
 
784
        assert self.currunning
 
785
        self.currunning = False
 
786
        self.runnext()
 
787
 
 
788
class RemakeRuleContext(object):
 
789
    def __init__(self, target, makefile, rule, deps,
 
790
                 targetstack, avoidremakeloop):
 
791
        self.target = target
 
792
        self.makefile = makefile
 
793
        self.rule = rule
 
794
        self.deps = deps
 
795
        self.targetstack = targetstack
 
796
        self.avoidremakeloop = avoidremakeloop
 
797
 
 
798
        self.running = False
 
799
        self.error = False
 
800
        self.depsremaining = len(deps) + 1
 
801
        self.remake = False
 
802
 
 
803
    def resolvedeps(self, serial, cb):
 
804
        self.resolvecb = cb
 
805
        self.didanything = False
 
806
        if serial:
 
807
            self._resolvedepsserial()
 
808
        else:
 
809
            self._resolvedepsparallel()
 
810
 
 
811
    def _weakdepfinishedserial(self, error, didanything):
 
812
        if error:
 
813
            self.remake = True
 
814
        self._depfinishedserial(False, didanything)
 
815
 
 
816
    def _depfinishedserial(self, error, didanything):
 
817
        assert error in (True, False)
 
818
 
 
819
        if didanything:
 
820
            self.didanything = True
 
821
 
 
822
        if error:
 
823
            self.error = True
 
824
            if not self.makefile.keepgoing:
 
825
                self.resolvecb(error=True, didanything=self.didanything)
 
826
                return
 
827
        
 
828
        if len(self.resolvelist):
 
829
            dep, weak = self.resolvelist.pop(0)
 
830
            self.makefile.context.defer(dep.make,
 
831
                                        self.makefile, self.targetstack, weak and self._weakdepfinishedserial or self._depfinishedserial)
 
832
        else:
 
833
            self.resolvecb(error=self.error, didanything=self.didanything)
 
834
 
 
835
    def _resolvedepsserial(self):
 
836
        self.resolvelist = list(self.deps)
 
837
        self._depfinishedserial(False, False)
 
838
 
 
839
    def _startdepparallel(self, d):
 
840
        if self.makefile.error:
 
841
            depfinished(True, False)
 
842
        else:
 
843
            dep, weak = d
 
844
            dep.make(self.makefile, self.targetstack, weak and self._weakdepfinishedparallel or self._depfinishedparallel)
 
845
 
 
846
    def _weakdepfinishedparallel(self, error, didanything):
 
847
        if error:
 
848
            self.remake = True
 
849
        self._depfinishedparallel(False, didanything)
 
850
 
 
851
    def _depfinishedparallel(self, error, didanything):
 
852
        assert error in (True, False)
 
853
 
 
854
        if error:
 
855
            print "<%s>: Found error" % self.target.target
 
856
            self.error = True
 
857
        if didanything:
 
858
            self.didanything = True
 
859
 
 
860
        self.depsremaining -= 1
 
861
        if self.depsremaining == 0:
 
862
            self.resolvecb(error=self.error, didanything=self.didanything)
 
863
 
 
864
    def _resolvedepsparallel(self):
 
865
        self.depsremaining -= 1
 
866
        if self.depsremaining == 0:
 
867
            self.resolvecb(error=self.error, didanything=self.didanything)
 
868
            return
 
869
 
 
870
        self.didanything = False
 
871
 
 
872
        for d in self.deps:
 
873
            self.makefile.context.defer(self._startdepparallel, d)
 
874
 
 
875
    def _commandcb(self, error):
 
876
        assert error in (True, False)
 
877
 
 
878
        if error:
 
879
            self.runcb(error=True)
 
880
            return
 
881
 
 
882
        if len(self.commands):
 
883
            self.commands.pop(0)(self._commandcb)
 
884
        else:
 
885
            self.runcb(error=False)
 
886
 
 
887
    def runcommands(self, indent, cb):
 
888
        assert not self.running
 
889
        self.running = True
 
890
 
 
891
        self.runcb = cb
 
892
 
 
893
        if self.rule is None or not len(self.rule.commands):
 
894
            if self.target.mtime is None:
 
895
                self.target.beingremade()
 
896
            else:
 
897
                for d, weak in self.deps:
 
898
                    if mtimeislater(d.mtime, self.target.mtime):
 
899
                        if d.mtime is None:
 
900
                            self.target.beingremade()
 
901
                        else:
 
902
                            _log.info("%sNot remaking %s ubecause it would have no effect, even though %s is newer.", indent, self.target.target, d.target)
 
903
                        break
 
904
            cb(error=False)
 
905
            return
 
906
 
 
907
        if self.rule.doublecolon:
 
908
            if len(self.deps) == 0:
 
909
                if self.avoidremakeloop:
 
910
                    _log.info("%sNot remaking %s using rule at %s because it would introduce an infinite loop.", indent, self.target.target, self.rule.loc)
 
911
                    cb(error=False)
 
912
                    return
 
913
 
 
914
        remake = self.remake
 
915
        if remake:
 
916
            _log.info("%sRemaking %s using rule at %s: weak dependency was not found.", indent, self.target.target, self.rule.loc)
 
917
        else:
 
918
            if self.target.mtime is None:
 
919
                remake = True
 
920
                _log.info("%sRemaking %s using rule at %s: target doesn't exist or is a forced target", indent, self.target.target, self.rule.loc)
 
921
 
 
922
        if not remake:
 
923
            if self.rule.doublecolon:
 
924
                if len(self.deps) == 0:
 
925
                    _log.info("%sRemaking %s using rule at %s because there are no prerequisites listed for a double-colon rule.", indent, self.target.target, self.rule.loc)
 
926
                    remake = True
 
927
 
 
928
        if not remake:
 
929
            for d, weak in self.deps:
 
930
                if mtimeislater(d.mtime, self.target.mtime):
 
931
                    _log.info("%sRemaking %s using rule at %s because %s is newer.", indent, self.target.target, self.rule.loc, d.target)
 
932
                    remake = True
 
933
                    break
 
934
 
 
935
        if remake:
 
936
            self.target.beingremade()
 
937
            self.target.didanything = True
 
938
            try:
 
939
                self.commands = [c for c in self.rule.getcommands(self.target, self.makefile)]
 
940
            except util.MakeError, e:
 
941
                print e
 
942
                sys.stdout.flush()
 
943
                cb(error=True)
 
944
                return
 
945
 
 
946
            self._commandcb(False)
 
947
        else:
 
948
            cb(error=False)
 
949
 
 
950
MAKESTATE_NONE = 0
 
951
MAKESTATE_FINISHED = 1
 
952
MAKESTATE_WORKING = 2
 
953
 
 
954
class Target(object):
 
955
    """
 
956
    An actual (non-pattern) target.
 
957
 
 
958
    It holds target-specific variables and a list of rules. It may also point to a parent
 
959
    PatternTarget, if this target is being created by an implicit rule.
 
960
 
 
961
    The rules associated with this target may be Rule instances or, in the case of static pattern
 
962
    rules, PatternRule instances.
 
963
    """
 
964
 
 
965
    wasremade = False
 
966
 
 
967
    def __init__(self, target, makefile):
 
968
        assert isinstance(target, str)
 
969
        self.target = target
 
970
        self.vpathtarget = None
 
971
        self.rules = []
 
972
        self.variables = Variables(makefile.variables)
 
973
        self.explicit = False
 
974
        self._state = MAKESTATE_NONE
 
975
 
 
976
    def addrule(self, rule):
 
977
        assert isinstance(rule, (Rule, PatternRuleInstance))
 
978
        if len(self.rules) and rule.doublecolon != self.rules[0].doublecolon:
 
979
            raise DataError("Cannot have single- and double-colon rules for the same target. Prior rule location: %s" % self.rules[0].loc, rule.loc)
 
980
 
 
981
        if isinstance(rule, PatternRuleInstance):
 
982
            if len(rule.prule.targetpatterns) != 1:
 
983
                raise DataError("Static pattern rules must only have one target pattern", rule.prule.loc)
 
984
            if rule.prule.targetpatterns[0].match(self.target) is None:
 
985
                raise DataError("Static pattern rule doesn't match target '%s'" % self.target, rule.loc)
 
986
 
 
987
        self.rules.append(rule)
 
988
 
 
989
    def isdoublecolon(self):
 
990
        return self.rules[0].doublecolon
 
991
 
 
992
    def isphony(self, makefile):
 
993
        """Is this a phony target? We don't check for existence of phony targets."""
 
994
        return makefile.gettarget('.PHONY').hasdependency(self.target)
 
995
 
 
996
    def hasdependency(self, t):
 
997
        for rule in self.rules:
 
998
            if t in rule.prerequisites:
 
999
                return True
 
1000
 
 
1001
        return False
 
1002
 
 
1003
    def resolveimplicitrule(self, makefile, targetstack, rulestack):
 
1004
        """
 
1005
        Try to resolve an implicit rule to build this target.
 
1006
        """
 
1007
        # The steps in the GNU make manual Implicit-Rule-Search.html are very detailed. I hope they can be trusted.
 
1008
 
 
1009
        indent = getindent(targetstack)
 
1010
 
 
1011
        _log.info("%sSearching for implicit rule to make '%s'", indent, self.target)
 
1012
 
 
1013
        dir, s, file = util.strrpartition(self.target, '/')
 
1014
        dir = dir + s
 
1015
 
 
1016
        candidates = [] # list of PatternRuleInstance
 
1017
 
 
1018
        hasmatch = util.any((r.hasspecificmatch(file) for r in makefile.implicitrules))
 
1019
 
 
1020
        for r in makefile.implicitrules:
 
1021
            if r in rulestack:
 
1022
                _log.info("%s %s: Avoiding implicit rule recursion", indent, r.loc)
 
1023
                continue
 
1024
 
 
1025
            if not len(r.commands):
 
1026
                continue
 
1027
 
 
1028
            for ri in r.matchesfor(dir, file, hasmatch):
 
1029
                candidates.append(ri)
 
1030
            
 
1031
        newcandidates = []
 
1032
 
 
1033
        for r in candidates:
 
1034
            depfailed = None
 
1035
            for p in r.prerequisites:
 
1036
                t = makefile.gettarget(p)
 
1037
                t.resolvevpath(makefile)
 
1038
                if not t.explicit and t.mtime is None:
 
1039
                    depfailed = p
 
1040
                    break
 
1041
 
 
1042
            if depfailed is not None:
 
1043
                if r.doublecolon:
 
1044
                    _log.info("%s Terminal rule at %s doesn't match: prerequisite '%s' not mentioned and doesn't exist.", indent, r.loc, depfailed)
 
1045
                else:
 
1046
                    newcandidates.append(r)
 
1047
                continue
 
1048
 
 
1049
            _log.info("%sFound implicit rule at %s for target '%s'", indent, r.loc, self.target)
 
1050
            self.rules.append(r)
 
1051
            return
 
1052
 
 
1053
        # Try again, but this time with chaining and without terminal (double-colon) rules
 
1054
 
 
1055
        for r in newcandidates:
 
1056
            newrulestack = rulestack + [r.prule]
 
1057
 
 
1058
            depfailed = None
 
1059
            for p in r.prerequisites:
 
1060
                t = makefile.gettarget(p)
 
1061
                try:
 
1062
                    t.resolvedeps(makefile, targetstack, newrulestack, True)
 
1063
                except ResolutionError:
 
1064
                    depfailed = p
 
1065
                    break
 
1066
 
 
1067
            if depfailed is not None:
 
1068
                _log.info("%s Rule at %s doesn't match: prerequisite '%s' could not be made.", indent, r.loc, depfailed)
 
1069
                continue
 
1070
 
 
1071
            _log.info("%sFound implicit rule at %s for target '%s'", indent, r.loc, self.target)
 
1072
            self.rules.append(r)
 
1073
            return
 
1074
 
 
1075
        _log.info("%sCouldn't find implicit rule to remake '%s'", indent, self.target)
 
1076
 
 
1077
    def ruleswithcommands(self):
 
1078
        "The number of rules with commands"
 
1079
        return reduce(lambda i, rule: i + (len(rule.commands) > 0), self.rules, 0)
 
1080
 
 
1081
    def resolvedeps(self, makefile, targetstack, rulestack, recursive):
 
1082
        """
 
1083
        Resolve the actual path of this target, using vpath if necessary.
 
1084
 
 
1085
        Recursively resolve dependencies of this target. This means finding implicit
 
1086
        rules which match the target, if appropriate.
 
1087
 
 
1088
        Figure out whether this target needs to be rebuild, and set self.outofdate
 
1089
        appropriately.
 
1090
 
 
1091
        @param targetstack is the current stack of dependencies being resolved. If
 
1092
               this target is already in targetstack, bail to prevent infinite
 
1093
               recursion.
 
1094
        @param rulestack is the current stack of implicit rules being used to resolve
 
1095
               dependencies. A rule chain cannot use the same implicit rule twice.
 
1096
        """
 
1097
        assert makefile.parsingfinished
 
1098
 
 
1099
        if self.target in targetstack:
 
1100
            raise ResolutionError("Recursive dependency: %s -> %s" % (
 
1101
                    " -> ".join(targetstack), self.target))
 
1102
 
 
1103
        targetstack = targetstack + [self.target]
 
1104
        
 
1105
        indent = getindent(targetstack)
 
1106
 
 
1107
        _log.info("%sConsidering target '%s'", indent, self.target)
 
1108
 
 
1109
        self.resolvevpath(makefile)
 
1110
 
 
1111
        # Sanity-check our rules. If we're single-colon, only one rule should have commands
 
1112
        ruleswithcommands = self.ruleswithcommands()
 
1113
        if len(self.rules) and not self.isdoublecolon():
 
1114
            if ruleswithcommands > 1:
 
1115
                # In GNU make this is a warning, not an error. I'm going to be stricter.
 
1116
                # TODO: provide locations
 
1117
                raise DataError("Target '%s' has multiple rules with commands." % self.target)
 
1118
 
 
1119
        if ruleswithcommands == 0:
 
1120
            self.resolveimplicitrule(makefile, targetstack, rulestack)
 
1121
 
 
1122
        # If a target is mentioned, but doesn't exist, has no commands and no
 
1123
        # prerequisites, it is special and exists just to say that targets which
 
1124
        # depend on it are always out of date. This is like .FORCE but more
 
1125
        # compatible with other makes.
 
1126
        # Otherwise, we don't know how to make it.
 
1127
        if not len(self.rules) and self.mtime is None and not util.any((len(rule.prerequisites) > 0
 
1128
                                                                        for rule in self.rules)):
 
1129
            raise ResolutionError("No rule to make target '%s' needed by %r" % (self.target,
 
1130
                                                                                targetstack))
 
1131
 
 
1132
        if recursive:
 
1133
            for r in self.rules:
 
1134
                newrulestack = rulestack + [r]
 
1135
                for d in r.prerequisites:
 
1136
                    dt = makefile.gettarget(d)
 
1137
                    if dt.explicit:
 
1138
                        continue
 
1139
 
 
1140
                    dt.resolvedeps(makefile, targetstack, newrulestack, True)
 
1141
 
 
1142
        for v in makefile.getpatternvariablesfor(self.target):
 
1143
            self.variables.merge(v)
 
1144
 
 
1145
    def resolvevpath(self, makefile):
 
1146
        if self.vpathtarget is not None:
 
1147
            return
 
1148
 
 
1149
        if self.isphony(makefile):
 
1150
            self.vpathtarget = self.target
 
1151
            self.mtime = None
 
1152
            return
 
1153
 
 
1154
        if self.target.startswith('-l'):
 
1155
            stem = self.target[2:]
 
1156
            f, s, e = makefile.variables.get('.LIBPATTERNS')
 
1157
            if e is not None:
 
1158
                libpatterns = [Pattern(stripdotslash(s)) for s in e.resolvesplit(makefile, makefile.variables)]
 
1159
                if len(libpatterns):
 
1160
                    searchdirs = ['']
 
1161
                    searchdirs.extend(makefile.getvpath(self.target))
 
1162
 
 
1163
                    for lp in libpatterns:
 
1164
                        if not lp.ispattern():
 
1165
                            raise DataError('.LIBPATTERNS contains a non-pattern')
 
1166
 
 
1167
                        libname = lp.resolve('', stem)
 
1168
 
 
1169
                        for dir in searchdirs:
 
1170
                            libpath = util.normaljoin(dir, libname).replace('\\', '/')
 
1171
                            fspath = util.normaljoin(makefile.workdir, libpath)
 
1172
                            mtime = getmtime(fspath)
 
1173
                            if mtime is not None:
 
1174
                                self.vpathtarget = libpath
 
1175
                                self.mtime = mtime
 
1176
                                return
 
1177
 
 
1178
                    self.vpathtarget = self.target
 
1179
                    self.mtime = None
 
1180
                    return
 
1181
 
 
1182
        search = [self.target]
 
1183
        if not os.path.isabs(self.target):
 
1184
            search += [util.normaljoin(dir, self.target).replace('\\', '/')
 
1185
                       for dir in makefile.getvpath(self.target)]
 
1186
 
 
1187
        targetandtime = self.searchinlocs(makefile, search)
 
1188
        if targetandtime is not None:
 
1189
            (self.vpathtarget, self.mtime) = targetandtime
 
1190
            return
 
1191
 
 
1192
        self.vpathtarget = self.target
 
1193
        self.mtime = None
 
1194
 
 
1195
    def searchinlocs(self, makefile, locs):
 
1196
        """
 
1197
        Look in the given locations relative to the makefile working directory
 
1198
        for a file. Return a pair of the target and the mtime if found, None
 
1199
        if not.
 
1200
        """
 
1201
        for t in locs:
 
1202
            fspath = util.normaljoin(makefile.workdir, t).replace('\\', '/')
 
1203
            mtime = getmtime(fspath)
 
1204
#            _log.info("Searching %s ... checking %s ... mtime %r" % (t, fspath, mtime))
 
1205
            if mtime is not None:
 
1206
                return (t, mtime)
 
1207
 
 
1208
        return None
 
1209
        
 
1210
    def beingremade(self):
 
1211
        """
 
1212
        When we remake ourself, we have to drop any vpath prefixes.
 
1213
        """
 
1214
        self.vpathtarget = self.target
 
1215
        self.wasremade = True
 
1216
 
 
1217
    def notifydone(self, makefile):
 
1218
        assert self._state == MAKESTATE_WORKING, "State was %s" % self._state
 
1219
        # If we were remade then resolve mtime again
 
1220
        if self.wasremade:
 
1221
            targetandtime = self.searchinlocs(makefile, [self.target])
 
1222
            if targetandtime is not None:
 
1223
                (_, self.mtime) = targetandtime
 
1224
            else:
 
1225
                self.mtime = None
 
1226
 
 
1227
        self._state = MAKESTATE_FINISHED
 
1228
        for cb in self._callbacks:
 
1229
            makefile.context.defer(cb, error=self.error, didanything=self.didanything)
 
1230
        del self._callbacks 
 
1231
 
 
1232
    def make(self, makefile, targetstack, cb, avoidremakeloop=False, printerror=True):
 
1233
        """
 
1234
        If we are out of date, asynchronously make ourself. This is a multi-stage process, mostly handled
 
1235
        by the helper objects RemakeTargetSerially, RemakeTargetParallel,
 
1236
        RemakeRuleContext. These helper objects should keep us from developing
 
1237
        any cyclical dependencies.
 
1238
 
 
1239
        * resolve dependencies (synchronous)
 
1240
        * gather a list of rules to execute and related dependencies (synchronous)
 
1241
        * for each rule (in parallel)
 
1242
        ** remake dependencies (asynchronous)
 
1243
        ** build list of commands to execute (synchronous)
 
1244
        ** execute each command (asynchronous)
 
1245
        * asynchronously notify when all rules are complete
 
1246
 
 
1247
        @param cb A callback function to notify when remaking is finished. It is called
 
1248
               thusly: callback(error=True/False, didanything=True/False)
 
1249
               If there is no asynchronous activity to perform, the callback may be called directly.
 
1250
        """
 
1251
 
 
1252
        serial = makefile.context.jcount == 1
 
1253
        
 
1254
        if self._state == MAKESTATE_FINISHED:
 
1255
            cb(error=self.error, didanything=self.didanything)
 
1256
            return
 
1257
            
 
1258
        if self._state == MAKESTATE_WORKING:
 
1259
            assert not serial
 
1260
            self._callbacks.append(cb)
 
1261
            return
 
1262
 
 
1263
        assert self._state == MAKESTATE_NONE
 
1264
 
 
1265
        self._state = MAKESTATE_WORKING
 
1266
        self._callbacks = [cb]
 
1267
        self.error = False
 
1268
        self.didanything = False
 
1269
 
 
1270
        indent = getindent(targetstack)
 
1271
 
 
1272
        try:
 
1273
            self.resolvedeps(makefile, targetstack, [], False)
 
1274
        except util.MakeError, e:
 
1275
            if printerror:
 
1276
                print e
 
1277
            self.error = True
 
1278
            self.notifydone(makefile)
 
1279
            return
 
1280
 
 
1281
        assert self.vpathtarget is not None, "Target was never resolved!"
 
1282
        if not len(self.rules):
 
1283
            self.notifydone(makefile)
 
1284
            return
 
1285
 
 
1286
        if self.isdoublecolon():
 
1287
            rulelist = [RemakeRuleContext(self, makefile, r, [(makefile.gettarget(p), False) for p in r.prerequisites], targetstack, avoidremakeloop) for r in self.rules]
 
1288
        else:
 
1289
            alldeps = []
 
1290
 
 
1291
            commandrule = None
 
1292
            for r in self.rules:
 
1293
                rdeps = [(makefile.gettarget(p), r.weakdeps) for p in r.prerequisites]
 
1294
                if len(r.commands):
 
1295
                    assert commandrule is None
 
1296
                    commandrule = r
 
1297
                    # The dependencies of the command rule are resolved before other dependencies,
 
1298
                    # no matter the ordering of the other no-command rules
 
1299
                    alldeps[0:0] = rdeps
 
1300
                else:
 
1301
                    alldeps.extend(rdeps)
 
1302
 
 
1303
            rulelist = [RemakeRuleContext(self, makefile, commandrule, alldeps, targetstack, avoidremakeloop)]
 
1304
 
 
1305
        targetstack = targetstack + [self.target]
 
1306
 
 
1307
        if serial:
 
1308
            RemakeTargetSerially(self, makefile, indent, rulelist)
 
1309
        else:
 
1310
            RemakeTargetParallel(self, makefile, indent, rulelist)
 
1311
 
 
1312
def dirpart(p):
 
1313
    d, s, f = util.strrpartition(p, '/')
 
1314
    if d == '':
 
1315
        return '.'
 
1316
 
 
1317
    return d
 
1318
 
 
1319
def filepart(p):
 
1320
    d, s, f = util.strrpartition(p, '/')
 
1321
    return f
 
1322
 
 
1323
def setautomatic(v, name, plist):
 
1324
    v.set(name, Variables.FLAVOR_SIMPLE, Variables.SOURCE_AUTOMATIC, ' '.join(plist))
 
1325
    v.set(name + 'D', Variables.FLAVOR_SIMPLE, Variables.SOURCE_AUTOMATIC, ' '.join((dirpart(p) for p in plist)))
 
1326
    v.set(name + 'F', Variables.FLAVOR_SIMPLE, Variables.SOURCE_AUTOMATIC, ' '.join((filepart(p) for p in plist)))
 
1327
 
 
1328
def setautomaticvariables(v, makefile, target, prerequisites):
 
1329
    prtargets = [makefile.gettarget(p) for p in prerequisites]
 
1330
    prall = [pt.vpathtarget for pt in prtargets]
 
1331
    proutofdate = [pt.vpathtarget for pt in withoutdups(prtargets)
 
1332
                   if target.mtime is None or mtimeislater(pt.mtime, target.mtime)]
 
1333
    
 
1334
    setautomatic(v, '@', [target.vpathtarget])
 
1335
    if len(prall):
 
1336
        setautomatic(v, '<', [prall[0]])
 
1337
 
 
1338
    setautomatic(v, '?', proutofdate)
 
1339
    setautomatic(v, '^', list(withoutdups(prall)))
 
1340
    setautomatic(v, '+', prall)
 
1341
 
 
1342
def splitcommand(command):
 
1343
    """
 
1344
    Using the esoteric rules, split command lines by unescaped newlines.
 
1345
    """
 
1346
    start = 0
 
1347
    i = 0
 
1348
    while i < len(command):
 
1349
        c = command[i]
 
1350
        if c == '\\':
 
1351
            i += 1
 
1352
        elif c == '\n':
 
1353
            yield command[start:i]
 
1354
            i += 1
 
1355
            start = i
 
1356
            continue
 
1357
 
 
1358
        i += 1
 
1359
 
 
1360
    if i > start:
 
1361
        yield command[start:i]
 
1362
 
 
1363
def findmodifiers(command):
 
1364
    """
 
1365
    Find any of +-@% prefixed on the command.
 
1366
    @returns (command, isHidden, isRecursive, ignoreErrors, isNative)
 
1367
    """
 
1368
 
 
1369
    isHidden = False
 
1370
    isRecursive = False
 
1371
    ignoreErrors = False
 
1372
    isNative = False
 
1373
 
 
1374
    realcommand = command.lstrip(' \t\n@+-%')
 
1375
    modset = set(command[:-len(realcommand)])
 
1376
    return realcommand, '@' in modset, '+' in modset, '-' in modset, '%' in modset
 
1377
 
 
1378
class _CommandWrapper(object):
 
1379
    def __init__(self, cline, ignoreErrors, loc, context, **kwargs):
 
1380
        self.ignoreErrors = ignoreErrors
 
1381
        self.loc = loc
 
1382
        self.cline = cline
 
1383
        self.kwargs = kwargs
 
1384
        self.context = context
 
1385
 
 
1386
    def _cb(self, res):
 
1387
        if res != 0 and not self.ignoreErrors:
 
1388
            print "%s: command '%s' failed, return code %i" % (self.loc, self.cline, res)
 
1389
            self.usercb(error=True)
 
1390
        else:
 
1391
            self.usercb(error=False)
 
1392
 
 
1393
    def __call__(self, cb):
 
1394
        self.usercb = cb
 
1395
        process.call(self.cline, loc=self.loc, cb=self._cb, context=self.context, **self.kwargs)
 
1396
 
 
1397
class _NativeWrapper(_CommandWrapper):
 
1398
    def __init__(self, cline, ignoreErrors, loc, context,
 
1399
                 pycommandpath, **kwargs):
 
1400
        _CommandWrapper.__init__(self, cline, ignoreErrors, loc, context,
 
1401
                                 **kwargs)
 
1402
        # get the module and method to call
 
1403
        parts, badchar = process.clinetoargv(cline, blacklist_gray=False)
 
1404
        if parts is None:
 
1405
            raise DataError("native command '%s': shell metacharacter '%s' in command line" % (cline, badchar), self.loc)
 
1406
        if len(parts) < 2:
 
1407
            raise DataError("native command '%s': no method name specified" % cline, self.loc)
 
1408
        if pycommandpath:
 
1409
            self.pycommandpath = re.split('[%s\s]+' % os.pathsep,
 
1410
                                          pycommandpath)
 
1411
        else:
 
1412
            self.pycommandpath = None
 
1413
        self.module = parts[0]
 
1414
        self.method = parts[1]
 
1415
        self.cline_list = parts[2:]
 
1416
 
 
1417
    def __call__(self, cb):
 
1418
        self.usercb = cb
 
1419
        process.call_native(self.module, self.method, self.cline_list,
 
1420
                            loc=self.loc, cb=self._cb, context=self.context,
 
1421
                            pycommandpath=self.pycommandpath, **self.kwargs)
 
1422
 
 
1423
def getcommandsforrule(rule, target, makefile, prerequisites, stem):
 
1424
    v = Variables(parent=target.variables)
 
1425
    setautomaticvariables(v, makefile, target, prerequisites)
 
1426
    if stem is not None:
 
1427
        setautomatic(v, '*', [stem])
 
1428
 
 
1429
    env = makefile.getsubenvironment(v)
 
1430
 
 
1431
    for c in rule.commands:
 
1432
        cstring = c.resolvestr(makefile, v)
 
1433
        for cline in splitcommand(cstring):
 
1434
            cline, isHidden, isRecursive, ignoreErrors, isNative = findmodifiers(cline)
 
1435
            if (isHidden or makefile.silent) and not makefile.justprint:
 
1436
                echo = None
 
1437
            else:
 
1438
                echo = "%s$ %s" % (c.loc, cline)
 
1439
            if not isNative:
 
1440
                yield _CommandWrapper(cline, ignoreErrors=ignoreErrors, env=env, cwd=makefile.workdir, loc=c.loc, context=makefile.context,
 
1441
                                      echo=echo, justprint=makefile.justprint)
 
1442
            else:
 
1443
                f, s, e = v.get("PYCOMMANDPATH", True)
 
1444
                if e:
 
1445
                    e = e.resolvestr(makefile, v, ["PYCOMMANDPATH"])
 
1446
                yield _NativeWrapper(cline, ignoreErrors=ignoreErrors,
 
1447
                                     env=env, cwd=makefile.workdir,
 
1448
                                     loc=c.loc, context=makefile.context,
 
1449
                                     echo=echo, justprint=makefile.justprint,
 
1450
                                     pycommandpath=e)
 
1451
 
 
1452
class Rule(object):
 
1453
    """
 
1454
    A rule contains a list of prerequisites and a list of commands. It may also
 
1455
    contain rule-specific variables. This rule may be associated with multiple targets.
 
1456
    """
 
1457
 
 
1458
    def __init__(self, prereqs, doublecolon, loc, weakdeps):
 
1459
        self.prerequisites = prereqs
 
1460
        self.doublecolon = doublecolon
 
1461
        self.commands = []
 
1462
        self.loc = loc
 
1463
        self.weakdeps = weakdeps
 
1464
 
 
1465
    def addcommand(self, c):
 
1466
        assert isinstance(c, (Expansion, StringExpansion))
 
1467
        self.commands.append(c)
 
1468
 
 
1469
    def getcommands(self, target, makefile):
 
1470
        assert isinstance(target, Target)
 
1471
 
 
1472
        return getcommandsforrule(self, target, makefile, self.prerequisites, stem=None)
 
1473
        # TODO: $* in non-pattern rules?
 
1474
 
 
1475
class PatternRuleInstance(object):
 
1476
    weakdeps = False
 
1477
 
 
1478
    """
 
1479
    A pattern rule instantiated for a particular target. It has the same API as Rule, but
 
1480
    different internals, forwarding most information on to the PatternRule.
 
1481
    """
 
1482
    def __init__(self, prule, dir, stem, ismatchany):
 
1483
        assert isinstance(prule, PatternRule)
 
1484
 
 
1485
        self.dir = dir
 
1486
        self.stem = stem
 
1487
        self.prule = prule
 
1488
        self.prerequisites = prule.prerequisitesforstem(dir, stem)
 
1489
        self.doublecolon = prule.doublecolon
 
1490
        self.loc = prule.loc
 
1491
        self.ismatchany = ismatchany
 
1492
        self.commands = prule.commands
 
1493
 
 
1494
    def getcommands(self, target, makefile):
 
1495
        assert isinstance(target, Target)
 
1496
        return getcommandsforrule(self, target, makefile, self.prerequisites, stem=self.dir + self.stem)
 
1497
 
 
1498
    def __str__(self):
 
1499
        return "Pattern rule at %s with stem '%s', matchany: %s doublecolon: %s" % (self.loc,
 
1500
                                                                                    self.dir + self.stem,
 
1501
                                                                                    self.ismatchany,
 
1502
                                                                                    self.doublecolon)
 
1503
 
 
1504
class PatternRule(object):
 
1505
    """
 
1506
    An implicit rule or static pattern rule containing target patterns, prerequisite patterns,
 
1507
    and a list of commands.
 
1508
    """
 
1509
 
 
1510
    def __init__(self, targetpatterns, prerequisites, doublecolon, loc):
 
1511
        self.targetpatterns = targetpatterns
 
1512
        self.prerequisites = prerequisites
 
1513
        self.doublecolon = doublecolon
 
1514
        self.loc = loc
 
1515
        self.commands = []
 
1516
 
 
1517
    def addcommand(self, c):
 
1518
        assert isinstance(c, (Expansion, StringExpansion))
 
1519
        self.commands.append(c)
 
1520
 
 
1521
    def ismatchany(self):
 
1522
        return util.any((t.ismatchany() for t in self.targetpatterns))
 
1523
 
 
1524
    def hasspecificmatch(self, file):
 
1525
        for p in self.targetpatterns:
 
1526
            if not p.ismatchany() and p.match(file) is not None:
 
1527
                return True
 
1528
 
 
1529
        return False
 
1530
 
 
1531
    def matchesfor(self, dir, file, skipsinglecolonmatchany):
 
1532
        """
 
1533
        Determine all the target patterns of this rule that might match target t.
 
1534
        @yields a PatternRuleInstance for each.
 
1535
        """
 
1536
 
 
1537
        for p in self.targetpatterns:
 
1538
            matchany = p.ismatchany()
 
1539
            if matchany:
 
1540
                if skipsinglecolonmatchany and not self.doublecolon:
 
1541
                    continue
 
1542
 
 
1543
                yield PatternRuleInstance(self, dir, file, True)
 
1544
            else:
 
1545
                stem = p.match(dir + file)
 
1546
                if stem is not None:
 
1547
                    yield PatternRuleInstance(self, '', stem, False)
 
1548
                else:
 
1549
                    stem = p.match(file)
 
1550
                    if stem is not None:
 
1551
                        yield PatternRuleInstance(self, dir, stem, False)
 
1552
 
 
1553
    def prerequisitesforstem(self, dir, stem):
 
1554
        return [p.resolve(dir, stem) for p in self.prerequisites]
 
1555
 
 
1556
class _RemakeContext(object):
 
1557
    def __init__(self, makefile, cb):
 
1558
        self.makefile = makefile
 
1559
        self.included = [(makefile.gettarget(f), required)
 
1560
                         for f, required in makefile.included]
 
1561
        self.toremake = list(self.included)
 
1562
        self.cb = cb
 
1563
 
 
1564
        self.remakecb(error=False, didanything=False)
 
1565
 
 
1566
    def remakecb(self, error, didanything):
 
1567
        assert error in (True, False)
 
1568
 
 
1569
        if error and self.required:
 
1570
            print "Error remaking makefiles (ignored)"
 
1571
 
 
1572
        if len(self.toremake):
 
1573
            target, self.required = self.toremake.pop(0)
 
1574
            target.make(self.makefile, [], avoidremakeloop=True, cb=self.remakecb, printerror=False)
 
1575
        else:
 
1576
            for t, required in self.included:
 
1577
                if t.wasremade:
 
1578
                    _log.info("Included file %s was remade, restarting make", t.target)
 
1579
                    self.cb(remade=True)
 
1580
                    return
 
1581
                elif required and t.mtime is None:
 
1582
                    self.cb(remade=False, error=DataError("No rule to remake missing include file %s" % t.target))
 
1583
                    return
 
1584
 
 
1585
            self.cb(remade=False)
 
1586
 
 
1587
class Makefile(object):
 
1588
    """
 
1589
    The top-level data structure for makefile execution. It holds Targets, implicit rules, and other
 
1590
    state data.
 
1591
    """
 
1592
 
 
1593
    def __init__(self, workdir=None, env=None, restarts=0, make=None,
 
1594
                 makeflags='', makeoverrides='',
 
1595
                 makelevel=0, context=None, targets=(), keepgoing=False,
 
1596
                 silent=False, justprint=False):
 
1597
        self.defaulttarget = None
 
1598
 
 
1599
        if env is None:
 
1600
            env = os.environ
 
1601
        self.env = env
 
1602
 
 
1603
        self.variables = Variables()
 
1604
        self.variables.readfromenvironment(env)
 
1605
 
 
1606
        self.context = context
 
1607
        self.exportedvars = {}
 
1608
        self._targets = {}
 
1609
        self.keepgoing = keepgoing
 
1610
        self.silent = silent
 
1611
        self.justprint = justprint
 
1612
        self._patternvariables = [] # of (pattern, variables)
 
1613
        self.implicitrules = []
 
1614
        self.parsingfinished = False
 
1615
 
 
1616
        self._patternvpaths = [] # of (pattern, [dir, ...])
 
1617
 
 
1618
        if workdir is None:
 
1619
            workdir = os.getcwd()
 
1620
        workdir = os.path.realpath(workdir)
 
1621
        self.workdir = workdir
 
1622
        self.variables.set('CURDIR', Variables.FLAVOR_SIMPLE,
 
1623
                           Variables.SOURCE_AUTOMATIC, workdir.replace('\\','/'))
 
1624
 
 
1625
        # the list of included makefiles, whether or not they existed
 
1626
        self.included = []
 
1627
 
 
1628
        self.variables.set('MAKE_RESTARTS', Variables.FLAVOR_SIMPLE,
 
1629
                           Variables.SOURCE_AUTOMATIC, restarts > 0 and str(restarts) or '')
 
1630
 
 
1631
        self.variables.set('.PYMAKE', Variables.FLAVOR_SIMPLE,
 
1632
                           Variables.SOURCE_MAKEFILE, "1")
 
1633
        if make is not None:
 
1634
            self.variables.set('MAKE', Variables.FLAVOR_SIMPLE,
 
1635
                               Variables.SOURCE_MAKEFILE, make)
 
1636
 
 
1637
        if makeoverrides != '':
 
1638
            self.variables.set('-*-command-variables-*-', Variables.FLAVOR_SIMPLE,
 
1639
                               Variables.SOURCE_AUTOMATIC, makeoverrides)
 
1640
            makeflags += ' -- $(MAKEOVERRIDES)'
 
1641
 
 
1642
        self.variables.set('MAKEOVERRIDES', Variables.FLAVOR_RECURSIVE,
 
1643
                           Variables.SOURCE_ENVIRONMENT,
 
1644
                           '${-*-command-variables-*-}')
 
1645
 
 
1646
        self.variables.set('MAKEFLAGS', Variables.FLAVOR_RECURSIVE,
 
1647
                           Variables.SOURCE_MAKEFILE, makeflags)
 
1648
        self.exportedvars['MAKEFLAGS'] = True
 
1649
 
 
1650
        self.makelevel = makelevel
 
1651
        self.variables.set('MAKELEVEL', Variables.FLAVOR_SIMPLE,
 
1652
                           Variables.SOURCE_MAKEFILE, str(makelevel))
 
1653
 
 
1654
        self.variables.set('MAKECMDGOALS', Variables.FLAVOR_SIMPLE,
 
1655
                           Variables.SOURCE_AUTOMATIC, ' '.join(targets))
 
1656
 
 
1657
        for vname, val in implicit.variables.iteritems():
 
1658
            self.variables.set(vname,
 
1659
                               Variables.FLAVOR_SIMPLE,
 
1660
                               Variables.SOURCE_IMPLICIT, val)
 
1661
 
 
1662
    def foundtarget(self, t):
 
1663
        """
 
1664
        Inform the makefile of a target which is a candidate for being the default target,
 
1665
        if there isn't already a default target.
 
1666
        """
 
1667
        if self.defaulttarget is None and t != '.PHONY':
 
1668
            self.defaulttarget = t
 
1669
 
 
1670
    def getpatternvariables(self, pattern):
 
1671
        assert isinstance(pattern, Pattern)
 
1672
 
 
1673
        for p, v in self._patternvariables:
 
1674
            if p == pattern:
 
1675
                return v
 
1676
 
 
1677
        v = Variables()
 
1678
        self._patternvariables.append( (pattern, v) )
 
1679
        return v
 
1680
 
 
1681
    def getpatternvariablesfor(self, target):
 
1682
        for p, v in self._patternvariables:
 
1683
            if p.match(target):
 
1684
                yield v
 
1685
 
 
1686
    def hastarget(self, target):
 
1687
        return target in self._targets
 
1688
 
 
1689
    def gettarget(self, target):
 
1690
        assert isinstance(target, str)
 
1691
 
 
1692
        target = target.rstrip('/')
 
1693
 
 
1694
        assert target != '', "empty target?"
 
1695
 
 
1696
        if target.find('*') != -1 or target.find('?') != -1 or target.find('[') != -1:
 
1697
            raise DataError("wildcards should have been expanded by the parser: '%s'" % (target,))
 
1698
 
 
1699
        t = self._targets.get(target, None)
 
1700
        if t is None:
 
1701
            t = Target(target, self)
 
1702
            self._targets[target] = t
 
1703
        return t
 
1704
 
 
1705
    def appendimplicitrule(self, rule):
 
1706
        assert isinstance(rule, PatternRule)
 
1707
        self.implicitrules.append(rule)
 
1708
 
 
1709
    def finishparsing(self):
 
1710
        """
 
1711
        Various activities, such as "eval", are not allowed after parsing is
 
1712
        finished. In addition, various warnings and errors can only be issued
 
1713
        after the parsing data model is complete. All dependency resolution
 
1714
        and rule execution requires that parsing be finished.
 
1715
        """
 
1716
        self.parsingfinished = True
 
1717
 
 
1718
        flavor, source, value = self.variables.get('GPATH')
 
1719
        if value is not None and value.resolvestr(self, self.variables, ['GPATH']).strip() != '':
 
1720
            raise DataError('GPATH was set: pymake does not support GPATH semantics')
 
1721
 
 
1722
        flavor, source, value = self.variables.get('VPATH')
 
1723
        if value is None:
 
1724
            self._vpath = []
 
1725
        else:
 
1726
            self._vpath = filter(lambda e: e != '',
 
1727
                                 re.split('[%s\s]+' % os.pathsep,
 
1728
                                          value.resolvestr(self, self.variables, ['VPATH'])))
 
1729
 
 
1730
        targets = list(self._targets.itervalues())
 
1731
        for t in targets:
 
1732
            t.explicit = True
 
1733
            for r in t.rules:
 
1734
                for p in r.prerequisites:
 
1735
                    self.gettarget(p).explicit = True
 
1736
 
 
1737
        np = self.gettarget('.NOTPARALLEL')
 
1738
        if len(np.rules):
 
1739
            self.context = process.getcontext(1)
 
1740
 
 
1741
        flavor, source, value = self.variables.get('.DEFAULT_GOAL')
 
1742
        if value is not None:
 
1743
            self.defaulttarget = value.resolvestr(self, self.variables, ['.DEFAULT_GOAL']).strip()
 
1744
 
 
1745
        self.error = False
 
1746
 
 
1747
    def include(self, path, required=True, weak=False, loc=None):
 
1748
        """
 
1749
        Include the makefile at `path`.
 
1750
        """
 
1751
        self.included.append((path, required))
 
1752
        fspath = util.normaljoin(self.workdir, path)
 
1753
        if os.path.exists(fspath):
 
1754
            stmts = parser.parsefile(fspath)
 
1755
            self.variables.append('MAKEFILE_LIST', Variables.SOURCE_AUTOMATIC, path, None, self)
 
1756
            stmts.execute(self, weak=weak)
 
1757
            self.gettarget(path).explicit = True
 
1758
 
 
1759
    def addvpath(self, pattern, dirs):
 
1760
        """
 
1761
        Add a directory to the vpath search for the given pattern.
 
1762
        """
 
1763
        self._patternvpaths.append((pattern, dirs))
 
1764
 
 
1765
    def clearvpath(self, pattern):
 
1766
        """
 
1767
        Clear vpaths for the given pattern.
 
1768
        """
 
1769
        self._patternvpaths = [(p, dirs)
 
1770
                               for p, dirs in self._patternvpaths
 
1771
                               if not p.match(pattern)]
 
1772
 
 
1773
    def clearallvpaths(self):
 
1774
        self._patternvpaths = []
 
1775
 
 
1776
    def getvpath(self, target):
 
1777
        vp = list(self._vpath)
 
1778
        for p, dirs in self._patternvpaths:
 
1779
            if p.match(target):
 
1780
                vp.extend(dirs)
 
1781
 
 
1782
        return withoutdups(vp)
 
1783
 
 
1784
    def remakemakefiles(self, cb):
 
1785
        mlist = []
 
1786
        for f, required in self.included:
 
1787
            t = self.gettarget(f)
 
1788
            t.explicit = True
 
1789
            t.resolvevpath(self)
 
1790
            oldmtime = t.mtime
 
1791
 
 
1792
            mlist.append((t, oldmtime))
 
1793
 
 
1794
        _RemakeContext(self, cb)
 
1795
 
 
1796
    def getsubenvironment(self, variables):
 
1797
        env = dict(self.env)
 
1798
        for vname, v in self.exportedvars.iteritems():
 
1799
            if v:
 
1800
                flavor, source, val = variables.get(vname)
 
1801
                if val is None:
 
1802
                    strval = ''
 
1803
                else:
 
1804
                    strval = val.resolvestr(self, variables, [vname])
 
1805
                env[vname] = strval
 
1806
            else:
 
1807
                env.pop(vname, None)
 
1808
 
 
1809
        makeflags = ''
 
1810
 
 
1811
        env['MAKELEVEL'] = str(self.makelevel + 1)
 
1812
        return env