- add third_party src.
[platform/framework/web/crosswalk.git] / src / 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('TargetMachine', map={'1': 'X86', '17': 'X64'}, prefix='/MACHINE:')
424     lib('AdditionalOptions')
425     return libflags
426
427   def GetDefFile(self, gyp_to_build_path):
428     """Returns the .def file from sources, if any.  Otherwise returns None."""
429     spec = self.spec
430     if spec['type'] in ('shared_library', 'loadable_module', 'executable'):
431       def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
432       if len(def_files) == 1:
433         return gyp_to_build_path(def_files[0])
434       elif len(def_files) > 1:
435         raise Exception("Multiple .def files")
436     return None
437
438   def _GetDefFileAsLdflags(self, ldflags, gyp_to_build_path):
439     """.def files get implicitly converted to a ModuleDefinitionFile for the
440     linker in the VS generator. Emulate that behaviour here."""
441     def_file = self.GetDefFile(gyp_to_build_path)
442     if def_file:
443       ldflags.append('/DEF:"%s"' % def_file)
444
445   def GetLdflags(self, config, gyp_to_build_path, expand_special,
446                  manifest_base_name, is_executable):
447     """Returns the flags that need to be added to link commands, and the
448     manifest files."""
449     config = self._TargetConfig(config)
450     ldflags = []
451     ld = self._GetWrapper(self, self.msvs_settings[config],
452                           'VCLinkerTool', append=ldflags)
453     self._GetDefFileAsLdflags(ldflags, gyp_to_build_path)
454     ld('GenerateDebugInformation', map={'true': '/DEBUG'})
455     ld('TargetMachine', map={'1': 'X86', '17': 'X64'}, prefix='/MACHINE:')
456     ldflags.extend(self._GetAdditionalLibraryDirectories(
457         'VCLinkerTool', config, gyp_to_build_path))
458     ld('DelayLoadDLLs', prefix='/DELAYLOAD:')
459     out = self.GetOutputName(config, expand_special)
460     if out:
461       ldflags.append('/OUT:' + out)
462     pdb = self.GetPDBName(config, expand_special)
463     if pdb:
464       ldflags.append('/PDB:' + pdb)
465     map_file = self.GetMapFileName(config, expand_special)
466     ld('GenerateMapFile', map={'true': '/MAP:' + map_file if map_file
467         else '/MAP'})
468     ld('MapExports', map={'true': '/MAPINFO:EXPORTS'})
469     ld('AdditionalOptions', prefix='')
470
471     minimum_required_version = self._Setting(
472         ('VCLinkerTool', 'MinimumRequiredVersion'), config, default='')
473     if minimum_required_version:
474       minimum_required_version = ',' + minimum_required_version
475     ld('SubSystem',
476        map={'1': 'CONSOLE%s' % minimum_required_version,
477             '2': 'WINDOWS%s' % minimum_required_version},
478        prefix='/SUBSYSTEM:')
479
480     ld('TerminalServerAware', map={'1': ':NO', '2': ''}, prefix='/TSAWARE')
481     ld('LinkIncremental', map={'1': ':NO', '2': ''}, prefix='/INCREMENTAL')
482     ld('BaseAddress', prefix='/BASE:')
483     ld('FixedBaseAddress', map={'1': ':NO', '2': ''}, prefix='/FIXED')
484     ld('RandomizedBaseAddress',
485         map={'1': ':NO', '2': ''}, prefix='/DYNAMICBASE')
486     ld('DataExecutionPrevention',
487         map={'1': ':NO', '2': ''}, prefix='/NXCOMPAT')
488     ld('OptimizeReferences', map={'1': 'NOREF', '2': 'REF'}, prefix='/OPT:')
489     ld('EnableCOMDATFolding', map={'1': 'NOICF', '2': 'ICF'}, prefix='/OPT:')
490     ld('LinkTimeCodeGeneration', map={'1': '/LTCG'})
491     ld('IgnoreDefaultLibraryNames', prefix='/NODEFAULTLIB:')
492     ld('ResourceOnlyDLL', map={'true': '/NOENTRY'})
493     ld('EntryPointSymbol', prefix='/ENTRY:')
494     ld('Profile', map={'true': '/PROFILE'})
495     ld('LargeAddressAware',
496         map={'1': ':NO', '2': ''}, prefix='/LARGEADDRESSAWARE')
497     # TODO(scottmg): This should sort of be somewhere else (not really a flag).
498     ld('AdditionalDependencies', prefix='')
499
500     # If the base address is not specifically controlled, DYNAMICBASE should
501     # be on by default.
502     base_flags = filter(lambda x: 'DYNAMICBASE' in x or x == '/FIXED',
503                         ldflags)
504     if not base_flags:
505       ldflags.append('/DYNAMICBASE')
506
507     # If the NXCOMPAT flag has not been specified, default to on. Despite the
508     # documentation that says this only defaults to on when the subsystem is
509     # Vista or greater (which applies to the linker), the IDE defaults it on
510     # unless it's explicitly off.
511     if not filter(lambda x: 'NXCOMPAT' in x, ldflags):
512       ldflags.append('/NXCOMPAT')
513
514     have_def_file = filter(lambda x: x.startswith('/DEF:'), ldflags)
515     manifest_flags, intermediate_manifest_file = self._GetLdManifestFlags(
516         config, manifest_base_name, is_executable and not have_def_file)
517     ldflags.extend(manifest_flags)
518     manifest_files = self._GetAdditionalManifestFiles(config, gyp_to_build_path)
519     manifest_files.append(intermediate_manifest_file)
520
521     return ldflags, manifest_files
522
523   def _GetLdManifestFlags(self, config, name, allow_isolation):
524     """Returns the set of flags that need to be added to the link to generate
525     a default manifest, as well as the name of the generated file."""
526     # The manifest is generated by default.
527     output_name = name + '.intermediate.manifest'
528     flags = [
529       '/MANIFEST',
530       '/ManifestFile:' + output_name,
531     ]
532
533     config = self._TargetConfig(config)
534     enable_uac = self._Setting(('VCLinkerTool', 'EnableUAC'), config,
535                                default='true')
536     if enable_uac == 'true':
537       execution_level = self._Setting(('VCLinkerTool', 'UACExecutionLevel'),
538                                       config, default='0')
539       execution_level_map = {
540         '0': 'asInvoker',
541         '1': 'highestAvailable',
542         '2': 'requireAdministrator'
543       }
544
545       ui_access = self._Setting(('VCLinkerTool', 'UACUIAccess'), config,
546                                 default='false')
547       flags.append('''/MANIFESTUAC:"level='%s' uiAccess='%s'"''' %
548           (execution_level_map[execution_level], ui_access))
549     else:
550       flags.append('/MANIFESTUAC:NO')
551
552     if allow_isolation:
553       flags.append('/ALLOWISOLATION')
554     return flags, output_name
555
556   def _GetAdditionalManifestFiles(self, config, gyp_to_build_path):
557     """Gets additional manifest files that are added to the default one
558     generated by the linker."""
559     files = self._Setting(('VCManifestTool', 'AdditionalManifestFiles'), config,
560                           default=[])
561     if isinstance(files, str):
562       files = files.split(';')
563     return [os.path.normpath(
564                 gyp_to_build_path(self.ConvertVSMacros(f, config=config)))
565             for f in files]
566
567   def IsUseLibraryDependencyInputs(self, config):
568     """Returns whether the target should be linked via Use Library Dependency
569     Inputs (using component .objs of a given .lib)."""
570     config = self._TargetConfig(config)
571     uldi = self._Setting(('VCLinkerTool', 'UseLibraryDependencyInputs'), config)
572     return uldi == 'true'
573
574   def IsEmbedManifest(self, config):
575     """Returns whether manifest should be linked into binary."""
576     config = self._TargetConfig(config)
577     embed = self._Setting(('VCManifestTool', 'EmbedManifest'), config)
578     return embed == 'true'
579
580   def IsLinkIncremental(self, config):
581     """Returns whether the target should be linked incrementally."""
582     config = self._TargetConfig(config)
583     link_inc = self._Setting(('VCLinkerTool', 'LinkIncremental'), config)
584     return link_inc != '1'
585
586   def GetRcflags(self, config, gyp_to_ninja_path):
587     """Returns the flags that need to be added to invocations of the resource
588     compiler."""
589     config = self._TargetConfig(config)
590     rcflags = []
591     rc = self._GetWrapper(self, self.msvs_settings[config],
592         'VCResourceCompilerTool', append=rcflags)
593     rc('AdditionalIncludeDirectories', map=gyp_to_ninja_path, prefix='/I')
594     rcflags.append('/I' + gyp_to_ninja_path('.'))
595     rc('PreprocessorDefinitions', prefix='/d')
596     # /l arg must be in hex without leading '0x'
597     rc('Culture', prefix='/l', map=lambda x: hex(int(x))[2:])
598     return rcflags
599
600   def BuildCygwinBashCommandLine(self, args, path_to_base):
601     """Build a command line that runs args via cygwin bash. We assume that all
602     incoming paths are in Windows normpath'd form, so they need to be
603     converted to posix style for the part of the command line that's passed to
604     bash. We also have to do some Visual Studio macro emulation here because
605     various rules use magic VS names for things. Also note that rules that
606     contain ninja variables cannot be fixed here (for example ${source}), so
607     the outer generator needs to make sure that the paths that are written out
608     are in posix style, if the command line will be used here."""
609     cygwin_dir = os.path.normpath(
610         os.path.join(path_to_base, self.msvs_cygwin_dirs[0]))
611     cd = ('cd %s' % path_to_base).replace('\\', '/')
612     args = [a.replace('\\', '/').replace('"', '\\"') for a in args]
613     args = ["'%s'" % a.replace("'", "'\\''") for a in args]
614     bash_cmd = ' '.join(args)
615     cmd = (
616         'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir +
617         'bash -c "%s ; %s"' % (cd, bash_cmd))
618     return cmd
619
620   def IsRuleRunUnderCygwin(self, rule):
621     """Determine if an action should be run under cygwin. If the variable is
622     unset, or set to 1 we use cygwin."""
623     return int(rule.get('msvs_cygwin_shell',
624                         self.spec.get('msvs_cygwin_shell', 1))) != 0
625
626   def _HasExplicitRuleForExtension(self, spec, extension):
627     """Determine if there's an explicit rule for a particular extension."""
628     for rule in spec.get('rules', []):
629       if rule['extension'] == extension:
630         return True
631     return False
632
633   def HasExplicitIdlRules(self, spec):
634     """Determine if there's an explicit rule for idl files. When there isn't we
635     need to generate implicit rules to build MIDL .idl files."""
636     return self._HasExplicitRuleForExtension(spec, 'idl')
637
638   def HasExplicitAsmRules(self, spec):
639     """Determine if there's an explicit rule for asm files. When there isn't we
640     need to generate implicit rules to assemble .asm files."""
641     return self._HasExplicitRuleForExtension(spec, 'asm')
642
643   def GetIdlBuildData(self, source, config):
644     """Determine the implicit outputs for an idl file. Returns output
645     directory, outputs, and variables and flags that are required."""
646     config = self._TargetConfig(config)
647     midl_get = self._GetWrapper(self, self.msvs_settings[config], 'VCMIDLTool')
648     def midl(name, default=None):
649       return self.ConvertVSMacros(midl_get(name, default=default),
650                                   config=config)
651     tlb = midl('TypeLibraryName', default='${root}.tlb')
652     header = midl('HeaderFileName', default='${root}.h')
653     dlldata = midl('DLLDataFileName', default='dlldata.c')
654     iid = midl('InterfaceIdentifierFileName', default='${root}_i.c')
655     proxy = midl('ProxyFileName', default='${root}_p.c')
656     # Note that .tlb is not included in the outputs as it is not always
657     # generated depending on the content of the input idl file.
658     outdir = midl('OutputDirectory', default='')
659     output = [header, dlldata, iid, proxy]
660     variables = [('tlb', tlb),
661                  ('h', header),
662                  ('dlldata', dlldata),
663                  ('iid', iid),
664                  ('proxy', proxy)]
665     # TODO(scottmg): Are there configuration settings to set these flags?
666     target_platform = 'win32' if self.GetArch(config) == 'x86' else 'x64'
667     flags = ['/char', 'signed', '/env', target_platform, '/Oicf']
668     return outdir, output, variables, flags
669
670
671 def _LanguageMatchesForPch(source_ext, pch_source_ext):
672   c_exts = ('.c',)
673   cc_exts = ('.cc', '.cxx', '.cpp')
674   return ((source_ext in c_exts and pch_source_ext in c_exts) or
675           (source_ext in cc_exts and pch_source_ext in cc_exts))
676
677
678 class PrecompiledHeader(object):
679   """Helper to generate dependencies and build rules to handle generation of
680   precompiled headers. Interface matches the GCH handler in xcode_emulation.py.
681   """
682   def __init__(
683       self, settings, config, gyp_to_build_path, gyp_to_unique_output, obj_ext):
684     self.settings = settings
685     self.config = config
686     pch_source = self.settings.msvs_precompiled_source[self.config]
687     self.pch_source = gyp_to_build_path(pch_source)
688     filename, _ = os.path.splitext(pch_source)
689     self.output_obj = gyp_to_unique_output(filename + obj_ext).lower()
690
691   def _PchHeader(self):
692     """Get the header that will appear in an #include line for all source
693     files."""
694     return os.path.split(self.settings.msvs_precompiled_header[self.config])[1]
695
696   def GetObjDependencies(self, sources, objs, arch):
697     """Given a list of sources files and the corresponding object files,
698     returns a list of the pch files that should be depended upon. The
699     additional wrapping in the return value is for interface compatability
700     with make.py on Mac, and xcode_emulation.py."""
701     assert arch is None
702     if not self._PchHeader():
703       return []
704     pch_ext = os.path.splitext(self.pch_source)[1]
705     for source in sources:
706       if _LanguageMatchesForPch(os.path.splitext(source)[1], pch_ext):
707         return [(None, None, self.output_obj)]
708     return []
709
710   def GetPchBuildCommands(self, arch):
711     """Not used on Windows as there are no additional build steps required
712     (instead, existing steps are modified in GetFlagsModifications below)."""
713     return []
714
715   def GetFlagsModifications(self, input, output, implicit, command,
716                             cflags_c, cflags_cc, expand_special):
717     """Get the modified cflags and implicit dependencies that should be used
718     for the pch compilation step."""
719     if input == self.pch_source:
720       pch_output = ['/Yc' + self._PchHeader()]
721       if command == 'cxx':
722         return ([('cflags_cc', map(expand_special, cflags_cc + pch_output))],
723                 self.output_obj, [])
724       elif command == 'cc':
725         return ([('cflags_c', map(expand_special, cflags_c + pch_output))],
726                 self.output_obj, [])
727     return [], output, implicit
728
729
730 vs_version = None
731 def GetVSVersion(generator_flags):
732   global vs_version
733   if not vs_version:
734     vs_version = gyp.MSVSVersion.SelectVisualStudioVersion(
735         generator_flags.get('msvs_version', 'auto'))
736   return vs_version
737
738 def _GetVsvarsSetupArgs(generator_flags, arch):
739   vs = GetVSVersion(generator_flags)
740   return vs.SetupScript()
741
742 def ExpandMacros(string, expansions):
743   """Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv
744   for the canonical way to retrieve a suitable dict."""
745   if '$' in string:
746     for old, new in expansions.iteritems():
747       assert '$(' not in new, new
748       string = string.replace(old, new)
749   return string
750
751 def _ExtractImportantEnvironment(output_of_set):
752   """Extracts environment variables required for the toolchain to run from
753   a textual dump output by the cmd.exe 'set' command."""
754   envvars_to_save = (
755       'goma_.*', # TODO(scottmg): This is ugly, but needed for goma.
756       'include',
757       'lib',
758       'libpath',
759       'path',
760       'pathext',
761       'systemroot',
762       'temp',
763       'tmp',
764       )
765   env = {}
766   for line in output_of_set.splitlines():
767     for envvar in envvars_to_save:
768       if re.match(envvar + '=', line.lower()):
769         var, setting = line.split('=', 1)
770         if envvar == 'path':
771           # Our own rules (for running gyp-win-tool) and other actions in
772           # Chromium rely on python being in the path. Add the path to this
773           # python here so that if it's not in the path when ninja is run
774           # later, python will still be found.
775           setting = os.path.dirname(sys.executable) + os.pathsep + setting
776         env[var.upper()] = setting
777         break
778   for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
779     if required not in env:
780       raise Exception('Environment variable "%s" '
781                       'required to be set to valid path' % required)
782   return env
783
784 def _FormatAsEnvironmentBlock(envvar_dict):
785   """Format as an 'environment block' directly suitable for CreateProcess.
786   Briefly this is a list of key=value\0, terminated by an additional \0. See
787   CreateProcess documentation for more details."""
788   block = ''
789   nul = '\0'
790   for key, value in envvar_dict.iteritems():
791     block += key + '=' + value + nul
792   block += nul
793   return block
794
795 def _ExtractCLPath(output_of_where):
796   """Gets the path to cl.exe based on the output of calling the environment
797   setup batch file, followed by the equivalent of `where`."""
798   # Take the first line, as that's the first found in the PATH.
799   for line in output_of_where.strip().splitlines():
800     if line.startswith('LOC:'):
801       return line[len('LOC:'):].strip()
802
803 def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags, open_out):
804   """It's not sufficient to have the absolute path to the compiler, linker,
805   etc. on Windows, as those tools rely on .dlls being in the PATH. We also
806   need to support both x86 and x64 compilers within the same build (to support
807   msvs_target_platform hackery). Different architectures require a different
808   compiler binary, and different supporting environment variables (INCLUDE,
809   LIB, LIBPATH). So, we extract the environment here, wrap all invocations
810   of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which
811   sets up the environment, and then we do not prefix the compiler with
812   an absolute path, instead preferring something like "cl.exe" in the rule
813   which will then run whichever the environment setup has put in the path.
814   When the following procedure to generate environment files does not
815   meet your requirement (e.g. for custom toolchains), you can pass
816   "-G ninja_use_custom_environment_files" to the gyp to suppress file
817   generation and use custom environment files prepared by yourself."""
818   archs = ('x86', 'x64')
819   if generator_flags.get('ninja_use_custom_environment_files', 0):
820     cl_paths = {}
821     for arch in archs:
822       cl_paths[arch] = 'cl.exe'
823     return cl_paths
824   vs = GetVSVersion(generator_flags)
825   cl_paths = {}
826   for arch in archs:
827     # Extract environment variables for subprocesses.
828     args = vs.SetupScript(arch)
829     args.extend(('&&', 'set'))
830     popen = subprocess.Popen(
831         args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
832     variables, _ = popen.communicate()
833     env = _ExtractImportantEnvironment(variables)
834     env_block = _FormatAsEnvironmentBlock(env)
835     f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb')
836     f.write(env_block)
837     f.close()
838
839     # Find cl.exe location for this architecture.
840     args = vs.SetupScript(arch)
841     args.extend(('&&',
842       'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i'))
843     popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
844     output, _ = popen.communicate()
845     cl_paths[arch] = _ExtractCLPath(output)
846   return cl_paths
847
848 def VerifyMissingSources(sources, build_dir, generator_flags, gyp_to_ninja):
849   """Emulate behavior of msvs_error_on_missing_sources present in the msvs
850   generator: Check that all regular source files, i.e. not created at run time,
851   exist on disk. Missing files cause needless recompilation when building via
852   VS, and we want this check to match for people/bots that build using ninja,
853   so they're not surprised when the VS build fails."""
854   if int(generator_flags.get('msvs_error_on_missing_sources', 0)):
855     no_specials = filter(lambda x: '$' not in x, sources)
856     relative = [os.path.join(build_dir, gyp_to_ninja(s)) for s in no_specials]
857     missing = filter(lambda x: not os.path.exists(x), relative)
858     if missing:
859       # They'll look like out\Release\..\..\stuff\things.cc, so normalize the
860       # path for a slightly less crazy looking output.
861       cleaned_up = [os.path.normpath(x) for x in missing]
862       raise Exception('Missing input files:\n%s' % '\n'.join(cleaned_up))
863
864 # Sets some values in default_variables, which are required for many
865 # generators, run on Windows.
866 def CalculateCommonVariables(default_variables, params):
867   generator_flags = params.get('generator_flags', {})
868
869   # Set a variable so conditions can be based on msvs_version.
870   msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags)
871   default_variables['MSVS_VERSION'] = msvs_version.ShortName()
872
873   # To determine processor word size on Windows, in addition to checking
874   # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
875   # process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which
876   # contains the actual word size of the system when running thru WOW64).
877   if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or
878       '64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')):
879     default_variables['MSVS_OS_BITS'] = 64
880   else:
881     default_variables['MSVS_OS_BITS'] = 32