| # SPDX-License-Identifier: Apache-2.0 | |
| # Copyright 2012-2022 The Meson development team | |
| from __future__ import annotations | |
| from .base import RSPFileSyntax | |
| from .. import mlog | |
| from ..mesonlib import ( | |
| EnvironmentException, | |
| Popen_safe, Popen_safe_logged, join_args, search_version | |
| ) | |
| import re | |
| import shlex | |
| import typing as T | |
| if T.TYPE_CHECKING: | |
| from .linkers import DynamicLinker, GnuDynamicLinker | |
| from ..environment import Environment | |
| from ..compilers import Compiler | |
| from ..mesonlib import MachineChoice | |
| defaults: T.Dict[str, T.List[str]] = {} | |
| defaults['static_linker'] = ['ar', 'gar'] | |
| 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['emxomf_static_linker'] = ['emxomfar'] | |
| def __failed_to_detect_linker(compiler: T.List[str], args: T.List[str], stdout: str, stderr: str) -> 'T.NoReturn': | |
| msg = 'Unable to detect linker for compiler `{}`\nstdout: {}\nstderr: {}'.format( | |
| join_args(compiler + args), stdout, stderr) | |
| raise EnvironmentException(msg) | |
| def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Type['Compiler'], | |
| comp_version: str, for_machine: MachineChoice, *, | |
| wrap_linker_args: bool = True, invoked_directly: bool = True, | |
| extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker': | |
| from . import linkers | |
| env.add_lang_args(comp_class.language, comp_class, for_machine) | |
| if invoked_directly or comp_class.get_argument_syntax() == 'msvc': | |
| rsp_syntax = RSPFileSyntax.MSVC | |
| else: | |
| rsp_syntax = RSPFileSyntax.GCC | |
| # Explicitly pass logo here so that we can get the version of link.exe | |
| if not wrap_linker_args or comp_class.LINKER_OPTION_STYLE is None: | |
| check_args = ['/logo', '--version'] | |
| else: | |
| check_args = comp_class.LINKER_OPTION_STYLE.wrap(['/logo', '--version']) | |
| check_args += env.coredata.get_external_link_args(for_machine, comp_class.language) | |
| override: T.List[str] = [] | |
| value = env.lookup_binary_entry(for_machine, comp_class.language + '_ld') | |
| if value is not None: | |
| override = comp_class.use_linker_args(value[0], comp_version) | |
| check_args += override | |
| elif 'lld-link' in compiler: | |
| override = comp_class.use_linker_args('lld-link', comp_version) | |
| check_args += override | |
| if extra_args is not None: | |
| check_args.extend(extra_args) | |
| if value is not None and invoked_directly: | |
| compiler = value | |
| p, o, e = Popen_safe(compiler + check_args) | |
| if 'LLD' in o.split('\n', maxsplit=1)[0]: | |
| if 'compatible with GNU linkers' in o: | |
| return linkers.LLVMDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, | |
| override, version=search_version(o)) | |
| if not invoked_directly: | |
| return linkers.ClangClDynamicLinker( | |
| env, for_machine, override, exelist=compiler, prefix=comp_class.LINKER_OPTION_STYLE, | |
| version=search_version(o), direct=False, machine=None, | |
| rsp_syntax=rsp_syntax) | |
| return linkers.ClangClDynamicLinker( | |
| env, for_machine, [], | |
| prefix=comp_class.LINKER_OPTION_STYLE if wrap_linker_args else None, | |
| exelist=compiler, version=search_version(o), direct=invoked_directly, | |
| rsp_syntax=rsp_syntax) | |
| elif 'OPTLINK' in o: | |
| # Optlink's stdout *may* begin with a \r character. | |
| return linkers.OptlinkDynamicLinker(compiler, env, for_machine, version=search_version(o)) | |
| elif o.startswith('Microsoft') or e.startswith('Microsoft'): | |
| out = o or e | |
| match = re.search(r'.*(X86|X64|ARM|ARM64).*', out) | |
| if match: | |
| target = str(match.group(1)) | |
| else: | |
| target = 'x86' | |
| return linkers.MSVCDynamicLinker( | |
| env, for_machine, [], machine=target, exelist=compiler, | |
| prefix=comp_class.LINKER_OPTION_STYLE if wrap_linker_args else None, | |
| version=search_version(out), direct=invoked_directly, | |
| rsp_syntax=rsp_syntax) | |
| elif 'GNU coreutils' in o: | |
| import shutil | |
| fullpath = shutil.which(compiler[0]) | |
| raise EnvironmentException( | |
| f"Found GNU link.exe instead of MSVC link.exe in {fullpath}.\n" | |
| "This link.exe is not a linker.\n" | |
| "You may need to reorder entries to your %PATH% variable to resolve this.") | |
| __failed_to_detect_linker(compiler, check_args, o, e) | |
| def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Type['Compiler'], | |
| comp_version: str, for_machine: MachineChoice, *, | |
| extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker': | |
| """Helper for guessing what linker to use on Unix-Like OSes. | |
| :compiler: Invocation to use to get linker | |
| :comp_class: The Compiler Type (uninstantiated) | |
| :comp_version: The compiler version string | |
| :for_machine: which machine this linker targets | |
| :extra_args: Any additional arguments required (such as a source file) | |
| """ | |
| from . import linkers | |
| from ..options import OptionKey | |
| env.add_lang_args(comp_class.language, comp_class, for_machine) | |
| extra_args = extra_args or [] | |
| system = env.machines[for_machine].system | |
| ldflags = env.coredata.get_external_link_args(for_machine, comp_class.language) | |
| extra_args += comp_class._unix_args_to_native(ldflags, env.machines[for_machine]) | |
| check_args = comp_class.LINKER_OPTION_STYLE.wrap(['--version']) + extra_args | |
| override: T.List[str] = [] | |
| value = env.lookup_binary_entry(for_machine, comp_class.language + '_ld') | |
| if value is not None: | |
| override = comp_class.use_linker_args(value[0], comp_version) | |
| check_args += override | |
| if env.machines[for_machine].is_os2() and env.coredata.optstore.get_value_for(OptionKey('os2_emxomf')): | |
| check_args += ['-Zomf'] | |
| mlog.debug('-----') | |
| p, o, e = Popen_safe_logged(compiler + check_args, msg='Detecting linker via') | |
| v = search_version(o + e) | |
| linker: DynamicLinker | |
| if 'LLD' in o.split('\n', maxsplit=1)[0] or 'tiarmlnk' in e: | |
| cmd = compiler + override + comp_class.LINKER_OPTION_STYLE.wrap(['-v']) + extra_args | |
| _, newo, newerr = Popen_safe_logged(cmd, msg='Detecting LLD linker via') | |
| lld_cls: T.Type[DynamicLinker] | |
| if 'ld64.lld' in newerr: | |
| lld_cls = linkers.LLVMLD64DynamicLinker | |
| else: | |
| lld_cls = linkers.LLVMDynamicLinker | |
| linker = lld_cls( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, system=system, version=v) | |
| elif o.startswith("eld"): | |
| linker = linkers.ELDDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, version=v) | |
| elif 'Snapdragon' in e and 'LLVM' in e: | |
| linker = linkers.QualcommLLVMDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, version=v) | |
| elif e.startswith('lld-link: '): | |
| # The LLD MinGW frontend didn't respond to --version before version 9.0.0, | |
| # and produced an error message about failing to link (when no object | |
| # files were specified), instead of printing the version number. | |
| # Let's try to extract the linker invocation command to grab the version. | |
| _, o, e = Popen_safe(compiler + check_args + ['-v']) | |
| try: | |
| linker_cmd = re.match(r'.*\n(.*?)\nlld-link: ', e, re.DOTALL).group(1) | |
| linker_cmd = shlex.split(linker_cmd)[0] | |
| except (AttributeError, IndexError, ValueError): | |
| pass | |
| else: | |
| _, o, e = Popen_safe([linker_cmd, '--version']) | |
| v = search_version(o) | |
| linker = linkers.LLVMDynamicLinker(compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, version=v) | |
| elif 'GNU' in o or 'GNU' in e: | |
| gnu_cls: T.Type[GnuDynamicLinker] | |
| # this is always the only thing on stdout, except for swift | |
| # which may or may not redirect the linker stdout to stderr | |
| if o.startswith('GNU gold') or e.startswith('GNU gold'): | |
| gnu_cls = linkers.GnuGoldDynamicLinker | |
| elif o.startswith('mold') or e.startswith('mold'): | |
| gnu_cls = linkers.MoldDynamicLinker | |
| else: | |
| gnu_cls = linkers.GnuBFDDynamicLinker | |
| linker = gnu_cls(compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, version=v) | |
| elif 'Solaris' in e or 'Solaris' in o: | |
| for line in (o+e).split('\n'): | |
| if 'ld: Software Generation Utilities' in line: | |
| v = line.split(':')[2].lstrip() | |
| break | |
| else: | |
| v = 'unknown version' | |
| linker = linkers.SolarisDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, | |
| version=v) | |
| elif 'ld: 0706-012 The -- flag is not recognized' in e: | |
| _, _, e = Popen_safe(compiler + comp_class.LINKER_OPTION_STYLE.wrap(['-V']) + extra_args) | |
| linker = linkers.AIXDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, | |
| version=search_version(e)) | |
| elif o.startswith('zig ld'): | |
| linker = linkers.ZigCCDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, version=v) | |
| # detect xtools first, bug #10805 | |
| elif 'xtools-' in o.split('\n', maxsplit=1)[0]: | |
| xtools = o.split(' ', maxsplit=1)[0] | |
| v = xtools.split('-', maxsplit=2)[1] | |
| linker = linkers.AppleDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, | |
| system=system, version=v | |
| ) | |
| # detect linker on MacOS - must be after other platforms because the | |
| # "(use -v to see invocation)" will match clang on other platforms, | |
| # but the rest of the checks will fail and call __failed_to_detect_linker. | |
| # First might be apple clang, second is for real gcc, the third is icc. | |
| # Note that "ld: unknown option: " sometimes instead is "ld: unknown options:". | |
| elif e.endswith('(use -v to see invocation)\n') or 'macosx_version' in e or 'ld: unknown option' in e: | |
| cmd = compiler + comp_class.LINKER_OPTION_STYLE.wrap(['-v']) + extra_args | |
| _, newo, newerr = Popen_safe_logged(cmd, msg='Detecting Apple linker via') | |
| is_dyld: bool | |
| for line in newerr.split('\n'): | |
| if 'PROJECT:ld' in line or 'PROJECT:dyld' in line: | |
| v = line.split('-')[1] | |
| is_dyld = 'PROJECT:dyld' in line | |
| break | |
| else: | |
| __failed_to_detect_linker(compiler, check_args, o, e) | |
| linker = linkers.AppleDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, | |
| system=system, version=v, is_dyld=is_dyld | |
| ) | |
| elif 'ld.exe: unrecognized option' in e or 'ld: unrecognized option' in e: | |
| linker = linkers.OS2AoutDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, | |
| version='none') | |
| elif 'emxomfld: invalid option' in e: | |
| linker = linkers.OS2OmfDynamicLinker( | |
| compiler, env, for_machine, comp_class.LINKER_OPTION_STYLE, override, | |
| version='none') | |
| else: | |
| __failed_to_detect_linker(compiler, check_args, o, e) | |
| return linker |