Qt: Difference between revisions

imported>Samueldr
Sandro (talk | contribs)
 
(25 intermediate revisions by 10 users not shown)
Line 7: Line 7:
<code>nix-shell -p qt5Full -p qtcreator --run qtcreator</code>
<code>nix-shell -p qt5Full -p qtcreator --run qtcreator</code>


Tip: if it finds no Qt Kits, <code>rm -rf ~/.config/QtProject*</code> and start again.
Tip: if it finds no Qt Kits, <code>rm -rf ~/.config/QtProject*</code> and start again. Sometimes it finds a kit, but cannot find a suitable qt version for it, in this case you can also type <code>which qmake</code> in your nix-shell and add a new entry in the <code>QT-Versions</code> tab in <code>Tools->Options->Kits</code>.


For using direnv, create a '''shell.nix''' file in the root of your project and paste these lines into it:
For using direnv, create a '''shell.nix''' file in the root of your project and paste these lines into it:


{{File|shell.nix|nix|<nowiki>
<syntaxHighlight lang=nix>
# shell.nix
{ pkgs ? import <nixpkgs> {} }:
{ pkgs ? import <nixpkgs> {} }:
   pkgs.mkShell {
   pkgs.mkShell {
     buildInputs = [
     buildInputs = [
                    pkgs.qt5.full
      pkgs.qt5.full
                    pkgs.qtcreator
      pkgs.qtcreator
                  ];
    ];
}
}
</nowiki>}}
</syntaxHighlight>
 
Tip: if you want use clang-format, add '''clang-format''' to '''buildinputs''' list.


Also create '''.envrc''' file and paste: <code>use_nix</code> into it.
Also create '''.envrc''' file and paste: <code>use_nix</code> into it.
Line 27: Line 26:
Happy qt coding :)
Happy qt coding :)


== Troubleshooting ==
=== Explicit Dependencies ===
 
