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.
6 This module helps emulate Visual Studio 2008 behavior on top of other
7 build systems, primarily ninja.
15 import gyp.MSVSVersion
17 windows_quoter_regex = re.compile(r'(\\*)"')
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
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.
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)
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('%', '%%')
41 # These commands are used in rsp files, so no escaping for the shell (via ^)
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 + '"'
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)
61 program = os.path.normpath(args[0])
62 return program + ' ' + ' '.join(QuoteForRspFile(arg) for arg in args[1:])
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."""
72 return _GenericRetrieve(root.get(path[0]), default, path[1:])
75 def _AddPrefix(element, prefix):
76 """Add |prefix| to |element| or each subelement if element is iterable."""
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]
83 return prefix + element
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:
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])
95 element = map(element)
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
103 if append is not None and element is not None:
104 if isinstance(element, list) or isinstance(element, tuple):
105 append.extend(element)
107 append.append(element)
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
120 dxsdk_dir = os.environ.get('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] + "\\"
130 _FindDirectXInstallation.dxsdk_dir = dxsdk_dir
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."""
139 def __init__(self, spec, generator_flags):
141 self.vs_version = GetVSVersion(generator_flags)
142 self.dxsdk_dir = _FindDirectXInstallation()
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')
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),
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())
164 self.msvs_cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])
166 def GetVSMacroEnv(self, base_to_build=None, config=None):
167 """Get a dict of variables mapping internal VS macro names to their gyp
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 ''
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)\\': '',
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(),
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 ''
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)
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]
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)
218 class _GetWrapper(object):
219 def __init__(self, parent, field, base_path, append=None):
222 self.base_path = [base_path]
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)
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')
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'):
249 if arch == 'x86' and config.endswith('_x64'):
250 config = config.rsplit('_', 1)[0]
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)
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)
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]
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)
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=[]))
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)
295 pdbname = expand_special(self.ConvertVSMacros(pdbname))
298 def GetMapFileName(self, config, expand_special):
299 """Gets the explicitly overriden map file name for a target or returns None
301 config = self._TargetConfig(config)
302 map_file = self._Setting(('VCLinkerTool', 'MapFileName'), config)
304 map_file = expand_special(self.ConvertVSMacros(map_file, config=config))
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)
316 output_file = expand_special(self.ConvertVSMacros(
317 output_file, config=config))
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)
326 output_file = expand_special(self.ConvertVSMacros(
327 output_file, config=config))
330 def GetCflags(self, config):
331 """Returns the flags that need to be added to .c and .cc compilations."""
332 config = self._TargetConfig(config)
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)
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')
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.
369 # ninja handles parallelism by itself, don't have the compiler do it too.
370 cflags = filter(lambda x: not x.startswith('/MP'), cflags)
373 def GetPrecompiledHeader(self, config, gyp_to_build_path):
374 """Returns an object that handles the generation of precompiled header
376 config = self._TargetConfig(config)
377 return _PchHelper(self, config, gyp_to_build_path)
379 def _GetPchFlags(self, config, extension):
380 """Get the flags to be added to the cflags for precompiled header support.
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']
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')
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')
403 def _GetAdditionalLibraryDirectories(self, root, config, gyp_to_build_path):
404 """Get and normalize the list of paths in AdditionalLibraryDirectories
406 config = self._TargetConfig(config)
407 libpaths = self._Setting((root, 'AdditionalLibraryDirectories'),
409 libpaths = [os.path.normpath(
410 gyp_to_build_path(self.ConvertVSMacros(p, config=config)))
412 return ['/LIBPATH:"' + p + '"' for p in libpaths]
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)
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')
427 def GetDefFile(self, gyp_to_build_path):
428 """Returns the .def file from sources, if any. Otherwise returns None."""
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")
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)
443 ldflags.append('/DEF:"%s"' % def_file)
445 def GetPGDName(self, config, expand_special):
446 """Gets the explicitly overridden pgd name for a target or returns None
447 if it's not overridden."""
448 config = self._TargetConfig(config)
449 output_file = self._Setting(
450 ('VCLinkerTool', 'ProfileGuidedDatabase'), config)
452 output_file = expand_special(self.ConvertVSMacros(
453 output_file, config=config))
456 def GetLdflags(self, config, gyp_to_build_path, expand_special,
457 manifest_base_name, is_executable, build_dir):
458 """Returns the flags that need to be added to link commands, and the
460 config = self._TargetConfig(config)
462 ld = self._GetWrapper(self, self.msvs_settings[config],
463 'VCLinkerTool', append=ldflags)
464 self._GetDefFileAsLdflags(ldflags, gyp_to_build_path)
465 ld('GenerateDebugInformation', map={'true': '/DEBUG'})
466 ld('TargetMachine', map={'1': 'X86', '17': 'X64'}, prefix='/MACHINE:')
467 ldflags.extend(self._GetAdditionalLibraryDirectories(
468 'VCLinkerTool', config, gyp_to_build_path))
469 ld('DelayLoadDLLs', prefix='/DELAYLOAD:')
470 ld('TreatLinkerWarningAsErrors', prefix='/WX',
471 map={'true': '', 'false': ':NO'})
472 out = self.GetOutputName(config, expand_special)
474 ldflags.append('/OUT:' + out)
475 pdb = self.GetPDBName(config, expand_special)
477 ldflags.append('/PDB:' + pdb)
478 pgd = self.GetPGDName(config, expand_special)
480 ldflags.append('/PGD:' + pgd)
481 map_file = self.GetMapFileName(config, expand_special)
482 ld('GenerateMapFile', map={'true': '/MAP:' + map_file if map_file
484 ld('MapExports', map={'true': '/MAPINFO:EXPORTS'})
485 ld('AdditionalOptions', prefix='')
487 minimum_required_version = self._Setting(
488 ('VCLinkerTool', 'MinimumRequiredVersion'), config, default='')
489 if minimum_required_version:
490 minimum_required_version = ',' + minimum_required_version
492 map={'1': 'CONSOLE%s' % minimum_required_version,
493 '2': 'WINDOWS%s' % minimum_required_version},
494 prefix='/SUBSYSTEM:')
496 ld('TerminalServerAware', map={'1': ':NO', '2': ''}, prefix='/TSAWARE')
497 ld('LinkIncremental', map={'1': ':NO', '2': ''}, prefix='/INCREMENTAL')
498 ld('BaseAddress', prefix='/BASE:')
499 ld('FixedBaseAddress', map={'1': ':NO', '2': ''}, prefix='/FIXED')
500 ld('RandomizedBaseAddress',
501 map={'1': ':NO', '2': ''}, prefix='/DYNAMICBASE')
502 ld('DataExecutionPrevention',
503 map={'1': ':NO', '2': ''}, prefix='/NXCOMPAT')
504 ld('OptimizeReferences', map={'1': 'NOREF', '2': 'REF'}, prefix='/OPT:')
505 ld('ForceSymbolReferences', prefix='/INCLUDE:')
506 ld('EnableCOMDATFolding', map={'1': 'NOICF', '2': 'ICF'}, prefix='/OPT:')
507 ld('LinkTimeCodeGeneration',
508 map={'1': '', '2': ':PGINSTRUMENT', '3': ':PGOPTIMIZE',
511 ld('IgnoreDefaultLibraryNames', prefix='/NODEFAULTLIB:')
512 ld('ResourceOnlyDLL', map={'true': '/NOENTRY'})
513 ld('EntryPointSymbol', prefix='/ENTRY:')
514 ld('Profile', map={'true': '/PROFILE'})
515 ld('LargeAddressAware',
516 map={'1': ':NO', '2': ''}, prefix='/LARGEADDRESSAWARE')
517 # TODO(scottmg): This should sort of be somewhere else (not really a flag).
518 ld('AdditionalDependencies', prefix='')
520 # If the base address is not specifically controlled, DYNAMICBASE should
522 base_flags = filter(lambda x: 'DYNAMICBASE' in x or x == '/FIXED',
525 ldflags.append('/DYNAMICBASE')
527 # If the NXCOMPAT flag has not been specified, default to on. Despite the
528 # documentation that says this only defaults to on when the subsystem is
529 # Vista or greater (which applies to the linker), the IDE defaults it on
530 # unless it's explicitly off.
531 if not filter(lambda x: 'NXCOMPAT' in x, ldflags):
532 ldflags.append('/NXCOMPAT')
534 have_def_file = filter(lambda x: x.startswith('/DEF:'), ldflags)
535 manifest_flags, intermediate_manifest, manifest_files = \
536 self._GetLdManifestFlags(config, manifest_base_name, gyp_to_build_path,
537 is_executable and not have_def_file, build_dir)
538 ldflags.extend(manifest_flags)
539 return ldflags, intermediate_manifest, manifest_files
541 def _GetLdManifestFlags(self, config, name, gyp_to_build_path,
542 allow_isolation, build_dir):
543 """Returns a 3-tuple:
544 - the set of flags that need to be added to the link to generate
546 - the intermediate manifest that the linker will generate that should be
547 used to assert it doesn't add anything to the merged one.
548 - the list of all the manifest files to be merged by the manifest tool and
549 included into the link."""
550 generate_manifest = self._Setting(('VCLinkerTool', 'GenerateManifest'),
553 if generate_manifest != 'true':
554 # This means not only that the linker should not generate the intermediate
555 # manifest but also that the manifest tool should do nothing even when
556 # additional manifests are specified.
557 return ['/MANIFEST:NO'], [], []
559 output_name = name + '.intermediate.manifest'
562 '/ManifestFile:' + output_name,
565 # Instead of using the MANIFESTUAC flags, we generate a .manifest to
566 # include into the list of manifests. This allows us to avoid the need to
567 # do two passes during linking. The /MANIFEST flag and /ManifestFile are
568 # still used, and the intermediate manifest is used to assert that the
569 # final manifest we get from merging all the additional manifest files
570 # (plus the one we generate here) isn't modified by merging the
571 # intermediate into it.
573 # Always NO, because we generate a manifest file that has what we want.
574 flags.append('/MANIFESTUAC:NO')
576 config = self._TargetConfig(config)
577 enable_uac = self._Setting(('VCLinkerTool', 'EnableUAC'), config,
580 generated_manifest_outer = \
581 "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>" \
582 "<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>%s" \
584 if enable_uac == 'true':
585 execution_level = self._Setting(('VCLinkerTool', 'UACExecutionLevel'),
587 execution_level_map = {
589 '1': 'highestAvailable',
590 '2': 'requireAdministrator'
593 ui_access = self._Setting(('VCLinkerTool', 'UACUIAccess'), config,
597 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
599 <requestedPrivileges>
600 <requestedExecutionLevel level='%s' uiAccess='%s' />
601 </requestedPrivileges>
603 </trustInfo>''' % (execution_level_map[execution_level], ui_access)
607 generated_manifest_contents = generated_manifest_outer % inner
608 generated_name = name + '.generated.manifest'
609 # Need to join with the build_dir here as we're writing it during
610 # generation time, but we return the un-joined version because the build
611 # will occur in that directory. We only write the file if the contents
612 # have changed so that simply regenerating the project files doesn't
614 build_dir_generated_name = os.path.join(build_dir, generated_name)
615 gyp.common.EnsureDirExists(build_dir_generated_name)
616 f = gyp.common.WriteOnDiff(build_dir_generated_name)
617 f.write(generated_manifest_contents)
619 manifest_files = [generated_name]
622 flags.append('/ALLOWISOLATION')
624 manifest_files += self._GetAdditionalManifestFiles(config,
626 return flags, output_name, manifest_files
628 def _GetAdditionalManifestFiles(self, config, gyp_to_build_path):
629 """Gets additional manifest files that are added to the default one
630 generated by the linker."""
631 files = self._Setting(('VCManifestTool', 'AdditionalManifestFiles'), config,
633 if isinstance(files, str):
634 files = files.split(';')
635 return [os.path.normpath(
636 gyp_to_build_path(self.ConvertVSMacros(f, config=config)))
639 def IsUseLibraryDependencyInputs(self, config):
640 """Returns whether the target should be linked via Use Library Dependency
641 Inputs (using component .objs of a given .lib)."""
642 config = self._TargetConfig(config)
643 uldi = self._Setting(('VCLinkerTool', 'UseLibraryDependencyInputs'), config)
644 return uldi == 'true'
646 def IsEmbedManifest(self, config):
647 """Returns whether manifest should be linked into binary."""
648 config = self._TargetConfig(config)
649 embed = self._Setting(('VCManifestTool', 'EmbedManifest'), config,
651 return embed == 'true'
653 def IsLinkIncremental(self, config):
654 """Returns whether the target should be linked incrementally."""
655 config = self._TargetConfig(config)
656 link_inc = self._Setting(('VCLinkerTool', 'LinkIncremental'), config)
657 return link_inc != '1'
659 def GetRcflags(self, config, gyp_to_ninja_path):
660 """Returns the flags that need to be added to invocations of the resource
662 config = self._TargetConfig(config)
664 rc = self._GetWrapper(self, self.msvs_settings[config],
665 'VCResourceCompilerTool', append=rcflags)
666 rc('AdditionalIncludeDirectories', map=gyp_to_ninja_path, prefix='/I')
667 rcflags.append('/I' + gyp_to_ninja_path('.'))
668 rc('PreprocessorDefinitions', prefix='/d')
669 # /l arg must be in hex without leading '0x'
670 rc('Culture', prefix='/l', map=lambda x: hex(int(x))[2:])
673 def BuildCygwinBashCommandLine(self, args, path_to_base):
674 """Build a command line that runs args via cygwin bash. We assume that all
675 incoming paths are in Windows normpath'd form, so they need to be
676 converted to posix style for the part of the command line that's passed to
677 bash. We also have to do some Visual Studio macro emulation here because
678 various rules use magic VS names for things. Also note that rules that
679 contain ninja variables cannot be fixed here (for example ${source}), so
680 the outer generator needs to make sure that the paths that are written out
681 are in posix style, if the command line will be used here."""
682 cygwin_dir = os.path.normpath(
683 os.path.join(path_to_base, self.msvs_cygwin_dirs[0]))
684 cd = ('cd %s' % path_to_base).replace('\\', '/')
685 args = [a.replace('\\', '/').replace('"', '\\"') for a in args]
686 args = ["'%s'" % a.replace("'", "'\\''") for a in args]
687 bash_cmd = ' '.join(args)
689 'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir +
690 'bash -c "%s ; %s"' % (cd, bash_cmd))
693 def IsRuleRunUnderCygwin(self, rule):
694 """Determine if an action should be run under cygwin. If the variable is
695 unset, or set to 1 we use cygwin."""
696 return int(rule.get('msvs_cygwin_shell',
697 self.spec.get('msvs_cygwin_shell', 1))) != 0
699 def _HasExplicitRuleForExtension(self, spec, extension):
700 """Determine if there's an explicit rule for a particular extension."""
701 for rule in spec.get('rules', []):
702 if rule['extension'] == extension:
706 def HasExplicitIdlRules(self, spec):
707 """Determine if there's an explicit rule for idl files. When there isn't we
708 need to generate implicit rules to build MIDL .idl files."""
709 return self._HasExplicitRuleForExtension(spec, 'idl')
711 def HasExplicitAsmRules(self, spec):
712 """Determine if there's an explicit rule for asm files. When there isn't we
713 need to generate implicit rules to assemble .asm files."""
714 return self._HasExplicitRuleForExtension(spec, 'asm')
716 def GetIdlBuildData(self, source, config):
717 """Determine the implicit outputs for an idl file. Returns output
718 directory, outputs, and variables and flags that are required."""
719 config = self._TargetConfig(config)
720 midl_get = self._GetWrapper(self, self.msvs_settings[config], 'VCMIDLTool')
721 def midl(name, default=None):
722 return self.ConvertVSMacros(midl_get(name, default=default),
724 tlb = midl('TypeLibraryName', default='${root}.tlb')
725 header = midl('HeaderFileName', default='${root}.h')
726 dlldata = midl('DLLDataFileName', default='dlldata.c')
727 iid = midl('InterfaceIdentifierFileName', default='${root}_i.c')
728 proxy = midl('ProxyFileName', default='${root}_p.c')
729 # Note that .tlb is not included in the outputs as it is not always
730 # generated depending on the content of the input idl file.
731 outdir = midl('OutputDirectory', default='')
732 output = [header, dlldata, iid, proxy]
733 variables = [('tlb', tlb),
735 ('dlldata', dlldata),
738 # TODO(scottmg): Are there configuration settings to set these flags?
739 target_platform = 'win32' if self.GetArch(config) == 'x86' else 'x64'
740 flags = ['/char', 'signed', '/env', target_platform, '/Oicf']
741 return outdir, output, variables, flags
744 def _LanguageMatchesForPch(source_ext, pch_source_ext):
746 cc_exts = ('.cc', '.cxx', '.cpp')
747 return ((source_ext in c_exts and pch_source_ext in c_exts) or
748 (source_ext in cc_exts and pch_source_ext in cc_exts))
751 class PrecompiledHeader(object):
752 """Helper to generate dependencies and build rules to handle generation of
753 precompiled headers. Interface matches the GCH handler in xcode_emulation.py.
756 self, settings, config, gyp_to_build_path, gyp_to_unique_output, obj_ext):
757 self.settings = settings
759 pch_source = self.settings.msvs_precompiled_source[self.config]
760 self.pch_source = gyp_to_build_path(pch_source)
761 filename, _ = os.path.splitext(pch_source)
762 self.output_obj = gyp_to_unique_output(filename + obj_ext).lower()
764 def _PchHeader(self):
765 """Get the header that will appear in an #include line for all source
767 return os.path.split(self.settings.msvs_precompiled_header[self.config])[1]
769 def GetObjDependencies(self, sources, objs, arch):
770 """Given a list of sources files and the corresponding object files,
771 returns a list of the pch files that should be depended upon. The
772 additional wrapping in the return value is for interface compatability
773 with make.py on Mac, and xcode_emulation.py."""
775 if not self._PchHeader():
777 pch_ext = os.path.splitext(self.pch_source)[1]
778 for source in sources:
779 if _LanguageMatchesForPch(os.path.splitext(source)[1], pch_ext):
780 return [(None, None, self.output_obj)]
783 def GetPchBuildCommands(self, arch):
784 """Not used on Windows as there are no additional build steps required
785 (instead, existing steps are modified in GetFlagsModifications below)."""
788 def GetFlagsModifications(self, input, output, implicit, command,
789 cflags_c, cflags_cc, expand_special):
790 """Get the modified cflags and implicit dependencies that should be used
791 for the pch compilation step."""
792 if input == self.pch_source:
793 pch_output = ['/Yc' + self._PchHeader()]
795 return ([('cflags_cc', map(expand_special, cflags_cc + pch_output))],
797 elif command == 'cc':
798 return ([('cflags_c', map(expand_special, cflags_c + pch_output))],
800 return [], output, implicit
804 def GetVSVersion(generator_flags):
807 vs_version = gyp.MSVSVersion.SelectVisualStudioVersion(
808 generator_flags.get('msvs_version', 'auto'))
811 def _GetVsvarsSetupArgs(generator_flags, arch):
812 vs = GetVSVersion(generator_flags)
813 return vs.SetupScript()
815 def ExpandMacros(string, expansions):
816 """Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv
817 for the canonical way to retrieve a suitable dict."""
819 for old, new in expansions.iteritems():
820 assert '$(' not in new, new
821 string = string.replace(old, new)
824 def _ExtractImportantEnvironment(output_of_set):
825 """Extracts environment variables required for the toolchain to run from
826 a textual dump output by the cmd.exe 'set' command."""
828 'goma_.*', # TODO(scottmg): This is ugly, but needed for goma.
839 for line in output_of_set.splitlines():
840 for envvar in envvars_to_save:
841 if re.match(envvar + '=', line.lower()):
842 var, setting = line.split('=', 1)
844 # Our own rules (for running gyp-win-tool) and other actions in
845 # Chromium rely on python being in the path. Add the path to this
846 # python here so that if it's not in the path when ninja is run
847 # later, python will still be found.
848 setting = os.path.dirname(sys.executable) + os.pathsep + setting
849 env[var.upper()] = setting
851 for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
852 if required not in env:
853 raise Exception('Environment variable "%s" '
854 'required to be set to valid path' % required)
857 def _FormatAsEnvironmentBlock(envvar_dict):
858 """Format as an 'environment block' directly suitable for CreateProcess.
859 Briefly this is a list of key=value\0, terminated by an additional \0. See
860 CreateProcess documentation for more details."""
863 for key, value in envvar_dict.iteritems():
864 block += key + '=' + value + nul
868 def _ExtractCLPath(output_of_where):
869 """Gets the path to cl.exe based on the output of calling the environment
870 setup batch file, followed by the equivalent of `where`."""
871 # Take the first line, as that's the first found in the PATH.
872 for line in output_of_where.strip().splitlines():
873 if line.startswith('LOC:'):
874 return line[len('LOC:'):].strip()
876 def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags, open_out):
877 """It's not sufficient to have the absolute path to the compiler, linker,
878 etc. on Windows, as those tools rely on .dlls being in the PATH. We also
879 need to support both x86 and x64 compilers within the same build (to support
880 msvs_target_platform hackery). Different architectures require a different
881 compiler binary, and different supporting environment variables (INCLUDE,
882 LIB, LIBPATH). So, we extract the environment here, wrap all invocations
883 of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which
884 sets up the environment, and then we do not prefix the compiler with
885 an absolute path, instead preferring something like "cl.exe" in the rule
886 which will then run whichever the environment setup has put in the path.
887 When the following procedure to generate environment files does not
888 meet your requirement (e.g. for custom toolchains), you can pass
889 "-G ninja_use_custom_environment_files" to the gyp to suppress file
890 generation and use custom environment files prepared by yourself."""
891 archs = ('x86', 'x64')
892 if generator_flags.get('ninja_use_custom_environment_files', 0):
895 cl_paths[arch] = 'cl.exe'
897 vs = GetVSVersion(generator_flags)
900 # Extract environment variables for subprocesses.
901 args = vs.SetupScript(arch)
902 args.extend(('&&', 'set'))
903 popen = subprocess.Popen(
904 args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
905 variables, _ = popen.communicate()
906 env = _ExtractImportantEnvironment(variables)
907 env_block = _FormatAsEnvironmentBlock(env)
908 f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb')
912 # Find cl.exe location for this architecture.
913 args = vs.SetupScript(arch)
915 'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i'))
916 popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
917 output, _ = popen.communicate()
918 cl_paths[arch] = _ExtractCLPath(output)
921 def VerifyMissingSources(sources, build_dir, generator_flags, gyp_to_ninja):
922 """Emulate behavior of msvs_error_on_missing_sources present in the msvs
923 generator: Check that all regular source files, i.e. not created at run time,
924 exist on disk. Missing files cause needless recompilation when building via
925 VS, and we want this check to match for people/bots that build using ninja,
926 so they're not surprised when the VS build fails."""
927 if int(generator_flags.get('msvs_error_on_missing_sources', 0)):
928 no_specials = filter(lambda x: '$' not in x, sources)
929 relative = [os.path.join(build_dir, gyp_to_ninja(s)) for s in no_specials]
930 missing = filter(lambda x: not os.path.exists(x), relative)
932 # They'll look like out\Release\..\..\stuff\things.cc, so normalize the
933 # path for a slightly less crazy looking output.
934 cleaned_up = [os.path.normpath(x) for x in missing]
935 raise Exception('Missing input files:\n%s' % '\n'.join(cleaned_up))
937 # Sets some values in default_variables, which are required for many
938 # generators, run on Windows.
939 def CalculateCommonVariables(default_variables, params):
940 generator_flags = params.get('generator_flags', {})
942 # Set a variable so conditions can be based on msvs_version.
943 msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags)
944 default_variables['MSVS_VERSION'] = msvs_version.ShortName()
946 # To determine processor word size on Windows, in addition to checking
947 # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
948 # process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which
949 # contains the actual word size of the system when running thru WOW64).
950 if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or
951 '64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')):
952 default_variables['MSVS_OS_BITS'] = 64
954 default_variables['MSVS_OS_BITS'] = 32