Merge pull request #6207 from dcbaker/linker-option

Add a way to select the dynamic linker meson uses
pull/6330/head
Jussi Pakkanen 5 years ago committed by GitHub
commit 17dd9e5bff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      ci/azure-steps.yml
  2. 7
      docs/markdown/Cross-compilation.md
  3. 1
      docs/markdown/Native-environments.md
  4. 25
      docs/markdown/howtox.md
  5. 17
      docs/markdown/snippets/linker_override.md
  6. 9
      mesonbuild/backend/ninjabackend.py
  7. 6
      mesonbuild/compilers/compilers.py
  8. 4
      mesonbuild/compilers/mixins/gnu.py
  9. 4
      mesonbuild/compilers/mixins/visualstudio.py
  10. 6
      mesonbuild/compilers/rust.py
  11. 2
      mesonbuild/envconfig.py
  12. 257
      mesonbuild/environment.py
  13. 69
      mesonbuild/linkers.py
  14. 9
      run_project_tests.py
  15. 89
      run_unittests.py

@ -156,9 +156,10 @@ steps:
echo ""
echo "Locating cl, rc:"
echo "Locating cl, rc, link:"
where.exe cl
where.exe rc
where.exe link
echo ""
echo "=== Start running tests ==="

@ -97,6 +97,7 @@ this:
[binaries]
c = '/usr/bin/i586-mingw32msvc-gcc'
cpp = '/usr/bin/i586-mingw32msvc-g++'
ld = 'gold'
ar = '/usr/i586-mingw32msvc/bin/ar'
strip = '/usr/i586-mingw32msvc/bin/strip'
pkgconfig = '/usr/bin/i586-mingw32msvc-pkg-config'
@ -112,6 +113,12 @@ of a wrapper, these lines are all you need to write. Meson will
automatically use the given wrapper when it needs to run host
binaries. This happens e.g. when running the project's test suite.
ld is special because it is compiler specific. For compilers like gcc and
clang which are used to invoke the linker this is a value to pass to their
"choose the linker" argument (-fuse-ld= in this case). For compilers like
MSVC and Clang-Cl, this is the path to a linker for meson to invoke, such as
`link.exe` or `lld-link.exe`. Support for ls is *new in 0.53.0*
The next section lists properties of the cross compiler and its target
system, and thus properties of host system of what we're building. It
looks like this:

