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