tool_wrapper.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. # Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Utility functions for Windows builds.
  5. This file is copied to the build directory as part of toolchain setup and
  6. is used to set up calls to tools used by the build that need wrappers.
  7. """
  8. from __future__ import print_function
  9. import os
  10. import re
  11. import shutil
  12. import subprocess
  13. import stat
  14. import sys
  15. BASE_DIR = os.path.dirname(os.path.abspath(__file__))
  16. # A regex matching an argument corresponding to the output filename passed to
  17. # link.exe.
  18. _LINK_EXE_OUT_ARG = re.compile('/OUT:(?P<out>.+)$', re.IGNORECASE)
  19. def main(args):
  20. exit_code = WinTool().Dispatch(args)
  21. if exit_code is not None:
  22. sys.exit(exit_code)
  23. class WinTool(object):
  24. """This class performs all the Windows tooling steps. The methods can either
  25. be executed directly, or dispatched from an argument list."""
  26. def _UseSeparateMspdbsrv(self, env, args):
  27. """Allows to use a unique instance of mspdbsrv.exe per linker instead of a
  28. shared one."""
  29. if len(args) < 1:
  30. raise Exception("Not enough arguments")
  31. if args[0] != 'link.exe':
  32. return
  33. # Use the output filename passed to the linker to generate an endpoint name
  34. # for mspdbsrv.exe.
  35. endpoint_name = None
  36. for arg in args:
  37. m = _LINK_EXE_OUT_ARG.match(arg)
  38. if m:
  39. endpoint_name = re.sub(r'\W+', '',
  40. '%s_%d' % (m.group('out'), os.getpid()))
  41. break
  42. if endpoint_name is None:
  43. return
  44. # Adds the appropriate environment variable. This will be read by link.exe
  45. # to know which instance of mspdbsrv.exe it should connect to (if it's
  46. # not set then the default endpoint is used).
  47. env['_MSPDBSRV_ENDPOINT_'] = endpoint_name
  48. def Dispatch(self, args):
  49. """Dispatches a string command to a method."""
  50. if len(args) < 1:
  51. raise Exception("Not enough arguments")
  52. method = "Exec%s" % self._CommandifyName(args[0])
  53. return getattr(self, method)(*args[1:])
  54. def _CommandifyName(self, name_string):
  55. """Transforms a tool name like recursive-mirror to RecursiveMirror."""
  56. return name_string.title().replace('-', '')
  57. def _GetEnv(self, arch):
  58. """Gets the saved environment from a file for a given architecture."""
  59. # The environment is saved as an "environment block" (see CreateProcess
  60. # and msvs_emulation for details). We convert to a dict here.
  61. # Drop last 2 NULs, one for list terminator, one for trailing vs. separator.
  62. pairs = open(arch).read()[:-2].split('\0')
  63. kvs = [item.split('=', 1) for item in pairs]
  64. return dict(kvs)
  65. def ExecDeleteFile(self, path):
  66. """Simple file delete command."""
  67. if os.path.exists(path):
  68. os.unlink(path)
  69. def ExecRecursiveMirror(self, source, dest):
  70. """Emulation of rm -rf out && cp -af in out."""
  71. if os.path.exists(dest):
  72. if os.path.isdir(dest):
  73. def _on_error(fn, path, dummy_excinfo):
  74. # The operation failed, possibly because the file is set to
  75. # read-only. If that's why, make it writable and try the op again.
  76. if not os.access(path, os.W_OK):
  77. os.chmod(path, stat.S_IWRITE)
  78. fn(path)
  79. shutil.rmtree(dest, onerror=_on_error)
  80. else:
  81. if not os.access(dest, os.W_OK):
  82. # Attempt to make the file writable before deleting it.
  83. os.chmod(dest, stat.S_IWRITE)
  84. os.unlink(dest)
  85. if os.path.isdir(source):
  86. shutil.copytree(source, dest)
  87. else:
  88. shutil.copy2(source, dest)
  89. # Try to diagnose crbug.com/741603
  90. if not os.path.exists(dest):
  91. raise Exception("Copying of %s to %s failed" % (source, dest))
  92. def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args):
  93. """Filter diagnostic output from link that looks like:
  94. ' Creating library ui.dll.lib and object ui.dll.exp'
  95. This happens when there are exports from the dll or exe.
  96. """
  97. env = self._GetEnv(arch)
  98. if use_separate_mspdbsrv == 'True':
  99. self._UseSeparateMspdbsrv(env, args)
  100. if sys.platform == 'win32':
  101. args = list(args) # *args is a tuple by default, which is read-only.
  102. args[0] = args[0].replace('/', '\\')
  103. # https://docs.python.org/2/library/subprocess.html:
  104. # "On Unix with shell=True [...] if args is a sequence, the first item
  105. # specifies the command string, and any additional items will be treated as
  106. # additional arguments to the shell itself. That is to say, Popen does the
  107. # equivalent of:
  108. # Popen(['/bin/sh', '-c', args[0], args[1], ...])"
  109. # For that reason, since going through the shell doesn't seem necessary on
  110. # non-Windows don't do that there.
  111. pe_name = None
  112. for arg in args:
  113. m = _LINK_EXE_OUT_ARG.match(arg)
  114. if m:
  115. pe_name = m.group('out')
  116. link = subprocess.Popen(args, shell=sys.platform == 'win32', env=env,
  117. stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  118. # Read output one line at a time as it shows up to avoid OOM failures when
  119. # GBs of output is produced.
  120. for line in link.stdout:
  121. if (not line.startswith(b' Creating library ')
  122. and not line.startswith(b'Generating code')
  123. and not line.startswith(b'Finished generating code')):
  124. print(line)
  125. return link.wait()
  126. def ExecAsmWrapper(self, arch, *args):
  127. """Filter logo banner from invocations of asm.exe."""
  128. env = self._GetEnv(arch)
  129. if sys.platform == 'win32':
  130. # Windows ARM64 uses clang-cl as assembler which has '/' as path
  131. # separator, convert it to '\\' when running on Windows.
  132. args = list(args) # *args is a tuple by default, which is read-only
  133. args[0] = args[0].replace('/', '\\')
  134. # See comment in ExecLinkWrapper() for why shell=False on non-win.
  135. popen = subprocess.Popen(args, shell=sys.platform == 'win32', env=env,
  136. stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  137. out, _ = popen.communicate()
  138. for line in out.decode('utf8').splitlines():
  139. if not line.startswith(' Assembling: '):
  140. print(line)
  141. return popen.returncode
  142. def ExecRcWrapper(self, arch, *args):
  143. """Converts .rc files to .res files."""
  144. env = self._GetEnv(arch)
  145. args = list(args)
  146. rcpy_args = args[:]
  147. rcpy_args[0:1] = [sys.executable, os.path.join(BASE_DIR, 'rc', 'rc.py')]
  148. rcpy_args.append('/showIncludes')
  149. return subprocess.call(rcpy_args, env=env)
  150. def ExecActionWrapper(self, arch, rspfile, *dirname):
  151. """Runs an action command line from a response file using the environment
  152. for |arch|. If |dirname| is supplied, use that as the working directory."""
  153. env = self._GetEnv(arch)
  154. # TODO(scottmg): This is a temporary hack to get some specific variables
  155. # through to actions that are set after GN-time. http://crbug.com/333738.
  156. for k, v in os.environ.items():
  157. if k not in env:
  158. env[k] = v
  159. args = open(rspfile).read()
  160. dirname = dirname[0] if dirname else None
  161. return subprocess.call(args, shell=True, env=env, cwd=dirname)
  162. if __name__ == '__main__':
  163. sys.exit(main(sys.argv[1:]))