From ba7a43c80ce06c566e36615303fc1a071868cd73 Mon Sep 17 00:00:00 2001 From: SCG82 Date: Thu, 2 Jan 2020 17:15:19 -0800 Subject: [PATCH] App bundle arg, improved rpath search algorithm, handle frameworks --- Makefile | 2 +- README.md | 16 ++- src/Dependency.cpp | 210 +++++++++++----------------- src/Dependency.h | 8 +- src/DylibBundler.cpp | 322 ++++++++++++++++++++++++++++++++++++------- src/DylibBundler.h | 18 ++- src/Settings.cpp | 98 +++++++++++-- src/Settings.h | 22 +++ src/Utils.cpp | 214 ++++++++++++++++++++++------ src/Utils.h | 34 ++++- src/main.cpp | 50 ++++--- 11 files changed, 728 insertions(+), 266 deletions(-) diff --git a/Makefile b/Makefile index d06633d..bdae4cf 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ DESTDIR= PREFIX=/usr/local -CXXFLAGS = -O2 +CXXFLAGS = -O2 -std=c++17 all: dylibbundler diff --git a/README.md b/README.md index b816d55..52ddf32 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,11 @@ Here is a list of flags you can pass to dylibbundler on the terminal. displays a summary of options +`-a`, `--app` (path to app bundle) +
+Application bundle to make self-contained. Fixes the main executable of the app bundle. Add additional binary files to fix up with the `-x` flag. +
+ `-x`, `--fix-file` (executable or plug-in filepath)
Fixes given executable or plug-in file (a .dylib can work too. anything on which `otool -L` works is accepted by `-x`). Dylibbundler will walk through the dependencies of the specified file to build a dependency list. It will also fix the said files' dependencies so that it expects to find the libraries relative to itself (e.g. in the app bundle) instead of at an absolute path (e.g. /usr/local/lib). To pass multiple files to fix, simply specify multiple `-x` flags. @@ -53,14 +58,19 @@ Copies libaries to a local directory, fixes their internal name so that they are fixes dependencies where bundled libraries depend on each other. If this option is not passed, no libraries will be prepared for distribution.
+`-f`, `--frameworks` +
+Copy framework dependencies to app bundle and fix internal names and rpaths. If this option is not passed, dependencies contained in frameworks will be ignored. +
+ `-i`, `--ignore` (path) > Dylibs in (path) will be ignored. By default, dylibbundler will ignore libraries installed in `/usr/lib` since they are assumed to be present by default on all OS X installations.*(It is usually recommend not to install additional stuff in `/usr/`, always use ` /usr/local/` or another prefix to avoid confusion between system libs and libs you added yourself)* `-d`, `--dest-dir` (directory) -> Sets the name of the directory in wich distribution-ready dylibs will be placed, relative to the current working directory. (Default is `./libs`) For an app bundle, it is often conveniant to set it to something like `./MyApp.app/Contents/libs`. +> Sets the name of the directory in wich distribution-ready dylibs will be placed, relative to the current working directory. (Default is either `./libs` or `./MyApp.app/Contents/Frameworks` if using the `-a` flag for an app bundle. `-p`, `--install-path` (libraries install path) -> Sets the "inner" installation path of libraries, usually inside the bundle and relative to executable. (Default is `@executable_path/../libs/`, which points to a directory named `libs` inside the `Contents` directory of the bundle.) +> Sets the "inner" installation path of libraries, usually inside the bundle and relative to executable. (Default is either `@executable_path/../libs/`, which points to a directory named `libs` inside the `Contents` directory of the bundle, or `@executable_path/../Frameworks/` if using the `-a` flag.) `-s`, `--search-path` (search path) > Check for libraries in the specified path @@ -77,7 +87,7 @@ fixes dependencies where bundled libraries depend on each other. If this option > If the output directory does not exist, create it. A command may look like -`% dylibbundler -od -b -x ./HelloWorld.app/Contents/MacOS/helloworld -d ./HelloWorld.app/Contents/libs/` +`% dylibbundler -cd -b -f -a ./HelloWorld.app -x ./HelloWorld.app/Contents/PlugIns/printsupport` If you want to create a universal binary by merging toghether two builds from PPC and Intel machines, you can ease it up by putting the ppc and intel libs in different directories, then to create the universal binary you only have to lipo the executable. diff --git a/src/Dependency.cpp b/src/Dependency.cpp index 32efff0..f132af1 100644 --- a/src/Dependency.cpp +++ b/src/Dependency.cpp @@ -39,66 +39,22 @@ THE SOFTWARE. #include #include -std::string stripPrefix(std::string in) -{ - return in.substr(in.rfind("/")+1); -} -std::string& rtrim(std::string &s) { - s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); - return s; -} - -//the paths to search for dylibs, store it globally to parse the environment variables only once -std::vector paths; - -//initialize the dylib search paths -void initSearchPaths(){ - //Check the same paths the system would search for dylibs - std::string searchPaths; - char *dyldLibPath = std::getenv("DYLD_LIBRARY_PATH"); - if( dyldLibPath!=0 ) - searchPaths = dyldLibPath; - dyldLibPath = std::getenv("DYLD_FALLBACK_FRAMEWORK_PATH"); - if (dyldLibPath != 0) - { - if (!searchPaths.empty() && searchPaths[ searchPaths.size()-1 ] != ':') searchPaths += ":"; - searchPaths += dyldLibPath; - } - dyldLibPath = std::getenv("DYLD_FALLBACK_LIBRARY_PATH"); - if (dyldLibPath!=0 ) - { - if (!searchPaths.empty() && searchPaths[ searchPaths.size()-1 ] != ':') searchPaths += ":"; - searchPaths += dyldLibPath; - } - if (!searchPaths.empty()) - { - std::stringstream ss(searchPaths); - std::string item; - while(std::getline(ss, item, ':')) - { - if (item[ item.size()-1 ] != '/') item += "/"; - paths.push_back(item); - } - } -} - -// if some libs are missing prefixes, this will be set to true -// more stuff will then be necessary to do -bool missing_prefixes = false; - -Dependency::Dependency(std::string path) +Dependency::Dependency(std::string path, std::string dependent_file) : is_framework(false) { char original_file_buffer[PATH_MAX]; std::string original_file; + std::string warning_msg; + + rtrim_in_place(path); if (isRpath(path)) { - original_file = searchFilenameInRpaths(path); + original_file = searchFilenameInRpaths(path, dependent_file); } - else if (not realpath(rtrim(path).c_str(), original_file_buffer)) + else if (not realpath(path.c_str(), original_file_buffer)) { - std::cerr << "\n/!\\ WARNING : Cannot resolve path '" << path.c_str() << "'" << std::endl; + warning_msg = "\n/!\\ WARNING : Cannot resolve path '" + path + "'\n"; original_file = path; } else @@ -107,72 +63,85 @@ Dependency::Dependency(std::string path) } // check if given path is a symlink - if (original_file != rtrim(path)) + if (original_file != path) addSymlink(path); + + prefix = filePrefix(original_file); + filename = stripPrefix(original_file); + + if (!prefix.empty() && prefix[ prefix.size()-1 ] != '/') prefix += "/"; + + // check if this dependency is in /usr/lib, /System/Library, or in ignored list + if (!Settings::isPrefixBundled(prefix)) return; + + if (original_file.find(".framework") != std::string::npos) { - filename = stripPrefix(original_file); - prefix = original_file.substr(0, original_file.rfind("/")+1); - addSymlink(path); + is_framework = true; + std::string framework_root = getFrameworkRoot(original_file); + std::string framework_path = getFrameworkPath(original_file); + std::string framework_name = stripPrefix(framework_root); + prefix = filePrefix(framework_root); + filename = framework_name + "/" + framework_path; } - else - { - filename = stripPrefix(path); - prefix = path.substr(0, path.rfind("/")+1); - } - + //check if the lib is in a known location - if( !prefix.empty() && prefix[ prefix.size()-1 ] != '/' ) prefix += "/"; - if( prefix.empty() || !fileExists( prefix+filename ) ) + if (prefix.empty() || !fileExists(prefix+filename)) { //the paths contains at least /usr/lib so if it is empty we have not initialized it - if( paths.empty() ) initSearchPaths(); + size_t search_path_count = Settings::searchPathAmount(); + if (search_path_count == 0) initSearchPaths(); //check if file is contained in one of the paths - for( size_t i=0; i " << symlinks[n].c_str() << std::endl;; + { + std::cout << " symlink --> " << symlinks[n].c_str() << std::endl; + } } std::string Dependency::getInstallPath() { return Settings::destFolder() + new_name; } + std::string Dependency::getInnerPath() { return Settings::inside_lib_path() + new_name; } - void Dependency::addSymlink(std::string s) { // calling std::find on this vector is not near as slow as an extra invocation of install_name_tool @@ -183,10 +152,11 @@ void Dependency::addSymlink(std::string s) // it returns true and merges both entries into one. bool Dependency::mergeIfSameAs(Dependency& dep2) { - if(dep2.getOriginalFileName().compare(filename) == 0) + if (dep2.getOriginalFileName().compare(filename) == 0) { const int samount = getSymlinkAmount(); - for(int n=0; n #include #include +#include #include #include #ifdef __linux @@ -39,8 +40,11 @@ THE SOFTWARE. std::vector deps; std::map > deps_per_file; std::map deps_collected; +std::set frameworks; std::set rpaths; std::map > rpaths_per_file; +std::map rpath_to_fullpath; +bool qt_plugins_called = false; void changeLibPathsOnFile(std::string file_to_fix) { @@ -48,13 +52,13 @@ void changeLibPathsOnFile(std::string file_to_fix) { collectDependencies(file_to_fix); } - std::cout << "\n* Fixing dependencies on " << file_to_fix.c_str() << std::endl; + if (!Settings::quietOutput()) std::cout << "\n"; + std::cout << "* Fixing dependencies on " << file_to_fix.c_str() << std::endl; - std::vector deps_in_file = deps_per_file[file_to_fix]; - const int dep_amount = deps_in_file.size(); + const int dep_amount = deps_per_file[file_to_fix].size(); for(int n=0; n::iterator it = rpaths.begin(); it != rpaths.end(); ++it) + const auto check_path = [&](std::string path) { - std::string path = *it + "/" + suffix; - if (realpath(path.c_str(), buffer)) + char buffer[PATH_MAX]; + std::string file_prefix = filePrefix(dependent_file); + if (path.find("@executable_path") != std::string::npos || path.find("@loader_path") != std::string::npos) { - fullpath = buffer; - break; + if (path.find("@executable_path") != std::string::npos) + { + if (Settings::appBundleProvided()) + { + path = std::regex_replace(path, std::regex("@executable_path/"), Settings::executableFolder()); + } + } + if (dependent_file != rpath_file) + { + if (path.find("@loader_path") != std::string::npos) + { + path = std::regex_replace(path, std::regex("@loader_path/"), file_prefix); + } + } + if (realpath(path.c_str(), buffer)) + { + fullpath = buffer; + rpath_to_fullpath[rpath_file] = fullpath; + return true; + } + } + else if (path.find("@rpath") != std::string::npos) + { + if (Settings::appBundleProvided()) + { + std::string pathE = std::regex_replace(path, std::regex("@rpath/"), Settings::executableFolder()); + if (realpath(pathE.c_str(), buffer)) + { + fullpath = buffer; + rpath_to_fullpath[rpath_file] = fullpath; + return true; + } + } + if (dependent_file != rpath_file) + { + std::string pathL = std::regex_replace(path, std::regex("@rpath/"), file_prefix); + if (realpath(pathL.c_str(), buffer)) + { + fullpath = buffer; + rpath_to_fullpath[rpath_file] = fullpath; + return true; + } + } + } + return false; + }; + + // fullpath previously stored + if (rpath_to_fullpath.find(rpath_file) != rpath_to_fullpath.end()) + { + fullpath = rpath_to_fullpath[rpath_file]; + } + else if (!check_path(rpath_file)) + { + for(auto it = rpaths_per_file[dependent_file].begin(); it != rpaths_per_file[dependent_file].end(); ++it) + { + std::string rpath = *it; + if (rpath[rpath.size()-1] != '/') rpath += "/"; + + std::string path = rpath + suffix; + if (check_path(path)) break; } } if (fullpath.empty()) { - std::cerr << "\n/!\\ WARNING : can't get path for '" << rpath_file << "'\n"; - fullpath = getUserInputDirForFile(suffix) + suffix; - if (realpath(fullpath.c_str(), buffer)) { - fullpath = buffer; + size_t search_path_count = Settings::searchPathAmount(); + for(size_t i=0; i rpaths_to_fix; @@ -168,7 +258,7 @@ void fixRpathsOnFile(const std::string& original_file, const std::string& file_t void addDependency(std::string path, std::string filename) { - Dependency dep(path); + Dependency dep(path, filename); // we need to check if this library was already added to avoid duplicates bool in_deps = false; @@ -179,15 +269,16 @@ void addDependency(std::string path, std::string filename) } // check if this library was already added to |deps_per_file[filename]| to avoid duplicates - std::vector deps_in_file = deps_per_file[filename]; bool in_deps_per_file = false; - const int deps_in_file_amount = deps_in_file.size(); + const int deps_in_file_amount = deps_per_file[filename].size(); for(int n=0; n& lines) { - // execute "otool -L" on the given file and collect the command's output - std::string cmd = "otool -L " + filename; + // execute "otool -l" on the given file and collect the command's output + std::string cmd = "otool -l " + filename; std::string output = system_get_output(cmd); - if(output.find("can't open file")!=std::string::npos or output.find("No such file")!=std::string::npos or output.size()<1) + if (output.find("can't open file")!=std::string::npos or output.find("No such file")!=std::string::npos or output.size()<1) { std::cerr << "Cannot find file " << filename << " to read its dependencies" << std::endl; exit(1); } // split output - tokenize(output, "\n", &lines); - deps_collected[filename] = true; -} + std::vector raw_lines; + tokenize(output, "\n", &raw_lines); + bool searching = false; + for(const auto& line : raw_lines) { + if (line.find("cmd LC_LOAD_DYLIB") != std::string::npos) + { + if (searching) + { + std::cerr << "\n\n/!\\ ERROR: Failed to find name before next cmd" << std::endl; + exit(1); + } + searching = true; + } + else if (searching) + { + size_t found = line.find("name "); + if (found != std::string::npos) + { + lines.push_back('\t' + line.substr(found+5, std::string::npos)); + searching = false; + } + } + } +} void collectDependencies(std::string filename) { std::vector lines; collectDependencies(filename, lines); - - std::cout << "."; fflush(stdout); - + const int line_amount = lines.size(); for(int n=0; n lines; std::string original_path = deps[n].getOriginalPath(); if (isRpath(original_path)) @@ -262,35 +366,34 @@ void collectSubDependencies() const int line_amount = lines.size(); for(int n=0; n::iterator it = frameworks.begin(); it != frameworks.end(); ++it) + { + std::string framework = *it; + if (framework.find("QtCore") != std::string::npos) + { + qtCoreFound = true; + original_file = framework; + } + if (framework.find("QtGui") != std::string::npos) + qtGuiFound = true; + if (framework.find("QtNetwork") != std::string::npos) + qtNetworkFound = true; + if (framework.find("QtSql") != std::string::npos) + qtSqlFound = true; + if (framework.find("QtSvg") != std::string::npos) + qtSvgFound = true; + if (framework.find("QtMultimedia") != std::string::npos) + qtMultimediaFound = true; + if (framework.find("Qt3DRender") != std::string::npos) + qt3dRenderFound = true; + if (framework.find("Qt3DQuickRender") != std::string::npos) + qt3dQuickRenderFound = true; + if (framework.find("QtPositioning") != std::string::npos) + qtPositioningFound = true; + if (framework.find("QtLocation") != std::string::npos) + qtLocationFound = true; + if (framework.find("TextToSpeech") != std::string::npos) + qtTextToSpeechFound = true; + if (framework.find("WebView") != std::string::npos) + qtWebViewFound = true; + } + + if (!qtCoreFound) return; + if (!qt_plugins_called) createQtConf(Settings::resourcesFolder()); + qt_plugins_called = true; + + const auto fixupPlugin = [original_file](std::string plugin) + { + std::string dest = Settings::pluginsFolder(); + std::string framework_root = getFrameworkRoot(original_file); + std::string prefix = filePrefix(framework_root); + std::string qt_prefix = filePrefix(prefix.substr(0, prefix.size()-1)); + std::string qt_plugins_prefix = qt_prefix + "plugins/"; + if (fileExists(qt_plugins_prefix + plugin)) + { + mkdir(dest + plugin); + copyFile(qt_plugins_prefix + plugin, dest); + std::vector files = lsDir(dest + plugin+"/"); + for (const auto& file : files) + { + Settings::addFileToFix(dest + plugin+"/"+file); + collectDependencies(dest + plugin+"/"+file); + changeId(dest + plugin+"/"+file, "@rpath/" + plugin+"/"+file); + } + } + }; + + std::string framework_root = getFrameworkRoot(original_file); + std::string prefix = filePrefix(framework_root); + std::string qt_prefix = filePrefix(prefix.substr(0, prefix.size()-1)); + std::string qt_plugins_prefix = qt_prefix + "plugins/"; + + std::string dest = Settings::pluginsFolder(); + mkdir(dest + "platforms"); + copyFile(qt_plugins_prefix + "platforms/libqcocoa.dylib", dest + "platforms"); + Settings::addFileToFix(dest + "platforms/libqcocoa.dylib"); + collectDependencies(dest + "platforms/libqcocoa.dylib"); + + fixupPlugin("printsupport"); + fixupPlugin("styles"); + fixupPlugin("imageformats"); + fixupPlugin("iconengines"); + if (!qtSvgFound) systemp(std::string("rm -f ") + dest + "imageformats/libqsvg.dylib"); + if (qtGuiFound) + { + fixupPlugin("platforminputcontexts"); + fixupPlugin("virtualkeyboard"); + } + if (qtNetworkFound) fixupPlugin("bearer"); + if (qtSqlFound) fixupPlugin("sqldrivers"); + if (qtMultimediaFound) + { + fixupPlugin("mediaservice"); + fixupPlugin("audio"); + } + if (qt3dRenderFound) + { + fixupPlugin("sceneparsers"); + fixupPlugin("geometryloaders"); + } + if (qt3dQuickRenderFound) fixupPlugin("renderplugins"); + if (qtPositioningFound) fixupPlugin("position"); + if (qtLocationFound) fixupPlugin("geoservices"); + if (qtTextToSpeechFound) fixupPlugin("texttospeech"); + if (qtWebViewFound) fixupPlugin("webview"); + + collectSubDependencies(); +} + diff --git a/src/DylibBundler.h b/src/DylibBundler.h index edf557b..b4ee82f 100644 --- a/src/DylibBundler.h +++ b/src/DylibBundler.h @@ -27,10 +27,22 @@ THE SOFTWARE. #include -void collectDependencies(std::string filename); -void collectSubDependencies(); -void doneWithDeps_go(); +void changeLibPathsOnFile(std::string file_to_fix); + bool isRpath(const std::string& path); +void collectRpaths(const std::string& filename); +void collectRpathsForFilename(const std::string& filename); +std::string searchFilenameInRpaths(const std::string& rpath_dep, const std::string& dependent_file); std::string searchFilenameInRpaths(const std::string& rpath_dep); +void fixRpathsOnFile(const std::string& original_file, const std::string& file_to_fix); + +void addDependency(std::string path, std::string dependent_file); +void collectDependencies(std::string dependent_file, std::vector& lines); +void collectDependencies(std::string dependent_file); +void collectSubDependencies(); + +void doneWithDeps_go(); + +void copyQtPlugins(); #endif diff --git a/src/Settings.cpp b/src/Settings.cpp index 5a74e0b..feb1114 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -23,6 +23,7 @@ THE SOFTWARE. */ #include "Settings.h" +#include "Utils.h" #include namespace Settings @@ -31,6 +32,18 @@ namespace Settings bool overwrite_files = false; bool overwrite_dir = false; bool create_dir = false; +bool bundle_libs = false; +bool bundle_frameworks = false; +bool quiet_output = false; + +std::string dest_folder_str = "./libs/"; +std::string dest_folder_str_app = "./Frameworks/"; +std::string dest_folder = dest_folder_str; +std::string dest_path = dest_folder; + +std::string inside_path_str = "@executable_path/../libs/"; +std::string inside_path_str_app = "@executable_path/../Frameworks/"; +std::string inside_path = inside_path_str; bool canOverwriteFiles(){ return overwrite_files; } bool canOverwriteDir(){ return overwrite_dir; } @@ -40,33 +53,76 @@ void canOverwriteFiles(bool permission){ overwrite_files = permission; } void canOverwriteDir(bool permission){ overwrite_dir = permission; } void canCreateDir(bool permission){ create_dir = permission; } +bool bundleLibs(){ return bundle_libs; } +void bundleLibs(bool on){ bundle_libs = on; } -bool bundleLibs_bool = false; -bool bundleLibs(){ return bundleLibs_bool; } -void bundleLibs(bool on){ bundleLibs_bool = on; } +bool bundleFrameworks(){ return bundle_frameworks; } +void bundleFrameworks(bool on){ bundle_frameworks = on; } +bool quietOutput(){ return quiet_output; } +void quietOutput(bool status){ quiet_output = status; } -std::string dest_folder_str = "./libs/"; -std::string destFolder(){ return dest_folder_str; } -void destFolder(std::string path) +std::string app_bundle; +bool appBundleProvided(){ return !app_bundle.empty(); } +std::string appBundle(){ return app_bundle; } +void appBundle(std::string path) { - dest_folder_str = path; + app_bundle = path; + char buffer[PATH_MAX]; + if(realpath(app_bundle.c_str(), buffer)) + app_bundle = buffer; // fix path if needed so it ends with '/' - if( dest_folder_str[ dest_folder_str.size()-1 ] != '/' ) dest_folder_str += "/"; + if( app_bundle[ app_bundle.size()-1 ] != '/' ) + app_bundle += "/"; + + std::string bundle_executable_path = app_bundle + "Contents/MacOS/" + bundleExecutableName(app_bundle); + if(realpath(bundle_executable_path.c_str(), buffer)) + bundle_executable_path = buffer; + addFileToFix(bundle_executable_path); + + if(inside_path == inside_path_str) + inside_path = inside_path_str_app; + if(dest_folder == dest_folder_str) + dest_folder = dest_folder_str_app; + + dest_path = app_bundle + "Contents/" + stripLSlash(dest_folder); + if(realpath(dest_path.c_str(), buffer)) + dest_path = buffer; + if( dest_path[ dest_path.size()-1 ] != '/' ) + dest_path += "/"; } +std::string destFolder(){ return dest_path; } +void destFolder(std::string path) +{ + dest_path = path; + if(appBundleProvided()) dest_path = app_bundle + "Contents/" + stripLSlash(path); + char buffer[PATH_MAX]; + if(realpath(dest_path.c_str(), buffer)) dest_path = buffer; + if( dest_path[ dest_path.size()-1 ] != '/' ) dest_path += "/"; +} + +std::string executableFolder() { return app_bundle + "Contents/MacOS/"; } +std::string frameworksFolder() { return app_bundle + "Contents/Frameworks/"; } +std::string pluginsFolder() { return app_bundle + "Contents/PlugIns/"; } +std::string resourcesFolder() { return app_bundle + "Contents/Resources/"; } + std::vector files; -void addFileToFix(std::string path){ files.push_back(path); } +void addFileToFix(std::string path) +{ + char buffer[PATH_MAX]; + if(realpath(path.c_str(), buffer)) path = buffer; + files.push_back(path); +} int fileToFixAmount(){ return files.size(); } std::string fileToFix(const int n){ return files[n]; } -std::string inside_path_str = "@executable_path/../libs/"; -std::string inside_lib_path(){ return inside_path_str; } +std::string inside_lib_path(){ return inside_path; } void inside_lib_path(std::string p) { - inside_path_str = p; + inside_path = p; // fix path if needed so it ends with '/' - if( inside_path_str[ inside_path_str.size()-1 ] != '/' ) inside_path_str += "/"; + if( inside_path[ inside_path.size()-1 ] != '/' ) inside_path += "/"; } std::vector prefixes_to_ignore; @@ -89,9 +145,10 @@ bool isPrefixIgnored(std::string prefix) bool isPrefixBundled(std::string prefix) { - if(prefix.find(".framework") != std::string::npos) return false; + if(!bundle_frameworks && prefix.find(".framework") != std::string::npos) return false; if(prefix.find("@executable_path") != std::string::npos) return false; - if(prefix.compare("/usr/lib/") == 0) return false; + if(prefix.find("/usr/lib/") == 0) return false; + if(prefix.find("/System/Library/") != std::string::npos) return false; if(isPrefixIgnored(prefix)) return false; return true; @@ -102,4 +159,15 @@ void addSearchPath(std::string path){ searchPaths.push_back(path); } int searchPathAmount(){ return searchPaths.size(); } std::string searchPath(const int n){ return searchPaths[n]; } +std::vector userSearchPaths; +void addUserSearchPath(std::string path){ userSearchPaths.push_back(path); } +size_t userSearchPathAmount(){ return userSearchPaths.size(); } +std::string userSearchPath(const int n){ return userSearchPaths[n]; } + +// if some libs are missing prefixes, this will be set to true +// more stuff will then be necessary to do +bool missing_prefixes = false; +bool missingPrefixes(){ return missing_prefixes; } +void missingPrefixes(bool status){ missing_prefixes = status; } + } diff --git a/src/Settings.h b/src/Settings.h index f1e7040..85ae3dd 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -46,9 +46,24 @@ void canCreateDir(bool permission); bool bundleLibs(); void bundleLibs(bool on); +bool bundleFrameworks(); +void bundleFrameworks(bool on); + +bool quietOutput(); +void quietOutput(bool status); + +bool appBundleProvided(); +std::string appBundle(); +void appBundle(std::string path); + std::string destFolder(); void destFolder(std::string path); +std::string executableFolder(); +std::string frameworksFolder(); +std::string pluginsFolder(); +std::string resourcesFolder(); + void addFileToFix(std::string path); int fileToFixAmount(); std::string fileToFix(const int n); @@ -60,5 +75,12 @@ void addSearchPath(std::string path); int searchPathAmount(); std::string searchPath(const int n); +void addUserSearchPath(std::string path); +size_t userSearchPathAmount(); +std::string userSearchPath(const int n); + +bool missingPrefixes(); +void missingPrefixes(bool status); + } #endif diff --git a/src/Utils.cpp b/src/Utils.cpp index 68d23a7..86baaad 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -28,18 +28,51 @@ THE SOFTWARE. #include "Settings.h" #include #include +#include #include +#include #include #include #include #include using namespace std; -/* -void setInstallPath(string loc) +string filePrefix(string in) { - path_to_libs_folder = loc; -}*/ + return in.substr(0, in.rfind("/")+1); +} + +string stripPrefix(string in) +{ + return in.substr(in.rfind("/")+1); +} + +string getFrameworkRoot(string in) +{ + return in.substr(0, in.find(".framework")+10); +} + +string getFrameworkPath(string in) +{ + return in.substr(in.rfind(".framework/")+11); +} + +string stripLSlash(string in) +{ + if (in[0] == '.' && in[1] == '/') in = in.substr(2, in.size()); + return in; +} + +void rtrim_in_place(string& s) +{ + s.erase(find_if(s.rbegin(), s.rend(), [](unsigned char c){ return !isspace(c); }).base(), s.end()); +} + +string rtrim(string s) +{ + rtrim_in_place(s); + return s; +} void tokenize(const string& str, const char* delim, vector* vectorarg) { @@ -67,36 +100,22 @@ void tokenize(const string& str, const char* delim, vector* vectorarg) } - - -bool fileExists( std::string filename ) +bool fileExists(string filename) { - if (access( filename.c_str(), F_OK ) != -1) - { - return true; // file exists - } - else - { - //std::cout << "access(filename) returned -1 on filename [" << filename << "] I will try trimming." << std::endl; - std::string delims = " \f\n\r\t\v"; - std::string rtrimmed = filename.substr(0, filename.find_last_not_of(delims) + 1); - std::string ftrimmed = rtrimmed.substr(rtrimmed.find_first_not_of(delims)); - if (access( ftrimmed.c_str(), F_OK ) != -1) - { - return true; - } - else - { - //std::cout << "Still failed. Cannot find the specified file." << std::endl; - return false;// file doesn't exist - } - } + if (access( filename.c_str(), F_OK ) != -1) return true; // file exists + + string delims = " \f\n\r\t\v"; + string rtrimmed = filename.substr(0, filename.find_last_not_of(delims)+1); + string ftrimmed = rtrimmed.substr(rtrimmed.find_first_not_of(delims)); + + if (access( ftrimmed.c_str(), F_OK ) != -1) return true; + return false; // file doesn't exist } void copyFile(string from, string to) { - bool override = Settings::canOverwriteFiles(); - if(!override) + bool overwrite = Settings::canOverwriteFiles(); + if(!overwrite) { if(fileExists( to )) { @@ -105,18 +124,17 @@ void copyFile(string from, string to) } } - string override_permission = string(override ? "-f " : "-n "); - // copy file to local directory - string command = string("cp ") + override_permission + from + string(" ") + to; - if( from != to && systemp( command ) != 0 ) + string overwrite_permission = string(overwrite ? "-f " : "-n "); + string command = string("cp -R ") + overwrite_permission + from + string(" ") + to; + if (from != to && systemp(command) != 0) { cerr << "\n\nError : An error occured while trying to copy file " << from << " to " << to << endl; exit(1); } // give it write permission - string command2 = string("chmod +w ") + to; + string command2 = string("chmod -R +w ") + to; if( systemp( command2 ) != 0 ) { cerr << "\n\nError : An error occured while trying to set write permissions on file " << to << endl; @@ -124,6 +142,44 @@ void copyFile(string from, string to) } } +void deleteFile(string path, bool overwrite) +{ + string overwrite_permission = string(overwrite ? "-f " : " "); + string command = string("rm -r ") + overwrite_permission + path; + if( systemp( command ) != 0 ) + { + cerr << "\n\nError: An error occured while trying to delete " << path << endl; + exit(1); + } +} + +void deleteFile(string path) +{ + bool overwrite = Settings::canOverwriteFiles(); + deleteFile(path, overwrite); +} + +std::vector lsDir(const std::string& path) +{ + std::string cmd = "ls " + path; + std::string output = system_get_output(cmd); + std::vector files; + tokenize(output, "\n", &files); + return files; +} + +bool mkdir(const std::string& path) +{ + cout << "Creating directory " << path << std::endl; + string command = string("mkdir -p ") + path; + if( systemp( command ) != 0 ) + { + cerr << "\n/!\\ ERROR: An error occured while creating " << path << endl; + return false; + } + return true; +} + std::string system_get_output(std::string cmd) { FILE * command_output; @@ -161,13 +217,13 @@ std::string system_get_output(std::string cmd) return full_output; } -int systemp(std::string& cmd) +int systemp(const std::string& cmd) { - std::cout << " " << cmd.c_str() << std::endl; + if(!Settings::quietOutput()) std::cout << " " << cmd << "\n"; return system(cmd.c_str()); } -std::string getUserInputDirForFile(const std::string& filename) +std::string getUserInputDirForFile(const std::string& filename, const std::string& dependent_file) { const int searchPathAmount = Settings::searchPathAmount(); for(int n=0; n*); bool fileExists( std::string filename ); void copyFile(std::string from, std::string to); +void deleteFile(std::string path, bool overwrite); +void deleteFile(std::string path); + +std::vector lsDir(const std::string& path); +bool mkdir(const std::string& path); + // executes a command in the native shell and returns output in string std::string system_get_output(std::string cmd); // like 'system', runs a command on the system shell, but also prints the command to stdout. -int systemp(std::string& cmd); -std::string getUserInputDirForFile(const std::string& filename); +int systemp(const std::string& cmd); + +std::string getUserInputDirForFile(const std::string& filename, const std::string& dependent_file); + +std::string bundleExecutableName(const std::string& app_bundle_path); + +void changeId(std::string binary_file, std::string new_id); +void changeInstallName(std::string binary_file, std::string old_name, std::string new_name); + +// check the same paths the system would search for dylibs +void initSearchPaths(); + +void createQtConf(std::string directory); #endif diff --git a/src/main.cpp b/src/main.cpp index 0c2e9ca..92df890 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -53,17 +53,20 @@ void showHelp() { std::cout << "dylibbundler " << VERSION << std::endl; std::cout << "dylibbundler is a utility that helps bundle dynamic libraries inside mac OS X app bundles.\n" << std::endl; - - std::cout << "-x, --fix-file " << std::endl; - std::cout << "-b, --bundle-deps" << std::endl; - std::cout << "-d, --dest-dir " << std::endl; - std::cout << "-p, --install-path <'inner' path of bundled libraries (usually relative to executable, by default '@executable_path/../libs/')>" << std::endl; - std::cout << "-s, --search-path " << std::endl; + + std::cout << "-a, --app (fixes the main executable of the app bundle)" << std::endl; + std::cout << "-x, --fix-file (additional files to fix up inside app)" << std::endl; + std::cout << "-b, --bundle-deps" << std::endl; + std::cout << "-f, --frameworks (copy dependencies that are frameworks)" << std::endl; + std::cout << "-d, --dest-dir (relative to MyApp.app/Contents if '-a' is passed)" << std::endl; + std::cout << "-p, --install-path <'inner' path of bundled libraries> (default: '@executable_path/../libs/' or '@executable_path/../Frameworks/' if '-a' is passed)" << std::endl; + std::cout << "-s, --search-path " << std::endl; std::cout << "-of, --overwrite-files (allow overwriting files in output directory)" << std::endl; std::cout << "-od, --overwrite-dir (totally overwrite output directory if it already exists. implies --create-dir)" << std::endl; std::cout << "-cd, --create-dir (creates output directory if necessary)" << std::endl; - std::cout << "-i, --ignore (will ignore libraries in this directory)" << std::endl; - std::cout << "-h, --help" << std::endl; + std::cout << "-i, --ignore (will ignore libraries in this directory)" << std::endl; + std::cout << "-q, --quiet (less verbose output)" << std::endl; + std::cout << "-h, --help" << std::endl; } int main (int argc, char * const argv[]) @@ -72,7 +75,13 @@ int main (int argc, char * const argv[]) // parse arguments for(int i=0; i0) { // if we meet an unknown flag, abort @@ -144,7 +162,7 @@ int main (int argc, char * const argv[]) exit(0); } - std::cout << "* Collecting dependencies"; fflush(stdout); + std::cout << "Collecting dependencies..." << std::endl; const int amount = Settings::fileToFixAmount(); for(int n=0; n