aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/compilers/rust.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/compilers/rust.py')
-rw-r--r--mesonbuild/compilers/rust.py289
1 files changed, 223 insertions, 66 deletions
diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py
index d0d2e69..ab0706d 100644
--- a/mesonbuild/compilers/rust.py
+++ b/mesonbuild/compilers/rust.py
@@ -5,19 +5,19 @@
from __future__ import annotations
import functools
-import subprocess, os.path
+import os.path
import textwrap
import re
import typing as T
from .. import options
-from ..mesonlib import EnvironmentException, MesonException, Popen_safe_logged
+from ..mesonlib import EnvironmentException, MesonException, Popen_safe_logged, version_compare
+from ..linkers.linkers import VisualStudioLikeLinkerMixin
from ..options import OptionKey
-from .compilers import Compiler, CompileCheckMode, clike_debug_args
+from .compilers import Compiler, CompileCheckMode, clike_debug_args, is_library
if T.TYPE_CHECKING:
from ..options import MutableKeyedOptionDictType
- from ..envconfig import MachineInfo
from ..environment import Environment # noqa: F401
from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
@@ -64,6 +64,15 @@ def get_rustup_run_and_args(exelist: T.List[str]) -> T.Optional[T.Tuple[T.List[s
except StopIteration:
return None
+def rustc_link_args(args: T.List[str]) -> T.List[str]:
+ if not args:
+ return args
+ rustc_args: T.List[str] = []
+ for arg in args:
+ rustc_args.append('-C')
+ rustc_args.append(f'link-arg={arg}')
+ return rustc_args
+
class RustCompiler(Compiler):
# rustc doesn't invoke the compiler itself, it doesn't need a LINKER_PREFIX
@@ -78,36 +87,42 @@ class RustCompiler(Compiler):
'everything': ['-W', 'warnings'],
}
- # Those are static libraries, but we use dylib= here as workaround to avoid
- # rust --tests to use /WHOLEARCHIVE.
- # https://github.com/rust-lang/rust/issues/116910
+ allow_nightly: bool
+
+ # libcore can be compiled with either static or dynamic CRT, so disable
+ # both of them just in case.
MSVCRT_ARGS: T.Mapping[str, T.List[str]] = {
'none': [],
- 'md': [], # this is the default, no need to inject anything
- 'mdd': ['-l', 'dylib=msvcrtd'],
- 'mt': ['-l', 'dylib=libcmt'],
- 'mtd': ['-l', 'dylib=libcmtd'],
+ 'md': ['-Clink-arg=/nodefaultlib:libcmt', '-Clink-arg=/defaultlib:msvcrt'],
+ 'mdd': ['-Clink-arg=/nodefaultlib:libcmt', '-Clink-arg=/nodefaultlib:msvcrt', '-Clink-arg=/defaultlib:msvcrtd'],
+ 'mt': ['-Clink-arg=/defaultlib:libcmt', '-Clink-arg=/nodefaultlib:msvcrt'],
+ 'mtd': ['-Clink-arg=/nodefaultlib:libcmt', '-Clink-arg=/nodefaultlib:msvcrt', '-Clink-arg=/defaultlib:libcmtd'],
}
def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
- is_cross: bool, info: 'MachineInfo',
- full_version: T.Optional[str] = None,
+ env: Environment, full_version: T.Optional[str] = None,
linker: T.Optional['DynamicLinker'] = None):
- super().__init__([], exelist, version, for_machine, info,
- is_cross=is_cross, full_version=full_version,
- linker=linker)
+ super().__init__([], exelist, version, for_machine, env,
+ full_version=full_version, linker=linker)
self.rustup_run_and_args: T.Optional[T.Tuple[T.List[str], T.List[str]]] = get_rustup_run_and_args(exelist)
- self.base_options.update({OptionKey(o) for o in ['b_colorout', 'b_ndebug']})
- if 'link' in self.linker.id:
+ self.base_options.update({OptionKey(o) for o in ['b_colorout', 'b_coverage', 'b_ndebug', 'b_pgo']})
+ if isinstance(self.linker, VisualStudioLikeLinkerMixin):
self.base_options.add(OptionKey('b_vscrt'))
self.native_static_libs: T.List[str] = []
self.is_beta = '-beta' in full_version
self.is_nightly = '-nightly' in full_version
+ self.has_check_cfg = version_compare(version, '>=1.80.0')
+
+ def init_from_options(self) -> None:
+ nightly_opt = self.get_compileropt_value('nightly', None)
+ if nightly_opt == 'enabled' and not self.is_nightly:
+ raise EnvironmentException(f'Rust compiler {self.name_string()} is not a nightly compiler as required by the "nightly" option.')
+ self.allow_nightly = nightly_opt != 'disabled' and self.is_nightly
def needs_static_linker(self) -> bool:
return False
- def sanity_check(self, work_dir: str, environment: Environment) -> None:
+ def sanity_check(self, work_dir: str) -> None:
source_name = os.path.join(work_dir, 'sanity.rs')
output_name = os.path.join(work_dir, 'rusttest.exe')
cmdlist = self.exelist.copy()
@@ -141,17 +156,7 @@ class RustCompiler(Compiler):
if pc.returncode != 0:
raise EnvironmentException(f'Rust compiler {self.name_string()} cannot compile programs.')
self._native_static_libs(work_dir, source_name)
- if self.is_cross:
- if not environment.has_exe_wrapper():
- # Can't check if the binaries run so we have to assume they do
- return
- cmdlist = environment.exe_wrapper.get_command() + [output_name]
- else:
- cmdlist = [output_name]
- pe = subprocess.Popen(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- pe.wait()
- if pe.returncode != 0:
- raise EnvironmentException(f'Executables created by Rust compiler {self.name_string()} are not runnable.')
+ self.run_sanity_check([output_name], work_dir)
def _native_static_libs(self, work_dir: str, source_name: str) -> None:
# Get libraries needed to link with a Rust staticlib
@@ -165,6 +170,9 @@ class RustCompiler(Compiler):
# no match and kernel == none (i.e. baremetal) is a valid use case.
# return and let native_static_libs list empty
return
+ if self.info.system == 'emscripten':
+ # no match and emscripten is valid after rustc 1.84
+ return
raise EnvironmentException('Failed to find native-static-libs in Rust compiler output.')
# Exclude some well known libraries that we don't need because they
# are always part of C/C++ linkers. Rustc probably should not print
@@ -192,10 +200,69 @@ class RustCompiler(Compiler):
return stdo.split('\n', maxsplit=1)[0]
@functools.lru_cache(maxsize=None)
- def get_crt_static(self) -> bool:
+ def get_cfgs(self) -> T.List[str]:
cmd = self.get_exelist(ccache=False) + ['--print', 'cfg']
p, stdo, stde = Popen_safe_logged(cmd)
- return bool(re.search('^target_feature="crt-static"$', stdo, re.MULTILINE))
+ return stdo.splitlines()
+
+ @functools.lru_cache(maxsize=None)
+ def get_crt_static(self) -> bool:
+ return 'target_feature="crt-static"' in self.get_cfgs()
+
+ def get_nightly(self, target: T.Optional[BuildTarget]) -> bool:
+ if not target:
+ return self.allow_nightly
+ key = self.form_compileropt_key('nightly')
+ nightly_opt = self.environment.coredata.get_option_for_target(target, key)
+ if nightly_opt == 'enabled' and not self.is_nightly:
+ raise EnvironmentException(f'Rust compiler {self.name_string()} is not a nightly compiler as required by the "nightly" option.')
+ return nightly_opt != 'disabled' and self.is_nightly
+
+ def sanitizer_link_args(self, target: T.Optional[BuildTarget], value: T.List[str]) -> T.List[str]:
+ # Sanitizers are not supported yet for Rust code. Nightly supports that
+ # with -Zsanitizer=, but procedural macros cannot use them. But even if
+ # Rust code cannot be instrumented, we can link in the sanitizer libraries
+ # for the sake of C/C++ code
+ return rustc_link_args(super().sanitizer_link_args(target, value))
+
+ @functools.lru_cache(maxsize=None)
+ def has_verbatim(self) -> bool:
+ if version_compare(self.version, '< 1.67.0'):
+ return False
+ # GNU ld support '-l:PATH'
+ if 'ld.' in self.linker.id and self.linker.id != 'ld.wasm':
+ return True
+ # -l:+verbatim does not work (yet?) with MSVC link or Apple ld64
+ # (https://github.com/rust-lang/rust/pull/138753). For ld64, it
+ # works together with -l:+whole_archive because -force_load (the macOS
+ # equivalent of --whole-archive), receives the full path to the library
+ # being linked. However, Meson uses "bundle", not "whole_archive".
+ return False
+
+ def lib_file_to_l_arg(self, libname: str) -> T.Optional[str]:
+ """Undo the effects of -l on the filename, returning the
+ argument that can be passed to -l, or None if the
+ library name is not supported."""
+ if not is_library(libname):
+ return None
+ libname, ext = os.path.splitext(libname)
+
+ # On Windows, rustc's -lfoo searches either foo.lib or libfoo.a.
+ # Elsewhere, it searches both static and shared libraries and always with
+ # the "lib" prefix; for simplicity just skip .lib on non-Windows.
+ if self.info.is_windows():
+ if ext == '.lib':
+ return libname
+ if ext != '.a':
+ return None
+ else:
+ if ext == '.lib':
+ return None
+
+ if not libname.startswith('lib'):
+ return None
+ libname = libname[3:]
+ return libname
def get_debug_args(self, is_debug: bool) -> T.List[str]:
return clike_debug_args[is_debug]
@@ -203,19 +270,13 @@ class RustCompiler(Compiler):
def get_optimization_args(self, optimization_level: str) -> T.List[str]:
return rust_optimization_args[optimization_level]
- def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str,
- rpath_paths: T.Tuple[str, ...], build_rpath: str,
- install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]:
- args, to_remove = super().build_rpath_args(env, build_dir, from_dir, rpath_paths,
- build_rpath, install_rpath)
-
- # ... but then add rustc's sysroot to account for rustup
- # installations
- rustc_rpath_args = []
- for arg in args:
- rustc_rpath_args.append('-C')
- rustc_rpath_args.append(f'link-arg={arg}:{self.get_target_libdir()}')
- return rustc_rpath_args, to_remove
+ def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget,
+ extra_paths: T.Optional[T.List[str]] = None
+ ) -> T.Tuple[T.List[str], T.Set[bytes]]:
+ # add rustc's sysroot to account for rustup installations
+ args, to_remove = super().build_rpath_args(
+ build_dir, from_dir, target, [self.get_target_libdir()])
+ return rustc_link_args(args), to_remove
def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str],
build_dir: str) -> T.List[str]:
@@ -247,6 +308,18 @@ class RustCompiler(Compiler):
'none',
choices=['none', '2015', '2018', '2021', '2024'])
+ key = self.form_compileropt_key('dynamic_std')
+ opts[key] = options.UserBooleanOption(
+ self.make_option_name(key),
+ 'Whether to link Rust build targets to a dynamic libstd',
+ False)
+
+ key = self.form_compileropt_key('nightly')
+ opts[key] = options.UserFeatureOption(
+ self.make_option_name(key),
+ "Nightly Rust compiler (enabled=required, disabled=don't use nightly feature, auto=use nightly feature if available)",
+ 'auto')
+
return opts
def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]:
@@ -255,9 +328,9 @@ class RustCompiler(Compiler):
# provided by the linker flags.
return []
- def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]:
+ def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]:
args = []
- std = self.get_compileropt_value('std', env, target, subproject)
+ std = self.get_compileropt_value('std', target, subproject)
assert isinstance(std, str)
if std != 'none':
args.append('--edition=' + std)
@@ -268,8 +341,11 @@ class RustCompiler(Compiler):
return []
def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]:
- if self.linker.id not in {'link', 'lld-link'}:
+ if not isinstance(self.linker, VisualStudioLikeLinkerMixin):
return []
+ # Rustc always use non-debug Windows runtime. Inject the one selected
+ # by Meson options instead.
+ # https://github.com/rust-lang/rust/issues/39016
return self.MSVCRT_ARGS[self.get_crt_val(crt_val, buildtype)]
def get_colorout_args(self, colortype: str) -> T.List[str]:
@@ -277,13 +353,65 @@ class RustCompiler(Compiler):
return [f'--color={colortype}']
raise MesonException(f'Invalid color type for rust {colortype}')
+ @functools.lru_cache(maxsize=None)
def get_linker_always_args(self) -> T.List[str]:
- args: T.List[str] = []
- # Rust is super annoying, calling -C link-arg foo does not work, it has
- # to be -C link-arg=foo
- for a in super().get_linker_always_args():
- args.extend(['-C', f'link-arg={a}'])
- return args
+ return rustc_link_args(super().get_linker_always_args()) + ['-Cdefault-linker-libraries']
+
+ def get_embed_bitcode_args(self, bitcode: bool, lto: bool) -> T.List[str]:
+ if bitcode:
+ return ['-C', 'embed-bitcode=yes']
+ elif lto:
+ return []
+ else:
+ return ['-C', 'embed-bitcode=no']
+
+ def get_lto_compile_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0,
+ mode: str = 'default') -> T.List[str]:
+ if target.rust_crate_type in {'dylib', 'proc-macro'}:
+ return []
+
+ # TODO: what about -Clinker-plugin-lto?
+ rustc_lto = 'lto=thin' if mode == 'thin' else 'lto'
+ return ['-C', rustc_lto]
+
+ def get_lto_link_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0,
+ mode: str = 'default', thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]:
+ # no need to specify anything because the rustc command line
+ # includes the result of get_lto_compile_args()
+ return []
+
+ def get_lto_obj_cache_path(self, path: str) -> T.List[str]:
+ return rustc_link_args(super().get_lto_obj_cache_path(path))
+
+ def get_coverage_args(self) -> T.List[str]:
+ return ['-C', 'instrument-coverage']
+
+ def get_coverage_link_args(self) -> T.List[str]:
+ return rustc_link_args(super().get_coverage_link_args())
+
+ def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]:
+ return rustc_link_args(super().gen_vs_module_defs_args(defsfile))
+
+ def get_profile_generate_args(self) -> T.List[str]:
+ return ['-C', 'profile-generate']
+
+ def get_profile_use_args(self) -> T.List[str]:
+ return ['-C', 'profile-use']
+
+ @functools.lru_cache(maxsize=None)
+ def get_asneeded_args(self) -> T.List[str]:
+ return rustc_link_args(super().get_asneeded_args())
+
+ def bitcode_args(self) -> T.List[str]:
+ return ['-C', 'embed-bitcode=yes']
+
+ @functools.lru_cache(maxsize=None)
+ def headerpad_args(self) -> T.List[str]:
+ return rustc_link_args(super().headerpad_args())
+
+ @functools.lru_cache(maxsize=None)
+ def get_allow_undefined_link_args(self) -> T.List[str]:
+ return rustc_link_args(super().get_allow_undefined_link_args())
def get_werror_args(self) -> T.List[str]:
# Use -D warnings, which makes every warning not explicitly allowed an
@@ -303,11 +431,11 @@ class RustCompiler(Compiler):
# pic is on by rustc
return []
- def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]:
+ def get_assert_args(self, disable: bool) -> T.List[str]:
action = "no" if disable else "yes"
return ['-C', f'debug-assertions={action}', '-C', 'overflow-checks=no']
- def get_rust_tool(self, name: str, env: Environment) -> T.List[str]:
+ def get_rust_tool(self, name: str) -> T.List[str]:
if self.rustup_run_and_args:
rustup_exelist, args = self.rustup_run_and_args
# do not use extend so that exelist is copied
@@ -317,7 +445,7 @@ class RustCompiler(Compiler):
args = self.get_exe_args()
from ..programs import find_external_program
- for prog in find_external_program(env, self.for_machine, exelist[0], exelist[0],
+ for prog in find_external_program(self.environment, self.for_machine, exelist[0], exelist[0],
[exelist[0]], allow_default_for_cross=False):
exelist[0] = prog.path
break
@@ -326,22 +454,32 @@ class RustCompiler(Compiler):
return exelist + args
- def has_multi_arguments(self, args: T.List[str], env: Environment) -> T.Tuple[bool, bool]:
- return self.compiles('fn main { std::process::exit(0) };\n', env, extra_args=args, mode=CompileCheckMode.COMPILE)
+ def has_multi_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]:
+ return self.compiles('fn main() { std::process::exit(0) }\n', extra_args=args, mode=CompileCheckMode.COMPILE)
- def has_multi_link_arguments(self, args: T.List[str], env: Environment) -> T.Tuple[bool, bool]:
- args = self.linker.fatal_warnings() + args
- return self.compiles('fn main { std::process::exit(0) };\n', env, extra_args=args, mode=CompileCheckMode.LINK)
+ def has_multi_link_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]:
+ args = rustc_link_args(self.linker.fatal_warnings()) + args
+ return self.compiles('fn main() { std::process::exit(0) }\n', extra_args=args, mode=CompileCheckMode.LINK)
@functools.lru_cache(maxsize=None)
- def get_rustdoc(self, env: 'Environment') -> T.Optional[RustdocTestCompiler]:
- exelist = self.get_rust_tool('rustdoc', env)
+ def get_rustdoc(self) -> T.Optional[RustdocTestCompiler]:
+ exelist = self.get_rust_tool('rustdoc')
if not exelist:
return None
return RustdocTestCompiler(exelist, self.version, self.for_machine,
- self.is_cross, self.info, full_version=self.full_version,
- linker=self.linker)
+ self.environment,
+ full_version=self.full_version,
+ linker=self.linker, rustc=self)
+
+ def enable_env_set_args(self) -> T.Optional[T.List[str]]:
+ '''Extra arguments to enable --env-set support in rustc.
+ Returns None if not supported.
+ '''
+ if version_compare(self.version, '>= 1.76') and self.allow_nightly:
+ return ['-Z', 'unstable-options']
+ return None
+
class ClippyRustCompiler(RustCompiler):
@@ -361,6 +499,25 @@ class RustdocTestCompiler(RustCompiler):
id = 'rustdoc --test'
+ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
+ env: Environment, full_version: T.Optional[str],
+ linker: T.Optional['DynamicLinker'], rustc: RustCompiler):
+ super().__init__(exelist, version, for_machine,
+ env, full_version, linker)
+ self.rustc = rustc
+
+ @functools.lru_cache(maxsize=None)
+ def get_sysroot(self) -> str:
+ return self.rustc.get_sysroot()
+
+ @functools.lru_cache(maxsize=None)
+ def get_target_libdir(self) -> str:
+ return self.rustc.get_target_libdir()
+
+ @functools.lru_cache(maxsize=None)
+ def get_cfgs(self) -> T.List[str]:
+ return self.rustc.get_cfgs()
+
def get_debug_args(self, is_debug: bool) -> T.List[str]:
return []