OXYGENHUB
◆ main openmsx / build / compilers.py
4.5 KiB · Python · last changed chore(): initial commit 1 month ago source blame history raw
1
from msysutils import msysActive, msysPathToNative
2
3
from os import environ
4
from shlex import split as shsplit
5
from subprocess import PIPE, STDOUT, Popen
6
7
if msysActive():
8
	def fixArgs(args):
9
		for arg in args:
10
			if arg.startswith('-I') or arg.startswith('-L'):
11
				yield arg[ : 2] + msysPathToNative(arg[2 : ])
12
			elif arg.startswith('/'):
13
				yield msysPathToNative(arg)
14
			else:
15
				yield arg
16
else:
17
	def fixArgs(args):
18
		return iter(args)
19
20
class _Command(object):
21
22
	@classmethod
23
	def fromLine(cls, commandStr, flagsStr):
24
		commandParts = shsplit(commandStr)
25
		flags = shsplit(flagsStr)
26
		env = {'LC_ALL': 'C.UTF-8'}
27
		while commandParts:
28
			if '=' in commandParts[0]:
29
				name, value = commandParts[0].split('=', 1)
30
				del commandParts[0]
31
				env[name] = value
32
			else:
33
				return cls(
34
					env,
35
					commandParts[0],
36
					list(fixArgs(commandParts[1 : ] + flags))
37
					)
38
		else:
39
			raise ValueError('No command specified in "%s"' % commandStr)
40
41
	def __init__(self, env, executable, flags):
42
		self.__env = env
43
		self.__executable = executable
44
		self.__flags = flags
45
46
		mergedEnv = dict(environ)
47
		mergedEnv.update(env)
48
		self.__mergedEnv = mergedEnv
49
50
	def __str__(self):
51
		return ' '.join(
52
			[ self.__executable ] + self.__flags + (
53
				[ '(%s)' % ' '.join(
54
					'%s=%s' % item
55
					for item in sorted(self.__env.items())
56
					) ] if self.__env else []
57
				)
58
			)
59
60
	def _run(self, log, name, args, inputSeq, captureOutput):
61
		commandLine = [ self.__executable ] + args + self.__flags
62
		try:
63
			proc = Popen(
64
				commandLine,
65
				bufsize = -1,
66
				env = self.__mergedEnv,
67
				stdin = None if inputSeq is None else PIPE,
68
				stdout = PIPE,
69
				stderr = PIPE if captureOutput else STDOUT,
70
				)
71
		except OSError as ex:
72
			print('failed to execute %s: %s' % (name, ex), file=log)
73
			return None if captureOutput else False
74
		inputText = None if inputSeq is None \
75
				else ('\n'.join(inputSeq) + '\n').encode('utf-8')
76
		stdoutdata, stderrdata = proc.communicate(inputText)
77
		stdouttext = stdoutdata.decode('utf-8', 'replace')
78
		if captureOutput:
79
			assert stderrdata is not None
80
			messages = stderrdata.decode('utf-8', 'replace')
81
		else:
82
			assert stderrdata is None
83
			messages = stdouttext
84
		if messages:
85
			log.write('%s command: %s\n' % (name, ' '.join(commandLine)))
86
			if inputText is not None:
87
				log.write('input:\n')
88
				log.write(str(inputText))
89
				if not inputText.endswith(b'\n'):
90
					log.write('\n')
91
				log.write('end input.\n')
92
			# pylint 0.18.0 somehow thinks 'messages' is a list, not a string.
93
			# pylint: disable-msg=E1103
94
			messages = messages.replace('\r', '')
95
			log.write(messages)
96
			if not messages.endswith('\n'):
97
				log.write('\n')
98
		if proc.returncode == 0:
99
			return stdouttext if captureOutput else True
100
		else:
101
			print('return code from %s: %d' % (name, proc.returncode), file=log)
102
			return None if captureOutput else False
103
104
class CompileCommand(_Command):
105
	__expandSignature = 'EXPAND_MACRO_'
106
107
	def compile(self, log, sourcePath, objectPath):
108
		return self._run(
109
			log, 'compiler', [ '-c', sourcePath, '-o', objectPath ], None, False
110
			)
111
112
	def expand(self, log, headers, *keys):
113
		signature = self.__expandSignature
114
		def iterLines():
115
			for header in headers:
116
				yield '#include %s' % header
117
			for key in keys:
118
				yield '%s%s %s' % (signature, key, key)
119
		output = self._run(
120
			log, 'preprocessor', [ '-E', '-' ], iterLines(), True
121
			)
122
		if output is None:
123
			if len(keys) == 1:
124
				return None
125
			else:
126
				return (None, ) * len(keys)
127
		else:
128
			expanded = {}
129
			prevKey = None
130
			for line in output.split('\n'):
131
				line = line.strip()
132
				if not line or line.startswith('#'):
133
					continue
134
				if line.startswith(signature):
135
					prevKey = None
136
					keyValueStr = line[len(signature) : ]
137
					try:
138
						key, value = keyValueStr.split(None, 1)
139
					except ValueError:
140
						key, value = keyValueStr, ''
141
					if key not in keys:
142
						log.write(
143
							'Ignoring macro expand signature match on '
144
							'non-requested macro "%s"\n' % key
145
							)
146
						continue
147
					elif value == '':
148
						# GCC5 puts value on separate line.
149
						prevKey = key
150
						continue
151
				elif prevKey is not None:
152
					key = prevKey
153
					value = line
154
					prevKey = None
155
				else:
156
					continue
157
				if value != key:
158
					expanded[key] = value
159
			if len(keys) == 1:
160
				return expanded.get(keys[0])
161
			else:
162
				return tuple(expanded.get(key) for key in keys)
163
164
class LinkCommand(_Command):
165
166
	def link(self, log, objectPaths, binaryPath):
167
		return self._run(
168
			log, 'linker', objectPaths + [ '-o', binaryPath ], None, False
169
			)