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