linuxdeployqt icon indicating copy to clipboard operation
linuxdeployqt copied to clipboard

Set environment variable on start of application

Open dismine opened this issue 5 years ago • 4 comments

Hi,

I try to bundle my application with AppImage. For this, I use Open Build Service. Image I build based on OpenSUSE Leap 15.1. My application based on Qt 5. And for its work, it requires the ICU library. Qt 5 depends on it. Unfortunately, the way it packaged in OpenSUSE prevents me from using it. According to documentation, there are few ways to define path to ICU data. And because in the ICU package the path predefined through preprocessor variable ICU_DATA_DIR, my app cannot find it on my system (Ubuntu).

So, there are few issues with my image:

  • ICU doesn't use share library to get access to data. Even if such a library is in the image, copied by linuxdeployqt.
  • The library tries to read /user/share/icu/%icu_version%/icudt60l.dat folder to get data.
  • Bundling icudt60l.dat file doesn't resolve the issue because I don't have a way to point the library to a dynamic folder.

Trying to bundle icudt60l.dat and set environment variable ICU_DATA from inside of my application doesn't resolve the issue completely. It is not early enough, even if I do it almost first in my main function. Restarting the application from itself works.

Is there a way to resolve this issue? If only the AppImage cloud set the variable ICU_DATA before running an application.

Of course, I understand that my case is very specific. But maybe someone has an idea how to resolve this issue.

Thanks.

dismine avatar Nov 26 '19 19:11 dismine

Managed to fix my issue with u_setDataDirectory and custom code to retrieve an exe file directory path.

dismine avatar Nov 29 '19 18:11 dismine

Thanks for letting us know, can you describe a bit more detailed how you solved this? Quite possibly this will be helpful for other users.

probonopd avatar Nov 29 '19 20:11 probonopd

All starts with this warning message: Could not create collator: 4. This warning produced by QCollator class. The relevant source code is here. The status code 4 = U_FILE_ACCESS_ERROR ("The requested file cannot be found"). In my case, it was a file from ICU library.

Note. Application will work even without ICU library. But it highly restricts the amount of codecs available for an application. Missing even such a basic like ASCII.

As I said previously, investigating this issue I have found that on OpenSUSE the package for ICU library has a hardcoded path to .dat file. They build it with -DICU_DATA_DIR=\\\"%_datadir/icu/%version/\\\"". This way will work perfectly fine with common .rpm packages but will fail with AppImage. Since our package starts to depend on system ICU library. Especially on the position of icudt60l.dat. Not only the name of the file change from distribution to distribution but path to it. And of course, it is breaking the whole idea of portable AppImage packages. We shouldn't ask to install something on a host system.

Because of the hardcoded path to icudt60l.dat, we no longer can rely on a shared library, even if linuxdeployqt bundles it for us. Other approaches mentioned in documentation give us hope to resolve this issue.

My initial question was about setting environment variable ICU_DATA. If only linuxdeployqt could recognize that we need ICU, copy icudt60l.dat into package and set for us ICU_DATA with a path after mounting then it would work. Unfortunately setting the variable from within an app is not an easy task. And I have failed to make it work properly. I have an idea why it failed for me, but we will talk about this later. One of the benefits of such an approach is that you don't need to link with the ICU library when building the code.

The last hope is u_setDataDirectory function. And I must say it works. But this way has requirements. Remember I said that I have failed with setting the environment variable? Well, it seems like you cannot use Qt for setting the path at all to make it work. Maybe I am wrong, but even working with QString class initialize ICU. So, no use of QString, no use of QCoreApplication::applicationDirPath(). This is a case when you realize how a lot Qt does for you. So, probably setting the environment variable will also work, but not with Qt's qputenv.

As suggested in official documentation, I have tried to build a package by using the Open Build Service.

  • Normally we don't need ICU in build requirements, but this time we do. libicu-devel
  • Don't forget to link to ICU. contains(DEFINES, APPIMAGE) { unix:!macx: LIBS += -licudata -licui18n -licuuc }
  • When building the project define name APPIMAGE when call qmake. DEFINES+=APPIMAGE.
  • After successful build copy ICU's .dat file inside of $BUILD_APPDIR. mkdir -p $BUILD_APPDIR/usr/share/icu cp /usr/share/icu/*/icudt*.dat $BUILD_APPDIR/usr/share/icu/

After this still left one more thing to do. Find a way to get path to exe file directory. Because we cannot use QCoreApplication::applicationDirPath() we must find another way. Good news for us that we can stop worrying about a cross platform solution. And I have decided to use binreloc. Just be careful, the code is not perfect and compiler together with cppcheck have found few bad places.

Here is the example how I use it:

#include <stdlib.h>
#include <unicode/putil.h>
#include <cstring>
extern "C" {
#include "binreloc.h"
}

char* IcuDataPath(const char* correction)
{
    char * data_path = nullptr;
    BrInitError error;
    if (br_init (&error))
    {
        char *path = br_find_exe_dir(nullptr);
        if (path)
        {
            data_path = static_cast<char *> (malloc(strlen(path) + strlen(correction) + 1));
            if(data_path)
            {
                strcpy(data_path, path);
                strcat(data_path, correction);
                u_setDataDirectory(data_path);
            }
            free(path);
        }
    }

    return data_path;
}

The last thing to do is to call it first in your main function.

#if defined(APPIMAGE) && defined(Q_OS_LINUX)
#   include <QScopeGuard>
#   include "../vmisc/appimage.h"
#endif // defined(APPIMAGE) && defined(Q_OS_LINUX)

//---------------------------------------------------------------------------------------------------------------------

int main(int argc, char *argv[])
{
#if defined(APPIMAGE) && defined(Q_OS_LINUX)
    /* Fix path to ICU_DATA when run AppImage.*/
    char *exe_dir = IcuDataPath("/../share/icu");
    auto FreeMemory = qScopeGuard([exe_dir] {free(exe_dir);});
#endif // defined(APPIMAGE) && defined(Q_OS_LINUX)

    // Do your staff here
}

It's strange that nobody before me did not mention this issue. As I see it, it breaks all Qt-based applications builded on OpenSUSE to deploy as AppImage package.

Now my users can finally start enjoying AppImage version!

dismine avatar Nov 30 '19 10:11 dismine

Hi @adrianschroeter:

As I said previously, investigating this issue I have found that on OpenSUSE the package for ICU library has a hardcoded path to .dat file. They build it with -DICU_DATA_DIR=\"%_datadir/icu/%version/\"". This way will work perfectly fine with common .rpm packages but will fail with AppImage.

Do you have an idea what could be done here?

probonopd avatar Nov 30 '19 13:11 probonopd