Home | History | Annotate | Download | only in libcxx
      1 #===----------------------------------------------------------------------===##
      2 #
      3 #                     The LLVM Compiler Infrastructure
      4 #
      5 # This file is dual licensed under the MIT and the University of Illinois Open
      6 # Source Licenses. See LICENSE.TXT for details.
      7 #
      8 #===----------------------------------------------------------------------===##
      9 
     10 import platform
     11 import os
     12 import libcxx.util
     13 
     14 
     15 class CXXCompiler(object):
     16     CM_Default = 0
     17     CM_PreProcess = 1
     18     CM_Compile = 2
     19     CM_Link = 3
     20 
     21     def __init__(self, path, flags=None, compile_flags=None, link_flags=None,
     22                  warning_flags=None, verify_supported=None,
     23                  verify_flags=None, use_verify=False,
     24                  modules_flags=None, use_modules=False,
     25                  use_ccache=False, use_warnings=False, compile_env=None,
     26                  cxx_type=None, cxx_version=None):
     27         self.source_lang = 'c++'
     28         self.path = path
     29         self.flags = list(flags or [])
     30         self.compile_flags = list(compile_flags or [])
     31         self.link_flags = list(link_flags or [])
     32         self.warning_flags = list(warning_flags or [])
     33         self.verify_supported = verify_supported
     34         self.use_verify = use_verify
     35         self.verify_flags = list(verify_flags or [])
     36         assert not use_verify or verify_supported
     37         assert not use_verify or verify_flags is not None
     38         self.modules_flags = list(modules_flags or [])
     39         self.use_modules = use_modules
     40         assert not use_modules or modules_flags is not None
     41         self.use_ccache = use_ccache
     42         self.use_warnings = use_warnings
     43         if compile_env is not None:
     44             self.compile_env = dict(compile_env)
     45         else:
     46             self.compile_env = None
     47         self.type = cxx_type
     48         self.version = cxx_version
     49         if self.type is None or self.version is None:
     50             self._initTypeAndVersion()
     51 
     52     def isVerifySupported(self):
     53         if self.verify_supported is None:
     54             self.verify_supported = self.hasCompileFlag(['-Xclang',
     55                                         '-verify-ignore-unexpected'])
     56             if self.verify_supported:
     57                 self.verify_flags = [
     58                     '-Xclang', '-verify',
     59                     '-Xclang', '-verify-ignore-unexpected=note',
     60                     '-ferror-limit=1024'
     61                 ]
     62         return self.verify_supported
     63 
     64     def useVerify(self, value=True):
     65         self.use_verify = value
     66         assert not self.use_verify or self.verify_flags is not None
     67 
     68     def useModules(self, value=True):
     69         self.use_modules = value
     70         assert not self.use_modules or self.modules_flags is not None
     71 
     72     def useCCache(self, value=True):
     73         self.use_ccache = value
     74 
     75     def useWarnings(self, value=True):
     76         self.use_warnings = value
     77 
     78     def _initTypeAndVersion(self):
     79         # Get compiler type and version
     80         macros = self.dumpMacros()
     81         if macros is None:
     82             return
     83         compiler_type = None
     84         major_ver = minor_ver = patchlevel = None
     85         if '__clang__' in macros.keys():
     86             compiler_type = 'clang'
     87             # Treat apple's llvm fork differently.
     88             if '__apple_build_version__' in macros.keys():
     89                 compiler_type = 'apple-clang'
     90             major_ver = macros['__clang_major__']
     91             minor_ver = macros['__clang_minor__']
     92             patchlevel = macros['__clang_patchlevel__']
     93         elif '__GNUC__' in macros.keys():
     94             compiler_type = 'gcc'
     95             major_ver = macros['__GNUC__']
     96             minor_ver = macros['__GNUC_MINOR__']
     97             patchlevel = macros['__GNUC_PATCHLEVEL__']
     98         self.type = compiler_type
     99         self.version = (major_ver, minor_ver, patchlevel)
    100 
    101     def _basicCmd(self, source_files, out, mode=CM_Default, flags=[],
    102                   input_is_cxx=False):
    103         cmd = []
    104         if self.use_ccache \
    105                 and not mode == self.CM_Link \
    106                 and not mode == self.CM_PreProcess:
    107             cmd += ['ccache']
    108         cmd += [self.path]
    109         if out is not None:
    110             cmd += ['-o', out]
    111         if input_is_cxx:
    112             cmd += ['-x', self.source_lang]
    113         if isinstance(source_files, list):
    114             cmd += source_files
    115         elif isinstance(source_files, str):
    116             cmd += [source_files]
    117         else:
    118             raise TypeError('source_files must be a string or list')
    119         if mode == self.CM_PreProcess:
    120             cmd += ['-E']
    121         elif mode == self.CM_Compile:
    122             cmd += ['-c']
    123         cmd += self.flags
    124         if self.use_verify:
    125             cmd += self.verify_flags
    126             assert mode in [self.CM_Default, self.CM_Compile]
    127         if self.use_modules:
    128             cmd += self.modules_flags
    129         if mode != self.CM_Link:
    130             cmd += self.compile_flags
    131             if self.use_warnings:
    132                 cmd += self.warning_flags
    133         if mode != self.CM_PreProcess and mode != self.CM_Compile:
    134             cmd += self.link_flags
    135         cmd += flags
    136         return cmd
    137 
    138     def preprocessCmd(self, source_files, out=None, flags=[]):
    139         return self._basicCmd(source_files, out, flags=flags,
    140                              mode=self.CM_PreProcess,
    141                              input_is_cxx=True)
    142 
    143     def compileCmd(self, source_files, out=None, flags=[]):
    144         return self._basicCmd(source_files, out, flags=flags,
    145                              mode=self.CM_Compile,
    146                              input_is_cxx=True) + ['-c']
    147 
    148     def linkCmd(self, source_files, out=None, flags=[]):
    149         return self._basicCmd(source_files, out, flags=flags,
    150                               mode=self.CM_Link)
    151 
    152     def compileLinkCmd(self, source_files, out=None, flags=[]):
    153         return self._basicCmd(source_files, out, flags=flags)
    154 
    155     def preprocess(self, source_files, out=None, flags=[], cwd=None):
    156         cmd = self.preprocessCmd(source_files, out, flags)
    157         out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env,
    158                                                   cwd=cwd)
    159         return cmd, out, err, rc
    160 
    161     def compile(self, source_files, out=None, flags=[], cwd=None):
    162         cmd = self.compileCmd(source_files, out, flags)
    163         out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env,
    164                                                   cwd=cwd)
    165         return cmd, out, err, rc
    166 
    167     def link(self, source_files, out=None, flags=[], cwd=None):
    168         cmd = self.linkCmd(source_files, out, flags)
    169         out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env,
    170                                                   cwd=cwd)
    171         return cmd, out, err, rc
    172 
    173     def compileLink(self, source_files, out=None, flags=[],
    174                     cwd=None):
    175         cmd = self.compileLinkCmd(source_files, out, flags)
    176         out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env,
    177                                                   cwd=cwd)
    178         return cmd, out, err, rc
    179 
    180     def compileLinkTwoSteps(self, source_file, out=None, object_file=None,
    181                             flags=[], cwd=None):
    182         if not isinstance(source_file, str):
    183             raise TypeError('This function only accepts a single input file')
    184         if object_file is None:
    185             # Create, use and delete a temporary object file if none is given.
    186             with_fn = lambda: libcxx.util.guardedTempFilename(suffix='.o')
    187         else:
    188             # Otherwise wrap the filename in a context manager function.
    189             with_fn = lambda: libcxx.util.nullContext(object_file)
    190         with with_fn() as object_file:
    191             cc_cmd, cc_stdout, cc_stderr, rc = self.compile(
    192                 source_file, object_file, flags=flags, cwd=cwd)
    193             if rc != 0:
    194                 return cc_cmd, cc_stdout, cc_stderr, rc
    195 
    196             link_cmd, link_stdout, link_stderr, rc = self.link(
    197                 object_file, out=out, flags=flags, cwd=cwd)
    198             return (cc_cmd + ['&&'] + link_cmd, cc_stdout + link_stdout,
    199                     cc_stderr + link_stderr, rc)
    200 
    201     def dumpMacros(self, source_files=None, flags=[], cwd=None):
    202         if source_files is None:
    203             source_files = os.devnull
    204         flags = ['-dM'] + flags
    205         cmd, out, err, rc = self.preprocess(source_files, flags=flags, cwd=cwd)
    206         if rc != 0:
    207             return cmd, out, err, rc
    208         parsed_macros = {}
    209         lines = [l.strip() for l in out.split('\n') if l.strip()]
    210         for l in lines:
    211             assert l.startswith('#define ')
    212             l = l[len('#define '):]
    213             macro, _, value = l.partition(' ')
    214             parsed_macros[macro] = value
    215         return parsed_macros
    216 
    217     def getTriple(self):
    218         cmd = [self.path] + self.flags + ['-dumpmachine']
    219         return libcxx.util.capture(cmd).strip()
    220 
    221     def hasCompileFlag(self, flag):
    222         if isinstance(flag, list):
    223             flags = list(flag)
    224         else:
    225             flags = [flag]
    226         # Add -Werror to ensure that an unrecognized flag causes a non-zero
    227         # exit code. -Werror is supported on all known compiler types.
    228         if self.type is not None:
    229             flags += ['-Werror', '-fsyntax-only']
    230         cmd, out, err, rc = self.compile(os.devnull, out=os.devnull,
    231                                          flags=flags)
    232         return rc == 0
    233 
    234     def addFlagIfSupported(self, flag):
    235         if isinstance(flag, list):
    236             flags = list(flag)
    237         else:
    238             flags = [flag]
    239         if self.hasCompileFlag(flags):
    240             self.flags += flags
    241             return True
    242         else:
    243             return False
    244 
    245     def addCompileFlagIfSupported(self, flag):
    246         if isinstance(flag, list):
    247             flags = list(flag)
    248         else:
    249             flags = [flag]
    250         if self.hasCompileFlag(flags):
    251             self.compile_flags += flags
    252             return True
    253         else:
    254             return False
    255 
    256     def hasWarningFlag(self, flag):
    257         """
    258         hasWarningFlag - Test if the compiler supports a given warning flag.
    259         Unlike addCompileFlagIfSupported, this function detects when
    260         "-Wno-<warning>" flags are unsupported. If flag is a
    261         "-Wno-<warning>" GCC will not emit an unknown option diagnostic unless
    262         another error is triggered during compilation.
    263         """
    264         assert isinstance(flag, str)
    265         assert flag.startswith('-W')
    266         if not flag.startswith('-Wno-'):
    267             return self.hasCompileFlag(flag)
    268         flags = ['-Werror', flag]
    269         old_use_warnings = self.use_warnings
    270         self.useWarnings(False)
    271         cmd = self.compileCmd('-', os.devnull, flags)
    272         self.useWarnings(old_use_warnings)
    273         # Remove '-v' because it will cause the command line invocation
    274         # to be printed as part of the error output.
    275         # TODO(EricWF): Are there other flags we need to worry about?
    276         if '-v' in cmd:
    277             cmd.remove('-v')
    278         out, err, rc = libcxx.util.executeCommand(
    279             cmd, input=libcxx.util.to_bytes('#error\n'))
    280 
    281         assert rc != 0
    282         if flag in err:
    283             return False
    284         return True
    285 
    286     def addWarningFlagIfSupported(self, flag):
    287         if self.hasWarningFlag(flag):
    288             if flag not in self.warning_flags:
    289                 self.warning_flags += [flag]
    290             return True
    291         return False
    292