diff options
Diffstat (limited to 'utils/libcxx/compiler.py')
-rw-r--r-- | utils/libcxx/compiler.py | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/utils/libcxx/compiler.py b/utils/libcxx/compiler.py new file mode 100644 index 0000000000000..983bc26c80bd2 --- /dev/null +++ b/utils/libcxx/compiler.py @@ -0,0 +1,290 @@ +#===----------------------------------------------------------------------===## +# +# The LLVM Compiler Infrastructure +# +# This file is dual licensed under the MIT and the University of Illinois Open +# Source Licenses. See LICENSE.TXT for details. +# +#===----------------------------------------------------------------------===## + +import platform +import os +import libcxx.util + + +class CXXCompiler(object): + CM_Default = 0 + CM_PreProcess = 1 + CM_Compile = 2 + CM_Link = 3 + + def __init__(self, path, flags=None, compile_flags=None, link_flags=None, + warning_flags=None, verify_supported=None, + verify_flags=None, use_verify=False, + modules_flags=None, use_modules=False, + use_ccache=False, use_warnings=False, compile_env=None, + cxx_type=None, cxx_version=None): + self.path = path + self.flags = list(flags or []) + self.compile_flags = list(compile_flags or []) + self.link_flags = list(link_flags or []) + self.warning_flags = list(warning_flags or []) + self.verify_supported = verify_supported + self.use_verify = use_verify + self.verify_flags = list(verify_flags or []) + assert not use_verify or verify_supported + assert not use_verify or verify_flags is not None + self.modules_flags = list(modules_flags or []) + self.use_modules = use_modules + assert not use_modules or modules_flags is not None + self.use_ccache = use_ccache + self.use_warnings = use_warnings + if compile_env is not None: + self.compile_env = dict(compile_env) + else: + self.compile_env = None + self.type = cxx_type + self.version = cxx_version + if self.type is None or self.version is None: + self._initTypeAndVersion() + + def isVerifySupported(self): + if self.verify_supported is None: + self.verify_supported = self.hasCompileFlag(['-Xclang', + '-verify-ignore-unexpected']) + if self.verify_supported: + self.verify_flags = [ + '-Xclang', '-verify', + '-Xclang', '-verify-ignore-unexpected=note', + '-ferror-limit=1024' + ] + return self.verify_supported + + def useVerify(self, value=True): + self.use_verify = value + assert not self.use_verify or self.verify_flags is not None + + def useModules(self, value=True): + self.use_modules = value + assert not self.use_modules or self.modules_flags is not None + + def useCCache(self, value=True): + self.use_ccache = value + + def useWarnings(self, value=True): + self.use_warnings = value + + def _initTypeAndVersion(self): + # Get compiler type and version + macros = self.dumpMacros() + if macros is None: + return + compiler_type = None + major_ver = minor_ver = patchlevel = None + if '__clang__' in macros.keys(): + compiler_type = 'clang' + # Treat apple's llvm fork differently. + if '__apple_build_version__' in macros.keys(): + compiler_type = 'apple-clang' + major_ver = macros['__clang_major__'] + minor_ver = macros['__clang_minor__'] + patchlevel = macros['__clang_patchlevel__'] + elif '__GNUC__' in macros.keys(): + compiler_type = 'gcc' + major_ver = macros['__GNUC__'] + minor_ver = macros['__GNUC_MINOR__'] + patchlevel = macros['__GNUC_PATCHLEVEL__'] + self.type = compiler_type + self.version = (major_ver, minor_ver, patchlevel) + + def _basicCmd(self, source_files, out, mode=CM_Default, flags=[], + input_is_cxx=False): + cmd = [] + if self.use_ccache \ + and not mode == self.CM_Link \ + and not mode == self.CM_PreProcess: + cmd += ['ccache'] + cmd += [self.path] + if out is not None: + cmd += ['-o', out] + if input_is_cxx: + cmd += ['-x', 'c++'] + if isinstance(source_files, list): + cmd += source_files + elif isinstance(source_files, str): + cmd += [source_files] + else: + raise TypeError('source_files must be a string or list') + if mode == self.CM_PreProcess: + cmd += ['-E'] + elif mode == self.CM_Compile: + cmd += ['-c'] + cmd += self.flags + if self.use_verify: + cmd += self.verify_flags + assert mode in [self.CM_Default, self.CM_Compile] + if self.use_modules: + cmd += self.modules_flags + if mode != self.CM_Link: + cmd += self.compile_flags + if self.use_warnings: + cmd += self.warning_flags + if mode != self.CM_PreProcess and mode != self.CM_Compile: + cmd += self.link_flags + cmd += flags + return cmd + + def preprocessCmd(self, source_files, out=None, flags=[]): + return self._basicCmd(source_files, out, flags=flags, + mode=self.CM_PreProcess, + input_is_cxx=True) + + def compileCmd(self, source_files, out=None, flags=[]): + return self._basicCmd(source_files, out, flags=flags, + mode=self.CM_Compile, + input_is_cxx=True) + ['-c'] + + def linkCmd(self, source_files, out=None, flags=[]): + return self._basicCmd(source_files, out, flags=flags, + mode=self.CM_Link) + + def compileLinkCmd(self, source_files, out=None, flags=[]): + return self._basicCmd(source_files, out, flags=flags) + + def preprocess(self, source_files, out=None, flags=[], cwd=None): + cmd = self.preprocessCmd(source_files, out, flags) + out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env, + cwd=cwd) + return cmd, out, err, rc + + def compile(self, source_files, out=None, flags=[], cwd=None): + cmd = self.compileCmd(source_files, out, flags) + out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env, + cwd=cwd) + return cmd, out, err, rc + + def link(self, source_files, out=None, flags=[], cwd=None): + cmd = self.linkCmd(source_files, out, flags) + out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env, + cwd=cwd) + return cmd, out, err, rc + + def compileLink(self, source_files, out=None, flags=[], + cwd=None): + cmd = self.compileLinkCmd(source_files, out, flags) + out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env, + cwd=cwd) + return cmd, out, err, rc + + def compileLinkTwoSteps(self, source_file, out=None, object_file=None, + flags=[], cwd=None): + if not isinstance(source_file, str): + raise TypeError('This function only accepts a single input file') + if object_file is None: + # Create, use and delete a temporary object file if none is given. + with_fn = lambda: libcxx.util.guardedTempFilename(suffix='.o') + else: + # Otherwise wrap the filename in a context manager function. + with_fn = lambda: libcxx.util.nullContext(object_file) + with with_fn() as object_file: + cc_cmd, cc_stdout, cc_stderr, rc = self.compile( + source_file, object_file, flags=flags, cwd=cwd) + if rc != 0: + return cc_cmd, cc_stdout, cc_stderr, rc + + link_cmd, link_stdout, link_stderr, rc = self.link( + object_file, out=out, flags=flags, cwd=cwd) + return (cc_cmd + ['&&'] + link_cmd, cc_stdout + link_stdout, + cc_stderr + link_stderr, rc) + + def dumpMacros(self, source_files=None, flags=[], cwd=None): + if source_files is None: + source_files = os.devnull + flags = ['-dM'] + flags + cmd, out, err, rc = self.preprocess(source_files, flags=flags, cwd=cwd) + if rc != 0: + return None + parsed_macros = {} + lines = [l.strip() for l in out.split('\n') if l.strip()] + for l in lines: + assert l.startswith('#define ') + l = l[len('#define '):] + macro, _, value = l.partition(' ') + parsed_macros[macro] = value + return parsed_macros + + def getTriple(self): + cmd = [self.path] + self.flags + ['-dumpmachine'] + return libcxx.util.capture(cmd).strip() + + def hasCompileFlag(self, flag): + if isinstance(flag, list): + flags = list(flag) + else: + flags = [flag] + # Add -Werror to ensure that an unrecognized flag causes a non-zero + # exit code. -Werror is supported on all known compiler types. + if self.type is not None: + flags += ['-Werror', '-fsyntax-only'] + cmd, out, err, rc = self.compile(os.devnull, out=os.devnull, + flags=flags) + return rc == 0 + + def addFlagIfSupported(self, flag): + if isinstance(flag, list): + flags = list(flag) + else: + flags = [flag] + if self.hasCompileFlag(flags): + self.flags += flags + return True + else: + return False + + def addCompileFlagIfSupported(self, flag): + if isinstance(flag, list): + flags = list(flag) + else: + flags = [flag] + if self.hasCompileFlag(flags): + self.compile_flags += flags + return True + else: + return False + + def hasWarningFlag(self, flag): + """ + hasWarningFlag - Test if the compiler supports a given warning flag. + Unlike addCompileFlagIfSupported, this function detects when + "-Wno-<warning>" flags are unsupported. If flag is a + "-Wno-<warning>" GCC will not emit an unknown option diagnostic unless + another error is triggered during compilation. + """ + assert isinstance(flag, str) + assert flag.startswith('-W') + if not flag.startswith('-Wno-'): + return self.hasCompileFlag(flag) + flags = ['-Werror', flag] + old_use_warnings = self.use_warnings + self.useWarnings(False) + cmd = self.compileCmd('-', os.devnull, flags) + self.useWarnings(old_use_warnings) + # Remove '-v' because it will cause the command line invocation + # to be printed as part of the error output. + # TODO(EricWF): Are there other flags we need to worry about? + if '-v' in cmd: + cmd.remove('-v') + out, err, rc = libcxx.util.executeCommand( + cmd, input=libcxx.util.to_bytes('#error\n')) + + assert rc != 0 + if flag in err: + return False + return True + + def addWarningFlagIfSupported(self, flag): + if self.hasWarningFlag(flag): + if flag not in self.warning_flags: + self.warning_flags += [flag] + return True + return False |