diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4cef04f..aa419c3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(linuxdeploy-plugin-qt main.cpp qt-modules.h) +add_executable(linuxdeploy-plugin-qt main.cpp qt-modules.h qt_tools_utils.hpp) target_link_libraries(linuxdeploy-plugin-qt linuxdeploy_core args) set_target_properties(linuxdeploy-plugin-qt PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") diff --git a/src/main.cpp b/src/main.cpp index 478daa2..1a41eac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ // local includes #include "qt-modules.h" +#include "qt_tools_utils.hpp" namespace bf = boost::filesystem; @@ -23,29 +24,31 @@ using namespace linuxdeploy::core; using namespace linuxdeploy::core::log; typedef struct { - bool success; - int retcode; - std::string stdoutOutput; - std::string stderrOutput; + bool success; + int retcode; + std::string stdoutOutput; + std::string stderrOutput; } procOutput; -procOutput check_command(const std::initializer_list args) { +procOutput check_command(const std::initializer_list args) +{ subprocess::Popen proc(args, subprocess::output(subprocess::PIPE), subprocess::error(subprocess::PIPE)); auto output = proc.communicate(); std::string out, err; - if (output.first.length > 0) + if (output.first.length>0) out = output.first.buf.data(); - if (output.second.length > 0) + if (output.second.length>0) err = output.second.buf.data(); - return {proc.retcode() == 0, proc.retcode(), out, err}; + return {proc.retcode()==0, proc.retcode(), out, err}; }; -const std::map queryQmake(const bf::path& qmakePath) { +const std::map queryQmake(const bf::path& qmakePath) +{ auto qmakeCall = check_command({qmakePath.c_str(), "-query"}); if (!qmakeCall.success) { @@ -61,23 +64,23 @@ const std::map queryQmake(const bf::path& qmakePath) { std::string line; auto stringSplit = [](const std::string& str, const char delim = ' ') { - std::stringstream ss; - ss << str; + std::stringstream ss; + ss << str; - std::string part; - std::vector parts; + std::string part; + std::vector parts; - while (std::getline(ss, part, delim)) { - parts.push_back(part); - } + while (std::getline(ss, part, delim)) { + parts.push_back(part); + } - return parts; + return parts; }; while (std::getline(ss, line)) { auto parts = stringSplit(line, ':'); - if (parts.size() != 2) + if (parts.size()!=2) continue; rv[parts[0]] = parts[1]; @@ -86,74 +89,82 @@ const std::map queryQmake(const bf::path& qmakePath) { return rv; }; -static std::string which(const std::string& name) { +static std::string which(const std::string& name) +{ subprocess::Popen proc({"which", name.c_str()}, subprocess::output(subprocess::PIPE)); auto output = proc.communicate(); ldLog() << LD_DEBUG << "Calling 'which" << name << LD_NO_SPACE << "'" << std::endl; - if (proc.retcode() != 0) { + if (proc.retcode()!=0) { ldLog() << LD_DEBUG << "which call failed, exit code:" << proc.retcode() << std::endl; return ""; } - std::string path = output.first.buf.data(); - while (path.back() == '\n') { - path.erase(path.end() - 1, path.end()); + std::string path = output.first.buf.data(); + while (path.back()=='\n') { + path.erase(path.end()-1, path.end()); } return path; } -template std::string join(Iter beg, Iter end) { +template +std::string join(Iter beg, Iter end) +{ std::stringstream rv; - if (beg != end) { + if (beg!=end) { rv << *beg; std::for_each(++beg, end, [&rv](const std::string& s) { - rv << " " << s; + rv << " " << s; }); } return rv.str(); } -std::string join(const std::vector& list) { +std::string join(const std::vector& list) +{ return join(list.begin(), list.end()); } -std::string join(const std::set& list) { +std::string join(const std::set& list) +{ return join(list.begin(), list.end()); } -bool strStartsWith(const std::string& str, const std::string& prefix) { - if (str.size() < prefix.size()) +bool strStartsWith(const std::string& str, const std::string& prefix) +{ + if (str.size()& modules) { +bool +deployTranslations(appdir::AppDir& appDir, const bf::path& qtTranslationsPath, const std::vector& modules) +{ if (qtTranslationsPath.empty() || !bf::is_directory(qtTranslationsPath)) { ldLog() << LD_WARNING << "Translation directory does not exist, skipping deployment"; return true; @@ -304,50 +327,55 @@ bool deployTranslations(appdir::AppDir& appDir, const bf::path& qtTranslationsPa ldLog() << "Qt translations directory:" << qtTranslationsPath << std::endl; auto checkName = [&appDir, &modules](const bf::path& fileName) { - if (!strEndsWith(fileName.string(), ".qm")) - return false; + 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; + // 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; - } + for (const auto& module : modules) { + if (!module.translationFilePrefix.empty() && strStartsWith(fileName.string(), module.translationFilePrefix)) + return true; + } - return false; + return false; }; - for (bf::directory_iterator i(qtTranslationsPath); i != bf::directory_iterator(); ++i) { + 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/"); + appDir.deployFile(*i, appDir.path()/"usr/translations/"); } return true; } -int main(const int argc, const char* const* const argv) { +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::ArgumentParser parser("linuxdeploy Qt plugin", + "Bundles Qt resources. For use with an existing AppDir, created by linuxdeploy."); args::ValueFlag appDirPath(parser, "appdir path", "Path to an existing AppDir", {"appdir"}); - args::ValueFlagList extraPlugins(parser, "plugin", "Extra Qt plugin to deploy (specified by name, filename or path)", {'p', "extra-plugin"}); + args::ValueFlagList 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&) { + } + catch (const args::ParseError&) { std::cerr << parser; return 1; } @@ -382,7 +410,8 @@ int main(const int argc, const char* const* const argv) { for (const auto& dependency : elf::ElfFile(path).traceDynamicDependencies()) { libraryNames.insert(dependency.filename().string()); } - } catch (const elf::ElfFileParseError& e) { + } + catch (const elf::ElfFileParseError& e) { ldLog() << LD_DEBUG << "Failed to parse file as ELF file:" << path << std::endl; } } @@ -392,48 +421,52 @@ int main(const int argc, const char* const* const argv) { std::vector 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 + "."; - - ldLog() << LD_DEBUG << "Checking library name '" << LD_NO_SPACE << libraryName - << LD_NO_SPACE << "' against library prefix '" << LD_NO_SPACE << libraryPrefix << LD_NO_SPACE - << "' and module name '" << LD_NO_SPACE << module.name << LD_NO_SPACE << "'" << std::endl; - - // 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; + // 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+"."; + + ldLog() << LD_DEBUG << "Checking library name '" << LD_NO_SPACE << libraryName + << LD_NO_SPACE << "' against library prefix '" << LD_NO_SPACE << libraryPrefix << LD_NO_SPACE + << "' and module name '" << LD_NO_SPACE << module.name << LD_NO_SPACE << "'" << std::endl; + + // 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, &extraPlugins](const QtModule& module) { - return std::find_if(libraryNames.begin(), libraryNames.end(), [&matchesQtModule, &module](const std::string& libraryName) { - return matchesQtModule(libraryName, module); - }) != libraryNames.end(); - }); - - std::copy_if(QtModules.begin(), QtModules.end(), std::back_inserter(extraQtModules), [&matchesQtModule, libraryNames, &extraPlugins](const QtModule& module) { - return std::find_if(extraPlugins.Get().begin(), extraPlugins.Get().end(), [&matchesQtModule, &module](const std::string& libraryName) { - return matchesQtModule(libraryName, module); - }) != extraPlugins.Get().end(); - }); + std::copy_if(QtModules.begin(), QtModules.end(), std::back_inserter(foundQtModules), + [&matchesQtModule, &libraryNames, &extraPlugins](const QtModule& module) { + return std::find_if(libraryNames.begin(), libraryNames.end(), + [&matchesQtModule, &module](const std::string& libraryName) { + return matchesQtModule(libraryName, module); + })!=libraryNames.end(); + }); + + std::copy_if(QtModules.begin(), QtModules.end(), std::back_inserter(extraQtModules), + [&matchesQtModule, libraryNames, &extraPlugins](const QtModule& module) { + return std::find_if(extraPlugins.Get().begin(), extraPlugins.Get().end(), + [&matchesQtModule, &module](const std::string& libraryName) { + return matchesQtModule(libraryName, module); + })!=extraPlugins.Get().end(); + }); { std::set moduleNames; std::for_each(foundQtModules.begin(), foundQtModules.end(), [&moduleNames](const QtModule& module) { - moduleNames.insert(module.name); + moduleNames.insert(module.name); }); ldLog() << "Found Qt modules:" << join(moduleNames) << std::endl; } @@ -441,7 +474,7 @@ int main(const int argc, const char* const* const argv) { { std::set moduleNames; std::for_each(extraQtModules.begin(), extraQtModules.end(), [&moduleNames](const QtModule& module) { - moduleNames.insert(module.name); + moduleNames.insert(module.name); }); ldLog() << "Extra Qt modules:" << join(moduleNames) << std::endl; } @@ -484,42 +517,56 @@ int main(const int argc, const char* const* const argv) { for (const auto& module : qtModulesToDeploy) { ldLog() << std::endl << "-- Deploying module:" << module.name << "--" << std::endl; - if (module.name == "gui") { + if (module.name=="core") { + string errStr; + string qtCoreLibraryPath; + for (auto libraryName: appDir.listSharedLibraries()) { + if (libraryName.filename().string().find("Qt5Core.")!=std::string::npos) + qtCoreLibraryPath = libraryName.string(); + } + patchQtCore(qtCoreLibraryPath, &errStr); + if (!errStr.empty()) { + ldLog() << "Failed to patch QtCore. " << errStr; + return 1; + } + } + + if (module.name=="gui") { if (!deployPlatformPlugins(appDir, qtPluginsPath)) return 1; } - if (module.name == "opengl" || module.name == "gui" || module.name == "xcbqpa") { + if (module.name=="opengl" || module.name=="gui" || module.name=="xcbqpa") { if (!deployXcbglIntegrationPlugins(appDir, qtPluginsPath)) return 1; } - if (module.name == "network") { + if (module.name=="network") { if (!deployBearerPlugins(appDir, qtPluginsPath)) return 1; } - if (module.name == "svg") { + if (module.name=="svg") { if (!deploySvgPlugins(appDir, qtPluginsPath)) return 1; } - if (module.name == "sql") { + if (module.name=="sql") { if (!deploySqlPlugins(appDir, qtPluginsPath)) return 1; } - if (module.name == "positioning") { + if (module.name=="positioning") { if (!deployPositioningPlugins(appDir, qtPluginsPath)) return 1; } - if (module.name == "multimedia") { + if (module.name=="multimedia") { if (!deployMultimediaPlugins(appDir, qtPluginsPath)) return 1; } - if (module.name == "webenginecore") { + if (module.name=="webenginecore") { if (!deployWebEnginePlugins(appDir, qtLibexecsPath, qtDataPath, qtTranslationsPath)) return 1; } diff --git a/src/qt_tools_utils.hpp b/src/qt_tools_utils.hpp new file mode 100644 index 0000000..cb74257 --- /dev/null +++ b/src/qt_tools_utils.hpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** 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$ +** +****************************************************************************/ + +#ifndef QT_TOOLS_UTILS +#define QT_TOOLS_UTILS + +#include +#include + +#include +#include +#include + +#include "../lib/linuxdeploy/include/linuxdeploy/core/log.h" + +using namespace std; +using namespace boost::filesystem; +using namespace linuxdeploy::core::log; + +vector readFileContent(const path& path, string* errorMessage); +void writeFileContent(const path& path, const vector& content, string* errorMessage); + +// Search for "qt_prfxpath=xxxx" in \a path, and replace it with "qt_prfxpath=." +void patchQtCore(const path& path, string* errorMessage = nullptr) +{ + ldLog() << "Patching " << path.filename() << "...\n"; + + auto content = readFileContent(path, errorMessage); + if (content.empty()) { + *errorMessage = "Unable to patch"+path.string()+": Could not read file content"; + return; + } + + char prfxpath[] = "qt_prfxpath="; + size_t len = strlen(prfxpath); + + auto startPos = std::search(content.begin(), content.end(), prfxpath, prfxpath+len); + if (startPos==content.end()) { + *errorMessage = "Unable to patch "+path.string()+": Could not locate pattern \"qt_prfxpath=\""; + return; + } + + startPos += len; + auto endPos = startPos; + while (endPos!=content.end() && *endPos!='\0') + endPos++; + + if (endPos==content.end()) { + *errorMessage = "Unable to patch "+path.string()+": Internal error"; + return; + } + + auto oldValue = vector(startPos, endPos); + vector newValue(static_cast(endPos-startPos), char(0)); + newValue[0] = '.'; + ldLog() << "Replaced prfxpath: " << oldValue << " by " << newValue; + + for (auto i = startPos; i& content, string* errorMessage) +{ + std::ofstream outfile(path.string(), std::ofstream::binary); + outfile.write(content.data(), content.size()); + if (outfile.fail()) + *errorMessage = "Unable to write : "+path.string(); + + outfile.close(); +} +vector readFileContent(const path& path, string* errorMessage) +{ + std::ifstream inFileStream(path.string(), std::ifstream::binary); + if (inFileStream) { + // get length of file: + inFileStream.seekg(0, std::ifstream::end); + auto length = static_cast(inFileStream.tellg()); + inFileStream.seekg(0, std::ifstream::beg); + auto* buffer = new char[length]; + + inFileStream.read(buffer, length); + + if (inFileStream.fail()) + *errorMessage = "Unable to read : "+path.string(); + + inFileStream.close(); + vector output(buffer, buffer+length); + delete[] buffer; + return output; + } +} + +#endif QT_TOOLS_UTILS \ No newline at end of file