# 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, 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'] 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': from . import linkers 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 linkers.LLVMDynamicLinker( compiler, for_machine, comp_class.LINKER_PREFIX, override, version=search_version(o)) elif not invoked_directly: return linkers.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 handled the non-direct case above p, o, e = Popen_safe(compiler + check_args) if 'LLD' in o.split('\n', maxsplit=1)[0]: return linkers.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 linkers.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 linkers.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) """ from . import linkers 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('-----') 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]: 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 _, 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, for_machine, comp_class.LINKER_PREFIX, override, version=v) elif 'Snapdragon' in e and 'LLVM' in e: linker = linkers.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 = linkers.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 _, newo, newerr = Popen_safe_logged(cmd, msg='Detecting Apple linker via') for line in newerr.split('\n'): if 'PROJECT:ld' in line or 'PROJECT:dyld' in line: v = line.split('-')[1] break else: __failed_to_detect_linker(compiler, check_args, o, e) linker = linkers.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 = linkers.GnuGoldDynamicLinker elif o.startswith('mold') or e.startswith('mold'): gnu_cls = linkers.MoldDynamicLinker else: gnu_cls = linkers.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 = linkers.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 = linkers.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