diff options
Diffstat (limited to '')
-rw-r--r-- | mesonbuild/compilers/detect.py | 1317 |
1 files changed, 1317 insertions, 0 deletions
diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py new file mode 100644 index 0000000..1f37833 --- /dev/null +++ b/mesonbuild/compilers/detect.py @@ -0,0 +1,1317 @@ +# Copyright 2012-2022 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations + +from ..mesonlib import ( + MesonException, EnvironmentException, MachineChoice, join_args, + search_version, is_windows, Popen_safe, windows_proof_rm, +) +from ..envconfig import BinaryTable +from .. import mlog + +from ..linkers import guess_win_linker, guess_nix_linker + +import subprocess +import platform +import re +import shutil +import tempfile +import os +import typing as T + +if T.TYPE_CHECKING: + from .compilers import Compiler + from .c import CCompiler + from .cpp import CPPCompiler + from .fortran import FortranCompiler + from .rust import RustCompiler + from ..linkers import StaticLinker + from ..environment import Environment + from ..programs import ExternalProgram + + +# Default compilers and linkers +# ============================= + +defaults: T.Dict[str, T.List[str]] = {} + +# List of potential compilers. +if is_windows(): + # Intel C and C++ compiler is icl on Windows, but icc and icpc elsewhere. + # Search for icl before cl, since Intel "helpfully" provides a + # cl.exe that returns *exactly the same thing* that microsofts + # cl.exe does, and if icl is present, it's almost certainly what + # you want. + defaults['c'] = ['icl', 'cl', 'cc', 'gcc', 'clang', 'clang-cl', 'pgcc'] + # There is currently no pgc++ for Windows, only for Mac and Linux. + defaults['cpp'] = ['icl', 'cl', 'c++', 'g++', 'clang++', 'clang-cl'] + defaults['fortran'] = ['ifort', 'gfortran', 'flang', 'pgfortran', 'g95'] + # Clang and clang++ are valid, but currently unsupported. + defaults['objc'] = ['cc', 'gcc'] + defaults['objcpp'] = ['c++', 'g++'] + defaults['cs'] = ['csc', 'mcs'] +else: + if platform.machine().lower() == 'e2k': + defaults['c'] = ['cc', 'gcc', 'lcc', 'clang'] + defaults['cpp'] = ['c++', 'g++', 'l++', 'clang++'] + defaults['objc'] = ['clang'] + defaults['objcpp'] = ['clang++'] + else: + defaults['c'] = ['cc', 'gcc', 'clang', 'nvc', 'pgcc', 'icc', 'icx'] + defaults['cpp'] = ['c++', 'g++', 'clang++', 'nvc++', 'pgc++', 'icpc', 'icpx'] + defaults['objc'] = ['cc', 'gcc', 'clang'] + defaults['objcpp'] = ['c++', 'g++', 'clang++'] + defaults['fortran'] = ['gfortran', 'flang', 'nvfortran', 'pgfortran', 'ifort', 'ifx', 'g95'] + defaults['cs'] = ['mcs', 'csc'] +defaults['d'] = ['ldc2', 'ldc', 'gdc', 'dmd'] +defaults['java'] = ['javac'] +defaults['cuda'] = ['nvcc'] +defaults['rust'] = ['rustc'] +defaults['swift'] = ['swiftc'] +defaults['vala'] = ['valac'] +defaults['cython'] = ['cython', 'cython3'] # Official name is cython, but Debian renamed it to cython3. +defaults['static_linker'] = ['ar', 'gar'] +defaults['strip'] = ['strip'] +defaults['vs_static_linker'] = ['lib'] +defaults['clang_cl_static_linker'] = ['llvm-lib'] +defaults['cuda_static_linker'] = ['nvlink'] +defaults['gcc_static_linker'] = ['gcc-ar'] +defaults['clang_static_linker'] = ['llvm-ar'] +defaults['nasm'] = ['nasm', 'yasm'] + + +def compiler_from_language(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Optional[Compiler]: + lang_map: T.Dict[str, T.Callable[['Environment', MachineChoice], Compiler]] = { + 'c': detect_c_compiler, + 'cpp': detect_cpp_compiler, + 'objc': detect_objc_compiler, + 'cuda': detect_cuda_compiler, + 'objcpp': detect_objcpp_compiler, + 'java': detect_java_compiler, + 'cs': detect_cs_compiler, + 'vala': detect_vala_compiler, + 'd': detect_d_compiler, + 'rust': detect_rust_compiler, + 'fortran': detect_fortran_compiler, + 'swift': detect_swift_compiler, + 'cython': detect_cython_compiler, + 'nasm': detect_nasm_compiler, + 'masm': detect_masm_compiler, + } + return lang_map[lang](env, for_machine) if lang in lang_map else None + +def detect_compiler_for(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Optional[Compiler]: + comp = compiler_from_language(env, lang, for_machine) + if comp is not None: + assert comp.for_machine == for_machine + env.coredata.process_new_compiler(lang, comp, env) + return comp + + +# Helpers +# ======= + +def _get_compilers(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Tuple[T.List[T.List[str]], T.List[str], T.Optional['ExternalProgram']]: + ''' + The list of compilers is detected in the exact same way for + C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. + ''' + value = env.lookup_binary_entry(for_machine, lang) + if value is not None: + comp, ccache = BinaryTable.parse_entry(value) + # Return value has to be a list of compiler 'choices' + compilers = [comp] + else: + if not env.machines.matches_build_machine(for_machine): + raise EnvironmentException(f'{lang!r} compiler binary not defined in cross or native file') + compilers = [[x] for x in defaults[lang]] + ccache = BinaryTable.detect_compiler_cache() + + if env.machines.matches_build_machine(for_machine): + exe_wrap: T.Optional[ExternalProgram] = None + else: + exe_wrap = env.get_exe_wrapper() + + return compilers, ccache, exe_wrap + +def _handle_exceptions( + exceptions: T.Mapping[str, T.Union[Exception, str]], + binaries: T.List[T.List[str]], + bintype: str = 'compiler') -> T.NoReturn: + errmsg = f'Unknown {bintype}(s): {binaries}' + if exceptions: + errmsg += '\nThe following exception(s) were encountered:' + for c, e in exceptions.items(): + errmsg += f'\nRunning `{c}` gave "{e}"' + raise EnvironmentException(errmsg) + + +# Linker specific +# =============== + +def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker: + from . import d + from ..linkers import linkers + linker = env.lookup_binary_entry(compiler.for_machine, 'ar') + if linker is not None: + trials = [linker] + else: + default_linkers = [[l] for l in defaults['static_linker']] + if compiler.language == 'cuda': + trials = [defaults['cuda_static_linker']] + default_linkers + elif compiler.get_argument_syntax() == 'msvc': + trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker']] + elif compiler.id == 'gcc': + # Use gcc-ar if available; needed for LTO + trials = [defaults['gcc_static_linker']] + default_linkers + elif compiler.id == 'clang': + # Use llvm-ar if available; needed for LTO + trials = [defaults['clang_static_linker']] + default_linkers + elif compiler.language == 'd': + # Prefer static linkers over linkers used by D compilers + if is_windows(): + trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker'], compiler.get_linker_exelist()] + else: + trials = default_linkers + elif compiler.id == 'intel-cl' and compiler.language == 'c': # why not cpp? Is this a bug? + # Intel has it's own linker that acts like microsoft's lib + trials = [['xilib']] + elif is_windows() and compiler.id == 'pgi': # this handles cpp / nvidia HPC, in addition to just c/fortran + trials = [['ar']] # For PGI on Windows, "ar" is just a wrapper calling link/lib. + else: + trials = default_linkers + popen_exceptions = {} + for linker in trials: + linker_name = os.path.basename(linker[0]) + + if any(os.path.basename(x) in {'lib', 'lib.exe', 'llvm-lib', 'llvm-lib.exe', 'xilib', 'xilib.exe'} for x in linker): + arg = '/?' + elif linker_name in {'ar2000', 'ar2000.exe', 'ar430', 'ar430.exe', 'armar', 'armar.exe'}: + arg = '?' + else: + arg = '--version' + try: + p, out, err = Popen_safe(linker + [arg]) + except OSError as e: + popen_exceptions[join_args(linker + [arg])] = e + continue + if "xilib: executing 'lib'" in err: + return linkers.IntelVisualStudioLinker(linker, getattr(compiler, 'machine', None)) + if '/OUT:' in out.upper() or '/OUT:' in err.upper(): + return linkers.VisualStudioLinker(linker, getattr(compiler, 'machine', None)) + if 'ar-Error-Unknown switch: --version' in err: + return linkers.PGIStaticLinker(linker) + if p.returncode == 0 and 'armar' in linker_name: + return linkers.ArmarLinker(linker) + if 'DMD32 D Compiler' in out or 'DMD64 D Compiler' in out: + assert isinstance(compiler, d.DCompiler) + return linkers.DLinker(linker, compiler.arch) + if 'LDC - the LLVM D compiler' in out: + assert isinstance(compiler, d.DCompiler) + return linkers.DLinker(linker, compiler.arch, rsp_syntax=compiler.rsp_file_syntax()) + if 'GDC' in out and ' based on D ' in out: + assert isinstance(compiler, d.DCompiler) + return linkers.DLinker(linker, compiler.arch) + if err.startswith('Renesas') and 'rlink' in linker_name: + return linkers.CcrxLinker(linker) + if out.startswith('GNU ar') and 'xc16-ar' in linker_name: + return linkers.Xc16Linker(linker) + if 'Texas Instruments Incorporated' in out: + if 'ar2000' in linker_name: + return linkers.C2000Linker(linker) + else: + return linkers.TILinker(linker) + if out.startswith('The CompCert'): + return linkers.CompCertLinker(linker) + if p.returncode == 0: + return linkers.ArLinker(compiler.for_machine, linker) + if p.returncode == 1 and err.startswith('usage'): # OSX + return linkers.AppleArLinker(compiler.for_machine, linker) + if p.returncode == 1 and err.startswith('Usage'): # AIX + return linkers.AIXArLinker(linker) + if p.returncode == 1 and err.startswith('ar: bad option: --'): # Solaris + return linkers.ArLinker(compiler.for_machine, linker) + _handle_exceptions(popen_exceptions, trials, 'linker') + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + + +# Compilers +# ========= + + +def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: MachineChoice, *, override_compiler: T.Optional[T.List[str]] = None) -> Compiler: + """Shared implementation for finding the C or C++ compiler to use. + + the override_compiler option is provided to allow compilers which use + the compiler (GCC or Clang usually) as their shared linker, to find + the linker they need. + """ + from . import c, cpp + from ..linkers import linkers + popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} + compilers, ccache, exe_wrap = _get_compilers(env, lang, for_machine) + if override_compiler is not None: + compilers = [override_compiler] + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + cls: T.Union[T.Type[CCompiler], T.Type[CPPCompiler]] + + for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] + compiler_name = os.path.basename(compiler[0]) + + if any(os.path.basename(x) in {'cl', 'cl.exe', 'clang-cl', 'clang-cl.exe'} for x in compiler): + # Watcom C provides it's own cl.exe clone that mimics an older + # version of Microsoft's compiler. Since Watcom's cl.exe is + # just a wrapper, we skip using it if we detect its presence + # so as not to confuse Meson when configuring for MSVC. + # + # Additionally the help text of Watcom's cl.exe is paged, and + # the binary will not exit without human intervention. In + # practice, Meson will block waiting for Watcom's cl.exe to + # exit, which requires user input and thus will never exit. + if 'WATCOM' in os.environ: + def sanitize(p: str) -> str: + return os.path.normcase(os.path.abspath(p)) + + watcom_cls = [sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl')), + sanitize(os.path.join(os.environ['WATCOM'], 'BINNT', 'cl.exe')), + sanitize(os.path.join(os.environ['WATCOM'], 'BINNT64', 'cl')), + sanitize(os.path.join(os.environ['WATCOM'], 'BINNT64', 'cl.exe'))] + found_cl = sanitize(shutil.which('cl')) + if found_cl in watcom_cls: + mlog.debug('Skipping unsupported cl.exe clone at:', found_cl) + continue + arg = '/?' + elif 'armcc' in compiler_name: + arg = '--vsn' + elif 'ccrx' in compiler_name: + arg = '-v' + elif 'xc16' in compiler_name: + arg = '--version' + elif 'ccomp' in compiler_name: + arg = '-version' + elif compiler_name in {'cl2000', 'cl2000.exe', 'cl430', 'cl430.exe', 'armcl', 'armcl.exe'}: + # TI compiler + arg = '-version' + elif compiler_name in {'icl', 'icl.exe'}: + # if you pass anything to icl you get stuck in a pager + arg = '' + else: + arg = '--version' + + cmd = compiler + [arg] + try: + mlog.debug('-----') + mlog.debug(f'Detecting compiler via: {join_args(cmd)}') + p, out, err = Popen_safe(cmd) + mlog.debug(f'compiler returned {p}') + mlog.debug(f'compiler stdout:\n{out}') + mlog.debug(f'compiler stderr:\n{err}') + except OSError as e: + popen_exceptions[join_args(cmd)] = e + continue + + if 'ccrx' in compiler_name: + out = err + + full_version = out.split('\n', 1)[0] + version = search_version(out) + + guess_gcc_or_lcc: T.Optional[str] = None + if 'Free Software Foundation' in out or 'xt-' in out: + guess_gcc_or_lcc = 'gcc' + if 'e2k' in out and 'lcc' in out: + guess_gcc_or_lcc = 'lcc' + if 'Microchip Technology' in out: + # this output has "Free Software Foundation" in its version + guess_gcc_or_lcc = None + + if guess_gcc_or_lcc: + defines = _get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[join_args(compiler)] = 'no pre-processor defines' + continue + + if guess_gcc_or_lcc == 'lcc': + version = _get_lcc_version_from_defines(defines) + cls = c.ElbrusCCompiler if lang == 'c' else cpp.ElbrusCPPCompiler + else: + version = _get_gnu_version_from_defines(defines) + cls = c.GnuCCompiler if lang == 'c' else cpp.GnuCPPCompiler + + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + + return cls( + ccache, compiler, version, for_machine, is_cross, + info, exe_wrap, defines=defines, full_version=full_version, + linker=linker) + + if 'Emscripten' in out: + cls = c.EmscriptenCCompiler if lang == 'c' else cpp.EmscriptenCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + + # emcc requires a file input in order to pass arguments to the + # linker. It'll exit with an error code, but still print the + # linker version. + with tempfile.NamedTemporaryFile(suffix='.c') as f: + cmd = compiler + [cls.LINKER_PREFIX + "--version", f.name] + _, o, _ = Popen_safe(cmd) + + linker = linkers.WASMDynamicLinker( + compiler, for_machine, cls.LINKER_PREFIX, + [], version=search_version(o)) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, linker=linker, full_version=full_version) + + if 'Arm C/C++/Fortran Compiler' in out: + arm_ver_match = re.search(r'version (\d+)\.(\d+)\.?(\d+)? \(build number (\d+)\)', out) + assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None + version = '.'.join([x for x in arm_ver_match.groups() if x is not None]) + if lang == 'c': + cls = c.ArmLtdClangCCompiler + elif lang == 'cpp': + cls = cpp.ArmLtdClangCPPCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, linker=linker) + if 'armclang' in out: + # The compiler version is not present in the first line of output, + # instead it is present in second line, startswith 'Component:'. + # So, searching for the 'Component' in out although we know it is + # present in second line, as we are not sure about the + # output format in future versions + arm_ver_match = re.search('.*Component.*', out) + if arm_ver_match is None: + popen_exceptions[join_args(compiler)] = 'version string not found' + continue + arm_ver_str = arm_ver_match.group(0) + # Override previous values + version = search_version(arm_ver_str) + full_version = arm_ver_str + cls = c.ArmclangCCompiler if lang == 'c' else cpp.ArmclangCPPCompiler + linker = linkers.ArmClangDynamicLinker(for_machine, version=version) + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + if 'CL.EXE COMPATIBILITY' in out: + # if this is clang-cl masquerading as cl, detect it as cl, not + # clang + arg = '--version' + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[join_args(compiler + [arg])] = e + version = search_version(out) + match = re.search('^Target: (.*?)-', out, re.MULTILINE) + if match: + target = match.group(1) + else: + target = 'unknown target' + cls = c.ClangClCCompiler if lang == 'c' else cpp.ClangClCPPCompiler + linker = guess_win_linker(env, ['lld-link'], cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, target, + exe_wrap, linker=linker) + if 'clang' in out or 'Clang' in out: + linker = None + + defines = _get_clang_compiler_defines(compiler) + + # Even if the for_machine is darwin, we could be using vanilla + # clang. + if 'Apple' in out: + cls = c.AppleClangCCompiler if lang == 'c' else cpp.AppleClangCPPCompiler + else: + cls = c.ClangCCompiler if lang == 'c' else cpp.ClangCPPCompiler + + if 'windows' in out or env.machines[for_machine].is_windows(): + # If we're in a MINGW context this actually will use a gnu + # style ld, but for clang on "real" windows we'll use + # either link.exe or lld-link.exe + try: + linker = guess_win_linker(env, compiler, cls, version, for_machine, invoked_directly=False) + except MesonException: + pass + if linker is None: + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, defines=defines, full_version=full_version, linker=linker) + + if 'Intel(R) C++ Intel(R)' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = c.IntelClCCompiler if lang == 'c' else cpp.IntelClCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, target, + exe_wrap, linker=linker) + if 'Intel(R) oneAPI DPC++/C++ Compiler for applications' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = c.IntelLLVMClCCompiler if lang == 'c' else cpp.IntelLLVMClCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, target, + exe_wrap, linker=linker) + if 'Microsoft' in out or 'Microsoft' in err: + # Latest versions of Visual Studio print version + # number to stderr but earlier ones print version + # on stdout. Why? Lord only knows. + # Check both outputs to figure out version. + for lookat in [err, out]: + version = search_version(lookat) + if version != 'unknown version': + break + else: + raise EnvironmentException(f'Failed to detect MSVC compiler version: stderr was\n{err!r}') + cl_signature = lookat.split('\n', maxsplit=1)[0] + match = re.search(r'.*(x86|x64|ARM|ARM64)([^_A-Za-z0-9]|$)', cl_signature) + if match: + target = match.group(1) + else: + m = f'Failed to detect MSVC compiler target architecture: \'cl /?\' output is\n{cl_signature}' + raise EnvironmentException(m) + cls = c.VisualStudioCCompiler if lang == 'c' else cpp.VisualStudioCPPCompiler + linker = guess_win_linker(env, ['link'], cls, version, for_machine) + # As of this writing, CCache does not support MSVC but sccache does. + if 'sccache' not in ccache: + ccache = [] + return cls( + ccache, compiler, version, for_machine, is_cross, info, target, + exe_wrap, full_version=cl_signature, linker=linker) + if 'PGI Compilers' in out: + cls = c.PGICCompiler if lang == 'c' else cpp.PGICPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, + info, exe_wrap, linker=linker) + if 'NVIDIA Compilers and Tools' in out: + cls = c.NvidiaHPC_CCompiler if lang == 'c' else cpp.NvidiaHPC_CPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.NvidiaHPC_DynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, + info, exe_wrap, linker=linker) + if '(ICC)' in out: + cls = c.IntelCCompiler if lang == 'c' else cpp.IntelCPPCompiler + l = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=l) + if 'Intel(R) oneAPI' in out: + cls = c.IntelLLVMCCompiler if lang == 'c' else cpp.IntelLLVMCPPCompiler + l = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=l) + if 'TMS320C2000 C/C++' in out or 'MSP430 C/C++' in out or 'TI ARM C/C++ Compiler' in out: + lnk: T.Union[T.Type[linkers.C2000DynamicLinker], T.Type[linkers.TIDynamicLinker]] + if 'TMS320C2000 C/C++' in out: + cls = c.C2000CCompiler if lang == 'c' else cpp.C2000CPPCompiler + lnk = linkers.C2000DynamicLinker + else: + cls = c.TICCompiler if lang == 'c' else cpp.TICPPCompiler + lnk = linkers.TIDynamicLinker + + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = lnk(compiler, for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + if 'ARM' in out: + cls = c.ArmCCompiler if lang == 'c' else cpp.ArmCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.ArmDynamicLinker(for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, + info, exe_wrap, full_version=full_version, linker=linker) + if 'RX Family' in out: + cls = c.CcrxCCompiler if lang == 'c' else cpp.CcrxCPPCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.CcrxDynamicLinker(for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Microchip Technology' in out: + cls = c.Xc16CCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.Xc16DynamicLinker(for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'CompCert' in out: + cls = c.CompCertCCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.CompCertDynamicLinker(for_machine, version=version) + return cls( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException(f'Unknown compiler {compilers}') + +def detect_c_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + return _detect_c_or_cpp_compiler(env, 'c', for_machine) + +def detect_cpp_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + return _detect_c_or_cpp_compiler(env, 'cpp', for_machine) + +def detect_cuda_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .cuda import CudaCompiler + from ..linkers.linkers import CudaLinker + popen_exceptions = {} + is_cross = env.is_cross_build(for_machine) + compilers, ccache, exe_wrap = _get_compilers(env, 'cuda', for_machine) + info = env.machines[for_machine] + for compiler in compilers: + arg = '--version' + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[join_args(compiler + [arg])] = e + continue + # Example nvcc printout: + # + # nvcc: NVIDIA (R) Cuda compiler driver + # Copyright (c) 2005-2018 NVIDIA Corporation + # Built on Sat_Aug_25_21:08:01_CDT_2018 + # Cuda compilation tools, release 10.0, V10.0.130 + # + # search_version() first finds the "10.0" after "release", + # rather than the more precise "10.0.130" after "V". + # The patch version number is occasionally important; For + # instance, on Linux, + # - CUDA Toolkit 8.0.44 requires NVIDIA Driver 367.48 + # - CUDA Toolkit 8.0.61 requires NVIDIA Driver 375.26 + # Luckily, the "V" also makes it very simple to extract + # the full version: + version = out.strip().rsplit('V', maxsplit=1)[-1] + cpp_compiler = detect_cpp_compiler(env, for_machine) + cls = CudaCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = CudaLinker(compiler, for_machine, CudaCompiler.LINKER_PREFIX, [], version=CudaLinker.parse_version()) + return cls(ccache, compiler, version, for_machine, is_cross, exe_wrap, host_compiler=cpp_compiler, info=info, linker=linker) + raise EnvironmentException(f'Could not find suitable CUDA compiler: "{"; ".join([" ".join(c) for c in compilers])}"') + +def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from . import fortran + from ..linkers import linkers + popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} + compilers, ccache, exe_wrap = _get_compilers(env, 'fortran', for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + cls: T.Type[FortranCompiler] + for compiler in compilers: + for arg in ['--version', '-V']: + try: + p, out, err = Popen_safe(compiler + [arg]) + except OSError as e: + popen_exceptions[join_args(compiler + [arg])] = e + continue + + version = search_version(out) + full_version = out.split('\n', 1)[0] + + guess_gcc_or_lcc: T.Optional[str] = None + if 'GNU Fortran' in out: + guess_gcc_or_lcc = 'gcc' + if 'e2k' in out and 'lcc' in out: + guess_gcc_or_lcc = 'lcc' + + if guess_gcc_or_lcc: + defines = _get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[join_args(compiler)] = 'no pre-processor defines' + continue + if guess_gcc_or_lcc == 'lcc': + version = _get_lcc_version_from_defines(defines) + cls = fortran.ElbrusFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, defines, full_version=full_version, linker=linker) + else: + version = _get_gnu_version_from_defines(defines) + cls = fortran.GnuFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, defines, full_version=full_version, linker=linker) + + if 'Arm C/C++/Fortran Compiler' in out: + cls = fortran.ArmLtdFlangFortranCompiler + arm_ver_match = re.search(r'version (\d+)\.(\d+)\.?(\d+)? \(build number (\d+)\)', out) + assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None + version = '.'.join([x for x in arm_ver_match.groups() if x is not None]) + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, linker=linker) + if 'G95' in out: + cls = fortran.G95FortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Sun Fortran' in err: + version = search_version(err) + cls = fortran.SunFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Intel(R) Fortran Compiler for applications' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = fortran.IntelLLVMClFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, + target, exe_wrap, linker=linker) + + if 'Intel(R) Visual Fortran' in err or 'Intel(R) Fortran' in err: + version = search_version(err) + target = 'x86' if 'IA-32' in err else 'x86_64' + cls = fortran.IntelClFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, + target, exe_wrap, linker=linker) + + if 'ifort (IFORT)' in out: + cls = fortran.IntelFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'ifx (IFORT)' in out: + cls = fortran.IntelLLVMFortranCompiler + linker = guess_nix_linker(env, compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'PathScale EKOPath(tm)' in err: + return fortran.PathScaleFortranCompiler( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version) + + if 'PGI Compilers' in out: + cls = fortran.PGIFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.PGIDynamicLinker(compiler, for_machine, + cls.LINKER_PREFIX, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, exe_wrap, + full_version=full_version, linker=linker) + + if 'NVIDIA Compilers and Tools' in out: + cls = fortran.NvidiaHPC_FortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.PGIDynamicLinker(compiler, for_machine, + cls.LINKER_PREFIX, [], version=version) + return cls( + compiler, version, for_machine, is_cross, info, exe_wrap, + full_version=full_version, linker=linker) + + if 'flang' in out or 'clang' in out: + cls = fortran.FlangFortranCompiler + linker = guess_nix_linker(env, + compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'Open64 Compiler Suite' in err: + cls = fortran.Open64FortranCompiler + linker = guess_nix_linker(env, + compiler, cls, version, for_machine) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + if 'NAG Fortran' in err: + full_version = err.split('\n', 1)[0] + version = full_version.split()[-1] + cls = fortran.NAGFortranCompiler + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.NAGDynamicLinker( + compiler, for_machine, cls.LINKER_PREFIX, [], + version=version) + return cls( + compiler, version, for_machine, is_cross, info, + exe_wrap, full_version=full_version, linker=linker) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_objc_compiler(env: 'Environment', for_machine: MachineChoice) -> 'Compiler': + return _detect_objc_or_objcpp_compiler(env, 'objc', for_machine) + +def detect_objcpp_compiler(env: 'Environment', for_machine: MachineChoice) -> 'Compiler': + return _detect_objc_or_objcpp_compiler(env, 'objcpp', for_machine) + +def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine: MachineChoice) -> 'Compiler': + from . import objc, objcpp + popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} + compilers, ccache, exe_wrap = _get_compilers(env, lang, for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + comp: T.Union[T.Type[objc.ObjCCompiler], T.Type[objcpp.ObjCPPCompiler]] + + for compiler in compilers: + arg = ['--version'] + try: + p, out, err = Popen_safe(compiler + arg) + except OSError as e: + popen_exceptions[join_args(compiler + arg)] = e + continue + version = search_version(out) + if 'Free Software Foundation' in out: + defines = _get_gnu_compiler_defines(compiler) + if not defines: + popen_exceptions[join_args(compiler)] = 'no pre-processor defines' + continue + version = _get_gnu_version_from_defines(defines) + comp = objc.GnuObjCCompiler if lang == 'objc' else objcpp.GnuObjCPPCompiler + linker = guess_nix_linker(env, compiler, comp, version, for_machine) + return comp( + ccache, compiler, version, for_machine, is_cross, info, + exe_wrap, defines, linker=linker) + if 'clang' in out: + linker = None + defines = _get_clang_compiler_defines(compiler) + if not defines: + popen_exceptions[join_args(compiler)] = 'no pre-processor defines' + continue + if 'Apple' in out: + comp = objc.AppleClangObjCCompiler if lang == 'objc' else objcpp.AppleClangObjCPPCompiler + else: + comp = objc.ClangObjCCompiler if lang == 'objc' else objcpp.ClangObjCPPCompiler + if 'windows' in out or env.machines[for_machine].is_windows(): + # If we're in a MINGW context this actually will use a gnu style ld + try: + linker = guess_win_linker(env, compiler, comp, version, for_machine) + except MesonException: + pass + + if not linker: + linker = guess_nix_linker(env, compiler, comp, version, for_machine) + return comp( + ccache, compiler, version, for_machine, + is_cross, info, exe_wrap, linker=linker, defines=defines) + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_java_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .java import JavaCompiler + exelist = env.lookup_binary_entry(for_machine, 'java') + info = env.machines[for_machine] + if exelist is None: + # TODO support fallback + exelist = [defaults['java'][0]] + + try: + p, out, err = Popen_safe(exelist + ['-version']) + except OSError: + raise EnvironmentException('Could not execute Java compiler: {}'.format(join_args(exelist))) + if 'javac' in out or 'javac' in err: + version = search_version(err if 'javac' in err else out) + if not version or version == 'unknown version': + parts = (err if 'javac' in err else out).split() + if len(parts) > 1: + version = parts[1] + comp_class = JavaCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class(exelist, version, for_machine, info) + raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) + +def detect_cs_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from . import cs + compilers, ccache, exe_wrap = _get_compilers(env, 'cs', for_machine) + popen_exceptions = {} + info = env.machines[for_machine] + for comp in compilers: + try: + p, out, err = Popen_safe(comp + ['--version']) + except OSError as e: + popen_exceptions[join_args(comp + ['--version'])] = e + continue + + version = search_version(out) + cls: T.Type[cs.CsCompiler] + if 'Mono' in out: + cls = cs.MonoCompiler + elif "Visual C#" in out: + cls = cs.VisualStudioCsCompiler + else: + continue + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + return cls(comp, version, for_machine, info) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_cython_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + """Search for a cython compiler.""" + from .cython import CythonCompiler + compilers, _, _ = _get_compilers(env, 'cython', MachineChoice.BUILD) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + + popen_exceptions: T.Dict[str, Exception] = {} + for comp in compilers: + try: + err = Popen_safe(comp + ['-V'])[2] + except OSError as e: + popen_exceptions[join_args(comp + ['-V'])] = e + continue + + version = search_version(err) + if 'Cython' in err: + comp_class = CythonCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class([], comp, version, for_machine, info, is_cross=is_cross) + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_vala_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .vala import ValaCompiler + exelist = env.lookup_binary_entry(MachineChoice.BUILD, 'vala') + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + if exelist is None: + # TODO support fallback + exelist = [defaults['vala'][0]] + + try: + p, out = Popen_safe(exelist + ['--version'])[0:2] + except OSError: + raise EnvironmentException('Could not execute Vala compiler: {}'.format(join_args(exelist))) + version = search_version(out) + if 'Vala' in out: + comp_class = ValaCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class(exelist, version, for_machine, is_cross, info) + raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) + +def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> RustCompiler: + from . import rust + from ..linkers import linkers + popen_exceptions = {} # type: T.Dict[str, Exception] + compilers, _, exe_wrap = _get_compilers(env, 'rust', for_machine) + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + + cc = detect_c_compiler(env, for_machine) + is_link_exe = isinstance(cc.linker, linkers.VisualStudioLikeLinkerMixin) + override = env.lookup_binary_entry(for_machine, 'rust_ld') + + for compiler in compilers: + arg = ['--version'] + try: + out = Popen_safe(compiler + arg)[1] + except OSError as e: + popen_exceptions[join_args(compiler + arg)] = e + continue + + version = search_version(out) + cls: T.Type[RustCompiler] = rust.RustCompiler + + # Clippy is a wrapper around rustc, but it doesn't have rustc in it's + # output. We can otherwise treat it as rustc. + if 'clippy' in out: + out = 'rustc' + cls = rust.ClippyRustCompiler + + if 'rustc' in out: + # On Linux and mac rustc will invoke gcc (clang for mac + # presumably) and it can do this windows, for dynamic linking. + # this means the easiest way to C compiler for dynamic linking. + # figure out what linker to use is to just get the value of the + # C compiler and use that as the basis of the rust linker. + # However, there are two things we need to change, if CC is not + # the default use that, and second add the necessary arguments + # to rust to use -fuse-ld + + if any(a.startswith('linker=') for a in compiler): + mlog.warning( + 'Please do not put -C linker= in your compiler ' + 'command, set rust_ld=command in your cross file ' + 'or use the RUST_LD environment variable, otherwise meson ' + 'will override your selection.') + + compiler = compiler.copy() # avoid mutating the original list + + if override is None: + extra_args: T.Dict[str, T.Union[str, bool]] = {} + always_args: T.List[str] = [] + if is_link_exe: + compiler.extend(cls.use_linker_args(cc.linker.exelist[0], '')) + extra_args['direct'] = True + extra_args['machine'] = cc.linker.machine + else: + exelist = cc.linker.exelist + cc.linker.get_always_args() + if 'ccache' in exelist[0]: + del exelist[0] + c = exelist.pop(0) + compiler.extend(cls.use_linker_args(c, '')) + + # Also ensure that we pass any extra arguments to the linker + for l in exelist: + compiler.extend(['-C', f'link-arg={l}']) + + # This trickery with type() gets us the class of the linker + # so we can initialize a new copy for the Rust Compiler + # TODO rewrite this without type: ignore + assert cc.linker is not None, 'for mypy' + if is_link_exe: + linker = type(cc.linker)(for_machine, always_args, exelist=cc.linker.exelist, # type: ignore + version=cc.linker.version, **extra_args) # type: ignore + else: + linker = type(cc.linker)(compiler, for_machine, cc.LINKER_PREFIX, + always_args=always_args, version=cc.linker.version, + **extra_args) + elif 'link' in override[0]: + linker = guess_win_linker(env, + override, cls, version, for_machine, use_linker_prefix=False) + # rustc takes linker arguments without a prefix, and + # inserts the correct prefix itself. + assert isinstance(linker, linkers.VisualStudioLikeLinkerMixin) + linker.direct = True + compiler.extend(cls.use_linker_args(linker.exelist[0], '')) + else: + # On linux and macos rust will invoke the c compiler for + # linking, on windows it will use lld-link or link.exe. + # we will simply ask for the C compiler that corresponds to + # it, and use that. + cc = _detect_c_or_cpp_compiler(env, 'c', for_machine, override_compiler=override) + linker = cc.linker + + # Of course, we're not going to use any of that, we just + # need it to get the proper arguments to pass to rustc + c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0] + compiler.extend(cls.use_linker_args(c, '')) + + env.coredata.add_lang_args(cls.language, cls, for_machine, env) + return cls( + compiler, version, for_machine, is_cross, info, exe_wrap, + linker=linker) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from . import c, d + info = env.machines[for_machine] + + # Detect the target architecture, required for proper architecture handling on Windows. + # MSVC compiler is required for correct platform detection. + c_compiler = {'c': detect_c_compiler(env, for_machine)} + is_msvc = isinstance(c_compiler['c'], c.VisualStudioCCompiler) + if not is_msvc: + c_compiler = {} + + # Import here to avoid circular imports + from ..environment import detect_cpu_family + arch = detect_cpu_family(c_compiler) + if is_msvc and arch == 'x86': + arch = 'x86_mscoff' + + popen_exceptions = {} + is_cross = env.is_cross_build(for_machine) + compilers, ccache, exe_wrap = _get_compilers(env, 'd', for_machine) + cls: T.Type[d.DCompiler] + for exelist in compilers: + # Search for a D compiler. + # We prefer LDC over GDC unless overridden with the DC + # environment variable because LDC has a much more + # up to date language version at time (2016). + if os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')): + raise EnvironmentException( + f'Meson does not support {exelist[-1]} as it is only a DMD frontend for another compiler.' + 'Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.') + try: + p, out = Popen_safe(exelist + ['--version'])[0:2] + except OSError as e: + popen_exceptions[join_args(exelist + ['--version'])] = e + continue + version = search_version(out) + full_version = out.split('\n', 1)[0] + + if 'LLVM D compiler' in out: + cls = d.LLVMDCompiler + # LDC seems to require a file + # We cannot use NamedTemproraryFile on windows, its documented + # to not work for our uses. So, just use mkstemp and only have + # one path for simplicity. + o, f = tempfile.mkstemp('.d') + os.close(o) + + try: + if info.is_windows() or info.is_cygwin(): + objfile = os.path.basename(f)[:-1] + 'obj' + linker = guess_win_linker(env, + exelist, + cls, full_version, for_machine, + use_linker_prefix=True, invoked_directly=False, + extra_args=[f]) + else: + # LDC writes an object file to the current working directory. + # Clean it up. + objfile = os.path.basename(f)[:-1] + 'o' + linker = guess_nix_linker(env, + exelist, cls, full_version, for_machine, + extra_args=[f]) + finally: + windows_proof_rm(f) + windows_proof_rm(objfile) + + return cls( + exelist, version, for_machine, info, arch, + full_version=full_version, linker=linker, version_output=out) + elif 'gdc' in out: + cls = d.GnuDCompiler + linker = guess_nix_linker(env, exelist, cls, version, for_machine) + return cls( + exelist, version, for_machine, info, arch, + exe_wrapper=exe_wrap, is_cross=is_cross, + full_version=full_version, linker=linker) + elif 'The D Language Foundation' in out or 'Digital Mars' in out: + cls = d.DmdDCompiler + # DMD seems to require a file + # We cannot use NamedTemproraryFile on windows, its documented + # to not work for our uses. So, just use mkstemp and only have + # one path for simplicity. + o, f = tempfile.mkstemp('.d') + os.close(o) + + # DMD as different detection logic for x86 and x86_64 + arch_arg = '-m64' if arch == 'x86_64' else '-m32' + + try: + if info.is_windows() or info.is_cygwin(): + objfile = os.path.basename(f)[:-1] + 'obj' + linker = guess_win_linker(env, + exelist, cls, full_version, for_machine, + invoked_directly=False, extra_args=[f, arch_arg]) + else: + objfile = os.path.basename(f)[:-1] + 'o' + linker = guess_nix_linker(env, + exelist, cls, full_version, for_machine, + extra_args=[f, arch_arg]) + finally: + windows_proof_rm(f) + windows_proof_rm(objfile) + + return cls( + exelist, version, for_machine, info, arch, + full_version=full_version, linker=linker) + raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) + + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_swift_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .swift import SwiftCompiler + exelist = env.lookup_binary_entry(for_machine, 'swift') + is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] + if exelist is None: + # TODO support fallback + exelist = [defaults['swift'][0]] + + try: + p, _, err = Popen_safe(exelist + ['-v']) + except OSError: + raise EnvironmentException('Could not execute Swift compiler: {}'.format(join_args(exelist))) + version = search_version(err) + if 'Swift' in err: + # As for 5.0.1 swiftc *requires* a file to check the linker: + with tempfile.NamedTemporaryFile(suffix='.swift') as f: + cls = SwiftCompiler + linker = guess_nix_linker(env, + exelist, cls, version, for_machine, + extra_args=[f.name]) + return cls( + exelist, version, for_machine, is_cross, info, linker=linker) + + raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) + +def detect_nasm_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + from .asm import NasmCompiler, YasmCompiler + compilers, _, _ = _get_compilers(env, 'nasm', for_machine) + is_cross = env.is_cross_build(for_machine) + + # We need a C compiler to properly detect the machine info and linker + cc = detect_c_compiler(env, for_machine) + if not is_cross: + from ..environment import detect_machine_info + info = detect_machine_info({'c': cc}) + else: + info = env.machines[for_machine] + + popen_exceptions: T.Dict[str, Exception] = {} + for comp in compilers: + if comp == ['nasm'] and is_windows() and not shutil.which(comp[0]): + # nasm is not in PATH on Windows by default + default_path = os.path.join(os.environ['ProgramFiles'], 'NASM') + comp[0] = shutil.which(comp[0], path=default_path) or comp[0] + try: + output = Popen_safe(comp + ['--version'])[1] + except OSError as e: + popen_exceptions[' '.join(comp + ['--version'])] = e + continue + + version = search_version(output) + if 'NASM' in output: + comp_class = NasmCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + elif 'yasm' in output: + comp_class = YasmCompiler + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + _handle_exceptions(popen_exceptions, compilers) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: + # We need a C compiler to properly detect the machine info and linker + is_cross = env.is_cross_build(for_machine) + cc = detect_c_compiler(env, for_machine) + if not is_cross: + from ..environment import detect_machine_info + info = detect_machine_info({'c': cc}) + else: + info = env.machines[for_machine] + + from .asm import MasmCompiler, MasmARMCompiler + comp_class: T.Type[Compiler] + if info.cpu_family == 'x86': + comp = ['ml'] + comp_class = MasmCompiler + arg = '/?' + elif info.cpu_family == 'x86_64': + comp = ['ml64'] + comp_class = MasmCompiler + arg = '/?' + elif info.cpu_family == 'arm': + comp = ['armasm'] + comp_class = MasmARMCompiler + arg = '-h' + elif info.cpu_family == 'aarch64': + comp = ['armasm64'] + comp_class = MasmARMCompiler + arg = '-h' + else: + raise EnvironmentException(f'Platform {info.cpu_family} not supported by MASM') + + popen_exceptions: T.Dict[str, Exception] = {} + try: + output = Popen_safe(comp + [arg])[2] + version = search_version(output) + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + except OSError as e: + popen_exceptions[' '.join(comp + [arg])] = e + _handle_exceptions(popen_exceptions, [comp]) + raise EnvironmentException('Unreachable code (exception to make mypy happy)') + +# GNU/Clang defines and version +# ============================= + +def _get_gnu_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: + """ + Detect GNU compiler platform type (Apple, MinGW, Unix) + """ + # Arguments to output compiler pre-processor defines to stdout + # gcc, g++, and gfortran all support these arguments + args = compiler + ['-E', '-dM', '-'] + mlog.debug(f'Running command: {join_args(args)}') + p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) + if p.returncode != 0: + raise EnvironmentException('Unable to detect GNU compiler type:\n' + f'Compiler stdout:\n{output}\n-----\n' + f'Compiler stderr:\n{error}\n-----\n') + # Parse several lines of the type: + # `#define ___SOME_DEF some_value` + # and extract `___SOME_DEF` + defines: T.Dict[str, str] = {} + for line in output.split('\n'): + if not line: + continue + d, *rest = line.split(' ', 2) + if d != '#define': + continue + if len(rest) == 1: + defines[rest[0]] = '' + if len(rest) == 2: + defines[rest[0]] = rest[1] + return defines + +def _get_clang_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: + """ + Get the list of Clang pre-processor defines + """ + args = compiler + ['-E', '-dM', '-'] + mlog.debug(f'Running command: {join_args(args)}') + p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) + if p.returncode != 0: + raise EnvironmentException('Unable to get clang pre-processor defines:\n' + f'Compiler stdout:\n{output}\n-----\n' + f'Compiler stderr:\n{error}\n-----\n') + defines: T.Dict[str, str] = {} + for line in output.split('\n'): + if not line: + continue + d, *rest = line.split(' ', 2) + if d != '#define': + continue + if len(rest) == 1: + defines[rest[0]] = '' + if len(rest) == 2: + defines[rest[0]] = rest[1] + return defines + +def _get_gnu_version_from_defines(defines: T.Dict[str, str]) -> str: + dot = '.' + major = defines.get('__GNUC__', '0') + minor = defines.get('__GNUC_MINOR__', '0') + patch = defines.get('__GNUC_PATCHLEVEL__', '0') + return dot.join((major, minor, patch)) + +def _get_lcc_version_from_defines(defines: T.Dict[str, str]) -> str: + dot = '.' + generation_and_major = defines.get('__LCC__', '100') + generation = generation_and_major[:1] + major = generation_and_major[1:] + minor = defines.get('__LCC_MINOR__', '0') + return dot.join((generation, major, minor)) |