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