diff options
Diffstat (limited to 'mesonbuild/linkers/detect.py')
-rw-r--r-- | mesonbuild/linkers/detect.py | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/mesonbuild/linkers/detect.py b/mesonbuild/linkers/detect.py new file mode 100644 index 0000000..97e770c --- /dev/null +++ b/mesonbuild/linkers/detect.py @@ -0,0 +1,258 @@ +# 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 .. import mlog +from ..mesonlib import ( + EnvironmentException, + Popen_safe, join_args, search_version +) +from .linkers import ( + AppleDynamicLinker, + LLVMLD64DynamicLinker, + GnuGoldDynamicLinker, + GnuBFDDynamicLinker, + MoldDynamicLinker, + LLVMDynamicLinker, + QualcommLLVMDynamicLinker, + MSVCDynamicLinker, + ClangClDynamicLinker, + SolarisDynamicLinker, + AIXDynamicLinker, + OptlinkDynamicLinker, +) + +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'] + +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, *, + use_linker_prefix: bool = True, invoked_directly: bool = True, + extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker': + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + + # Explicitly pass logo here so that we can get the version of link.exe + if not use_linker_prefix or comp_class.LINKER_PREFIX is None: + check_args = ['/logo', '--version'] + elif isinstance(comp_class.LINKER_PREFIX, str): + check_args = [comp_class.LINKER_PREFIX + '/logo', comp_class.LINKER_PREFIX + '--version'] + elif isinstance(comp_class.LINKER_PREFIX, list): + check_args = comp_class.LINKER_PREFIX + ['/logo'] + comp_class.LINKER_PREFIX + ['--version'] + + check_args += env.coredata.get_external_link_args(for_machine, comp_class.language) + + override = [] # type: 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 extra_args is not None: + check_args.extend(extra_args) + + p, o, _ = Popen_safe(compiler + check_args) + if 'LLD' in o.split('\n', maxsplit=1)[0]: + if '(compatible with GNU linkers)' in o: + return LLVMDynamicLinker( + compiler, for_machine, comp_class.LINKER_PREFIX, + override, version=search_version(o)) + elif not invoked_directly: + return ClangClDynamicLinker( + for_machine, override, exelist=compiler, prefix=comp_class.LINKER_PREFIX, + version=search_version(o), direct=False, machine=None) + + if value is not None and invoked_directly: + compiler = value + # We've already hanedled the non-direct case above + + p, o, e = Popen_safe(compiler + check_args) + if 'LLD' in o.split('\n', maxsplit=1)[0]: + return ClangClDynamicLinker( + for_machine, [], + prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [], + exelist=compiler, version=search_version(o), direct=invoked_directly) + elif 'OPTLINK' in o: + # Optlink's stdout *may* begin with a \r character. + return OptlinkDynamicLinker(compiler, 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 MSVCDynamicLinker( + for_machine, [], machine=target, exelist=compiler, + prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [], + version=search_version(out), direct=invoked_directly) + 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) + """ + env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + extra_args = extra_args or [] + + 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]) + + if isinstance(comp_class.LINKER_PREFIX, str): + check_args = [comp_class.LINKER_PREFIX + '--version'] + extra_args + else: + check_args = comp_class.LINKER_PREFIX + ['--version'] + extra_args + + override = [] # type: 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 + + mlog.debug('-----') + mlog.debug(f'Detecting linker via: {join_args(compiler + check_args)}') + p, o, e = Popen_safe(compiler + check_args) + mlog.debug(f'linker returned {p}') + mlog.debug(f'linker stdout:\n{o}') + mlog.debug(f'linker stderr:\n{e}') + + v = search_version(o + e) + linker: DynamicLinker + if 'LLD' in o.split('\n', maxsplit=1)[0]: + if isinstance(comp_class.LINKER_PREFIX, str): + cmd = compiler + override + [comp_class.LINKER_PREFIX + '-v'] + extra_args + else: + cmd = compiler + override + comp_class.LINKER_PREFIX + ['-v'] + extra_args + mlog.debug('-----') + mlog.debug(f'Detecting LLD linker via: {join_args(cmd)}') + _, newo, newerr = Popen_safe(cmd) + mlog.debug(f'linker stdout:\n{newo}') + mlog.debug(f'linker stderr:\n{newerr}') + + lld_cls: T.Type[DynamicLinker] + if 'ld64.lld' in newerr: + lld_cls = LLVMLD64DynamicLinker + else: + lld_cls = LLVMDynamicLinker + + linker = lld_cls( + compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + elif 'Snapdragon' in e and 'LLVM' in e: + linker = QualcommLLVMDynamicLinker( + compiler, for_machine, comp_class.LINKER_PREFIX, 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 = LLVMDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + # first might be apple clang, second is for real gcc, the third is icc + elif e.endswith('(use -v to see invocation)\n') or 'macosx_version' in e or 'ld: unknown option:' in e: + if isinstance(comp_class.LINKER_PREFIX, str): + cmd = compiler + [comp_class.LINKER_PREFIX + '-v'] + extra_args + else: + cmd = compiler + comp_class.LINKER_PREFIX + ['-v'] + extra_args + mlog.debug('-----') + mlog.debug(f'Detecting Apple linker via: {join_args(cmd)}') + _, newo, newerr = Popen_safe(cmd) + mlog.debug(f'linker stdout:\n{newo}') + mlog.debug(f'linker stderr:\n{newerr}') + + for line in newerr.split('\n'): + if 'PROJECT:ld' in line: + v = line.split('-')[1] + break + else: + __failed_to_detect_linker(compiler, check_args, o, e) + linker = AppleDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, 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 = GnuGoldDynamicLinker + elif o.startswith('mold') or e.startswith('mold'): + gnu_cls = MoldDynamicLinker + else: + gnu_cls = GnuBFDDynamicLinker + linker = gnu_cls(compiler, for_machine, comp_class.LINKER_PREFIX, 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 = SolarisDynamicLinker( + compiler, for_machine, comp_class.LINKER_PREFIX, override, + version=v) + elif 'ld: 0706-012 The -- flag is not recognized' in e: + if isinstance(comp_class.LINKER_PREFIX, str): + _, _, e = Popen_safe(compiler + [comp_class.LINKER_PREFIX + '-V'] + extra_args) + else: + _, _, e = Popen_safe(compiler + comp_class.LINKER_PREFIX + ['-V'] + extra_args) + linker = AIXDynamicLinker( + compiler, for_machine, comp_class.LINKER_PREFIX, override, + version=search_version(e)) + else: + __failed_to_detect_linker(compiler, check_args, o, e) + return linker |