Repository: conan-io/conan-center-index

Issue:
[package] qt/6.x.x: component support
The qt 6.x.x package lacks component support. I understand this could be quite a bit of work to add this. I would be willing to put some effort in, but if I am being honest I don't see a good way forward. The generated Qt cmake files contain quite a bit of extra stuff (ref. below). For me the issue with the current setup is that for example:

- not all functionality (tools/functions/macros) is available,
- targets are missing meaning you are not clear on what you are actually using,
- targets are missing which make it hard to link some plugins (like the platform plugins),
- using conan package is non-transparent, and
- order of libraries could be wrong.

To fix the above you need to replicate the functionality contained in Qt, which is mostly targets and properties (see below for example properties). I think there are a few ways to go:

1. make this info available in the package (json, yaml, conf etc.) and generate all files.
1. parse qt cmake files to extract info and generate all files, or
1. use the qt cmake files.

For me options 1 or 3 seem the way to go. Do you see any other options or has there already been some work on this? I expanded a bit on them below. @ericLemanissier what do you think?

<details><summary>Typical qt cmake target properties</summary>
```cmake
 set_target_properties(Qt6::Gui PROPERTIES
  COMPATIBLE_INTERFACE_STRING "QT_MAJOR_VERSION"
  INTERFACE_COMPILE_DEFINITIONS "QT_GUI_LIB"
  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include/QtGui;${_IMPORT_PREFIX}/include"
  INTERFACE_LINK_LIBRARIES "Qt6::Core;d3d11;dxgi;dxguid"
  INTERFACE_QT_MAJOR_VERSION "6"
  INTERFACE_SOURCES "\$<\$<BOOL:\$<TARGET_PROPERTY:QT_CONSUMES_METATYPES>>:${_IMPORT_PREFIX}/lib/metatypes/qt6gui_release_metatypes.json>"
  MODULE_PLUGIN_TYPES "accessiblebridge;platforms;platforms_darwin;xcbglintegrations;platformthemes;platforminputcontexts;generic;iconengines;imageformats;egldeviceintegrations"
  QT_DISABLED_PRIVATE_FEATURES "accessibility_atspi_bridge;directfb;directwrite3;evdev;fontconfig;qqnx_imf;integrityfb;kms;drm_atomic;libinput;integrityhid;libinput_axis_api;linuxfb;vsp2;vnc;mtdev;egl_x11;eglfs;eglfs_brcm;eglfs_egldevice;eglfs_gbm;eglfs_vsp2;eglfs_mali;eglfs_viv;eglfs_rcar;eglfs_viv_wl;eglfs_openwfd;eglfs_x11;jpeg;system_jpeg;tslib;xcb;xcb_glx_plugin;xcb_glx;xcb_egl_plugin;xcb_native_painting;xrender;xcb_xlib;xcb_sm;system_xcb_xinput;xkbcommon;xkbcommon_x11;xlib"
  QT_DISABLED_PUBLIC_FEATURES "accessibility_atspi_bridge;fontconfig;opengles2;opengles3;opengles31;opengles32;vulkan;openvg;egl;system_textmarkdownreader"
  QT_ENABLED_PRIVATE_FEATURES "directwrite;direct2d;direct2d1_1;freetype;system_freetype;harfbuzz;system_harfbuzz;vkgen;gif;ico;png;system_png;imageio_text_loading;tuiotouch;multiprocess;raster_64bit"
  QT_ENABLED_PUBLIC_FEATURES "freetype;harfbuzz;dynamicgl;opengl;ico;sessionmanager;texthtmlparser;textmarkdownreader;textmarkdownwriter;textodfwriter;cssparser;draganddrop;action;cursor;clipboard;wheelevent;tabletevent;im;highdpiscaling;validator;standarditemmodel;filesystemmodel;imageformatplugin;movie;imageformat_bmp;imageformat_ppm;imageformat_xbm;imageformat_xpm;imageformat_png;imageformat_jpeg;image_heuristic_mask;image_text;picture;colornames;pdf;desktopservices;systemtrayicon;accessibility;whatsthis;undocommand;undostack;undogroup"
  QT_PLUGINS "QMinimalIntegrationPlugin;QOffscreenIntegrationPlugin;QWindowsIntegrationPlugin;QWindowsDirect2DIntegrationPlugin;QICOPlugin;QGifPlugin;QTuioTouchPlugin;QSvgIconPlugin;QSvgPlugin"
  QT_QMAKE_PRIVATE_CONFIG ""
  QT_QMAKE_PUBLIC_CONFIG ""
  QT_QMAKE_PUBLIC_QT_CONFIG ""
  _qt_config_module_name "gui"
  _qt_module_has_headers "ON"
)
```
</details>

