3 # Copyright (c) 2012 Google Inc. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
7 """Utility functions for Windows builds.
9 These functions are executed via gyp-win-tool when using the ninja generator.
18 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
20 # A regex matching an argument corresponding to a PDB filename passed as an
21 # argument to link.exe.
22 _LINK_EXE_PDB_ARG = re.compile('/PDB:(?P<pdb>.+\.exe\.pdb)$', re.IGNORECASE)
26 exit_code = executor.Dispatch(args)
27 if exit_code is not None:
31 class WinTool(object):
32 """This class performs all the Windows tooling steps. The methods can either
33 be executed directly, or dispatched from an argument list."""
35 def _MaybeUseSeparateMspdbsrv(self, env, args):
36 """Allows to use a unique instance of mspdbsrv.exe for the linkers linking
37 an .exe target if GYP_USE_SEPARATE_MSPDBSRV has been set."""
38 if not os.environ.get('GYP_USE_SEPARATE_MSPDBSRV'):
42 raise Exception("Not enough arguments")
44 if args[0] != 'link.exe':
47 # Checks if this linker produces a PDB for an .exe target. If so use the
48 # name of this PDB to generate an endpoint name for mspdbsrv.exe.
51 m = _LINK_EXE_PDB_ARG.match(arg)
53 endpoint_name = '%s_%d' % (m.group('pdb'), os.getpid())
56 if endpoint_name is None:
59 # Adds the appropriate environment variable. This will be read by link.exe
60 # to know which instance of mspdbsrv.exe it should connect to (if it's
61 # not set then the default endpoint is used).
62 env['_MSPDBSRV_ENDPOINT_'] = endpoint_name
64 def Dispatch(self, args):
65 """Dispatches a string command to a method."""
67 raise Exception("Not enough arguments")
69 method = "Exec%s" % self._CommandifyName(args[0])
70 return getattr(self, method)(*args[1:])
72 def _CommandifyName(self, name_string):
73 """Transforms a tool name like recursive-mirror to RecursiveMirror."""
74 return name_string.title().replace('-', '')
76 def _GetEnv(self, arch):
77 """Gets the saved environment from a file for a given architecture."""
78 # The environment is saved as an "environment block" (see CreateProcess
79 # and msvs_emulation for details). We convert to a dict here.
80 # Drop last 2 NULs, one for list terminator, one for trailing vs. separator.
81 pairs = open(arch).read()[:-2].split('\0')
82 kvs = [item.split('=', 1) for item in pairs]
85 def ExecStamp(self, path):
86 """Simple stamp command."""
87 open(path, 'w').close()
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):
96 if os.path.isdir(source):
97 shutil.copytree(source, dest)
99 shutil.copy2(source, dest)
101 def ExecLinkWrapper(self, arch, *args):
102 """Filter diagnostic output from link that looks like:
103 ' Creating library ui.dll.lib and object ui.dll.exp'
104 This happens when there are exports from the dll or exe.
106 env = self._GetEnv(arch)
107 self._MaybeUseSeparateMspdbsrv(env, args)
108 link = subprocess.Popen(args,
111 stdout=subprocess.PIPE,
112 stderr=subprocess.STDOUT)
113 out, _ = link.communicate()
114 for line in out.splitlines():
115 if not line.startswith(' Creating library '):
117 return link.returncode
119 def ExecManifestWrapper(self, arch, *args):
120 """Run manifest tool with environment set. Strip out undesirable warning
121 (some XML blocks are recognized by the OS loader, but not the manifest
123 env = self._GetEnv(arch)
124 popen = subprocess.Popen(args, shell=True, env=env,
125 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
126 out, _ = popen.communicate()
127 for line in out.splitlines():
128 if line and 'manifest authoring warning 81010002' not in line:
130 return popen.returncode
132 def ExecManifestToRc(self, arch, *args):
133 """Creates a resource file pointing a SxS assembly manifest.
134 |args| is tuple containing path to resource file, path to manifest file
135 and resource name which can be "1" (for executables) or "2" (for DLLs)."""
136 manifest_path, resource_path, resource_name = args
137 with open(resource_path, 'wb') as output:
138 output.write('#include <windows.h>\n%s RT_MANIFEST "%s"' % (
140 os.path.abspath(manifest_path).replace('\\', '/')))
142 def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl,
144 """Filter noisy filenames output from MIDL compile step that isn't
145 quietable via command line flags.
147 args = ['midl', '/nologo'] + list(flags) + [
155 env = self._GetEnv(arch)
156 popen = subprocess.Popen(args, shell=True, env=env,
157 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
158 out, _ = popen.communicate()
159 # Filter junk out of stdout, and write filtered versions. Output we want
160 # to filter is pairs of lines that look like this:
161 # Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl
163 lines = out.splitlines()
164 prefix = 'Processing '
165 processing = set(os.path.basename(x) for x in lines if x.startswith(prefix))
167 if not line.startswith(prefix) and line not in processing:
169 return popen.returncode
171 def ExecAsmWrapper(self, arch, *args):
172 """Filter logo banner from invocations of asm.exe."""
173 env = self._GetEnv(arch)
174 # MSVS doesn't assemble x64 asm files.
175 if arch == 'environment.x64':
177 popen = subprocess.Popen(args, shell=True, env=env,
178 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
179 out, _ = popen.communicate()
180 for line in out.splitlines():
181 if (not line.startswith('Copyright (C) Microsoft Corporation') and
182 not line.startswith('Microsoft (R) Macro Assembler') and
183 not line.startswith(' Assembling: ') and
186 return popen.returncode
188 def ExecRcWrapper(self, arch, *args):
189 """Filter logo banner from invocations of rc.exe. Older versions of RC
190 don't support the /nologo flag."""
191 env = self._GetEnv(arch)
192 popen = subprocess.Popen(args, shell=True, env=env,
193 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
194 out, _ = popen.communicate()
195 for line in out.splitlines():
196 if (not line.startswith('Microsoft (R) Windows (R) Resource Compiler') and
197 not line.startswith('Copyright (C) Microsoft Corporation') and
200 return popen.returncode
202 def ExecActionWrapper(self, arch, rspfile, *dir):
203 """Runs an action command line from a response file using the environment
204 for |arch|. If |dir| is supplied, use that as the working directory."""
205 env = self._GetEnv(arch)
206 args = open(rspfile).read()
207 dir = dir[0] if dir else None
208 return subprocess.call(args, shell=True, env=env, cwd=dir)
210 if __name__ == '__main__':
211 sys.exit(main(sys.argv[1:]))