summaryrefslogtreecommitdiffstats
path: root/mesonbuild/linkers/detect.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/linkers/detect.py')
-rw-r--r--mesonbuild/linkers/detect.py258
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