File: makeutils.py

package info (click to toggle)
openmsx 0.10.1-2
  • links: PTS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 16,628 kB
  • ctags: 19,723
  • sloc: cpp: 131,938; xml: 25,418; tcl: 15,394; python: 4,012; sh: 365; makefile: 26
file content (124 lines) | stat: -rw-r--r-- 3,993 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from os.path import isdir
import re

def filterLines(lines, regex):
	'''Filters each line of the given line iterator using the given regular
	expression string. For each match, a tuple containing the text matching
	each capture group from the regular expression is yielded.
	'''
	matcher = re.compile(regex)
	for line in lines:
		if line.endswith('\n'):
			line = line[ : -1]
		match = matcher.match(line)
		if match:
			yield match.groups()

def filterFile(filePath, regex):
	'''Filters each line of the given text file using the given regular
	expression string. For each match, a tuple containing the text matching
	each capture group from the regular expression is yielded.
	'''
	inp = open(filePath, 'r')
	try:
		for groups in filterLines(inp, regex):
			yield groups
	finally:
		inp.close()

def joinContinuedLines(lines):
	'''Iterates through the given lines, replacing lines that are continued
	using a trailing backslash with a single line.
	'''
	buf = ''
	for line in lines:
		if line.endswith('\\\n'):
			buf += line[ : -2]
		elif line.endswith('\\'):
			buf += line[ : -1]
		else:
			yield buf + line
			buf = ''
	if buf:
		raise ValueError('Continuation on last line')

_reEval = re.compile('(\$\(|\))')
def evalMakeExpr(expr, makeVars):
	'''Evaluates variable references in an expression.
	Raises ValueError if there is a syntax error in the expression.
	Raises KeyError if the expression references a non-existing variable.
	'''
	stack = [ [] ]
	for part in _reEval.split(expr):
		if part == '$(':
			stack.append([])
		elif part == ')' and len(stack) != 1:
			name = ''.join(stack.pop())
			if name.startswith('addprefix '):
				prefix, args = name[len('addprefix') : ].split(',')
				prefix = prefix.strip()
				value = ' '.join(prefix + arg for arg in args.split())
			elif name.startswith('addsuffix '):
				suffix, args = name[len('addsuffix') : ].split(',')
				suffix = suffix.strip()
				value = ' '.join(arg + suffix for arg in args.split())
			elif name.startswith('shell '):
				# Unsupported; assume result is never used.
				value = '?'
			elif name.startswith('call DIR_IF_EXISTS,'):
				# This is our function, not a Make function, but the goal is
				# not to emulate Make, so we emulate our function instead.
				path = name[len('call DIR_IF_EXISTS,'): ]
				value = path if isdir(path) else ''
			elif name.isdigit():
				# This is a function argument; assume the evaluated result is
				# never used.
				value = '?'
			else:
				value = makeVars[name]
			stack[-1].append(value)
		else:
			stack[-1].append(part)
	if len(stack) != 1:
		raise ValueError('Open without close in "%s"' % expr)
	return ''.join(stack.pop())

def extractMakeVariables(filePath, makeVars = None):
	'''Extract all variable definitions from the given Makefile.
	The optional makeVars argument is a dictionary containing the already
	defined variables. These variables will be included in the output; the
	given dictionary is not modified.
	Returns a dictionary that maps each variable name to its value.
	'''
	makeVars = {} if makeVars is None else dict(makeVars)
	inp = open(filePath, 'r')
	try:
		for name, assign, value in filterLines(
			joinContinuedLines(inp),
			r'[ ]*([A-Za-z0-9_]+)[ ]*([+:]?=)(.*)'
			):
			if assign == '=':
				makeVars[name] = value.strip()
			elif assign == ':=':
				makeVars[name] = evalMakeExpr(value, makeVars).strip()
			elif assign == '+=':
				# Note: Make will or will not evaluate the added expression
				#       depending on how the variable was originally defined,
				#       but we don't store that information.
				makeVars[name] = makeVars[name] + ' ' + value.strip()
			else:
				assert False, assign
	finally:
		inp.close()
	return makeVars

def parseBool(valueStr):
	'''Parses a string containing a boolean value.
	Accepted values are "true" and "false"; anything else raises ValueError.
	'''
	if valueStr == 'true':
		return True
	elif valueStr == 'false':
		return False
	else:
		raise ValueError('Invalid boolean "%s"' % valueStr)