@ -40,6 +40,7 @@ like `llvm-config`
c = '/usr/local/bin/clang'
cpp = '/usr/local/bin/clang++'
rust = '/usr/local/bin/rust'
ld = 'gold'
llvm-config = '/usr/local/llvm-svn/bin/llvm-config'
```

@ -23,8 +23,29 @@ compilation is done by setting `CC` to point to the cross compiler
that Meson supports natively the case where you compile helper tools
(such as code generators) and use the results during the
build. Because of this Meson needs to know both the native and the
cross compiler. The former is set via the environment variables and
the latter via the cross file only.
cross compiler. The former is set via the environment variables or
native-files and the latter via the cross file only.
## Set dynamic linker
```console
$ CC=clang LD=lld meson <options>
```
or
```console
$ CC=clang-cl LD=link meson <options>
```
Like the compiler, the linker is selected via the LD environment variable, or
through the `ld` entry in a native or cross file. You must be aware of
whehter you're using a compiler that invokes the linker itself (most
compilers including GCC and Clang) or a linker that is invoked directly (when
using MSVC or compilers that act like it, including Clang-Cl). With the
former `ld` or `LD` should be the value to pass to the compiler's special
argument (such as `-fuse-ld` with clang and gcc), with the latter it should
be an exectuable, such as `lld-link.exe`.
## Set default C/C++ language version

@ -0,0 +1,17 @@
## Generic Overrider for Dynamic Linker selection
Previous to meson 0.52.0 you set the dynamic linker using compiler specific
flags passed via language flags and hoped things worked out. In meson 0.52.0
meson started detecting the linker and making intelligent decisions about
using it. Unfortunately this broke choosing a non-default linker.
Now there is a generic mechanism for doing this, you may use the LD
environment variable (with normal meson environment variable rules), or add
the following to a cross or native file:
```ini
[binaries]
ld = 'gold'
```
And meson will select the linker if possible.

@ -1291,6 +1291,15 @@ int dummy;
else:
raise InvalidArguments('Unknown target type for rustc.')
args.append(cratetype)
# If we're dynamically linking, add those arguments
#
# Rust is super annoying, calling -C link-arg foo does not work, it has
# to be -C link-arg=foo
if cratetype in {'bin', 'dylib'}:
for a in rustc.linker.get_always_args():
args += ['-C', 'link-arg={}'.format(a)]
args += ['--crate-name', target.name]
args += rustc.get_buildtype_args(self.get_option_for_target('buildtype', target))
args += rustc.get_debug_args(self.get_option_for_target('debug', target))

@ -1192,6 +1192,12 @@ class Compiler:
def get_dependency_link_args(self, dep):
return dep.get_link_args()
@classmethod
def use_linker_args(cls, linker: str) -> typing.List[str]:
"""Get a list of arguments to pass to the compiler to set the linker.
"""
return []
def get_largefile_args(compiler):
'''

@ -299,6 +299,10 @@ class GnuLikeCompiler(metaclass=abc.ABCMeta):
return ['-isystem' + path]
return ['-I' + path]
@classmethod
def use_linker_args(cls, linker: str) -> typing.List[str]:
return ['-fuse-ld={}'.format(linker)]
class GnuCompiler(GnuLikeCompiler):
"""

@ -381,3 +381,7 @@ class VisualStudioLikeCompiler(metaclass=abc.ABCMeta):
def get_argument_syntax(self) -> str:
return 'msvc'
@classmethod
def use_linker_args(cls, linker: str) -> typing.List[str]:
return []

@ -32,7 +32,7 @@ rust_optimization_args = {'0': [],
class RustCompiler(Compiler):
LINKER_PREFIX = '-Wl,'
# rustc doesn't invoke the compiler itself, it doesn't need a LINKER_PREFIX
def __init__(self, exelist, version, for_machine: MachineChoice,
is_cross, info: 'MachineInfo', exe_wrapper=None, **kwargs):
@ -109,3 +109,7 @@ class RustCompiler(Compiler):
def get_std_exe_link_args(self):
return []
# Rust does not have a use_linker_args because it dispatches to a gcc-like
# C compiler for dynamic linking, as such we invoke the C compiler's
# use_linker_args method instead.

@ -309,7 +309,9 @@ class BinaryTable(HasEnvVarFallback):
'strip': 'STRIP',
'ar': 'AR',
'windres': 'WINDRES',
'ld': 'LD',
# Other tools
'cmake': 'CMAKE',
'qmake': 'QMAKE',
'pkgconfig': 'PKG_CONFIG',

@ -53,10 +53,9 @@ from .linkers import (
PGIDynamicLinker,
PGIStaticLinker,
SolarisDynamicLinker,
XildAppleDynamicLinker,
XildLinuxDynamicLinker,
XilinkDynamicLinker,
CudaLinker,
VisualStudioLikeLinkerMixin,
)
from functools import lru_cache
from .compilers import (
@ -733,55 +732,88 @@ class Environment:
errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e)
raise EnvironmentException(errmsg)
@staticmethod
def _guess_win_linker(compiler: typing.List[str], for_machine: MachineChoice,
prefix: typing.Union[str, typing.List[str]]) -> 'DynamicLinker':
def _guess_win_linker(self, compiler: typing.List[str], comp_class: Compiler,
for_machine: MachineChoice, *,
use_linker_prefix: bool = True) -> 'DynamicLinker':
# Explicitly pass logo here so that we can get the version of link.exe
if isinstance(prefix, str):
check_args = [prefix + '/logo', prefix + '--version']
else:
check_args = prefix + ['/logo'] + prefix + ['--version']
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']
override = [] # type: typing.List[str]
value = self.binaries[for_machine].lookup_entry('ld')
if value is not None:
override = comp_class.use_linker_args(value[0])
check_args += override
p, o, _ = Popen_safe(compiler + check_args)
if o.startswith('LLD'):
if '(compatible with GNU linkers)' in o:
return LLVMDynamicLinker(compiler, for_machine, 'lld', prefix, version=search_version(o))
else:
return ClangClDynamicLinker(for_machine, exelist=compiler, prefix=prefix, version=search_version(o))
elif o.startswith('Microsoft'):
match = re.search(r'.*(X86|X64|ARM|ARM64).*', o)
return LLVMDynamicLinker(
compiler, for_machine, 'lld', comp_class.LINKER_PREFIX,
override, version=search_version(o))
if value is not None:
compiler = value
p, o, e = Popen_safe(compiler + check_args)
if o.startswith('LLD'):
return ClangClDynamicLinker(
for_machine, [],
prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [],
exelist=compiler, version=search_version(o))
elif 'OPTLINK' in o:
# Opltink's stdout *may* beging with a \r character.
return OptlinkDynamicLinker(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=prefix,
version=search_version(o))
raise MesonException('Cannot guess dynamic linker')
@staticmethod
def _guess_nix_linker(compiler: typing.List[str], for_machine: MachineChoice,
prefix: typing.Union[str, typing.List[str]], *,
return MSVCDynamicLinker(
for_machine, [], machine=target, exelist=compiler,
prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [],
version=search_version(out))
elif 'GNU coreutils' in o:
raise EnvironmentException(
"Found GNU link.exe instead of MSVC link.exe. This link.exe "
"is not a linker. You may need to reorder entries to your "
"%PATH% variable to resolve this.")
raise EnvironmentException('Unable to determine dynamic linker')
def _guess_nix_linker(self, compiler: typing.List[str], comp_class: typing.Type[Compiler],
for_machine: MachineChoice, *,
extra_args: typing.Optional[typing.List[str]] = None) -> 'DynamicLinker':
"""Helper for guessing what linker to use on Unix-Like OSes.
:prefix: The prefix that the compiler uses to proxy arguments to the
linker, if required. This can be passed as a string or a list of
strings. If it is passed as a string then the arguments to be
proxied to the linker will be concatenated, if it is a list they
will be appended. This means that if a space is required (such as
with swift which wants `-Xlinker --version` and *not*
`-Xlinker=--version`) you must pass as a list.
:compiler: Invocation to use to get linker
:comp_class: The Compiler Type (uninstantiated)
:for_machine: which machine this linker targets
:extra_args: Any additional arguments required (such as a source file)
"""
extra_args = typing.cast(typing.List[str], extra_args or [])
if isinstance(prefix, str):
check_args = [prefix + '--version'] + extra_args
if isinstance(comp_class.LINKER_PREFIX, str):
check_args = [comp_class.LINKER_PREFIX + '--version'] + extra_args
else:
check_args = prefix + ['--version'] + extra_args
check_args = comp_class.LINKER_PREFIX + ['--version'] + extra_args
override = [] # type: typing.List[str]
value = self.binaries[for_machine].lookup_entry('ld')
if value is not None:
override = comp_class.use_linker_args(value[0])
check_args += override
_, o, e = Popen_safe(compiler + check_args)
v = search_version(o)
if o.startswith('LLD'):
linker = LLVMDynamicLinker(compiler, for_machine, 'lld', prefix, version=v) # type: DynamicLinker
linker = LLVMDynamicLinker(compiler, for_machine, 'lld', comp_class.LINKER_PREFIX, override, version=v) # type: DynamicLinker
elif e.startswith('lld-link: '):
# Toolchain wrapper got in the way; this happens with e.g. https://github.com/mstorsjo/llvm-mingw
# Let's try to extract the linker invocation command to grab the version.
@ -797,13 +829,13 @@ class Environment:
_, o, e = Popen_safe([linker_cmd, '--version'])
v = search_version(o)
linker = LLVMDynamicLinker(compiler, for_machine, 'lld', prefix, version=v)
linker = LLVMDynamicLinker(compiler, for_machine, 'lld', comp_class.LINKER_PREFIX, override, version=v)
# first is for apple clang, second is for real gcc
elif e.endswith('(use -v to see invocation)\n') or 'macosx_version' in e:
if isinstance(prefix, str):
_, _, e = Popen_safe(compiler + [prefix + '-v'] + extra_args)
if isinstance(comp_class.LINKER_PREFIX, str):
_, _, e = Popen_safe(compiler + [comp_class.LINKER_PREFIX + '-v'] + extra_args)
else:
_, _, e = Popen_safe(compiler + prefix + ['-v'] + extra_args)
_, _, e = Popen_safe(compiler + comp_class.LINKER_PREFIX + ['-v'] + extra_args)
i = 'APPLE ld'
for line in e.split('\n'):
if 'PROJECT:ld' in line:
@ -811,19 +843,19 @@ class Environment:
break
else:
v = 'unknown version'
linker = AppleDynamicLinker(compiler, for_machine, i, prefix, version=v)
linker = AppleDynamicLinker(compiler, for_machine, i, comp_class.LINKER_PREFIX, override, version=v)
elif 'GNU' in o:
if 'gold' in o:
i = 'GNU ld.gold'
else:
i = 'GNU ld.bfd'
linker = GnuDynamicLinker(compiler, for_machine, i, prefix, version=v)
linker = GnuDynamicLinker(compiler, for_machine, i, comp_class.LINKER_PREFIX, override, version=v)
elif 'Solaris' in e or 'Solaris' in o:
linker = SolarisDynamicLinker(
compiler, for_machine, 'solaris', prefix,
compiler, for_machine, 'solaris', comp_class.LINKER_PREFIX, override,
version=search_version(e))
else:
raise MesonException('Unable to determine dynamic linker.')
raise EnvironmentException('Unable to determine dynamic linker')
return linker
def _detect_c_or_cpp_compiler(self, lang: str, for_machine: MachineChoice) -> Compiler:
@ -898,7 +930,7 @@ class Environment:
version = self.get_gnu_version_from_defines(defines)
cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler
linker = self._guess_nix_linker(compiler, for_machine, cls.LINKER_PREFIX)
linker = self._guess_nix_linker(compiler, cls, for_machine)
return cls(
ccache + compiler, version, for_machine, is_cross,
info, exe_wrap, defines, full_version=full_version,
@ -925,7 +957,7 @@ class Environment:
version = search_version(arm_ver_str)
full_version = arm_ver_str
cls = ArmclangCCompiler if lang == 'c' else ArmclangCPPCompiler
linker = ArmClangDynamicLinker(for_machine, version=version)
linker = ArmClangDynamicLinker(for_machine, [], version=version)
return cls(
ccache + compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
@ -944,7 +976,7 @@ class Environment:
else:
target = 'unknown target'
cls = ClangClCCompiler if lang == 'c' else ClangClCPPCompiler
linker = ClangClDynamicLinker(for_machine, version=version)
linker = self._guess_win_linker(['lld-link'], cls, for_machine)
return cls(
compiler, version, for_machine, is_cross, info, exe_wrap,
target, linker=linker)
@ -963,11 +995,11 @@ class Environment:
# style ld, but for clang on "real" windows we'll use
# either link.exe or lld-link.exe
try:
linker = self._guess_win_linker(compiler, for_machine, cls.LINKER_PREFIX)
linker = self._guess_win_linker(compiler, cls, for_machine)
except MesonException:
pass
if linker is None:
linker = self._guess_nix_linker(compiler, for_machine, cls.LINKER_PREFIX)
linker = self._guess_nix_linker(compiler, cls, for_machine)
return cls(
ccache + compiler, version, for_machine, is_cross, info,
@ -999,23 +1031,23 @@ class Environment:
else:
m = 'Failed to detect MSVC compiler target architecture: \'cl /?\' output is\n{}'
raise EnvironmentException(m.format(cl_signature))
linker = MSVCDynamicLinker(for_machine, version=version)
cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler
linker = self._guess_win_linker(['link'], cls, for_machine)
return cls(
compiler, version, for_machine, is_cross, info, exe_wrap,
target, linker=linker)
if 'PGI Compilers' in out:
cls = PGICCompiler if lang == 'c' else PGICPPCompiler
linker = PGIDynamicLinker(compiler, for_machine, 'pgi', cls.LINKER_PREFIX, version=version)
linker = PGIDynamicLinker(compiler, for_machine, 'pgi', cls.LINKER_PREFIX, [], version=version)
return cls(
ccache + compiler, version, for_machine, is_cross,
info, exe_wrap, linker=linker)
if '(ICC)' in out:
cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler
if self.machines[for_machine].is_darwin():
l = XildAppleDynamicLinker(compiler, for_machine, 'xild', cls.LINKER_PREFIX, version=version)
l = AppleDynamicLinker(compiler, for_machine, 'APPLE ld', cls.LINKER_PREFIX, [], version=version)
else:
l = XildLinuxDynamicLinker(compiler, for_machine, 'xild', cls.LINKER_PREFIX, version=version)
l = self._guess_nix_linker(compiler, cls, for_machine)
return cls(
ccache + compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=l)
@ -1113,7 +1145,7 @@ class Environment:
version = self.get_gnu_version_from_defines(defines)
cls = GnuFortranCompiler
linker = self._guess_nix_linker(
compiler, for_machine, cls.LINKER_PREFIX)
compiler, cls, for_machine)
return cls(
compiler, version, for_machine, is_cross, info,
exe_wrap, defines, full_version=full_version,
@ -1121,7 +1153,7 @@ class Environment:
if 'G95' in out:
linker = self._guess_nix_linker(
compiler, for_machine, cls.LINKER_PREFIX)
compiler, cls, for_machine)
return G95FortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
@ -1129,7 +1161,7 @@ class Environment:
if 'Sun Fortran' in err:
version = search_version(err)
linker = self._guess_nix_linker(
compiler, for_machine, cls.LINKER_PREFIX)
compiler, cls, for_machine)
return SunFortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
@ -1137,14 +1169,13 @@ class Environment:
if 'Intel(R) Visual Fortran' in err:
version = search_version(err)
target = 'x86' if 'IA-32' in err else 'x86_64'
linker = XilinkDynamicLinker(for_machine, version=version)
linker = XilinkDynamicLinker(for_machine, [], version=version)
return IntelClFortranCompiler(
compiler, version, for_machine, is_cross, target,
info, exe_wrap, linker=linker)
if 'ifort (IFORT)' in out:
linker = XildLinuxDynamicLinker(
compiler, for_machine, 'xild', IntelFortranCompiler.LINKER_PREFIX, version=version)
linker = self._guess_nix_linker(compiler, IntelFortranCompiler, for_machine)
return IntelFortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
@ -1164,21 +1195,21 @@ class Environment:
if 'flang' in out or 'clang' in out:
linker = self._guess_nix_linker(
compiler, for_machine, FlangFortranCompiler.LINKER_PREFIX)
compiler, FlangFortranCompiler, for_machine)
return FlangFortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
if 'Open64 Compiler Suite' in err:
linker = self._guess_nix_linker(
compiler, for_machine, Open64FortranCompiler.LINKER_PREFIX)
compiler, Open64FortranCompiler, for_machine)
return Open64FortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
if 'NAG Fortran' in err:
linker = self._guess_nix_linker(
compiler, for_machine, NAGFortranCompiler.LINKER_PREFIX)
compiler, NAGFortranCompiler, for_machine)
return NAGFortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
@ -1217,7 +1248,7 @@ class Environment:
continue
version = self.get_gnu_version_from_defines(defines)
comp = GnuObjCCompiler if objc else GnuObjCPPCompiler
linker = self._guess_nix_linker(compiler, for_machine, comp.LINKER_PREFIX)
linker = self._guess_nix_linker(compiler, comp, for_machine)
return comp(
ccache + compiler, version, for_machine, is_cross, info,
exe_wrap, defines, linker=linker)
@ -1227,13 +1258,13 @@ class Environment:
if 'windows' in out or self.machines[for_machine].is_windows():
# If we're in a MINGW context this actually will use a gnu style ld
try:
linker = self._guess_win_linker(compiler, for_machine, comp.LINKER_PREFIX)
linker = self._guess_win_linker(compiler, comp, for_machine)
except MesonException:
pass
if not linker:
linker = self._guess_nix_linker(
compiler, for_machine, comp.LINKER_PREFIX)
compiler, comp, for_machine)
return comp(
ccache + compiler, version, for_machine,
is_cross, info, exe_wrap, linker=linker)
@ -1303,6 +1334,10 @@ class Environment:
compilers, ccache, exe_wrap = self._get_compilers('rust', for_machine)
is_cross = not self.machines.matches_build_machine(for_machine)
info = self.machines[for_machine]
cc = self.detect_c_compiler(for_machine)
is_link_exe = isinstance(cc.linker, VisualStudioLikeLinkerMixin)
for compiler in compilers:
if isinstance(compiler, str):
compiler = [compiler]
@ -1316,19 +1351,42 @@ class Environment:
version = search_version(out)
if 'rustc' in out:
# Chalk up another quirk for rust. There is no way (AFAICT) to
# figure out what linker rustc is using for a non-nightly compiler
# (On nightly you can pass -Z print-link-args). So we're going to
# hard code the linker based on the platform.
# Currently gnu ld is used for everything except apple by
# default, and apple ld is used on mac.
# TODO: find some better way to figure this out.
if self.machines[for_machine].is_darwin():
linker = AppleDynamicLinker(
[], for_machine, 'Apple ld', RustCompiler.LINKER_PREFIX)
# 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
extra_args = {}
always_args = []
if is_link_exe:
compiler.extend(['-C', 'linker={}'.format(cc.linker.exelist[0])])
extra_args['direct'] = True
extra_args['machine'] = cc.linker.machine
elif not ((info.is_darwin() and isinstance(cc, AppleClangCCompiler)) or
isinstance(cc, GnuCCompiler)):
c = cc.exelist[1] if cc.exelist[0].endswith('ccache') else cc.exelist[0]
compiler.extend(['-C', 'linker={}'.format(c)])
value = self.binaries[for_machine].lookup_entry('ld')
if value is not None:
for a in cc.use_linker_args(value[0]):
always_args.extend(['-C', 'link-arg={}'.format(a)])
# This trickery with type() gets us the class of the linker
# so we can initialize a new copy for the Rust Compiler
if is_link_exe:
linker = type(cc.linker)(for_machine, always_args, exelist=cc.linker.exelist,
version=cc.linker.version, **extra_args)
else:
linker = GnuDynamicLinker(
[], for_machine, 'GNU ld', RustCompiler.LINKER_PREFIX)
linker = type(cc.linker)(compiler, for_machine, cc.linker.id, cc.LINKER_PREFIX,
always_args=always_args, version=cc.linker.version,
**extra_args)
return RustCompiler(
compiler, version, for_machine, is_cross, info, exe_wrap,
linker=linker)
@ -1339,10 +1397,11 @@ class Environment:
info = self.machines[for_machine]
# Detect the target architecture, required for proper architecture handling on Windows.
c_compiler = {}
is_msvc = mesonlib.is_windows() and 'VCINSTALLDIR' in os.environ
if is_msvc:
c_compiler = {'c': self.detect_c_compiler(for_machine)} # MSVC compiler is required for correct platform detection.
# MSVC compiler is required for correct platform detection.
c_compiler = {'c': self.detect_c_compiler(for_machine)}
is_msvc = isinstance(c_compiler['c'], VisualStudioCCompiler)
if not is_msvc:
c_compiler = {}
arch = detect_cpu_family(c_compiler)
if is_msvc and arch == 'x86':
@ -1372,25 +1431,22 @@ class Environment:
if 'LLVM D compiler' in out:
# LDC seems to require a file
m = self.machines[for_machine]
if m.is_windows() or m.is_cygwin():
if is_msvc:
linker = MSVCDynamicLinker(for_machine, version=version)
else:
# Getting LDC on windows to give useful linker output when not
# doing real work is painfully hard. It ships with a version of
# lld-link, so just assume that we're going to use lld-link
# with it.
_, o, _ = Popen_safe(['lld-link.exe', '--version'])
linker = ClangClDynamicLinker(for_machine, version=search_version(o))
if info.is_windows() or info.is_cygwin():
# Getting LDC on windows to give useful linker output when
# not doing real work is painfully hard. It ships with a
# version of lld-link, so unless we think the user wants
# link.exe, just assume that we're going to use lld-link
# with it.
linker = self._guess_win_linker(
['link' if is_msvc else 'lld-link'],
compilers.LLVMDCompiler, for_machine, use_linker_prefix=False)
else:
with tempfile.NamedTemporaryFile(suffix='.d') as f:
# LDC writes an object file to the current working directory.
# Clean it up.
objectfile = os.path.basename(f.name)[:-1] + 'o'
linker = self._guess_nix_linker(
exelist, for_machine,
compilers.LLVMDCompiler.LINKER_PREFIX,
exelist, compilers.LLVMDCompiler, for_machine,
extra_args=[f.name])
try:
os.unlink(objectfile)
@ -1401,27 +1457,25 @@ class Environment:
exelist, version, for_machine, info, arch,
full_version=full_version, linker=linker)
elif 'gdc' in out:
linker = self._guess_nix_linker(exelist, for_machine, compilers.GnuDCompiler.LINKER_PREFIX)
linker = self._guess_nix_linker(exelist, compilers.GnuDCompiler, for_machine)
return compilers.GnuDCompiler(
exelist, version, for_machine, info, arch, is_cross, exe_wrap,
full_version=full_version, linker=linker)
elif 'The D Language Foundation' in out or 'Digital Mars' in out:
# DMD seems to require a file
m = self.machines[for_machine]
if m.is_windows() or m.is_cygwin():
if info.is_windows() or info.is_cygwin():
if is_msvc:
linker = MSVCDynamicLinker(for_machine, version=version)
linker_cmd = ['link']
elif arch == 'x86':
linker = OptlinkDynamicLinker(for_machine, version=full_version)
linker_cmd = ['optlink']
else:
# DMD ships with lld-link
_, o, _ = Popen_safe(['lld-link.exe', '--version'])
linker = ClangClDynamicLinker(for_machine, version=search_version(o))
linker_cmd = ['lld-link']
linker = self._guess_win_linker(linker_cmd, compilers.DmdDCompiler, for_machine,
use_linker_prefix=False)
else:
with tempfile.NamedTemporaryFile(suffix='.d') as f:
linker = self._guess_nix_linker(
exelist, for_machine,
compilers.LLVMDCompiler.LINKER_PREFIX,
exelist, compilers.DmdDCompiler, for_machine,
extra_args=[f.name])
return compilers.DmdDCompiler(
exelist, version, for_machine, info, arch,
@ -1447,8 +1501,7 @@ class Environment:
# As for 5.0.1 swiftc *requires* a file to check the linker:
with tempfile.NamedTemporaryFile(suffix='.swift') as f:
linker = self._guess_nix_linker(
exelist, for_machine,
compilers.SwiftCompiler.LINKER_PREFIX,
exelist, compilers.SwiftCompiler, for_machine,
extra_args=[f.name])
return compilers.SwiftCompiler(
exelist, version, for_machine, info, is_cross, linker=linker)

@ -247,17 +247,21 @@ class DynamicLinker(metaclass=abc.ABCMeta):
} # type: typing.Dict[str, typing.List[str]]
def _apply_prefix(self, arg: str) -> typing.List[str]:
if isinstance(self.prefix_arg, str):
if self.prefix_arg is None:
return [arg]
elif isinstance(self.prefix_arg, str):
return [self.prefix_arg + arg]
return self.prefix_arg + [arg]
def __init__(self, exelist: typing.List[str], for_machine: mesonlib.MachineChoice,
id_: str, prefix_arg: typing.Union[str, typing.List[str]], *, version: str = 'unknown version'):
id_: str, prefix_arg: typing.Union[str, typing.List[str]],
always_args: typing.List[str], *, version: str = 'unknown version'):
self.exelist = exelist
self.for_machine = for_machine
self.version = version
self.id = id_
self.prefix_arg = prefix_arg
self.always_args = always_args
def __repr__(self) -> str:
return '<{}: v{} `{}`>'.format(type(self).__name__, self.version, ' '.join(self.exelist))
@ -276,7 +280,7 @@ class DynamicLinker(metaclass=abc.ABCMeta):
return mesonlib.is_windows()
def get_always_args(self) -> typing.List[str]:
return []
return self.always_args.copy()
def get_lib_prefix(self) -> str:
return ''
@ -395,6 +399,11 @@ class DynamicLinker(metaclass=abc.ABCMeta):
install_rpath: str) -> typing.List[str]:
return []
def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str,
suffix: str, soversion: str, darwin_versions: typing.Tuple[str, str],
is_shared_module: bool) -> typing.List[str]:
return []
class PosixDynamicLinkerMixin:
@ -419,8 +428,8 @@ class GnuLikeDynamicLinkerMixin:
"""Mixin class for dynamic linkers that provides gnu-like interface.
This acts as a base for the GNU linkers (bfd and gold), the Intel Xild
(which comes with ICC), LLVM's lld, and other linkers like GNU-ld.
This acts as a base for the GNU linkers (bfd and gold), LLVM's lld, and
other linkers like GNU-ld.
"""
_BUILDTYPE_ARGS = {
@ -595,7 +604,7 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker):
return self._apply_prefix('-undefined,error')
def get_always_args(self) -> typing.List[str]:
return self._apply_prefix('-headerpad_max_install_names')
return self._apply_prefix('-headerpad_max_install_names') + super().get_always_args()
def bitcode_args(self) -> typing.List[str]:
return self._apply_prefix('-bitcode_bundle')
@ -656,26 +665,6 @@ class LLVMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, Dyna
pass
class XildLinuxDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker):
"""Representation of Intel's Xild linker.
This is only the linux-like linker which dispatches to Gnu ld.
"""
pass
class XildAppleDynamicLinker(AppleDynamicLinker):
"""Representation of Intel's Xild linker.
This is the apple linker, which dispatches to Apple's ld.
"""
pass
class CcrxDynamicLinker(DynamicLinker):
"""Linker for Renesis CCrx compiler."""
@ -715,7 +704,7 @@ class ArmDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker):
def __init__(self, for_machine: mesonlib.MachineChoice,
*, version: str = 'unknown version'):
super().__init__(['armlink'], for_machine, 'armlink', '',
super().__init__(['armlink'], for_machine, 'armlink', '', [],
version=version)
def get_accepts_rsp(self) -> bool:
@ -802,7 +791,7 @@ class VisualStudioLikeLinkerMixin:
self.machine = machine
def invoked_by_compiler(self) -> bool:
return self.direct
return not self.direct
def get_debug_crt_args(self) -> typing.List[str]:
"""Arguments needed to select a debug crt for the linker.
@ -818,7 +807,7 @@ class VisualStudioLikeLinkerMixin:
return self._apply_prefix('/MACHINE:' + self.machine) + self._apply_prefix('/OUT:' + outputname)
def get_always_args(self) -> typing.List[str]:
return self._apply_prefix('/nologo')
return self._apply_prefix('/nologo') + super().get_always_args()
def get_search_args(self, dirname: str) -> typing.List[str]:
return self._apply_prefix('/LIBPATH:' + dirname)
@ -853,33 +842,35 @@ class MSVCDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker):
"""Microsoft's Link.exe."""
def __init__(self, for_machine: mesonlib.MachineChoice, *,
def __init__(self, for_machine: mesonlib.MachineChoice, always_args: typing.List[str], *,
exelist: typing.Optional[typing.List[str]] = None,
prefix: typing.Union[str, typing.List[str]] = '',
machine: str = 'x86', version: str = 'unknown version'):
machine: str = 'x86', version: str = 'unknown version',
direct: bool = True):
super().__init__(exelist or ['link.exe'], for_machine, 'link',
prefix, machine=machine, version=version)
prefix, always_args, machine=machine, version=version, direct=direct)
class ClangClDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker):
"""Clang's lld-link.exe."""
def __init__(self, for_machine: mesonlib.MachineChoice, *,
def __init__(self, for_machine: mesonlib.MachineChoice, always_args: typing.List[str], *,
exelist: typing.Optional[typing.List[str]] = None,
prefix: typing.Union[str, typing.List[str]] = '',
version: str = 'unknown version'):
super().__init__(exelist or ['lld-link.exe'], for_machine,
'lld-link', prefix, version=version)
machine: str = 'x86', version: str = 'unknown version',
direct: bool = True):
super().__init__(exelist or ['lld-link.exe'], for_machine, 'lld-link',
prefix, always_args, machine=machine, version=version, direct=direct)
class XilinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker):
"""Intel's Xilink.exe."""
def __init__(self, for_machine: mesonlib.MachineChoice,
def __init__(self, for_machine: mesonlib.MachineChoice, always_args: typing.List[str],
*, version: str = 'unknown version'):
super().__init__(['xilink.exe'], for_machine, 'xilink', '', version=version)
super().__init__(['xilink.exe'], for_machine, 'xilink', '', always_args, version=version)
class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker):
@ -936,7 +927,7 @@ class OptlinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker):
*, version: str = 'unknown version'):
# Use optlink instead of link so we don't interfer with other link.exe
# implementations.
super().__init__(['optlink.exe'], for_machine, 'optlink', prefix_arg='', version=version)
super().__init__(['optlink.exe'], for_machine, 'optlink', '', [], version=version)
def get_allow_undefined_args(self) -> typing.List[str]:
return []

@ -621,14 +621,13 @@ def has_broken_rustc() -> bool:
mesonlib.windows_proof_rmtree(dirname)
return pc.returncode != 0
def should_skip_rust() -> bool:
def should_skip_rust(backend: Backend) -> bool:
if not shutil.which('rustc'):
return True
if backend is not Backend.ninja:
return True
if mesonlib.is_windows():
if has_broken_rustc():
return True
if mesonlib.is_windows() and has_broken_rustc():
return True
return False
def detect_tests_to_run(only: typing.List[str]) -> typing.List[typing.Tuple[str, typing.List[Path], bool]]:
@ -666,7 +665,7 @@ def detect_tests_to_run(only: typing.List[str]) -> typing.List[typing.Tuple[str,
('java', 'java', backend is not Backend.ninja or mesonlib.is_osx() or not have_java()),
('C#', 'csharp', skip_csharp(backend)),
('vala', 'vala', backend is not Backend.ninja or not shutil.which(os.environ.get('VALAC', 'valac'))),
('rust', 'rust', should_skip_rust()),
('rust', 'rust', should_skip_rust(backend)),
('d', 'd', backend is not Backend.ninja or not have_d_compiler()),
('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or not have_objc_compiler()),
('objective c++', 'objcpp', backend not in (Backend.ninja, Backend.xcode) or not have_objcpp_compiler()),

@ -291,26 +291,6 @@ def no_pkgconfig():
shutil.which = old_which
ExternalProgram._search = old_search
class PatchModule:
'''
Fancy monkey-patching! Whee! Can't use mock.patch because it only
patches in the local namespace.
'''
def __init__(self, func, name, impl):
self.func = func
assert(isinstance(name, str))
self.func_name = name
self.old_impl = None
self.new_impl = impl
def __enter__(self):
self.old_impl = self.func
exec('{} = self.new_impl'.format(self.func_name))
def __exit__(self, *args):
exec('{} = self.old_impl'.format(self.func_name))
class InternalTests(unittest.TestCase):
@ -442,7 +422,7 @@ class InternalTests(unittest.TestCase):
def test_compiler_args_class_gnuld(self):
cargsfunc = mesonbuild.compilers.CompilerArgs
## Test --start/end-group
linker = mesonbuild.linkers.GnuDynamicLinker([], MachineChoice.HOST, 'fake', '-Wl,')
linker = mesonbuild.linkers.GnuDynamicLinker([], MachineChoice.HOST, 'fake', '-Wl,', [])
gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker)
## Ensure that the fake compiler is never called by overriding the relevant function
gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include']
@ -471,7 +451,7 @@ class InternalTests(unittest.TestCase):
def test_compiler_args_remove_system(self):
cargsfunc = mesonbuild.compilers.CompilerArgs
## Test --start/end-group
linker = mesonbuild.linkers.GnuDynamicLinker([], MachineChoice.HOST, 'fake', '-Wl,')
linker = mesonbuild.linkers.GnuDynamicLinker([], MachineChoice.HOST, 'fake', '-Wl,', [])
gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker)
## Ensure that the fake compiler is never called by overriding the relevant function
gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include']
@ -2316,11 +2296,11 @@ class AllPlatformTests(BasePlatformTests):
if isinstance(cc, intel):
self.assertIsInstance(linker, ar)
if is_osx():
self.assertIsInstance(cc.linker, mesonbuild.linkers.XildAppleDynamicLinker)
self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
elif is_windows():
self.assertIsInstance(cc.linker, mesonbuild.linkers.XilinkDynamicLinker)
else:
self.assertIsInstance(cc.linker, mesonbuild.linkers.XildLinuxDynamicLinker)
self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuDynamicLinker)
if isinstance(cc, msvc):
self.assertTrue(is_windows())
self.assertIsInstance(linker, lib)
@ -4558,6 +4538,29 @@ class WindowsTests(BasePlatformTests):
self.assertTrue('prog.pdb' in files)
def _check_ld(self, name: str, lang: str, expected: str) -> None:
if not shutil.which(name):
raise unittest.SkipTest('Could not find {}.'.format(name))
with mock.patch.dict(os.environ, {'LD': name}):
env = get_fake_env()
try:
comp = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST)
except EnvironmentException:
raise unittest.SkipTest('Could not find a compiler for {}'.format(lang))
self.assertEqual(comp.linker.id, expected)
def test_link_environment_variable_lld_link(self):
self._check_ld('lld-link', 'c', 'lld-link')
def test_link_environment_variable_link(self):
self._check_ld('link', 'c', 'link')
def test_link_environment_variable_optlink(self):
self._check_ld('optlink', 'c', 'optlink')
def test_link_environment_variable_rust(self):
self._check_ld('link', 'rust', 'link')
@unittest.skipUnless(is_osx(), "requires Darwin")
class DarwinTests(BasePlatformTests):
'''
@ -5785,6 +5788,44 @@ c = ['{0}']
self.build()
self.run_tests()
def _check_ld(self, check: str, name: str, lang: str, expected: str) -> None:
if is_sunos():
raise unittest.SkipTest('Solaris currently cannot override the linker.')
if not shutil.which(check):
raise unittest.SkipTest('Could not find {}.'.format(check))
with mock.patch.dict(os.environ, {'LD': name}):
env = get_fake_env()
comp = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST)
if lang != 'rust' and comp.use_linker_args('foo') == []:
raise unittest.SkipTest(
'Compiler {} does not support using alternative linkers'.format(comp.id))
self.assertEqual(comp.linker.id, expected)
def test_ld_environment_variable_bfd(self):
self._check_ld('ld.bfd', 'bfd', 'c', 'GNU ld.bfd')
def test_ld_environment_variable_gold(self):
self._check_ld('ld.gold', 'gold', 'c', 'GNU ld.gold')
def test_ld_environment_variable_lld(self):
self._check_ld('ld.lld', 'lld', 'c', 'lld')
def test_ld_environment_variable_rust(self):
self._check_ld('ld.gold', 'gold', 'rust', 'GNU ld.gold')
def test_ld_environment_variable_cpp(self):
self._check_ld('ld.gold', 'gold', 'cpp', 'GNU ld.gold')
def test_ld_environment_variable_objc(self):
self._check_ld('ld.gold', 'gold', 'objc', 'GNU ld.gold')
def test_ld_environment_variable_objcpp(self):
self._check_ld('ld.gold', 'gold', 'objcpp', 'GNU ld.gold')
def test_ld_environment_variable_fortran(self):
self._check_ld('ld.gold', 'gold', 'fortran', 'GNU ld.gold')
def should_run_cross_arm_tests():
return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm')

Loading…
Cancel
Save