| # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 | """Utility functions for Windows builds. | 
 |  | 
 | This file is copied to the build directory as part of toolchain setup and | 
 | is used to set up calls to tools used by the build that need wrappers. | 
 | """ | 
 |  | 
 | import os | 
 | import pathlib | 
 | import re | 
 | import shutil | 
 | import subprocess | 
 | import stat | 
 | import sys | 
 |  | 
 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 
 |  | 
 | # A regex matching an argument corresponding to the output filename passed to | 
 | # link.exe. | 
 | _LINK_EXE_OUT_ARG = re.compile('/OUT:(?P<out>.+)$', re.IGNORECASE) | 
 |  | 
 |  | 
 | def main(args): | 
 |     executor = WinTool() | 
 |     exit_code = executor.Dispatch(args) | 
 |     if exit_code is not None: | 
 |         sys.exit(exit_code) | 
 |  | 
 |  | 
 | class WinTool(object): | 
 |     """This class performs all the Windows tooling steps. The methods can either | 
 |   be executed directly, or dispatched from an argument list.""" | 
 |  | 
 |     def _UseSeparateMspdbsrv(self, env, args): | 
 |         """Allows to use a unique instance of mspdbsrv.exe per linker instead of a | 
 |     shared one.""" | 
 |         if len(args) < 1: | 
 |             raise Exception("Not enough arguments") | 
 |  | 
 |         if args[0] != 'link.exe': | 
 |             return | 
 |  | 
 |         # Use the output filename passed to the linker to generate an endpoint name | 
 |         # for mspdbsrv.exe. | 
 |         endpoint_name = None | 
 |         for arg in args: | 
 |             m = _LINK_EXE_OUT_ARG.match(arg) | 
 |             if m: | 
 |                 endpoint_name = re.sub(r'\W+', '', | 
 |                                        '%s_%d' % (m.group('out'), os.getpid())) | 
 |                 break | 
 |  | 
 |         if endpoint_name is None: | 
 |             return | 
 |  | 
 |         # Adds the appropriate environment variable. This will be read by link.exe | 
 |         # to know which instance of mspdbsrv.exe it should connect to (if it's | 
 |         # not set then the default endpoint is used). | 
 |         env['_MSPDBSRV_ENDPOINT_'] = endpoint_name | 
 |  | 
 |     def Dispatch(self, args): | 
 |         """Dispatches a string command to a method.""" | 
 |         if len(args) < 1: | 
 |             raise Exception("Not enough arguments") | 
 |  | 
 |         method = "Exec%s" % self._CommandifyName(args[0]) | 
 |         return getattr(self, method)(*args[1:]) | 
 |  | 
 |     def _CommandifyName(self, name_string): | 
 |         """Transforms a tool name like recursive-mirror to RecursiveMirror.""" | 
 |         return name_string.title().replace('-', '') | 
 |  | 
 |     def _GetEnv(self, arch): | 
 |         """Gets the saved environment from a file for a given architecture.""" | 
 |         # The environment is saved as an "environment block" (see CreateProcess | 
 |         # and msvs_emulation for details). We convert to a dict here. | 
 |         # Drop last 2 NULs, one for list terminator, one for trailing vs. separator. | 
 |         pairs = open(arch).read()[:-2].split('\0') | 
 |         kvs = [item.split('=', 1) for item in pairs] | 
 |         return dict(kvs) | 
 |  | 
 |     def ExecStamp(self, path): | 
 |         """Simple stamp command.""" | 
 |         open(path, 'w').close() | 
 |  | 
 |     def ExecDeleteFile(self, path): | 
 |         """Simple file delete command.""" | 
 |         if os.path.exists(path): | 
 |             os.unlink(path) | 
 |  | 
 |     def ExecRecursiveMirror(self, source, dest): | 
 |         """Emulation of rm -rf out && cp -af in out.""" | 
 |         if os.path.exists(dest): | 
 |             if os.path.isdir(dest): | 
 |  | 
 |                 def _on_error(fn, path, dummy_excinfo): | 
 |                     # The operation failed, possibly because the file is set to | 
 |                     # read-only. If that's why, make it writable and try the op again. | 
 |                     if not os.access(path, os.W_OK): | 
 |                         os.chmod(path, stat.S_IWRITE) | 
 |                     fn(path) | 
 |  | 
 |                 shutil.rmtree(dest, onerror=_on_error) | 
 |             else: | 
 |                 if not os.access(dest, os.W_OK): | 
 |                     # Attempt to make the file writable before deleting it. | 
 |                     os.chmod(dest, stat.S_IWRITE) | 
 |                 os.unlink(dest) | 
 |  | 
 |         if os.path.isdir(source): | 
 |             shutil.copytree(source, dest) | 
 |         else: | 
 |             shutil.copy2(source, dest) | 
 |             # Try to diagnose crbug.com/741603 | 
 |             if not os.path.exists(dest): | 
 |                 raise Exception("Copying of %s to %s failed" % (source, dest)) | 
 |  | 
 |     def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args): | 
 |         """Filter diagnostic output from link that looks like: | 
 |     '   Creating library ui.dll.lib and object ui.dll.exp' | 
 |     This happens when there are exports from the dll or exe. | 
 |     """ | 
 |         env = self._GetEnv(arch) | 
 |         if use_separate_mspdbsrv == 'True': | 
 |             self._UseSeparateMspdbsrv(env, args) | 
 |         if sys.platform == 'win32': | 
 |             args = list( | 
 |                 args)  # *args is a tuple by default, which is read-only. | 
 |             args[0] = args[0].replace('/', '\\') | 
 |         # https://docs.python.org/2/library/subprocess.html: | 
 |         # "On Unix with shell=True [...] if args is a sequence, the first item | 
 |         # specifies the command string, and any additional items will be treated as | 
 |         # additional arguments to the shell itself.  That is to say, Popen does the | 
 |         # equivalent of: | 
 |         #   Popen(['/bin/sh', '-c', args[0], args[1], ...])" | 
 |         # For that reason, since going through the shell doesn't seem necessary on | 
 |         # non-Windows don't do that there. | 
 |         link = subprocess.Popen(args, | 
 |                                 shell=sys.platform == 'win32', | 
 |                                 env=env, | 
 |                                 stdout=subprocess.PIPE, | 
 |                                 stderr=subprocess.STDOUT, | 
 |                                 universal_newlines=True) | 
 |         # Read output one line at a time as it shows up to avoid OOM failures when | 
 |         # GBs of output is produced. | 
 |         for line in link.stdout: | 
 |             if (not line.startswith('   Creating library ') and | 
 |                     not line.startswith('Generating code') and | 
 |                     not line.startswith('Finished generating code')): | 
 |                 print(line) | 
 |         link_result = link.wait() | 
 |  | 
 |         if link_result != 0: | 
 |             return link_result | 
 |  | 
 |         # The toolchain configuration in gn always expects a .lib file to be | 
 |         # included in the output of the link step. However, this only happens | 
 |         # when the output has exports, and that is not always the case. In | 
 |         # order to satisfy the expected outputs, we create a dummy .lib file | 
 |         # in cases where the link step didn't actually create one. | 
 |         for arg in args: | 
 |             m = _LINK_EXE_OUT_ARG.match(arg) | 
 |             if m: | 
 |                 output_filename = m.group('out') | 
 |                 (basename, extension) = os.path.splitext(output_filename) | 
 |                 if extension == '.exe': | 
 |                     lib_path = pathlib.Path(basename + ".lib") | 
 |                     if not os.path.exists(lib_path): | 
 |                         lib_path.touch() | 
 |                     break | 
 |  | 
 |         return link_result | 
 |  | 
 |     def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl, | 
 |                         *flags): | 
 |         """Filter noisy filenames output from MIDL compile step that isn't | 
 |     quietable via command line flags. | 
 |     """ | 
 |         args = ['midl', '/nologo'] + list(flags) + [ | 
 |             '/out', outdir, '/tlb', tlb, '/h', h, '/dlldata', dlldata, '/iid', | 
 |             iid, '/proxy', proxy, idl | 
 |         ] | 
 |         env = self._GetEnv(arch) | 
 |         popen = subprocess.Popen(args, | 
 |                                  shell=True, | 
 |                                  env=env, | 
 |                                  stdout=subprocess.PIPE, | 
 |                                  stderr=subprocess.STDOUT, | 
 |                                  universal_newlines=True) | 
 |         out, _ = popen.communicate() | 
 |         # Filter junk out of stdout, and write filtered versions. Output we want | 
 |         # to filter is pairs of lines that look like this: | 
 |         # Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl | 
 |         # objidl.idl | 
 |         lines = out.splitlines() | 
 |         prefixes = ('Processing ', '64 bit Processing ') | 
 |         processing = set( | 
 |             os.path.basename(x) for x in lines if x.startswith(prefixes)) | 
 |         for line in lines: | 
 |             if not line.startswith(prefixes) and line not in processing: | 
 |                 print(line) | 
 |         return popen.returncode | 
 |  | 
 |     def ExecAsmWrapper(self, arch, *args): | 
 |         """Filter logo banner from invocations of asm.exe.""" | 
 |         env = self._GetEnv(arch) | 
 |         popen = subprocess.Popen(args, | 
 |                                  shell=True, | 
 |                                  env=env, | 
 |                                  stdout=subprocess.PIPE, | 
 |                                  stderr=subprocess.STDOUT, | 
 |                                  universal_newlines=True) | 
 |         out, _ = popen.communicate() | 
 |         for line in out.splitlines(): | 
 |             # Split to avoid triggering license checks: | 
 |             if (not line.startswith('Copy' + 'right (C' + | 
 |                                     ') Microsoft Corporation') and | 
 |                     not line.startswith('Microsoft (R) Macro Assembler') and | 
 |                     not line.startswith(' Assembling: ') and line): | 
 |                 print(line) | 
 |         return popen.returncode | 
 |  | 
 |     def ExecRcWrapper(self, arch, *args): | 
 |         """Filter logo banner from invocations of rc.exe. Older versions of RC | 
 |     don't support the /nologo flag.""" | 
 |         env = self._GetEnv(arch) | 
 |         popen = subprocess.Popen(args, | 
 |                                  shell=True, | 
 |                                  env=env, | 
 |                                  stdout=subprocess.PIPE, | 
 |                                  stderr=subprocess.STDOUT, | 
 |                                  universal_newlines=True) | 
 |         out, _ = popen.communicate() | 
 |         for line in out.splitlines(): | 
 |             if (not line.startswith( | 
 |                     'Microsoft (R) Windows (R) Resource Compiler') and | 
 |                     not line.startswith('Copy' + 'right (C' + | 
 |                                         ') Microsoft Corporation') and line): | 
 |                 print(line) | 
 |         return popen.returncode | 
 |  | 
 |     def ExecActionWrapper(self, arch, rspfile, *dirname): | 
 |         """Runs an action command line from a response file using the environment | 
 |     for |arch|. If |dirname| is supplied, use that as the working directory.""" | 
 |         env = self._GetEnv(arch) | 
 |         # TODO(scottmg): This is a temporary hack to get some specific variables | 
 |         # through to actions that are set after GN-time. http://crbug.com/333738. | 
 |         for k, v in os.environ.items(): | 
 |             if k not in env: | 
 |                 env[k] = v | 
 |         args = open(rspfile).read() | 
 |         dirname = dirname[0] if dirname else None | 
 |         return subprocess.call(args, shell=True, env=env, cwd=dirname) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     sys.exit(main(sys.argv[1:])) |