The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1380 lines
64 KiB
1380 lines
64 KiB
# 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, DynamicLinker |
|
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, skip_sanity_check: bool) -> T.Optional[Compiler]: |
|
comp = compiler_from_language(env, lang, for_machine) |
|
if comp is None: |
|
return comp |
|
assert comp.for_machine == for_machine |
|
env.coredata.process_new_compiler(lang, comp, env) |
|
if not skip_sanity_check: |
|
comp.sanity_check(env.get_scratch_dir(), env) |
|
env.coredata.compilers[comp.for_machine][lang] = comp |
|
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. |
|
elif is_windows() and compiler.id == 'nasm': |
|
# This may well be LINK.EXE if it's under a MSVC environment |
|
trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker']] + default_linkers |
|
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 out.strip().startswith('Metrowerks') or out.strip().startswith('Freescale'): |
|
if 'ARM' in out: |
|
return linkers.MetrowerksStaticLinkerARM(linker) |
|
else: |
|
return linkers.MetrowerksStaticLinkerEmbeddedPowerPC(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]] |
|
lnk: T.Union[T.Type[StaticLinker], T.Type[DynamicLinker]] |
|
|
|
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 out.startswith('xt-'): |
|
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 complaining 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: |
|
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 and not ('Metrowerks' in out or 'Freescale' 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) |
|
|
|
if 'Metrowerks C/C++' in out or 'Freescale C/C++' in out: |
|
if 'ARM' in out: |
|
cls = c.MetrowerksCCompilerARM if lang == 'c' else cpp.MetrowerksCPPCompilerARM |
|
lnk = linkers.MetrowerksLinkerARM |
|
else: |
|
cls = c.MetrowerksCCompilerEmbeddedPowerPC if lang == 'c' else cpp.MetrowerksCPPCompilerEmbeddedPowerPC |
|
lnk = linkers.MetrowerksLinkerEmbeddedPowerPC |
|
|
|
mwcc_ver_match = re.search(r'Version (\d+)\.(\d+)\.?(\d+)? build (\d+)', out) |
|
assert mwcc_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None |
|
compiler_version = '.'.join(x for x in mwcc_ver_match.groups() if x is not None) |
|
|
|
env.coredata.add_lang_args(cls.language, cls, for_machine, env) |
|
ld = env.lookup_binary_entry(for_machine, cls.language + '_ld') |
|
|
|
if ld is not None: |
|
_, o_ld, _ = Popen_safe(ld + ['--version']) |
|
|
|
mwld_ver_match = re.search(r'Version (\d+)\.(\d+)\.?(\d+)? build (\d+)', o_ld) |
|
assert mwld_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None |
|
linker_version = '.'.join(x for x in mwld_ver_match.groups() if x is not None) |
|
|
|
linker = lnk(ld, for_machine, version=linker_version) |
|
else: |
|
raise EnvironmentException(f'Failed to detect linker for {cls.id!r} compiler. Please update your cross file(s).') |
|
|
|
return cls( |
|
ccache, compiler, 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 complaining 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: |
|
# clippy returns its own version and not the rustc version by |
|
# default so try harder here to get the correct version. |
|
# Also replace the whole output with the rustc output in |
|
# case this is later used for other purposes. |
|
arg = ['--rustc', '--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 = 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 NamedTemporaryFile 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 NamedTemporaryFile 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, MetrowerksAsmCompilerARM, MetrowerksAsmCompilerEmbeddedPowerPC |
|
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) |
|
elif 'Metrowerks' in output or 'Freescale' in output: |
|
if 'ARM' in output: |
|
comp_class_mwasmarm = MetrowerksAsmCompilerARM |
|
env.coredata.add_lang_args(comp_class_mwasmarm.language, comp_class_mwasmarm, for_machine, env) |
|
return comp_class_mwasmarm([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) |
|
else: |
|
comp_class_mwasmeppc = MetrowerksAsmCompilerEmbeddedPowerPC |
|
env.coredata.add_lang_args(comp_class_mwasmeppc.language, comp_class_mwasmeppc, for_machine, env) |
|
return comp_class_mwasmeppc([], 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))
|
|
|