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.
12 from ctypes import windll, wintypes
18 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
23 exit_code = executor.Dispatch(args)
24 if exit_code is not None:
28 class LinkLock(object):
29 """A flock-style lock to limit the number of concurrent links to one.
31 Uses a session-local mutex based on the file's directory.
34 name = 'Local\\%s' % BASE_DIR.replace('\\', '_').replace(':', '_')
35 self.mutex = windll.kernel32.CreateMutexW(
38 wintypes.create_unicode_buffer(name))
40 result = windll.kernel32.WaitForSingleObject(
41 self.mutex, wintypes.c_int(0xFFFFFFFF))
42 # 0x80 means another process was killed without releasing the mutex, but
43 # that this process has been given ownership. This is fine for our
45 assert result in (0, 0x80), (
46 "%s, %s" % (result, windll.kernel32.GetLastError()))
48 def __exit__(self, type, value, traceback):
49 windll.kernel32.ReleaseMutex(self.mutex)
50 windll.kernel32.CloseHandle(self.mutex)
53 class WinTool(object):
54 """This class performs all the Windows tooling steps. The methods can either
55 be executed directly, or dispatched from an argument list."""
57 def Dispatch(self, args):
58 """Dispatches a string command to a method."""
60 raise Exception("Not enough arguments")
62 method = "Exec%s" % self._CommandifyName(args[0])
63 return getattr(self, method)(*args[1:])
65 def _CommandifyName(self, name_string):
66 """Transforms a tool name like recursive-mirror to RecursiveMirror."""
67 return name_string.title().replace('-', '')
69 def _GetEnv(self, arch):
70 """Gets the saved environment from a file for a given architecture."""
71 # The environment is saved as an "environment block" (see CreateProcess
72 # and msvs_emulation for details). We convert to a dict here.
73 # Drop last 2 NULs, one for list terminator, one for trailing vs. separator.
74 pairs = open(arch).read()[:-2].split('\0')
75 kvs = [item.split('=', 1) for item in pairs]
78 def ExecStamp(self, path):
79 """Simple stamp command."""
80 open(path, 'w').close()
82 def ExecRecursiveMirror(self, source, dest):
83 """Emulation of rm -rf out && cp -af in out."""
84 if os.path.exists(dest):
85 if os.path.isdir(dest):
89 if os.path.isdir(source):
90 shutil.copytree(source, dest)
92 shutil.copy2(source, dest)
94 def ExecLinkWrapper(self, arch, *args):
95 """Filter diagnostic output from link that looks like:
96 ' Creating library ui.dll.lib and object ui.dll.exp'
97 This happens when there are exports from the dll or exe.
100 env = self._GetEnv(arch)
101 popen = subprocess.Popen(args, shell=True, env=env,
102 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
103 out, _ = popen.communicate()
104 for line in out.splitlines():
105 if not line.startswith(' Creating library '):
107 return popen.returncode
109 def ExecManifestWrapper(self, arch, *args):
110 """Run manifest tool with environment set. Strip out undesirable warning
111 (some XML blocks are recognized by the OS loader, but not the manifest
113 env = self._GetEnv(arch)
114 popen = subprocess.Popen(args, shell=True, env=env,
115 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
116 out, _ = popen.communicate()
117 for line in out.splitlines():
118 if line and 'manifest authoring warning 81010002' not in line:
120 return popen.returncode
122 def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl,
124 """Filter noisy filenames output from MIDL compile step that isn't
125 quietable via command line flags.
127 args = ['midl', '/nologo'] + list(flags) + [
135 env = self._GetEnv(arch)
136 popen = subprocess.Popen(args, shell=True, env=env,
137 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
138 out, _ = popen.communicate()
139 # Filter junk out of stdout, and write filtered versions. Output we want
140 # to filter is pairs of lines that look like this:
141 # Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl
143 lines = out.splitlines()
144 prefix = 'Processing '
145 processing = set(os.path.basename(x) for x in lines if x.startswith(prefix))
147 if not line.startswith(prefix) and line not in processing:
149 return popen.returncode
151 def ExecAsmWrapper(self, arch, *args):
152 """Filter logo banner from invocations of asm.exe."""
153 env = self._GetEnv(arch)
154 # MSVS doesn't assemble x64 asm files.
155 if arch == 'environment.x64':
157 popen = subprocess.Popen(args, shell=True, env=env,
158 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
159 out, _ = popen.communicate()
160 for line in out.splitlines():
161 if (not line.startswith('Copyright (C) Microsoft Corporation') and
162 not line.startswith('Microsoft (R) Macro Assembler') and
163 not line.startswith(' Assembling: ') and
166 return popen.returncode
168 def ExecRcWrapper(self, arch, *args):
169 """Filter logo banner from invocations of rc.exe. Older versions of RC
170 don't support the /nologo flag."""
171 env = self._GetEnv(arch)
172 popen = subprocess.Popen(args, shell=True, env=env,
173 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
174 out, _ = popen.communicate()
175 for line in out.splitlines():
176 if (not line.startswith('Microsoft (R) Windows (R) Resource Compiler') and
177 not line.startswith('Copyright (C) Microsoft Corporation') and
180 return popen.returncode
182 def ExecActionWrapper(self, arch, rspfile, *dir):
183 """Runs an action command line from a response file using the environment
184 for |arch|. If |dir| is supplied, use that as the working directory."""
185 env = self._GetEnv(arch)
186 args = open(rspfile).read()
187 dir = dir[0] if dir else None
188 popen = subprocess.Popen(args, shell=True, env=env, cwd=dir)
190 return popen.returncode
192 if __name__ == '__main__':
193 sys.exit(main(sys.argv[1:]))