File: compilers.py

package info (click to toggle)
openmsx 21.0%2Bdfsg-2
  • links: PTS
  • area: main
  • in suites: forky
  • size: 28,132 kB
  • sloc: cpp: 244,928; xml: 54,344; tcl: 15,603; python: 5,335; perl: 281; sh: 78; makefile: 57
file content (169 lines) | stat: -rw-r--r-- 4,560 bytes parent folder | download | duplicates (3)
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
from msysutils import msysActive, msysPathToNative

from os import environ
from shlex import split as shsplit
from subprocess import PIPE, STDOUT, Popen

if msysActive():
	def fixArgs(args):
		for arg in args:
			if arg.startswith('-I') or arg.startswith('-L'):
				yield arg[ : 2] + msysPathToNative(arg[2 : ])
			elif arg.startswith('/'):
				yield msysPathToNative(arg)
			else:
				yield arg
else:
	def fixArgs(args):
		return iter(args)

class _Command(object):

	@classmethod
	def fromLine(cls, commandStr, flagsStr):
		commandParts = shsplit(commandStr)
		flags = shsplit(flagsStr)
		env = {'LC_ALL': 'C.UTF-8'}
		while commandParts:
			if '=' in commandParts[0]:
				name, value = commandParts[0].split('=', 1)
				del commandParts[0]
				env[name] = value
			else:
				return cls(
					env,
					commandParts[0],
					list(fixArgs(commandParts[1 : ] + flags))
					)
		else:
			raise ValueError('No command specified in "%s"' % commandStr)

	def __init__(self, env, executable, flags):
		self.__env = env
		self.__executable = executable
		self.__flags = flags

		mergedEnv = dict(environ)
		mergedEnv.update(env)
		self.__mergedEnv = mergedEnv

	def __str__(self):
		return ' '.join(
			[ self.__executable ] + self.__flags + (
				[ '(%s)' % ' '.join(
					'%s=%s' % item
					for item in sorted(self.__env.items())
					) ] if self.__env else []
				)
			)

	def _run(self, log, name, args, inputSeq, captureOutput):
		commandLine = [ self.__executable ] + args + self.__flags
		try:
			proc = Popen(
				commandLine,
				bufsize = -1,
				env = self.__mergedEnv,
				stdin = None if inputSeq is None else PIPE,
				stdout = PIPE,
				stderr = PIPE if captureOutput else STDOUT,
				)
		except OSError as ex:
			print('failed to execute %s: %s' % (name, ex), file=log)
			return None if captureOutput else False
		inputText = None if inputSeq is None \
				else ('\n'.join(inputSeq) + '\n').encode('utf-8')
		stdoutdata, stderrdata = proc.communicate(inputText)
		stdouttext = stdoutdata.decode('utf-8', 'replace')
		if captureOutput:
			assert stderrdata is not None
			messages = stderrdata.decode('utf-8', 'replace')
		else:
			assert stderrdata is None
			messages = stdouttext
		if messages:
			log.write('%s command: %s\n' % (name, ' '.join(commandLine)))
			if inputText is not None:
				log.write('input:\n')
				log.write(str(inputText))
				if not inputText.endswith(b'\n'):
					log.write('\n')
				log.write('end input.\n')
			# pylint 0.18.0 somehow thinks 'messages' is a list, not a string.
			# pylint: disable-msg=E1103
			messages = messages.replace('\r', '')
			log.write(messages)
			if not messages.endswith('\n'):
				log.write('\n')
		if proc.returncode == 0:
			return stdouttext if captureOutput else True
		else:
			print('return code from %s: %d' % (name, proc.returncode), file=log)
			return None if captureOutput else False

class CompileCommand(_Command):
	__expandSignature = 'EXPAND_MACRO_'

	def compile(self, log, sourcePath, objectPath):
		return self._run(
			log, 'compiler', [ '-c', sourcePath, '-o', objectPath ], None, False
			)

	def expand(self, log, headers, *keys):
		signature = self.__expandSignature
		def iterLines():
			for header in headers:
				yield '#include %s' % header
			for key in keys:
				yield '%s%s %s' % (signature, key, key)
		output = self._run(
			log, 'preprocessor', [ '-E', '-' ], iterLines(), True
			)
		if output is None:
			if len(keys) == 1:
				return None
			else:
				return (None, ) * len(keys)
		else:
			expanded = {}
			prevKey = None
			for line in output.split('\n'):
				line = line.strip()
				if not line or line.startswith('#'):
					continue
				if line.startswith(signature):
					prevKey = None
					keyValueStr = line[len(signature) : ]
					try:
						key, value = keyValueStr.split(None, 1)
					except ValueError:
						key, value = keyValueStr, ''
					if key not in keys:
						log.write(
							'Ignoring macro expand signature match on '
							'non-requested macro "%s"\n' % key
							)
						continue
					elif value == '':
						# GCC5 puts value on separate line.
						prevKey = key
						continue
				elif prevKey is not None:
					key = prevKey
					value = line
					prevKey = None
				else:
					continue
				if value != key:
					expanded[key] = value
			if len(keys) == 1:
				return expanded.get(keys[0])
			else:
				return tuple(expanded.get(key) for key in keys)

class LinkCommand(_Command):

	def link(self, log, objectPaths, binaryPath):
		return self._run(
			log, 'linker', objectPaths + [ '-o', binaryPath ], None, False
			)