Repository: mesonbuild/meson

Issue:
Cross-compilation from Apple Silicon macOS Big Sur
**Describe the bug**
Meson's cross-compilation capabilities don't seem to work properly on Apple Silicon build machines.
Describing the bug specifically is a little difficult because there are a couple of outcomes that, to me, seem non-sensical.

The toolchain I'm using is a from-git build of Clang 12.0.0 including LLD. The clang build has its default target triple
adjusted to `x86_64-unknown-windows-msvc` and default linker set to `lld`.

* Scenario 1: Meson configures the project but fails to detect the host machine cpu properly, instead marking the target cpu as `x86_64`
```
C compiler for the host machine: /usr/local/cross/x86_64-unknown-windows-msvc/bin/clang (clang 12.0.0 "e820 clang version 12.0.0 (git@github.com:llvm/llvm-project.git 80e326a8c4cfae770c10d7bb3d0ae291011bd91f)")
C linker for the host machine: /usr/local/cross/x86_64-unknown-windows-msvc/bin/clang ld64 unknown version
C compiler for the build machine: cc (clang 12.0.0 "Apple clang version 12.0.0 (clang-1200.0.32.27)")
C linker for the build machine: cc ld64 609.7
Build machine cpu family: aarch64
Build machine cpu: arm64
Host machine cpu family: aarch64
Host machine cpu: arm64
Target machine cpu family: x86_64
Target machine cpu: x86_64
Build targets in project: 1
```

* Scenario 2: Adding `c_ld=lld` to the `binaries` section of the cross-file crashes meson with a `file not found` exception even though meson shouldn't treat it as a filename, instead passing it to clang via `-fuse-ld=lld`
```
Traceback (most recent call last):
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/mesonmain.py", line 140, in run
    return options.run_func(options)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/msetup.py", line 253, in run
    app.generate()
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/msetup.py", line 161, in generate
    self._generate(env)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/msetup.py", line 178, in _generate
    intr = interpreter.Interpreter(b)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreter.py", line 2427, in __init__
    self.parse_project()
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreterbase.py", line 504, in parse_project
    self.evaluate_codeblock(self.ast, end=1)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreterbase.py", line 544, in evaluate_codeblock
    raise e
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreterbase.py", line 537, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreterbase.py", line 550, in evaluate_statement
    return self.function_call(cur)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreterbase.py", line 909, in function_call
    return func(node, func_args, kwargs)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreterbase.py", line 197, in wrapped
    return f(*wrapped_args, **wrapped_kwargs)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreterbase.py", line 228, in wrapped
    return f(*wrapped_args, **wrapped_kwargs)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreter.py", line 3201, in func_project
    self.add_languages(proj_langs, True, MachineChoice.HOST)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreter.py", line 3325, in add_languages
    success = self.add_languages_for(args, required, for_machine)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/interpreter.py", line 3357, in add_languages_for
    comp = self.environment.detect_compiler_for(lang, for_machine)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/environment.py", line 1860, in detect_compiler_for
    comp = self.compiler_from_language(lang, for_machine)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/environment.py", line 1832, in compiler_from_language
    comp = self.detect_c_compiler(for_machine)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/environment.py", line 1334, in detect_c_compiler
    return self._detect_c_or_cpp_compiler('c', for_machine)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/environment.py", line 1228, in _detect_c_or_cpp_compiler
    linker = self._guess_win_linker(compiler, cls, for_machine)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/environment.py", line 945, in _guess_win_linker
    p, o, e = Popen_safe(compiler + check_args)
  File "/opt/homebrew/Cellar/meson/0.56.0/lib/python3.9/site-packages/mesonbuild/mesonlib.py", line 1217, in Popen_safe
    p = subprocess.Popen(args, universal_newlines=True, close_fds=False,
  File "/opt/homebrew/Cellar/python@3.9/3.9.0_4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/subprocess.py", line 947, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/opt/homebrew/Cellar/python@3.9/3.9.0_4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/subprocess.py", line 1819, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'lld'
```

* Scenario 3 (only valid if `win_subsystem` is included): Meson reports that the `efi_application` subsystem isn't supported for MinGW even though I'm running clang on macOS
```
C compiler for the host machine: /usr/local/cross/x86_64-unknown-windows-msvc/bin/clang (clang 12.0.0 "e820 clang version 12.0.0 (git@github.com:llvm/llvm-project.git 80e326a8c4cfae770c10d7bb3d0ae291011bd91f)")
C linker for the host machine: /usr/local/cross/x86_64-unknown-windows-msvc/bin/clang ld64 unknown version
C compiler for the build machine: cc (clang 12.0.0 "Apple clang version 12.0.0 (clang-1200.0.32.27)")
C linker for the build machine: cc ld64 609.7
Build machine cpu family: aarch64
Build machine cpu: arm64
Host machine cpu family: aarch64
Host machine cpu: arm64
Target machine cpu family: x86_64
Target machine cpu: x86_64
Build targets in project: 1

Found ninja-1.10.2 at /opt/homebrew/bin/ninja

ERROR: Only "windows" and "console" are supported for win_subsystem with MinGW, not "efi_application".
```

