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 os
     11 import lit.util
     12 import libcxx.util
     13 
     14 
     15 class CXXCompiler(object):
     16     def __init__(self, path, flags=None, compile_flags=None, link_flags=None,
     17                  use_ccache=False):
     18         self.path = path
     19         self.flags = list(flags or [])
     20         self.compile_flags = list(compile_flags or [])
     21         self.link_flags = list(link_flags or [])
     22         self.use_ccache = use_ccache
     23         self.type = None
     24         self.version = None
     25         self._initTypeAndVersion()
     26 
     27     def _initTypeAndVersion(self):
     28         # Get compiler type and version
     29         macros = self.dumpMacros()
     30         if macros is None:
     31             return
     32         compiler_type = None
     33         major_ver = minor_ver = patchlevel = None
     34         if '__clang__' in macros.keys():
     35             compiler_type = 'clang'
     36             # Treat apple's llvm fork differently.
     37             if '__apple_build_version__' in macros.keys():
     38                 compiler_type = 'apple-clang'
     39             major_ver = macros['__clang_major__']
     40             minor_ver = macros['__clang_minor__']
     41             patchlevel = macros['__clang_patchlevel__']
     42         elif '__GNUC__' in macros.keys():
     43             compiler_type = 'gcc'
     44             major_ver = macros['__GNUC__']
     45             minor_ver = macros['__GNUC_MINOR__']
     46             patchlevel = macros['__GNUC_PATCHLEVEL__']
     47         self.type = compiler_type
     48         self.version = (major_ver, minor_ver, patchlevel)
     49 
     50     def _basicCmd(self, source_files, out, is_link=False, input_is_cxx=False):
     51         cmd = []
     52         if self.use_ccache and not is_link:
     53             cmd += ['ccache']
     54         cmd += [self.path]
     55         if out is not None:
     56             cmd += ['-o', out]
     57         if input_is_cxx:
     58             cmd += ['-x', 'c++']
     59         if isinstance(source_files, list):
     60             cmd += source_files
     61         elif isinstance(source_files, str):
     62             cmd += [source_files]
     63         else:
     64             raise TypeError('source_files must be a string or list')
     65         return cmd
     66 
     67     def preprocessCmd(self, source_files, out=None, flags=[]):
     68         cmd = self._basicCmd(source_files, out, input_is_cxx=True) + ['-E']
     69         cmd += self.flags + self.compile_flags + flags
     70         return cmd
     71 
     72     def compileCmd(self, source_files, out=None, flags=[]):
     73         cmd = self._basicCmd(source_files, out, input_is_cxx=True) + ['-c']
     74         cmd += self.flags + self.compile_flags + flags
     75         return cmd
     76 
     77     def linkCmd(self, source_files, out=None, flags=[]):
     78         cmd = self._basicCmd(source_files, out, is_link=True)
     79         cmd += self.flags + self.link_flags + flags
     80         return cmd
     81 
     82     def compileLinkCmd(self, source_files, out=None, flags=[]):
     83         cmd = self._basicCmd(source_files, out, is_link=True)
     84         cmd += self.flags + self.compile_flags + self.link_flags + flags
     85         return cmd
     86 
     87     def preprocess(self, source_files, out=None, flags=[], env=None, cwd=None):
     88         cmd = self.preprocessCmd(source_files, out, flags)
     89         out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd)
     90         return cmd, out, err, rc
     91 
     92     def compile(self, source_files, out=None, flags=[], env=None, cwd=None):
     93         cmd = self.compileCmd(source_files, out, flags)
     94         out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd)
     95         return cmd, out, err, rc
     96 
     97     def link(self, source_files, out=None, flags=[], env=None, cwd=None):
     98         cmd = self.linkCmd(source_files, out, flags)
     99         out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd)
    100         return cmd, out, err, rc
    101 
    102     def compileLink(self, source_files, out=None, flags=[], env=None,
    103                     cwd=None):
    104         cmd = self.compileLinkCmd(source_files, out, flags)
    105         out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd)
    106         return cmd, out, err, rc
    107 
    108     def compileLinkTwoSteps(self, source_file, out=None, object_file=None,
    109                             flags=[], env=None, cwd=None):
    110         if not isinstance(source_file, str):
    111             raise TypeError('This function only accepts a single input file')
    112         if object_file is None:
    113             # Create, use and delete a temporary object file if none is given.
    114             with_fn = lambda: libcxx.util.guardedTempFilename(suffix='.o')
    115         else:
    116             # Otherwise wrap the filename in a context manager function.
    117             with_fn = lambda: libcxx.util.nullContext(object_file)
    118         with with_fn() as object_file:
    119             cc_cmd, cc_stdout, cc_stderr, rc = self.compile(
    120                     source_file, object_file, flags=flags, env=env, cwd=cwd)
    121             if rc != 0:
    122                 return cc_cmd, cc_stdout, cc_stderr, rc
    123 
    124             link_cmd, link_stdout, link_stderr, rc = self.link(
    125                     object_file, out=out, flags=flags, env=env, cwd=cwd)
    126             return (cc_cmd + ['&&'] + link_cmd, cc_stdout + link_stdout,
    127                     cc_stderr + link_stderr, rc)
    128 
    129     def dumpMacros(self, source_files=None, flags=[], env=None, cwd=None):
    130         if source_files is None:
    131             source_files = os.devnull
    132         flags = ['-dM'] + flags
    133         cmd, out, err, rc = self.preprocess(source_files, flags=flags, env=env,
    134                                             cwd=cwd)
    135         if rc != 0:
    136             return None
    137         parsed_macros = {}
    138         lines = [l.strip() for l in out.split('\n') if l.strip()]
    139         for l in lines:
    140             assert l.startswith('#define ')
    141             l = l[len('#define '):]
    142             macro, _, value = l.partition(' ')
    143             parsed_macros[macro] = value
    144         return parsed_macros
    145 
    146     def getTriple(self):
    147         cmd = [self.path] + self.flags + ['-dumpmachine']
    148         return lit.util.capture(cmd).strip()
    149 
    150     def hasCompileFlag(self, flag):
    151         if isinstance(flag, list):
    152             flags = list(flag)
    153         else:
    154             flags = [flag]
    155         # Add -Werror to ensure that an unrecognized flag causes a non-zero
    156         # exit code. -Werror is supported on all known compiler types.
    157         if self.type is not None:
    158             flags += ['-Werror']
    159         cmd, out, err, rc = self.compile(os.devnull, out=os.devnull,
    160                                          flags=flags)
    161         return rc == 0
    162 
    163     def addCompileFlagIfSupported(self, flag):
    164         if isinstance(flag, list):
    165             flags = list(flag)
    166         else:
    167             flags = [flag]
    168         if self.hasCompileFlag(flags):
    169             self.compile_flags += flags
    170             return True
    171         else:
    172             return False
    173 
    174     def addWarningFlagIfSupported(self, flag):
    175         """
    176         addWarningFlagIfSupported - Add a warning flag if the compiler
    177         supports it. Unlike addCompileFlagIfSupported, this function detects
    178         when "-Wno-<warning>" flags are unsupported. If flag is a
    179         "-Wno-<warning>" GCC will not emit an unknown option diagnostic unless
    180         another error is triggered during compilation.
    181         """
    182         assert isinstance(flag, str)
    183         if not flag.startswith('-Wno-'):
    184             return self.addCompileFlagIfSupported(flag)
    185         flags = ['-Werror', flag]
    186         cmd = self.compileCmd('-', os.devnull, flags)
    187         # Remove '-v' because it will cause the command line invocation
    188         # to be printed as part of the error output.
    189         # TODO(EricWF): Are there other flags we need to worry about?
    190         if '-v' in cmd:
    191             cmd.remove('-v')
    192         out, err, rc = lit.util.executeCommand(cmd, input='#error\n')
    193         assert rc != 0
    194         if flag in err:
    195             return False
    196         self.compile_flags += [flag]
    197         return True
    198