1
|
|
|
# Copyright (c) 2012 Google Inc. All rights reserved. |
2
|
|
|
# Use of this source code is governed by a BSD-style license that can be |
3
|
|
|
# found in the LICENSE file. |
4
|
|
|
|
5
|
|
|
""" |
6
|
|
|
This module helps emulate Visual Studio 2008 behavior on top of other |
7
|
|
|
build systems, primarily ninja. |
8
|
|
|
""" |
9
|
|
|
|
10
|
|
|
import os |
11
|
|
|
import re |
12
|
|
|
import subprocess |
13
|
|
|
import sys |
14
|
|
|
|
15
|
|
|
from gyp.common import OrderedSet |
16
|
|
|
import gyp.MSVSUtil |
17
|
|
|
import gyp.MSVSVersion |
18
|
|
|
|
19
|
|
|
|
20
|
|
|
windows_quoter_regex = re.compile(r'(\\*)"') |
21
|
|
|
|
22
|
|
|
|
23
|
|
|
def QuoteForRspFile(arg): |
24
|
|
|
"""Quote a command line argument so that it appears as one argument when |
25
|
|
|
processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for |
26
|
|
|
Windows programs).""" |
27
|
|
|
# See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment |
28
|
|
|
# threads. This is actually the quoting rules for CommandLineToArgvW, not |
29
|
|
|
# for the shell, because the shell doesn't do anything in Windows. This |
30
|
|
|
# works more or less because most programs (including the compiler, etc.) |
31
|
|
|
# use that function to handle command line arguments. |
32
|
|
|
|
33
|
|
|
# For a literal quote, CommandLineToArgvW requires 2n+1 backslashes |
34
|
|
|
# preceding it, and results in n backslashes + the quote. So we substitute |
35
|
|
|
# in 2* what we match, +1 more, plus the quote. |
36
|
|
|
arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg) |
37
|
|
|
|
38
|
|
|
# %'s also need to be doubled otherwise they're interpreted as batch |
39
|
|
|
# positional arguments. Also make sure to escape the % so that they're |
40
|
|
|
# passed literally through escaping so they can be singled to just the |
41
|
|
|
# original %. Otherwise, trying to pass the literal representation that |
42
|
|
|
# looks like an environment variable to the shell (e.g. %PATH%) would fail. |
43
|
|
|
arg = arg.replace('%', '%%') |
44
|
|
|
|
45
|
|
|
# These commands are used in rsp files, so no escaping for the shell (via ^) |
46
|
|
|
# is necessary. |
47
|
|
|
|
48
|
|
|
# Finally, wrap the whole thing in quotes so that the above quote rule |
49
|
|
|
# applies and whitespace isn't a word break. |
50
|
|
|
return '"' + arg + '"' |
51
|
|
|
|
52
|
|
|
|
53
|
|
|
def EncodeRspFileList(args): |
54
|
|
|
"""Process a list of arguments using QuoteCmdExeArgument.""" |
55
|
|
|
# Note that the first argument is assumed to be the command. Don't add |
56
|
|
|
# quotes around it because then built-ins like 'echo', etc. won't work. |
57
|
|
|
# Take care to normpath only the path in the case of 'call ../x.bat' because |
58
|
|
|
# otherwise the whole thing is incorrectly interpreted as a path and not |
59
|
|
|
# normalized correctly. |
60
|
|
|
if not args: return '' |
61
|
|
|
if args[0].startswith('call '): |
62
|
|
|
call, program = args[0].split(' ', 1) |
63
|
|
|
program = call + ' ' + os.path.normpath(program) |
64
|
|
|
else: |
65
|
|
|
program = os.path.normpath(args[0]) |
66
|
|
|
return program + ' ' + ' '.join(QuoteForRspFile(arg) for arg in args[1:]) |
67
|
|
|
|
68
|
|
|
|
69
|
|
|
def _GenericRetrieve(root, default, path): |
70
|
|
|
"""Given a list of dictionary keys |path| and a tree of dicts |root|, find |
71
|
|
|
value at path, or return |default| if any of the path doesn't exist.""" |
72
|
|
|
if not root: |
73
|
|
|
return default |
74
|
|
|
if not path: |
75
|
|
|
return root |
76
|
|
|
return _GenericRetrieve(root.get(path[0]), default, path[1:]) |
77
|
|
|
|
78
|
|
|
|
79
|
|
|
def _AddPrefix(element, prefix): |
80
|
|
|
"""Add |prefix| to |element| or each subelement if element is iterable.""" |
81
|
|
|
if element is None: |
82
|
|
|
return element |
83
|
|
|
# Note, not Iterable because we don't want to handle strings like that. |
84
|
|
|
if isinstance(element, list) or isinstance(element, tuple): |
85
|
|
|
return [prefix + e for e in element] |
86
|
|
|
else: |
87
|
|
|
return prefix + element |
88
|
|
|
|
89
|
|
|
|
90
|
|
|
def _DoRemapping(element, map): |
91
|
|
|
"""If |element| then remap it through |map|. If |element| is iterable then |
92
|
|
|
each item will be remapped. Any elements not found will be removed.""" |
93
|
|
|
if map is not None and element is not None: |
94
|
|
|
if not callable(map): |
95
|
|
|
map = map.get # Assume it's a dict, otherwise a callable to do the remap. |
96
|
|
|
if isinstance(element, list) or isinstance(element, tuple): |
97
|
|
|
element = filter(None, [map(elem) for elem in element]) |
98
|
|
|
else: |
99
|
|
|
element = map(element) |
100
|
|
|
return element |
101
|
|
|
|
102
|
|
|
|
103
|
|
|
def _AppendOrReturn(append, element): |
104
|
|
|
"""If |append| is None, simply return |element|. If |append| is not None, |
105
|
|
|
then add |element| to it, adding each item in |element| if it's a list or |
106
|
|
|
tuple.""" |
107
|
|
|
if append is not None and element is not None: |
108
|
|
|
if isinstance(element, list) or isinstance(element, tuple): |
109
|
|
|
append.extend(element) |
110
|
|
|
else: |
111
|
|
|
append.append(element) |
112
|
|
|
else: |
113
|
|
|
return element |
114
|
|
|
|
115
|
|
|
|
116
|
|
|
def _FindDirectXInstallation(): |
117
|
|
|
"""Try to find an installation location for the DirectX SDK. Check for the |
118
|
|
|
standard environment variable, and if that doesn't exist, try to find |
119
|
|
|
via the registry. May return None if not found in either location.""" |
120
|
|
|
# Return previously calculated value, if there is one |
121
|
|
|
if hasattr(_FindDirectXInstallation, 'dxsdk_dir'): |
122
|
|
|
return _FindDirectXInstallation.dxsdk_dir |
123
|
|
|
|
124
|
|
|
dxsdk_dir = os.environ.get('DXSDK_DIR') |
125
|
|
|
if not dxsdk_dir: |
126
|
|
|
# Setup params to pass to and attempt to launch reg.exe. |
127
|
|
|
cmd = ['reg.exe', 'query', r'HKLM\Software\Microsoft\DirectX', '/s'] |
128
|
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
129
|
|
|
for line in p.communicate()[0].splitlines(): |
130
|
|
|
if 'InstallPath' in line: |
131
|
|
|
dxsdk_dir = line.split(' ')[3] + "\\" |
132
|
|
|
|
133
|
|
|
# Cache return value |
134
|
|
|
_FindDirectXInstallation.dxsdk_dir = dxsdk_dir |
135
|
|
|
return dxsdk_dir |
136
|
|
|
|
137
|
|
|
|
138
|
|
|
def GetGlobalVSMacroEnv(vs_version): |
139
|
|
|
"""Get a dict of variables mapping internal VS macro names to their gyp |
140
|
|
|
equivalents. Returns all variables that are independent of the target.""" |
141
|
|
|
env = {} |
142
|
|
|
# '$(VSInstallDir)' and '$(VCInstallDir)' are available when and only when |
143
|
|
|
# Visual Studio is actually installed. |
144
|
|
|
if vs_version.Path(): |
145
|
|
|
env['$(VSInstallDir)'] = vs_version.Path() |
146
|
|
|
env['$(VCInstallDir)'] = os.path.join(vs_version.Path(), 'VC') + '\\' |
147
|
|
|
# Chromium uses DXSDK_DIR in include/lib paths, but it may or may not be |
148
|
|
|
# set. This happens when the SDK is sync'd via src-internal, rather than |
149
|
|
|
# by typical end-user installation of the SDK. If it's not set, we don't |
150
|
|
|
# want to leave the unexpanded variable in the path, so simply strip it. |
151
|
|
|
dxsdk_dir = _FindDirectXInstallation() |
152
|
|
|
env['$(DXSDK_DIR)'] = dxsdk_dir if dxsdk_dir else '' |
153
|
|
|
# Try to find an installation location for the Windows DDK by checking |
154
|
|
|
# the WDK_DIR environment variable, may be None. |
155
|
|
|
env['$(WDK_DIR)'] = os.environ.get('WDK_DIR', '') |
156
|
|
|
return env |
157
|
|
|
|
158
|
|
|
def ExtractSharedMSVSSystemIncludes(configs, generator_flags): |
159
|
|
|
"""Finds msvs_system_include_dirs that are common to all targets, removes |
160
|
|
|
them from all targets, and returns an OrderedSet containing them.""" |
161
|
|
|
all_system_includes = OrderedSet( |
162
|
|
|
configs[0].get('msvs_system_include_dirs', [])) |
163
|
|
|
for config in configs[1:]: |
164
|
|
|
system_includes = config.get('msvs_system_include_dirs', []) |
165
|
|
|
all_system_includes = all_system_includes & OrderedSet(system_includes) |
166
|
|
|
if not all_system_includes: |
167
|
|
|
return None |
168
|
|
|
# Expand macros in all_system_includes. |
169
|
|
|
env = GetGlobalVSMacroEnv(GetVSVersion(generator_flags)) |
170
|
|
|
expanded_system_includes = OrderedSet([ExpandMacros(include, env) |
171
|
|
|
for include in all_system_includes]) |
172
|
|
|
if any(['$' in include for include in expanded_system_includes]): |
173
|
|
|
# Some path relies on target-specific variables, bail. |
174
|
|
|
return None |
175
|
|
|
|
176
|
|
|
# Remove system includes shared by all targets from the targets. |
177
|
|
|
for config in configs: |
178
|
|
|
includes = config.get('msvs_system_include_dirs', []) |
179
|
|
|
if includes: # Don't insert a msvs_system_include_dirs key if not needed. |
180
|
|
|
# This must check the unexpanded includes list: |
181
|
|
|
new_includes = [i for i in includes if i not in all_system_includes] |
182
|
|
|
config['msvs_system_include_dirs'] = new_includes |
183
|
|
|
return expanded_system_includes |
184
|
|
|
|
185
|
|
|
|
186
|
|
|
class MsvsSettings(object): |
187
|
|
|
"""A class that understands the gyp 'msvs_...' values (especially the |
188
|
|
|
msvs_settings field). They largely correpond to the VS2008 IDE DOM. This |
189
|
|
|
class helps map those settings to command line options.""" |
190
|
|
|
|
191
|
|
|
def __init__(self, spec, generator_flags): |
192
|
|
|
self.spec = spec |
193
|
|
|
self.vs_version = GetVSVersion(generator_flags) |
194
|
|
|
|
195
|
|
|
supported_fields = [ |
196
|
|
|
('msvs_configuration_attributes', dict), |
197
|
|
|
('msvs_settings', dict), |
198
|
|
|
('msvs_system_include_dirs', list), |
199
|
|
|
('msvs_disabled_warnings', list), |
200
|
|
|
('msvs_precompiled_header', str), |
201
|
|
|
('msvs_precompiled_source', str), |
202
|
|
|
('msvs_configuration_platform', str), |
203
|
|
|
('msvs_target_platform', str), |
204
|
|
|
] |
205
|
|
|
configs = spec['configurations'] |
206
|
|
|
for field, default in supported_fields: |
207
|
|
|
setattr(self, field, {}) |
208
|
|
|
for configname, config in configs.iteritems(): |
209
|
|
|
getattr(self, field)[configname] = config.get(field, default()) |
210
|
|
|
|
211
|
|
|
self.msvs_cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.']) |
212
|
|
|
|
213
|
|
|
unsupported_fields = [ |
214
|
|
|
'msvs_prebuild', |
215
|
|
|
'msvs_postbuild', |
216
|
|
|
] |
217
|
|
|
unsupported = [] |
218
|
|
|
for field in unsupported_fields: |
219
|
|
|
for config in configs.values(): |
220
|
|
|
if field in config: |
221
|
|
|
unsupported += ["%s not supported (target %s)." % |
222
|
|
|
(field, spec['target_name'])] |
223
|
|
|
if unsupported: |
224
|
|
|
raise Exception('\n'.join(unsupported)) |
225
|
|
|
|
226
|
|
|
def GetExtension(self): |
227
|
|
|
"""Returns the extension for the target, with no leading dot. |
228
|
|
|
|
229
|
|
|
Uses 'product_extension' if specified, otherwise uses MSVS defaults based on |
230
|
|
|
the target type. |
231
|
|
|
""" |
232
|
|
|
ext = self.spec.get('product_extension', None) |
233
|
|
|
if ext: |
234
|
|
|
return ext |
235
|
|
|
return gyp.MSVSUtil.TARGET_TYPE_EXT.get(self.spec['type'], '') |
236
|
|
|
|
237
|
|
|
def GetVSMacroEnv(self, base_to_build=None, config=None): |
238
|
|
|
"""Get a dict of variables mapping internal VS macro names to their gyp |
239
|
|
|
equivalents.""" |
240
|
|
|
target_platform = 'Win32' if self.GetArch(config) == 'x86' else 'x64' |
241
|
|
|
target_name = self.spec.get('product_prefix', '') + \ |
242
|
|
|
self.spec.get('product_name', self.spec['target_name']) |
243
|
|
|
target_dir = base_to_build + '\\' if base_to_build else '' |
244
|
|
|
target_ext = '.' + self.GetExtension() |
245
|
|
|
target_file_name = target_name + target_ext |
246
|
|
|
|
247
|
|
|
replacements = { |
248
|
|
|
'$(InputName)': '${root}', |
249
|
|
|
'$(InputPath)': '${source}', |
250
|
|
|
'$(IntDir)': '$!INTERMEDIATE_DIR', |
251
|
|
|
'$(OutDir)\\': target_dir, |
252
|
|
|
'$(PlatformName)': target_platform, |
253
|
|
|
'$(ProjectDir)\\': '', |
254
|
|
|
'$(ProjectName)': self.spec['target_name'], |
255
|
|
|
'$(TargetDir)\\': target_dir, |
256
|
|
|
'$(TargetExt)': target_ext, |
257
|
|
|
'$(TargetFileName)': target_file_name, |
258
|
|
|
'$(TargetName)': target_name, |
259
|
|
|
'$(TargetPath)': os.path.join(target_dir, target_file_name), |
260
|
|
|
} |
261
|
|
|
replacements.update(GetGlobalVSMacroEnv(self.vs_version)) |
262
|
|
|
return replacements |
263
|
|
|
|
264
|
|
|
def ConvertVSMacros(self, s, base_to_build=None, config=None): |
265
|
|
|
"""Convert from VS macro names to something equivalent.""" |
266
|
|
|
env = self.GetVSMacroEnv(base_to_build, config=config) |
267
|
|
|
return ExpandMacros(s, env) |
268
|
|
|
|
269
|
|
|
def AdjustLibraries(self, libraries): |
270
|
|
|
"""Strip -l from library if it's specified with that.""" |
271
|
|
|
libs = [lib[2:] if lib.startswith('-l') else lib for lib in libraries] |
272
|
|
|
return [lib + '.lib' if not lib.endswith('.lib') else lib for lib in libs] |
273
|
|
|
|
274
|
|
|
def _GetAndMunge(self, field, path, default, prefix, append, map): |
275
|
|
|
"""Retrieve a value from |field| at |path| or return |default|. If |
276
|
|
|
|append| is specified, and the item is found, it will be appended to that |
277
|
|
|
object instead of returned. If |map| is specified, results will be |
278
|
|
|
remapped through |map| before being returned or appended.""" |
279
|
|
|
result = _GenericRetrieve(field, default, path) |
280
|
|
|
result = _DoRemapping(result, map) |
281
|
|
|
result = _AddPrefix(result, prefix) |
282
|
|
|
return _AppendOrReturn(append, result) |
283
|
|
|
|
284
|
|
|
class _GetWrapper(object): |
285
|
|
|
def __init__(self, parent, field, base_path, append=None): |
286
|
|
|
self.parent = parent |
287
|
|
|
self.field = field |
288
|
|
|
self.base_path = [base_path] |
289
|
|
|
self.append = append |
290
|
|
|
def __call__(self, name, map=None, prefix='', default=None): |
291
|
|
|
return self.parent._GetAndMunge(self.field, self.base_path + [name], |
292
|
|
|
default=default, prefix=prefix, append=self.append, map=map) |
293
|
|
|
|
294
|
|
|
def GetArch(self, config): |
295
|
|
|
"""Get architecture based on msvs_configuration_platform and |
296
|
|
|
msvs_target_platform. Returns either 'x86' or 'x64'.""" |
297
|
|
|
configuration_platform = self.msvs_configuration_platform.get(config, '') |
298
|
|
|
platform = self.msvs_target_platform.get(config, '') |
299
|
|
|
if not platform: # If no specific override, use the configuration's. |
300
|
|
|
platform = configuration_platform |
301
|
|
|
# Map from platform to architecture. |
302
|
|
|
return {'Win32': 'x86', 'x64': 'x64'}.get(platform, 'x86') |
303
|
|
|
|
304
|
|
|
def _TargetConfig(self, config): |
305
|
|
|
"""Returns the target-specific configuration.""" |
306
|
|
|
# There's two levels of architecture/platform specification in VS. The |
307
|
|
|
# first level is globally for the configuration (this is what we consider |
308
|
|
|
# "the" config at the gyp level, which will be something like 'Debug' or |
309
|
|
|
# 'Release_x64'), and a second target-specific configuration, which is an |
310
|
|
|
# override for the global one. |config| is remapped here to take into |
311
|
|
|
# account the local target-specific overrides to the global configuration. |
312
|
|
|
arch = self.GetArch(config) |
313
|
|
|
if arch == 'x64' and not config.endswith('_x64'): |
314
|
|
|
config += '_x64' |
315
|
|
|
if arch == 'x86' and config.endswith('_x64'): |
316
|
|
|
config = config.rsplit('_', 1)[0] |
317
|
|
|
return config |
318
|
|
|
|
319
|
|
|
def _Setting(self, path, config, |
320
|
|
|
default=None, prefix='', append=None, map=None): |
321
|
|
|
"""_GetAndMunge for msvs_settings.""" |
322
|
|
|
return self._GetAndMunge( |
323
|
|
|
self.msvs_settings[config], path, default, prefix, append, map) |
324
|
|
|
|
325
|
|
|
def _ConfigAttrib(self, path, config, |
326
|
|
|
default=None, prefix='', append=None, map=None): |
327
|
|
|
"""_GetAndMunge for msvs_configuration_attributes.""" |
328
|
|
|
return self._GetAndMunge( |
329
|
|
|
self.msvs_configuration_attributes[config], |
330
|
|
|
path, default, prefix, append, map) |
331
|
|
|
|
332
|
|
|
def AdjustIncludeDirs(self, include_dirs, config): |
333
|
|
|
"""Updates include_dirs to expand VS specific paths, and adds the system |
334
|
|
|
include dirs used for platform SDK and similar.""" |
335
|
|
|
config = self._TargetConfig(config) |
336
|
|
|
includes = include_dirs + self.msvs_system_include_dirs[config] |
337
|
|
|
includes.extend(self._Setting( |
338
|
|
|
('VCCLCompilerTool', 'AdditionalIncludeDirectories'), config, default=[])) |
339
|
|
|
return [self.ConvertVSMacros(p, config=config) for p in includes] |
340
|
|
|
|
341
|
|
|
def AdjustMidlIncludeDirs(self, midl_include_dirs, config): |
342
|
|
|
"""Updates midl_include_dirs to expand VS specific paths, and adds the |
343
|
|
|
system include dirs used for platform SDK and similar.""" |
344
|
|
|
config = self._TargetConfig(config) |
345
|
|
|
includes = midl_include_dirs + self.msvs_system_include_dirs[config] |
346
|
|
|
includes.extend(self._Setting( |
347
|
|
|
('VCMIDLTool', 'AdditionalIncludeDirectories'), config, default=[])) |
348
|
|
|
return [self.ConvertVSMacros(p, config=config) for p in includes] |
349
|
|
|
|
350
|
|
|
def GetComputedDefines(self, config): |
351
|
|
|
"""Returns the set of defines that are injected to the defines list based |
352
|
|
|
on other VS settings.""" |
353
|
|
|
config = self._TargetConfig(config) |
354
|
|
|
defines = [] |
355
|
|
|
if self._ConfigAttrib(['CharacterSet'], config) == '1': |
356
|
|
|
defines.extend(('_UNICODE', 'UNICODE')) |
357
|
|
|
if self._ConfigAttrib(['CharacterSet'], config) == '2': |
358
|
|
|
defines.append('_MBCS') |
359
|
|
|
defines.extend(self._Setting( |
360
|
|
|
('VCCLCompilerTool', 'PreprocessorDefinitions'), config, default=[])) |
361
|
|
|
return defines |
362
|
|
|
|
363
|
|
|
def GetCompilerPdbName(self, config, expand_special): |
364
|
|
|
"""Get the pdb file name that should be used for compiler invocations, or |
365
|
|
|
None if there's no explicit name specified.""" |
366
|
|
|
config = self._TargetConfig(config) |
367
|
|
|
pdbname = self._Setting( |
368
|
|
|
('VCCLCompilerTool', 'ProgramDataBaseFileName'), config) |
369
|
|
|
if pdbname: |
370
|
|
|
pdbname = expand_special(self.ConvertVSMacros(pdbname)) |
371
|
|
|
return pdbname |
372
|
|
|
|
373
|
|
|
def GetMapFileName(self, config, expand_special): |
374
|
|
|
"""Gets the explicitly overriden map file name for a target or returns None |
375
|
|
|
if it's not set.""" |
376
|
|
|
config = self._TargetConfig(config) |
377
|
|
|
map_file = self._Setting(('VCLinkerTool', 'MapFileName'), config) |
378
|
|
|
if map_file: |
379
|
|
|
map_file = expand_special(self.ConvertVSMacros(map_file, config=config)) |
380
|
|
|
return map_file |
381
|
|
|
|
382
|
|
|
def GetOutputName(self, config, expand_special): |
383
|
|
|
"""Gets the explicitly overridden output name for a target or returns None |
384
|
|
|
if it's not overridden.""" |
385
|
|
|
config = self._TargetConfig(config) |
386
|
|
|
type = self.spec['type'] |
387
|
|
|
root = 'VCLibrarianTool' if type == 'static_library' else 'VCLinkerTool' |
388
|
|
|
# TODO(scottmg): Handle OutputDirectory without OutputFile. |
389
|
|
|
output_file = self._Setting((root, 'OutputFile'), config) |
390
|
|
|
if output_file: |
391
|
|
|
output_file = expand_special(self.ConvertVSMacros( |
392
|
|
|
output_file, config=config)) |
393
|
|
|
return output_file |
394
|
|
|
|
395
|
|
|
def GetPDBName(self, config, expand_special, default): |
396
|
|
|
"""Gets the explicitly overridden pdb name for a target or returns |
397
|
|
|
default if it's not overridden, or if no pdb will be generated.""" |
398
|
|
|
config = self._TargetConfig(config) |
399
|
|
|
output_file = self._Setting(('VCLinkerTool', 'ProgramDatabaseFile'), config) |
400
|
|
|
generate_debug_info = self._Setting( |
401
|
|
|
('VCLinkerTool', 'GenerateDebugInformation'), config) |
402
|
|
|
if generate_debug_info == 'true': |
403
|
|
|
if output_file: |
404
|
|
|
return expand_special(self.ConvertVSMacros(output_file, config=config)) |
405
|
|
|
else: |
406
|
|
|
return default |
407
|
|
|
else: |
408
|
|
|
return None |
409
|
|
|
|
410
|
|
|
def GetNoImportLibrary(self, config): |
411
|
|
|
"""If NoImportLibrary: true, ninja will not expect the output to include |
412
|
|
|
an import library.""" |
413
|
|
|
config = self._TargetConfig(config) |
414
|
|
|
noimplib = self._Setting(('NoImportLibrary',), config) |
415
|
|
|
return noimplib == 'true' |
416
|
|
|
|
417
|
|
|
def GetAsmflags(self, config): |
418
|
|
|
"""Returns the flags that need to be added to ml invocations.""" |
419
|
|
|
config = self._TargetConfig(config) |
420
|
|
|
asmflags = [] |
421
|
|
|
safeseh = self._Setting(('MASM', 'UseSafeExceptionHandlers'), config) |
422
|
|
|
if safeseh == 'true': |
423
|
|
|
asmflags.append('/safeseh') |
424
|
|
|
return asmflags |
425
|
|
|
|
426
|
|
|
def GetCflags(self, config): |
427
|
|
|
"""Returns the flags that need to be added to .c and .cc compilations.""" |
428
|
|
|
config = self._TargetConfig(config) |
429
|
|
|
cflags = [] |
430
|
|
|
cflags.extend(['/wd' + w for w in self.msvs_disabled_warnings[config]]) |
431
|
|
|
cl = self._GetWrapper(self, self.msvs_settings[config], |
432
|
|
|
'VCCLCompilerTool', append=cflags) |
433
|
|
|
cl('Optimization', |
434
|
|
|
map={'0': 'd', '1': '1', '2': '2', '3': 'x'}, prefix='/O', default='2') |
435
|
|
|
cl('InlineFunctionExpansion', prefix='/Ob') |
436
|
|
|
cl('DisableSpecificWarnings', prefix='/wd') |
437
|
|
|
cl('StringPooling', map={'true': '/GF'}) |
438
|
|
|
cl('EnableFiberSafeOptimizations', map={'true': '/GT'}) |
439
|
|
|
cl('OmitFramePointers', map={'false': '-', 'true': ''}, prefix='/Oy') |
440
|
|
|
cl('EnableIntrinsicFunctions', map={'false': '-', 'true': ''}, prefix='/Oi') |
441
|
|
|
cl('FavorSizeOrSpeed', map={'1': 't', '2': 's'}, prefix='/O') |
442
|
|
|
cl('FloatingPointModel', |
443
|
|
|
map={'0': 'precise', '1': 'strict', '2': 'fast'}, prefix='/fp:', |
444
|
|
|
default='0') |
445
|
|
|
cl('CompileAsManaged', map={'false': '', 'true': '/clr'}) |
446
|
|
|
cl('WholeProgramOptimization', map={'true': '/GL'}) |
447
|
|
|
cl('WarningLevel', prefix='/W') |
448
|
|
|
cl('WarnAsError', map={'true': '/WX'}) |
449
|
|
|
cl('CallingConvention', |
450
|
|
|
map={'0': 'd', '1': 'r', '2': 'z', '3': 'v'}, prefix='/G') |
451
|
|
|
cl('DebugInformationFormat', |
452
|
|
|
map={'1': '7', '3': 'i', '4': 'I'}, prefix='/Z') |
453
|
|
|
cl('RuntimeTypeInfo', map={'true': '/GR', 'false': '/GR-'}) |
454
|
|
|
cl('EnableFunctionLevelLinking', map={'true': '/Gy', 'false': '/Gy-'}) |
455
|
|
|
cl('MinimalRebuild', map={'true': '/Gm'}) |
456
|
|
|
cl('BufferSecurityCheck', map={'true': '/GS', 'false': '/GS-'}) |
457
|
|
|
cl('BasicRuntimeChecks', map={'1': 's', '2': 'u', '3': '1'}, prefix='/RTC') |
458
|
|
|
cl('RuntimeLibrary', |
459
|
|
|
map={'0': 'T', '1': 'Td', '2': 'D', '3': 'Dd'}, prefix='/M') |
460
|
|
|
cl('ExceptionHandling', map={'1': 'sc','2': 'a'}, prefix='/EH') |
461
|
|
|
cl('DefaultCharIsUnsigned', map={'true': '/J'}) |
462
|
|
|
cl('TreatWChar_tAsBuiltInType', |
463
|
|
|
map={'false': '-', 'true': ''}, prefix='/Zc:wchar_t') |
464
|
|
|
cl('EnablePREfast', map={'true': '/analyze'}) |
465
|
|
|
cl('AdditionalOptions', prefix='') |
466
|
|
|
cl('EnableEnhancedInstructionSet', |
467
|
|
|
map={'1': 'SSE', '2': 'SSE2', '3': 'AVX', '4': 'IA32', '5': 'AVX2'}, |
468
|
|
|
prefix='/arch:') |
469
|
|
|
cflags.extend(['/FI' + f for f in self._Setting( |
470
|
|
|
('VCCLCompilerTool', 'ForcedIncludeFiles'), config, default=[])]) |
471
|
|
|
if self.vs_version.short_name in ('2013', '2013e', '2015'): |
472
|
|
|
# New flag required in 2013 to maintain previous PDB behavior. |
473
|
|
|
cflags.append('/FS') |
474
|
|
|
# ninja handles parallelism by itself, don't have the compiler do it too. |
475
|
|
|
cflags = filter(lambda x: not x.startswith('/MP'), cflags) |
476
|
|
|
return cflags |
477
|
|
|
|
478
|
|
|
def _GetPchFlags(self, config, extension): |
479
|
|
|
"""Get the flags to be added to the cflags for precompiled header support. |
480
|
|
|
""" |
481
|
|
|
config = self._TargetConfig(config) |
482
|
|
|
# The PCH is only built once by a particular source file. Usage of PCH must |
483
|
|
|
# only be for the same language (i.e. C vs. C++), so only include the pch |
484
|
|
|
# flags when the language matches. |
485
|
|
|
if self.msvs_precompiled_header[config]: |
486
|
|
|
source_ext = os.path.splitext(self.msvs_precompiled_source[config])[1] |
487
|
|
|
if _LanguageMatchesForPch(source_ext, extension): |
488
|
|
|
pch = os.path.split(self.msvs_precompiled_header[config])[1] |
489
|
|
|
return ['/Yu' + pch, '/FI' + pch, '/Fp${pchprefix}.' + pch + '.pch'] |
490
|
|
|
return [] |
491
|
|
|
|
492
|
|
|
def GetCflagsC(self, config): |
493
|
|
|
"""Returns the flags that need to be added to .c compilations.""" |
494
|
|
|
config = self._TargetConfig(config) |
495
|
|
|
return self._GetPchFlags(config, '.c') |
496
|
|
|
|
497
|
|
|
def GetCflagsCC(self, config): |
498
|
|
|
"""Returns the flags that need to be added to .cc compilations.""" |
499
|
|
|
config = self._TargetConfig(config) |
500
|
|
|
return ['/TP'] + self._GetPchFlags(config, '.cc') |
501
|
|
|
|
502
|
|
|
def _GetAdditionalLibraryDirectories(self, root, config, gyp_to_build_path): |
503
|
|
|
"""Get and normalize the list of paths in AdditionalLibraryDirectories |
504
|
|
|
setting.""" |
505
|
|
|
config = self._TargetConfig(config) |
506
|
|
|
libpaths = self._Setting((root, 'AdditionalLibraryDirectories'), |
507
|
|
|
config, default=[]) |
508
|
|
|
libpaths = [os.path.normpath( |
509
|
|
|
gyp_to_build_path(self.ConvertVSMacros(p, config=config))) |
510
|
|
|
for p in libpaths] |
511
|
|
|
return ['/LIBPATH:"' + p + '"' for p in libpaths] |
512
|
|
|
|
513
|
|
|
def GetLibFlags(self, config, gyp_to_build_path): |
514
|
|
|
"""Returns the flags that need to be added to lib commands.""" |
515
|
|
|
config = self._TargetConfig(config) |
516
|
|
|
libflags = [] |
517
|
|
|
lib = self._GetWrapper(self, self.msvs_settings[config], |
518
|
|
|
'VCLibrarianTool', append=libflags) |
519
|
|
|
libflags.extend(self._GetAdditionalLibraryDirectories( |
520
|
|
|
'VCLibrarianTool', config, gyp_to_build_path)) |
521
|
|
|
lib('LinkTimeCodeGeneration', map={'true': '/LTCG'}) |
522
|
|
|
lib('TargetMachine', map={'1': 'X86', '17': 'X64', '3': 'ARM'}, |
523
|
|
|
prefix='/MACHINE:') |
524
|
|
|
lib('AdditionalOptions') |
525
|
|
|
return libflags |
526
|
|
|
|
527
|
|
|
def GetDefFile(self, gyp_to_build_path): |
528
|
|
|
"""Returns the .def file from sources, if any. Otherwise returns None.""" |
529
|
|
|
spec = self.spec |
530
|
|
|
if spec['type'] in ('shared_library', 'loadable_module', 'executable'): |
531
|
|
|
def_files = [s for s in spec.get('sources', []) if s.endswith('.def')] |
532
|
|
|
if len(def_files) == 1: |
533
|
|
|
return gyp_to_build_path(def_files[0]) |
534
|
|
|
elif len(def_files) > 1: |
535
|
|
|
raise Exception("Multiple .def files") |
536
|
|
|
return None |
537
|
|
|
|
538
|
|
|
def _GetDefFileAsLdflags(self, ldflags, gyp_to_build_path): |
539
|
|
|
""".def files get implicitly converted to a ModuleDefinitionFile for the |
540
|
|
|
linker in the VS generator. Emulate that behaviour here.""" |
541
|
|
|
def_file = self.GetDefFile(gyp_to_build_path) |
542
|
|
|
if def_file: |
543
|
|
|
ldflags.append('/DEF:"%s"' % def_file) |
544
|
|
|
|
545
|
|
|
def GetPGDName(self, config, expand_special): |
546
|
|
|
"""Gets the explicitly overridden pgd name for a target or returns None |
547
|
|
|
if it's not overridden.""" |
548
|
|
|
config = self._TargetConfig(config) |
549
|
|
|
output_file = self._Setting( |
550
|
|
|
('VCLinkerTool', 'ProfileGuidedDatabase'), config) |
551
|
|
|
if output_file: |
552
|
|
|
output_file = expand_special(self.ConvertVSMacros( |
553
|
|
|
output_file, config=config)) |
554
|
|
|
return output_file |
555
|
|
|
|
556
|
|
|
def GetLdflags(self, config, gyp_to_build_path, expand_special, |
557
|
|
|
manifest_base_name, output_name, is_executable, build_dir): |
558
|
|
|
"""Returns the flags that need to be added to link commands, and the |
559
|
|
|
manifest files.""" |
560
|
|
|
config = self._TargetConfig(config) |
561
|
|
|
ldflags = [] |
562
|
|
|
ld = self._GetWrapper(self, self.msvs_settings[config], |
563
|
|
|
'VCLinkerTool', append=ldflags) |
564
|
|
|
self._GetDefFileAsLdflags(ldflags, gyp_to_build_path) |
565
|
|
|
ld('GenerateDebugInformation', map={'true': '/DEBUG'}) |
566
|
|
|
ld('TargetMachine', map={'1': 'X86', '17': 'X64', '3': 'ARM'}, |
567
|
|
|
prefix='/MACHINE:') |
568
|
|
|
ldflags.extend(self._GetAdditionalLibraryDirectories( |
569
|
|
|
'VCLinkerTool', config, gyp_to_build_path)) |
570
|
|
|
ld('DelayLoadDLLs', prefix='/DELAYLOAD:') |
571
|
|
|
ld('TreatLinkerWarningAsErrors', prefix='/WX', |
572
|
|
|
map={'true': '', 'false': ':NO'}) |
573
|
|
|
out = self.GetOutputName(config, expand_special) |
574
|
|
|
if out: |
575
|
|
|
ldflags.append('/OUT:' + out) |
576
|
|
|
pdb = self.GetPDBName(config, expand_special, output_name + '.pdb') |
577
|
|
|
if pdb: |
578
|
|
|
ldflags.append('/PDB:' + pdb) |
579
|
|
|
pgd = self.GetPGDName(config, expand_special) |
580
|
|
|
if pgd: |
581
|
|
|
ldflags.append('/PGD:' + pgd) |
582
|
|
|
map_file = self.GetMapFileName(config, expand_special) |
583
|
|
|
ld('GenerateMapFile', map={'true': '/MAP:' + map_file if map_file |
584
|
|
|
else '/MAP'}) |
585
|
|
|
ld('MapExports', map={'true': '/MAPINFO:EXPORTS'}) |
586
|
|
|
ld('AdditionalOptions', prefix='') |
587
|
|
|
|
588
|
|
|
minimum_required_version = self._Setting( |
589
|
|
|
('VCLinkerTool', 'MinimumRequiredVersion'), config, default='') |
590
|
|
|
if minimum_required_version: |
591
|
|
|
minimum_required_version = ',' + minimum_required_version |
592
|
|
|
ld('SubSystem', |
593
|
|
|
map={'1': 'CONSOLE%s' % minimum_required_version, |
594
|
|
|
'2': 'WINDOWS%s' % minimum_required_version}, |
595
|
|
|
prefix='/SUBSYSTEM:') |
596
|
|
|
|
597
|
|
|
stack_reserve_size = self._Setting( |
598
|
|
|
('VCLinkerTool', 'StackReserveSize'), config, default='') |
599
|
|
|
if stack_reserve_size: |
600
|
|
|
stack_commit_size = self._Setting( |
601
|
|
|
('VCLinkerTool', 'StackCommitSize'), config, default='') |
602
|
|
|
if stack_commit_size: |
603
|
|
|
stack_commit_size = ',' + stack_commit_size |
604
|
|
|
ldflags.append('/STACK:%s%s' % (stack_reserve_size, stack_commit_size)) |
605
|
|
|
|
606
|
|
|
ld('TerminalServerAware', map={'1': ':NO', '2': ''}, prefix='/TSAWARE') |
607
|
|
|
ld('LinkIncremental', map={'1': ':NO', '2': ''}, prefix='/INCREMENTAL') |
608
|
|
|
ld('BaseAddress', prefix='/BASE:') |
609
|
|
|
ld('FixedBaseAddress', map={'1': ':NO', '2': ''}, prefix='/FIXED') |
610
|
|
|
ld('RandomizedBaseAddress', |
611
|
|
|
map={'1': ':NO', '2': ''}, prefix='/DYNAMICBASE') |
612
|
|
|
ld('DataExecutionPrevention', |
613
|
|
|
map={'1': ':NO', '2': ''}, prefix='/NXCOMPAT') |
614
|
|
|
ld('OptimizeReferences', map={'1': 'NOREF', '2': 'REF'}, prefix='/OPT:') |
615
|
|
|
ld('ForceSymbolReferences', prefix='/INCLUDE:') |
616
|
|
|
ld('EnableCOMDATFolding', map={'1': 'NOICF', '2': 'ICF'}, prefix='/OPT:') |
617
|
|
|
ld('LinkTimeCodeGeneration', |
618
|
|
|
map={'1': '', '2': ':PGINSTRUMENT', '3': ':PGOPTIMIZE', |
619
|
|
|
'4': ':PGUPDATE'}, |
620
|
|
|
prefix='/LTCG') |
621
|
|
|
ld('IgnoreDefaultLibraryNames', prefix='/NODEFAULTLIB:') |
622
|
|
|
ld('ResourceOnlyDLL', map={'true': '/NOENTRY'}) |
623
|
|
|
ld('EntryPointSymbol', prefix='/ENTRY:') |
624
|
|
|
ld('Profile', map={'true': '/PROFILE'}) |
625
|
|
|
ld('LargeAddressAware', |
626
|
|
|
map={'1': ':NO', '2': ''}, prefix='/LARGEADDRESSAWARE') |
627
|
|
|
# TODO(scottmg): This should sort of be somewhere else (not really a flag). |
628
|
|
|
ld('AdditionalDependencies', prefix='') |
629
|
|
|
|
630
|
|
|
if self.GetArch(config) == 'x86': |
631
|
|
|
safeseh_default = 'true' |
632
|
|
|
else: |
633
|
|
|
safeseh_default = None |
634
|
|
|
ld('ImageHasSafeExceptionHandlers', |
635
|
|
|
map={'false': ':NO', 'true': ''}, prefix='/SAFESEH', |
636
|
|
|
default=safeseh_default) |
637
|
|
|
|
638
|
|
|
# If the base address is not specifically controlled, DYNAMICBASE should |
639
|
|
|
# be on by default. |
640
|
|
|
base_flags = filter(lambda x: 'DYNAMICBASE' in x or x == '/FIXED', |
641
|
|
|
ldflags) |
642
|
|
|
if not base_flags: |
643
|
|
|
ldflags.append('/DYNAMICBASE') |
644
|
|
|
|
645
|
|
|
# If the NXCOMPAT flag has not been specified, default to on. Despite the |
646
|
|
|
# documentation that says this only defaults to on when the subsystem is |
647
|
|
|
# Vista or greater (which applies to the linker), the IDE defaults it on |
648
|
|
|
# unless it's explicitly off. |
649
|
|
|
if not filter(lambda x: 'NXCOMPAT' in x, ldflags): |
650
|
|
|
ldflags.append('/NXCOMPAT') |
651
|
|
|
|
652
|
|
|
have_def_file = filter(lambda x: x.startswith('/DEF:'), ldflags) |
653
|
|
|
manifest_flags, intermediate_manifest, manifest_files = \ |
654
|
|
|
self._GetLdManifestFlags(config, manifest_base_name, gyp_to_build_path, |
655
|
|
|
is_executable and not have_def_file, build_dir) |
656
|
|
|
ldflags.extend(manifest_flags) |
657
|
|
|
return ldflags, intermediate_manifest, manifest_files |
658
|
|
|
|
659
|
|
|
def _GetLdManifestFlags(self, config, name, gyp_to_build_path, |
660
|
|
|
allow_isolation, build_dir): |
661
|
|
|
"""Returns a 3-tuple: |
662
|
|
|
- the set of flags that need to be added to the link to generate |
663
|
|
|
a default manifest |
664
|
|
|
- the intermediate manifest that the linker will generate that should be |
665
|
|
|
used to assert it doesn't add anything to the merged one. |
666
|
|
|
- the list of all the manifest files to be merged by the manifest tool and |
667
|
|
|
included into the link.""" |
668
|
|
|
generate_manifest = self._Setting(('VCLinkerTool', 'GenerateManifest'), |
669
|
|
|
config, |
670
|
|
|
default='true') |
671
|
|
|
if generate_manifest != 'true': |
672
|
|
|
# This means not only that the linker should not generate the intermediate |
673
|
|
|
# manifest but also that the manifest tool should do nothing even when |
674
|
|
|
# additional manifests are specified. |
675
|
|
|
return ['/MANIFEST:NO'], [], [] |
676
|
|
|
|
677
|
|
|
output_name = name + '.intermediate.manifest' |
678
|
|
|
flags = [ |
679
|
|
|
'/MANIFEST', |
680
|
|
|
'/ManifestFile:' + output_name, |
681
|
|
|
] |
682
|
|
|
|
683
|
|
|
# Instead of using the MANIFESTUAC flags, we generate a .manifest to |
684
|
|
|
# include into the list of manifests. This allows us to avoid the need to |
685
|
|
|
# do two passes during linking. The /MANIFEST flag and /ManifestFile are |
686
|
|
|
# still used, and the intermediate manifest is used to assert that the |
687
|
|
|
# final manifest we get from merging all the additional manifest files |
688
|
|
|
# (plus the one we generate here) isn't modified by merging the |
689
|
|
|
# intermediate into it. |
690
|
|
|
|
691
|
|
|
# Always NO, because we generate a manifest file that has what we want. |
692
|
|
|
flags.append('/MANIFESTUAC:NO') |
693
|
|
|
|
694
|
|
|
config = self._TargetConfig(config) |
695
|
|
|
enable_uac = self._Setting(('VCLinkerTool', 'EnableUAC'), config, |
696
|
|
|
default='true') |
697
|
|
|
manifest_files = [] |
698
|
|
|
generated_manifest_outer = \ |
699
|
|
|
"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>" \ |
700
|
|
|
"<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>%s" \ |
701
|
|
|
"</assembly>" |
702
|
|
|
if enable_uac == 'true': |
703
|
|
|
execution_level = self._Setting(('VCLinkerTool', 'UACExecutionLevel'), |
704
|
|
|
config, default='0') |
705
|
|
|
execution_level_map = { |
706
|
|
|
'0': 'asInvoker', |
707
|
|
|
'1': 'highestAvailable', |
708
|
|
|
'2': 'requireAdministrator' |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
ui_access = self._Setting(('VCLinkerTool', 'UACUIAccess'), config, |
712
|
|
|
default='false') |
713
|
|
|
|
714
|
|
|
inner = ''' |
715
|
|
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> |
716
|
|
|
<security> |
717
|
|
|
<requestedPrivileges> |
718
|
|
|
<requestedExecutionLevel level='%s' uiAccess='%s' /> |
719
|
|
|
</requestedPrivileges> |
720
|
|
|
</security> |
721
|
|
|
</trustInfo>''' % (execution_level_map[execution_level], ui_access) |
722
|
|
|
else: |
723
|
|
|
inner = '' |
724
|
|
|
|
725
|
|
|
generated_manifest_contents = generated_manifest_outer % inner |
726
|
|
|
generated_name = name + '.generated.manifest' |
727
|
|
|
# Need to join with the build_dir here as we're writing it during |
728
|
|
|
# generation time, but we return the un-joined version because the build |
729
|
|
|
# will occur in that directory. We only write the file if the contents |
730
|
|
|
# have changed so that simply regenerating the project files doesn't |
731
|
|
|
# cause a relink. |
732
|
|
|
build_dir_generated_name = os.path.join(build_dir, generated_name) |
733
|
|
|
gyp.common.EnsureDirExists(build_dir_generated_name) |
734
|
|
|
f = gyp.common.WriteOnDiff(build_dir_generated_name) |
735
|
|
|
f.write(generated_manifest_contents) |
736
|
|
|
f.close() |
737
|
|
|
manifest_files = [generated_name] |
738
|
|
|
|
739
|
|
|
if allow_isolation: |
740
|
|
|
flags.append('/ALLOWISOLATION') |
741
|
|
|
|
742
|
|
|
manifest_files += self._GetAdditionalManifestFiles(config, |
743
|
|
|
gyp_to_build_path) |
744
|
|
|
return flags, output_name, manifest_files |
745
|
|
|
|
746
|
|
|
def _GetAdditionalManifestFiles(self, config, gyp_to_build_path): |
747
|
|
|
"""Gets additional manifest files that are added to the default one |
748
|
|
|
generated by the linker.""" |
749
|
|
|
files = self._Setting(('VCManifestTool', 'AdditionalManifestFiles'), config, |
750
|
|
|
default=[]) |
751
|
|
|
if isinstance(files, str): |
752
|
|
|
files = files.split(';') |
753
|
|
|
return [os.path.normpath( |
754
|
|
|
gyp_to_build_path(self.ConvertVSMacros(f, config=config))) |
755
|
|
|
for f in files] |
756
|
|
|
|
757
|
|
|
def IsUseLibraryDependencyInputs(self, config): |
758
|
|
|
"""Returns whether the target should be linked via Use Library Dependency |
759
|
|
|
Inputs (using component .objs of a given .lib).""" |
760
|
|
|
config = self._TargetConfig(config) |
761
|
|
|
uldi = self._Setting(('VCLinkerTool', 'UseLibraryDependencyInputs'), config) |
762
|
|
|
return uldi == 'true' |
763
|
|
|
|
764
|
|
|
def IsEmbedManifest(self, config): |
765
|
|
|
"""Returns whether manifest should be linked into binary.""" |
766
|
|
|
config = self._TargetConfig(config) |
767
|
|
|
embed = self._Setting(('VCManifestTool', 'EmbedManifest'), config, |
768
|
|
|
default='true') |
769
|
|
|
return embed == 'true' |
770
|
|
|
|
771
|
|
|
def IsLinkIncremental(self, config): |
772
|
|
|
"""Returns whether the target should be linked incrementally.""" |
773
|
|
|
config = self._TargetConfig(config) |
774
|
|
|
link_inc = self._Setting(('VCLinkerTool', 'LinkIncremental'), config) |
775
|
|
|
return link_inc != '1' |
776
|
|
|
|
777
|
|
|
def GetRcflags(self, config, gyp_to_ninja_path): |
778
|
|
|
"""Returns the flags that need to be added to invocations of the resource |
779
|
|
|
compiler.""" |
780
|
|
|
config = self._TargetConfig(config) |
781
|
|
|
rcflags = [] |
782
|
|
|
rc = self._GetWrapper(self, self.msvs_settings[config], |
783
|
|
|
'VCResourceCompilerTool', append=rcflags) |
784
|
|
|
rc('AdditionalIncludeDirectories', map=gyp_to_ninja_path, prefix='/I') |
785
|
|
|
rcflags.append('/I' + gyp_to_ninja_path('.')) |
786
|
|
|
rc('PreprocessorDefinitions', prefix='/d') |
787
|
|
|
# /l arg must be in hex without leading '0x' |
788
|
|
|
rc('Culture', prefix='/l', map=lambda x: hex(int(x))[2:]) |
789
|
|
|
return rcflags |
790
|
|
|
|
791
|
|
|
def BuildCygwinBashCommandLine(self, args, path_to_base): |
792
|
|
|
"""Build a command line that runs args via cygwin bash. We assume that all |
793
|
|
|
incoming paths are in Windows normpath'd form, so they need to be |
794
|
|
|
converted to posix style for the part of the command line that's passed to |
795
|
|
|
bash. We also have to do some Visual Studio macro emulation here because |
796
|
|
|
various rules use magic VS names for things. Also note that rules that |
797
|
|
|
contain ninja variables cannot be fixed here (for example ${source}), so |
798
|
|
|
the outer generator needs to make sure that the paths that are written out |
799
|
|
|
are in posix style, if the command line will be used here.""" |
800
|
|
|
cygwin_dir = os.path.normpath( |
801
|
|
|
os.path.join(path_to_base, self.msvs_cygwin_dirs[0])) |
802
|
|
|
cd = ('cd %s' % path_to_base).replace('\\', '/') |
803
|
|
|
args = [a.replace('\\', '/').replace('"', '\\"') for a in args] |
804
|
|
|
args = ["'%s'" % a.replace("'", "'\\''") for a in args] |
805
|
|
|
bash_cmd = ' '.join(args) |
806
|
|
|
cmd = ( |
807
|
|
|
'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir + |
808
|
|
|
'bash -c "%s ; %s"' % (cd, bash_cmd)) |
809
|
|
|
return cmd |
810
|
|
|
|
811
|
|
|
def IsRuleRunUnderCygwin(self, rule): |
812
|
|
|
"""Determine if an action should be run under cygwin. If the variable is |
813
|
|
|
unset, or set to 1 we use cygwin.""" |
814
|
|
|
return int(rule.get('msvs_cygwin_shell', |
815
|
|
|
self.spec.get('msvs_cygwin_shell', 1))) != 0 |
816
|
|
|
|
817
|
|
|
def _HasExplicitRuleForExtension(self, spec, extension): |
818
|
|
|
"""Determine if there's an explicit rule for a particular extension.""" |
819
|
|
|
for rule in spec.get('rules', []): |
820
|
|
|
if rule['extension'] == extension: |
821
|
|
|
return True |
822
|
|
|
return False |
823
|
|
|
|
824
|
|
|
def _HasExplicitIdlActions(self, spec): |
825
|
|
|
"""Determine if an action should not run midl for .idl files.""" |
826
|
|
|
return any([action.get('explicit_idl_action', 0) |
827
|
|
|
for action in spec.get('actions', [])]) |
828
|
|
|
|
829
|
|
|
def HasExplicitIdlRulesOrActions(self, spec): |
830
|
|
|
"""Determine if there's an explicit rule or action for idl files. When |
831
|
|
|
there isn't we need to generate implicit rules to build MIDL .idl files.""" |
832
|
|
|
return (self._HasExplicitRuleForExtension(spec, 'idl') or |
833
|
|
|
self._HasExplicitIdlActions(spec)) |
834
|
|
|
|
835
|
|
|
def HasExplicitAsmRules(self, spec): |
836
|
|
|
"""Determine if there's an explicit rule for asm files. When there isn't we |
837
|
|
|
need to generate implicit rules to assemble .asm files.""" |
838
|
|
|
return self._HasExplicitRuleForExtension(spec, 'asm') |
839
|
|
|
|
840
|
|
|
def GetIdlBuildData(self, source, config): |
841
|
|
|
"""Determine the implicit outputs for an idl file. Returns output |
842
|
|
|
directory, outputs, and variables and flags that are required.""" |
843
|
|
|
config = self._TargetConfig(config) |
844
|
|
|
midl_get = self._GetWrapper(self, self.msvs_settings[config], 'VCMIDLTool') |
845
|
|
|
def midl(name, default=None): |
846
|
|
|
return self.ConvertVSMacros(midl_get(name, default=default), |
847
|
|
|
config=config) |
848
|
|
|
tlb = midl('TypeLibraryName', default='${root}.tlb') |
849
|
|
|
header = midl('HeaderFileName', default='${root}.h') |
850
|
|
|
dlldata = midl('DLLDataFileName', default='dlldata.c') |
851
|
|
|
iid = midl('InterfaceIdentifierFileName', default='${root}_i.c') |
852
|
|
|
proxy = midl('ProxyFileName', default='${root}_p.c') |
853
|
|
|
# Note that .tlb is not included in the outputs as it is not always |
854
|
|
|
# generated depending on the content of the input idl file. |
855
|
|
|
outdir = midl('OutputDirectory', default='') |
856
|
|
|
output = [header, dlldata, iid, proxy] |
857
|
|
|
variables = [('tlb', tlb), |
858
|
|
|
('h', header), |
859
|
|
|
('dlldata', dlldata), |
860
|
|
|
('iid', iid), |
861
|
|
|
('proxy', proxy)] |
862
|
|
|
# TODO(scottmg): Are there configuration settings to set these flags? |
863
|
|
|
target_platform = 'win32' if self.GetArch(config) == 'x86' else 'x64' |
864
|
|
|
flags = ['/char', 'signed', '/env', target_platform, '/Oicf'] |
865
|
|
|
return outdir, output, variables, flags |
866
|
|
|
|
867
|
|
|
|
868
|
|
|
def _LanguageMatchesForPch(source_ext, pch_source_ext): |
869
|
|
|
c_exts = ('.c',) |
870
|
|
|
cc_exts = ('.cc', '.cxx', '.cpp') |
871
|
|
|
return ((source_ext in c_exts and pch_source_ext in c_exts) or |
872
|
|
|
(source_ext in cc_exts and pch_source_ext in cc_exts)) |
873
|
|
|
|
874
|
|
|
|
875
|
|
|
class PrecompiledHeader(object): |
876
|
|
|
"""Helper to generate dependencies and build rules to handle generation of |
877
|
|
|
precompiled headers. Interface matches the GCH handler in xcode_emulation.py. |
878
|
|
|
""" |
879
|
|
|
def __init__( |
880
|
|
|
self, settings, config, gyp_to_build_path, gyp_to_unique_output, obj_ext): |
881
|
|
|
self.settings = settings |
882
|
|
|
self.config = config |
883
|
|
|
pch_source = self.settings.msvs_precompiled_source[self.config] |
884
|
|
|
self.pch_source = gyp_to_build_path(pch_source) |
885
|
|
|
filename, _ = os.path.splitext(pch_source) |
886
|
|
|
self.output_obj = gyp_to_unique_output(filename + obj_ext).lower() |
887
|
|
|
|
888
|
|
|
def _PchHeader(self): |
889
|
|
|
"""Get the header that will appear in an #include line for all source |
890
|
|
|
files.""" |
891
|
|
|
return os.path.split(self.settings.msvs_precompiled_header[self.config])[1] |
892
|
|
|
|
893
|
|
|
def GetObjDependencies(self, sources, objs, arch): |
894
|
|
|
"""Given a list of sources files and the corresponding object files, |
895
|
|
|
returns a list of the pch files that should be depended upon. The |
896
|
|
|
additional wrapping in the return value is for interface compatibility |
897
|
|
|
with make.py on Mac, and xcode_emulation.py.""" |
898
|
|
|
assert arch is None |
899
|
|
|
if not self._PchHeader(): |
900
|
|
|
return [] |
901
|
|
|
pch_ext = os.path.splitext(self.pch_source)[1] |
902
|
|
|
for source in sources: |
903
|
|
|
if _LanguageMatchesForPch(os.path.splitext(source)[1], pch_ext): |
904
|
|
|
return [(None, None, self.output_obj)] |
905
|
|
|
return [] |
906
|
|
|
|
907
|
|
|
def GetPchBuildCommands(self, arch): |
908
|
|
|
"""Not used on Windows as there are no additional build steps required |
909
|
|
|
(instead, existing steps are modified in GetFlagsModifications below).""" |
910
|
|
|
return [] |
911
|
|
|
|
912
|
|
|
def GetFlagsModifications(self, input, output, implicit, command, |
913
|
|
|
cflags_c, cflags_cc, expand_special): |
914
|
|
|
"""Get the modified cflags and implicit dependencies that should be used |
915
|
|
|
for the pch compilation step.""" |
916
|
|
|
if input == self.pch_source: |
917
|
|
|
pch_output = ['/Yc' + self._PchHeader()] |
918
|
|
|
if command == 'cxx': |
919
|
|
|
return ([('cflags_cc', map(expand_special, cflags_cc + pch_output))], |
920
|
|
|
self.output_obj, []) |
921
|
|
|
elif command == 'cc': |
922
|
|
|
return ([('cflags_c', map(expand_special, cflags_c + pch_output))], |
923
|
|
|
self.output_obj, []) |
924
|
|
|
return [], output, implicit |
925
|
|
|
|
926
|
|
|
|
927
|
|
|
vs_version = None |
928
|
|
|
def GetVSVersion(generator_flags): |
929
|
|
|
global vs_version |
930
|
|
|
if not vs_version: |
931
|
|
|
vs_version = gyp.MSVSVersion.SelectVisualStudioVersion( |
932
|
|
|
generator_flags.get('msvs_version', 'auto'), |
933
|
|
|
allow_fallback=False) |
934
|
|
|
return vs_version |
935
|
|
|
|
936
|
|
|
def _GetVsvarsSetupArgs(generator_flags, arch): |
937
|
|
|
vs = GetVSVersion(generator_flags) |
938
|
|
|
return vs.SetupScript() |
939
|
|
|
|
940
|
|
|
def ExpandMacros(string, expansions): |
941
|
|
|
"""Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv |
942
|
|
|
for the canonical way to retrieve a suitable dict.""" |
943
|
|
|
if '$' in string: |
944
|
|
|
for old, new in expansions.iteritems(): |
945
|
|
|
assert '$(' not in new, new |
946
|
|
|
string = string.replace(old, new) |
947
|
|
|
return string |
948
|
|
|
|
949
|
|
|
def _ExtractImportantEnvironment(output_of_set): |
950
|
|
|
"""Extracts environment variables required for the toolchain to run from |
951
|
|
|
a textual dump output by the cmd.exe 'set' command.""" |
952
|
|
|
envvars_to_save = ( |
953
|
|
|
'goma_.*', # TODO(scottmg): This is ugly, but needed for goma. |
954
|
|
|
'include', |
955
|
|
|
'lib', |
956
|
|
|
'libpath', |
957
|
|
|
'path', |
958
|
|
|
'pathext', |
959
|
|
|
'systemroot', |
960
|
|
|
'temp', |
961
|
|
|
'tmp', |
962
|
|
|
) |
963
|
|
|
env = {} |
964
|
|
|
for line in output_of_set.splitlines(): |
965
|
|
|
for envvar in envvars_to_save: |
966
|
|
|
if re.match(envvar + '=', line.lower()): |
967
|
|
|
var, setting = line.split('=', 1) |
968
|
|
|
if envvar == 'path': |
969
|
|
|
# Our own rules (for running gyp-win-tool) and other actions in |
970
|
|
|
# Chromium rely on python being in the path. Add the path to this |
971
|
|
|
# python here so that if it's not in the path when ninja is run |
972
|
|
|
# later, python will still be found. |
973
|
|
|
setting = os.path.dirname(sys.executable) + os.pathsep + setting |
974
|
|
|
env[var.upper()] = setting |
975
|
|
|
break |
976
|
|
|
for required in ('SYSTEMROOT', 'TEMP', 'TMP'): |
977
|
|
|
if required not in env: |
978
|
|
|
raise Exception('Environment variable "%s" ' |
979
|
|
|
'required to be set to valid path' % required) |
980
|
|
|
return env |
981
|
|
|
|
982
|
|
|
def _FormatAsEnvironmentBlock(envvar_dict): |
983
|
|
|
"""Format as an 'environment block' directly suitable for CreateProcess. |
984
|
|
|
Briefly this is a list of key=value\0, terminated by an additional \0. See |
985
|
|
|
CreateProcess documentation for more details.""" |
986
|
|
|
block = '' |
987
|
|
|
nul = '\0' |
988
|
|
|
for key, value in envvar_dict.iteritems(): |
989
|
|
|
block += key + '=' + value + nul |
990
|
|
|
block += nul |
991
|
|
|
return block |
992
|
|
|
|
993
|
|
|
def _ExtractCLPath(output_of_where): |
994
|
|
|
"""Gets the path to cl.exe based on the output of calling the environment |
995
|
|
|
setup batch file, followed by the equivalent of `where`.""" |
996
|
|
|
# Take the first line, as that's the first found in the PATH. |
997
|
|
|
for line in output_of_where.strip().splitlines(): |
998
|
|
|
if line.startswith('LOC:'): |
999
|
|
|
return line[len('LOC:'):].strip() |
1000
|
|
|
|
1001
|
|
|
def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags, |
1002
|
|
|
system_includes, open_out): |
1003
|
|
|
"""It's not sufficient to have the absolute path to the compiler, linker, |
1004
|
|
|
etc. on Windows, as those tools rely on .dlls being in the PATH. We also |
1005
|
|
|
need to support both x86 and x64 compilers within the same build (to support |
1006
|
|
|
msvs_target_platform hackery). Different architectures require a different |
1007
|
|
|
compiler binary, and different supporting environment variables (INCLUDE, |
1008
|
|
|
LIB, LIBPATH). So, we extract the environment here, wrap all invocations |
1009
|
|
|
of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which |
1010
|
|
|
sets up the environment, and then we do not prefix the compiler with |
1011
|
|
|
an absolute path, instead preferring something like "cl.exe" in the rule |
1012
|
|
|
which will then run whichever the environment setup has put in the path. |
1013
|
|
|
When the following procedure to generate environment files does not |
1014
|
|
|
meet your requirement (e.g. for custom toolchains), you can pass |
1015
|
|
|
"-G ninja_use_custom_environment_files" to the gyp to suppress file |
1016
|
|
|
generation and use custom environment files prepared by yourself.""" |
1017
|
|
|
archs = ('x86', 'x64') |
1018
|
|
|
if generator_flags.get('ninja_use_custom_environment_files', 0): |
1019
|
|
|
cl_paths = {} |
1020
|
|
|
for arch in archs: |
1021
|
|
|
cl_paths[arch] = 'cl.exe' |
1022
|
|
|
return cl_paths |
1023
|
|
|
vs = GetVSVersion(generator_flags) |
1024
|
|
|
cl_paths = {} |
1025
|
|
|
for arch in archs: |
1026
|
|
|
# Extract environment variables for subprocesses. |
1027
|
|
|
args = vs.SetupScript(arch) |
1028
|
|
|
args.extend(('&&', 'set')) |
1029
|
|
|
popen = subprocess.Popen( |
1030
|
|
|
args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
1031
|
|
|
variables, _ = popen.communicate() |
1032
|
|
|
env = _ExtractImportantEnvironment(variables) |
1033
|
|
|
|
1034
|
|
|
# Inject system includes from gyp files into INCLUDE. |
1035
|
|
|
if system_includes: |
1036
|
|
|
system_includes = system_includes | OrderedSet( |
1037
|
|
|
env.get('INCLUDE', '').split(';')) |
1038
|
|
|
env['INCLUDE'] = ';'.join(system_includes) |
1039
|
|
|
|
1040
|
|
|
env_block = _FormatAsEnvironmentBlock(env) |
1041
|
|
|
f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb') |
1042
|
|
|
f.write(env_block) |
1043
|
|
|
f.close() |
1044
|
|
|
|
1045
|
|
|
# Find cl.exe location for this architecture. |
1046
|
|
|
args = vs.SetupScript(arch) |
1047
|
|
|
args.extend(('&&', |
1048
|
|
|
'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i')) |
1049
|
|
|
popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE) |
1050
|
|
|
output, _ = popen.communicate() |
1051
|
|
|
cl_paths[arch] = _ExtractCLPath(output) |
1052
|
|
|
return cl_paths |
1053
|
|
|
|
1054
|
|
|
def VerifyMissingSources(sources, build_dir, generator_flags, gyp_to_ninja): |
1055
|
|
|
"""Emulate behavior of msvs_error_on_missing_sources present in the msvs |
1056
|
|
|
generator: Check that all regular source files, i.e. not created at run time, |
1057
|
|
|
exist on disk. Missing files cause needless recompilation when building via |
1058
|
|
|
VS, and we want this check to match for people/bots that build using ninja, |
1059
|
|
|
so they're not surprised when the VS build fails.""" |
1060
|
|
|
if int(generator_flags.get('msvs_error_on_missing_sources', 0)): |
1061
|
|
|
no_specials = filter(lambda x: '$' not in x, sources) |
1062
|
|
|
relative = [os.path.join(build_dir, gyp_to_ninja(s)) for s in no_specials] |
1063
|
|
|
missing = filter(lambda x: not os.path.exists(x), relative) |
1064
|
|
|
if missing: |
1065
|
|
|
# They'll look like out\Release\..\..\stuff\things.cc, so normalize the |
1066
|
|
|
# path for a slightly less crazy looking output. |
1067
|
|
|
cleaned_up = [os.path.normpath(x) for x in missing] |
1068
|
|
|
raise Exception('Missing input files:\n%s' % '\n'.join(cleaned_up)) |
1069
|
|
|
|
1070
|
|
|
# Sets some values in default_variables, which are required for many |
1071
|
|
|
# generators, run on Windows. |
1072
|
|
|
def CalculateCommonVariables(default_variables, params): |
1073
|
|
|
generator_flags = params.get('generator_flags', {}) |
1074
|
|
|
|
1075
|
|
|
# Set a variable so conditions can be based on msvs_version. |
1076
|
|
|
msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags) |
1077
|
|
|
default_variables['MSVS_VERSION'] = msvs_version.ShortName() |
1078
|
|
|
|
1079
|
|
|
# To determine processor word size on Windows, in addition to checking |
1080
|
|
|
# PROCESSOR_ARCHITECTURE (which reflects the word size of the current |
1081
|
|
|
# process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which |
1082
|
|
|
# contains the actual word size of the system when running thru WOW64). |
1083
|
|
|
if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or |
1084
|
|
|
'64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')): |
1085
|
|
|
default_variables['MSVS_OS_BITS'] = 64 |
1086
|
|
|
else: |
1087
|
|
|
default_variables['MSVS_OS_BITS'] = 32 |
1088
|
|
|
|