## Option 1

While I think this option will take some work and can be fragile it is quite clear. I attached an example yaml, it does not contain all information that is in a typical qt target as I am not sure what info is needed and what is not. Next to this yaml we would need to generate a few other yaml files defining targets such as Qt6::qmake, typically contained in the tools targets (Qt6::CoreTools). You could use this list to check which libraries (including their dependencies) were built and add the correct targets.

Advantages:

- clear,
- easy to generate proper conan cmake files,

Disadvantages:

- can be fragile,
- new libs require work.

<details><summary>Example components yaml</summary>

```yaml
components:
  "Core":
    Windows:
      systemlibs:
      - "Version"
      - "Winmm"  
      - "Netapi32"
      - "UserEnv"
    Macos:
      systemlibs:
      - "IOKit"
      - "Cocoa"
      - "Security"
    requires:
      - "zlib::zlib"
      - "EntryPoint"
      - "CoreTools"
  "CoreTools":
    build_modules:
      - "Qt6CoreTools.cmake"
  "Concurrent":
    requires:
      - "Core"
  "DBus":
    requires:
      - "DBusTools"
  "EntryPoint"
  "DeviceDiscoverySupport":
    requires:
      "Core"
  "FbSupport":
    requires:
      - "Core"
      - "Gui"
  "Gui":
    requires:
      - "Core"
      - "GuiTools"
  "GuiTools":
    build_modules:
      - "Qt6GuiTools.cmake"
  "Network":
    requires:
      - "Core"
    Windows:
      systemlibs:
      - "DnsApi"
  "OpenGL":
    requires:
      - "Core"
      - "Gui"
  "OpenGLWidgets":
    requires:
      - "OpenGL"
      - "Widgets"
  "PrintSupport":
    requires:
      - "Core"
      - "Gui"
      - "Widgets"
  "Sql":
    requires:
      - "Core"
  "Test":
    requires:
      - "Core"
  "Widgets"
    requires:
      - "WidgetsTools"
      - "Core"
      - "Gui"
  "CoreTools":
    build_modules:
      - "Qt6WidgetsTools.cmake"
  "Xml":
    requires:
      - "Core"
```
</details>

## Option 2

The Qt cmake files are quite structured, so it might be possible to parse them to extract information similar to the yaml and then regenerate them through conan.

Advantages:

- new libraries require no work,
- changes to existing require no work if not major.

Disadvantages:

- fragile parsing of files,
- unclear

## Option 3

This would be to do what the old bincrafter recipes do, just expose the Qt cmake files through conan.

Advantages:

- small effort to implement, and
- very limited maintenance required.

Disadvantages:

- https://github.com/conan-io/conan-center-index/blob/master/docs/faqs.md#why-are-cmake-findconfig-files-and-pkg-config-files-not-packaged




Discussion:

Resolving patch:
diff --git a/recipes/qt/6.x.x/conanfile.py b/recipes/qt/6.x.x/conanfile.py
--- a/recipes/qt/6.x.x/conanfile.py
+++ b/recipes/qt/6.x.x/conanfile.py
@@ -124,6 +124,8 @@ def config_options(self):
             raise ConanInvalidConfiguration("qt 6 does not support apple-clang before 11")
         if self.settings.os == "Windows":
             self.options.opengl = "dynamic"
+        if self.settings.os != "Linux":
+            self.options.qtwayland = False
 
     def configure(self):
 
@@ -232,6 +234,8 @@ def requirements(self):
             self.requires("opengl/system")
         if self.options.with_zstd:
             self.requires("zstd/1.4.8")
