【(灰常详细) QT宿主机使用linuxdeployqt 打包并在没有QT环境的目的机运行程序中遇到的坑】

银河麒麟Linux使用QT写程序,并用linuxdeployqt打包后正常运行程序

QT 安装

QT官网历史版本目录: https://download.qt.io/archive/qt/
因为项目移植前是基于5.12.3版本的QT
在这里插入图片描述

所以我下载的是 https://download.qt.io/archive/qt/5.12/5.12.3/qt-opensource-linux-x64-5.12.3.run
下载后直接系统断网双击安装(银河麒麟系统虽说自带QT环境, 但是版本太低了)
安装时记得选择安装对应的GCC编译链:(如果用到了QT对应的组件也要勾上, 我直接全勾了懒得选别学我)
在这里插入图片描述

期间会弹窗是否运行程序运行相关操作, 直接点允许(麒麟系统才会有?)
期间可能会安装错误, 删掉已安装的东西 重新安装即可.

linuxdeployqt 安装

由于项目需要跨平台到银河麒麟V10系统上(2203和旧的202007两个版本的), 以前在windows上的打包方式windeployqt就用不了了, 查询攻略说Linux上也有个对应的工具 linuxdeployqt(github地址).

首先我直接下载了他的发行版程序: https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage
然后发现高版本Linux内核(具体哪个版本ok我也不清楚)不能正常使用.
于是要重新下载源码自己编译.

下载源码并配置

  1. 首先安装git:
    sudo apt install git g++
    
  2. 再安装patchelf
    sudo apt install patchelf
    
  3. 找个合适的文件夹cd进去后克隆最新的源码, cd到源码目录内的tools/linuxdeployqt
    mkdir linuxdeployqt
    cd linuxdeployqt
    git clone https://github.com/probonopd/linuxdeployqt --depth=1
    
  4. 这里要修改几个地方, 否则生成Makfile报错
    cd linuxdeployqt
    vi CMakeLists.txt
    
    

①. 将 find_program 开始到 # set version and build number 之间的代码注释掉

# CMake configuration for linuxdeployqt
# Not meant to replace the qmake build system, but for use with CMake based IDEs.

cmake_minimum_required(VERSION 3.2)

project(linuxdeployqt)

#find_program(GIT git)
#
#if("${GIT}" STREQUAL "GIT-NOTFOUND")
#    message(WARNING "Could not find git, commit and tag info cannot be updated")
#
#    if(NOT GIT_COMMIT)
#        message(FATAL_ERROR "Commit ID not set, please call with -DGIT_COMMIT=...")
#    endif()
#
#    if(NOT GIT_TAG_NAME)
#        message(FATAL_ERROR "Tag name not set, please call with -DGIT_TAG_NAME=...")
#    endif()
#else()
#    # make sure Git revision ID and latest tag is not stored in the CMake cache
#    # otherwise, one would have to reset the CMake cache on every new commit to make sure the Git commit ID is up to date
#    unset(GIT_COMMIT CACHE)
#    unset(GIT_LATEST_TAG CACHE)
#
#    # read Git revision ID and latest tag number
#    execute_process(
#        COMMAND "${GIT}" rev-parse --short HEAD
#        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
#        OUTPUT_VARIABLE GIT_COMMIT
#        OUTPUT_STRIP_TRAILING_WHITESPACE
#        RESULT_VARIABLE GIT_COMMIT_RESULT
#    )
#    if(NOT GIT_COMMIT_RESULT EQUAL 0)
#        message(FATAL_ERROR "Failed to determine git commit ID")
#    endif()
#    mark_as_advanced(GIT_COMMIT GIT_COMMIT_RESULT)
#
#    execute_process(
#        COMMAND "${GIT}" rev-list --tags --skip=1 --max-count=1
#        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
#        OUTPUT_VARIABLE GIT_TAG_ID
#        OUTPUT_STRIP_TRAILING_WHITESPACE
#        RESULT_VARIABLE GIT_TAG_ID_RESULT
#    )
#    if(NOT GIT_TAG_ID_RESULT EQUAL 0)
#        message(FATAL_ERROR "Failed to determine git tag ID")
#    endif()
#    mark_as_advanced(GIT_TAG_ID GIT_TAG_ID_RESULT)
#
#    execute_process(
#        COMMAND "${GIT}" describe --tags ${GIT_TAG_ID} --abbrev=0
#        WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
#        OUTPUT_VARIABLE GIT_TAG_NAME
#        OUTPUT_STRIP_TRAILING_WHITESPACE
#        RESULT_VARIABLE GIT_TAG_NAME_RESULT
#    )
#    if(NOT GIT_TAG_NAME_RESULT EQUAL 0)
#        message(FATAL_ERROR "Failed to determine git tag name")
#    endif()
#    mark_as_advanced(GIT_TAG_NAME GIT_TAG_NAME_RESULT)
#endif()

# set version and build number
set(VERSION 1-alpha)
if("$ENV{TRAVIS_BUILD_NUMBER}" STREQUAL "")
    set(BUILD_NUMBER "<local dev build>")
