Files
linuxdeploy-plugin-qt/src/main.cpp
T
TheAssassin 72014cfbec Fix deployment of xcbglintegrations plugins
The destination didn't end with a slash, so, like cp, liblinuxdeploy
just created a file with that name and overwrote it with every new
plugin.
2019-02-23 14:00:48 +01:00

553 lines
20 KiB
C++

// system includes
#include <iostream>
#include <set>
#include <sstream>
#include <tuple>
#include <vector>
// library includes
#include <boost/filesystem.hpp>
#include <linuxdeploy/core/appdir.h>
#include <linuxdeploy/core/elf.h>
#include <linuxdeploy/core/log.h>
#include <linuxdeploy/util/util.h>
// local includes
#include "qt-modules.h"
#include "qml.h"
#include "util.h"
namespace bf = boost::filesystem;
using namespace linuxdeploy::core;
using namespace linuxdeploy::util::misc;
using namespace linuxdeploy::core::log;
std::string join(const std::vector<std::string> &list) {
return join(list.begin(), list.end());
}
std::string join(const std::set<std::string> &list) {
return join(list.begin(), list.end());
}
bool strStartsWith(const std::string &str, const std::string &prefix) {
if (str.size() < prefix.size())
return false;
return strncmp(str.c_str(), prefix.c_str(), prefix.size()) == 0;
}
bool strEndsWith(const std::string &str, const std::string &suffix) {
if (str.size() < suffix.size())
return false;
return strncmp(str.c_str() + (str.size() - suffix.size()), suffix.c_str(), suffix.size()) == 0;
}
bool deployPlatformPlugins(appdir::AppDir &appDir, const bf::path &qtPluginsPath) {
ldLog() << "Deploying platform plugins" << std::endl;
if (!appDir.deployLibrary(qtPluginsPath / "platforms/libqxcb.so", appDir.path() / "usr/plugins/platforms/"))
return false;
for (bf::directory_iterator i(qtPluginsPath / "platforminputcontexts"); i != bf::directory_iterator(); ++i) {
if (!appDir.deployLibrary(*i, appDir.path() / "usr/plugins/platforminputcontexts/"))
return false;
}
for (bf::directory_iterator i(qtPluginsPath / "imageformats"); i != bf::directory_iterator(); ++i) {
if (!appDir.deployLibrary(*i, appDir.path() / "usr/plugins/imageformats/"))
return false;
}
// TODO: platform themes -- https://github.com/probonopd/linuxdeployqt/issues/236
return true;
}
// little helper called by other integration plugins
bool deployIntegrationPlugins(appdir::AppDir& appDir, const bf::path& qtPluginsPath, const std::initializer_list<bf::path>& subDirs) {
for (const bf::path& subDir : subDirs) {
// make sure the path ends with a / so that liblinuxdeploy recognize the destination as a directory
auto dir = qtPluginsPath / subDir / "/";
if (!bf::is_directory(dir)) {
ldLog() << "Directory" << dir << "doesn't exist, skipping deployment" << std::endl;
continue;
}
for (bf::directory_iterator i(dir); i != bf::directory_iterator(); ++i) {
// append a trailing slash to make linuxdeploy aware of the destination being a directory
// otherwise, when the directory doesn't exist, it might just copy all files to files called like
// destinationDir
auto destinationDir = appDir.path() / "usr/plugins" / subDir / "";
if (!appDir.deployLibrary(*i, destinationDir))
return false;
}
}
return true;
}
bool deployXcbglIntegrationPlugins(appdir::AppDir& appDir, const bf::path& qtPluginsPath) {
ldLog() << "Deploying xcb-gl integrations" << std::endl;
return deployIntegrationPlugins(appDir, qtPluginsPath, {"xcbglintegrations/"});
}
bool deploySvgPlugins(appdir::AppDir &appDir, const bf::path &qtPluginsPath) {
ldLog() << "Deploying svg icon engine" << std::endl;
if (!appDir.deployLibrary(qtPluginsPath / "iconengines/libqsvgicon.so", appDir.path() / "usr/plugins/iconengines/"))
return false;
return true;
}
bool deployBearerPlugins(appdir::AppDir &appDir, const bf::path &qtPluginsPath) {
ldLog() << "Deploying bearer plugins" << std::endl;
for (bf::directory_iterator i(qtPluginsPath / "bearer"); i != bf::directory_iterator(); ++i) {
if (!appDir.deployLibrary(*i, appDir.path() / "usr/plugins/bearer/"))
return false;
}
return true;
}
bool deploySqlPlugins(appdir::AppDir &appDir, const bf::path &qtPluginsPath) {
ldLog() << "Deploying SQL plugins" << std::endl;
for (bf::directory_iterator i(qtPluginsPath / "sqldrivers"); i != bf::directory_iterator(); ++i) {
if (!appDir.deployLibrary(*i, appDir.path() / "usr/plugins/sqldrivers/"))
return false;
}
return true;
}
bool deployPositioningPlugins(appdir::AppDir &appDir, const bf::path &qtPluginsPath) {
ldLog() << "Deploying positioning plugins" << std::endl;
for (bf::directory_iterator i(qtPluginsPath / "position"); i != bf::directory_iterator(); ++i) {
if (!appDir.deployLibrary(*i, appDir.path() / "usr/plugins/position/"))
return false;
}
return true;
}
bool deployMultimediaPlugins(appdir::AppDir &appDir, const bf::path &qtPluginsPath) {
ldLog() << "Deploying mediaservice plugins" << std::endl;
for (bf::directory_iterator i(qtPluginsPath / "mediaservice"); i != bf::directory_iterator(); ++i) {
if (!appDir.deployLibrary(*i, appDir.path() / "usr/plugins/mediaservice/"))
return false;
}
ldLog() << "Deploying audio plugins" << std::endl;
for (bf::directory_iterator i(qtPluginsPath / "audio"); i != bf::directory_iterator(); ++i) {
if (!appDir.deployLibrary(*i, appDir.path() / "usr/plugins/audio/"))
return false;
}
return true;
}
bool deployWebEnginePlugins(appdir::AppDir &appDir, const bf::path &qtLibexecsPath, const bf::path &qtDataPath,
const bf::path &qtTranslationsPath) {
ldLog() << "Deploying web engine plugins" << std::endl;
const auto newLibexecPath = appDir.path() / "usr/libexec/";
// make sure directory is there before trying to write a qt.conf file
bf::create_directories(newLibexecPath);
for (bf::directory_iterator i(qtLibexecsPath); i != bf::directory_iterator(); ++i) {
auto &entry = *i;
const std::string prefix = "QtWeb";
auto fileName = entry.path().filename();
// skip files which don't start with prefix
if (!strStartsWith(fileName.string(), prefix))
continue;
if (!appDir.deployExecutable(*i, newLibexecPath))
return false;
}
for (const auto &fileName : {"qtwebengine_resources.pak",
"qtwebengine_devtools_resources.pak",
"qtwebengine_resources_100p.pak",
"qtwebengine_resources_200p.pak", "icudtl.dat"}) {
auto path = qtDataPath / "resources" / fileName;
if (bf::is_regular_file(path))
appDir.deployFile(path, appDir.path() / "usr/resources/");
}
if (bf::is_directory(qtTranslationsPath / "qtwebengine_locales")) {
for (bf::directory_iterator i(qtTranslationsPath / "qtwebengine_locales"); i != bf::directory_iterator(); ++i) {
appDir.deployFile(*i, appDir.path() / "usr/translations/qtwebengine_locales/");
}
}
const auto qtConfPath = newLibexecPath / "qt.conf";
std::ofstream ofs(qtConfPath.string());
if (!ofs) {
ldLog() << LD_ERROR << "Failed to open" << qtConfPath << "for writing" << std::endl;
return false;
}
ofs << "# generated by linuxdeploy" << std::endl
<< "[Paths]" << std::endl
<< "Prefix = ../" << std::endl;
return true;
}
bool createQtConf(appdir::AppDir &appDir) {
auto qtConfPath = appDir.path() / "usr" / "bin" / "qt.conf";
if (bf::is_regular_file(qtConfPath)) {
ldLog() << LD_WARNING << "Skipping creation of Qt conf file: file exists:" << qtConfPath << std::endl;
} else {
ldLog() << "Creating Qt conf file:" << qtConfPath << std::endl;
}
std::ofstream ofs(qtConfPath.string());
if (!ofs) {
ldLog() << LD_ERROR << "Failed to open qt.conf for writing" << std::endl;
return false;
}
ofs << "# generated by linuxdeploy" << std::endl
<< "[Paths]" << std::endl
<< "Prefix = ../" << std::endl
<< "Plugins = plugins" << std::endl
<< "Imports = qml" << std::endl
<< "Qml2Imports = qml" << std::endl;
return true;
}
bool
deployTranslations(appdir::AppDir &appDir, const bf::path &qtTranslationsPath, const std::vector<QtModule> &modules) {
if (qtTranslationsPath.empty() || !bf::is_directory(qtTranslationsPath)) {
ldLog() << LD_WARNING << "Translation directory does not exist, skipping deployment";
return true;
}
ldLog() << "Qt translations directory:" << qtTranslationsPath << std::endl;
auto checkName = [&appDir, &modules](const bf::path &fileName) {
if (!strEndsWith(fileName.string(), ".qm"))
return false;
// always deploy basic Qt translations
if (strStartsWith(fileName.string(), "qt_") && bf::basename(fileName).size() >= 5 &&
bf::basename(fileName).size() <= 6)
return true;
for (const auto &module : modules) {
if (!module.translationFilePrefix.empty() && strStartsWith(fileName.string(), module.translationFilePrefix))
return true;
}
return false;
};
for (bf::directory_iterator i(qtTranslationsPath); i != bf::directory_iterator(); ++i) {
if (!bf::is_regular_file(*i))
continue;
const auto fileName = (*i).path().filename();
if (checkName(fileName))
appDir.deployFile(*i, appDir.path() / "usr/translations/");
}
const auto& appDirTranslationsPath = appDir.path() / "usr/translations";
for (auto& i: bf::recursive_directory_iterator(appDir.path())) {
if (!bf::is_regular_file(i) || pathContainsFile(appDirTranslationsPath, i))
continue;
const auto fileName = i.path().filename();
if (strEndsWith(fileName.string(), ".qm"))
appDir.createRelativeSymlink(i, appDir.path() / "usr/translations" / fileName);
}
return true;
}
bool deployQmlFiles(appdir::AppDir &appDir, const bf::path &installLibsPath) {
try {
deployQml(appDir, installLibsPath);
} catch (const QmlImportScannerError &) {
return false;
}
return true;
}
int main(const int argc, const char *const *const argv) {
// set up verbose logging if $DEBUG is set
if (getenv("DEBUG"))
ldLog::setVerbosity(LD_DEBUG);
args::ArgumentParser parser("linuxdeploy Qt plugin",
"Bundles Qt resources. For use with an existing AppDir, created by linuxdeploy.");
args::ValueFlag<bf::path> appDirPath(parser, "appdir path", "Path to an existing AppDir", {"appdir"});
args::ValueFlagList<std::string> extraPlugins(parser, "plugin",
"Extra Qt plugin to deploy (specified by name, filename or path)",
{'p', "extra-plugin"});
args::Flag pluginType(parser, "", "Print plugin type and exit", {"plugin-type"});
args::Flag pluginApiVersion(parser, "", "Print plugin API version and exit", {"plugin-api-version"});
try {
parser.ParseCLI(argc, argv);
} catch (const args::ParseError &) {
std::cerr << parser;
return 1;
}
if (pluginType) {
std::cout << "input" << std::endl;
return 0;
}
if (pluginApiVersion) {
std::cout << "0" << std::endl;
return 0;
}
if (!appDirPath) {
ldLog() << LD_ERROR << "--appdir parameter required" << std::endl;
std::cout << std::endl << parser;
return 1;
}
if (!bf::is_directory(appDirPath.Get())) {
ldLog() << LD_ERROR << "No such directory:" << appDirPath.Get() << std::endl;
return 1;
}
appdir::AppDir appDir(appDirPath.Get());
// allow disabling copyright files deployment via environment variable
if (getenv("DISABLE_COPYRIGHT_FILES_DEPLOYMENT") != nullptr) {
ldLog() << std::endl << LD_WARNING << "Copyright files deployment disabled" << std::endl;
appDir.setDisableCopyrightFilesDeployment(true);
}
// check which libraries and plugins the binaries and libraries depend on
std::set<std::string> libraryNames;
for (const auto &path : appDir.listSharedLibraries()) {
libraryNames.insert(path.filename().string());
try {
for (const auto &dependency : elf::ElfFile(path).traceDynamicDependencies()) {
libraryNames.insert(dependency.filename().string());
}
} catch (const elf::ElfFileParseError &e) {
ldLog() << LD_DEBUG << "Failed to parse file as ELF file:" << path << std::endl;
}
}
{
ldLog() << LD_DEBUG << "Libraries to consider: ";
for (const auto &libraryName : libraryNames)
ldLog() << " " << libraryName;
ldLog() << std::endl;
}
// check for Qt modules
std::vector<QtModule> foundQtModules;
std::vector<QtModule> extraQtModules;
auto matchesQtModule = [](std::string libraryName, const QtModule &module) {
// extract filename if argument is path
if (bf::is_regular_file(libraryName))
libraryName = bf::path(libraryName).filename().string();
// adding the trailing dot makes sure e.g., libQt5WebEngineCore won't be matched as webengine and webenginecore
const auto &libraryPrefix = module.libraryFilePrefix + ".";
// match plugin filename
if (strncmp(libraryName.c_str(), libraryPrefix.c_str(), libraryPrefix.size()) == 0) {
ldLog() << LD_DEBUG << "-> matches library filename, found module:" << module.name << std::endl;
return true;
}
// match plugin name
if (strcmp(libraryName.c_str(), module.name.c_str()) == 0) {
ldLog() << LD_DEBUG << "-> matches module name, found module:" << module.name << std::endl;
return true;
}
return false;
};
std::copy_if(QtModules.begin(), QtModules.end(), std::back_inserter(foundQtModules),
[&matchesQtModule, &libraryNames](const QtModule &module) {
return std::find_if(libraryNames.begin(), libraryNames.end(),
[&matchesQtModule, &module](const std::string &libraryName) {
return matchesQtModule(libraryName, module);
}) != libraryNames.end();
});
std::vector<std::string> extraPluginsFromEnv;
const auto* const extraPluginsFromEnvData = getenv("EXTRA_QT_PLUGINS");
if (extraPluginsFromEnvData != nullptr)
extraPluginsFromEnv = linuxdeploy::util::split(std::string(extraPluginsFromEnvData, ';'));
for (const auto& pluginsList : {static_cast<std::vector<std::string>>(extraPlugins.Get()), extraPluginsFromEnv}) {
std::copy_if(QtModules.begin(), QtModules.end(), std::back_inserter(extraQtModules),
[&matchesQtModule, &libraryNames, &pluginsList](const QtModule &module) {
return std::find_if(pluginsList.begin(), pluginsList.end(),
[&matchesQtModule, &module](const std::string &libraryName) {
return matchesQtModule(libraryName, module);
}) != pluginsList.end();
}
);
}
{
std::set<std::string> moduleNames;
std::for_each(foundQtModules.begin(), foundQtModules.end(), [&moduleNames](const QtModule &module) {
moduleNames.insert(module.name);
});
ldLog() << "Found Qt modules:" << join(moduleNames) << std::endl;
}
{
std::set<std::string> moduleNames;
std::for_each(extraQtModules.begin(), extraQtModules.end(), [&moduleNames](const QtModule &module) {
moduleNames.insert(module.name);
});
ldLog() << "Extra Qt modules:" << join(moduleNames) << std::endl;
}
if (foundQtModules.empty() && extraQtModules.empty()) {
ldLog() << LD_ERROR << "Could not find Qt modules to deploy" << std::endl;
return 1;
}
auto qmakePath = findQmake();
if (qmakePath.empty()) {
ldLog() << LD_ERROR << "Could not find qmake, please install or provide path using $QMAKE" << std::endl;
return 1;
}
if (!bf::exists(qmakePath)) {
ldLog() << LD_ERROR << "No such file or directory:" << qmakePath << std::endl;
return 1;
}
ldLog() << "Using qmake:" << qmakePath << std::endl;
auto qmakeVars = queryQmake(qmakePath);
if (qmakeVars.empty()) {
ldLog() << LD_ERROR << "Failed to query Qt paths using qmake -query" << std::endl;
return 1;
}
const bf::path qtPluginsPath = qmakeVars["QT_INSTALL_PLUGINS"];
const bf::path qtLibexecsPath = qmakeVars["QT_INSTALL_LIBEXECS"];
const bf::path qtDataPath = qmakeVars["QT_INSTALL_DATA"];
const bf::path qtTranslationsPath = qmakeVars["QT_INSTALL_TRANSLATIONS"];
const bf::path qtLibsPath = qmakeVars["QT_INSTALL_LIBS"];
const bf::path qtInstallQmlPath = qmakeVars["QT_INSTALL_QML"];
ldLog() << std::endl;
ldLog() << "QT_INSTALL_LIBS:" << qtLibsPath << std::endl;
std::ostringstream newLibraryPath;
newLibraryPath << qtLibsPath.string() << ":" << getenv("LD_LIBRARY_PATH");
setenv("LD_LIBRARY_PATH", newLibraryPath.str().c_str(), true);
ldLog() << "Prepending QT_INSTALL_LIBS path to $LD_LIBRARY_PATH, new $LD_LIBRARY_PATH:" << newLibraryPath.str()
<< std::endl;
auto qtModulesToDeploy = foundQtModules;
qtModulesToDeploy.reserve(extraQtModules.size());
std::copy(extraQtModules.begin(), extraQtModules.end(), std::back_inserter(qtModulesToDeploy));
for (const auto &module : qtModulesToDeploy) {
ldLog() << std::endl << "-- Deploying module:" << module.name << "--" << std::endl;
if (module.name == "gui") {
if (!deployPlatformPlugins(appDir, qtPluginsPath))
return 1;
}
if (module.name == "opengl" || module.name == "gui" || module.name == "xcbqpa") {
if (!deployXcbglIntegrationPlugins(appDir, qtPluginsPath))
return 1;
}
if (module.name == "network") {
if (!deployBearerPlugins(appDir, qtPluginsPath))
return 1;
}
if (module.name == "svg") {
if (!deploySvgPlugins(appDir, qtPluginsPath))
return 1;
}
if (module.name == "sql") {
if (!deploySqlPlugins(appDir, qtPluginsPath))
return 1;
}
if (module.name == "positioning") {
if (!deployPositioningPlugins(appDir, qtPluginsPath))
return 1;
}
if (module.name == "multimedia") {
if (!deployMultimediaPlugins(appDir, qtPluginsPath))
return 1;
}
if (module.name == "webenginecore") {
if (!deployWebEnginePlugins(appDir, qtLibexecsPath, qtDataPath, qtTranslationsPath))
return 1;
}
if (module.name == "qml") {
if (!deployQmlFiles(appDir, qtInstallQmlPath))
return 1;
}
}
ldLog() << std::endl << "-- Deploying translations --" << std::endl;
if (!deployTranslations(appDir, qtTranslationsPath, qtModulesToDeploy)) {
ldLog() << LD_ERROR << "Failed to deploy translations" << std::endl;
return 1;
}
ldLog() << std::endl << "-- Executing deferred operations --" << std::endl;
if (!appDir.executeDeferredOperations()) {
ldLog() << LD_ERROR << "Failed to execute deferred operations" << std::endl;
return 1;
}
ldLog() << std::endl << "-- Creating qt.conf in AppDir --" << std::endl;
if (!createQtConf(appDir))
return 1;
ldLog() << std::endl << "Done!" << std::endl;
}