Repository: mesonbuild/meson

Issue:
Cross compiling D project fails with "Unable to determine dynamic linker"
**Describe the bug**
When trying to cross compile a D library with 64 bit Arch Linux as build machine and 32 bit Windows as host machine I get "Unable to determine dynamic linker" error.

To provide binaries needed from cross compilation I use [MXE](https://mxe.cc/).

**To Reproduce**
`meson.build`:  
```meson
project('libfoobar', 'd',
  version: '0.0.1',
)

libfoobar = library(
    'foobar',
    [
      'source/foobar/lib.d',
      'source/foobar/types.d',
      'source/foobar/util.d',
    ],
    include_directories: ['source'],
    version: meson.version(),
    soversion : '0',
)

libfoobar_dep = declare_dependency(
    include_directories: ['include'],
    link_with: libfoobar,
)
```

`lin64-win32.conf`:  
```ini
[host_machine]
system = 'windows'
cpu_family = 'x86'
cpu = 'i686'
endian = 'little'


[binaries]
ar = 'i686-w64-mingw32.static-ar'
c = 'i686-w64-mingw32.static-gcc'
cpp = 'i686-w64-mingw32.static-g++'
d = '/home/nawordar/apps/ldc2/bin/ldc2'
ld = 'i686-w64-mingw32.static-ld.bfd'
pkgconfig = 'i686-w64-mingw32.static-pkg-config'
strip = 'i686-w64-mingw32.static-strip'


[properties]
# d_args = ['--linker=lld']
```

```shell
❯ meson build_win32 --cross-file=lin64-win32.conf 
The Meson build system
Version: 0.54.3
Source dir: /home/nawordar/projects/libfoobar
Build dir: /home/nawordar/projects/libfoobar/build_win32
Build type: cross build
Project name: libfoobar
Project version: 0.0.1
D compiler for the build machine: ldc2 (llvm 1.22.0 "LDC - the LLVM D compiler (1.22.0):")
D linker for the build machine: ldc2 ld.gold 2.34.0

meson.build:1:0: ERROR: Unable to determine dynamic linker

A full log can be found at /home/nawordar/projects/libfoobar/build_win32/meson-logs/meson-log.txt
```

I have noticed, that when I add `d_args = ['--linker=lld']` to `[properties]`, meson succeeds, but ninja fails with:  
```shell
❯ ninja -C build_win32                           
ninja: Entering directory `build_win32'
[4/4] Linking target libfoobar-0.dll
FAILED: libfoobar-0.dll 
/home/nawordar/apps/ldc2/bin/ldc2  -of=libfoobar-0.dll 'foobar@sha/source_foobar_lib.d.obj' 'foobar@sha/source_foobar_types.d.obj' 'foobar@sha/source_foobar_util.d.obj' -L=--start-group -L=--out-implib=libfoobar.dll.a -L=--end-group -L=--allow-shlib-undefined -mscrtlib=msvcrtd -L=/NODEFAULTLIB:libcmt -L=/NODEFAULTLIB:libvcruntime -L=/DEFAULTLIB:legacy_stdio_definitions.lib -m64 -shared -relocation-model=pic
Error: unrecognized file extension obj
ninja: build stopped: subcommand failed.
```

**Expected behavior**
Meson and ninja should successfully build the project.

**system parameters**
* Cross build
* Arch Linux x86_64
* Python 3.8.5
* Meson 0.54.3
* Ninja 1.10.0


Discussion:
I think (but I'm not certain as I don't have a dev machine in front of me) you probably need to pass `-mtriple=i686-pc-win32` in the properties section of your cross file (I'm basing this on the [docs](https://wiki.dlang.org/Cross-compiling_with_LDC) for ldc2):
```ini
[properties]
d_args = ['-mtriple=i686-pc-win32']
d_link_args = ['-mtriple=i686-pc-win32']
```

With these properties I get the following:
```shell
❯ ninja -C build_win32
ninja: Entering directory `build_win32'
[1/4] Compiling D object 'foobar@sha/source_foobar_lib.d.obj'
FAILED: foobar@sha/source_foobar_lib.d.obj 
/home/nawordar/apps/ldc2/bin/ldc2 -I=foobar@sha -I=. -I=.. -I=../source -enable-color -wi -m64 -g -d-debug -mtriple=i686-pc-win32 -relocation-model=pic -of='foobar@sha/source_foobar_lib.d.obj' -c ../source/foobar/lib.d
Error: -m32 and -m64 switches cannot be used together with -march and -mtriple switches
[2/4] Compiling D object 'foobar@sha/source_foobar_types.d.obj'
FAILED: foobar@sha/source_foobar_types.d.obj 
/home/nawordar/apps/ldc2/bin/ldc2 -I=foobar@sha -I=. -I=.. -I=../source -enable-color -wi -m64 -g -d-debug -mtriple=i686-pc-win32 -relocation-model=pic -of='foobar@sha/source_foobar_types.d.obj' -c ../source/foobar/types.d
Error: -m32 and -m64 switches cannot be used together with -march and -mtriple switches
[3/4] Compiling D object 'foobar@sha/source_foobar_util.d.obj'
FAILED: foobar@sha/source_foobar_util.d.obj 
/home/nawordar/apps/ldc2/bin/ldc2 -I=foobar@sha -I=. -I=.. -I=../source -enable-color -wi -m64 -g -d-debug -mtriple=i686-pc-win32 -relocation-model=pic -of='foobar@sha/source_foobar_util.d.obj' -c ../source/foobar/util.d
Error: -m32 and -m64 switches cannot be used together with -march and -mtriple switches
ninja: build stopped: subcommand failed.
```

BTW I think that it should be `-m32` not `-m64`.
related wish: https://github.com/mesonbuild/meson/issues/7098

Resolving patch:
diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py
--- a/mesonbuild/compilers/d.py
+++ b/mesonbuild/compilers/d.py
@@ -443,10 +443,18 @@ def sanity_check(self, work_dir: str, environment: 'Environment') -> None:
         output_name = os.path.join(work_dir, 'dtest')
         with open(source_name, 'w', encoding='utf-8') as ofile:
             ofile.write('''void main() { }''')
-        pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name], cwd=work_dir)
+
+        compile_cmdlist = self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name]
+
+        # If cross-compiling, we can't run the sanity check, only compile it.
+        if environment.need_exe_wrapper(self.for_machine) and not environment.has_exe_wrapper():
+            compile_cmdlist += self.get_compile_only_args()
+
+        pc = subprocess.Popen(compile_cmdlist, cwd=work_dir)
         pc.wait()
         if pc.returncode != 0:
             raise EnvironmentException('D compiler %s cannot compile programs.' % self.name_string())
