diff --git a/CMakeLists.txt b/CMakeLists.txt index 0908f751..5bd2dae9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -684,6 +684,7 @@ if(NOT PIP_FREERTOS) add_subdirectory("utils/system_test") add_subdirectory("utils/remote_console") add_subdirectory("utils/udp_file_transfer") + add_subdirectory("utils/deploy_tool") if(sodium_FOUND) add_subdirectory("utils/system_daemon") add_subdirectory("utils/crypt_tool") diff --git a/src_main/core/piincludes.cpp b/src_main/core/piincludes.cpp index 10347b9c..405e6f91 100755 --- a/src_main/core/piincludes.cpp +++ b/src_main/core/piincludes.cpp @@ -76,10 +76,7 @@ PIString errorString() { } PIString PIPVersion() { - static PIString ret(PIString::fromNumber(PIP_VERSION_MAJOR) + "." + - PIString::fromNumber(PIP_VERSION_MINOR) + "." + - PIString::fromNumber(PIP_VERSION_REVISION) + - PIP_VERSION_SUFFIX); + static PIString ret = PIStringAscii(PIP_VERSION_NAME); return ret; } diff --git a/utils/deploy_tool/CMakeLists.txt b/utils/deploy_tool/CMakeLists.txt new file mode 100644 index 00000000..7a568aa0 --- /dev/null +++ b/utils/deploy_tool/CMakeLists.txt @@ -0,0 +1,10 @@ +message(STATUS "Building deploy_tool") +file(GLOB CPPS "*.cpp") +file(GLOB HDRS "*.h") +add_executable(deploy_tool ${CPPS} ${HDRS}) +target_link_libraries(deploy_tool pip) +if (DEFINED LIB) + install(TARGETS deploy_tool DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +else() + install(TARGETS deploy_tool DESTINATION bin) +endif() diff --git a/utils/deploy_tool/main.cpp b/utils/deploy_tool/main.cpp new file mode 100644 index 00000000..bd5df2dd --- /dev/null +++ b/utils/deploy_tool/main.cpp @@ -0,0 +1,433 @@ +#include +#include +#include + +using namespace PICoutManipulators; + +PIString cmd_copy, cmd_copydir, cmd_suffix; +PIString qplatforms; + +void setCommands() { +#ifdef WINDOWS + cmd_copy = "copy /y "; + cmd_copydir = "copy /y "; + cmd_suffix = " 1> NUL"; + qplatforms = "windows"; +#else + cmd_copy = "cp -f "; + cmd_copydir = "cp -rf "; +# ifdef MAC_OS + qplatforms = "cocoa"; +# else + qplatforms = "xcb"; +# endif +#endif +} + + +void usage() { + piCout << Bold << "Deploy tool"; + piCout << Cyan << "Version PIP" << Bold << PIPVersion(); + piCout << ""; + piCout << "Copy all dependencies of to directory "; + piCout << "If depend on QtCore library, then copy all basic Qt plugins, "; + piCout << " and "; + piCout << ""; + piCout << Green << Bold << "Usage:" << Default << "\"deploy_tool [-hvfC] [--dependencies [--prefix ]] [-p ] [-s ] [-S ] [-l ] [-D ] [-L | -W | -M ] [-d ] [-q ] [-a ] [-S ] [-P ] -o [ ...]\"" << NewLine; + piCout << Green << Bold << "Details:"; + piCout << Bold << "Debug control"; + piCout << "-h " << Green << "- display this message and exit"; + piCout << "-v " << Green << "- be verbose"; + piCout << ""; + piCout << Bold << "Processing control"; + piCout << "-f, --fake " << Green << "- don`t copy, only print"; + piCout << "-s " << Green << "- set search pathes for system libraries, may be separated by \";\", default \"/usr/lib\""; + piCout << "-l " << Green << "- \"ldd\" path, default \"/usr/bin/ldd\""; + piCout << "-L " << Green << "- \"readelf\" path, overrides \"ldd\""; + piCout << "-W " << Green << "- \"objdump\" path, overrides \"ldd\""; + piCout << "-M " << Green << "- \"otool\" path, overrides \"ldd\""; + piCout << "-D " << Green << "- \"dpkg\" path, default \"/usr/bin/dpkg\""; + piCout << "-d " << Green << "- dependepcies depth, default 1"; + piCout << "-q " << Green << "- path where Qt root dir, default takes from \"qmake -v\""; + piCout << "-C, --Conf " << Green << "- make \"qt.conf\""; + //piCout << "-Q, --Qt-force " << Green << "- force add Qt root dir, default takes from \"qmake -v\""; + piCout << ""; + piCout << Bold << "Output control"; + piCout << "-o " << Green << "- path for libraries copy to"; + piCout << "-p " << Green << "- path for Qt plugins, default \"/plugins\""; + piCout << "--dependencies " << Green << "- search dependencies by , print them and copy missing libraries"; + piCout << "--prefix " << Green << "- print before dependencies"; + piCout << ""; + piCout << Bold << "Input control"; + piCout << " ... " << Green << "- executable to process"; + piCout << "-S " << Green << "- add Qt styles (e.g. \"oxygen,breeze\"), default \"\""; + piCout << "-P " << Green << "- add Qt platforms (e.g. \"win,mini\"), default by host system"; + piCout << "-a " << Green << "- additional libs, separated by \";\""; +} + + +int depth = 1; +bool need_qt = false, fake = false, is_ldd = true, is_deps = false; +PIString ldd, readelf, objdump, otool, dpkg, out_dir, qt_dir, out_plugins_dir; +PIStringList styles, lib_dirs, add_libs, platforms; +PISet all_libs, miss_libs, all_deps, frameworks, miss_frameworks; + + +PIString findLib(const PIString & l) { + if (PIFile::isExists(l)) return l; + piForeachC (PIString & s, lib_dirs) { + if (PIFile::isExists(s + l)) { + return s + l; + } + } + return ""; +} + + +void procLdd(PIString file, bool ext_lib = false, int cur_depth = 0) { + ++cur_depth; + if (cur_depth > depth) return; + piCout << "scan" << file << "..."; + PISet cur_libs; + if (ext_lib) { + if (!all_libs[file]) { + cur_libs << file; + all_libs << file; + } + } + PIStringList lines; + if (is_ldd) { + PIProcess proc; + proc.setGrabOutput(true); + proc.exec(ldd, file); + proc.waitForFinish(5000); + lines = PIString(proc.readOutput()).split("\n"); + } else { + PIString cmd; + if (!readelf.isEmpty()) { + cmd = readelf + " -a " + file; + cmd += " | grep \"Shared library:\" | grep -oG \"\\[.*\\]\""; + } + if (!objdump.isEmpty()) { + cmd = objdump + " -p " + file; + cmd += " | grep \"DLL Name:\""; + } + if (!otool.isEmpty()) { + cmd = otool + " -L " + file; + cmd += " | grep -o \".*(\""; + } + //piCout << cmd; + FILE * fp = popen(cmd.dataAscii(), "r"); + PIString vs; + if (fp) { + const int sz = 256; + char in[sz]; + memset(in, 0, sz); + while (true) { + int r = fread(in, 1, sz, fp); + if (r <= 0) break; + vs += PIString(in, r); + } + pclose(fp); + } + lines = vs.split("\n"); + if (!objdump.isEmpty()) { + piForeach (PIString & l, lines) { + l.trim().cutLeft(9).trim(); + l.append('.').prepend('.'); + } + } + if (!otool.isEmpty()) { + piForeach (PIString & l, lines) { + l.trim().cutRight(1).trim(); + l.append('.').prepend('.'); + } + } + //piCout << "readelf:" << vs; + } + piForeachC (PIString & sl, lines) { + PIString l = sl.trimmed(); + if (!otool.isEmpty()) { + if (l.contains(".framework/")) { + PIStringList ll = l.split("/"); + piForeachRC (PIString & _f, ll) { + if (_f.endsWith(".framework")) { + l = _f; + piBreak; + } + } + frameworks << l; + if (l.contains("QtCore") || l.contains("Qt5Core")) + need_qt = true; + continue; + } + } + if (is_ldd) { + if (!l.contains("=>")) continue; + l.cutLeft(l.find("=>") + 2); + if (l.contains("(")) + l.cutRight(l.length() - l.find("(")); + l.trim(); + if (l.toLowerCase() == "not found") { + miss_libs << sl.left(sl.find("=>")).trim(); + continue; + } + } else { + l.cutLeft(1).cutRight(1); + if (l.isEmpty()) continue; + if (!otool.isEmpty()) { + PIFile::FileInfo fi; + fi.path = l; + l = fi.name(); + } + PIString flp = findLib(l); + if (flp.isEmpty()) { + piCout << "Can`t find" << l; + continue; + } + l = flp; + } + if (all_libs[l]) continue; + if (!need_qt) { + PIFile::FileInfo fi; + fi.path = l; + PIString ln = fi.name(); + if (ln.contains("QtCore") || ln.contains("Qt5Core")) + need_qt = true; + } + cur_libs << l; + all_libs << l; + } + PIVector clibs = cur_libs.toVector(); + piForeachC (PIString & l, clibs) { + procLdd(l, cur_depth); + } +} + +void copyWildcard(const PIString & from, const PIString & to) { + piCout << "copy" << from; + if (fake || is_deps) return; + PIDir(to).make(); +#ifdef WINDOWS + PIFile::FileInfo fi; fi.path = from; + system("robocopy \"" + fi.dir() + "\" \"" + to + "\" \"" + fi.name() + "\" /NJH /NJS /NP /NDL /NS /NC /NFL 1> NUL"); +#else + system(cmd_copy + from + " \"" + to + "/\"" + cmd_suffix); +#endif +} + + +void procQt() { + //piCout << "qmake ..."; + PIString vs; + if (qt_dir.isEmpty()) { + FILE * fp = popen("qmake -v", "r"); + if (fp) { + char in[1024]; + memset(in, 0, 1024); + if (fread(in, 1, 1024, fp)) + vs = in; + pclose(fp); + } + if (vs.isEmpty()) { + piCout << "Can`t exec \"qmake -v\"!"; + return; + } + } else + vs = "QMake version ?.?\nUsing Qt version ?.?.? in " + qt_dir; + PIStringList vsl = vs.split("\n"); + PIStringList pdirs; + pdirs << "imageformats" << "sqldrivers"; + if (!fake && !is_deps) + PIDir(out_plugins_dir).make(true); + piForeach (PIString l, vsl) { + if (l.trim().contains("Qt version")) { + l.cutLeft(l.find("Qt version") + 10).trim(); + PIString qv = l.takeWord(); + l.takeWord(); + PIString qloc = l.trim(); + piCout << "Qt" << qv << "in" << qloc; + PIString qdir = qloc; +#ifdef WINDOWS + if (qt_dir.isEmpty()) + qdir += "/../plugins/"; + else + qdir += "/plugins/"; +#else + if (qt_dir.isEmpty()) + qdir += "/qt5/plugins/"; + else + qdir += "/plugins/"; +#endif + piForeachC (PIString & p, pdirs) { + copyWildcard(qdir + p + "/*", out_plugins_dir + p); + } + piForeachC (PIString & s, platforms) { + copyWildcard(qdir + "platforms/*" + s + "*", out_plugins_dir + "platforms"); + } + piForeachC (PIString & s, styles) { + copyWildcard(qdir + "styles/*" + s + "*", out_plugins_dir + "styles"); + } + } + } + pdirs << "platforms" << "styles"; + piForeachC (PIString & p, pdirs) { + PIVector fil = PIDir(out_plugins_dir + p).entries(); + piForeachC (PIFile::FileInfo & f, fil) { + if (f.isFile()) + procLdd(f.path); + } + } +} + + +bool procDpkg(const PIString & l) { + PIString vs, cmd = dpkg + " -S " + l + " 2> /dev/null"; + FILE * fp = popen(cmd.dataAscii(), "r"); + if (fp) { + const int sz = 256; + char in[sz]; + memset(in, 0, sz); + while (true) { + int r = fread(in, 1, sz, fp); + if (r <= 0) break; + vs += PIString(in, r); + } + pclose(fp); + } + if (!vs.isEmpty()) { + vs = vs.left(vs.find(":")); + if (!vs.isEmpty()) + all_deps << vs; + return true; + } + //piCout << "No dep on" << l; + return false; +} + + +int main(int argc, char * argv[]) { + PICLI cli(argc, argv); + //piCout << cli.rawArguments(); + cli.setOptionalArgumentsCount(-1); + cli.addArgument("help"); + cli.addArgument("verbose"); + cli.addArgument("Conf"); + cli.addArgument("fake"); + cli.addArgument("dependencies", PIChar('\0')); + cli.addArgument("prefix", PIChar('\0'), true); + cli.addArgument("output", true); + cli.addArgument("pqt_out_plugins", true); + cli.addArgument("search_path", true); + cli.addArgument("Styles", true); + cli.addArgument("Platforms", true); + cli.addArgument("ldd", true); + cli.addArgument("Lreadelf", true); + cli.addArgument("Wobjdump", true); + cli.addArgument("Motool", true); + cli.addArgument("Dpkg", true); + cli.addArgument("depth", true); + cli.addArgument("qtdir", true); + cli.addArgument("add_libs", true); + if (cli.hasArgument("help") || cli.argumentValue("output").isEmpty() || (cli.optionalArguments().size_s() < 1)) { + usage(); + return 0; + } + setCommands(); + + fake = cli.hasArgument("fake"); + piDebug = cli.hasArgument("verbose"); + is_deps = cli.hasArgument("dependencies"); + PIString bin = cli.optionalArguments()[0]; + out_dir = cli.argumentValue("output"); + lib_dirs = cli.argumentValue("search_path").split(";"); + add_libs = cli.argumentValue("add_libs").split(";"); + qt_dir = cli.argumentValue("qtdir"); + ldd = cli.argumentValue("ldd"); + readelf = cli.argumentValue("Lreadelf"); + objdump = cli.argumentValue("Wobjdump"); + otool = cli.argumentValue("Motool"); + dpkg = cli.argumentValue("Dpkg"); + if (dpkg.isEmpty()) + dpkg = "/usr/bin/dpkg"; + out_plugins_dir = out_dir + "plugins/"; + if (!cli.argumentValue("pqt_out_plugins").isEmpty()) { + out_plugins_dir = cli.argumentValue("pqt_out_plugins"); + if (!out_plugins_dir.endsWith("/")) + out_plugins_dir.append('/'); + } + int etcnt = 0; + if (!readelf.isEmpty()) ++etcnt; + if (!objdump.isEmpty()) ++etcnt; + if (!otool.isEmpty()) ++etcnt; + if (etcnt > 1) { + piCout << "Can use only one of \"readelf\", \"objdump\" and \"otool\"!"; + return 1; + } + if (etcnt > 0) is_ldd = false; + if (!cli.argumentValue("Platforms").isEmpty()) + qplatforms = cli.argumentValue("Platforms"); + platforms = qplatforms.split(","); + piForeach (PIString & s, lib_dirs) { + if (!s.endsWith("/")) s += "/"; + } + if (out_dir.isEmpty()) out_dir = "."; + if (!out_dir.endsWith("/")) out_dir += "/"; + if (ldd.isEmpty()) ldd = "/usr/bin/ldd"; + styles = cli.argumentValue("Styles").split(","); + if (!fake) + PIDir(out_dir).make(); + if (!cli.argumentValue("depth").isEmpty()) + depth = cli.argumentValue("depth").toInt(); + + if (depth > 0) + procLdd(bin); + piForeach (PIString & s, add_libs) { + if (s.isEmpty()) continue; + piCout << s << "->" << findLib(s); + procLdd(findLib(s), true); + } + if (need_qt && !is_deps) + procQt(); + +#ifdef WINDOWS + out_dir.replaceAll("/", "\\"); +#endif + PIVector clibs = all_libs.toVector(); + piForeach (PIString l, clibs) { +#ifdef WINDOWS + l.replaceAll("/", "\\"); +#endif + bool need_cp = true; + if (is_deps) + need_cp = !procDpkg(l); + if (need_cp) { + piCout << "copy" << l; + if (!fake) + system(cmd_copy + "\"" + l + "\" \"" + out_dir + "\"" + cmd_suffix); + } + } + PIVector fwdirs = frameworks.toVector(); + piForeachC (PIString & f, fwdirs) { + PIString fd = findLib(f); + if (!fd.isEmpty()) { + piCout << "copy framework" << f; + if (!fake) + system(cmd_copydir + "\"" + fd + "\" \"" + out_dir + "\"" + cmd_suffix); + } else + miss_frameworks << f; + } + + if (is_deps) { + PICout(PICoutManipulators::AddNone) << cli.argumentValue("prefix"); + PICout(PICoutManipulators::AddNewLine) << PIStringList(all_deps.toVector()).join(", "); + } else { + piCout << "copied" << clibs.size_s() << "files"; + if (!miss_libs.isEmpty()) + piCout << "Missing libraries:\n - " << PIStringList(miss_libs.toVector()).join("\n - "); + if (!miss_frameworks.isEmpty()) + piCout << "Missing frameworks:\n - " << PIStringList(miss_frameworks.toVector()).join("\n - "); + } + + return 0; +}