银河麒麟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我也不清楚)不能正常使用.
于是要重新下载源码自己编译.
下载源码并配置
- 首先安装git:
sudo apt install git g++
- 再安装patchelf
sudo apt install patchelf
- 找个合适的文件夹cd进去后克隆最新的源码, cd到源码目录内的tools/linuxdeployqt
mkdir linuxdeployqt cd linuxdeployqt git clone https://github.com/probonopd/linuxdeployqt --depth=1
- 这里要修改几个地方, 否则生成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);
}
- 执行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.
- 下载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程序
- 将项目代码拷贝到该系统上, 删掉 .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即可