else()
    set(BUILD_NUMBER "$ENV{TRAVIS_BUILD_NUMBER}")
endif()

# get current date
execute_process(
    COMMAND env LC_ALL=C date -u "+%Y-%m-%d %H:%M:%S %Z"
    OUTPUT_VARIABLE DATE
    OUTPUT_STRIP_TRAILING_WHITESPACE
    RESULT_VARIABLE DATE_RESULT
)
if(NOT DATE_RESULT EQUAL 0)
    message(FATAL_ERROR "Failed to determine date string")
endif()
mark_as_advanced(DATE DATE_RESULT)

add_subdirectory(tools/linuxdeployqt)

②. 切换到 ./tools/linuxdeployqt 下, 这里还有个 CMakeLists.txt 要改, find_package 这里根据QT版本是啥写啥, 后面 target_link_libraries也要同步修改

set(CMAKE_AUTOMOC ON)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# expose version data as compiler definition
add_definitions("-DLINUXDEPLOYQT_VERSION=\"${GIT_TAG_NAME}\"")
add_definitions("-DLINUXDEPLOYQT_GIT_COMMIT=\"${GIT_COMMIT}\"")
add_definitions("-DBUILD_DATE=\"${DATE}\"")
add_definitions("-DBUILD_NUMBER=\"${BUILD_NUMBER}\"")

#find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) #修改前
find_package(Qt5 REQUIRED COMPONENTS Core) #修改后

# update excludelist
message(STATUS "Updating excludelist...")
execute_process(
    COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/../generate-excludelist.sh
    OUTPUT_VARIABLE EXCLUDELIST
    TIMEOUT 10
    RESULT_VARIABLE EXCLUDELIST_RESULT
)
if(NOT EXCLUDELIST_RESULT EQUAL 0)
    message(WARNING "Updating excludelist failed, using outdated copy")
endif()
mark_as_advanced(EXCLUDELIST EXCLUDELIST_RESULT)

add_executable(linuxdeployqt main.cpp shared.cpp)
target_include_directories(linuxdeployqt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
#target_link_libraries(linuxdeployqt Qt${QT_VERSION_MAJOR}::Core) #修改前
target_link_libraries(linuxdeployqt Qt5::Core) #修改后
target_compile_definitions(linuxdeployqt PRIVATE -DEXCLUDELIST="${EXCLUDELIST}")

③. 同目录下还有个 main.cpp 要改, 51行到54行版本检测注释掉, 192行到213行的libc版本检测注释掉:

/****************************************************************************
**
** Copyright (C) 2016-19 The Qt Company Ltd. and Simon Peter
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QCoreApplication>
#include <QDir>
#include <QProcessEnvironment>
#include "shared.h"
#include <QRegularExpression>
#include <stdlib.h>
#include <QSettings>
#include <QDirIterator>
#include <sstream>
#include "excludelist.h"
# include <gnu/libc-version.h>

int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);

    extern QString appBinaryPath;
    appBinaryPath = ""; // Cannot do it in one go due to "extern"

    QString firstArgument = QString::fromLocal8Bit(argv[1]);

    // print version statement
    std::stringstream version;
#if 0
    version << "linuxdeployqt " << LINUXDEPLOYQT_VERSION
            << " (commit " << LINUXDEPLOYQT_GIT_COMMIT << "), "
            << "build " << BUILD_NUMBER << " built on " << BUILD_DATE;
    qInfo().noquote() << QString::fromStdString(version.str());
#endif
    bool plugins = true;
    bool appimage = false;
    extern bool runStripEnabled;
    extern bool bundleAllButCoreLibs;
    extern bool bundleEverything;
    extern bool fhsLikeMode;
    extern QString fhsPrefix;
    extern QStringList librarySearchPath;
    extern bool alwaysOwerwriteEnabled;    
    QStringList additionalExecutables;
    bool qmldirArgumentUsed = false;
    bool skipTranslations = false;
    bool skipGlibcCheck = false;
    QStringList qmlDirs;
    QStringList qmlImportPaths;
    QString qmakeExecutable;
    extern QStringList extraQtPlugins;
    extern QStringList excludeLibs;
    extern QStringList ignoreGlob;
    extern bool copyCopyrightFiles;
    extern QString updateInformation;
    extern QString qtLibInfix;

    // Check arguments
    // Due to the structure of the argument parser, we have to check all arguments at first to check whether the user
    // wants to get the version only
    // TODO: replace argument parser with position independent, less error prone version
    for (int i = 0; i < argc; i++ ) {
        QString argument = argv[i];
        if (argument == "-version" || argument == "-V" || argument == "--version") {
            // can just exit normally, version has been printed above
            return 0;
        }
        if (argument == QByteArray("-show-exclude-libs")) {
            qInfo() << generatedExcludelist;
            return 0;
        }
    }
    for (int i = 2; i < argc; ++i) {
        QByteArray argument = QByteArray(argv[i]);

        if (argument == QByteArray("-no-plugins")) {
            LogDebug() << "Argument found:" << argument;
            plugins = false;
        } else if (argument == QByteArray("-appimage")) {
            LogDebug() << "Argument found:" << argument;
            appimage = true;
            bundleAllButCoreLibs = true;
        } else if (argument == QByteArray("-unsupported-bundle-everything")) {
            LogDebug() << "Argument found:" << argument;
            skipGlibcCheck = true;
            bundleEverything = true;
        } else if (argument == QByteArray("-no-strip")) {
            LogDebug() << "Argument found:" << argument;
            runStripEnabled = false;
        } else if (argument == QByteArray("-bundle-non-qt-libs")) {
            LogDebug() << "Argument found:" << argument;
            bundleAllButCoreLibs = true;
        } else if (argument.startsWith(QByteArray("-verbose"))) {
            LogDebug() << "Argument found:" << argument;
            int index = argument.indexOf("=");
            bool ok = false;
            int number = argument.mid(index+1).toInt(&ok);
            if (!ok)
                LogError() << "Could not parse verbose level";
            else
                logLevel = number;
        } else if (argument.startsWith(QByteArray("-executable"))) {
            LogDebug() << "Argument found:" << argument;
            int index = argument.indexOf('=');
            if (index == -1)
                LogError() << "Missing executable path";
            else
                additionalExecutables << argument.mid(index+1);
        } else if (argument.startsWith(QByteArray("-qmldir"))) {
            LogDebug() << "Argument found:" << argument;
            qmldirArgumentUsed = true;
            int index = argument.indexOf('=');
            if (index == -1)
                LogError() << "Missing qml directory path";
            else
                qmlDirs << argument.mid(index+1);
        } else if (argument.startsWith(QByteArray("-qmlimport"))) {
            LogDebug() << "Argument found:" << argument;
            int index = argument.indexOf('=');
            if (index == -1)
                LogError() << "Missing qml import path";
            else
                qmlImportPaths << argument.mid(index+1);
        } else if (argument.startsWith("-no-copy-copyright-files")) {
            LogDebug() << "Argument found:" << argument;
            copyCopyrightFiles = false;
        } else if (argument == QByteArray("-always-overwrite")) {
            LogDebug() << "Argument found:" << argument;
            alwaysOwerwriteEnabled = true;
        } else if (argument.startsWith("-qmake=")) {
            LogDebug() << "Argument found:" << argument;
            int index = argument.indexOf("=");
            qmakeExecutable = argument.mid(index+1);
        } else if (argument == QByteArray("-no-translations")) {
            LogDebug() << "Argument found:" << argument;
            skipTranslations = true;
        } else if (argument == QByteArray("-unsupported-allow-new-glibc")) {
            LogDebug() << "Argument found:" << argument;
            skipGlibcCheck = true;
        } else if (argument.startsWith("-extra-plugins=")) {
            LogDebug() << "Argument found:" << argument;
            int index = argument.indexOf("=");
            extraQtPlugins = QString(argument.mid(index + 1)).split(",");
        } else if (argument.startsWith("-exclude-libs=")) {
            LogDebug() << "Argument found:" << argument;
            int index = argument.indexOf("=");
            excludeLibs = QString(argument.mid(index + 1)).split(",");
        } else if (argument.startsWith("-ignore-glob=")) {
            LogDebug() << "Argument found:" << argument;
            int index = argument.indexOf("=");
            ignoreGlob += argument.mid(index + 1);
        } else if (argument.startsWith("-updateinformation=")) {
            LogDebug() << "Argument found:" << argument;
            int index = argument.indexOf("=");
            updateInformation = QString(argument.mid(index+1));
        } else if (argument.startsWith("-qtlibinfix=")) {
            LogDebug() << "Argument found:" << argument;
            int index = argument.indexOf("=");
            qtLibInfix = QString(argument.mid(index+1));
        } else if (argument.startsWith("--")) {
            LogError() << "Error: arguments must not start with --, only -:" << argument << "\n";
            return 1;
        } else {
            LogError() << "Unknown argument:" << argument << "\n";
            return 1;
        }
    }
    
    // We need to catch those errors at the source of the problem
    // https://github.com/AppImage/appimage.github.io/search?q=GLIBC&unscoped_q=GLIBC&type=Issues
    //const char *glcv = gnu_get_libc_version ();
    //if(skipGlibcCheck) {
    //    qInfo() << "WARNING: Not checking glibc on the host system.";
    //    qInfo() << "         The resulting AppDir or AppImage may not run on older systems.";
    //    qInfo() << "         This mode is unsupported and discouraged.";
    //    qInfo() << "         For more information, please see";
    //    qInfo() << "         https://github.com/probonopd/linuxdeployqt/issues/340";
    // } else {
    //    // openSUSE Leap 15.0 uses glibc 2.26 and is used on OBS
    //    // Ubuntu Xenial (16.04) uses glibc 2.23
    //    // Ubuntu Bionic (18.04) uses glibc 2.27
    //    if (strverscmp (glcv, "2.28") >= 0) {
    //        qInfo() << "ERROR: The host system is too new.";
    //        qInfo() << "Please run on a system with a glibc version no newer than what comes with the oldest";
    //        qInfo() << "currently still-supported mainstream distribution (Ubuntu Bionic), which is glibc 2.27.";
    //        qInfo() << "This is so that the resulting bundle will work on most still-supported Linux distributions.";
    //        qInfo() << "For more information, please see";
    //        qInfo() << "https://github.com/probonopd/linuxdeployqt/issues/340";
    //        return 1;
    //    }
    //}

    if (argc < 2 || (firstArgument.startsWith("-"))) {
        qInfo() << "";
        qInfo() << "Usage: linuxdeployqt <app-binary|desktop file> [options]";
        qInfo() << "";
        qInfo() << "Options:";
        qInfo() << "   -always-overwrite        : Copy files even if the target file exists.";
        qInfo() << "   -appimage                : Create an AppImage (implies -bundle-non-qt-libs).";
        qInfo() << "   -bundle-non-qt-libs      : Also bundle non-core, non-Qt libraries.";
        qInfo() << "   -exclude-libs=<list>     : List of libraries which should be excluded,";
        qInfo() << "                              separated by comma.";
        qInfo() << "   -ignore-glob=<glob>      : Glob pattern relative to appdir to ignore when";
        qInfo() << "                              searching for libraries.";
        qInfo() << "   -executable=<path>       : Let the given executable use the deployed libraries";
        qInfo() << "                              too";
        qInfo() << "   -extra-plugins=<list>    : List of extra plugins which should be deployed,";
        qInfo() << "                              separated by comma.";
        qInfo() << "   -no-copy-copyright-files : Skip deployment of copyright files.";
        qInfo() << "   -no-plugins              : Skip plugin deployment.";
        qInfo() << "   -no-strip                : Don't run 'strip' on the binaries.";
        qInfo() << "   -no-translations         : Skip deployment of translations.";
        qInfo() << "   -qmake=<path>            : The qmake executable to use.";
        qInfo() << "   -qmldir=<path>           : Scan for QML imports in the given path.";
        qInfo() << "   -qmlimport=<path>        : Add the given path to QML module search locations.";
        qInfo() << "   -show-exclude-libs       : Print exclude libraries list.";
        qInfo() << "   -verbose=<0-3>           : 0 = no output, 1 = error/warning (default),";
        qInfo() << "                              2 = normal, 3 = debug.";
        qInfo() << "   -updateinformation=<update string>        : Embed update information STRING; if zsyncmake is installed, generate zsync file";
        qInfo() << "   -qtlibinfix=<infix>      : Adapt the .so search if your Qt distribution has infix.";
        qInfo() << "   -version                 : Print version statement and exit.";
        qInfo() << "";
        qInfo() << "linuxdeployqt takes an application as input and makes it";
        qInfo() << "self-contained by copying in the Qt libraries and plugins that";
        qInfo() << "the application uses.";
        qInfo() << "";
        qInfo() << "By default it deploys the Qt instance that qmake on the $PATH points to.";
        qInfo() << "The '-qmake' option can be used to point to the qmake executable";
        qInfo() << "to be used instead.";
        qInfo() << "";
        qInfo() << "Plugins related to a Qt library are copied in with the library.";
        /* TODO: To be implemented
        qDebug() << "The accessibility, image formats, and text codec";
        qDebug() << "plugins are always copied, unless \"-no-plugins\" is specified.";
        */
        qInfo() << "";
        qInfo() << "See the \"Deploying Applications on Linux\" topic in the";
        qInfo() << "documentation for more information about deployment on Linux.";

        return 1;
    }

    QString desktopFile = "";
    QString desktopExecEntry = "";
    QString desktopIconEntry = "";

    if (argc > 2) {
        /* If we got a desktop file as the argument, try to figure out the application binary from it.
         * This has the advantage that we can also figure out the icon file this way, and have less work
         * to do when using linuxdeployqt. */
        if (firstArgument.endsWith(".desktop")){
            qDebug() << "Desktop file as first argument:" << firstArgument;

            /* Check if the desktop file really exists */
            if (! QFile::exists(firstArgument)) {
                LogError() << "Desktop file in first argument does not exist!";
                return 1;
            }
            QSettings * settings = 0;
            settings = new QSettings(firstArgument, QSettings::IniFormat);
            desktopExecEntry = settings->value("Desktop Entry/Exec", "r").toString().split(' ').first().split('/').last().trimmed();
            qDebug() << "desktopExecEntry:" << desktopExecEntry;
            desktopFile = firstArgument;
            desktopIconEntry = settings->value("Desktop Entry/Icon", "r").toString().split(' ').first();
            qDebug() << "desktopIconEntry:" << desktopIconEntry;

            QString candidateBin = QDir::cleanPath(QFileInfo(firstArgument).absolutePath() + desktopExecEntry); // Not FHS-like

            /* Search directory for an executable with the name in the Exec= key */
            QString directoryToBeSearched;
            directoryToBeSearched = QDir::cleanPath(QFileInfo(firstArgument).absolutePath());

            QDirIterator it(directoryToBeSearched, QDirIterator::Subdirectories);
            while (it.hasNext()) {
                it.next();
                if((it.fileName() == desktopExecEntry) && (it.fileInfo().isFile()) && (it.fileInfo().isExecutable())){
                    qDebug() << "Found binary from desktop file:" << it.fileInfo().canonicalFilePath();
                    appBinaryPath = it.fileInfo().absoluteFilePath();
                    break;
                }
            }

            /* Only if we could not find it below the directory in which the desktop file resides, search above */
            if(appBinaryPath == ""){
                if(QFileInfo(QDir::cleanPath(QFileInfo(firstArgument).absolutePath() + "/../../bin/" + desktopExecEntry)).exists()){
                    directoryToBeSearched = QDir::cleanPath(QFileInfo(firstArgument).absolutePath() + "/../../");
                } else {
                    directoryToBeSearched = QDir::cleanPath(QFileInfo(firstArgument).absolutePath() + "/../");
                }
                QDirIterator it2(directoryToBeSearched, QDirIterator::Subdirectories);
                while (it2.hasNext()) {
                    it2.next();
                    if((it2.fileName() == desktopExecEntry) && (it2.fileInfo().isFile()) && (it2.fileInfo().isExecutable())){
                        qDebug() << "Found binary from desktop file:" << it2.fileInfo().canonicalFilePath();
                        appBinaryPath = it2.fileInfo().absoluteFilePath();
                        break;
                    }
                }
            }

            if(appBinaryPath == ""){
                if((QFileInfo(candidateBin).isFile()) && (QFileInfo(candidateBin).isExecutable())) {
                    appBinaryPath = QFileInfo(candidateBin).absoluteFilePath();
                } else {
                    LogError() << "Could not determine the path to the executable based on the desktop file\n";
                    return 1;
                }
            }

        } else {
            appBinaryPath = firstArgument;
            appBinaryPath = QFileInfo(QDir::cleanPath(appBinaryPath)).absoluteFilePath();
        }
    }

    // Allow binaries next to linuxdeployqt to be found; this is useful for bundling
    // this application itself together with helper binaries such as patchelf
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
    QString oldPath = env.value("PATH");
    QString newPath = QCoreApplication::applicationDirPath() + ":" + oldPath;
    LogDebug() << newPath;
    setenv("PATH",newPath.toUtf8().constData(),1);

    QString appName = QDir::cleanPath(QFileInfo(appBinaryPath).fileName());

    QString appDir = QDir::cleanPath(appBinaryPath + "/../");
    if (QDir().exists(appDir) == false) {
        qDebug() << "Error: Could not find AppDir" << appDir;
        return 1;
    }

    /* FHS-like mode is for an application that has been installed to a $PREFIX which is otherwise empty, e.g., /path/to/usr.
     * In this case, we want to construct an AppDir in /path/to. */
    if (QDir().exists((QDir::cleanPath(appBinaryPath + "/../../bin"))) == true) {
        fhsPrefix = QDir::cleanPath(appBinaryPath + "/../../");
        qDebug() << "FHS-like mode with PREFIX, fhsPrefix:" << fhsPrefix;
        fhsLikeMode = true;
    } else {
        qDebug() << "Not using FHS-like mode";
    }

    if (QDir().exists(appBinaryPath)) {
        qDebug() << "app-binary:" << appBinaryPath;
    } else {
        LogError() << "Error: Could not find app-binary" << appBinaryPath;
        return 1;
    }

    QString appDirPath;
    QString relativeBinPath;
    if(fhsLikeMode == false){
        appDirPath = appDir;
        relativeBinPath = appName;
    } else {
        appDirPath = QDir::cleanPath(fhsPrefix + "/../");
        QString relativePrefix = fhsPrefix.replace(appDirPath+"/", "");
        relativeBinPath = relativePrefix + "/bin/" + appName;
    }
    if(appDirPath == "/"){
        LogError() << "'/' is not a valid AppDir. Please refer to the documentation.";
        LogError() << "Consider adding INSTALL_ROOT or DESTDIR to your install steps.";
        return 1;
    }
    qDebug() << "appDirPath:" << appDirPath;
    qDebug() << "relativeBinPath:" << relativeBinPath;

    QFile appRun(appDirPath + "/AppRun");
    if(appRun.exists()){
        qDebug() << "Keeping existing AppRun";
    } else {
        if (!QFile::link(relativeBinPath, appDirPath + "/AppRun")) {
            LogError() << "Could not create AppRun link";
        }
    }

    /* Copy the desktop file in place, into the top level of the AppDir */
    if(desktopFile != ""){
        QString destination = QDir::cleanPath(appDirPath + "/" + QFileInfo(desktopFile).fileName());
        if(QFileInfo(destination).exists() == false){
            if (QFile::copy(desktopFile, destination)){
                qDebug() << "Copied" << desktopFile << "to" << destination;
            }
        }
        if(QFileInfo(destination).isFile() == false){
            LogError() << destination << "does not exist and could not be copied there\n";
            return 1;
        }
    }

    /* To make an AppDir, we need to find the icon and copy it in place */
    QStringList candidates;
    QString iconToBeUsed = "";
    if(desktopIconEntry != ""){
        QDirIterator it3(appDirPath, QDirIterator::Subdirectories);
        while (it3.hasNext()) {
            it3.next();
            if((it3.fileName().startsWith(desktopIconEntry)) && ((it3.fileName().endsWith(".png")) || (it3.fileName().endsWith(".svg")) || (it3.fileName().endsWith(".svgz")) || (it3.fileName().endsWith(".xpm")))){
                candidates.append(it3.filePath());
            }
        }
        qDebug() << "Found icons from desktop file:" << candidates;

        /* Select the main icon from the candidates */
        if(candidates.length() == 1){
            iconToBeUsed = candidates.at(0); // The only choice
        } else if(candidates.length() > 1){
            const QStringList iconPriorities{"256", "128", "svg", "svgz", "512", "1024", "64", "48", "xpm"};
            foreach (const QString &iconPriority, iconPriorities) {
                const auto filteredCandidates = candidates.filter(iconPriority);
                if (!filteredCandidates.isEmpty()) {
                    iconToBeUsed = filteredCandidates.first();
                    break;
                }
            }
        }

        /* Additional check to make sure that the undocumented, unsupported and not recommended
         * -unsupported-allow-new-glibc option is not abused to create results that are broken; see
         * https://github.com/probonopd/linuxdeployqt/issues/340 for more information
         * TODO: Add funtionality that would automatically bundle glibc fully and correctly in this case */
        if(skipGlibcCheck == true){
            if(QFileInfo(appDirPath + "/usr/share/doc/libc6/copyright").exists() == false) exit(1);
        }

        /* Copy in place */
        if(iconToBeUsed != ""){
            /* Check if there is already an icon and only if there is not, copy it to the AppDir.
             * As per the ROX AppDir spec, also copying to .DirIcon. */
            QString preExistingToplevelIcon = "";
            if(QFileInfo(appDirPath + "/" + desktopIconEntry + ".xpm").exists() == true){
                preExistingToplevelIcon = appDirPath + "/" + desktopIconEntry + ".xpm";
                if(QFileInfo(appDirPath + "/.DirIcon").exists() == false) QFile::copy(preExistingToplevelIcon, appDirPath + "/.DirIcon");
            }
            if(QFileInfo(appDirPath + "/" + desktopIconEntry + ".svgz").exists() == true){
                preExistingToplevelIcon = appDirPath + "/" + desktopIconEntry + ".svgz";
                if(QFileInfo(appDirPath + "/.DirIcon").exists() == false) QFile::copy(preExistingToplevelIcon, appDirPath + "/.DirIcon");
            }
            if(QFileInfo(appDirPath + "/" + desktopIconEntry + ".svg").exists() == true){
                preExistingToplevelIcon = appDirPath + "/" + desktopIconEntry + ".svg";
                if(QFileInfo(appDirPath + "/.DirIcon").exists() == false) QFile::copy(preExistingToplevelIcon, appDirPath + "/.DirIcon");
            }
            if(QFileInfo(appDirPath + "/" + desktopIconEntry + ".png").exists() == true){
                preExistingToplevelIcon = appDirPath + "/" + desktopIconEntry + ".png";
                if(QFileInfo(appDirPath + "/.DirIcon").exists() == false) QFile::copy(preExistingToplevelIcon, appDirPath + "/.DirIcon");
            }

            if(preExistingToplevelIcon != ""){
                qDebug() << "preExistingToplevelIcon:" << preExistingToplevelIcon;
            } else {
                qDebug() << "iconToBeUsed:" << iconToBeUsed;
                QString targetIconPath = appDirPath + "/" + QFileInfo(iconToBeUsed).fileName();
                if (QFile::copy(iconToBeUsed, targetIconPath)){
                    qDebug() << "Copied" << iconToBeUsed << "to" << targetIconPath;
                    QFile::copy(targetIconPath, appDirPath + "/.DirIcon");
                } else {
                    LogError() << "Could not copy" << iconToBeUsed << "to" << targetIconPath << "\n";
                    exit(1);
                }
            }
        }
    }

    if (appimage) {
        if(checkAppImagePrerequisites(appDirPath) == false){
            LogError() << "checkAppImagePrerequisites failed\n";
            return 1;
        }
    }

    if (!excludeLibs.isEmpty())
    {
        qWarning() << "WARNING: Excluding the following libraries might break the AppImage. Please double-check the list:" << excludeLibs;
    }

    DeploymentInfo deploymentInfo = deployQtLibraries(appDirPath, additionalExecutables,
                                                      qmakeExecutable);

    // Convenience: Look for .qml files in the current directoty if no -qmldir specified.
    if (qmlDirs.isEmpty()) {
        QDir dir;
        if (!dir.entryList(QStringList() << QStringLiteral("*.qml")).isEmpty()) {
            qmlDirs += QStringLiteral(".");
        }
    }

    if (!qmlDirs.isEmpty()) {
        bool ok = deployQmlImports(appDirPath, deploymentInfo, qmlDirs, qmlImportPaths);
        if (!ok && qmldirArgumentUsed)
            return 1; // exit if the user explicitly asked for qml import deployment
        // Update deploymentInfo.deployedLibraries - the QML imports
        // may have brought in extra libraries as dependencies.
        deploymentInfo.deployedLibraries += findAppLibraries(appDirPath);
        deploymentInfo.deployedLibraries.removeDuplicates();
    }

    deploymentInfo.usedModulesMask = 0;
    findUsedModules(deploymentInfo);

    if (plugins && !deploymentInfo.qtPath.isEmpty()) {
        if (deploymentInfo.pluginPath.isEmpty())
            deploymentInfo.pluginPath = QDir::cleanPath(deploymentInfo.qtPath + "/../plugins");
        deployPlugins(appDirPath, deploymentInfo);
        createQtConf(appDirPath);
    }

    if (runStripEnabled)
        stripAppBinary(appDirPath);

    if (!skipTranslations) {
        deployTranslations(appDirPath, deploymentInfo.usedModulesMask);
    }

    if (appimage) {
        int result = createAppImage(appDirPath);
        LogDebug() << "result:" << result;
        exit(result);
    }
    exit(0);
}

  1. 执行cmake
    cmake CMakeLists.txt
    

202007版直接执行成功了, 而2203版的报错

CMake Warning (dev) in CMakeLists.txt:
  No project() command is present.  The top-level CMakeLists.txt file must
  contain a literal, direct call to the project() command.  Add a line of
  code such as

    project(ProjectName)

  near the top of the file, but after cmake_minimum_required().

  CMake is pretending there is a "project(Project)" command on the first
  line.
This warning is for project developers.  Use -Wno-dev to suppress it.

-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Warning (dev) at CMakeLists.txt:7 (add_definitions):
  Policy CMP0005 is not set: Preprocessor definition values are now escaped
  automatically.  Run "cmake --help-policy CMP0005" for policy details.  Use
  the cmake_policy command to set the policy and suppress this warning.
This warning is for project developers.  Use -Wno-dev to suppress it.

CMake Warning (dev) at CMakeLists.txt:8 (add_definitions):
  Policy CMP0005 is not set: Preprocessor definition values are now escaped
  automatically.  Run "cmake --help-policy CMP0005" for policy details.  Use
  the cmake_policy command to set the policy and suppress this warning.
This warning is for project developers.  Use -Wno-dev to suppress it.

CMake Warning (dev) at CMakeLists.txt:9 (add_definitions):
  Policy CMP0005 is not set: Preprocessor definition values are now escaped
  automatically.  Run "cmake --help-policy CMP0005" for policy details.  Use
  the cmake_policy command to set the policy and suppress this warning.
This warning is for project developers.  Use -Wno-dev to suppress it.

CMake Warning (dev) at CMakeLists.txt:10 (add_definitions):
  Policy CMP0005 is not set: Preprocessor definition values are now escaped
  automatically.  Run "cmake --help-policy CMP0005" for policy details.  Use
  the cmake_policy command to set the policy and suppress this warning.
This warning is for project developers.  Use -Wno-dev to suppress it.

CMake Error at CMakeLists.txt:12 (find_package):
  By not providing "FindQt5.cmake" in CMAKE_MODULE_PATH this project has
  asked CMake to find a package configuration file provided by "Qt5", but
  CMake did not find one.

  Could not find a package configuration file provided by "Qt5" with any of
  the following names:

    Qt5Config.cmake
    qt5-config.cmake

  Add the installation prefix of "Qt5" to CMAKE_PREFIX_PATH or set "Qt5_DIR"
  to a directory containing one of the above files.  If "Qt5" provides a
  separate development package or SDK, be sure it has been installed.


CMake Warning (dev) in CMakeLists.txt:
  No cmake_minimum_required command is present.  A line of code such as

    cmake_minimum_required(VERSION 3.16)

  should be added at the top of the file.  The version specified may be lower
  if you wish to support older CMake versions for this project.  For more
  information run "cmake --help-policy CMP0000".
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Configuring incomplete, errors occurred!
See also "/home/kylin/linuxdeployqt/linuxdeployqt/tools/linuxdeployqt/CMakeFiles/CMakeOutput.log".

主要是找不到QT环境

Could not find a package configuration file provided by "Qt5" with any of
  the following names:

    Qt5Config.cmake
    qt5-config.cmake

  Add the installation prefix of "Qt5" to CMAKE_PREFIX_PATH or set "Qt5_DIR"
  to a directory containing one of the above files.  If "Qt5" provides a
  separate development package or SDK, be sure it has been installed.
  1. 下载cmake-gui配置环境变量
    sudo apt install cmake-gui
    cmake-giu
    

点击Browse_Source选择如下参考目录: linuxdeployqt 目录下的 tool/linuxdeployqt
并在该目录新建个build目录, 然后点击Browse_Build选择build目录
点击Configure
在这里插入图片描述
选择默认的Unix Makefiles 生成方式
在这里插入图片描述
报错是正常的, 因为这里都还没配置QT环境变量
在这里插入图片描述
配置QT环境变量参考如下:(相对自己安装QT的目录, QT安装目录下包含Qt-5Config.cmake的文件夹路径)
在这里插入图片描述
点击Configure重新检测配置, 没问题了, 他自己会添加另一个环境变量
在这里插入图片描述
点击Generate生成Makefile,没问题的话可以看到在build目录下已经有Makefile了.
(这里可能会报什么moc错误, 查看前面的步骤有没有错误, 照着做我这没有出现报错了.)
然后关掉cmake-gui, 进行make

cd ./build
make

可以看到已经生成了 linuxdeployqt 可执行程序了, 然后把它放在 /usr/local/bin 下即可日常使用

sudo cp ./linuxdeployqt /usr/local/bin

发布QT程序

  1. 将项目代码拷贝到该系统上, 删掉 .user 后缀文件, 用QT Creator 打开 .pro工程, 会重新配置 编译链, 选择该系统的编译链
    在这里插入图片描述
    由于使用的是跟windows上同版本的QT IDE, 所以编译很顺利.
    编译release后, cd到release目录, 删除掉除了目标程序之外的所有没必要的东西(.o, moc*, .h等等), 执行 linuxdeployqt 进行发布, 比如我的程序角 myProgram
linuxdeployqt myProgram -appimage

我这里报错了, 是因为没有设置QT qmake的环境变量

ERROR: qmake not found on the $PATH

设置环境变量到 ~/.bashrc 的最后并 source 生效一下

vi ~/.bashrc
export PATH=/home/kylin/Qt5.12.3/5.12.3/gcc_64/bin:$PATH
export LD_LIBRARY_PATH=/home/kylin/Qt5.12.3/5.12.3/gcc_64/lib:$LD_LIBRARY_PATH
export QT_PLUGIN_PATH=/home/kylin/Qt5.12.3/5.12.3/gcc_64/plugins:$QT_PLUGIN_PATH
export QML2_IMPORT_PATH=/home/kylin/Qt5.12.3/5.12.3/gcc_64/qml:$QML2_IMPORT_PATH
source ~/.bashrc

