/* PIP - Platform Independent Primitives Deploy tool Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "picli.h" #include "pidir.h" #include "piiostream.h" #define DELIM "::" using namespace PICoutManipulators; PIString cmd_copy, cmd_copydir, cmd_suffix, ign_err_suffix; PIString qplatforms; void setCommands() { #ifdef WINDOWS cmd_copy = "copy /y "; cmd_copydir = "copy /y "; cmd_suffix = " 1> NUL"; ign_err_suffix = " 2> NUL"; qplatforms = "windows"; #else cmd_copy = "cp -f "; cmd_copydir = "cp -rf "; ign_err_suffix = " 2> /dev/null"; # ifdef MAC_OS qplatforms = "cocoa"; # else qplatforms = "xcb,wayland"; # endif #endif } void usage() { piCout << Bold << "Deploy tool"; piCout << Cyan << "Version PIP" << Bold << PIPVersion(); piCout << ""; piCout << "Copy all dependencies of to directory "; piCout << "You can specify any file or directory as ."; piCout << "In case of directory it will be scan recursively."; piCout << ""; piCout << "If some Qt dependency found, copy corresponding Qt plugins."; piCout << "Styles and platforms selected by -S and -P flags,"; piCout << "any other plugins described by --qt-plugins flag in next format:"; piCout << "\"[*=" DELIM "]=," DELIM "=,\", e.g."; piCout << "\"sqldrivers=lite,mysql" DELIM "geoservices=" DELIM "position=nmea\"."; piCout << "If no regexp specified for plugins, nothing will be copied."; piCout << "Default regexp set by \"*=\"."; piCout << "\"*=\" disable optional plugins."; piCout << ""; piCout << Green << Bold << "Usage:" << Default << "\"deploy_tool [-hvfC] [--dependencies [--prefix ]] " "[--qt-plugins-dir ] [-s ] [--ignore ] [-S ] " "[-l ] [-D ] [--dpkg-workdir ] [-L | -W | -M ] " "[--name-tool ] [--rpath] [-d ] [-q ] [-a ] [-S ] " "[-P ] [--qt-plugins ] [--qt-modules ] [--qt-conf-dir ] -o [ ...]\"" << NewLine; piCout << Green << Bold << "Details:"; piCout << Bold << "Debug control"; piCout << "-h, --help " << Green << "- display this message and exit"; piCout << "-v, --verbose " << 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 \"" DELIM "\", default \"/usr/lib\""; piCout << "--ignore " << Green << "- ignore libraries names, may be separated by \"" DELIM "\", default \"\""; 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 << "--dpkg-workdir " << Green << "- dpkg \"admindir\" path, default \"\""; piCout << "--name-tool " << Green << "- \"install_name_tool\" path, default \"install_name_tool\""; piCout << "--strip " << Green << "- \"strip\" path, default \"strip\""; piCout << "--rpath " << Green << "- set rpath for copied files using \"patchelf\""; piCout << "-d " << Green << "- maximum dependepcies depth, default 8"; piCout << ""; piCout << Bold << "Qt control"; piCout << "-q " << Green << "- path where Qt root dir, default takes from \"qmake -v\""; piCout << "-S " << Green << "- set Qt styles (e.g. \"oxygen,breeze\"), default \"*\""; piCout << "-P " << Green << "- set Qt platforms (e.g. \"win,mini\"), default by host system"; piCout << "--qt-plugins " << Green << "- set Qt plugins description"; piCout << "--qt-modules " << Green << "- additional Qt modules, may be separated by \"" DELIM "\" (e.g. \"Sql" DELIM "Xml\")"; piCout << "--qml-modules " << Green << "- additional Qt QML modules, may be separated by \"" DELIM "\" (e.g. \"Qt" DELIM "QtQuick.2\")"; piCout << ""; piCout << Bold << "Output control"; piCout << "-o " << Green << "- path for libraries copy to"; piCout << "--qt-plugins-dir " << Green << "- dir where place Qt plugins, default \"\""; piCout << "--qt-qml-dir " << Green << "- dir where place Qt QML modules, default \"\""; piCout << "--qt-conf-dir " << Green << "- dir where place \"qt.conf\", default \"\""; 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 << "-a " << Green << "- additional libs, separated by \"" DELIM "\". Libraries will be searched in "; } struct QtDep { QtDep(const PIString & l = PIString(), const PIStringList & p = PIStringList()): lib(l), plugins(p) {} PIString lib; PIStringList plugins; }; QtDep qt_deps[] = {QtDep("core", PIStringList() << "platforms"), QtDep("gui", PIStringList() << "imageformats"), QtDep("widgets", PIStringList() << "styles"), QtDep("sql", PIStringList() << "sqldrivers"), QtDep("positioning", PIStringList() << "position"), QtDep("location", PIStringList() << "geoservices"), QtDep("multimedia", PIStringList() << "audio" << "mediaservice" << "playlistformats"), QtDep("printsupport", PIStringList() << "printsupport"), QtDep("virtualkeyboard", PIStringList() << "platforminputcontexts"), QtDep("sensors", PIStringList() << "sensors" << "sensorgestures"), QtDep("texttospeech", PIStringList() << "texttospeech"), QtDep("serialbus", PIStringList() << "canbus"), QtDep()}; int depth = 8; bool fake = false, is_ldd = true, is_deps = false, need_qt = false, make_qt_format = true, rpath = false, win_target = false; PIString ldd, readelf, objdump, otool, dpkg, nametool, strip, out_dir, qt_dir, dpkg_workdir; PIString qt_pref, qt_suff, qt_conf_dir, qt_plugins_dir, qt_qml_dir, target_dir; PIStringList styles, lib_dirs, add_libs, platforms, sqldrivers, input_files, plugin_libs, qt_add_libs, qml_list; PISet all_libs, miss_libs, all_deps, frameworks, framework_libs, miss_frameworks, qt_plugins, ignore_libs, qt_libs; PIMap qt_filters; PIString findLib(const PIString & l) { if (PIFile::isExists(l)) return l; piForeachC(PIString & s, lib_dirs) { if (PIFile::isExists(s + l)) return s + l; if (win_target || l.toLowerCase().endsWith("dll")) { PIFile::FileInfo info(l); PIString fn = info.baseName(), fe = info.extension(), nn; nn = s + fn + "." + fe.toLowerCase(); if (PIFile::isExists(nn)) return nn; nn = s + fn + "." + fe.toUpperCase(); if (PIFile::isExists(nn)) return nn; nn = s + fn.toLowerCase() + "." + fe; if (PIFile::isExists(nn)) return nn; nn = s + fn.toLowerCase() + "." + fe.toLowerCase(); if (PIFile::isExists(nn)) return nn; nn = s + fn.toLowerCase() + "." + fe.toUpperCase(); if (PIFile::isExists(nn)) return nn; nn = s + fn.toUpperCase() + "." + fe; if (PIFile::isExists(nn)) return nn; nn = s + fn.toUpperCase() + "." + fe.toLowerCase(); if (PIFile::isExists(nn)) return nn; nn = s + fn.toUpperCase() + "." + fe.toUpperCase(); if (PIFile::isExists(nn)) return nn; } } return ""; } PIString frameworkName(const PIString & l) { if (!l.contains(".framework/")) return ""; PIStringList ll = l.split("/"); piForeachRC(PIString & _f, ll) { if (_f.endsWith(".framework")) { return _f; } } return ""; } PIString frameworkInternalPath(const PIString & l) { return l.right(l.size_s() - l.findLast('.') - 11); } PIString execute(const PIString & cmd) { FILE * fp = popen(cmd.dataAscii(), "r"); PIString ret; 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; ret += PIString(in, r); } pclose(fp); } return ret; } PIStringList filter(const PIString & in, const PIString & f) { PIStringList ret, lines = in.split("\n"); for (const PIString & l: lines) if (l.contains(f)) ret << l.trimmed(); return ret; } void checkQtLib(PIString lib) { PIString base = lib.toLowerCase(), pref, suff; if (base.startsWith("lib")) { pref += "lib"; base.cutLeft(3); } if (base.startsWith("qt5")) { pref += "Qt5"; base.cutLeft(3); } if (base.startsWith("qt6")) { pref += "Qt6"; base.cutLeft(3); } if (base.startsWith("qt")) { pref += "Qt"; base.cutLeft(2); } if (base.find('.') >= 0) { suff = base.right(base.size_s() - base.find('.')); base = base.left(base.find('.')); } for (int i = 0;; ++i) { if (qt_deps[i].lib.isEmpty()) break; if (qt_deps[i].lib == base) { qt_plugins << PISet(qt_deps[i].plugins); // piCout << "add qt plugins" << qt_deps[i].plugins << "now" << qt_plugins; need_qt = true; qt_libs << lib; if (make_qt_format) { make_qt_format = false; qt_pref = pref; qt_suff = suff; } break; } } } void procLdd(PIString file, bool ext_lib = false, int cur_depth = 0) { ++cur_depth; if (cur_depth > depth) return; static PIStringList ignore_ext = {"png", "jpg", "jpeg", "gif", "bmp", "svg", "tif", "tiff", "ico", "pdf", "txt", "cfg", "conf", "json", "qml", "glsl", "fraq", "vert", "geom", "qmltypes", "metainfo", "ts", "qm", "ttf", "htm", "html", "md", "sms", "smsee", "blockmodel"}; PIString ext = PIFile::FileInfo(file).extension(); if (ignore_ext.contains(ext.toLowerCase())) 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) { lines = execute(ldd + " \"" + file + "\"").split("\n"); } else { PIString cmd; if (!readelf.isEmpty()) { lines = filter(execute(readelf + " -a \"" + file + "\""), "Shared library:"); for (PIString & l: lines) { l.cutRight(1); l.cutLeft(l.find('[') + 1); l.trim(); //.append('.').prepend('.'); } } if (!objdump.isEmpty()) { PIString out = execute(objdump + " -p \"" + file + "\"" + ign_err_suffix); lines = filter(out, "DLL Name:"); lines << filter(out, "NEEDED"); for (PIString & l: lines) { if (l.startsWith("DLL")) l.cutLeft(9); else l.cutLeft(6); l.trim(); //.append('.').prepend('.'); } } if (!otool.isEmpty()) { lines = filter(execute(otool + " -L \"" + file + "\""), "("); piForeach(PIString & l, lines) { l = l.left(l.find('(')); l.trim(); //.append('.').prepend('.'); } } } piForeachC(PIString & sl, lines) { PIString l = sl.trimmed(); if (!otool.isEmpty()) { PIString fname = frameworkName(l); if (!fname.isEmpty()) { frameworks << fname; framework_libs << l.cutLeft(1).cutRight(1); checkQtLib(fname); 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).trim(); if (l.isEmpty()) continue; if (!otool.isEmpty()) { if (!l.startsWith("/usr/local/")) { PIFile::FileInfo fi; fi.path = l; l = fi.name(); } } PIString flp = findLib(l); if (flp.isEmpty()) { // piCout << "Can`t find" << l; miss_libs << l; continue; } l = flp; } if (all_libs[l]) continue; PIFile::FileInfo fi; fi.path = l; PIString lname = fi.baseName(); if (lname.startsWith("lib")) lname.cutLeft(3); // piCout << "check ignore" << lname << ignore_libs; if (ignore_libs.contains(lname)) { continue; } checkQtLib(fi.name()); cur_libs << l; all_libs << l; } PIVector clibs = cur_libs.toVector(); if (!clibs.isEmpty()) piCout << " new dependencies:\n -" << PIStringList(clibs).join("\n - "); piForeachC(PIString & l, clibs) { procLdd(l, false, cur_depth); } } void copyWildcard(const PIString & from, const PIString & to) { piCout << "copy" << from; if (fake) return; PIDir(to).make(); #ifdef WINDOWS PIFile::FileInfo fi(from); system(("robocopy \"" + fi.dir() + "\" \"" + to + "\" \"" + fi.name() + "\" /E /NJH /NJS /NP /NDL /NS /NC /NFL 1> NUL").data()); #else system((cmd_copy + from + " \"" + to + "/\"" + cmd_suffix).data()); #endif } void procQt() { // piCout << "qmake ..."; PIString vs; if (qt_dir.isEmpty()) { vs = execute("qmake -v"); 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 = qt_plugins.toVector(); if (!fake) { PIDir(qt_plugins_dir).make(true); PIDir(qt_qml_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; PIStringList suffixes({".", "..", "qt5", "../qt5", "qt6", "../qt6"}); piForeachC(PIString s, suffixes) { PIString qd = qloc + "/" + s + "/plugins/"; if (piDebug) PICout(AddSpaces) << "Qt plugins root try" << qd << "..."; if (PIDir::isExists(qd + "platforms")) { qdir = qloc + "/" + s + "/"; piCout << " yes"; break; } piCout << " no"; } if (qdir.isEmpty()) break; piForeachC(PIString & plugin, pdirs) { PIStringList filters = qt_filters[plugin]; piCout << "PLUG" << plugin << filters; piForeachC(PIString & f, filters) { if (f.isEmpty()) continue; copyWildcard(qdir + "plugins/" + plugin + "/" + f, qt_plugins_dir + plugin); PIVector copied = PIDir(qt_plugins_dir + plugin).entries(); piForeachC(PIFile::FileInfo & fi, copied) { if (fi.isFile()) { procLdd(fi.path); plugin_libs << fi.path; } } } } for (const auto & q: qml_list) { if (q.isEmpty()) continue; copyWildcard(qdir + "qml/" + q + "/*", qt_qml_dir + q); PIVector copied = PIDir(qt_qml_dir + q).allEntries(); piForeachC(PIFile::FileInfo & fi, copied) { if (fi.isFile()) { procLdd(fi.path); plugin_libs << fi.path; } } } break; } } } bool procDpkg(const PIString & l) { PIString dpkgdir; if (!dpkg_workdir.isEmpty()) dpkgdir = " --admindir=" + dpkg_workdir; PIFile::FileInfo fi; fi.path = l; PIString cmd = dpkg + dpkgdir + " -S " + fi.name() + ign_err_suffix; // PICout(true) << cmd; PIString vs = execute(cmd); if (!vs.isEmpty()) { vs = vs.left(vs.find(":")); if (!vs.isEmpty() && !vs.endsWith("-cross")) all_deps << vs; return true; } // piCout << "No dep on" << l; return false; } void patchNameTool() { if (nametool.isEmpty()) return; PIStringList clibs = all_libs.toVector(), flibs = framework_libs.toVector(), patch_list; PIStringList dlibs; PIString libname, cmd; // PICout(DefaultControls) << "start patch" << clibs; PIFile::FileInfo fi; patch_list = input_files; patch_list << plugin_libs; piForeach(PIString l, clibs) { fi.path = l; patch_list << (out_dir + fi.name()); } piForeach(PIString local_lib, patch_list) { execute("chmod +w \"" + local_lib + "\""); fi.path = local_lib; cmd = nametool + " -id \"@executable_path/../Frameworks/" + fi.name() + "\""; cmd += " \"" + local_lib + "\"" + ign_err_suffix; // piCout << " " << cmd; execute(cmd); } piForeach(PIString f, flibs) { PIString fl = findLib(frameworkName(f)); if (fl.isEmpty()) continue; patch_list << (out_dir + frameworkName(f) + "/" + frameworkInternalPath(f)); // PICout(DefaultControls) << "map" << f << "->" << (out_dir + frameworkName(f) + "/" + frameworkInternalPath(f)); } piForeach(PIString local_lib, patch_list) { dlibs = filter(execute(otool + " -L \"" + local_lib + "\""), "("); piForeach(PIString & l, dlibs) { l = l.left(l.find('(')); l.trim(); } if (!dlibs.isEmpty()) { execute("chmod +w \"" + local_lib + "\""); } piCout << "patch" << local_lib; piForeach(PIString sys_lib, dlibs) { sys_lib.cutRight(1).trim(); fi.path = sys_lib; libname = fi.name(); PIString fl = findLib(libname), fname = frameworkName(sys_lib); // piCout << " check" << sys_lib << fl; PIString new_path; if (all_libs.contains(fl) || all_libs.contains(sys_lib)) { new_path = "@executable_path/../Frameworks/" + libname; piCout << " depend on lib" << (fl.isEmpty() ? sys_lib : fl); } else { if (frameworks.contains(fname)) { fl = findLib(fname); if (fl.isEmpty()) continue; new_path = "@executable_path/../Frameworks/" + fname + "/" + frameworkInternalPath(sys_lib); piCout << " depend on framework" << fl; } } if (!new_path.isEmpty() && (sys_lib != new_path)) { cmd = nametool + " -change \"" + sys_lib + "\""; cmd += " \"" + new_path + "\""; cmd += " \"" + local_lib + "\"" + ign_err_suffix; // piCout << " *" << cmd; execute(cmd); } } } piForeach(PIString bin, input_files) { cmd = nametool + " -add_rpath \"@executable_path/../Frameworks\""; cmd += " \"" + bin + "\"" + ign_err_suffix; execute(cmd); } } void patchRPathFile(const PIFile::FileInfo & file) { PIString fo = execute("file \"" + file.path + "\""); if (!fo.contains("ELF")) return; PIString rp = "\\$ORIGIN:\\$ORIGIN/lib"; PIString arp = PIDir(file.dir()).relative(out_dir); if (!arp.isEmpty() && arp != "." && arp != "lib") rp.append(":\\$ORIGIN/" + arp); PIString cmd = "patchelf --set-rpath \"" + rp + "\" \"" + file.path + "\"" + ign_err_suffix; piCout << "set rpath" << file.path << "to" << rp; execute(cmd); } void patchRPath() { PIStringList dirs({out_dir, target_dir, qt_plugins_dir, qt_qml_dir}); dirs.removeDuplicates().removeStrings(PIString()); piForeachC(PIString & d, dirs) { PIVector files = PIDir(d).allEntries(); piForeachC(PIFile::FileInfo & f, files) { if (f.isDir()) continue; patchRPathFile(f.path); } } } 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("search_path", true); cli.addArgument("ignore", PIChar('\0'), true); cli.addArgument("Styles", true); cli.addArgument("Platforms", true); cli.addArgument("qt-plugins", PIChar('\0'), true); cli.addArgument("qt-modules", PIChar('\0'), true); cli.addArgument("qml-modules", PIChar('\0'), true); cli.addArgument("qt-conf-dir", PIChar('\0'), true); cli.addArgument("qt-plugins-dir", PIChar('\0'), true); cli.addArgument("qt-qml-dir", PIChar('\0'), true); cli.addArgument("ldd", true); cli.addArgument("Lreadelf", true); cli.addArgument("Wobjdump", true); cli.addArgument("Motool", true); cli.addArgument("name-tool", PIChar('\0'), true); cli.addArgument("rpath", PIChar('\0')); cli.addArgument("strip", PIChar('\0'), true); cli.addArgument("Dpkg", true); cli.addArgument("dpkg-workdir", PIChar('\0'), true); cli.addArgument("depth", true); cli.addArgument("qtdir", true); cli.addArgument("add_libs", true); if (cli.hasArgument("help") || cli.argumentValue("output").isEmpty() || cli.optionalArguments().isEmpty()) { usage(); return 0; } setCommands(); fake = cli.hasArgument("fake"); piDebug = cli.hasArgument("verbose"); is_deps = cli.hasArgument("dependencies"); out_dir = cli.argumentValue("output"); lib_dirs = cli.argumentValue("search_path").split(DELIM).removeAll(""); add_libs = cli.argumentValue("add_libs").split(DELIM).removeAll(""); ignore_libs = PISet(cli.argumentValue("ignore").split(DELIM).removeAll("")); qt_dir = cli.argumentValue("qtdir"); ldd = cli.argumentValue("ldd"); readelf = cli.argumentValue("Lreadelf"); objdump = cli.argumentValue("Wobjdump"); otool = cli.argumentValue("Motool"); nametool = cli.argumentValue("name-tool"); strip = cli.argumentValue("strip"); rpath = cli.hasArgument("rpath"); if (nametool.isEmpty()) nametool = "install_name_tool"; if (strip.isEmpty()) strip = "strip"; dpkg = cli.argumentValue("Dpkg"); dpkg_workdir = cli.argumentValue("dpkg-workdir"); #ifdef WINDOWS readelf.replaceAll("/", "\\"); objdump.replaceAll("/", "\\"); otool.replaceAll("/", "\\"); nametool.replaceAll("/", "\\"); dpkg.replaceAll("/", "\\"); #endif if (dpkg.isEmpty()) dpkg = "/usr/bin/dpkg"; int etcnt = 0; if (!readelf.isEmpty()) ++etcnt; if (!objdump.isEmpty()) ++etcnt; if (!otool.isEmpty()) ++etcnt; if (etcnt > 1) { piCerr << "Can use only one of \"readelf\", \"objdump\" and \"otool\"!"; return 1; } if (etcnt > 0) is_ldd = false; if (!qt_dir.isEmpty()) { PIString qroot = qt_dir; if (!qroot.endsWith("/")) qroot.append("/"); lib_dirs << (qroot + "bin") << (qroot + "lib") << (qroot + "Frameworks"); } piForeach(PIString & s, lib_dirs) { s.trim(); if (!s.endsWith("/")) s += "/"; } if (out_dir.isEmpty()) out_dir = "."; if (!out_dir.endsWith("/")) out_dir += "/"; if (ldd.isEmpty()) ldd = "/usr/bin/ldd"; out_dir.replaceAll("//", "/"); for (int i = 0;; ++i) { if (qt_deps[i].lib.isEmpty()) break; qt_deps[i].plugins.forEach([](const PIString & p) { qt_filters[p] = PIStringList("*"); }); } if (!cli.argumentValue("Platforms").isEmpty()) qplatforms = cli.argumentValue("Platforms"); if (platforms.contains("windows")) win_target = true; platforms = qplatforms.split(","); styles = cli.argumentValue("Styles").split(","); if (styles.isEmpty()) styles << ""; PIStringList qpd = cli.argumentValue("qt-plugins").toLowerCase().split(DELIM).removeAll(""); piForeachC(PIString & qp, qpd) { int _i = qp.find('='); if (_i < 0) continue; PIString pname = qp.left(_i).trim(); PIStringList pfilt = qp.mid(_i + 1).trim().split(","); if (pname == "*") { for (int i = 0;; ++i) { if (qt_deps[i].lib.isEmpty()) break; qt_deps[i].plugins.forEach([&pfilt](const PIString & p) { qt_filters[p] = pfilt; }); } } else { qt_plugins << pname; qt_filters[pname] = pfilt; } } qt_filters["platforms"] = platforms; qt_filters["styles"] = styles; qt_add_libs = cli.argumentValue("qt-modules").split(DELIM).removeAll(""); auto getQtSubdir = [&cli](const char * arg, PIString & var) { var = cli.argumentValue(arg).trim(); if (var.isEmpty()) var = out_dir; else { if (!PIFile::FileInfo(var).isAbsolute()) var = out_dir + "/" + var; } if (!var.endsWith("/")) var += "/"; var.replaceAll("//", "/"); }; getQtSubdir("qt-conf-dir", qt_conf_dir); getQtSubdir("qt-plugins-dir", qt_plugins_dir); getQtSubdir("qt-qml-dir", qt_qml_dir); qml_list = cli.argumentValue("qml-modules").split(DELIM).removeAll(""); need_qt = !qt_add_libs.isEmpty(); auto it = qt_filters.makeIterator(); while (it.next()) it.value().forEach([](PIString & i) { if (!i.startsWith("*")) i.prepend("*"); if (!i.endsWith("*")) i.append("*"); }); // PICout(PICoutManipulators::DefaultControls) << qt_filters; if (!fake) PIDir(out_dir).make(); if (!cli.argumentValue("depth").isEmpty()) depth = cli.argumentValue("depth").toInt(); cli.optionalArguments().forEach([](const PIString & a) { if (PIDir::isExists(a)) { if (target_dir.isEmpty()) target_dir = a; PIDir(a).allEntries().forEach([](const PIFile::FileInfo & fi) { if (fi.isFile()) input_files << fi.path; }); } else { if (target_dir.isEmpty()) target_dir = PIFile::FileInfo(a).dir(); if (PIFile::isExists(a)) input_files << a; } }); // piCout << files; if (depth > 0) { input_files.forEach([](const PIString & f) { procLdd(f); }); qt_add_libs.forEach([](const PIString & f) { PIString l = findLib(qt_pref + f + qt_suff); if (l.isEmpty()) return; procLdd(l, true); checkQtLib(f); }); } piForeach(PIString & s, add_libs) { if (s.isEmpty()) continue; PIString alib = findLib(s); if (alib.isEmpty()) continue; piCout << s << "->" << alib; procLdd(alib, true); if (!all_libs[alib]) all_libs << alib; } if (need_qt) { bool need_qt_plugins = true; if (is_deps) { // if Qt in system, then no plugins copy PIVector qlibs = qt_libs.toVector(); piCout << "check for installed Qt" << qlibs; piForeach(PIString l, qlibs) { if (procDpkg(l)) { piCout << "system Qt found!"; need_qt_plugins = false; break; } } } if (need_qt_plugins) { procQt(); if (!ldd.isEmpty() || !readelf.isEmpty()) { // qt.conf for Linux and Windows PIFile qtc(qt_conf_dir + "qt.conf", PIIODevice::ReadWrite); qtc.clear(); PIString pp = PIDir(qt_conf_dir).relative(qt_plugins_dir), qp = PIDir(qt_conf_dir).relative(qt_qml_dir), lp = "lang/"; if (!pp.isEmpty() && !pp.endsWith('/')) pp.append('/'); if (!qp.isEmpty() && !qp.endsWith('/')) qp.append('/'); if (!otool.isEmpty()) { pp = "PlugIns"; lp = "Resources/lang"; } // piCout << pp; PIIOTextStream ts(&qtc); ts << "[Paths]\n\tPlugins = " << pp << "\n\tQml2Imports = " << qp << "\n\tTranslations = " << lp << "\n"; } } } #ifdef WINDOWS out_dir.replaceAll("/", "\\"); #endif PIVector clibs = all_libs.toVector(); piForeach(PIString l, clibs) { PIFile::FileInfo fi; fi.path = l; #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).data()); if (!otool.isEmpty()) // Apple system((strip + " -S \"" + out_dir + fi.name() + "\"").data()); else system((strip + " --strip-unneeded \"" + out_dir + fi.name() + "\"").data()); } } } 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).data()); } else miss_frameworks << f; } if (!otool.isEmpty()) patchNameTool(); if (rpath) patchRPath(); 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; }