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