1import os 2import lit.util 3import libcxx.util 4 5 6class CXXCompiler(object): 7 def __init__(self, path, flags=None, compile_flags=None, link_flags=None, 8 use_ccache=False): 9 self.path = path 10 self.flags = list(flags or []) 11 self.compile_flags = list(compile_flags or []) 12 self.link_flags = list(link_flags or []) 13 self.use_ccache = use_ccache 14 self.type = None 15 self.version = None 16 self._initTypeAndVersion() 17 18 def _initTypeAndVersion(self): 19 # Get compiler type and version 20 macros = self.dumpMacros() 21 if macros is None: 22 return 23 compiler_type = None 24 major_ver = minor_ver = patchlevel = None 25 if '__clang__' in macros.keys(): 26 compiler_type = 'clang' 27 # Treat apple's llvm fork differently. 28 if '__apple_build_version__' in macros.keys(): 29 compiler_type = 'apple-clang' 30 major_ver = macros['__clang_major__'] 31 minor_ver = macros['__clang_minor__'] 32 patchlevel = macros['__clang_patchlevel__'] 33 elif '__GNUC__' in macros.keys(): 34 compiler_type = 'gcc' 35 major_ver = macros['__GNUC__'] 36 minor_ver = macros['__GNUC_MINOR__'] 37 patchlevel = macros['__GNUC_PATCHLEVEL__'] 38 self.type = compiler_type 39 self.version = (major_ver, minor_ver, patchlevel) 40 41 def _basicCmd(self, source_files, out, is_link=False, input_is_cxx=False): 42 cmd = [] 43 if self.use_ccache and not is_link: 44 cmd += ['ccache'] 45 cmd += [self.path] 46 if out is not None: 47 cmd += ['-o', out] 48 if input_is_cxx: 49 cmd += ['-x', 'c++'] 50 if isinstance(source_files, list): 51 cmd += source_files 52 elif isinstance(source_files, str): 53 cmd += [source_files] 54 else: 55 raise TypeError('source_files must be a string or list') 56 return cmd 57 58 def preprocessCmd(self, source_files, out=None, flags=[]): 59 cmd = self._basicCmd(source_files, out, input_is_cxx=True) + ['-E'] 60 cmd += self.flags + self.compile_flags + flags 61 return cmd 62 63 def compileCmd(self, source_files, out=None, flags=[]): 64 cmd = self._basicCmd(source_files, out, input_is_cxx=True) + ['-c'] 65 cmd += self.flags + self.compile_flags + flags 66 return cmd 67 68 def linkCmd(self, source_files, out=None, flags=[]): 69 cmd = self._basicCmd(source_files, out, is_link=True) 70 cmd += self.flags + self.link_flags + flags 71 return cmd 72 73 def compileLinkCmd(self, source_files, out=None, flags=[]): 74 cmd = self._basicCmd(source_files, out, is_link=True) 75 cmd += self.flags + self.compile_flags + self.link_flags + flags 76 return cmd 77 78 def preprocess(self, source_files, out=None, flags=[], env=None, cwd=None): 79 cmd = self.preprocessCmd(source_files, out, flags) 80 out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd) 81 return cmd, out, err, rc 82 83 def compile(self, source_files, out=None, flags=[], env=None, cwd=None): 84 cmd = self.compileCmd(source_files, out, flags) 85 out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd) 86 return cmd, out, err, rc 87 88 def link(self, source_files, out=None, flags=[], env=None, cwd=None): 89 cmd = self.linkCmd(source_files, out, flags) 90 out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd) 91 return cmd, out, err, rc 92 93 def compileLink(self, source_files, out=None, flags=[], env=None, 94 cwd=None): 95 cmd = self.compileLinkCmd(source_files, out, flags) 96 out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd) 97 return cmd, out, err, rc 98 99 def compileLinkTwoSteps(self, source_file, out=None, object_file=None, 100 flags=[], env=None, cwd=None): 101 if not isinstance(source_file, str): 102 raise TypeError('This function only accepts a single input file') 103 if object_file is None: 104 # Create, use and delete a temporary object file if none is given. 105 with_fn = lambda: libcxx.util.guardedTempFilename(suffix='.o') 106 else: 107 # Otherwise wrap the filename in a context manager function. 108 with_fn = lambda: libcxx.util.nullContext(object_file) 109 with with_fn() as object_file: 110 cc_cmd, cc_stdout, cc_stderr, rc = self.compile( 111 source_file, object_file, flags=flags, env=env, cwd=cwd) 112 if rc != 0: 113 return cc_cmd, cc_stdout, cc_stderr, rc 114 115 link_cmd, link_stdout, link_stderr, rc = self.link( 116 object_file, out=out, flags=flags, env=env, cwd=cwd) 117 return (cc_cmd + ['&&'] + link_cmd, cc_stdout + link_stdout, 118 cc_stderr + link_stderr, rc) 119 120 def dumpMacros(self, source_files=None, flags=[], env=None, cwd=None): 121 if source_files is None: 122 source_files = os.devnull 123 flags = ['-dM'] + flags 124 cmd, out, err, rc = self.preprocess(source_files, flags=flags, env=env, 125 cwd=cwd) 126 if rc != 0: 127 return None 128 parsed_macros = {} 129 lines = [l.strip() for l in out.split('\n') if l.strip()] 130 for l in lines: 131 assert l.startswith('#define ') 132 l = l[len('#define '):] 133 macro, _, value = l.partition(' ') 134 parsed_macros[macro] = value 135 return parsed_macros 136 137 def getTriple(self): 138 cmd = [self.path] + self.flags + ['-dumpmachine'] 139 return lit.util.capture(cmd).strip() 140 141 def hasCompileFlag(self, flag): 142 if isinstance(flag, list): 143 flags = list(flag) 144 else: 145 flags = [flag] 146 # Add -Werror to ensure that an unrecognized flag causes a non-zero 147 # exit code. -Werror is supported on all known compiler types. 148 if self.type is not None: 149 flags += ['-Werror'] 150 cmd, out, err, rc = self.compile(os.devnull, out=os.devnull, 151 flags=flags) 152 return rc == 0 153 154 def addCompileFlagIfSupported(self, flag): 155 if isinstance(flag, list): 156 flags = list(flag) 157 else: 158 flags = [flag] 159 if self.hasCompileFlag(flags): 160 self.compile_flags += flags 161 return True 162 else: 163 return False 164