OXYGENHUB
◆ main openmsx / build / makeutils.py
3.7 KiB · Python · last changed chore(): initial commit 1 month ago source blame history raw
1
from io import open
2
from os.path import isdir
3
import re
4
5
def filterLines(lines, regex):
6
	'''Filters each line of the given line iterator using the given regular
7
	expression string. For each match, a tuple containing the text matching
8
	each capture group from the regular expression is yielded.
9
	'''
10
	matcher = re.compile(regex)
11
	for line in lines:
12
		if line.endswith('\n'):
13
			line = line[ : -1]
14
		match = matcher.match(line)
15
		if match:
16
			yield match.groups()
17
18
def filterFile(filePath, regex):
19
	'''Filters each line of the given text file using the given regular
20
	expression string. For each match, a tuple containing the text matching
21
	each capture group from the regular expression is yielded.
22
	'''
23
	with open(filePath, 'r', encoding='utf-8') as inp:
24
		for groups in filterLines(inp, regex):
25
			yield groups
26
27
def joinContinuedLines(lines):
28
	'''Iterates through the given lines, replacing lines that are continued
29
	using a trailing backslash with a single line.
30
	'''
31
	buf = ''
32
	for line in lines:
33
		if line.endswith('\\\n'):
34
			buf += line[ : -2]
35
		elif line.endswith('\\'):
36
			buf += line[ : -1]
37
		else:
38
			yield buf + line
39
			buf = ''
40
	if buf:
41
		raise ValueError('Continuation on last line')
42
43
_reEval = re.compile(r'(\$\(|\))')
44
def evalMakeExpr(expr, makeVars):
45
	'''Evaluates variable references in an expression.
46
	Raises ValueError if there is a syntax error in the expression.
47
	Raises KeyError if the expression references a non-existing variable.
48
	'''
49
	stack = [ [] ]
50
	for part in _reEval.split(expr):
51
		if part == '$(':
52
			stack.append([])
53
		elif part == ')' and len(stack) != 1:
54
			name = ''.join(stack.pop())
55
			if name.startswith('addprefix '):
56
				prefix, args = name[len('addprefix') : ].split(',')
57
				prefix = prefix.strip()
58
				value = ' '.join(prefix + arg for arg in args.split())
59
			elif name.startswith('addsuffix '):
60
				suffix, args = name[len('addsuffix') : ].split(',')
61
				suffix = suffix.strip()
62
				value = ' '.join(arg + suffix for arg in args.split())
63
			elif name.startswith('shell '):
64
				# Unsupported; assume result is never used.
65
				value = '?'
66
			elif name.isdigit():
67
				# This is a function argument; assume the evaluated result is
68
				# never used.
69
				value = '?'
70
			else:
71
				value = makeVars[name]
72
			stack[-1].append(value)
73
		else:
74
			stack[-1].append(part)
75
	if len(stack) != 1:
76
		raise ValueError('Open without close in "%s"' % expr)
77
	return ''.join(stack.pop())
78
79
def extractMakeVariables(filePath, makeVars = None):
80
	'''Extract all variable definitions from the given Makefile.
81
	The optional makeVars argument is a dictionary containing the already
82
	defined variables. These variables will be included in the output; the
83
	given dictionary is not modified.
84
	Returns a dictionary that maps each variable name to its value.
85
	'''
86
	makeVars = {} if makeVars is None else dict(makeVars)
87
	with open(filePath, 'r', encoding='utf-8') as inp:
88
		for name, assign, value in filterLines(
89
			joinContinuedLines(inp),
90
			r'[ ]*([A-Za-z0-9_]+)[ ]*([+:]?=)(.*)'
91
			):
92
			if assign == '=':
93
				makeVars[name] = value.strip()
94
			elif assign == ':=':
95
				makeVars[name] = evalMakeExpr(value, makeVars).strip()
96
			elif assign == '+=':
97
				# Note: Make will or will not evaluate the added expression
98
				#       depending on how the variable was originally defined,
99
				#       but we don't store that information.
100
				makeVars[name] = makeVars[name] + ' ' + value.strip()
101
			else:
102
				assert False, assign
103
	return makeVars
104
105
def parseBool(valueStr):
106
	'''Parses a string containing a boolean value.
107
	Accepted values are "true" and "false"; anything else raises ValueError.
108
	'''
109
	if valueStr == 'true':
110
		return True
111
	elif valueStr == 'false':
112
		return False
113
	else:
114
		raise ValueError('Invalid boolean "%s"' % valueStr)