From f41cf5289a75674bb4f1e7ab63a1bc8486e8b328 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 4 Feb 2025 18:16:40 +0100 Subject: [PATCH 1/3] linkers: add extra_paths to build_rpath_args Allow adding extra directories to the rpath. Rust needs this when Rustup is in use. Signed-off-by: Paolo Bonzini --- mesonbuild/linkers/linkers.py | 40 ++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/mesonbuild/linkers/linkers.py b/mesonbuild/linkers/linkers.py index 176fb3348204..66f500a38975 100644 --- a/mesonbuild/linkers/linkers.py +++ b/mesonbuild/linkers/linkers.py @@ -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]: @@ -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, @@ -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' @@ -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(): @@ -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() @@ -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)) @@ -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()) @@ -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): @@ -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): @@ -1218,8 +1222,8 @@ 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' @@ -1227,6 +1231,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.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)) @@ -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()) @@ -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]) @@ -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. @@ -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 != '': @@ -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]: From f084bca291e209dd5a3ca5594211f8c653feb32a Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 4 Feb 2025 18:18:30 +0100 Subject: [PATCH 2/3] compilers: add Rustup libdir even if there is no other rpath argument RustCompiler.build_rpath_args works by appending the directory to the arguments computed by self.linker.build_rpath_args. This does not work if there is no argument to begin with, which happens for example in program crates. Use the new extra_paths argument to force inclusion of the libdir into the rpath of the binary, even in that case. Signed-off-by: Paolo Bonzini --- mesonbuild/compilers/rust.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index aacdc07d7ccc..5c1ed01c86c0 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -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], From 273198ee8fba09156db483205774334b4db42992 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 4 Feb 2025 17:53:44 +0100 Subject: [PATCH 3/3] rust: add rust_dynamic_std option As an initial implementation, simply adding "-C prefer-dynamic" works for binary crates (as well as dylib and proc-macro that already used it). In the future this could be extended to other crate types. For more information see the comment in the changed file, as well as https://github.com/mesonbuild/meson/issues/8828 and https://github.com/mesonbuild/meson/issues/14215. Signed-off-by: Paolo Bonzini --- docs/markdown/Builtin-options.md | 1 + docs/markdown/snippets/rust-dynamic-std.md | 8 ++++++++ mesonbuild/backend/ninjabackend.py | 17 +++++++++++++++-- mesonbuild/compilers/rust.py | 6 ++++++ test cases/rust/1 basic/meson.build | 6 ++++++ test cases/rust/1 basic/test.json | 4 +++- 6 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 docs/markdown/snippets/rust-dynamic-std.md diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md index ffbab47d879d..6c4cd189e7bc 100644 --- a/docs/markdown/Builtin-options.md +++ b/docs/markdown/Builtin-options.md @@ -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 diff --git a/docs/markdown/snippets/rust-dynamic-std.md b/docs/markdown/snippets/rust-dynamic-std.md new file mode 100644 index 000000000000..63d4cfccd655 --- /dev/null +++ b/docs/markdown/snippets/rust-dynamic-std.md @@ -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. diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 24758866c6b5..7f68b0c549c5 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -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 diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 5c1ed01c86c0..9f5089f7cbe8 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -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]: diff --git a/test cases/rust/1 basic/meson.build b/test cases/rust/1 basic/meson.build index f422beb74857..00bd2124843d 100644 --- a/test cases/rust/1 basic/meson.build +++ b/test cases/rust/1 basic/meson.build @@ -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 diff --git a/test cases/rust/1 basic/test.json b/test cases/rust/1 basic/test.json index 95e6ced7e4ba..3cbdefa78af8 100644 --- a/test cases/rust/1 basic/test.json +++ b/test cases/rust/1 basic/test.json @@ -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"} ] }