Files
pip/utils/deploy_tool/main.cpp

830 lines
28 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#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 <file> to directory <out_path>";
piCout << "You can specify any file or directory as <file>.";
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 "]<plugins>=<regexp>,<regexp>" DELIM "<plugins>=<regexp>,<regexp>\", 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 \"*=<regexp>\".";
piCout << "\"*=\" disable optional plugins.";
piCout << "";
piCout << Green << Bold << "Usage:" << Default
<< "\"deploy_tool [-hvfC] [--dependencies [--prefix <text>]] "
"[--qt-plugins-dir <d>] [-s <search_path>] [--ignore <libs>] [-S <styles>] "
"[-l <ldd>] [-D <dpkg>] [--dpkg-workdir <d>] [-L <readelf> | -W <objdump> | -M <otool>] "
"[--name-tool <pathS>] [--rpath] [-d <depth>] [-q <qtdir>] [-a <add_libs>] [-S <styles>] "
"[-P <platforms>] [--qt-plugins <d>] [--qt-modules <l>] [--qt-conf-dir <d>] -o <out_path> <file> [<file2> ...]\""
<< 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 <search_path> " << Green
<< "- set search pathes for system libraries, may be separated by \"" DELIM "\", default \"/usr/lib\"";
piCout << "--ignore <libs> " << Green << "- ignore libraries names, may be separated by \"" DELIM "\", default \"\"";
piCout << "-l <ldd> " << Green << "- \"ldd\" path, default \"/usr/bin/ldd\"";
piCout << "-L <readelf> " << Green << "- \"readelf\" path, overrides \"ldd\"";
piCout << "-W <objdump> " << Green << "- \"objdump\" path, overrides \"ldd\"";
piCout << "-M <otool> " << Green << "- \"otool\" path, overrides \"ldd\"";
piCout << "-D <dpkg> " << Green << "- \"dpkg\" path, default \"/usr/bin/dpkg\"";
piCout << "--dpkg-workdir <d> " << Green << "- dpkg \"admindir\" path, default \"\"";
piCout << "--name-tool <path> " << Green << "- \"install_name_tool\" path, default \"install_name_tool\"";
piCout << "--strip <path> " << Green << "- \"strip\" path, default \"strip\"";
piCout << "--rpath " << Green << "- set rpath for copied files using \"patchelf\"";
piCout << "-d <depth> " << Green << "- maximum dependepcies depth, default 8";
piCout << "";
piCout << Bold << "Qt control";
piCout << "-q <qtdir> " << Green << "- path where Qt root dir, default takes from \"qmake -v\"";
piCout << "-S <styles> " << Green << "- set Qt styles (e.g. \"oxygen,breeze\"), default \"*\"";
piCout << "-P <platforms> " << Green << "- set Qt platforms (e.g. \"win,mini\"), default by host system";
piCout << "--qt-plugins <d> " << Green << "- set Qt plugins description";
piCout << "--qt-modules <list> " << Green << "- additional Qt modules, may be separated by \"" DELIM "\" (e.g. \"Sql" DELIM "Xml\")";
piCout << "--qml-modules <list> " << Green
<< "- additional Qt QML modules, may be separated by \"" DELIM "\" (e.g. \"Qt" DELIM "QtQuick.2\")";
piCout << "";
piCout << Bold << "Output control";
piCout << "-o <out_path> " << Green << "- path for libraries copy to";
piCout << "--qt-plugins-dir <d> " << Green << "- dir where place Qt plugins, default \"<out_path>\"";
piCout << "--qt-qml-dir <d> " << Green << "- dir where place Qt QML modules, default \"<out_path>\"";
piCout << "--qt-conf-dir <dir> " << Green << "- dir where place \"qt.conf\", default \"<out_path>\"";
piCout << "--dependencies " << Green << "- search dependencies by <dpkg>, print them and copy missing libraries";
piCout << "--prefix <text> " << Green << "- print <text> before dependencies";
piCout << "";
piCout << Bold << "Input control";
piCout << "<file> ... " << Green << "- executable to process";
piCout << "-a <add_libs> " << Green
<< "- additional libs, separated by \"" DELIM "\". Libraries will be searched in <search_path>";
}
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<PIString> all_libs, miss_libs, all_deps, frameworks, framework_libs, miss_frameworks, qt_plugins, ignore_libs, qt_libs;
PIMap<PIString, PIStringList> 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<PIString>(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<PIString> 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<PIString> 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<PIFile::FileInfo> 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<PIFile::FileInfo> 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<PIFile::FileInfo> 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);
add_libs = cli.argumentValue("add_libs").split(DELIM);
ignore_libs = PISet<PIString>(cli.argumentValue("ignore").split(DELIM));
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) {
piCout << "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);
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);
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);
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<PIString> 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<PIString> 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<PIString> 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;
}