+        if self.options.qtwayland:
+            self.requires("wayland/1.18.0")
 
     def source(self):
         tools.get(**self.conan_data["sources"][self.version])
@@ -522,26 +526,147 @@ def package_id(self):
                 self.info.settings.compiler.runtime = "MT/MTd"
 
     def package_info(self):
-        # FIXME add components
-        self.cpp_info.libs = tools.collect_libs(self)
+        self.cpp_info.names["cmake_find_package_multi"] = "Qt6"
 
-        # Add top level include directory, so code compile if someone uses
-        # includes with prefixes (e.g. "#include <QtCore/QString>")
-        self.cpp_info.includedirs = ["include"]
+        libsuffix = ""
+        if self.settings.build_type == "Debug":
+            if self.settings.os == "Windows":
+                libsuffix = "d"
+            if tools.is_apple_os(self.settings.os):
+                libsuffix = "_debug"
+
+        def _get_corrected_reqs(requires):
+            reqs = []
+            for r in requires:
+                reqs.append(r if "::" in r else "qt%s" % r)
+            return reqs
+
+        def _create_module(module, requires=[]):
+            componentname = "qt%s" % module
+            assert componentname not in self.cpp_info.components, "Module %s already present in self.cpp_info.components" % module
+            self.cpp_info.components[componentname].names["cmake_find_package"] = module
+            self.cpp_info.components[componentname].names["cmake_find_package_multi"] = module
+            self.cpp_info.components[componentname].libs = ["Qt6%s%s" % (module, libsuffix)]
+            self.cpp_info.components[componentname].includedirs = ["include", os.path.join("include", "Qt%s" % module)]
+            self.cpp_info.components[componentname].defines = ["QT_%s_LIB" % module.upper()]
+            if module != "Core" and "Core" not in requires:
+                requires.append("Core")
+            self.cpp_info.components[componentname].requires = _get_corrected_reqs(requires)
+
+        def _create_plugin(pluginname, libname, type, requires):
+            componentname = "qt%s" % pluginname
+            assert componentname not in self.cpp_info.components, "Plugin %s already present in self.cpp_info.components" % pluginname
+            self.cpp_info.components[componentname].names["cmake_find_package"] = pluginname
+            self.cpp_info.components[componentname].names["cmake_find_package_multi"] = pluginname
+            self.cpp_info.components[componentname].libs = [libname + libsuffix]
+            self.cpp_info.components[componentname].libdirs = [os.path.join("res", "archdatadir", "plugins", type)]
+            self.cpp_info.components[componentname].includedirs = []
+            if "Core" not in requires:
+                requires.append("Core")
+            self.cpp_info.components[componentname].requires = _get_corrected_reqs(requires)
+
+        core_reqs = ["zlib::zlib"]
+        if self.options.with_pcre2:
+            core_reqs.append("pcre2::pcre2")
+        if self.options.with_doubleconversion:
+            core_reqs.append("double-conversion::double-conversion")
+        if self.options.get_safe("with_icu", False):
+            core_reqs.append("icu::icu")
+
+        _create_module("Core", core_reqs)
+        self.cpp_info.components["qtCore"].libs.append("Qt6Core_qobject%s" % libsuffix)
+        if self.options.gui:
+            gui_reqs = []
+            if self.options.with_freetype:
+                gui_reqs.append("freetype::freetype")
+            if self.options.with_libpng:
+                gui_reqs.append("libpng::libpng")
+            if self.options.get_safe("with_fontconfig", False):
+                gui_reqs.append("fontconfig::fontconfig")
+            if self.settings.os in ["Linux", "FreeBSD"]:
+                gui_reqs.append("xorg::xorg")
+                if not tools.cross_building(self, skip_x64_x86=True):
+                    gui_reqs.append("xkbcommon::xkbcommon")
+            if self.settings.os != "Windows" and self.options.get_safe("opengl", "no") != "no":
+                gui_reqs.append("opengl::opengl")
+            _create_module("Gui", gui_reqs)
+        if self.options.with_sqlite3:
+            _create_plugin("QSQLiteDriverPlugin", "qsqlite", "sqldrivers", ["sqlite3::sqlite3"])
+        if self.options.with_pq:
+            _create_plugin("QPSQLDriverPlugin", "qsqlpsql", "sqldrivers", ["libpq::libpq"])
+        if self.options.with_odbc:
+            if self.settings.os != "Windows":
+                _create_plugin("QODBCDriverPlugin", "qsqlodbc", "sqldrivers", ["odbc::odbc"])
+        _create_module("Network", ["openssl::openssl"] if self.options.openssl else [])
+        _create_module("Sql")
+        _create_module("Test")
+        if self.options.widgets:
+            _create_module("Widgets", ["Gui"])
+        if self.options.gui and self.options.widgets:
+            _create_module("PrintSupport", ["Gui", "Widgets"])
+        if self.options.get_safe("opengl", "no") != "no" and self.options.gui:
+            _create_module("OpenGL", ["Gui"])
+        if self.options.widgets and self.options.get_safe("opengl", "no") != "no":
+            _create_module("OpenGLWidgets", ["OpenGL", "Widgets"])
+        _create_module("DBus")
+        _create_module("Concurrent")
+        _create_module("Xml")
+
+        if self.options.qt5compat:
+            _create_module("Core5Compat")
+
+        if self.options.qtdeclarative:
+            _create_module("Qml", ["Network"])
+            _create_module("QmlModels", ["Qml"])
+            if self.options.gui:
+                _create_module("Quick", ["Gui", "Qml", "QmlModels"])
+                if self.options.widgets:
+                    _create_module("QuickWidgets", ["Gui", "Qml", "Quick", "Widgets"])
+                _create_module("QuickShapes", ["Gui", "Qml", "Quick"])
+            _create_module("QmlWorkerScript", ["Qml"])
+            _create_module("QuickTest", ["Test"])
+
+        if self.options.qttools and self.options.gui and self.options.widgets:
+            _create_module("UiPlugin", ["Gui", "Widgets"])
+            self.cpp_info.components["qtUiPlugin"].libs = [] # this is a collection of abstract classes, so this is header-only
+            self.cpp_info.components["qtUiPlugin"].libdirs = []
+            _create_module("UiTools", ["UiPlugin", "Gui", "Widgets"])
+            _create_module("Designer", ["Gui", "UiPlugin", "Widgets", "Xml"])
+            _create_module("Help", ["Gui", "Sql", "Widgets"])
+
+        if self.options.qtquick3d and self.options.gui:
+            _create_module("Quick3DUtils", ["Gui"])
+            _create_module("Quick3DAssetImport", ["Gui", "Qml", "Quick3DUtils"])
+            _create_module("Quick3DRuntimeRender", ["Gui", "Quick", "Quick3DAssetImport", "Quick3DUtils", "ShaderTools"])
+            _create_module("Quick3D", ["Gui", "Qml", "Quick", "Quick3DRuntimeRender"])
+
+        if self.options.qtquickcontrols2 and self.options.gui:
+            _create_module("QuickControls2", ["Gui", "Quick"])
+            _create_module("QuickTemplates2", ["Gui", "Quick"])
+
+        if self.options.qtshadertools and self.options.gui:
+            _create_module("ShaderTools", ["Gui"])
+
+        if self.options.qtsvg and self.options.gui:
+            _create_module("Svg", ["Gui"])
+            if self.options.widgets:
+                _create_module("SvgWidgets", ["Gui", "Svg", "Widgets"])
+
+        if self.options.qtwayland and self.options.gui:
+            _create_module("WaylandClient", ["Gui", "wayland::wayland-client"])
+            _create_module("WaylandCompositor", ["Gui", "wayland::wayland-server"])
 
-        # Add all Qt module directories (QtCore, QtGui, QtWidgets and so on), so prefix
-        # can be omited in includes (e.g. "#include <QtCore/QString>" => "#include <QString>")
-        fu = ["include/" + f.name for f in os.scandir("include") if f.is_dir()]
-        self.cpp_info.includedirs.extend(fu)
 
         if not self.options.shared:
             if self.settings.os == "Windows":
-                self.cpp_info.system_libs.append("Version")  # qtcore requires "GetFileVersionInfoW" and "VerQueryValueW" which are in "Version.lib" library
-                self.cpp_info.system_libs.append("Winmm")    # qtcore requires "__imp_timeSetEvent" which is in "Winmm.lib" library
-                self.cpp_info.system_libs.append("Netapi32") # qtcore requires "NetApiBufferFree" which is in "Netapi32.lib" library
-                self.cpp_info.system_libs.append("UserEnv")  # qtcore requires "__imp_GetUserProfileDirectoryW " which is in "UserEnv.Lib" library
+                self.cpp_info.components["qtCore"].system_libs.append("version")  # qtcore requires "GetFileVersionInfoW" and "VerQueryValueW" which are in "Version.lib" library
+                self.cpp_info.components["qtCore"].system_libs.append("winmm")    # qtcore requires "__imp_timeSetEvent" which is in "Winmm.lib" library
+                self.cpp_info.components["qtCore"].system_libs.append("netapi32") # qtcore requires "NetApiBufferFree" which is in "Netapi32.lib" library
+                self.cpp_info.components["qtCore"].system_libs.append("userenv")  # qtcore requires "__imp_GetUserProfileDirectoryW " which is in "UserEnv.Lib" library
+                self.cpp_info.components["qtCore"].system_libs.append("ws2_32")  # qtcore requires "WSAStartup " which is in "Ws2_32.Lib" library
+
 
             if self.settings.os == "Macos":
-                self.cpp_info.frameworks.append("IOKit")     # qtcore requires "_IORegistryEntryCreateCFProperty", "_IOServiceGetMatchingService" and much more which are in "IOKit" framework
-                self.cpp_info.frameworks.append("Cocoa")     # qtcore requires "_OBJC_CLASS_$_NSApplication" and more, which are in "Cocoa" framework
-                self.cpp_info.frameworks.append("Security")  # qtcore requires "_SecRequirementCreateWithString" and more, which are in "Security" framework
+                self.cpp_info.components["qtCore"].frameworks.append("IOKit")     # qtcore requires "_IORegistryEntryCreateCFProperty", "_IOServiceGetMatchingService" and much more which are in "IOKit" framework
+                self.cpp_info.components["qtCore"].frameworks.append("Cocoa")     # qtcore requires "_OBJC_CLASS_$_NSApplication" and more, which are in "Cocoa" framework
+                self.cpp_info.components["qtCore"].frameworks.append("Security")  # qtcore requires "_SecRequirementCreateWithString" and more, which are in "Security" framework


Test patch:
diff --git a/recipes/qt/6.x.x/test_package/CMakeLists.txt b/recipes/qt/6.x.x/test_package/CMakeLists.txt
--- a/recipes/qt/6.x.x/test_package/CMakeLists.txt
+++ b/recipes/qt/6.x.x/test_package/CMakeLists.txt
@@ -9,10 +9,10 @@ conan_set_vs_runtime()
 conan_set_libcxx()
 conan_output_dirs_setup()
 
-find_package(qt REQUIRED CONFIG)
+find_package(Qt6 REQUIRED CONFIG)
 
 add_executable(${PROJECT_NAME} test_package.cpp greeter.h moc_greeter.cpp)
 # Must compile with "-fPIC" since Qt was built with -reduce-relocations.
 target_compile_options(${PROJECT_NAME} PRIVATE -fPIC)
 
-target_link_libraries(${PROJECT_NAME} qt::qt)
+target_link_libraries(${PROJECT_NAME} Qt6::Core)
diff --git a/recipes/qt/6.x.x/test_package/conanfile.py b/recipes/qt/6.x.x/test_package/conanfile.py
--- a/recipes/qt/6.x.x/test_package/conanfile.py
+++ b/recipes/qt/6.x.x/test_package/conanfile.py
@@ -27,7 +27,7 @@ def _qmake_supported(self):
         return self.settings.compiler != "Visual Studio" or self.options["qt"].shared
 
     def _cmake_multi_supported(self):
-        return self.settings.compiler != "Visual Studio" or not self.options["qt"].shared or self.settings.build_type != "Debug"
+        return True
 
     def _build_with_qmake(self):
         if not self._qmake_supported():