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.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, exec_path=None, flags=[], cwd=None): 168 cmd = self.linkCmd(source_files, exec_path, flags) 169 out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env, 170 cwd=cwd) 171 cs_cmd, cs_out, cs_err, cs_rc = self.codesign(exec_path, cwd) 172 if cs_rc != 0: 173 return cs_cmd, cs_out, cs_err, cs_rc 174 return cmd, out, err, rc 175 176 def compileLink(self, source_files, exec_path=None, flags=[], 177 cwd=None): 178 cmd = self.compileLinkCmd(source_files, exec_path, flags) 179 out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env, 180 cwd=cwd) 181 cs_cmd, cs_out, cs_err, cs_rc = self.codesign(exec_path, cwd) 182 if cs_rc != 0: 183 return cs_cmd, cs_out, cs_err, cs_rc 184 return cmd, out, err, rc 185 186 def codesign(self, exec_path, cwd=None): 187 null_op = [], '', '', 0 188 if not exec_path: 189 return null_op 190 codesign_ident = self.libcxx_config.get_lit_conf('llvm_codesign_identity', '') 191 if not codesign_ident: 192 return null_op 193 cmd = ['xcrun', 'codesign', '-s', codesign_ident, exec_path] 194 out, err, rc = libcxx.util.executeCommand(cmd, cwd=cwd) 195 return cmd, out, err, rc 196 197 def compileLinkTwoSteps(self, source_file, out=None, object_file=None, 198 flags=[], cwd=None): 199 if not isinstance(source_file, str): 200 raise TypeError('This function only accepts a single input file') 201 if object_file is None: 202 # Create, use and delete a temporary object file if none is given. 203 with_fn = lambda: libcxx.util.guardedTempFilename(suffix='.o') 204 else: 205 # Otherwise wrap the filename in a context manager function. 206 with_fn = lambda: libcxx.util.nullContext(object_file) 207 with with_fn() as object_file: 208 cc_cmd, cc_stdout, cc_stderr, rc = self.compile( 209 source_file, object_file, flags=flags, cwd=cwd) 210 if rc != 0: 211 return cc_cmd, cc_stdout, cc_stderr, rc 212 213 link_cmd, link_stdout, link_stderr, rc = self.link( 214 object_file, exec_path=out, flags=flags, cwd=cwd) 215 return (cc_cmd + ['&&'] + link_cmd, cc_stdout + link_stdout, 216 cc_stderr + link_stderr, rc) 217 218 def dumpMacros(self, source_files=None, flags=[], cwd=None): 219 if source_files is None: 220 source_files = os.devnull 221 flags = ['-dM'] + flags 222 cmd, out, err, rc = self.preprocess(source_files, flags=flags, cwd=cwd) 223 if rc != 0: 224 return cmd, out, err, rc 225 parsed_macros = {} 226 lines = [l.strip() for l in out.split('\n') if l.strip()] 227 for l in lines: 228 assert l.startswith('#define ') 229 l = l[len('#define '):] 230 macro, _, value = l.partition(' ') 231 parsed_macros[macro] = value 232 return parsed_macros 233 234 def getTriple(self): 235 cmd = [self.path] + self.flags + ['-dumpmachine'] 236 return libcxx.util.capture(cmd).strip() 237 238 def hasCompileFlag(self, flag): 239 if isinstance(flag, list): 240 flags = list(flag) 241 else: 242 flags = [flag] 243 # Add -Werror to ensure that an unrecognized flag causes a non-zero 244 # exit code. -Werror is supported on all known compiler types. 245 if self.type is not None: 246 flags += ['-Werror', '-fsyntax-only'] 247 cmd, out, err, rc = self.compile(os.devnull, out=os.devnull, 248 flags=flags) 249 return rc == 0 250 251 def addFlagIfSupported(self, flag): 252 if isinstance(flag, list): 253 flags = list(flag) 254 else: 255 flags = [flag] 256 if self.hasCompileFlag(flags): 257 self.flags += flags 258 return True 259 else: 260 return False 261 262 def addCompileFlagIfSupported(self, flag): 263 if isinstance(flag, list): 264 flags = list(flag) 265 else: 266 flags = [flag] 267 if self.hasCompileFlag(flags): 268 self.compile_flags += flags 269 return True 270 else: 271 return False 272 273 def hasWarningFlag(self, flag): 274 """ 275 hasWarningFlag - Test if the compiler supports a given warning flag. 276 Unlike addCompileFlagIfSupported, this function detects when 277 "-Wno-<warning>" flags are unsupported. If flag is a 278 "-Wno-<warning>" GCC will not emit an unknown option diagnostic unless 279 another error is triggered during compilation. 280 """ 281 assert isinstance(flag, str) 282 assert flag.startswith('-W') 283 if not flag.startswith('-Wno-'): 284 return self.hasCompileFlag(flag) 285 flags = ['-Werror', flag] 286 old_use_warnings = self.use_warnings 287 self.useWarnings(False) 288 cmd = self.compileCmd('-', os.devnull, flags) 289 self.useWarnings(old_use_warnings) 290 # Remove '-v' because it will cause the command line invocation 291 # to be printed as part of the error output. 292 # TODO(EricWF): Are there other flags we need to worry about? 293 if '-v' in cmd: 294 cmd.remove('-v') 295 out, err, rc = libcxx.util.executeCommand( 296 cmd, input=libcxx.util.to_bytes('#error\n')) 297 298 assert rc != 0 299 if flag in err: 300 return False 301 return True 302 303 def addWarningFlagIfSupported(self, flag): 304 if self.hasWarningFlag(flag): 305 if flag not in self.warning_flags: 306 self.warning_flags += [flag] 307 return True 308 return False 309