However if fetching the entirety of <code>pkgs.qt6.full</code> is not appealing and you know which parts of Qt you need, your first instinct might be adding something like <code>pkgs.qt6.qtdeclarative</code> for creating QML-based Qt programs to <code>buildInputs</code>, '''however''' that will not work and you will get compile errors for missing libraries. <code>pkgs.qt6.full</code> is actually [https://github.com/NixOS/nixpkgs/blob/nixos-24.11/pkgs/development/libraries/qt-6/default.nix#L94-L144 creating an environment that contains all Qt libraries] that allows <code>qmake</code> and tools to find those libraries, so you must do the same and <code>pkgs.qt6.env</code> will help make one. For example:
 
<syntaxhighlight lang="nix">
# shell.nix
{ pkgs ? import <nixpkgs> {} }:
let
  # pkgs.qt6.env already includes pkgs.qt6.qtbase
  # And using `with` to prevent a lot of typing.
  qtEnv = with pkgs.qt6; env "qt-custom-${qtbase.version}" [
    qtdeclarative
  ];
in
  pkgs.mkShell {
    buildInputs = [
      qtEnv
      # pkgs.qt6.qtdeclarative depends on pkgs.libglvnd
      # Also worth noting it could be in qtEnv if preferred for "relatedness" reasons
      pkgs.libglvnd
      pkgs.qtcreator
    ];
}
</syntaxhighlight>
 
== Packaging ==
 
[https://nixos.org/manual/nixpkgs/stable/#sec-language-qt See] for the entry in the nixpkgs manual.
 
Qt applications can't be called with <code>callPackage</code>, since they expect more inputs. Namely <code>qtbase</code> and <code>wrapQtAppsHook</code>. Instead they should be called with <code>libsForQt5.callPackage</code>.
 
{{File|./build/default.nix|nix|<nowiki>
{ stdenv, lib, qtbase, wrapQtAppsHook }:
 
stdenv.mkDerivation {
  pname = "myapp";
  version = "1.0";
 
  buildInputs = [ qtbase ];
  nativeBuildInputs = [ wrapQtAppsHook ];
}
</nowiki>}}
 
<syntaxHighlight lang=nix>
#nix-repl
myapp = callPackage ./build/myapp/default.nix { } # Will complain it wasn't called with qtbase, etc.
myapp = libsForQt5.callPackage ./build/myapp/default.nix { } # Should work
</syntaxHighlight>


=== Cannot mix incompatible Qt library (version 0x_____) with this library (version 0x_____) ===


This is a known issue, see {{issue|30551}} for the current status.
== Projects using python (e.g. PyQt5) ==


=== This application failed to start because it could not find or load the Qt platform plugin ??? in "" ===
It's possible to package a program that uses internally python and Qt (like PyQt5) by providing a python executable with the appropriate libraries like that <code>myPython = python3.withPackages (pkgs: with pkgs; [ pyqt5 ]);</code>. `<code>wrapQtAppsHook</code>` even seems  to be optional when using `mkderivation` (at least this program can be run without) since anyway it does not patch scripts.
==== qt4 ====
Qt4 depends on the environment variable <code>QT_PLUGIN_PATH</code> to find plugins. It is normally already
present in the environment on NixOS at least, but for example systemd user units are launched in a pretty empty environment. A solution is to use the command {{Commands|systemctl --user import-environment QT_PLUGIN_PATH}} from a sane environment. For example add it to the <code>services.xserver.displayManager.sessionCommands</code> option.


==== qt5 ====
<syntaxHighlight lang=nix>
{{warning|This recommendation is deprecated, see {{issue|65399}}. Failing packages should be updated to use <code>wrapQtAppsHook</code>.}}
{ mkDerivation,
  lib,
  stdenv,
  fetchFromGitHub,
  jack2,
  which,
  python3,
  qtbase,
  qttools,
  wrapQtAppsHook,
  liblo,
  git,
}:
let
  myPython = python3.withPackages (pkgs: with pkgs; [ pyqt5 liblo pyliblo pyxdg ]);
in
mkDerivation rec {
  pname = "RaySession";
  version = "0.11.1";


Qt5 seems (?) to look for plugins in the <code>PATH</code>. This will fail from a systemd user unit for example, because their path is nearly empty by default. As an example, here is a workaround to have <code>usbguard-applet</code> launched from a systemd user unit:
   src = fetchFromGitHub {
{{File|/etc/nixos/configuration.nix|nix|<nowiki>
     owner = "Houston4444";
   systemd.user.services.usbguard-applet = {
     repo = pname;
     description = "USBGuard applet";
     rev = "v${version}";
     partOf = [ "graphical-session.target" ];
     sha256 = "sha256-EbDBuOcF0JQq/LOrakb040Yfrpdi3FOB1iczQTeXBkc=";
     wantedBy = [ "graphical-session.target" ];
     path = [ "/run/current-system/sw/" ]; ### Fix empty PATH to find qt plugins
    serviceConfig = {
      ExecStart = "${pkgs.usbguard}/bin/usbguard-applet-qt";
    };
   };
   };
</nowiki>}}


==== qt.qpa.plugin: Could not find the Qt platform plugin "xcb" in "" ====
  # This patch is required to be able to create a new session, but not a problem to compile and start the program
Here is a concrete example:
  # patches = [ ./copy_template_writable.patch ];
 
  # Otherwise lrelease-qt is not found:
  postPatch = ''
  substituteInPlace Makefile \
    --replace "lrelease-qt4" "${qttools.dev}/bin/lrelease" \
    --replace '$(DESTDIR)/' '$(DESTDIR)$(PREFIX)' # Otherwise problem with installing manual etc...
  '';
 
  nativeBuildInputs = [
    myPython
    wrapQtAppsHook # Not really useful since it will not pack scripts. And actually it seems that it's not required?
    which
    qttools
  ];
  propagatedBuildInputs = [ myPython qtbase jack2 git ];
 
  # Prefix must be set correctly due to sed -i "s?X-PREFIX-X?$(PREFIX)?"
  makeFlags = [ "PREFIX=$(out)" ]; # prefix does not work since due to line "install -d $(DESTDIR)/etc/xdg/"
}
</syntaxHighlight>
 
Call it with
<syntaxHighlight>
{ pkgs ? import <nixpkgs> {} }:
pkgs.libsForQt5.callPackage ./derivation.nix {}
</syntaxHighlight>
 
For actual python applications, you may also use something like that (to test) :
<syntaxHighlight>
python3.pkgs.buildPythonApplication {
  pname = "blabla";
  version = "3.32.2";
 
  nativeBuildInputs = [
    wrapQtAppsHook
    ...
  ];
 
  dontWrapQtApps = true; # wrapQtApps won't patch script anyway. TODO: save to use if it contains executables?
 
  # Arguments to be passed to `makeWrapper`, only used by buildPython*
  preFixup = ''
        qtWrapperArgs+=("''${gappsWrapperArgs[@]}")
        # You can manually patch scripts using: wrapQtApp "$out/bin/myapp". TODO: check when it's required.
  '';
}
</syntaxHighlight>
 
== Migrating apps from Qt5 to Qt6 ==
 
# Replace <code>libsForQt5.callPackage</code> with <code>qt6Packages.callPackage</code>
# Add the dependency [https://www.qt.io/blog/porting-from-qt-5-to-qt-6-using-qt5compat-library qt5compat]
# Hope for the best ; )
 
<code>qt5compat</code> is only needed for Qt5 projects, which are not-yet migrated to Qt6.
 
Maybe add libraries like <code>qtwayland</code>
 
Conditional blocks in qmake <code>*.pro</code> files
 
<syntaxHighlight lang=pro>
lessThan(QT_MAJOR_VERSION, 6) {
  # qt5, qt4, ...
  QT += x11extras
}
equals(QT_MAJOR_VERSION, 6) {
  # qt6
  QT += core-private
}
</syntaxHighlight>
 
See also:
 
* [https://doc.qt.io/qt-6/portingguide.html Porting to Qt 6]
* [https://stackoverflow.com/questions/18663331 How to check the selected version of Qt in a .pro file?]
 
== Hello world involving QML, Qt5, nix and cmake, qmake or meson ==
 
You can find [https://gist.github.com/tobiasBora/04d0febda0b3f09707b5e1b7b85390a5 here] a minimal example to use QML, cmake, nix and Qt5, and [https://gist.github.com/tobiasBora/6f114cca1affb5528c872ca01d7e28c1 here] is the same example with qmake instead and [https://gist.github.com/tobiasBora/812701e8741814393f3df7b23a11eb4b here] is the same with meson instead. There is nothing special to nix there, but note that if you provide the qml file using something like <code>qrc:///main.qml</code>, then you need to write a qrc file that lists all the resources that must be included in the qt resource manager. This file is then used to compile the resources and include them in the binary (you have to compile the binaries, either automatically with cmake or qmake, or manually using rcc). With cmake you compile it using <code>qt5_add_resources(SOURCES qml.qrc)</code> ([https://doc.qt.io/qt-5/qtcore-cmake-qt5-add-resources.html doc]) as illustrated in the above example (make sure to use a variable as the source and to reuse the same variable in <code>add_executable</code>).
 
 
== Troubleshooting ==
 
=== This application failed to start because it could not find or load the Qt platform plugin ??? in "" ===
 
{{warning|This recommendation is deprecated for 19.09 and up, see {{issue|65399}}. Failing packages should be updated to use <code>wrapQtAppsHook</code>.}}


<pre>
<pre>
Line 62: Line 202:
</pre>
</pre>


The package will need to be fixed to use [the new https://github.com/NixOS/nixpkgs/issues/65399 <code>wrapQtAppsHook</code>].
The package will need to be fixed to use [the new https://github.com/NixOS/nixpkgs/issues/65399 <code>wrapQtAppsHook</code>]. The hook wraps every qt application with adding <code>QT_PLUGIN_PATH</code> and <code>XDG_DATA_DIRS</code> as well as <code>XDG_CONFIG_DIRS</code>.See [https://github.com/NixOS/nixpkgs/blob/nixos-19.09/pkgs/development/libraries/qt-5/hooks/wrap-qt-apps-hook.sh wrap-qt-apps-hook.sh in nixpkgs]


==== Debugging methods ====
=== Debugging methods ===
As a general rule, exporting <code>QT_DEBUG_PLUGINS=1</code> make qt print where it looks for plugins.
As a general rule, exporting <code>QT_DEBUG_PLUGINS=1</code> make qt print where it looks for plugins.


If a plugin exists in a directory but is ignored with a message like <code>QLibraryPrivate::loadPlugin failed on "/nix/store/...-teamspeak-client-3.1.6/lib/teamspeak/platforms/libqxcb.so" : "Cannot load library /nix/store/...-client-3.1.6/lib/teamspeak/platforms/libqxcb.so: "</code> it can be that the library cannot be <code>dlopen()</code>ed because of dependencies/rpath issues and needs <code>patchelf</code>ing. Exporting <code>LD_DEBUG=libs</code> may prove helpful in this scenario.
If a plugin exists in a directory but is ignored with a message like <code>QLibraryPrivate::loadPlugin failed on "/nix/store/...-teamspeak-client-3.1.6/lib/teamspeak/platforms/libqxcb.so" : "Cannot load library /nix/store/...-client-3.1.6/lib/teamspeak/platforms/libqxcb.so: "</code> it can be that the library cannot be <code>dlopen()</code>ed because of dependencies/rpath issues and needs <code>patchelf</code>ing. Exporting <code>LD_DEBUG=libs</code> may prove helpful in this scenario.