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):
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('EnableCOMDATFolding', map={'1': 'NOICF', '2': 'ICF'}, prefix='/OPT:')
506 ld('LinkTimeCodeGeneration',
507 map={'1': '', '2': ':PGINSTRUMENT', '3': ':PGOPTIMIZE',
510 ld('IgnoreDefaultLibraryNames', prefix='/NODEFAULTLIB:')
511 ld('ResourceOnlyDLL', map={'true': '/NOENTRY'})
512 ld('EntryPointSymbol', prefix='/ENTRY:')
513 ld('Profile', map={'true': '/PROFILE'})
514 ld('LargeAddressAware',
515 map={'1': ':NO', '2': ''}, prefix='/LARGEADDRESSAWARE')
516 # TODO(scottmg): This should sort of be somewhere else (not really a flag).
517 ld('AdditionalDependencies', prefix='')
519 # If the base address is not specifically controlled, DYNAMICBASE should
521 base_flags = filter(lambda x: 'DYNAMICBASE' in x or x == '/FIXED',
524 ldflags.append('/DYNAMICBASE')
526 # If the NXCOMPAT flag has not been specified, default to on. Despite the
527 # documentation that says this only defaults to on when the subsystem is
528 # Vista or greater (which applies to the linker), the IDE defaults it on
529 # unless it's explicitly off.
530 if not filter(lambda x: 'NXCOMPAT' in x, ldflags):
531 ldflags.append('/NXCOMPAT')
533 have_def_file = filter(lambda x: x.startswith('/DEF:'), ldflags)
534 manifest_flags, manifest_files = self._GetLdManifestFlags(
535 config, manifest_base_name, gyp_to_build_path,
536 is_executable and not have_def_file)
537 ldflags.extend(manifest_flags)
538 return ldflags, manifest_files
540 def _GetLdManifestFlags(self, config, name, gyp_to_build_path,
542 """Returns the set of flags that need to be added to the link to generate
543 a default manifest, as well as the list of all the manifest files to be
544 merged by the manifest tool."""
545 generate_manifest = self._Setting(('VCLinkerTool', 'GenerateManifest'),
548 if generate_manifest != 'true':
549 # This means not only that the linker should not generate the intermediate
550 # manifest but also that the manifest tool should do nothing even when
551 # additional manifests are specified.
552 return ['/MANIFEST:NO'], []
554 output_name = name + '.intermediate.manifest'
557 '/ManifestFile:' + output_name,
560 config = self._TargetConfig(config)
561 enable_uac = self._Setting(('VCLinkerTool', 'EnableUAC'), config,
563 if enable_uac == 'true':
564 execution_level = self._Setting(('VCLinkerTool', 'UACExecutionLevel'),
566 execution_level_map = {
568 '1': 'highestAvailable',
569 '2': 'requireAdministrator'
572 ui_access = self._Setting(('VCLinkerTool', 'UACUIAccess'), config,
574 flags.append('''/MANIFESTUAC:"level='%s' uiAccess='%s'"''' %
575 (execution_level_map[execution_level], ui_access))
577 flags.append('/MANIFESTUAC:NO')
580 flags.append('/ALLOWISOLATION')
582 manifest_files = [output_name]
583 manifest_files += self._GetAdditionalManifestFiles(config,
585 return flags, manifest_files
587 def _GetAdditionalManifestFiles(self, config, gyp_to_build_path):
588 """Gets additional manifest files that are added to the default one
589 generated by the linker."""
590 files = self._Setting(('VCManifestTool', 'AdditionalManifestFiles'), config,
592 if isinstance(files, str):
593 files = files.split(';')
594 return [os.path.normpath(
595 gyp_to_build_path(self.ConvertVSMacros(f, config=config)))
598 def IsUseLibraryDependencyInputs(self, config):
599 """Returns whether the target should be linked via Use Library Dependency
600 Inputs (using component .objs of a given .lib)."""
601 config = self._TargetConfig(config)
602 uldi = self._Setting(('VCLinkerTool', 'UseLibraryDependencyInputs'), config)
603 return uldi == 'true'
605 def IsEmbedManifest(self, config):
606 """Returns whether manifest should be linked into binary."""
607 config = self._TargetConfig(config)
608 embed = self._Setting(('VCManifestTool', 'EmbedManifest'), config)
609 return embed == 'true'
611 def IsLinkIncremental(self, config):
612 """Returns whether the target should be linked incrementally."""
613 config = self._TargetConfig(config)
614 link_inc = self._Setting(('VCLinkerTool', 'LinkIncremental'), config)
615 return link_inc != '1'
617 def GetRcflags(self, config, gyp_to_ninja_path):
618 """Returns the flags that need to be added to invocations of the resource
620 config = self._TargetConfig(config)
622 rc = self._GetWrapper(self, self.msvs_settings[config],
623 'VCResourceCompilerTool', append=rcflags)
624 rc('AdditionalIncludeDirectories', map=gyp_to_ninja_path, prefix='/I')
625 rcflags.append('/I' + gyp_to_ninja_path('.'))
626 rc('PreprocessorDefinitions', prefix='/d')
627 # /l arg must be in hex without leading '0x'
628 rc('Culture', prefix='/l', map=lambda x: hex(int(x))[2:])
631 def BuildCygwinBashCommandLine(self, args, path_to_base):
632 """Build a command line that runs args via cygwin bash. We assume that all
633 incoming paths are in Windows normpath'd form, so they need to be
634 converted to posix style for the part of the command line that's passed to
635 bash. We also have to do some Visual Studio macro emulation here because
636 various rules use magic VS names for things. Also note that rules that
637 contain ninja variables cannot be fixed here (for example ${source}), so
638 the outer generator needs to make sure that the paths that are written out
639 are in posix style, if the command line will be used here."""
640 cygwin_dir = os.path.normpath(
641 os.path.join(path_to_base, self.msvs_cygwin_dirs[0]))
642 cd = ('cd %s' % path_to_base).replace('\\', '/')
643 args = [a.replace('\\', '/').replace('"', '\\"') for a in args]
644 args = ["'%s'" % a.replace("'", "'\\''") for a in args]
645 bash_cmd = ' '.join(args)
647 'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir +
648 'bash -c "%s ; %s"' % (cd, bash_cmd))
651 def IsRuleRunUnderCygwin(self, rule):
652 """Determine if an action should be run under cygwin. If the variable is
653 unset, or set to 1 we use cygwin."""
654 return int(rule.get('msvs_cygwin_shell',
655 self.spec.get('msvs_cygwin_shell', 1))) != 0
657 def _HasExplicitRuleForExtension(self, spec, extension):
658 """Determine if there's an explicit rule for a particular extension."""
659 for rule in spec.get('rules', []):
660 if rule['extension'] == extension:
664 def HasExplicitIdlRules(self, spec):
665 """Determine if there's an explicit rule for idl files. When there isn't we
666 need to generate implicit rules to build MIDL .idl files."""
667 return self._HasExplicitRuleForExtension(spec, 'idl')
669 def HasExplicitAsmRules(self, spec):
670 """Determine if there's an explicit rule for asm files. When there isn't we
671 need to generate implicit rules to assemble .asm files."""
672 return self._HasExplicitRuleForExtension(spec, 'asm')
674 def GetIdlBuildData(self, source, config):
675 """Determine the implicit outputs for an idl file. Returns output
676 directory, outputs, and variables and flags that are required."""
677 config = self._TargetConfig(config)
678 midl_get = self._GetWrapper(self, self.msvs_settings[config], 'VCMIDLTool')
679 def midl(name, default=None):
680 return self.ConvertVSMacros(midl_get(name, default=default),
682 tlb = midl('TypeLibraryName', default='${root}.tlb')
683 header = midl('HeaderFileName', default='${root}.h')
684 dlldata = midl('DLLDataFileName', default='dlldata.c')
685 iid = midl('InterfaceIdentifierFileName', default='${root}_i.c')
686 proxy = midl('ProxyFileName', default='${root}_p.c')
687 # Note that .tlb is not included in the outputs as it is not always
688 # generated depending on the content of the input idl file.
689 outdir = midl('OutputDirectory', default='')
690 output = [header, dlldata, iid, proxy]
691 variables = [('tlb', tlb),
693 ('dlldata', dlldata),
696 # TODO(scottmg): Are there configuration settings to set these flags?
697 target_platform = 'win32' if self.GetArch(config) == 'x86' else 'x64'
698 flags = ['/char', 'signed', '/env', target_platform, '/Oicf']
699 return outdir, output, variables, flags
702 def _LanguageMatchesForPch(source_ext, pch_source_ext):
704 cc_exts = ('.cc', '.cxx', '.cpp')
705 return ((source_ext in c_exts and pch_source_ext in c_exts) or
706 (source_ext in cc_exts and pch_source_ext in cc_exts))
709 class PrecompiledHeader(object):
710 """Helper to generate dependencies and build rules to handle generation of
711 precompiled headers. Interface matches the GCH handler in xcode_emulation.py.
714 self, settings, config, gyp_to_build_path, gyp_to_unique_output, obj_ext):
715 self.settings = settings
717 pch_source = self.settings.msvs_precompiled_source[self.config]
718 self.pch_source = gyp_to_build_path(pch_source)
719 filename, _ = os.path.splitext(pch_source)
720 self.output_obj = gyp_to_unique_output(filename + obj_ext).lower()
722 def _PchHeader(self):
723 """Get the header that will appear in an #include line for all source
725 return os.path.split(self.settings.msvs_precompiled_header[self.config])[1]
727 def GetObjDependencies(self, sources, objs, arch):
728 """Given a list of sources files and the corresponding object files,
729 returns a list of the pch files that should be depended upon. The
730 additional wrapping in the return value is for interface compatability
731 with make.py on Mac, and xcode_emulation.py."""
733 if not self._PchHeader():
735 pch_ext = os.path.splitext(self.pch_source)[1]
736 for source in sources:
737 if _LanguageMatchesForPch(os.path.splitext(source)[1], pch_ext):
738 return [(None, None, self.output_obj)]
741 def GetPchBuildCommands(self, arch):
742 """Not used on Windows as there are no additional build steps required
743 (instead, existing steps are modified in GetFlagsModifications below)."""
746 def GetFlagsModifications(self, input, output, implicit, command,
747 cflags_c, cflags_cc, expand_special):
748 """Get the modified cflags and implicit dependencies that should be used
749 for the pch compilation step."""
750 if input == self.pch_source:
751 pch_output = ['/Yc' + self._PchHeader()]
753 return ([('cflags_cc', map(expand_special, cflags_cc + pch_output))],
755 elif command == 'cc':
756 return ([('cflags_c', map(expand_special, cflags_c + pch_output))],
758 return [], output, implicit
762 def GetVSVersion(generator_flags):
765 vs_version = gyp.MSVSVersion.SelectVisualStudioVersion(
766 generator_flags.get('msvs_version', 'auto'))
769 def _GetVsvarsSetupArgs(generator_flags, arch):
770 vs = GetVSVersion(generator_flags)
771 return vs.SetupScript()
773 def ExpandMacros(string, expansions):
774 """Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv
775 for the canonical way to retrieve a suitable dict."""
777 for old, new in expansions.iteritems():
778 assert '$(' not in new, new
779 string = string.replace(old, new)
782 def _ExtractImportantEnvironment(output_of_set):
783 """Extracts environment variables required for the toolchain to run from
784 a textual dump output by the cmd.exe 'set' command."""
786 'goma_.*', # TODO(scottmg): This is ugly, but needed for goma.
797 for line in output_of_set.splitlines():
798 for envvar in envvars_to_save:
799 if re.match(envvar + '=', line.lower()):
800 var, setting = line.split('=', 1)
802 # Our own rules (for running gyp-win-tool) and other actions in
803 # Chromium rely on python being in the path. Add the path to this
804 # python here so that if it's not in the path when ninja is run
805 # later, python will still be found.
806 setting = os.path.dirname(sys.executable) + os.pathsep + setting
807 env[var.upper()] = setting
809 for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
810 if required not in env:
811 raise Exception('Environment variable "%s" '
812 'required to be set to valid path' % required)
815 def _FormatAsEnvironmentBlock(envvar_dict):
816 """Format as an 'environment block' directly suitable for CreateProcess.
817 Briefly this is a list of key=value\0, terminated by an additional \0. See
818 CreateProcess documentation for more details."""
821 for key, value in envvar_dict.iteritems():
822 block += key + '=' + value + nul
826 def _ExtractCLPath(output_of_where):
827 """Gets the path to cl.exe based on the output of calling the environment
828 setup batch file, followed by the equivalent of `where`."""
829 # Take the first line, as that's the first found in the PATH.
830 for line in output_of_where.strip().splitlines():
831 if line.startswith('LOC:'):
832 return line[len('LOC:'):].strip()
834 def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags, open_out):
835 """It's not sufficient to have the absolute path to the compiler, linker,
836 etc. on Windows, as those tools rely on .dlls being in the PATH. We also
837 need to support both x86 and x64 compilers within the same build (to support
838 msvs_target_platform hackery). Different architectures require a different
839 compiler binary, and different supporting environment variables (INCLUDE,
840 LIB, LIBPATH). So, we extract the environment here, wrap all invocations
841 of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which
842 sets up the environment, and then we do not prefix the compiler with
843 an absolute path, instead preferring something like "cl.exe" in the rule
844 which will then run whichever the environment setup has put in the path.
845 When the following procedure to generate environment files does not
846 meet your requirement (e.g. for custom toolchains), you can pass
847 "-G ninja_use_custom_environment_files" to the gyp to suppress file
848 generation and use custom environment files prepared by yourself."""
849 archs = ('x86', 'x64')
850 if generator_flags.get('ninja_use_custom_environment_files', 0):
853 cl_paths[arch] = 'cl.exe'
855 vs = GetVSVersion(generator_flags)
858 # Extract environment variables for subprocesses.
859 args = vs.SetupScript(arch)
860 args.extend(('&&', 'set'))
861 popen = subprocess.Popen(
862 args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
863 variables, _ = popen.communicate()
864 env = _ExtractImportantEnvironment(variables)
865 env_block = _FormatAsEnvironmentBlock(env)
866 f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb')
870 # Find cl.exe location for this architecture.
871 args = vs.SetupScript(arch)
873 'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i'))
874 popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
875 output, _ = popen.communicate()
876 cl_paths[arch] = _ExtractCLPath(output)
879 def VerifyMissingSources(sources, build_dir, generator_flags, gyp_to_ninja):
880 """Emulate behavior of msvs_error_on_missing_sources present in the msvs
881 generator: Check that all regular source files, i.e. not created at run time,
882 exist on disk. Missing files cause needless recompilation when building via
883 VS, and we want this check to match for people/bots that build using ninja,
884 so they're not surprised when the VS build fails."""
885 if int(generator_flags.get('msvs_error_on_missing_sources', 0)):
886 no_specials = filter(lambda x: '$' not in x, sources)
887 relative = [os.path.join(build_dir, gyp_to_ninja(s)) for s in no_specials]
888 missing = filter(lambda x: not os.path.exists(x), relative)
890 # They'll look like out\Release\..\..\stuff\things.cc, so normalize the
891 # path for a slightly less crazy looking output.
892 cleaned_up = [os.path.normpath(x) for x in missing]
893 raise Exception('Missing input files:\n%s' % '\n'.join(cleaned_up))
895 # Sets some values in default_variables, which are required for many
896 # generators, run on Windows.
897 def CalculateCommonVariables(default_variables, params):
898 generator_flags = params.get('generator_flags', {})
900 # Set a variable so conditions can be based on msvs_version.
901 msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags)
902 default_variables['MSVS_VERSION'] = msvs_version.ShortName()
904 # To determine processor word size on Windows, in addition to checking
905 # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
906 # process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which
907 # contains the actual word size of the system when running thru WOW64).
908 if ('64' in os.environ.get('PROCESSOR_ARCHITECTURE', '') or
909 '64' in os.environ.get('PROCESSOR_ARCHITEW6432', '')):
910 default_variables['MSVS_OS_BITS'] = 64
912 default_variables['MSVS_OS_BITS'] = 32