ambuild2/frontend/cpp.py
author David Anderson <dvander@alliedmods.net>
Mon Nov 18 11:46:58 2013 -0800 (2013-11-18)
changeset 199 61e1d1fcb9bb
parent 191 591aacecffa3
child 200 dd255a16b1bb
permissions -rw-r--r--
Fix rc errors.
     1 # vim: set ts=8 sts=2 sw=2 tw=99 et:
     2 #
     3 # This file is part of AMBuild.
     4 # 
     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.
     9 # 
    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.
    14 # 
    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
    18 import subprocess
    19 import re, os, copy
    20 from ambuild2 import util
    21 
    22 class Vendor(object):
    23   def __init__(self, name, version, behavior, command, objSuffix):
    24     self.name = name
    25     self.version = version
    26     self.behavior = behavior
    27     self.command = command
    28     self.objSuffix = objSuffix
    29 
    30 class MSVC(Vendor):
    31   def __init__(self, command, version):
    32     super(MSVC, self).__init__('msvc', version, 'msvc', command, '.obj')
    33     self.definePrefix = '/D'
    34     self.pdbSuffix = '.pdb'
    35 
    36   @staticmethod
    37   def IncludePath(outputPath, includePath):
    38     #Hack - try and get a relative path because CL, with either 
    39     #/Zi or /ZI, combined with subprocess, apparently tries and
    40     #looks for paths like c:\bleh\"c:\bleh" <-- wtf
    41     #.. this according to Process Monitor
    42     outputPath = os.path.normcase(outputPath)
    43     includePath = os.path.normcase(includePath)
    44     outputDrive = os.path.splitdrive(outputPath)[0]
    45     includeDrive = os.path.splitdrive(includePath)[0]
    46     if outputDrive == includeDrive:
    47       return os.path.relpath(includePath, outputPath)
    48     return includePath
    49 
    50   def formatInclude(self, outputPath, includePath):
    51     return ['/I', self.IncludePath(outputPath, includePath)]
    52 
    53   def preprocessArgs(self, sourceFile):
    54     return ['/showIncludes', '/nologo', '/E', '/c', sourceFile]
    55 
    56   def objectArgs(self, sourceFile, objFile):
    57     return ['/showIncludes', '/nologo', '/c', sourceFile, '/Fo' + objFile]
    58 
    59 class CompatGCC(Vendor):
    60   def __init__(self, name, command, version):
    61     super(CompatGCC, self).__init__(name, version, 'gcc', command, '.o')
    62     parts = version.split('.')
    63     self.majorVersion = int(parts[0])
    64     self.minorVersion = int(parts[1])
    65     self.definePrefix = '-D'
    66     self.pdbSuffix = None
    67 
    68   def formatInclude(self, outputPath, includePath):
    69     return ['-I', os.path.normpath(includePath)]
    70 
    71   def objectArgs(self, sourceFile, objFile):
    72     return ['-H', '-c', sourceFile, '-o', objFile]
    73 
    74 class GCC(CompatGCC):
    75   def __init__(self, command, version):
    76     super(GCC, self).__init__('gcc', command, version)
    77 
    78 class Clang(CompatGCC):
    79   def __init__(self, command, version):
    80     super(Clang, self).__init__('clang', command, version)
    81 
    82 class SunPro(Vendor):
    83   def __init__(self, command, version):
    84     super(SunPro, self).__init__('sun', version, 'sun', command, '.o')
    85     parts = version.split('.')
    86     self.definePrefix = '-D'
    87     self.pdbSuffix = None
    88 
    89   def formatInclude(self, outputPath, includePath):
    90     return ['-I', os.path.normpath(includePath)]
    91 
    92   def objectArgs(self, sourceFile, objFile):
    93     return ['-H', '-c', sourceFile, '-o', objFile]
    94 
    95 def TryVerifyCompiler(cx, env, mode, cmd):
    96   if util.IsWindows():
    97     cc = VerifyCompiler(cx, env, mode, cmd, 'msvc')
    98     if cc:
    99       return cc
   100   return VerifyCompiler(cx, env, mode, cmd, 'gcc')
   101 
   102 CompilerSearch = {
   103   'CC': {
   104     'mac': ['cc', 'clang', 'gcc', 'icc'],
   105     'windows': ['cl'],
   106     'default': ['cc', 'gcc', 'clang', 'icc']
   107     },
   108   'CXX': {
   109     'mac': ['c++', 'clang++', 'g++', 'icc'],
   110     'windows': ['cl'],
   111     'default': ['c++', 'g++', 'clang++', 'icc']
   112   }
   113 }
   114 
   115 def DetectCompiler(cx, env, var):
   116   if var in env:
   117     trys = [env[var]]
   118   else:
   119     if util.Platform() in CompilerSearch[var]:
   120       trys = CompilerSearch[var][util.Platform()]
   121     else:
   122       trys = CompilerSearch[var]['default']
   123   for i in trys:
   124     cc = TryVerifyCompiler(cx, env, var, i)
   125     if cc:
   126       return cc
   127   raise Exception('Unable to find a suitable ' + var + ' compiler')
   128 
   129 def VerifyCompiler(cx, env, mode, cmd, vendor):
   130   args = cmd.split(' ')
   131   if 'CXXFLAGS' in env:
   132     args.extend(env['CXXFLAGS'])
   133   if 'CFLAGS' in env:
   134     args.extend(env['CFLAGS'])
   135   if mode == 'CXX' and 'CXXFLAGS' in env:
   136     args.extend(env['CXXFLAGS'])
   137   if mode == 'CXX':
   138     filename = 'test.cpp'
   139   else:
   140     filename = 'test.c'
   141   file = open(filename, 'w')
   142   file.write("""
   143 #include <stdio.h>
   144 #include <stdlib.h>
   145 
   146 int main()
   147 {
   148 #if defined __ICC
   149   printf("icc %d\\n", __ICC);
   150 #elif defined __clang__
   151 # if defined(__clang_major__) && defined(__clang_minor__)
   152   printf("clang %d.%d\\n", __clang_major__, __clang_minor__);
   153 # else
   154   printf("clang 1.%d\\n", __GNUC_MINOR__);
   155 # endif
   156 #elif defined __GNUC__
   157   printf("gcc %d.%d\\n", __GNUC__, __GNUC_MINOR__);
   158 #elif defined _MSC_VER
   159   printf("msvc %d\\n", _MSC_VER);
   160 #elif defined __TenDRA__
   161   printf("tendra 0\\n");
   162 #elif defined __SUNPRO_C
   163   printf("sun %x\\n", __SUNPRO_C);
   164 #elif defined __SUNPRO_CC
   165   printf("sun %x\\n", __SUNPRO_CC);
   166 #else
   167 #error "Unrecognized compiler!"
   168 #endif
   169 #if defined __cplusplus
   170   printf("CXX\\n");
   171 #else
   172   printf("CC\\n");
   173 #endif
   174   exit(0);
   175 }
   176 """)
   177   file.close()
   178   if mode == 'CC':
   179     executable = 'test' + util.ExecutableSuffix()
   180   elif mode == 'CXX':
   181     executable = 'testp' + util.ExecutableSuffix()
   182 
   183   # Make sure the exe is gone.
   184   if os.path.exists(executable):
   185     os.unlink(executable)
   186 
   187   # Until we can better detect vendors, don't do this.
   188   # if vendor == 'gcc' and mode == 'CXX':
   189   #   args.extend(['-fno-exceptions', '-fno-rtti'])
   190   args.extend([filename, '-o', executable])
   191   util.con_out(
   192     util.ConsoleHeader,
   193     'Checking {0} compiler (vendor test {1})... '.format(mode, vendor),
   194     util.ConsoleBlue,
   195     '{0}'.format(args),
   196     util.ConsoleNormal
   197   )
   198   p = util.CreateProcess(args)
   199   if p == None:
   200     print('not found')
   201     return False
   202   if util.WaitForProcess(p) != 0:
   203     print('failed with return code {0}'.format(p.returncode))
   204     return False
   205 
   206   exe = util.MakePath('.', executable)
   207   p = util.CreateProcess([executable], executable = exe)
   208   if p == None:
   209     print('failed to create executable')
   210     return False
   211   if util.WaitForProcess(p) != 0:
   212     print('executable failed with return code {0}'.format(p.returncode))
   213     return False
   214   lines = p.stdoutText.splitlines()
   215   if len(lines) != 2:
   216     print('invalid executable output')
   217     return False
   218   if lines[1] != mode:
   219     print('requested {0} compiler, found {1}'.format(mode, lines[1]))
   220     return False
   221 
   222   vendor, version = lines[0].split(' ')
   223   if vendor == 'gcc':
   224     v = GCC(cmd, version)
   225   elif vendor == 'clang':
   226     v = Clang(cmd, version)
   227   elif vendor == 'msvc':
   228     v = MSVC(cmd, version)
   229   elif vendor == 'sun':
   230     v = SunPro(cmd, version)
   231   else:
   232     print('Unknown vendor {0}'.format(vendor))
   233     return False
   234 
   235   util.con_out(
   236     util.ConsoleHeader,
   237     'found {0} version {1}'.format(vendor, version),
   238     util.ConsoleNormal
   239   )
   240   return v
   241 
   242 class Dep(object):
   243   def __init__(self, text, node):
   244     self.text = text
   245     self.node = node
   246 
   247 class Compiler(object):
   248   attrs = [
   249     'includes',         # C and C++ include paths
   250     'cxxincludes',      # C++-only include paths
   251     'cflags',           # C and C++ compiler flags
   252     'cxxflags',         # C++-only compiler flags
   253     'defines',          # C and C++ #defines
   254     'cxxdefines',       # C++-only #defines
   255 
   256     'rcdefines',        # Resource Compiler (RC) defines
   257 
   258     # Link flags. If any members are not strings, they will be interpreted as
   259     # Dep entries created from BinaryBuilder.
   260     'linkflags',
   261 
   262     # An array of objects to link, after all link flags have been specified.
   263     # Entries may either be strings containing a path, or Dep entries created
   264     # from BinaryBuilder.
   265     'postlink',
   266 
   267     # An array of nodes which should be weak dependencies on each source
   268     # compilation command.
   269     'sourcedeps',
   270   ]
   271 
   272   def __init__(self, cc, cxx):
   273     self.cc = cc
   274     self.cxx = cxx
   275     for attr in Compiler.attrs:
   276       setattr(self, attr, [])
   277 
   278   def clone(self):
   279     cc = Compiler(self.cc, self.cxx)
   280     cc.cc = self.cc
   281     cc.cxx = self.cxx
   282     for attr in Compiler.attrs:
   283       setattr(cc, attr, copy.copy(getattr(self, attr)))
   284     return cc
   285 
   286   def Program(self, name):
   287     return Program(self, name)
   288 
   289   def Library(self, name):
   290     return Library(self, name)
   291 
   292   @staticmethod
   293   def Dep(text, node=None):
   294     return Dep(text, node)
   295 
   296 # Environment representing a C/C++ compiler invocation. Encapsulates most
   297 # arguments.
   298 class CCommandEnv(object):
   299   def __init__(self, outputPath, config, compiler):
   300     args = compiler.command.split(' ')
   301     args += config.cflags
   302     if compiler == config.cxx:
   303       args += config.cxxflags
   304     args += [compiler.definePrefix + define for define in config.defines]
   305     if compiler == config.cxx:
   306       args += [compiler.definePrefix + define for define in config.cxxdefines]
   307     for include in config.includes:
   308       args += compiler.formatInclude(outputPath, include)
   309     if compiler == config.cxx:
   310       for include in config.cxxincludes:
   311         args += compiler.formatInclude(outputPath, include)
   312     self.argv = args
   313     self.compiler = compiler
   314 
   315 def NameForObjectFile(file):
   316   return re.sub('[^a-zA-Z0-9_]+', '_', os.path.splitext(file)[0]);
   317 
   318 class ObjectFile(object):
   319   def __init__(self, sourceFile, outputFile, argv):
   320     self.sourceFile = sourceFile
   321     self.outputFile = outputFile
   322     self.argv = argv
   323 
   324 class RCFile(object):
   325   def __init__(self, sourceFile, preprocFile, outputFile, cl_argv, rc_argv):
   326     self.sourceFile = sourceFile
   327     self.preprocFile = preprocFile
   328     self.outputFile = outputFile
   329     self.cl_argv = cl_argv 
   330     self.rc_argv = rc_argv
   331 
   332 def LinkFlags(compiler):
   333   argv = []
   334   for item in compiler.linkflags:
   335     if type(item) is not Dep:
   336       argv.append(item)
   337     else:
   338       argv.append(item.text)
   339   for item in compiler.postlink:
   340     if type(item) is not Dep:
   341       argv.append(item)
   342     else:
   343       argv.append(item.text)
   344   return argv
   345 
   346 class BinaryBuilder(object):
   347   def __init__(self, compiler, name):
   348     super(BinaryBuilder, self).__init__()
   349     self.compiler = compiler.clone()
   350     self.sources = []
   351     self.name_ = name
   352     self.used_cxx_ = False
   353     self.linker_ = None
   354 
   355   def generate(self, generator, cx):
   356     return generator.addCxxTasks(cx, self)
   357 
   358   # Make an item that can be passed into linkflags/postlink but has an attached
   359   # dependency.
   360   def Dep(self, text, node=None): 
   361     return Dep(text, node)
   362 
   363   # The folder we'll be in, relative to our build context.
   364   @property
   365   def localFolder(self):
   366     return self.name_
   367 
   368   # Exposed only for frontends.
   369   @property
   370   def linker(self):
   371     return self.linker_
   372 
   373   # Compute the build folder.
   374   def getBuildFolder(self, builder):
   375     return os.path.join(builder.buildFolder, self.localFolder)
   376 
   377   def finish(self, cx):
   378     # Because we want to compute relative include folders for MSVC (see its
   379     # vendor object), we need to compute an absolute path to the build folder.
   380     self.outputFolder = self.getBuildFolder(cx)
   381     self.outputPath = os.path.join(cx.buildPath, self.outputFolder)
   382     self.default_c_env = CCommandEnv(self.outputPath, self.compiler, self.compiler.cc)
   383     self.default_cxx_env = CCommandEnv(self.outputPath, self.compiler, self.compiler.cxx)
   384 
   385     self.objects = []
   386     self.resources = []
   387     for item in self.sources:
   388       if os.path.isabs(item):
   389         sourceFile = item
   390       else:
   391         sourceFile = os.path.join(cx.currentSourcePath, item)
   392       sourceFile = os.path.normpath(sourceFile)
   393       encname = NameForObjectFile(filename)
   394 
   395       filename, extension = os.path.splitext(item)
   396       if extension == '.rc':
   397         cenv = self.default_c_env
   398         objectFile = encname + '.res'
   399       else:
   400         if extension == '.c':
   401           cenv = self.default_c_env
   402         else:
   403           cenv = self.default_cxx_env
   404           self.used_cxx_ = True
   405         objectFile = encname + cenv.compiler.objSuffix
   406 
   407       if extension == '.rc':
   408         # This is only relevant on Windows.
   409         vendor = cenv.compiler
   410         defines = self.compiler.defines + self.compiler.cxxdefines + self.compiler.rcdefines
   411         cl_argv = vendor.command.split(' ')
   412         cl_argv += [vendor.definePrefix + define for define in defines]
   413         for include in (self.compiler.includes + self.compiler.cxxincludes):
   414           cl_argv += vendor.formatInclude(objectFile, include)
   415         cl_argv += vendor.preprocessArgs(sourceFile)
   416         cl_argv += ['/Fo' + encname + '.i']
   417 
   418         rc_argv = ['rc', '/nologo']
   419         for define in defines:
   420           rc_argv.extend(['/d', define])
   421         for include in (self.compiler.includes + self.compiler.cxxincludes):
   422           rc_argv.extend(['/i', MSVC.IncludePath(objectFile, include)])
   423         rc_argv.append('/fo' + objectFile)
   424         rc_argv.append(sourceFile)
   425 
   426         self.resources.append(RCFile(sourceFile, encname + '.i', objectFile, cl_argv, rc_argv))
   427       else:
   428         argv = cenv.argv + cenv.compiler.objectArgs(sourceFile, objectFile)
   429         self.objects.append(ObjectFile(sourceFile, objectFile, argv))
   430 
   431     if not self.linker_:
   432       if self.used_cxx_:
   433         self.linker_ = self.compiler.cxx
   434       else:
   435         self.linker_ = self.compiler.cc
   436 
   437     argv = self.linker_.command.split(' ')
   438     for objfile in self.objects:
   439       argv.append(objfile.outputFile)
   440     for rcfile in self.resources:
   441       argv.append(rcfile.outputFile)
   442 
   443     argv = self.generateBinary(cx, argv)
   444 
   445     self.argv = argv
   446     if self.linker_.pdbSuffix:
   447       self.pdbFile = self.name_ + self.linker_.pdbSuffix
   448     else:
   449       self.pdbFile = None
   450 
   451 class Program(BinaryBuilder):
   452   def __init__(self, compiler, name):
   453     super(Program, self).__init__(compiler, name)
   454     self.outputFile = name + util.ExecutableSuffix()
   455 
   456   def generateBinary(self, cx, argv):
   457     if isinstance(self.linker_, MSVC):
   458       argv.append('/link')
   459     argv.extend(LinkFlags(self.compiler))
   460     if isinstance(self.linker_, MSVC):
   461       argv.append('/OUT:' + self.outputFile)
   462       argv.append('/DEBUG')
   463       argv.append('/nologo')
   464       argv.append('/PDB:"' + self.name_ + '.pdb"')
   465     else:
   466       argv.extend(['-o', self.outputFile])
   467 
   468     return argv
   469 
   470 class Library(BinaryBuilder):
   471   def __init__(self, compiler, name):
   472     super(Library, self).__init__(compiler, name)
   473     self.outputFile = name + util.SharedLibSuffix()
   474 
   475   def generateBinary(self, cx, argv):
   476     if isinstance(self.linker_, MSVC):
   477       argv.append('/link')
   478     argv.extend(LinkFlags(self.compiler))
   479     if isinstance(self.linker_, MSVC):
   480       argv.append('/OUT:' + self.outputFile)
   481       argv.append('/DEBUG')
   482       argv.append('/nologo')
   483       argv.append('/DLL')
   484       argv.append('/PDB:"' + self.name_ + '.pdb"')
   485     elif isinstance(self.linker_, CompatGCC):
   486       if util.IsMac():
   487         argv.append('-dynamiclib')
   488       else:
   489         argv.append('-shared')
   490       argv.extend(['-o', self.outputFile])
   491 
   492     return argv