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 10import platform 11import os 12import libcxx.util 13 14 15class 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