Repository: mesonbuild/meson

Issue:
Qt6 on Windows -> QtEntryPoint
**Describe the bug**

A very simple Qt 6 executable in the windows subsystem requires a WinMain function. Previously, this was provided by the static qtmain.lib, but, it seems, in Qt 6 it is provided by the new QtEntryPoint library. This is not heeded by the "main" keyword of the qt dependency().

**To Reproduce**
Trivial main.cpp
```
#include <QApplication>
#include <QPushButton>

auto main(int argc, char * argv[]) -> int
{
    auto Application = QApplication{argc, argv};
    auto Button = QPushButton{"Exit"};
    QObject::connect(&Button, &QAbstractButton::clicked, []() { QApplication::quit(); });
    Button.show();
    return Application.exec();
}
```

Trivial meson.build:
```
project('bug', 'cpp', default_options: ['cpp_std=c++17'])
qt_dep = dependency('qt6', modules: ['Core', 'Widgets'], main: true)
app = executable('app', 'main.cpp', dependencies: [qt_dep], win_subsystem: 'windows')
```

**Expected behavior**

This should compile successfully.

There are two options to solve this:
1. rely on the programmer to supply the 'EntryPoint' module in the dependency, and remove the 'main' argument for qt6
   I.e. this works: `qt_dep = dependency('qt6', modules: ['Core', 'EntryPoint', 'Widgets'])`
2. leave the interface as is and improve the Qt6 dependency handling to map from `'main': true` to the 'EntryPoint' module

It should be noted, that QtEntryPoint is not available on at least Ubuntu 22.04. So, the second options seems to be more portable, as it doesn't need OS-specific handling in the meson.build file.

**system parameters**
* no cross-compile
* OS: Windows, MSVC and Qt 6.3.2, run inside MSYS2
* meson --version: 0.63.2
* ninja --version: 1.11.1


Discussion:
It seems like we should deprecate `main: true` and rely on the user to supply `'EntryPoint'`. Unless `main: true` is required for non-windows subsystems
Why should it be deprecated? I'm not sure I understand the rationale.

It makes a lot more sense as a kwarg than as a distinct dependency lookup, because:
- it works the same on Qt5 and Qt6
- it automatically handles being a no-op for non-Windows platforms
- it is compatible with existing builds

What is the argument in favor of deprecating it?
In essence I have to agree with @eli-schwartz. I've implemented that solution, but if deprecating 'main' gains favour, I'm not opposed to implementing that as well.

Resolving patch:
diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py
--- a/mesonbuild/dependencies/qt.py
+++ b/mesonbuild/dependencies/qt.py
@@ -145,15 +145,18 @@ def __init__(self, name: str, kwargs: T.Dict[str, T.Any]):
         if not isinstance(self.qtmain, bool):
             raise DependencyException('"main" argument must be a boolean')
 
-    def _link_with_qtmain(self, is_debug: bool, libdir: T.Union[str, T.List[str]]) -> bool:
+    def _link_with_qt_winmain(self, is_debug: bool, libdir: T.Union[str, T.List[str]]) -> bool:
         libdir = mesonlib.listify(libdir)  # TODO: shouldn't be necessary
-        base_name = 'qtmaind' if is_debug else 'qtmain'
-        qtmain = self.clib_compiler.find_library(base_name, self.env, libdir)
-        if qtmain:
-            self.link_args.append(qtmain[0])
+        base_name = self.get_qt_winmain_base_name(is_debug)
+        qt_winmain = self.clib_compiler.find_library(base_name, self.env, libdir)
+        if qt_winmain:
+            self.link_args.append(qt_winmain[0])
             return True
         return False
 
+    def get_qt_winmain_base_name(self, is_debug: bool) -> str:
+        return 'qtmaind' if is_debug else 'qtmain'
+
     def get_exe_args(self, compiler: 'Compiler') -> T.List[str]:
         # Originally this was -fPIE but nowadays the default
         # for upstream and distros seems to be -reduce-relocations
@@ -212,7 +215,7 @@ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]):
                     is_debug = True
                     break
             libdir = self.get_pkgconfig_variable('libdir', [], None)
-            if not self._link_with_qtmain(is_debug, libdir):
+            if not self._link_with_qt_winmain(is_debug, libdir):
                 self.is_found = False
                 return
 
@@ -332,7 +335,7 @@ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]):
             self.link_args.append(libfile)
 
         if self.env.machines[self.for_machine].is_windows() and self.qtmain:
-            if not self._link_with_qtmain(is_debug, libdir):
+            if not self._link_with_qt_winmain(is_debug, libdir):
                 self.is_found = False
 
     def _sanitize_version(self, version: str) -> str:
@@ -375,6 +378,12 @@ def log_info(self) -> str:
         return 'qmake'
 
 
+class Qt6WinMainMixin:
+
+    def get_qt_winmain_base_name(self, is_debug: bool) -> str:
+        return 'Qt6EntryPointd' if is_debug else 'Qt6EntryPoint'
+
+
 class Qt4ConfigToolDependency(QmakeQtDependency):
 
     def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
@@ -387,7 +396,7 @@ def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
         return _qt_get_private_includes(mod_inc_dir, module, self.version)
 
 
-class Qt6ConfigToolDependency(QmakeQtDependency):
+class Qt6ConfigToolDependency(Qt6WinMainMixin, QmakeQtDependency):
 
     def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
         return _qt_get_private_includes(mod_inc_dir, module, self.version)
@@ -431,7 +440,7 @@ def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
         return _qt_get_private_includes(mod_inc_dir, module, self.version)
 
 
-class Qt6PkgConfigDependency(QtPkgConfigDependency):
+class Qt6PkgConfigDependency(Qt6WinMainMixin, QtPkgConfigDependency):
 
     def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]):
         super().__init__(name, env, kwargs)


Test patch: