Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loads qt plugin paths as registered... #44047

Conversation

samueldr
Copy link
Member

@samueldr samueldr commented Jul 24, 2018

Motivation for this change

Fixes Qt plugin loading

This error message:

This application failed to start because it could not find or load the Qt platform plugin "xcb"

This is a sturdier(?) fix to #24256. The current fixes doesn't seem to work properly in some situation.

What is going on currently is that for any Qt plugin, they will be loaded according to the environment, and according to some hard-coded paths relative to components of PATH. This will not work properly when the environment is cleared or manipulated. (With non-NixOS platforms, using nix-shell to start a Qt application when none have been installed using nix may cause such failure.)

Furthermore, there is an issue. See how I said "components of PATH", well, the platform plugins are taken either from QT_PLUGIN_PATH (that's an upstream variable) or from a path relative to the PATH components.

~/tmp/nixpkgs/nixpkgs-qt $ nix-shell -p qt5.qtbase.bin --pure
[nix-shell:~/tmp/nixpkgs/nixpkgs-qt]$ (IFS=: ; for p in $QT_PLUGIN_PATH; do echo $p; done | grep -i qt)
[nix-shell:~/tmp/nixpkgs/nixpkgs-qt]$ (IFS=: ; for p in $PATH; do echo $p; done | grep -i qt)

The current qt5.qtbase.bin does not create $bin/bin, which is why (AFAICT) it is not added to the PATH. (In a previous revision, I tested adding a bin folder and it seemed to help.)

What this change does is it collects all plugin paths from qtbase up to the built package, using a setup hook. The generated file is then found by the application and then used to seed the plugin paths.

It prefixes the plugin paths with those collected paths; this means that previous QT_PLUGIN_PATH and PATH based shenanigans should still work; it will prefer compiled-in paths. The behaviour is opt-out via dontAddPluginPaths though I'm not sure if there are any reasons to opt out.

Possibly fixes mismatched Qt versions

I hope this fixes #30551, but I have a hard time reproducing the issue. The issue, AFAICT, is when software is compiled with Qt 5.X.Y and ran with 5.X.Z, where both X are the same, and Y and Z differ.

I would like a step-by-step repro of this issue, as I was unable to reproduce it faithfully. I got it one time when trying to force it, but I must have goofed up when reproducing it, as I was unable to reproduce it.

Request for comments

First of all, let's talk about the implementation. If the implementation seems right, THEN we'll talk about the colour of the bike shed.

That said, I have a couple questions.

The approach

Is it a good approach to save those plugin paths that way?

I personally feel it's right, otherwise those compiled application will search wherever to find a match for a probably Qt library. This means that installing in your environment from a different channel may cause either system apps or user environment apps to stop working. This should fix this issue.

Then, are the details right enough? Should nix-support be used for this, or are there more appropriate folders?

First time setup hook

Is this done correctly? I think that unless someone removes genericBuild from their builders, this should always run when qtbase or any packages depending on qtbase are in the mix, right?

Issue with cyclic dependencies

There is a FIXME comment in the setup-hooks. See the (github) comment I left in the setup hooks file.

cycle detected in the references of '/nix/store/dkaprffmpi2aql8c0a2kxsid9pfvf5zj-qtsvg-5.11.1' from '/nix/store/v91n5pqk908brijpcd23wfrp4pkrg5g0-qtsvg-5.11.1-bin'

C++/Qt code quality

I always hate hacking on C++, I know enough of it to be dangerous, I'd even say I know enough of it to know what I did is probably not bad, but I'm not sure if it's good.

First, the C++ code itself I think is ok. RAII makes it so I don't have to release anything I added?

Then, Qt code, are there any tricks, shortcuts I could have used?

I think climbing up from ::applicationFilePath is sane, then I'm looking up until I reach the nix store. This means that applications deeply nested (e.g. libexec) will also be able to read the paths.

Testing this

Any known recalcitrant Qt packages to test? Right now I haven't made in-depth tests, only superficially verified it works. I will test a bunch more later, unless there are known trick to test this.

As for what I tested, here is an example using quaternion.

A not working, 18.03 install, nix-shell to nixos-unstable:

[nix-shell:~/tmp/nixpkgs/nixpkgs-qt]$ export LD_LIBRARY_PATH=/run/opengl-driver/lib:/run/opengl-driver-32/lib
[nix-shell:~/tmp/nixpkgs/nixpkgs-qt]$ quaternion
qt.qpa.plugin: Could not find the Qt platform plugin "xcb" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
Aborted

Working quaternion, from a 18.03 install, nix-shell to nixos-unstable+patch.

[nix-shell:~/tmp/nixpkgs/nixpkgs-qt]$ export LD_LIBRARY_PATH=/run/opengl-driver/lib:/run/opengl-driver-32/lib
[nix-shell:~/tmp/nixpkgs/nixpkgs-qt]$ quaternion
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/run/user/1000/runtime-samuel'
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/run/user/1000/runtime-samuel'
[... it works ...]

I also verified with strace that it reads the paths inside nix-support.

Using dontAddPluginPaths I also verified how apps will work if they don't find the nix-support path; they simply go back to working the way they worked before.

What's left to do?

  • Answer to feedback.
  • Also port to 5.6 (trivial).
  • Merge with other qtbase patches? If not, at least rename the patch.
  • Test on other Linux.
  • Test on macOS.

Commit message:

Loads qt plugin paths as registered...

This adds the list of Qt plugin paths into builds using qtbase.

The plugins list will be added to the Qt plugin path through the
additional patch.

This ensures that no special environment is required to get the desired
plugin path for Qt software.

This should fix all issues where Qt is unable to load its platform
plugin.

This may fix the issue where Qt tries to load a mismatched version of a
plugin.

Things done

Cut the things done part, until this is ready to be merged.

*Note that this is tested on nixos only, and built using sandboxing.


Additional notes:

local inputs

# FIXME : this causes output path cycles...
# I am unsure if it is even needed, though.
Copy link
Member Author

@samueldr samueldr Jul 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and there's this...

I'm not sure I need to do this, but maybe I need, otherwise some parts may be missing.

The situation I'm thinking about is just like how qtbase has dev and bin. When you buildInputs = [ qt5.qtbase ] you get qt5.qtbase.dev. This is fine, except the plugins are in qt5.qtbase.bin, which won't get figured out. This is why I'm forcibly adding it to nix-support/qt-plugin-paths at build time.

I'll have to first wrap my mind around what the error is, then figure out something.

cycle detected in the references of '/nix/store/dkaprffmpi2aql8c0a2kxsid9pfvf5zj-qtsvg-5.11.1' from '/nix/store/v91n5pqk908brijpcd23wfrp4pkrg5g0-qtsvg-5.11.1-bin'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The situation I'm thinking about is just like how qtbase has dev and bin.

This is the situation for almost all libraries. I noticed that you hardcoded the dependency on qtbase below; wouldn't we need to do this for all Qt libraries? (Missing the plugins from qtbase is the usually the only problem that is fatal, but missing the plugins from other libraries still leads to strange behavior and missing features.)

@matthewbauer
Copy link
Member

Hmm... I don't think it should have to be so much extra stuff. You should be able to reuse qtbase.patch and do something like:

app_libpaths->append(#QTBASEDIR "/lib"));

Here: https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/libraries/qt-5/5.11/qtbase.patch#L893

and a definition for QTBASEDIR here:

https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/libraries/qt-5/modules/qtbase.nix#L186

On closure size, I actually don't think this will have a huge effect. IIRC qtbase is leaking into it anyway so I wouldn't worry about it much:

$ nix why-depends /nix/store/b9pqh1m7a5rdfl6khn6zb38w2m9qc58a-wireshark-qt-2.4.7 /nix/store/jlci4w1ssyspjv0waz0z7lzbgiy9svhp-qtbase-5.10.1-dev
/nix/store/b9pqh1m7a5rdfl6khn6zb38w2m9qc58a-wireshark-qt-2.4.7
╚═══bin/wireshark: …ber in DLT sentence../nix/store/jlci4w1ssyspjv0waz0z7lzbgiy9svhp-qtbase-5.10.1-dev/include/QtCor…
    => /nix/store/jlci4w1ssyspjv0waz0z7lzbgiy9svhp-qtbase-5.10.1-dev

@samueldr
Copy link
Member Author

samueldr commented Jul 24, 2018

Hmm... I don't think it should have to be so much extra stuff. You should be able to reuse qtbase.patch and do something like [adding QTBASEDIR to app_libpaths].

Shortened quote with what I understand of it.

That would work fine for qtbase, but that won't add other plugin paths into the app's app_libpaths. There are ~150 packages (give or take for per-qt duplicates) packages providing plugins as per what Qt understands.

 $ nix-locate -1r qt-5.*plugins/.*so | grep -v '^(' | sort -u | wc -l
149

This does not only provide the platforms (like libqxcb), but also provides many parts for Qt applications to compose themselves with

 $ nix-locate -r qt-5.*plugins/.*so | grep -v '^(' | cut -b59- | cut -d'/' -f7- | sort | uniq | wc -l
1009

And as for "You should be able to reuse qtbase.patch", this is already noted as things to do (Merge with other qtbase patches? If not, at least rename the patch.) As for right now, keeping the patch separate makes it easier for me to adjust as needed, and makes it easier to review (I hope).

@samueldr
Copy link
Member Author

Had a quick ask-around on IRC today and @acowley replied with stuff I could try.


tribler

This PR "doesn't work for tribler". This points towards needing a bit of work for PyQt things. Though, since the new behaviour is additional to the previous behaviour, this means that it isn't inherently broken, it works exactly the way it works now.

What's happening is that it's searching relative to the python interpreter (understandably).

11998 lstat("/nix/store/d3aa78jf7pm9lk6ad67x4f21a068dxn6-python-2.7.15/bin/python2.7", {st_mode=S_IFREG|0555, st_size=7904, ...}) = 0
11998 access("/nix/store/d3aa78jf7pm9lk6ad67x4f21a068dxn6-python-2.7.15/bin/nix-support/qt-plugin-paths", F_OK) = -1 ENOENT (No such file or directory)
11998 access("/nix/store/d3aa78jf7pm9lk6ad67x4f21a068dxn6-python-2.7.15/nix-support/qt-plugin-paths", F_OK) = -1 ENOENT (No such file or directory)

Something would need to be done to nudge the patch into looking relative to tribler... Except that tribler itself doesn't have a qt-plugin-paths entry; it is python27Packages.pyqt5 that has it.

 $ ls -l /nix/store/fd21rmcwil26334bhcl69cqd0qz94l3d-python2.7-PyQt-5.10.1-dev/nix-support/qt-plugin-paths
-r--r--r-- 1 root root 511 Dec 31  1969 /nix/store/fd21rmcwil26334bhcl69cqd0qz94l3d-python2.7-PyQt-5.10.1-dev/nix-support/qt-plugin-paths

Thus, it may be required for the additional nudging to be done at that level, and not in the applications using PyQt. (Maybe with collaboration with an environment variable forcing a qt-plugin-paths file instead of detecting it?)

These are implementation details to implement if the overall idea is right; for now, it still seems to be right to me.


With a KDE desktop

18:13 <acowley> samueldr: My loose understanding of the problem was that integrating applications with a Plasma desktop was one of the breaking points. If the app insists on the plugins it was built with, and someone updates their desktop bringing in new plugin versions, then the two won't coexist happily. If the app relies on the environment for the plugins, then updating the desktop updates everything.
...
18:16 <acowley> samueldr: Right, that's the frustrating bit. But it seems that the alternative caused worse breakage for KDE users than those of us who just got a missing plugin message.

Uh, this is going to be hard to test with entire certainty. Though, I think I can do those test cases without too much pain (compiling all of KDE)

  • build-vm a nixos-18.03 graphical desktop using kde
    • execute the full path without nix-enving it
    • nix-env Qt Apps and run them
    • Somehow add it to the KDE menu (?)

Where Qt Apps are the two tribler and quaternion. First, using nixos-unstable clean, then with my patch.

If nothing breaks, I may not be trying hard enough. Another thing to try is to downgrade the Qt minor version (qt 5.11.0) to try to force a version mismatch. (So, in addition to this current patch, use 5.11.0 for testing purposes.)

@samueldr
Copy link
Member Author

samueldr commented Jul 27, 2018

Progress!

Looks like that with the patch applied, using build-vm with services.xserver.desktopManager.plasma5.enable = true;, there is still an issue when using minor-release mismatched Qt.

Though, this may be solvable. Playing around with the env, I was able to reduce it to two environment variables (both need to be unset).

$ /nix/store/.../bin/$app
...
Cannot mix incompatible Qt library (version 0x50b01) with this library (version 0x50b00)
Aborted
$ ( unset KDE_FULL_SESSION XDG_CURRENT_DESKTOP; /nix/store/.../bin/$app )
# [ runs! ]
$ # re-running it here would fail due to using a subshell previously.

This means that for Qt things, tests inside and outside Plasma will need to be done. (Good to know!)


For the record, I have built a 5.11.0 equivalent without the patch to compare

Running without touching the environment results in:

qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.

As it tried using the libqxcb.so from qtbase-5.11.1 from the environment.

As expected, unsetting both previous values doesn't change a thing.


This means that at the current stage: there is improvement, and no regression as far as I tested.


Exploring the variables

They both are only used in detectDesktopEnvironment, which is in turn used in QGenericUnixServices::desktopEnvironment()

This probably means that it is rooted into one of the 11 files using this function. A chunk of those are platform definitions, which can be discarded. Left are three files, one using it for indicator support under unity (not an issue here), another using it for font configuration under unity and gnome (not an issue here).

Looks like qgenericunixthemes.cpp is our culprit.

It is Qt's integration for KDE theming that causes issues. unsetting KDE_FULL_SESSION, setting XDG_CURRENT_DESKTOP to KDE causes the same issue. Doing it again, but with XDG_CURRENT_DESKTOP=FOO doesn't cause issues, and using one of the values from the gtk-based environments doesn't seem to cause issue either. (Though this may be because of entirely missing plugin platforms.)

Further investigation is needed, but this looks like it could be solvable if qtPluginPrefix contained the minor version number too (as suggested on IRC).

@matthewbauer
Copy link
Member

Sorry I misunderstood what Qt plugins were for. It looks like you're doing great work here! I would definitely recommend getting this reviewed from @ttuegel who should be able to provide feedback on some of the internals of Qt stuff.

@samueldr
Copy link
Member Author

Maybe you know, whether or not github's CODEOWNER notifications are noisy enough that @ttuegel would have seen it without mentioning?

image

Thanks for the words, it's not initially obvious that so much is built as plugins in Qt.

@samueldr
Copy link
Member Author

samueldr commented Jul 28, 2018

Success!

image
In this screenshot, we see how the search paths go from a 5.11.1 folder, go up a couple, go down a couple searching for 5.11.0.

I have to, first, do archaeological searches through the git log to see if there is any reason not to set the minor version in qtCompatVersion, but the comment says: "3. Update qtCompatVersion below if the minor version number changes.". (EDIT: Realised that in X.Y.Z Y is minor). Though it looks promising; adding the patch version there makes it so no environment manipulation is needed at all.

@ttuegel any particular reason for using the major.minor only? Qt seems to dislike mis-matched libraries, at least going from higher to lower patch releases.


For the record, here's the VM I'm using for testing, with glorious hardcoded paths in the build.sh script.

@nyanloutre
Copy link
Member

Is there currently a workaround to use the affected programs ?

@ttuegel
Copy link
Member

ttuegel commented Sep 8, 2018

any particular reason for using the major.minor only? Qt seems to dislike mis-matched libraries, at least going from higher to lower patch releases.

Qt claims that there is forward and backward binary compatibility along Z, if the version number is X.Y.Z. If you are seeing something different, I will defer to you; I learned long ago that everything they have to say about binary compatibility is an outright lie.

I have not had time to review this in depth, but the general principle seems sound to me. Thanks for tackling this!

@flokli
Copy link
Contributor

flokli commented Jan 24, 2019

Extending @timokau's comment a bit:
Especially when looking at the history and how often packages somehow broken, because wrapGAppsHook wasn't there, I'd also favour fixing this in our version of qt itself, rather than in a wrapper.

There might be users not very deep into packaging for NixOS (so for sure don't know about the wrapper) trying to get binaries linked with qt to work - it would be good if qt just did the right thing (tm).

@samueldr
Copy link
Member Author

samueldr commented Jan 24, 2019

I still need to test an assumption before speaking (and I hate to comment before testing it), but I think the hook might not solve the most damaging bug (#47552) where installing through nix-env can break the whole plasma session. (This is 100% an assumption and not verified, I have not even checked the wrapper's implementation and may be 100% wrong and kinda hope to be wrong here.) I also need to re-check whether this PR (#44047) fixes it, I think I verified and it did, but I need to re-check. The main issue here is that I need to go back to a 5.11 Qt world since it needs to happen between two minor releases (and maybe other factors?).

@ttuegel
Copy link
Member

ttuegel commented Jan 25, 2019

@samueldr

I think the hook might not solve the most damaging bug (#47552) where installing through nix-env can break the whole plasma session.

That bug is caused by having plugins installed into profile paths, which get picked up by Plasma packages. I did not see anything in this pull request to address that. Could you explain how this works to solve that problem?

The hook would solve #47552 once it is included in the Plasma packages because the QT_PLUGIN_PATH is prefixed with the build inputs' plugins. There would still be some corner cases, e.g.: if the user installs a Qt theme using a new Qt version and directs Plasma to load that theme, bad things would happen--but I don't see any way to solve that right now without breaking themes entirely.

Actually, this should already be dealt with by having a different $qtPluginPrefix for each minor version--that is why I added that around the time of Qt 5.9. 😕

@flokli

Especially when looking at the history and how often packages somehow broken, because wrapGAppsHook wasn't there, I'd also favour fixing this in our version of qt itself, rather than in a wrapper.

My experience has been that some packages always fall outside our attempts to automate. That is also true of the hook I wrote for #54525. The burden of debugging corner cases always falls on individual maintainers; in that case, the wrapper is easier to debug than a patched Qt. I want to automate packaging as much as possible, but not at the expense of giving packagers better tools when things go wrong.

This adds the list of Qt plugin paths into builds using `qtbase`.

The plugins list will be added to the Qt plugin path through the
additional patch.

This ensures that no special environment is required to get the desired
plugin path for Qt software.

This should fix all issues where Qt is unable to load its platform
plugin.

This may fix the issue where Qt tries to load a mismatched version of a
plugin.
@samueldr samueldr force-pushed the feature/stricter-qt-platform-path_unstable branch from a0b1624 to 475c7c5 Compare January 25, 2019 02:32
@samueldr
Copy link
Member Author

samueldr commented Jan 25, 2019

After some compilation time, yes, #47552 is handled by this PR.


That bug is caused by having plugins installed into profile paths, which get picked up by Plasma packages. I did not see anything in this pull request to address that. Could you explain how this works to solve that problem?

I think you're right, re-reading the implementation, and thinking about how it works, nothing specifically addresses the issue, but the way the dependencies are resolved helps. Instead of going through the PATH elements, which may end up providing unmatched plugins, it will instead first look at what it was built with, providing working plugins.

The implementation basically bundles a QT_PLUGIN_PATH internally inside the built derivation, in nix-support, which the patched Qt knows to look for first. It might be possible to get unmatched plugins, I'm not a Qt expert so bear with me, if e.g. a specialized "image" plugin was installed in the profile, while only the basic image plugins were provided in the built derivation.

Actually, this should already be dealt with by having a different $qtPluginPrefix for each minor version--that is why I added that around the time of Qt 5.9. 😕

I might have misspoke and meant patch level. The easiest way to trigger the issue is with patch levels, assuming MAJOR.minor.patch, a mis-matched patch as in #47552 will cause breakage. The qtCompatVersion only handles MAJOR.minor. Going from memory, no reproduction noted, adding patch to qtCompatVersion wasn't "enough", I would have loved to have a reproduction and notes as to why I remember that.

@ttuegel
Copy link
Member

ttuegel commented Jan 31, 2019

The implementation basically bundles a QT_PLUGIN_PATH internally inside the built derivation, in nix-support, which the patched Qt knows to look for first.

That was my thought, too. #54525 addresses this the same way: paths registered at build-time are searched first, then we go through PATH.

The easiest way to trigger the issue is with patch levels, assuming MAJOR.minor.patch, a mis-matched patch as in #47552 will cause breakage. The qtCompatVersion only handles MAJOR.minor.

So, Qt-upstream claims that the patch-level versions are all ABI-compatible. I, uh, have found reason to mistrust them in the past. Particularly with "platform plugins", which is what causes the crashes. (Qt applications crash if they cannot load a platform plugin, but they usually handle other misversioned plugins correctly by not loading them.) I'm not going to merge anything until I'm satisfied that I can reproduce and fix #47552, but I think it will be enough not to put platform plugins into propagatedUserEnvPkgs.

_Qt_sortless_uniq() {
# `uniq`, but keeps initial order.
# This is to remove risks of combinatorial explosion of plugin paths.
cat -n | sort -uk2 | sort -nk1 | cut -f2-
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks to exist at https://stackoverflow.com/a/20639730 - Stackoverflow links are sometimes worthwhile comments

@flokli flokli mentioned this pull request Feb 5, 2019
+ // Start at the binary; this allows us to *always* start by stripping the last part.
+ QStringList components = applicationFilePath().split(QDir::separator());
+
+ // We don't care about /nix/store/nix-support, only /nix/store/*/nix-support
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am uncomfortable with searching upward from the executable in this way. Going up one level should be fine (from /nix/store/.../bin/foo to /nix/store/.../nix-support/). The part about components.length() > 3 smells fishy to me; what happens if Nix is built with a different store path, such as a user-mode installation?

+ // This is why we're checking for more than 3 parts. It will bail out once /nix/xtore/*/nix-support/qt-plugin-paths has been tested.
+ while (components.length() > 3) {
+ components.removeLast();
+ const QString support_plugin_paths = QDir::cleanPath(QDir::separator() + components.join(QDir::separator()) + QStringLiteral("/nix-support/qt-plugin-paths"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QtDeclarative-based applications also need QML2_IMPORT_PATH. Some Qt applications and all KDE applications need XDG_DATA_DIRS and XDG_CONFIG_DIRS. Could you extend this to cover those cases?

local inputs

# FIXME : this causes output path cycles...
# I am unsure if it is even needed, though.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The situation I'm thinking about is just like how qtbase has dev and bin.

This is the situation for almost all libraries. I noticed that you hardcoded the dependency on qtbase below; wouldn't we need to do this for all Qt libraries? (Missing the plugins from qtbase is the usually the only problem that is fatal, but missing the plugins from other libraries still leads to strange behavior and missing features.)

@jackTaw88
Copy link

I updated nix package manager and all my installed apps. I am getting the same error for kolourpaint (kde app). :/

@samueldr
Copy link
Member Author

Closing since #54525 handles this issue.

@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/announcing-nomia-a-general-resource-manager-inspired-by-nix/12591/26

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Mixing incompatible Qt library (version 0x50902) with this library (version 0x50901)