Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust: allow linking with dynamic libstd #14224

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/markdown/Builtin-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ or compiler being used:
| cpp_thread_count | 4 | integer value ≥ 0 | Number of threads to use with emcc when using threads |
| cpp_winlibs | see below | free-form comma-separated list | Standard Windows libs to link against |
| fortran_std | none | [none, legacy, f95, f2003, f2008, f2018] | Fortran language standard to use |
| rust_dynamic_std | false | true, false | Whether to link dynamically to the Rust standard library *(Added in 1.8.0)* |
| cuda_ccbindir | | filesystem path | CUDA non-default toolchain directory to use (-ccbin) *(Added in 0.57.1)* |

The default values of `c_winlibs` and `cpp_winlibs` are in
Expand Down
8 changes: 8 additions & 0 deletions docs/markdown/snippets/rust-dynamic-std.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## New experimental option `rust_dynamic_std`

A new option `rust_dynamic_std` can be used to link Rust programs so
that they use a dynamic library for the Rust `libstd`.

Right now, C ABI crates (corresponding to Rust crate types `cdylib` and
`staticlib`) cannot be produced if `rust_dynamic_std` is true, but this
may change in the future.
17 changes: 15 additions & 2 deletions mesonbuild/backend/ninjabackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2115,13 +2115,26 @@ def _link_library(libname: str, static: bool, bundle: bool = False):
and dep.rust_crate_type == 'dylib'
for dep in target_deps)

if target.rust_crate_type in {'dylib', 'proc-macro'} or has_rust_shared_deps:
if target.rust_crate_type in {'cdylib', 'staticlib'} \
and target.get_option(OptionKey('rust_dynamic_std')):
# cdylib and staticlib crates always include a copy of the Rust
# libstd, therefore it is not possible to also link it dynamically.
# The options to avoid this (-Z staticlib-allow-rdylib-deps and
# -Z staticlib-prefer-dynamic) are not yet stable; alternatively,
# one could use "--emit obj" (implemented in the pull request at
# https://github.com/mesonbuild/meson/pull/11213) or "--emit rlib"
# (officially not recommended for linking with C programs).
raise MesonException('rust_dynamic_std does not support cdylib and staticlib crates yet')

if target.rust_crate_type in {'dylib', 'proc-macro'} or has_rust_shared_deps \
or target.get_option(OptionKey('rust_dynamic_std')):
# add prefer-dynamic if any of the Rust libraries we link
# against are dynamic or this is a dynamic library itself,
# otherwise we'll end up with multiple implementations of libstd.
args += ['-C', 'prefer-dynamic']

if isinstance(target, build.SharedLibrary) or has_shared_deps:
if isinstance(target, build.SharedLibrary) or has_shared_deps \
or target.get_option(OptionKey('rust_dynamic_std')):
args += self.get_build_rpath_args(target, rustc)

proc_macro_dylib_path = None
Expand Down
16 changes: 11 additions & 5 deletions mesonbuild/compilers/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,15 @@ def get_optimization_args(self, optimization_level: str) -> T.List[str]:
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)
# add rustc's sysroot to account for rustup installations
args, to_remove = self.linker.build_rpath_args(env, build_dir, from_dir, rpath_paths,
build_rpath, install_rpath,
[self.get_target_libdir()])

# ... 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()}')
rustc_rpath_args.append('link-arg=' + arg)
return rustc_rpath_args, to_remove

def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str],
Expand Down Expand Up @@ -243,6 +243,12 @@ def get_options(self) -> MutableKeyedOptionDictType:
'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 programs to a dynamic libstd',
False)

return opts

def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]:
Expand Down
40 changes: 25 additions & 15 deletions mesonbuild/linkers/linkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def get_coverage_link_args(self) -> T.List[str]:

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]]:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
return ([], set())

def thread_link_flags(self, env: 'Environment') -> T.List[str]:
Expand Down Expand Up @@ -283,7 +283,7 @@ def bitcode_args(self) -> T.List[str]:

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]]:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
return ([], set())

def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str,
Expand Down Expand Up @@ -686,11 +686,11 @@ def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str,

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]]:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
m = env.machines[self.for_machine]
if m.is_windows() or m.is_cygwin():
return ([], set())
if not rpath_paths and not install_rpath and not build_rpath:
if not rpath_paths and not install_rpath and not build_rpath and not extra_paths:
return ([], set())
args: T.List[str] = []
origin_placeholder = '$ORIGIN'
Expand All @@ -707,6 +707,8 @@ def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str,
all_paths.add(build_rpath)
for p in build_rpath.split(':'):
rpath_dirs_to_remove.add(p.encode('utf8'))
if extra_paths:
all_paths.update(extra_paths)

