5
import parser, data, util
6
import subprocess, os, logging
7
from globrelative import glob
8
from cStringIO import StringIO
10
log = logging.getLogger('pymake.data')
12
class Function(object):
14
An object that represents a function call. This class is always subclassed
15
with the following methods and attributes:
17
minargs = minimum # of arguments
18
maxargs = maximum # of arguments (0 means unlimited)
20
def resolve(self, makefile, variables, fd, setting)
22
calls fd.write() with strings
25
__slots__ = ('_arguments', 'loc')
27
def __init__(self, loc):
30
assert self.minargs > 0
32
def __getitem__(self, key):
33
return self._arguments[key]
36
argc = len(self._arguments)
38
if argc < self.minargs:
39
raise data.DataError("Not enough arguments to function %s, requires %s" % (self.name, self.minargs), self.loc)
41
assert self.maxargs == 0 or argc <= self.maxargs, "Parser screwed up, gave us too many args"
43
def append(self, arg):
44
assert isinstance(arg, (data.Expansion, data.StringExpansion))
45
self._arguments.append(arg)
48
return len(self._arguments)
50
class VariableRef(Function):
51
__slots__ = ('vname', 'loc')
53
def __init__(self, loc, vname):
55
assert isinstance(vname, (data.Expansion, data.StringExpansion))
59
assert False, "Shouldn't get here"
61
def resolve(self, makefile, variables, fd, setting):
62
vname = self.vname.resolvestr(makefile, variables, setting)
64
raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
66
flavor, source, value = variables.get(vname)
68
log.debug("%s: variable '%s' was not set" % (self.loc, vname))
71
value.resolve(makefile, variables, fd, setting + [vname])
73
class SubstitutionRef(Function):
74
"""$(VARNAME:.c=.o) and $(VARNAME:%.c=%.o)"""
76
__slots__ = ('loc', 'vname', 'substfrom', 'substto')
78
def __init__(self, loc, varname, substfrom, substto):
81
self.substfrom = substfrom
82
self.substto = substto
85
assert False, "Shouldn't get here"
87
def resolve(self, makefile, variables, fd, setting):
88
vname = self.vname.resolvestr(makefile, variables, setting)
90
raise data.DataError("Setting variable '%s' recursively references itself." % (vname,), self.loc)
92
substfrom = self.substfrom.resolvestr(makefile, variables, setting)
93
substto = self.substto.resolvestr(makefile, variables, setting)
95
flavor, source, value = variables.get(vname)
97
log.debug("%s: variable '%s' was not set" % (self.loc, vname))
100
f = data.Pattern(substfrom)
101
if not f.ispattern():
102
f = data.Pattern('%' + substfrom)
103
substto = '%' + substto
105
fd.write(' '.join([f.subst(substto, word, False)
106
for word in value.resolvesplit(makefile, variables, setting + [vname])]))
108
class SubstFunction(Function):
113
__slots__ = Function.__slots__
115
def resolve(self, makefile, variables, fd, setting):
116
s = self._arguments[0].resolvestr(makefile, variables, setting)
117
r = self._arguments[1].resolvestr(makefile, variables, setting)
118
d = self._arguments[2].resolvestr(makefile, variables, setting)
119
fd.write(d.replace(s, r))
121
class PatSubstFunction(Function):
126
__slots__ = Function.__slots__
128
def resolve(self, makefile, variables, fd, setting):
129
s = self._arguments[0].resolvestr(makefile, variables, setting)
130
r = self._arguments[1].resolvestr(makefile, variables, setting)
133
fd.write(' '.join([p.subst(r, word, False)
134
for word in self._arguments[2].resolvesplit(makefile, variables, setting)]))
136
class StripFunction(Function):
141
__slots__ = Function.__slots__
143
def resolve(self, makefile, variables, fd, setting):
144
util.joiniter(fd, self._arguments[0].resolvesplit(makefile, variables, setting))
146
class FindstringFunction(Function):
151
__slots__ = Function.__slots__
153
def resolve(self, makefile, variables, fd, setting):
154
s = self._arguments[0].resolvestr(makefile, variables, setting)
155
r = self._arguments[1].resolvestr(makefile, variables, setting)
160
class FilterFunction(Function):
165
__slots__ = Function.__slots__
167
def resolve(self, makefile, variables, fd, setting):
168
plist = [data.Pattern(p)
169
for p in self._arguments[0].resolvesplit(makefile, variables, setting)]
171
fd.write(' '.join([w for w in self._arguments[1].resolvesplit(makefile, variables, setting)
172
if util.any((p.match(w) for p in plist))]))
174
class FilteroutFunction(Function):
179
__slots__ = Function.__slots__
181
def resolve(self, makefile, variables, fd, setting):
182
plist = [data.Pattern(p)
183
for p in self._arguments[0].resolvesplit(makefile, variables, setting)]
185
fd.write(' '.join([w for w in self._arguments[1].resolvesplit(makefile, variables, setting)
186
if not util.any((p.match(w) for p in plist))]))
188
class SortFunction(Function):
193
__slots__ = Function.__slots__
195
def resolve(self, makefile, variables, fd, setting):
196
d = list(self._arguments[0].resolvesplit(makefile, variables, setting))
200
class WordFunction(Function):
205
__slots__ = Function.__slots__
207
def resolve(self, makefile, variables, fd, setting):
208
n = self._arguments[0].resolvestr(makefile, variables, setting)
209
# TODO: provide better error if this doesn't convert
211
words = list(self._arguments[1].resolvesplit(makefile, variables, setting))
212
if n < 1 or n > len(words):
214
fd.write(words[n - 1])
216
class WordlistFunction(Function):
221
__slots__ = Function.__slots__
223
def resolve(self, makefile, variables, fd, setting):
224
nfrom = self._arguments[0].resolvestr(makefile, variables, setting)
225
nto = self._arguments[1].resolvestr(makefile, variables, setting)
226
# TODO: provide better errors if this doesn't convert
230
words = list(self._arguments[2].resolvesplit(makefile, variables, setting))
237
util.joiniter(fd, words[nfrom - 1:nto])
239
class WordsFunction(Function):
244
__slots__ = Function.__slots__
246
def resolve(self, makefile, variables, fd, setting):
247
fd.write(str(len(self._arguments[0].resolvesplit(makefile, variables, setting))))
249
class FirstWordFunction(Function):
254
__slots__ = Function.__slots__
256
def resolve(self, makefile, variables, fd, setting):
257
l = self._arguments[0].resolvesplit(makefile, variables, setting)
261
class LastWordFunction(Function):
266
__slots__ = Function.__slots__
268
def resolve(self, makefile, variables, fd, setting):
269
l = self._arguments[0].resolvesplit(makefile, variables, setting)
273
def pathsplit(path, default='./'):
275
Splits a path into dirpart, filepart on the last slash. If there is no slash, dirpart
278
dir, slash, file = util.strrpartition(path, '/')
282
return dir + slash, file
284
class DirFunction(Function):
289
def resolve(self, makefile, variables, fd, setting):
290
fd.write(' '.join([pathsplit(path)[0]
291
for path in self._arguments[0].resolvesplit(makefile, variables, setting)]))
293
class NotDirFunction(Function):
298
__slots__ = Function.__slots__
300
def resolve(self, makefile, variables, fd, setting):
301
fd.write(' '.join([pathsplit(path)[1]
302
for path in self._arguments[0].resolvesplit(makefile, variables, setting)]))
304
class SuffixFunction(Function):
309
__slots__ = Function.__slots__
314
dir, file = pathsplit(w)
315
base, dot, suffix = util.strrpartition(file, '.')
319
def resolve(self, makefile, variables, fd, setting):
320
util.joiniter(fd, self.suffixes(self._arguments[0].resolvesplit(makefile, variables, setting)))
322
class BasenameFunction(Function):
327
__slots__ = Function.__slots__
330
def basenames(words):
332
dir, file = pathsplit(w, '')
333
base, dot, suffix = util.strrpartition(file, '.')
339
def resolve(self, makefile, variables, fd, setting):
340
util.joiniter(fd, self.basenames(self._arguments[0].resolvesplit(makefile, variables, setting)))
342
class AddSuffixFunction(Function):
347
__slots__ = Function.__slots__
349
def resolve(self, makefile, variables, fd, setting):
350
suffix = self._arguments[0].resolvestr(makefile, variables, setting)
352
fd.write(' '.join([w + suffix for w in self._arguments[1].resolvesplit(makefile, variables, setting)]))
354
class AddPrefixFunction(Function):
359
def resolve(self, makefile, variables, fd, setting):
360
prefix = self._arguments[0].resolvestr(makefile, variables, setting)
362
fd.write(' '.join([prefix + w for w in self._arguments[1].resolvesplit(makefile, variables, setting)]))
364
class JoinFunction(Function):
369
__slots__ = Function.__slots__
372
def iterjoin(l1, l2):
373
for i in xrange(0, max(len(l1), len(l2))):
374
i1 = i < len(l1) and l1[i] or ''
375
i2 = i < len(l2) and l2[i] or ''
378
def resolve(self, makefile, variables, fd, setting):
379
list1 = list(self._arguments[0].resolvesplit(makefile, variables, setting))
380
list2 = list(self._arguments[1].resolvesplit(makefile, variables, setting))
382
util.joiniter(fd, self.iterjoin(list1, list2))
384
class WildcardFunction(Function):
389
__slots__ = Function.__slots__
391
def resolve(self, makefile, variables, fd, setting):
392
patterns = self._arguments[0].resolvesplit(makefile, variables, setting)
394
fd.write(' '.join([x.replace('\\','/')
396
for x in glob(makefile.workdir, p)]))
398
__slots__ = Function.__slots__
400
class RealpathFunction(Function):
405
def resolve(self, makefile, variables, fd, setting):
406
fd.write(' '.join([os.path.realpath(os.path.join(makefile.workdir, path)).replace('\\', '/')
407
for path in self._arguments[0].resolvesplit(makefile, variables, setting)]))
409
class AbspathFunction(Function):
414
__slots__ = Function.__slots__
416
def resolve(self, makefile, variables, fd, setting):
417
assert os.path.isabs(makefile.workdir)
418
fd.write(' '.join([os.path.join(makefile.workdir, path).replace('\\', '/')
419
for path in self._arguments[0].resolvesplit(makefile, variables, setting)]))
421
class IfFunction(Function):
426
__slots__ = Function.__slots__
430
self._arguments[0].lstrip()
431
self._arguments[0].rstrip()
433
def resolve(self, makefile, variables, fd, setting):
434
condition = self._arguments[0].resolvestr(makefile, variables, setting)
437
self._arguments[1].resolve(makefile, variables, fd, setting)
438
elif len(self._arguments) > 2:
439
return self._arguments[2].resolve(makefile, variables, fd, setting)
441
class OrFunction(Function):
446
__slots__ = Function.__slots__
448
def resolve(self, makefile, variables, fd, setting):
449
for arg in self._arguments:
450
r = arg.resolvestr(makefile, variables, setting)
455
class AndFunction(Function):
460
__slots__ = Function.__slots__
462
def resolve(self, makefile, variables, fd, setting):
465
for arg in self._arguments:
466
r = arg.resolvestr(makefile, variables, setting)
472
class ForEachFunction(Function):
477
__slots__ = Function.__slots__
479
def resolve(self, makefile, variables, fd, setting):
480
vname = self._arguments[0].resolvestr(makefile, variables, setting)
481
e = self._arguments[2]
483
v = data.Variables(parent=variables)
486
for w in self._arguments[1].resolvesplit(makefile, variables, setting):
492
v.set(vname, data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, w)
493
e.resolve(makefile, v, fd, setting)
495
class CallFunction(Function):
500
__slots__ = Function.__slots__
502
def resolve(self, makefile, variables, fd, setting):
503
vname = self._arguments[0].resolvestr(makefile, variables, setting)
505
raise data.DataError("Recursively setting variable '%s'" % (vname,))
507
v = data.Variables(parent=variables)
508
v.set('0', data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, vname)
509
for i in xrange(1, len(self._arguments)):
510
param = self._arguments[i].resolvestr(makefile, variables, setting)
511
v.set(str(i), data.Variables.FLAVOR_SIMPLE, data.Variables.SOURCE_AUTOMATIC, param)
513
flavor, source, e = variables.get(vname)
518
if flavor == data.Variables.FLAVOR_SIMPLE:
519
log.warning("%s: calling variable '%s' which is simply-expanded" % (self.loc, vname))
521
# but we'll do it anyway
522
e.resolve(makefile, v, fd, setting + [vname])
524
class ValueFunction(Function):
529
__slots__ = Function.__slots__
531
def resolve(self, makefile, variables, fd, setting):
532
varname = self._arguments[0].resolvestr(makefile, variables, setting)
534
flavor, source, value = variables.get(varname, expand=False)
535
if value is not None:
538
class EvalFunction(Function):
543
def resolve(self, makefile, variables, fd, setting):
544
if makefile.parsingfinished:
545
# GNU make allows variables to be set by recursive expansion during
546
# command execution. This seems really dumb to me, so I don't!
547
raise data.DataError("$(eval) not allowed via recursive expansion after parsing is finished", self.loc)
549
text = StringIO(self._arguments[0].resolvestr(makefile, variables, setting))
550
stmts = parser.parsestream(text, 'evaluation from %s' % self.loc)
551
stmts.execute(makefile)
553
class OriginFunction(Function):
558
__slots__ = Function.__slots__
560
def resolve(self, makefile, variables, fd, setting):
561
vname = self._arguments[0].resolvestr(makefile, variables, setting)
563
flavor, source, value = variables.get(vname)
566
elif source == data.Variables.SOURCE_OVERRIDE:
569
elif source == data.Variables.SOURCE_MAKEFILE:
571
elif source == data.Variables.SOURCE_ENVIRONMENT:
573
elif source == data.Variables.SOURCE_COMMANDLINE:
575
elif source == data.Variables.SOURCE_AUTOMATIC:
577
elif source == data.Variables.SOURCE_IMPLICIT:
582
class FlavorFunction(Function):
587
__slots__ = Function.__slots__
589
def resolve(self, makefile, variables, fd, setting):
590
varname = self._arguments[0].resolvestr(makefile, variables, setting)
592
flavor, source, value = variables.get(varname)
595
elif flavor == data.Variables.FLAVOR_RECURSIVE:
597
elif flavor == data.Variables.FLAVOR_SIMPLE:
601
class ShellFunction(Function):
606
__slots__ = Function.__slots__
608
def resolve(self, makefile, variables, fd, setting):
609
#TODO: call this once up-front somewhere and save the result?
610
shell, msys = util.checkmsyscompat()
611
cline = self._arguments[0].resolvestr(makefile, variables, setting)
613
log.debug("%s: running shell command '%s'" % (self.loc, cline))
615
cline = [shell, "-c", cline]
616
p = subprocess.Popen(cline, shell=not msys, stdout=subprocess.PIPE, cwd=makefile.workdir)
617
stdout, stderr = p.communicate()
619
stdout = stdout.replace('\r\n', '\n')
620
if stdout.endswith('\n'):
622
stdout = stdout.replace('\n', ' ')
626
class ErrorFunction(Function):
631
__slots__ = Function.__slots__
633
def resolve(self, makefile, variables, fd, setting):
634
v = self._arguments[0].resolvestr(makefile, variables, setting)
635
raise data.DataError(v, self.loc)
637
class WarningFunction(Function):
642
__slots__ = Function.__slots__
644
def resolve(self, makefile, variables, fd, setting):
645
v = self._arguments[0].resolvestr(makefile, variables, setting)
648
class InfoFunction(Function):
653
__slots__ = Function.__slots__
655
def resolve(self, makefile, variables, fd, setting):
656
v = self._arguments[0].resolvestr(makefile, variables, setting)
660
'subst': SubstFunction,
661
'patsubst': PatSubstFunction,
662
'strip': StripFunction,
663
'findstring': FindstringFunction,
664
'filter': FilterFunction,
665
'filter-out': FilteroutFunction,
666
'sort': SortFunction,
667
'word': WordFunction,
668
'wordlist': WordlistFunction,
669
'words': WordsFunction,
670
'firstword': FirstWordFunction,
671
'lastword': LastWordFunction,
673
'notdir': NotDirFunction,
674
'suffix': SuffixFunction,
675
'basename': BasenameFunction,
676
'addsuffix': AddSuffixFunction,
677
'addprefix': AddPrefixFunction,
678
'join': JoinFunction,
679
'wildcard': WildcardFunction,
680
'realpath': RealpathFunction,
681
'abspath': AbspathFunction,
685
'foreach': ForEachFunction,
686
'call': CallFunction,
687
'value': ValueFunction,
688
'eval': EvalFunction,
689
'origin': OriginFunction,
690
'flavor': FlavorFunction,
691
'shell': ShellFunction,
692
'error': ErrorFunction,
693
'warning': WarningFunction,
694
'info': InfoFunction,