Add /FS for /Zi builds on Visual Studio 2013.
1 # vim: set ts=8 sts=2 sw=2 tw=99 et:
3 # This file is part of AMBuild.
5 # AMBuild is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # AMBuild is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with AMBuild. If not, see <http://www.gnu.org/licenses/>.
17 from __future__ import print_function
20 from ambuild2 import util
23 def __init__(self, name, version, behavior, command, objSuffix):
25 self.version = version
26 self.behavior = behavior
27 self.command = command
28 self.objSuffix = objSuffix
29 self.debuginfo_argv = []
32 def __init__(self, command, version):
33 super(MSVC, self).__init__('msvc', version, 'msvc', command, '.obj')
34 self.definePrefix = '/D'
35 self.debuginfo_argv = ['/Zi']
36 if self.version >= 1800:
37 self.debuginfo_argv += ['/FS']
40 def IncludePath(outputPath, includePath):
41 # Hack - try and get a relative path because CL, with either
42 # /Zi or /ZI, combined with subprocess, apparently tries and
43 # looks for paths like c:\bleh\"c:\bleh" <-- wtf
44 # .. this according to Process Monitor
45 outputPath = os.path.normcase(outputPath)
46 includePath = os.path.normcase(includePath)
47 outputDrive = os.path.splitdrive(outputPath)[0]
48 includeDrive = os.path.splitdrive(includePath)[0]
49 if outputDrive == includeDrive:
50 return os.path.relpath(includePath, outputPath)
53 def formatInclude(self, outputPath, includePath):
54 return ['/I', self.IncludePath(outputPath, includePath)]
56 def preprocessArgs(self, sourceFile):
57 return ['/showIncludes', '/nologo', '/P', '/c', sourceFile]
59 def objectArgs(self, sourceFile, objFile):
60 return ['/showIncludes', '/nologo', '/c', sourceFile, '/Fo' + objFile]
62 class CompatGCC(Vendor):
63 def __init__(self, name, command, version):
64 super(CompatGCC, self).__init__(name, version, 'gcc', command, '.o')
65 parts = version.split('.')
66 self.majorVersion = int(parts[0])
67 self.minorVersion = int(parts[1])
68 self.definePrefix = '-D'
70 def formatInclude(self, outputPath, includePath):
71 return ['-I', os.path.normpath(includePath)]
73 def objectArgs(self, sourceFile, objFile):
74 return ['-H', '-c', sourceFile, '-o', objFile]
77 def __init__(self, command, version):
78 super(GCC, self).__init__('gcc', command, version)
79 self.debuginfo_argv = ['-g3', '-ggdb3']
81 class Clang(CompatGCC):
82 def __init__(self, command, version):
83 super(Clang, self).__init__('clang', command, version)
84 self.debuginfo_argv = ['-g3']
87 def __init__(self, command, version):
88 super(SunPro, self).__init__('sun', version, 'sun', command, '.o')
89 self.definePrefix = '-D'
90 self.debuginfo_argv = ['-g3']
92 def formatInclude(self, outputPath, includePath):
93 return ['-I', os.path.normpath(includePath)]
95 def objectArgs(self, sourceFile, objFile):
96 return ['-H', '-c', sourceFile, '-o', objFile]
98 def TryVerifyCompiler(cx, env, mode, cmd):
100 cc = VerifyCompiler(cx, env, mode, cmd, 'msvc')
103 return VerifyCompiler(cx, env, mode, cmd, 'gcc')
107 'mac': ['cc', 'clang', 'gcc', 'icc'],
109 'default': ['cc', 'gcc', 'clang', 'icc']
112 'mac': ['c++', 'clang++', 'g++', 'icc'],
114 'default': ['c++', 'g++', 'clang++', 'icc']
118 def DetectCompiler(cx, env, var):
122 if util.Platform() in CompilerSearch[var]:
123 trys = CompilerSearch[var][util.Platform()]
125 trys = CompilerSearch[var]['default']
127 cc = TryVerifyCompiler(cx, env, var, i)
130 raise Exception('Unable to find a suitable ' + var + ' compiler')
132 def VerifyCompiler(cx, env, mode, cmd, vendor):
133 args = cmd.split(' ')
134 if 'CXXFLAGS' in env:
135 args.extend(env['CXXFLAGS'])
137 args.extend(env['CFLAGS'])
138 if mode == 'CXX' and 'CXXFLAGS' in env:
139 args.extend(env['CXXFLAGS'])
141 filename = 'test.cpp'
144 file = open(filename, 'w')
152 printf("icc %d\\n", __ICC);
153 #elif defined __clang__
154 # if defined(__clang_major__) && defined(__clang_minor__)
155 printf("clang %d.%d\\n", __clang_major__, __clang_minor__);
157 printf("clang 1.%d\\n", __GNUC_MINOR__);
159 #elif defined __GNUC__
160 printf("gcc %d.%d\\n", __GNUC__, __GNUC_MINOR__);
161 #elif defined _MSC_VER
162 printf("msvc %d\\n", _MSC_VER);
163 #elif defined __TenDRA__
164 printf("tendra 0\\n");
165 #elif defined __SUNPRO_C
166 printf("sun %x\\n", __SUNPRO_C);
167 #elif defined __SUNPRO_CC
168 printf("sun %x\\n", __SUNPRO_CC);
170 #error "Unrecognized compiler!"
172 #if defined __cplusplus
182 executable = 'test' + util.ExecutableSuffix()
184 executable = 'testp' + util.ExecutableSuffix()
186 # Make sure the exe is gone.
187 if os.path.exists(executable):
188 os.unlink(executable)
190 # Until we can better detect vendors, don't do this.
191 # if vendor == 'gcc' and mode == 'CXX':
192 # args.extend(['-fno-exceptions', '-fno-rtti'])
193 args.extend([filename, '-o', executable])
196 'Checking {0} compiler (vendor test {1})... '.format(mode, vendor),
201 p = util.CreateProcess(args)
205 if util.WaitForProcess(p) != 0:
206 print('failed with return code {0}'.format(p.returncode))
209 exe = util.MakePath('.', executable)
210 p = util.CreateProcess([executable], executable = exe)
212 print('failed to create executable')
214 if util.WaitForProcess(p) != 0:
215 print('executable failed with return code {0}'.format(p.returncode))
217 lines = p.stdoutText.splitlines()
219 print('invalid executable output')
222 print('requested {0} compiler, found {1}'.format(mode, lines[1]))
225 vendor, version = lines[0].split(' ')
227 v = GCC(cmd, version)
228 elif vendor == 'clang':
229 v = Clang(cmd, version)
230 elif vendor == 'msvc':
231 v = MSVC(cmd, version)
232 elif vendor == 'sun':
233 v = SunPro(cmd, version)
235 print('Unknown vendor {0}'.format(vendor))
240 'found {0} version {1}'.format(vendor, version),
246 def __init__(self, text, node):
250 class Compiler(object):
252 'includes', # C and C++ include paths
253 'cxxincludes', # C++-only include paths
254 'cflags', # C and C++ compiler flags
255 'cxxflags', # C++-only compiler flags
256 'defines', # C and C++ #defines
257 'cxxdefines', # C++-only #defines
259 'rcdefines', # Resource Compiler (RC) defines
261 # Link flags. If any members are not strings, they will be interpreted as
262 # Dep entries created from BinaryBuilder.
265 # An array of objects to link, after all link flags have been specified.
266 # Entries may either be strings containing a path, or Dep entries created
267 # from BinaryBuilder.
270 # An array of nodes which should be weak dependencies on each source
271 # compilation command.
275 def __init__(self, cc, cxx):
278 self.debuginfo = True
279 for attr in Compiler.attrs:
280 setattr(self, attr, [])
283 cc = Compiler(self.cc, self.cxx)
286 cc.debuginfo = self.debuginfo
287 for attr in Compiler.attrs:
288 setattr(cc, attr, copy.copy(getattr(self, attr)))
291 def Program(self, name):
292 return Program(self, name)
294 def Library(self, name):
295 return Library(self, name)
297 def StaticLibrary(self, name):
298 return StaticLibrary(self, name)
301 def Dep(text, node=None):
302 return Dep(text, node)
304 # Environment representing a C/C++ compiler invocation. Encapsulates most
306 class CCommandEnv(object):
307 def __init__(self, outputPath, config, compiler):
308 args = compiler.command.split(' ')
309 args += config.cflags
311 args += compiler.debuginfo_argv
312 if compiler == config.cxx:
313 args += config.cxxflags
314 args += [compiler.definePrefix + define for define in config.defines]
315 if compiler == config.cxx:
316 args += [compiler.definePrefix + define for define in config.cxxdefines]
317 for include in config.includes:
318 args += compiler.formatInclude(outputPath, include)
319 if compiler == config.cxx:
320 for include in config.cxxincludes:
321 args += compiler.formatInclude(outputPath, include)
323 self.compiler = compiler
325 def NameForObjectFile(file):
326 return re.sub('[^a-zA-Z0-9_]+', '_', os.path.splitext(file)[0]);
328 class ObjectFile(object):
329 def __init__(self, sourceFile, outputFile, argv, sharedOutputs):
330 self.sourceFile = sourceFile
331 self.outputFile = outputFile
333 self.sharedOutputs = sharedOutputs
335 class RCFile(object):
336 def __init__(self, sourceFile, preprocFile, outputFile, cl_argv, rc_argv):
337 self.sourceFile = sourceFile
338 self.preprocFile = preprocFile
339 self.outputFile = outputFile
340 self.cl_argv = cl_argv
341 self.rc_argv = rc_argv
343 class BinaryBuilder(object):
344 def __init__(self, compiler, name):
345 super(BinaryBuilder, self).__init__()
346 self.compiler = compiler.clone()
349 self.used_cxx_ = False
352 def generate(self, generator, cx):
353 return generator.addCxxTasks(cx, self)
355 # Make an item that can be passed into linkflags/postlink but has an attached
357 def Dep(self, text, node=None):
358 return Dep(text, node)
360 # The folder we'll be in, relative to our build context.
362 def localFolder(self):
365 # Exposed only for frontends.
370 # Compute the build folder.
371 def getBuildFolder(self, builder):
372 return os.path.join(builder.buildFolder, self.localFolder)
374 def linkFlag(self, cx, item):
375 if type(item) is Dep:
376 # If the dep is a file dependency (no node attached), and has a relative
377 # path, make it absolute so the linker knows where to look.
378 if item.node is None and not os.path.isabs(item.text):
379 return os.path.join(cx.currentSourcePath, item.text)
382 if hasattr(item, 'path'):
383 if os.path.isabs(item.path):
386 local_path = os.path.join(cx.buildFolder, self.localFolder)
387 return os.path.relpath(item.path, local_path)
391 def linkFlags(self, cx):
392 argv = [self.linkFlag(cx, item) for item in self.compiler.linkflags]
393 argv += [self.linkFlag(cx, item) for item in self.compiler.postlink]
396 def finish(self, cx):
397 # Because we want to compute relative include folders for MSVC (see its
398 # vendor object), we need to compute an absolute path to the build folder.
399 self.outputFolder = self.getBuildFolder(cx)
400 self.outputPath = os.path.join(cx.buildPath, self.outputFolder)
401 self.default_c_env = CCommandEnv(self.outputPath, self.compiler, self.compiler.cc)
402 self.default_cxx_env = CCommandEnv(self.outputPath, self.compiler, self.compiler.cxx)
404 shared_cc_outputs = []
405 if self.compiler.debuginfo and self.compiler.cc.behavior == 'msvc':
406 cl_version = int(self.compiler.cc.version) - 600
407 shared_pdb = 'vc{0}.pdb'.format(int(cl_version / 10))
408 shared_cc_outputs += [shared_pdb]
412 for item in self.sources:
413 if os.path.isabs(item):
416 sourceFile = os.path.join(cx.currentSourcePath, item)
417 sourceFile = os.path.normpath(sourceFile)
419 filename, extension = os.path.splitext(item)
420 encname = NameForObjectFile(filename)
422 if extension == '.rc':
423 cenv = self.default_c_env
424 objectFile = encname + '.res'
426 if extension == '.c':
427 cenv = self.default_c_env
429 cenv = self.default_cxx_env
430 self.used_cxx_ = True
431 objectFile = encname + cenv.compiler.objSuffix
433 if extension == '.rc':
434 # This is only relevant on Windows.
435 vendor = cenv.compiler
436 defines = self.compiler.defines + self.compiler.cxxdefines + self.compiler.rcdefines
437 cl_argv = vendor.command.split(' ')
438 cl_argv += [vendor.definePrefix + define for define in defines]
439 for include in (self.compiler.includes + self.compiler.cxxincludes):
440 cl_argv += vendor.formatInclude(objectFile, include)
441 cl_argv += vendor.preprocessArgs(sourceFile)
443 rc_argv = ['rc', '/nologo']
444 for define in defines:
445 rc_argv.extend(['/d', define])
446 for include in (self.compiler.includes + self.compiler.cxxincludes):
447 rc_argv.extend(['/i', MSVC.IncludePath(objectFile, include)])
448 rc_argv.append('/fo' + objectFile)
449 rc_argv.append(sourceFile)
451 self.resources.append(RCFile(sourceFile, encname + '.i', objectFile, cl_argv, rc_argv))
453 argv = cenv.argv + cenv.compiler.objectArgs(sourceFile, objectFile)
454 obj = ObjectFile(sourceFile, objectFile, argv, shared_cc_outputs)
455 self.objects.append(obj)
459 self.linker_ = self.compiler.cxx
461 self.linker_ = self.compiler.cc
463 files = [out.outputFile for out in self.objects + self.resources]
464 self.argv = self.generateBinary(cx, files)
465 self.linker_outputs = [self.outputFile]
466 self.debug_entry = None
468 if self.linker_.behavior == 'msvc':
469 if isinstance(self, Library):
470 # In theory, .dlls should have exports, so MSVC will generate these
471 # files. If this turns out not to be true, we may have to get fancier.
472 self.linker_outputs += [self.name_ + '.lib']
473 self.linker_outputs += [self.name_ + '.exp']
475 if self.compiler.debuginfo:
476 self.perform_symbol_steps(cx)
478 def perform_symbol_steps(self, cx):
479 if isinstance(self.linker_, MSVC):
480 # Note, pdb is last since we read the pdb as outputs[-1].
481 self.linker_outputs += [self.name_ + '.pdb']
482 elif cx.target_platform is 'mac':
483 bundle_folder = os.path.join(self.localFolder, self.outputFile + '.dSYM')
484 bundle_entry = cx.AddFolder(bundle_folder)
487 'Contents/Resources',
488 'Contents/Resources/DWARF',
490 for folder in bundle_layout:
491 cx.AddFolder(os.path.join(bundle_folder, folder))
492 self.linker_outputs += [
493 self.outputFile + '.dSYM/Contents/Info.plist',
494 self.outputFile + '.dSYM/Contents/Resources/DWARF/' + self.outputFile
496 self.debug_entry = bundle_entry
497 self.argv = ['ambuild_dsymutil_wrapper.sh', self.outputFile] + self.argv
498 elif cx.target_platform is 'linux':
499 self.linker_outputs += [
500 self.outputFile + '.dbg'
502 self.argv = ['ambuild_objcopy_wrapper.sh', self.outputFile] + self.argv
504 def link(self, context, folder, inputs):
505 # The existence of .ilk files on Windows does not seem reliable, so we
506 # treat it as "shared" which does not participate in the DAG (yet).
508 if self.linker_.behavior == 'msvc':
509 if not isinstance(self, StaticLibrary) and '/INCREMENTAL:NO' not in self.argv:
510 shared_outputs += [self.name_ + '.ilk']
512 ignore, outputs = context.AddCommand(
515 outputs = self.linker_outputs,
517 shared_outputs = shared_outputs
519 if not self.debug_entry and self.compiler.debuginfo:
520 self.debug_entry = outputs[-1]
521 return outputs[0], self.debug_entry
523 class Program(BinaryBuilder):
524 def __init__(self, compiler, name):
525 super(Program, self).__init__(compiler, name)
526 self.outputFile = name + util.ExecutableSuffix()
528 def generateBinary(self, cx, files):
529 argv = self.linker_.command.split(' ')
532 if isinstance(self.linker_, MSVC):
534 argv.extend(self.linkFlags(cx))
535 argv.append('/nologo')
537 '/OUT:' + self.outputFile,
540 if self.compiler.debuginfo:
541 argv += ['/DEBUG', '/PDB:"' + self.name_ + '.pdb"']
543 argv.extend(self.linkFlags(cx))
544 argv.extend(['-o', self.outputFile])
548 class Library(BinaryBuilder):
549 def __init__(self, compiler, name):
550 super(Library, self).__init__(compiler, name)
551 self.outputFile = name + util.SharedLibSuffix()
553 def generateBinary(self, cx, files):
554 argv = self.linker_.command.split(' ')
557 if isinstance(self.linker_, MSVC):
559 argv.extend(self.linkFlags(cx))
561 '/OUT:' + self.outputFile,
566 if self.compiler.debuginfo:
567 argv += ['/DEBUG', '/PDB:"' + self.name_ + '.pdb"']
568 elif isinstance(self.linker_, CompatGCC):
569 argv.extend(self.linkFlags(cx))
571 argv.append('-dynamiclib')
573 argv.append('-shared')
574 argv.extend(['-o', self.outputFile])
578 class StaticLibrary(BinaryBuilder):
579 def __init__(self, compiler, name):
580 super(StaticLibrary, self).__init__(compiler, name)
581 self.outputFile = util.StaticLibPrefix() + name + util.StaticLibSuffix()
583 def generateBinary(self, cx, files):
584 if isinstance(self.linker_, MSVC):
585 argv = ['lib.exe', '/OUT:' + self.outputFile]
587 argv = ['ar', 'rcs', self.outputFile]
591 def perform_symbol_steps(self, cx):