MATLAB-generated Python packages conflict with PyQt5 on Ubuntu - possible library issue

Viewed 272

I am building an application using Ubuntu 18.04 and PyQt 5.12.1, which imports Python packages generated from MATLAB code (these packages depend on the MATLAB Runtime). MATLAB packages in Python require the LD_LIBRARY_PATH environment variable to be set; without this, the program raises an exception when a MATLAB-generated package is imported.

However, I have found that PyQt cannot function when LD_LIBRARY_PATH is set. The program runs fine with the MATLAB Runtime installed, as long as the MATLAB package is not imported and the LD_LIBRARY_PATH is not set.

As prompted by the MATLAB Runtime installer, I added this to the environment variables in my PyCharm run/debug configuration:

LD_LIBRARY_PATH=/usr/local/MATLAB/MATLAB_Runtime/v96/runtime/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v96/bin/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v96/sys/os/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v96/extern/bin/glnxa64.

This causes a crash in the PyQt part of the program. Using the QT_DEBUG_PLUGINS=1 environment variable, the error message is as follows:

Got keys from plugin meta data ("xcb")
QFactoryLoader::QFactoryLoader() checking directory path "<redacted>/PyMODA/venv/bin/platforms" ...
Cannot load library <redacted>/venv/lib/python3.6/site-packages/PyQt5/Qt/plugins/platforms/libqxcb.so: (/usr/local/MATLAB/MATLAB_Runtime/v96/bin/glnxa64/libQt5XcbQpa.so.5: undefined symbol: _ZNK14QPlatformTheme14fileIconPixmapERK9QFileInfoRK6QSizeF6QFlagsINS_10IconOptionEE)
QLibraryPrivate::loadPlugin failed on "<redacted>/venv/lib/python3.6/site-packages/PyQt5/Qt/plugins/platforms/libqxcb.so" : "Cannot load library <redacted>/venv/lib/python3.6/site-packages/PyQt5/Qt/plugins/platforms/libqxcb.so: (/usr/local/MATLAB/MATLAB_Runtime/v96/bin/glnxa64/libQt5XcbQpa.so.5: undefined symbol: _ZNK14QPlatformTheme14fileIconPixmapERK9QFileInfoRK6QSizeF6QFlagsINS_10IconOptionEE)"
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vnc, wayland-egl, wayland, wayland-xcomposite-egl, wayland-xcomposite-glx, webgl, xcb.

The important part:

"Cannot load library <...>/libqxcb.so: (/usr/local/MATLAB/MATLAB_Runtime/v96/bin/glnxa64/libQt5XcbQpa.so.5: undefined symbol: _ZNK14QPlatformTheme14fileIconPixmapERK9QFileInfoRK6QSizeF6QFlagsINS_10IconOptionEE)"

The MATLAB Runtime ships libQt5XcbQpa.so.5 in /usr/local/MATLAB/MATLAB_Runtime/v96/bin/glnxa64/, which must be exported to the LD_LIBRARY_PATH. It seems that this is being used by PyQt when the LD_LIBRARY_PATH is set, and it is an old version which is incompatible with the current version of PyQt.

Another library with the same name is in /usr/lib/x86_64-linux-gnu/, and it has a different MD5 checksum to the MATLAB version. However, adding this directory to the start of the LD_LIBRARY_PATH does not help. Setting the QT_QPA_PLATFORM_PLUGIN_PATH does not help either.

Is there a way to make the version in /usr/lib/x86_64-linux-gnu/ a higher priority than the MATLAB-supplied library? Is there another way to fix this issue?

1 Answers

I have discovered a workaround:

  • Run all MATLAB-packaged code in a new process; this is barely an inconvenience, since the computations must be run on a separate thread or process to prevent freezing the GUI anyway.
  • In each process which runs MATLAB-packaged code, set the LD_LIBRARY_PATH environment variable programmatically before importing the MATLAB modules. The import statements will have to be in a function rather than at the top of the file.

Here is a relatively minimal example:

class MyPlot(PlotComponent):
    """
    A class which inherits from a base class PlotComponent, which is 
    a subclass of QWidget. In this simple example, the window 
    gets the data and calls the function `plot(self, data)` on an 
    instance of this class. 
    """
    def __init__(self, parent):
        super().__init__(parent)
        self.queue = Queue()

    def plot(self, data):
        """Calculate the results from the provided data, and plot them."""
        fs = data.frequency

        self.times = data.times
        signal = data.signal.tolist()

        # Create the process, supplying all data in non-MATLAB types.
        self.proc = Process(target=generate_solutions, args=(self.queue, signal, fs))
        self.proc.start()

        # Check for a result in 1 second.
        QTimer.singleShot(1000, self.check_result)

    def check_result(self):
        """Checks for a result from the other process."""
        if self.queue.empty(): # No data yet; check again in 1 second.
            QTimer.singleShot(1000, self.check_result)
            return

        w, l = self.queue.get() # Get the data from the process.

        a = np.asarray(w)
        gh = np.asarray(l)

        # Create the plot.
        self.axes.pcolormesh(self.times, gh, np.abs(a))

def generate_solutions(queue, signal, freq):
    """
    Generates the solutions from the provided data, using the MATLAB-packaged
    code. Must be run in a new process.
    """
    import os

    # Set the LD_LIBRARY_PATH for this process. The particular value may
    # differ, depending on your installation.
    os.environ["LD_LIBRARY_PATH"] = "/usr/local/MATLAB/MATLAB_Runtime/v96/runtime/glnxa64:" \
    "/usr/local/MATLAB/MATLAB_Runtime/v96/bin/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v96/sys/os/glnxa64:" \ 
    "/usr/local/MATLAB/MATLAB_Runtime/v96/extern/bin/glnxa64"

    # Import these modules AFTER setting up the environment variables.
    import my_matlab_package
    import matlab

    package = my_matlab_package.initialize()

    # Convert the input into MATLAB data-types, to pass to the MATLAB package.
    A = matlab.double([signal])
    fs_matlab = matlab.double([freq])

    # Calculate the result.
    w, l = package.perform_my_calculation(A, fs_matlab, nargout=2)

    # Convert the results back to normal Python data-types so that the
    # main process can use them without importing matlab, and put them 
    # in the queue.
    queue.put((np.asarray(w), np.asarray(l),))
Related