重新 linuxdeployqt myProgram -appimage 一下通过.期间狂点允许…

在这里插入图片描述
设置好桌面快捷方式的内容和图标后就可以打包了(dpkg或者直接tar)

在没有QT环境的目的机上运行

将文件夹拷贝(或者打好的包安装)到目的机上.
执行目的程序报错:

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.

不怕, 说找不到Qt platform plugin, 但打包好的目录下是有个plugins的, 说明是找不到而非没有. 在宿主机上能用就说明宿主机是有相关环境变量的, 那么到底是哪个环境变量呢, 在宿主机上 grep 所有环境变量找一下:

export | grep "plugins"

只出了一条搜索结果, 那感情就是它了

declare -x QT_PLUGIN_PATH="/home/kylin/Qt5.12.3/5.12.3/gcc_64/plugins:"

在目的程序目录设置目的机的环境变量

export QT_PLUGIN_PATH=$QT_PLUGIN_PATH:./plugins

然后程序运行成功~! 其实可以把这个环境变量搞成运行脚本, 通过快捷方式指向这个脚本即可不用手动配置了.

还可能报找不到./lib下的库, 则相对的 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib即可

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Angular Intro.js 是一个 AngularJS 模块,用于集成 Intro.js 库,以提供网站或应用程序的新手引导和演示。下面是使用步骤: 1. 安装 Angular Intro.js:通过 npm 安装 Angular Intro.js 模块。 ``` npm install angular-intro.js --save ``` 2. 引入 Intro.js 库:在项目引入 Intro.js 库,可以通过 npm 安装或直接使用 CDN 引入。 ``` <script src="https://cdnjs.cloudflare.com/ajax/libs/intro.js/2.9.3/intro.min.js"></script> ``` 3. 引入 Angular Intro.js 模块:在 AngularJS 应用程序引入 Angular Intro.js 模块。 ``` angular.module('myApp', ['angular-intro']); ``` 4. 在 HTML 使用:在 HTML 使用指令来添加新手引导和演示。 ``` <div introjs intro-options="options"> <p>这是一个新手引导示例</p> <button ng-click="showStep2()">下一步</button> </div> ``` 上面的代码,`introjs` 指令告诉 Angular Intro.js 应该在这个元素上启用引导,`intro-options` 属性包含 Intro.js 配置选项,`showStep2()` 是一个 AngularJS 控制器的函数,用于显示下一个步骤。 5. 在控制器配置选项和方法:在 AngularJS 控制器配置 Intro.js 选项和方法。 ``` angular.module('myApp').controller('myCtrl', function($scope) { $scope.options = { steps: [ { element: document.querySelector('#step1'), intro: '这是第一步' }, { element: document.querySelector('#step2'), intro: '这是第二步' } ] }; $scope.showStep2 = function() { introJs().goToStep(2).start(); }; }); ``` 上面的代码,`steps` 选项包含 Intro.js 步骤对象,每个步骤是一个包含 `element` 和 `intro` 属性的对象。`showStep2()` 函数使用 Intro.js 方法来显示第二步。 以上是 Angular Intro.js 的简单使用方法,可以根据需要进行配置和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值