+
         if environment.need_exe_wrapper(self.for_machine):
             if not environment.has_exe_wrapper():
                 # Can't check if the binaries run so we have to assume they do
@@ -545,7 +553,9 @@ def _get_target_arch_args(self) -> T.List[str]:
         # LDC2 on Windows targets to current OS architecture, but
         # it should follow the target specified by the MSVC toolchain.
         if self.info.is_windows():
-            if self.arch == 'x86_64':
+            if self.is_cross:
+                return [f'-mtriple={self.arch}-windows-msvc']
+            elif self.arch == 'x86_64':
                 return ['-m64']
             return ['-m32']
         return []
diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py
--- a/mesonbuild/compilers/detect.py
+++ b/mesonbuild/compilers/detect.py
@@ -1143,11 +1143,15 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile
             try:
                 if info.is_windows() or info.is_cygwin():
                     objfile = os.path.basename(f)[:-1] + 'obj'
+                    extra_args = [f]
+                    if is_cross:
+                        extra_args.append(f'-mtriple={info.cpu}-windows')
+
                     linker = guess_win_linker(env,
                                               exelist,
                                               cls, full_version, for_machine,
                                               use_linker_prefix=True, invoked_directly=False,
-                                              extra_args=[f])
+                                              extra_args=extra_args)
                 else:
                     # LDC writes an object file to the current working directory.
                     # Clean it up.
@@ -1161,7 +1165,8 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile
 
             return cls(
                 exelist, version, for_machine, info, arch,
-                full_version=full_version, linker=linker, version_output=out)
+                full_version=full_version, linker=linker,
+                is_cross=is_cross, version_output=out)
         elif 'gdc' in out:
             cls = d.GnuDCompiler
             linker = guess_nix_linker(env, exelist, cls, version, for_machine)


Test patch: