21 May 2019

Qt for Android - Installing A Library Built With qmake Into The Qt Directory

Have you ever written a library on top of Qt? It is actually quite ease. Consider we have a very simple library called my-qt-lib, with the following structure:

.
├── myqtlib.cpp
├── my-qt-lib_global.h
├── myqtlib.h
└── my-qt-lib.pro

This is a very simple skeleton which Qt Creator usually creates for your in case you ask it to create a shared library project. Let’s have a closer look at the file my-qt-lib.pro, where we need to apply some changes:

QT       -= gui

TARGET = my-qt-lib
TEMPLATE = lib

DEFINES += MYQTLIB_LIBRARY

DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += \
        myqtlib.cpp

HEADERS += \
        myqtlib.h \
        my-qt-lib_global.h 

# Install the library into the Qt 
# installation's libraries directory:
target.path = $$[QT_INSTALL_LIBS]
INSTALLS += target

# Install the headers into the Qt 
# installation's include directory:
headers.files = myqtlib.h my-qt-lib_global.h
headers.path = $$[QT_INSTALL_HEADERS]
INSTALLS += headers

That’s also quite simple and mostly what Qt Creator is able to create for you automatically. The interesting thing are the few lines at the end. We set up custom installation paths for our library, pointing it to libraries installation location of the current Qt installation (by using $$[QT_INSTALL_LIBS]). In addition, we create a custom install target headers, which we use to copy the header files into the Qt installation (concrete, into the include directory, which we get by using $$[QT_INSTALL_HEADERS]).

With this setup, we can do the following to install our library into a Qt installation:

mkdir build-linux-x86_64
cd build-linux-x86_64
$HOME/Qt/5.12.2/gcc_64/bin/qmake ..
make
make install

The last line will actually copy the library plus header files into the Qt installation. To better understand, what gets copied where, let’s tweak it and see which files would get copied:

make install INSTALL_ROOT=$PWD/tmp

This will basically do a “fake” installation into the sub-directory tmp/ inside the current directory. Let’s check the content of this directory:

tmp
└── home
    └── martin
        └── Qt
            └── 5.12.2
                └── gcc_64
                    ├── include
                    │   ├── my-qt-lib_global.h
                    │   └── myqtlib.h
                    └── lib
                        ├── libmy-qt-lib.so -> libmy-qt-lib.so.1.0.0
                        ├── libmy-qt-lib.so.1 -> libmy-qt-lib.so.1.0.0
                        ├── libmy-qt-lib.so.1.0 -> libmy-qt-lib.so.1.0.0
                        └── libmy-qt-lib.so.1.0.0

Cool, exactly what we would have expected: The process would have copied the library (including some symlinks, which are typical for a Linux installation of a library) as well as the header files into the Qt installation. Great.

What is this useful for? Well, if you have an addon library for Qt, you can easily install it into your Qt installation and every other library or app building against this Qt will be able to use your library. Fancy, isn’t it?

There is just one little issue. Let’s assume we also want to use our library on Android. Consequentially, we need to install it into our Android Qt installation. Let’s go:

# Make the path to the Android NDK known:
export ANDROID_NDK_ROOT=$HOME/Android/android-ndk-r19c
mkdir build-android-armv7
cd build-android-armv7
$HOME/Qt/5.12.2/android_armv7/bin/qmake ..
make
make install INSTALL_ROOT=$PWD/tmp

The last line will again install the library into a temporary directory, so we can check the installation:

tmp/
├── home
│   └── martin
│       └── Qt
│           └── 5.12.2
│               └── android_armv7
│                   └── include
│                       ├── my-qt-lib_global.h
│                       └── myqtlib.h
└── libs
    └── armeabi-v7a
        └── libmy-qt-lib.so

Uff, and here, something is wrong. The headers are correctly installed. However, the library would get installed into /libs/armeabi-v7a. What went wrong?

The answer is simple: qmake has some built in magic when it comes to Android. You can read about this in detail in the official docs. The point is: This magic prevents us from installing the library where we want it to land.

As always, the solution to this issue is not as well documented as it could be, but sooner or later you land in Qt’s bug tracker and there indeed you find a solution. We just need to remove the android_install configuration option:

QT       -= gui

TARGET = my-qt-lib
TEMPLATE = lib

DEFINES += MYQTLIB_LIBRARY

DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += \
        myqtlib.cpp

HEADERS += \
        myqtlib.h \
        my-qt-lib_global.h 

target.path = $$[QT_INSTALL_LIBS]
INSTALLS += target

headers.files = myqtlib.h my-qt-lib_global.h
headers.path = $$[QT_INSTALL_HEADERS]
INSTALLS += headers

# Disable qmake's magic for installing libraries
# on android:
CONFIG -= android_install

If we repeat the above steps to build and install the library, we now get the expected results:

tmp
└── home
    └── martin
        └── Qt
            └── 5.12.2
                └── android_armv7
                    ├── include
                    │   ├── my-qt-lib_global.h
                    │   └── myqtlib.h
                    └── lib
                        └── libmy-qt-lib.so

Very good! However, we now have a little problem: If someone uses our library as a sub-module inside another project, he will not be able to use qmake’s magic in case he targets Android. So in case we want to allow both (installation of the library into the Qt installation when building standalone as well as deployment to the standard location in case of it is built as part of another project), we can just keep the *.pro file as it is and instead modify the call to qmake on the command line:

# Disable the special Android library handling on command line:
$HOME/Qt/5.12.2/android_armv7/bin/qmake CONFIG-=android_install ..

# And now proceed as usual:
make
make install
Thank You For Reading
Martin Hoeher

I am a software/firmware developer, working in Dresden, Germany. In my free time, I spent some time on developing apps, presenting some interesting findings here in my blog.

Comments
Your comment has been filed

We'll review it and it will appear here as soon as we're done.

Sorry, something went wrong...

Your comment could not be posted.

Regarding your personal information...
  • The name you enter will be shown next to your comment. You may enter your real name or whatever you like.
  • Your e-mail will be used to show an image representing you next to your comment. We do not store nor show your address somewhere. Instead, an ID will be calculated from it and stored. This ID is then used to retrieve an image from Gravatar.