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