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