How can I build a Python package that uses Qt?

Every time I run my python program that uses a Qt-based library (ete3), I get the error: qt.qpa.plugin: Could not find the Qt platform plugin "xcb" in "". I saw on this wiki page that you could use wrapQtAppsHook, but it doesn’t seem to have any effect.

I’m very new at this, so I’m probably way off with something.

My default.nix file as it currently stands is here.. As you can see, I’m trying to write my own expression for the ete3 library, but it doesn’t seem to be able to find Qt plugins.

Any help would me most appreciated.

2 Likes

The QT section of the nixpkgs manual explains this. Sort of.

Note: wrapQtAppsHook ignores files that are non-ELF executables. This means that scripts won’t be automatically wrapped so you’ll need to manually wrap them as previously mentioned. An example of when you’d always need to do this is with Python applications that use PyQT.”

There’s an example of how to use the wrap function manually in there, as well.

Unless I’m mistaken, those are examples for how to wrap QT applications. But if it’s a library I’m trying to build, and not an application, I don’t have a binary, so I wouldn’t know how to use that.

Really what I’m asking here is: how can I fix the ete section in my default.nix so that (a) it builds, and (b) I can run python example.py from within a nix shell, and have it not complain about a missing xcb plugin?

You can wrap the application that you are using the library with. If you are developing the application, it may be easier to set QT_QPA_PLATFORM_PLUGIN_PATH in e.g. a project-specific shell.nix file. For example:

2 Likes

The documentation does not really give a nice example of use at the paragraph that specifies that wrapQtAppsHook, however this section seems to suggest that buildPython... should use dontWrapGApps = true; not to automatically wrap the packages, and then add in the preFixup phase a command makeWrapperArgs+=("''${gappsWrapperArgs[@]}") (it will be handled automatically by the buildPython... derivation):

python3.pkgs.buildPythonApplication {
  pname = "gnome-music";
  version = "3.32.2";

  nativeBuildInputs = [
    wrapGAppsHook
    gobject-introspection
    ...
  ];

  dontWrapGApps = true;

  # Arguments to be passed to `makeWrapper`, only used by buildPython*
  preFixup = ''
    makeWrapperArgs+=("''${gappsWrapperArgs[@]}")
  '';
}

You can do something similar if you use the mkDerivation provided by Qt, except that the line will be qtWrapperArgs+=("''${gappsWrapperArgs[@]}"):

mkDerivation {
  pname = "calibre";
  version = "3.47.0";

  nativeBuildInputs = [
    wrapGAppsHook
    qmake
    ...
  ];

  dontWrapGApps = true;

  # Arguments to be passed to `makeWrapper`, only used by qt5’s mkDerivation
  preFixup = ''
    qtWrapperArgs+=("''${gappsWrapperArgs[@]}")
  '';
}

The above link gives more advices, for example you can also use the program wrapGApp to patch a file outside /bin.

Oups, in the first example I used wrapGAppsHook (for GTK) instead of the QT version. Adapt with the second example. Also, in case your application is not a pure python application, I found this example to be practical to provide to the python scripts PyQt5:

{ 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";

  src = fetchFromGitHub {
    owner = "Houston4444";
    repo = pname;
    # rev = "v${version}";
    rev = "b2724aafbc794e08ae5d69249e42aeaec15ced61";
    sha256 = "sha256-EbDBuOcF0JQq/LOrakb040Yfrpdi3FOB1iczQTeXBkc=";
  };

  # This patch is required to be able to create a new session, but not a problem to compile and start the program
  # 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/"
}