**To Reproduce**
Project Meson file & cross-file attached.

**Expected behavior**
Host-machine cpu family & host-machine cpu should be detected as `x86_64` as specified in the cross-file instead
of `aarch64` & `arm64` respectively. When using `win_subsystem` Meson generates `-Wl,-subsystem:efi_application` instead of throwing a configuration error.

**System Parameters**
* Cross-compilation from aarch64 macOS to x86_64 Windows
* macOS Big Sur
* Python 3.9.0
* Meson 0.56
* Ninja 1.10.2

[meson.txt](https://github.com/mesonbuild/meson/files/5640628/meson.txt)
[clang.txt](https://github.com/mesonbuild/meson/files/5640631/clang.txt)



Discussion:
This check seems to be bogus:
```python
def get_win_subsystem_args(self, value: str) -> T.List[str]:
        args = []
        if self.info.is_windows() or self.info.is_cygwin():
            if 'windows' in value:
                args = ['-Wl,--subsystem,windows']
            elif 'console' in value:
                args = ['-Wl,--subsystem,console']
            else:
                raise mesonlib.MesonException('Only "windows" and "console" are supported for win_subsystem with MinGW, not "{}".'.format(value))
        if ',' in value:
            args[-1] = args[-1] + ':' + value.split(',')[1]
        return args
```

Clang with the GNU compatible driver can invoke `lld-link` to build EFI targets just fine. I'm not sure about MinGW specifically but since this applies to cross-compilations completely unrelated to MinGW as in my case above, this should probably be adjusted.

As an example on my Apple Silicon build machine:
```sh
/opt/homebrew/Cellar/llvm/11.0.0/bin/clang -target x86_64-unknown-windows-msvc -ffreestanding -fshort-wchar -mno-red-zone -nostdlib -c ../src/main.c

/opt/homebrew/Cellar/llvm/11.0.0/bin/clang -target x86_64-unknown-windows-msvc -fuse-ld=lld -nostdlib -Wl,-entry:efi_main -Wl,-subsystem:efi_application -o owl.efi main.o
```
These two invocations successfully compile an EFI application.

*EDIT*: The argument syntax used here isn't valid for `lld-link` either since it only accepts the `link.exe` style flags, granted using the GNU style `-` prefix instead of `/` is also supported as illustrated in the example above.

Resolving patch:
diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py
--- a/mesonbuild/compilers/mixins/clang.py
+++ b/mesonbuild/compilers/mixins/clang.py
@@ -19,7 +19,7 @@
 import typing as T
 
 from ... import mesonlib
-from ...linkers import AppleDynamicLinker
+from ...linkers import AppleDynamicLinker, ClangClDynamicLinker
 from ..compilers import CompileCheckMode
 from .gnu import GnuLikeCompiler
 
@@ -108,6 +108,12 @@ def openmp_flags(self) -> T.List[str]:
         else:
             # Shouldn't work, but it'll be checked explicitly in the OpenMP dependency.
             return []
+    
+    def get_win_subsystem_args(self, value: str) -> T.List[str]:
+        if self.info.is_windows() and not self.info.is_cygwin() and isinstance(self.linker, ClangClDynamicLinker):
+            return [f'-Wl,/subsystem:{value}']
+        
+        return super().get_win_subsystem_args(value)
 
     @classmethod
     def use_linker_args(cls, linker: str) -> T.List[str]:
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -940,6 +940,10 @@ def _guess_win_linker(self, compiler: T.List[str], comp_class: Compiler,
                 return LLVMDynamicLinker(
                     compiler, for_machine, comp_class.LINKER_PREFIX,
                     override, version=search_version(o))
+            elif not invoked_directly:
+                return ClangClDynamicLinker(
+                    for_machine, override, exelist=compiler, prefix=comp_class.LINKER_PREFIX,
+                    version=search_version(o), direct=False, machine=None)
 
         if value is not None and invoked_directly:
             compiler = value
@@ -1236,7 +1240,7 @@ def sanitize(p):
                     # style ld, but for clang on "real" windows we'll use
                     # either link.exe or lld-link.exe
                     try:
-                        linker = self._guess_win_linker(compiler, cls, for_machine)
+                        linker = self._guess_win_linker(compiler, cls, for_machine, invoked_directly=False)
                     except MesonException:
                         pass
                 if linker is None:
diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py
--- a/mesonbuild/linkers.py
+++ b/mesonbuild/linkers.py
@@ -1164,6 +1164,14 @@ def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str]
         super().__init__(exelist or ['lld-link.exe'], for_machine,
                          prefix, always_args, machine=machine, version=version, direct=direct)
 
+    def get_output_args(self, outputname: str) -> T.List[str]:
+        # If we're being driven indirectly by clang just skip /MACHINE
+        # as clang's target triple will handle the machine selection
+        if self.machine is None:
+            return self._apply_prefix([f"/OUT:{outputname}"])
+
+        return super().get_output_args(outputname)
+
 
 class XilinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker):
 


Test patch: