8 #include <boost/filesystem.hpp>
12 #include "includes/appdir.h"
13 #include "includes/CImg.h"
14 #include "includes/elf_file.h"
15 #include "includes/log.h"
16 #include "includes/util.h"
17 #include "includes/subprocess.h"
18 #include "includes/copyright.h"
20 // auto-generated headers
21 #include "includes/excludelist.h"
22 #include "includes/appdir_root_setup.h"
24 using namespace linuxdeploy::core;
25 using namespace linuxdeploy::desktopfile;
26 using namespace linuxdeploy::core::log;
28 using namespace cimg_library;
29 namespace bf = boost::filesystem;
33 constexpr bf::perms DEFAULT_PERMS = bf::owner_write | bf::owner_read | bf::group_read | bf::others_read;
35 constexpr bf::perms EXECUTABLE_PERMS = DEFAULT_PERMS | bf::owner_exe | bf::group_exe | bf::others_exe;
41 bf::perms addedPermissions;
44 typedef std::map<bf::path, CopyOperation> CopyOperationsMap;
47 * Stores copy operations.
48 * This way, the storage logic does not have to be known to the using class.
50 class CopyOperationsStorage {
52 // using a map to make sure every target path is there only once
53 CopyOperationsMap _storedOperations;
56 CopyOperationsStorage() = default;
60 * @param fromPath path to copy from
61 * @param toPath path to copy to
62 * @param addedPermissions permissions to add to the file's permissions
64 void addOperation(const bf::path& fromPath, const bf::path& toPath, const bf::perms addedPermissions) {
65 CopyOperation operation{fromPath, toPath, addedPermissions};
66 _storedOperations[fromPath] = operation;
71 * @return vector containing all operations (random order).
73 std::vector<CopyOperation> getOperations() {
74 std::vector<CopyOperation> operations;
75 operations.reserve(_storedOperations.size());
77 for (const auto& operationsPair : _storedOperations) {
78 operations.emplace_back(operationsPair.second);
85 * Clear internal storage.
88 _storedOperations.clear();
93 namespace linuxdeploy {
96 class AppDir::PrivateData {
100 // store deferred operations
101 // these can be executed by calling excuteDeferredOperations
102 CopyOperationsStorage copyOperationsStorage;
103 std::set<bf::path> stripOperations;
104 std::map<bf::path, std::string> setElfRPathOperations;
106 // stores all files that have been visited by the deploy functions, e.g., when they're blacklisted,
107 // have been added to the deferred operations already, etc.
108 // lookups in a single container are a lot faster than having to look up in several ones, therefore
109 // the little amount of additional memory is worth it, considering the improved performance
110 std::set<bf::path> visitedFiles;
112 // used to automatically rename resources to improve the UX, e.g. icons
115 // platform dependent implementation of copyright files deployment
116 std::shared_ptr<copyright::ICopyrightFilesManager> copyrightFilesManager;
118 // decides whether copyright files deployment is performed
119 bool disableCopyrightFilesDeployment = false;
122 PrivateData() : copyOperationsStorage(), stripOperations(), setElfRPathOperations(), visitedFiles(), appDirPath() {
123 copyrightFilesManager = copyright::ICopyrightFilesManager::getInstance();
127 // calculate library directory name for given ELF file, taking system architecture into account
128 static std::string getLibraryDirName(const bf::path& path) {
129 const auto systemElfClass = elf_file::ElfFile::getSystemElfClass();
130 const auto elfClass = elf_file::ElfFile(path).getElfClass();
132 std::string libDirName = "lib";
134 if (systemElfClass != elfClass) {
135 if (elfClass == ELFCLASS32)
144 // actually copy file
145 // mimics cp command behavior
146 // also adds minimum file permissions (by default adds 0644 to existing permissions)
147 static bool copyFile(const bf::path& from, bf::path to, bf::perms addedPerms, bool overwrite = false) {
148 ldLog() << "Copying file" << from << "to" << to << std::endl;
151 if (!to.parent_path().empty() && !bf::is_directory(to.parent_path()) && !bf::create_directories(to.parent_path())) {
152 ldLog() << LD_ERROR << "Failed to create parent directory" << to.parent_path() << "for path" << to << std::endl;
156 if (*(to.string().end() - 1) == '/' || bf::is_directory(to))
157 to /= from.filename();
159 if (!overwrite && bf::exists(to)) {
160 ldLog() << LD_DEBUG << "File exists, skipping:" << to << std::endl;
164 bf::copy_file(from, to, bf::copy_option::overwrite_if_exists);
165 bf::permissions(to, addedPerms | bf::add_perms);
166 } catch (const bf::filesystem_error& e) {
167 ldLog() << LD_ERROR << "Failed to copy file" << from << "to" << to << LD_NO_SPACE << ":" << e.what() << std::endl;
175 static bool symlinkFile(const bf::path& target, bf::path symlink, const bool useRelativePath = true) {
176 ldLog() << "Creating symlink for file" << target << "in/as" << symlink << std::endl;
178 if (!useRelativePath) {
179 ldLog() << LD_ERROR << "Not implemented" << std::endl;
183 bf::path relativeTargetPath;
185 // cannot use ln's --relative option any more since we want to support old distros as well
186 // (looking at you, CentOS 6!)
188 auto symlinkBase = symlink;
190 if (!bf::is_directory(symlinkBase))
191 symlinkBase = symlinkBase.parent_path();
193 relativeTargetPath = bf::relative(target, symlinkBase);
196 // if a directory is passed as path to create the symlink as/in, we need to complete it with
197 // the filename of the source file to mimic ln's behavior
198 if (bf::is_directory(symlink))
199 symlink /= target.filename();
201 // override existing target (similar to ln's -f flag)
202 if (bf::exists(symlink))
205 // actually perform symlink creation
207 bf::create_symlink(relativeTargetPath, symlink);
208 } catch (const bf::filesystem_error& e) {
209 ldLog() << LD_ERROR << "symlink creation failed:" << e.what() << std::endl;
216 bool hasBeenVisitedAlready(const bf::path& path) {
217 return visitedFiles.find(path) != visitedFiles.end();
220 // execute deferred copy operations registered with the deploy* functions
221 bool executeDeferredOperations() {
224 const auto copyOperations = copyOperationsStorage.getOperations();
225 std::for_each(copyOperations.begin(), copyOperations.end(), [&success](const CopyOperation& operation) {
226 if (!copyFile(operation.fromPath, operation.toPath, operation.addedPermissions)) {
230 copyOperationsStorage.clear();
235 if (getenv("NO_STRIP") != nullptr) {
236 ldLog() << LD_WARNING << "$NO_STRIP environment variable detected, not stripping binaries" << std::endl;
237 stripOperations.clear();
239 const auto stripPath = getStripPath();
241 while (!stripOperations.empty()) {
242 const auto& filePath = *(stripOperations.begin());
244 if (util::stringStartsWith(elf_file::ElfFile(filePath).getRPath(), "$")) {
245 ldLog() << LD_WARNING << "Not calling strip on binary" << filePath << LD_NO_SPACE
246 << ": rpath starts with $" << std::endl;
248 ldLog() << "Calling strip on library" << filePath << std::endl;
250 subprocess::subprocess_env_map_t env;
251 env.insert(std::make_pair(std::string("LC_ALL"), std::string("C")));
253 subprocess::subprocess proc({stripPath, filePath.string()}, env);
255 const auto result = proc.run();
256 const auto& err = result.stderr_string();
258 if (result.exit_code() != 0 &&
259 !util::stringContains(err, "Not enough room for program headers")) {
260 ldLog() << LD_ERROR << "Strip call failed:" << err << std::endl;
265 stripOperations.erase(stripOperations.begin());
272 while (!setElfRPathOperations.empty()) {
273 const auto& currentEntry = *(setElfRPathOperations.begin());
274 const auto& filePath = currentEntry.first;
275 const auto& rpath = currentEntry.second;
277 elf_file::ElfFile elfFile(filePath);
279 // no need to set rpath in debug symbols files
280 // also, patchelf crashes on such symbols
281 if (isInDebugSymbolsLocation(filePath) || elfFile.isDebugSymbolsFile()) {
282 ldLog() << LD_WARNING << "Not setting rpath in debug symbols file:" << filePath
284 } else if (!elfFile.isDynamicallyLinked()) {
285 ldLog() << LD_WARNING << "Not setting rpath in statically-linked file: " << filePath
288 ldLog() << "Setting rpath in ELF file" << filePath << "to" << rpath << std::endl;
289 if (!elf_file::ElfFile(filePath).setRPath(rpath)) {
290 ldLog() << LD_ERROR << "Failed to set rpath in ELF file:" << filePath << std::endl;
295 setElfRPathOperations.erase(setElfRPathOperations.begin());
301 // search for copyright file for file and deploy it to AppDir
302 bool deployCopyrightFiles(const bf::path& from) {
303 if (disableCopyrightFilesDeployment)
306 if (copyrightFilesManager == nullptr)
309 auto copyrightFiles = copyrightFilesManager->getCopyrightFilesForPath(from);
311 if (copyrightFiles.empty())
314 ldLog() << "Deploying copyright files for file" << from << std::endl;
316 for (const auto& file : copyrightFiles) {
317 std::string targetDir = file.string();
318 targetDir.erase(0, 1);
319 deployFile(file, appDirPath / targetDir, DEFAULT_PERMS);
325 // register copy operation that will be executed later
326 // by compiling a list of files to copy instead of just copying everything, one can ensure that
327 // the files are touched once only
328 // returns the full path of the deployment destination (useful if to is a directory
329 bf::path deployFile(const bf::path& from, bf::path to, bf::perms addedPerms, bool verbose = false) {
330 // not sure whether this is 100% bullet proof, but it simulates the cp command behavior
331 if (to.string().back() == '/' || bf::is_directory(to)) {
332 to /= from.filename();
336 ldLog() << "Deploying file" << from << "to" << to << std::endl;
338 copyOperationsStorage.addOperation(from, to, addedPerms);
340 // mark file as visited
341 visitedFiles.insert(from);
346 bool deployElfDependencies(const bf::path& path) {
347 ldLog() << "Deploying dependencies for ELF file" << path << std::endl;
349 for (const auto &dependencyPath : elf_file::ElfFile(path).traceDynamicDependencies())
350 if (!deployLibrary(dependencyPath, false, false))
352 } catch (const elf_file::DependencyNotFoundError& e) {
353 ldLog() << LD_ERROR << e.what() << std::endl;
360 static std::string getStripPath() {
361 // by default, try to use a strip next to the makeappimage binary
362 // if that isn't available, fall back to searching for strip in the PATH
363 std::string stripPath = "strip";
365 auto binDirPath = bf::path(util::getOwnExecutablePath()).parent_path();
366 auto localStripPath = binDirPath / "strip";
368 if (bf::exists(localStripPath))
369 stripPath = localStripPath.string();
371 ldLog() << LD_DEBUG << "Using strip:" << stripPath << std::endl;
376 static std::string calculateRelativeRPath(const bf::path& originDir, const bf::path& dependencyLibrariesDir) {
377 auto relPath = bf::relative(bf::absolute(dependencyLibrariesDir), bf::absolute(originDir));
378 std::string rpath = "$ORIGIN/" + relPath.string() + ":$ORIGIN";
382 bool deployLibrary(const bf::path& path, bool forceDeploy = false, bool deployDependencies = true, const bf::path& destination = bf::path()) {
383 if (!forceDeploy && hasBeenVisitedAlready(path)) {
384 ldLog() << LD_DEBUG << "File has been visited already:" << path << std::endl;
388 if (!bf::exists(path)) {
389 ldLog() << LD_ERROR << "Cannot deploy non-existing library file:" << path << std::endl;
393 static auto isInExcludelist = [](const bf::path& fileName) {
394 for (const auto& excludePattern : generatedExcludelist) {
395 // simple string match is faster than using fnmatch
396 if (excludePattern == fileName)
399 auto fnmatchResult = fnmatch(excludePattern.c_str(), fileName.string().c_str(), FNM_PATHNAME);
400 switch (fnmatchResult) {
406 ldLog() << LD_ERROR << "fnmatch() reported error:" << fnmatchResult << std::endl;
414 if (!forceDeploy && isInExcludelist(path.filename())) {
415 ldLog() << "Skipping deployment of blacklisted library" << path << std::endl;
417 // mark file as visited
418 visitedFiles.insert(path);
423 // note for self: make sure to have a trailing slash in libraryDir, otherwise copyFile won't
424 // create a directory
425 bf::path libraryDir = appDirPath / "usr" / (getLibraryDirName(path) + "/");
427 ldLog() << "Deploying shared library" << path;
428 if (!destination.empty())
429 ldLog() << " (destination:" << destination << LD_NO_SPACE << ")";
430 ldLog() << std::endl;
432 auto actualDestination = destination.empty() ? libraryDir : destination;
434 // not sure whether this is 100% bullet proof, but it simulates the cp command behavior
435 if (actualDestination.string().back() == '/' || bf::is_directory(actualDestination)) {
436 actualDestination /= path.filename();
439 // in case destinationPath is a directory, deployFile will give us the deployed file's path
440 actualDestination = deployFile(path, actualDestination, DEFAULT_PERMS);
441 deployCopyrightFiles(path);
443 std::string rpath = "$ORIGIN";
445 if (!destination.empty()) {
446 // destination is the place where to deploy this file
447 // therefore, rpathDestination means
448 std::string rpathOriginDir = destination.string();
450 if (destination.string().back() == '/') {
451 rpathOriginDir = destination.string();
453 while (rpathOriginDir.back() == '/')
454 rpathOriginDir.erase(rpathOriginDir.end() - 1, rpathOriginDir.end());
456 rpathOriginDir = destination.parent_path().string();
459 rpath = calculateRelativeRPath(rpathOriginDir, libraryDir);
462 // no need to set rpath in debug symbols files
463 // also, patchelf crashes on such symbols
464 if (!isInDebugSymbolsLocation(actualDestination)) {
465 setElfRPathOperations[actualDestination] = rpath;
468 stripOperations.insert(actualDestination);
470 if (!deployDependencies)
473 return deployElfDependencies(path);
476 bool deployExecutable(const bf::path& path, const boost::filesystem::path& destination) {
477 if (hasBeenVisitedAlready(path)) {
478 ldLog() << LD_DEBUG << "File has been visited already:" << path << std::endl;
482 ldLog() << "Deploying executable" << path << std::endl;
484 // FIXME: make executables executable
486 auto destinationPath = destination.empty() ? appDirPath / "usr/bin/" : destination;
488 deployFile(path, destinationPath, EXECUTABLE_PERMS);
489 deployCopyrightFiles(path);
491 std::string rpath = "$ORIGIN/../" + getLibraryDirName(path);
493 if (!destination.empty()) {
494 std::string rpathDestination = destination.string();
496 if (destination.string().back() == '/') {
497 rpathDestination = destination.string();
499 while (rpathDestination.back() == '/')
500 rpathDestination.erase(rpathDestination.end() - 1, rpathDestination.end());
502 rpathDestination = destination.parent_path().string();
505 auto relPath = bf::relative(bf::absolute(appDirPath) / "usr" / getLibraryDirName(path), bf::absolute(rpathDestination));
506 rpath = "$ORIGIN/" + relPath.string();
509 setElfRPathOperations[destinationPath / path.filename()] = rpath;
510 stripOperations.insert(destinationPath / path.filename());
512 if (!deployElfDependencies(path))
518 bool deployDesktopFile(const DesktopFile& desktopFile) {
519 if (hasBeenVisitedAlready(desktopFile.path())) {
520 ldLog() << LD_DEBUG << "File has been visited already:" << desktopFile.path() << std::endl;
524 if (!desktopFile.validate()) {
525 ldLog() << LD_ERROR << "Failed to validate desktop file:" << desktopFile.path() << std::endl;
528 ldLog() << "Deploying desktop file" << desktopFile.path() << std::endl;
530 deployFile(desktopFile.path(), appDirPath / "usr/share/applications/", DEFAULT_PERMS);
535 bool deployIcon(const bf::path& path, const std::string targetFilename = "") {
536 if (hasBeenVisitedAlready(path)) {
537 ldLog() << LD_DEBUG << "File has been visited already:" << path << std::endl;
541 ldLog() << "Deploying icon" << path << std::endl;
543 std::string resolution;
545 // if file is a vector image, use "scalable" directory
546 if (util::strLower(path.filename().extension().string()) == ".svg") {
547 resolution = "scalable";
550 CImg<unsigned char> image(path.c_str());
552 auto xRes = image.width();
553 auto yRes = image.height();
556 ldLog() << LD_WARNING << "x and y resolution of icon are not equal:" << path;
559 resolution = std::to_string(xRes) + "x" + std::to_string(yRes);
561 // otherwise, test resolution against "known good" values, and reject invalid ones
562 const auto knownResolutions = {8, 16, 20, 22, 24, 28, 32, 36, 42, 48, 64, 72, 96, 128, 160, 192, 256, 384, 480, 512};
565 bool invalidXRes = true, invalidYRes = true;
567 for (const auto res : knownResolutions) {
574 auto printIconHint = [&knownResolutions]() {
575 std::stringstream ss;
576 for (const auto res : knownResolutions) {
577 ss << res << "x" << res;
579 if (res != *(knownResolutions.end() - 1))
583 ldLog() << LD_ERROR << "Valid resolutions for icons are:" << ss.str() << std::endl;
587 ldLog() << LD_ERROR << "Icon" << path << "has invalid x resolution:" << xRes << std::endl;
593 ldLog() << LD_ERROR << "Icon" << path << "has invalid y resolution:" << yRes << std::endl;
597 } catch (const CImgException& e) {
598 ldLog() << LD_ERROR << "CImg error: " << e.what() << std::endl;
603 auto filename = path.filename().string();
605 // if the user wants us to automatically rename icon files, we can do so
606 // this is useful when passing multiple icons via -i in different resolutions
607 if (!targetFilename.empty()) {
608 auto newFilename = targetFilename + path.extension().string();
609 if (newFilename != filename) {
610 ldLog() << LD_WARNING << "Changing name of icon" << path << "to target filename" << newFilename << std::endl;
611 filename = newFilename;
615 deployFile(path, appDirPath / "usr/share/icons/hicolor" / resolution / "apps" / filename, DEFAULT_PERMS);
616 deployCopyrightFiles(path);
621 static bool isInDebugSymbolsLocation(const bf::path& path) {
622 // TODO: check if there's more potential locations for debug symbol files
623 for (const std::string& dbgSymbolsPrefix : {".debug/"}) {
624 if (path.string().substr(0, dbgSymbolsPrefix.size()) == dbgSymbolsPrefix)
632 AppDir::AppDir(const bf::path& path) {
633 d = std::make_shared<PrivateData>();
635 d->appDirPath = path;
638 AppDir::AppDir(const std::string& path) : AppDir(bf::path(path)) {}
640 bool AppDir::createBasicStructure() const {
641 std::vector<std::string> dirPaths = {
644 "usr/share/applications/",
645 "usr/share/icons/hicolor/",
648 for (const std::string& resolution : {"16x16", "32x32", "64x64", "128x128", "256x256", "scalable"}) {
649 auto iconPath = "usr/share/icons/hicolor/" + resolution + "/apps/";
650 dirPaths.push_back(iconPath);
653 for (const auto& dirPath : dirPaths) {
654 auto fullDirPath = d->appDirPath / dirPath;
656 ldLog() << "Creating directory" << fullDirPath << std::endl;
658 // skip directory if it exists
659 if (bf::is_directory(fullDirPath))
663 bf::create_directories(fullDirPath);
664 } catch (const bf::filesystem_error&) {
665 ldLog() << LD_ERROR << "Failed to create directory" << fullDirPath;
673 bool AppDir::deployLibrary(const bf::path& path, const bf::path& destination) {
674 return d->deployLibrary(path, false, true, destination);
677 bool AppDir::forceDeployLibrary(const bf::path& path, const bf::path& destination) {
678 return d->deployLibrary(path, true, true, destination);
681 bool AppDir::deployExecutable(const bf::path& path, const boost::filesystem::path& destination) {
682 return d->deployExecutable(path, destination);
685 bool AppDir::deployDesktopFile(const DesktopFile& desktopFile) {
686 return d->deployDesktopFile(desktopFile);
689 bool AppDir::deployIcon(const bf::path& path) {
690 return d->deployIcon(path);
693 bool AppDir::deployIcon(const bf::path& path, const std::string& targetFilename) {
694 return d->deployIcon(path, targetFilename);
697 bool AppDir::executeDeferredOperations() {
698 return d->executeDeferredOperations();
701 boost::filesystem::path AppDir::path() const {
702 return d->appDirPath;
705 static std::vector<bf::path> listFilesInDirectory(const bf::path& path, const bool recursive = true) {
706 std::vector<bf::path> foundPaths;
708 // directory_iterators throw exceptions if the directory doesn't exist
709 if (!bf::is_directory(path)) {
710 ldLog() << LD_DEBUG << "No such directory:" << path << std::endl;
715 for (bf::recursive_directory_iterator i(path); i != bf::recursive_directory_iterator(); ++i) {
716 if (bf::is_regular_file(*i)) {
717 foundPaths.push_back((*i).path());
721 for (bf::directory_iterator i(path); i != bf::directory_iterator(); ++i) {
722 if (bf::is_regular_file(*i)) {
723 foundPaths.push_back((*i).path());
731 std::vector<bf::path> AppDir::deployedIconPaths() const {
732 auto icons = listFilesInDirectory(path() / "usr/share/icons/");
733 auto pixmaps = listFilesInDirectory(path() / "usr/share/pixmaps/", false);
734 icons.reserve(pixmaps.size());
735 std::copy(pixmaps.begin(), pixmaps.end(), std::back_inserter(icons));
739 std::vector<bf::path> AppDir::deployedExecutablePaths() const {
740 return listFilesInDirectory(path() / "usr/bin/", false);
743 std::vector<DesktopFile> AppDir::deployedDesktopFiles() const {
744 std::vector<DesktopFile> desktopFiles;
746 auto paths = listFilesInDirectory(path() / "usr/share/applications/", false);
747 paths.erase(std::remove_if(paths.begin(), paths.end(), [](const bf::path& path) {
748 return path.extension() != ".desktop";
751 for (const auto& path : paths) {
752 desktopFiles.emplace_back(path.string());
758 bool AppDir::setUpAppDirRoot(const DesktopFile& desktopFile, boost::filesystem::path customAppRunPath) {
759 AppDirRootSetup setup(*this);
760 return setup.run(desktopFile, customAppRunPath);
763 bf::path AppDir::deployFile(const boost::filesystem::path& from, const boost::filesystem::path& to) {
764 return d->deployFile(from, to, DEFAULT_PERMS, true);
767 bool AppDir::copyFile(const bf::path& from, const bf::path& to, bool overwrite) const {
768 return d->copyFile(from, to, DEFAULT_PERMS, overwrite);
771 bool AppDir::createRelativeSymlink(const bf::path& target, const bf::path& symlink) const {
772 return d->symlinkFile(target, symlink, true);
775 std::vector<bf::path> AppDir::listExecutables() const {
776 std::vector<bf::path> executables;
778 for (const auto& file : listFilesInDirectory(path() / "usr" / "bin", false)) {
779 // make sure it's an ELF file
781 elf_file::ElfFile elfFile(file);
782 } catch (const elf_file::ElfFileParseError&) {
783 // FIXME: remove this workaround once the MIME check below works as intended
787 executables.push_back(file);
793 std::vector<bf::path> AppDir::listSharedLibraries() const {
794 std::vector<bf::path> sharedLibraries;
796 for (const auto& file : listFilesInDirectory(path() / "usr" / "lib", true)) {
797 // exclude debug symbols
798 if (d->isInDebugSymbolsLocation(file))
801 // make sure it's an ELF file
803 elf_file::ElfFile elfFile(file);
804 } catch (const elf_file::ElfFileParseError&) {
805 // FIXME: remove this workaround once the MIME check below works as intended
809 sharedLibraries.push_back(file);
812 return sharedLibraries;
815 bool AppDir::deployDependenciesForExistingFiles() const {
816 for (const auto& executable : listExecutables()) {
817 if (bf::is_symlink(executable))
820 if (!d->deployElfDependencies(executable))
823 std::string rpath = "$ORIGIN/../" + PrivateData::getLibraryDirName(executable);
825 d->setElfRPathOperations[executable] = rpath;
828 for (const auto& sharedLibrary : listSharedLibraries()) {
829 if (bf::is_symlink(sharedLibrary))
832 if (!d->deployElfDependencies(sharedLibrary))
835 const auto rpath = elf_file::ElfFile(sharedLibrary).getRPath();
836 auto rpathList = util::split(rpath, ':');
837 if (std::find(rpathList.begin(), rpathList.end(), "$ORIGIN") == rpathList.end()) {
838 rpathList.push_back("$ORIGIN");
839 d->setElfRPathOperations[sharedLibrary] = util::join(rpathList, ":");
841 d->setElfRPathOperations[sharedLibrary] = rpath;
845 // used to bundle dependencies of executables or libraries in the AppDir without moving them
846 // useful e.g., for plugin systems, etc.
848 constexpr auto VAR_NAME = "ADDITIONAL_BIN_DIRS";
850 const auto additionalBinDirs = getenv(VAR_NAME);
852 if (additionalBinDirs != nullptr) {
853 ldLog() << LD_DEBUG << "Read value of" << VAR_NAME << LD_NO_SPACE << ":" << additionalBinDirs << std::endl;
855 auto additionalBinaryDirs = util::split(getenv(VAR_NAME));
857 for (const auto& additionalBinaryDir : additionalBinaryDirs) {
858 ldLog() << "Deploying additional executables in directory:" << additionalBinaryDir << std::endl;
860 if (!bf::is_directory(additionalBinaryDir)) {
861 ldLog() << LD_ERROR << "Could not find additional binary dir, skipping:" << additionalBinaryDir;
864 for (bf::directory_iterator it(additionalBinaryDir); it != bf::directory_iterator(); ++it) {
865 const auto entry = *it;
866 const auto& path = entry.path();
868 // can't bundle directories
869 if (!bf::is_regular_file(entry)) {
870 ldLog() << LD_DEBUG << "Skipping non-file directory entry:" << entry.path() << std::endl;
874 // make sure we have an ELF file
876 elf_file::ElfFile(entry.path().string());
877 } catch (const elf_file::ElfFileParseError& e) {
878 ldLog() << LD_DEBUG << "Skipping non-ELF directory entry:" << entry.path() << std::endl;
881 ldLog() << "Deploying additional executable:" << entry.path().string() << std::endl;
883 // bundle dependencies
884 if (!d->deployElfDependencies(path))
887 // set rpath correctly
888 const auto rpathDestination = this->path() / "usr/lib";
890 const auto rpath = PrivateData::calculateRelativeRPath(additionalBinaryDir, rpathDestination);
891 ldLog() << LD_DEBUG << "Calculated rpath:" << rpath << std::endl;
893 d->setElfRPathOperations[path] = rpath;
902 // TODO: quite similar to deployDependenciesForExistingFiles... maybe they should be merged or use each other
903 bool AppDir::deployDependenciesOnlyForElfFile(const boost::filesystem::path& elfFilePath, bool failSilentForNonElfFile) {
904 // preconditions: file must be an ELF one, and file must be contained in the AppDir
905 const auto canonicalElfFilePath = bf::canonical(elfFilePath);
907 // can't bundle directories
908 if (!bf::is_regular_file(canonicalElfFilePath)) {
909 ldLog() << LD_DEBUG << "Skipping non-file directory entry:" << canonicalElfFilePath << std::endl;
913 // to do a proper prefix check, we need a proper absolute canonical path for the AppDir
914 const auto canonicalAppDirPath = bf::canonical(this->path());
915 ldLog() << LD_DEBUG << "absolute canonical AppDir path:" << canonicalAppDirPath << std::endl;
917 // a fancy way to check STL strings for prefixes is to "ab"use rfind
918 if (canonicalElfFilePath.string().rfind(canonicalAppDirPath.string()) != 0) {
919 ldLog() << LD_ERROR << "File" << canonicalElfFilePath << "is not contained in AppDir, its dependencies cannot be deployed into the AppDir" << std::endl;
923 // make sure we have an ELF file
925 elf_file::ElfFile(canonicalElfFilePath.string());
926 } catch (const elf_file::ElfFileParseError& e) {
927 auto level = LD_ERROR;
929 if (failSilentForNonElfFile) {
933 ldLog() << level << "Not an ELF file:" << canonicalElfFilePath << std::endl;
935 return failSilentForNonElfFile;
938 // relative path makes for a nicer and more consistent log
939 ldLog() << "Deploying dependencies for ELF file in AppDir:" << elfFilePath << std::endl;
941 // bundle dependencies
942 if (!d->deployElfDependencies(canonicalElfFilePath))
945 // set rpath correctly
946 const auto rpathDestination = this->path() / "usr/lib";
947 ldLog() << LD_DEBUG << "rpath destination:" << rpathDestination << std::endl;
949 const auto rpath = PrivateData::calculateRelativeRPath(elfFilePath.parent_path(), rpathDestination);
950 ldLog() << LD_DEBUG << "Calculated rpath:" << rpath << std::endl;
952 d->setElfRPathOperations[canonicalElfFilePath] = rpath;
957 void AppDir::setDisableCopyrightFilesDeployment(bool disable) {
958 d->disableCopyrightFilesDeployment = disable;