diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 17ac421ea3db..ba614fd564f7 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1948,6 +1948,49 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: args += output linkdirs = mesonlib.OrderedSet() external_deps = target.external_deps.copy() + + # Have we already injected msvc-crt args? + # + # If we don't have A C, C++, or Fortran compiler that is + # VisualStudioLike treat this as if we've already injected them + # + # We handle this here rather than in the rust compiler because in + # general we don't want to link rust targets to a non-default crt. + # However, because of the way that MSCRTs work you can only link to one + # per target, so if A links to the debug one, and B links to the normal + # one you can't link A and B. Rust is hardcoded to the default one, + # so if we compile C/C++ code and link against a non-default MSCRT then + # linking will fail. We can work around this by injecting MSCRT link + # arguments early in the rustc command line + # https://github.com/rust-lang/rust/issues/39016 + crt_args_injected = not any(x is not None and x.get_argument_syntax() == 'msvc' for x in + (self.environment.coredata.compilers[target.for_machine].get(l) + for l in ['c', 'cpp', 'fortran'])) + + crt_link_args: T.List[str] = [] + try: + buildtype = self.environment.coredata.options[OptionKey('buildtype')].value + crt = self.environment.coredata.options[OptionKey('b_vscrt')].value + is_debug = buildtype == 'debug' + + if crt == 'from_buildtype': + crt = 'mdd' if is_debug else 'md' + elif crt == 'static_from_buildtype': + crt = 'mtd' if is_debug else 'mt' + + if crt == 'mdd': + crt_link_args = ['-l', 'static=msvcrtd'] + elif crt == 'md': + # this is the default, no need to inject anything + crt_args_injected = True + elif crt == 'mtd': + crt_link_args = ['-l', 'static=libcmtd'] + elif crt == 'mt': + crt_link_args = ['-l', 'static=libcmt'] + + except KeyError: + crt_args_injected = True + # TODO: we likely need to use verbatim to handle name_prefix and name_suffix for d in target.link_targets: linkdirs.add(d.subdir) @@ -1961,7 +2004,13 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: d_name = self._get_rust_dependency_name(target, d) args += ['--extern', '{}={}'.format(d_name, os.path.join(d.subdir, d.filename))] project_deps.append(RustDep(d_name, self.rust_crates[d.name].order)) - elif isinstance(d, build.StaticLibrary): + continue + + if not crt_args_injected and not {'c', 'cpp', 'fortran'}.isdisjoint(d.compilers): + args += crt_link_args + crt_args_injected = True + + if isinstance(d, build.StaticLibrary): # Rustc doesn't follow Meson's convention that static libraries # are called .a, and import libraries are .lib, so we have to # manually handle that. @@ -2001,6 +2050,10 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: args += ['--extern', '{}={}'.format(d_name, os.path.join(d.subdir, d.filename))] project_deps.append(RustDep(d_name, self.rust_crates[d.name].order)) else: + if not crt_args_injected and not {'c', 'cpp', 'fortran'}.isdisjoint(d.compilers): + crt_args_injected = True + crt_args_injected = True + if rustc.linker.id in {'link', 'lld-link'}: if verbatim: # If we can use the verbatim modifier, then everything is great diff --git a/test cases/rust/20 rust and cpp/test.json b/test cases/rust/20 rust and cpp/test.json new file mode 100644 index 000000000000..c072a6c943de --- /dev/null +++ b/test cases/rust/20 rust and cpp/test.json @@ -0,0 +1,15 @@ +{ + "matrix": { + "options": { + "b_vscrt": [ + { "val": "none" }, + { "val": "mdd" }, + { "val": "md" }, + { "val": "mtd" }, + { "val": "mt" }, + { "val": "from_buildtype" }, + { "val": "static_from_buildtype" } + ] + } + } +}