compilers.py 4.5 KB

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