# TODO: should this actually be "for (dragonfly|open)bsd"?
if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd():
Expand Down Expand Up @@ -843,8 +845,8 @@ def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str,

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]]:
if not rpath_paths and not install_rpath and not build_rpath:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
if not rpath_paths and not install_rpath and not build_rpath and not extra_paths:
return ([], set())
args: T.List[str] = []
rpath_dirs_to_remove: T.Set[bytes] = set()
Expand All @@ -855,6 +857,8 @@ def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str,
all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths])
if build_rpath != '':
all_paths.update(build_rpath.split(':'))
if extra_paths:
all_paths.update(extra_paths)
for rp in all_paths:
rpath_dirs_to_remove.add(rp.encode('utf8'))
args.extend(self._apply_prefix('-rpath,' + rp))
Expand Down Expand Up @@ -991,7 +995,7 @@ def get_asneeded_args(self) -> T.List[str]:

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]]:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
return ([], set())


Expand Down Expand Up @@ -1069,7 +1073,7 @@ def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str,

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]]:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
return ([], set())

class CompCertDynamicLinker(DynamicLinker):
Expand Down Expand Up @@ -1112,7 +1116,7 @@ def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str,

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]]:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
return ([], set())

class TIDynamicLinker(DynamicLinker):
Expand Down Expand Up @@ -1218,15 +1222,17 @@ class NAGDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker):

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]]:
if not rpath_paths and not install_rpath and not build_rpath:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
if not rpath_paths and not install_rpath and not build_rpath and not extra_paths:
return ([], set())
args: T.List[str] = []
origin_placeholder = '$ORIGIN'
processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir)
all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths])
if build_rpath != '':
all_paths.add(build_rpath)
if extra_paths:
all_paths.update(extra_paths)
for rp in all_paths:
args.extend(self._apply_prefix('-Wl,-Wl,,-rpath,,' + rp))

Expand Down Expand Up @@ -1263,7 +1269,7 @@ def get_std_shared_lib_args(self) -> T.List[str]:

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]]:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
if not env.machines[self.for_machine].is_windows():
return (['-R' + os.path.join(build_dir, p) for p in rpath_paths], set())
return ([], set())
Expand Down Expand Up @@ -1474,8 +1480,8 @@ def fatal_warnings(self) -> T.List[str]:

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]]:
if not rpath_paths and not install_rpath and not build_rpath:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
if not rpath_paths and not install_rpath and not build_rpath and not extra_paths:
return ([], set())
processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir)
all_paths = mesonlib.OrderedSet([os.path.join('$ORIGIN', p) for p in processed_rpaths])
Expand All @@ -1486,6 +1492,8 @@ def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str,
all_paths.add(build_rpath)
for p in build_rpath.split(':'):
rpath_dirs_to_remove.add(p.encode('utf8'))
if extra_paths:
all_paths.update(extra_paths)

# In order to avoid relinking for RPATH removal, the binary needs to contain just
# enough space in the ELF header to hold the final installation RPATH.
Expand Down Expand Up @@ -1544,7 +1552,7 @@ def get_link_whole_for(self, args: T.List[str]) -> T.List[str]:

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]]:
install_rpath: str, extra_paths: T.Optional[T.List[str]] = None) -> T.Tuple[T.List[str], T.Set[bytes]]:
all_paths: mesonlib.OrderedSet[str] = mesonlib.OrderedSet()
# install_rpath first, followed by other paths, and the system path last
if install_rpath != '':
Expand All @@ -1565,6 +1573,8 @@ def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str,
for p in sys_path:
if os.path.isdir(p):
all_paths.add(p)
if extra_paths:
all_paths.update(extra_paths)
return (self._apply_prefix('-blibpath:' + ':'.join(all_paths)), set())

def thread_flags(self, env: 'Environment') -> T.List[str]:
Expand Down
6 changes: 6 additions & 0 deletions test cases/rust/1 basic/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ e = executable('rust-program', 'prog.rs',
)
test('rusttest', e)

e = executable('rust-dynamic', 'prog.rs',
override_options: {'rust_dynamic_std': true},
install : true
)
test('rusttest-dynamic', e)

subdir('subdir')

# this should fail due to debug_assert
Expand Down
4 changes: 3 additions & 1 deletion test cases/rust/1 basic/test.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
{"type": "exe", "file": "usr/bin/rust-program"},
{"type": "pdb", "file": "usr/bin/rust-program"},
{"type": "exe", "file": "usr/bin/rust-program2"},
{"type": "pdb", "file": "usr/bin/rust-program2"}
{"type": "pdb", "file": "usr/bin/rust-program2"},
{"type": "exe", "file": "usr/bin/rust-dynamic"},
{"type": "pdb", "file": "usr/bin/rust-dynamic"}
]
}
Loading