1
|
|
|
#!/usr/bin/env python |
2
|
|
|
|
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. |
6
|
|
|
|
7
|
|
|
"""Utility functions for Windows builds. |
8
|
|
|
|
9
|
|
|
These functions are executed via gyp-win-tool when using the ninja generator. |
10
|
|
|
""" |
11
|
|
|
|
12
|
|
|
import os |
13
|
|
|
import re |
14
|
|
|
import shutil |
15
|
|
|
import subprocess |
16
|
|
|
import stat |
17
|
|
|
import string |
18
|
|
|
import sys |
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
|
|
|
executor = WinTool() |
28
|
|
|
exit_code = executor.Dispatch(args) |
29
|
|
|
if exit_code is not None: |
30
|
|
|
sys.exit(exit_code) |
31
|
|
|
|
32
|
|
|
|
33
|
|
|
class WinTool(object): |
34
|
|
|
"""This class performs all the Windows tooling steps. The methods can either |
35
|
|
|
be executed directly, or dispatched from an argument list.""" |
36
|
|
|
|
37
|
|
|
def _UseSeparateMspdbsrv(self, env, args): |
38
|
|
|
"""Allows to use a unique instance of mspdbsrv.exe per linker instead of a |
39
|
|
|
shared one.""" |
40
|
|
|
if len(args) < 1: |
41
|
|
|
raise Exception("Not enough arguments") |
42
|
|
|
|
43
|
|
|
if args[0] != 'link.exe': |
44
|
|
|
return |
45
|
|
|
|
46
|
|
|
# Use the output filename passed to the linker to generate an endpoint name |
47
|
|
|
# for mspdbsrv.exe. |
48
|
|
|
endpoint_name = None |
49
|
|
|
for arg in args: |
50
|
|
|
m = _LINK_EXE_OUT_ARG.match(arg) |
51
|
|
|
if m: |
52
|
|
|
endpoint_name = re.sub(r'\W+', '', |
53
|
|
|
'%s_%d' % (m.group('out'), os.getpid())) |
54
|
|
|
break |
55
|
|
|
|
56
|
|
|
if endpoint_name is None: |
57
|
|
|
return |
58
|
|
|
|
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 |
63
|
|
|
|
64
|
|
|
def Dispatch(self, args): |
65
|
|
|
"""Dispatches a string command to a method.""" |
66
|
|
|
if len(args) < 1: |
67
|
|
|
raise Exception("Not enough arguments") |
68
|
|
|
|
69
|
|
|
method = "Exec%s" % self._CommandifyName(args[0]) |
70
|
|
|
return getattr(self, method)(*args[1:]) |
71
|
|
|
|
72
|
|
|
def _CommandifyName(self, name_string): |
73
|
|
|
"""Transforms a tool name like recursive-mirror to RecursiveMirror.""" |
74
|
|
|
return name_string.title().replace('-', '') |
75
|
|
|
|
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] |
83
|
|
|
return dict(kvs) |
84
|
|
|
|
85
|
|
|
def ExecStamp(self, path): |
86
|
|
|
"""Simple stamp command.""" |
87
|
|
|
open(path, 'w').close() |
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, 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
|
|
|
|
111
|
|
|
def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args): |
112
|
|
|
"""Filter diagnostic output from link that looks like: |
113
|
|
|
' Creating library ui.dll.lib and object ui.dll.exp' |
114
|
|
|
This happens when there are exports from the dll or exe. |
115
|
|
|
""" |
116
|
|
|
env = self._GetEnv(arch) |
117
|
|
|
if use_separate_mspdbsrv == 'True': |
118
|
|
|
self._UseSeparateMspdbsrv(env, args) |
119
|
|
|
link = subprocess.Popen([args[0].replace('/', '\\')] + list(args[1:]), |
120
|
|
|
shell=True, |
121
|
|
|
env=env, |
122
|
|
|
stdout=subprocess.PIPE, |
123
|
|
|
stderr=subprocess.STDOUT) |
124
|
|
|
out, _ = link.communicate() |
125
|
|
|
for line in out.splitlines(): |
126
|
|
|
if (not line.startswith(' Creating library ') and |
127
|
|
|
not line.startswith('Generating code') and |
128
|
|
|
not line.startswith('Finished generating code')): |
129
|
|
|
print line |
130
|
|
|
return link.returncode |
131
|
|
|
|
132
|
|
|
def ExecLinkWithManifests(self, arch, embed_manifest, out, ldcmd, resname, |
133
|
|
|
mt, rc, intermediate_manifest, *manifests): |
134
|
|
|
"""A wrapper for handling creating a manifest resource and then executing |
135
|
|
|
a link command.""" |
136
|
|
|
# The 'normal' way to do manifests is to have link generate a manifest |
137
|
|
|
# based on gathering dependencies from the object files, then merge that |
138
|
|
|
# manifest with other manifests supplied as sources, convert the merged |
139
|
|
|
# manifest to a resource, and then *relink*, including the compiled |
140
|
|
|
# version of the manifest resource. This breaks incremental linking, and |
141
|
|
|
# is generally overly complicated. Instead, we merge all the manifests |
142
|
|
|
# provided (along with one that includes what would normally be in the |
143
|
|
|
# linker-generated one, see msvs_emulation.py), and include that into the |
144
|
|
|
# first and only link. We still tell link to generate a manifest, but we |
145
|
|
|
# only use that to assert that our simpler process did not miss anything. |
146
|
|
|
variables = { |
147
|
|
|
'python': sys.executable, |
148
|
|
|
'arch': arch, |
149
|
|
|
'out': out, |
150
|
|
|
'ldcmd': ldcmd, |
151
|
|
|
'resname': resname, |
152
|
|
|
'mt': mt, |
153
|
|
|
'rc': rc, |
154
|
|
|
'intermediate_manifest': intermediate_manifest, |
155
|
|
|
'manifests': ' '.join(manifests), |
156
|
|
|
} |
157
|
|
|
add_to_ld = '' |
158
|
|
|
if manifests: |
159
|
|
|
subprocess.check_call( |
160
|
|
|
'%(python)s gyp-win-tool manifest-wrapper %(arch)s %(mt)s -nologo ' |
161
|
|
|
'-manifest %(manifests)s -out:%(out)s.manifest' % variables) |
162
|
|
|
if embed_manifest == 'True': |
163
|
|
|
subprocess.check_call( |
164
|
|
|
'%(python)s gyp-win-tool manifest-to-rc %(arch)s %(out)s.manifest' |
165
|
|
|
' %(out)s.manifest.rc %(resname)s' % variables) |
166
|
|
|
subprocess.check_call( |
167
|
|
|
'%(python)s gyp-win-tool rc-wrapper %(arch)s %(rc)s ' |
168
|
|
|
'%(out)s.manifest.rc' % variables) |
169
|
|
|
add_to_ld = ' %(out)s.manifest.res' % variables |
170
|
|
|
subprocess.check_call(ldcmd + add_to_ld) |
171
|
|
|
|
172
|
|
|
# Run mt.exe on the theoretically complete manifest we generated, merging |
173
|
|
|
# it with the one the linker generated to confirm that the linker |
174
|
|
|
# generated one does not add anything. This is strictly unnecessary for |
175
|
|
|
# correctness, it's only to verify that e.g. /MANIFESTDEPENDENCY was not |
176
|
|
|
# used in a #pragma comment. |
177
|
|
|
if manifests: |
178
|
|
|
# Merge the intermediate one with ours to .assert.manifest, then check |
179
|
|
|
# that .assert.manifest is identical to ours. |
180
|
|
|
subprocess.check_call( |
181
|
|
|
'%(python)s gyp-win-tool manifest-wrapper %(arch)s %(mt)s -nologo ' |
182
|
|
|
'-manifest %(out)s.manifest %(intermediate_manifest)s ' |
183
|
|
|
'-out:%(out)s.assert.manifest' % variables) |
184
|
|
|
assert_manifest = '%(out)s.assert.manifest' % variables |
185
|
|
|
our_manifest = '%(out)s.manifest' % variables |
186
|
|
|
# Load and normalize the manifests. mt.exe sometimes removes whitespace, |
187
|
|
|
# and sometimes doesn't unfortunately. |
188
|
|
|
with open(our_manifest, 'rb') as our_f: |
189
|
|
|
with open(assert_manifest, 'rb') as assert_f: |
190
|
|
|
our_data = our_f.read().translate(None, string.whitespace) |
191
|
|
|
assert_data = assert_f.read().translate(None, string.whitespace) |
192
|
|
|
if our_data != assert_data: |
193
|
|
|
os.unlink(out) |
194
|
|
|
def dump(filename): |
195
|
|
|
sys.stderr.write('%s\n-----\n' % filename) |
196
|
|
|
with open(filename, 'rb') as f: |
197
|
|
|
sys.stderr.write(f.read() + '\n-----\n') |
198
|
|
|
dump(intermediate_manifest) |
199
|
|
|
dump(our_manifest) |
200
|
|
|
dump(assert_manifest) |
201
|
|
|
sys.stderr.write( |
202
|
|
|
'Linker generated manifest "%s" added to final manifest "%s" ' |
203
|
|
|
'(result in "%s"). ' |
204
|
|
|
'Were /MANIFEST switches used in #pragma statements? ' % ( |
205
|
|
|
intermediate_manifest, our_manifest, assert_manifest)) |
206
|
|
|
return 1 |
207
|
|
|
|
208
|
|
|
def ExecManifestWrapper(self, arch, *args): |
209
|
|
|
"""Run manifest tool with environment set. Strip out undesirable warning |
210
|
|
|
(some XML blocks are recognized by the OS loader, but not the manifest |
211
|
|
|
tool).""" |
212
|
|
|
env = self._GetEnv(arch) |
213
|
|
|
popen = subprocess.Popen(args, shell=True, env=env, |
214
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
215
|
|
|
out, _ = popen.communicate() |
216
|
|
|
for line in out.splitlines(): |
217
|
|
|
if line and 'manifest authoring warning 81010002' not in line: |
218
|
|
|
print line |
219
|
|
|
return popen.returncode |
220
|
|
|
|
221
|
|
|
def ExecManifestToRc(self, arch, *args): |
222
|
|
|
"""Creates a resource file pointing a SxS assembly manifest. |
223
|
|
|
|args| is tuple containing path to resource file, path to manifest file |
224
|
|
|
and resource name which can be "1" (for executables) or "2" (for DLLs).""" |
225
|
|
|
manifest_path, resource_path, resource_name = args |
226
|
|
|
with open(resource_path, 'wb') as output: |
227
|
|
|
output.write('#include <windows.h>\n%s RT_MANIFEST "%s"' % ( |
228
|
|
|
resource_name, |
229
|
|
|
os.path.abspath(manifest_path).replace('\\', '/'))) |
230
|
|
|
|
231
|
|
|
def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl, |
232
|
|
|
*flags): |
233
|
|
|
"""Filter noisy filenames output from MIDL compile step that isn't |
234
|
|
|
quietable via command line flags. |
235
|
|
|
""" |
236
|
|
|
args = ['midl', '/nologo'] + list(flags) + [ |
237
|
|
|
'/out', outdir, |
238
|
|
|
'/tlb', tlb, |
239
|
|
|
'/h', h, |
240
|
|
|
'/dlldata', dlldata, |
241
|
|
|
'/iid', iid, |
242
|
|
|
'/proxy', proxy, |
243
|
|
|
idl] |
244
|
|
|
env = self._GetEnv(arch) |
245
|
|
|
popen = subprocess.Popen(args, shell=True, env=env, |
246
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
247
|
|
|
out, _ = popen.communicate() |
248
|
|
|
# Filter junk out of stdout, and write filtered versions. Output we want |
249
|
|
|
# to filter is pairs of lines that look like this: |
250
|
|
|
# Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl |
251
|
|
|
# objidl.idl |
252
|
|
|
lines = out.splitlines() |
253
|
|
|
prefixes = ('Processing ', '64 bit Processing ') |
254
|
|
|
processing = set(os.path.basename(x) |
255
|
|
|
for x in lines if x.startswith(prefixes)) |
256
|
|
|
for line in lines: |
257
|
|
|
if not line.startswith(prefixes) and line not in processing: |
258
|
|
|
print line |
259
|
|
|
return popen.returncode |
260
|
|
|
|
261
|
|
|
def ExecAsmWrapper(self, arch, *args): |
262
|
|
|
"""Filter logo banner from invocations of asm.exe.""" |
263
|
|
|
env = self._GetEnv(arch) |
264
|
|
|
popen = subprocess.Popen(args, shell=True, env=env, |
265
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
266
|
|
|
out, _ = popen.communicate() |
267
|
|
|
for line in out.splitlines(): |
268
|
|
|
if (not line.startswith('Copyright (C) Microsoft Corporation') and |
269
|
|
|
not line.startswith('Microsoft (R) Macro Assembler') and |
270
|
|
|
not line.startswith(' Assembling: ') and |
271
|
|
|
line): |
272
|
|
|
print line |
273
|
|
|
return popen.returncode |
274
|
|
|
|
275
|
|
|
def ExecRcWrapper(self, arch, *args): |
276
|
|
|
"""Filter logo banner from invocations of rc.exe. Older versions of RC |
277
|
|
|
don't support the /nologo flag.""" |
278
|
|
|
env = self._GetEnv(arch) |
279
|
|
|
popen = subprocess.Popen(args, shell=True, env=env, |
280
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
281
|
|
|
out, _ = popen.communicate() |
282
|
|
|
for line in out.splitlines(): |
283
|
|
|
if (not line.startswith('Microsoft (R) Windows (R) Resource Compiler') and |
284
|
|
|
not line.startswith('Copyright (C) Microsoft Corporation') and |
285
|
|
|
line): |
286
|
|
|
print line |
287
|
|
|
return popen.returncode |
288
|
|
|
|
289
|
|
|
def ExecActionWrapper(self, arch, rspfile, *dir): |
290
|
|
|
"""Runs an action command line from a response file using the environment |
291
|
|
|
for |arch|. If |dir| is supplied, use that as the working directory.""" |
292
|
|
|
env = self._GetEnv(arch) |
293
|
|
|
# TODO(scottmg): This is a temporary hack to get some specific variables |
294
|
|
|
# through to actions that are set after gyp-time. http://crbug.com/333738. |
295
|
|
|
for k, v in os.environ.iteritems(): |
296
|
|
|
if k not in env: |
297
|
|
|
env[k] = v |
298
|
|
|
args = open(rspfile).read() |
299
|
|
|
dir = dir[0] if dir else None |
300
|
|
|
return subprocess.call(args, shell=True, env=env, cwd=dir) |
301
|
|
|
|
302
|
|
|
def ExecClCompile(self, project_dir, selected_files): |
303
|
|
|
"""Executed by msvs-ninja projects when the 'ClCompile' target is used to |
304
|
|
|
build selected C/C++ files.""" |
305
|
|
|
project_dir = os.path.relpath(project_dir, BASE_DIR) |
306
|
|
|
selected_files = selected_files.split(';') |
307
|
|
|
ninja_targets = [os.path.join(project_dir, filename) + '^^' |
308
|
|
|
for filename in selected_files] |
309
|
|
|
cmd = ['ninja.exe'] |
310
|
|
|
cmd.extend(ninja_targets) |
311
|
|
|
return subprocess.call(cmd, shell=True, cwd=BASE_DIR) |
312
|
|
|
|
313
|
|
|
if __name__ == '__main__': |
314
|
|
|
sys.exit(main(sys.argv[1:])) |
315
|
|
|
|