Search code examples
macosqtdylib

How deploy an application with external .dylib who calls an other .dylib?


I have build an application who loads a self-made .dylib and this .dylib had to call an other .dylib.

I'm on OS X and I use Qt. When I'm in Qt Creator I can Debug/Release and it works. It also works when I click on the .app and all the .dylib used by my application where located in .app/Contents/ Frameworks. My problem is for the deployment. When I run macdeployqt, all works and a .dmg is generated but when I launch this .dmg, an error occurs.

This error is logic because the .dylib called by the main .dylib is not in the folder .app/Contents/ Frameworks. I don't know how to add it if I had it in this folder it's fail. It also logic because the app don't call the second .dylib but the main dylib. don't have an app folder so ...


Solution

  • You have to copy the required libraries into the application bundle before you use the macdeployqt tool. You can perform the copy from the project file of the application itself, by adding custom targets that the main target depends on. These targets will be the copies of the dylibs, and you'll copy them from somewhere else in the build directory.

    Usually, you'd have a top-level subdirs project that builds the libraries and your application, e.g.:

    +--- lib-dylib-32920629
        +--- lib-dylib-32920629.pro
        +--- lib1
        |   +--- lib1.pro
        |   +--- lib1.cpp
        |   \--- lib1.h
        \--- main
            +--- main.pro
            \--- main.cpp
    

    Download this project from: https://github.com/KubaO/stackoverflown/tree/master/questions/lib-dylib-39206929

    lib-dylib-39206929.pro

    TEMPLATE = subdirs
    SUBDIRS += lib1 main
    main.depends += lib1
    

    lib1/lib1.pro

    QT = core
    CONFIG += c++11
    TEMPLATE = lib
    HEADERS += lib1.h
    SOURCES += lib1.cpp
    

    lib1/lib1.h

    #ifndef LIB1_H
    #define LIB1_H
    
    #include <QObject>
    
    class Lib1 {
    public:
        QString text();
    };
    
    #endif
    

    lib1/lib1.cpp

    #include "lib1.h"
    
    QString Lib1::text() {
        return QStringLiteral("Hello from Lib1");
    }
    

    main/main.pro

    A custom deployLib function can be used to deploy the library into the application bundle:

    QT = widgets
    CONFIG += c++11
    TEMPLATE = app
    SOURCES += main.cpp
    LIBS += -L../lib1 -llib1
    INCLUDEPATH += ..
    DEPENDPATH += ..
    
    defineReplace(libVersions) {
       # libVersions(1,2,3) - returns .1.2.3. .1.2. .1. .
       versions=.$${1}.$${2}.$${3}. .$${1}.$${2}. .$${1}. .
       return($$versions)
    }
    defineReplace(dylibs) {
       # dylibs(base,1,2,3) - returns libbase.1.2.3.dylib libbase.1.2.dylib ... libbase.dylib
       base = $$1
       versions = $$libVersions("$$2","$$3","$$4")
       libs =
       for (version, versions): libs += lib$${base}$${version}dylib
       return($$libs)
    }
    defineTest(deployLib) {
       # deployLib(target,target2path,target2,1,2,3)
       #   deploys target2path/libtarget2.1.2.3.dylib,... to the target's application bundle
       target = $$1
       libpath = $$2
       libtarget = $$3
       libs = $$dylibs($$libtarget,$$4,$$5,$$6)
       targetdir = $${target}.app/Contents/MacOS
       mktargetdir = "(test -d $$targetdir/ || mkdir -p $$targetdir/)"
       for (lib, libs) {
          out = $$targetdir/$$lib
          $${lib}.target = $$out
          $${lib}.commands = $$mktargetdir
          $${lib}.commands += "&& $$QMAKE_COPY_FILE $$libpath/$$libtarget/$$lib $$out"
          export($${lib}.target)
          export($${lib}.commands)
          QMAKE_EXTRA_TARGETS += $$lib
          PRE_TARGETDEPS += $$out
       }
       export(QMAKE_EXTRA_TARGETS)
       export(PRE_TARGETDEPS)
       return(true)
    }
    
    macx {
       deployLib(main, .., lib1, 1, 0, 0)
    }
    

    main/main.cpp

    #include "lib1/lib1.h"
    #include <QtWidgets>
    
    int main(int argc, char ** argv) {
        QApplication app{argc, argv};
        Lib1 lib1;
        QLabel label{lib1.text()};
        label.setMinimumSize(200, 200);
        label.setFont(QFont{"Helvetica", 20});
        label.show();
        return app.exec();
    }