// system headers #include #include // library headers #include // local headers #include "linuxdeploy/core/appdir.h" #include "linuxdeploy/desktopfile/desktopfile.h" #include "linuxdeploy/core/elf.h" #include "linuxdeploy/core/log.h" #include "linuxdeploy/plugin/plugin.h" #include "linuxdeploy/util/util.h" #include "core.h" using namespace linuxdeploy; using namespace linuxdeploy::core; using namespace linuxdeploy::core::log; using namespace linuxdeploy::util; namespace bf = boost::filesystem; int main(int argc, char** argv) { args::ArgumentParser parser( "linuxdeploy -- create AppDir bundles with ease" ); args::HelpFlag help(parser, "help", "Display this help text", {'h', "help"}); args::Flag showVersion(parser, "", "Print version and exit", {'V', "version"}); args::ValueFlag verbosity(parser, "verbosity", "Verbosity of log output (0 = debug, 1 = info (default), 2 = warning, 3 = error)", {'v', "verbosity"}); args::ValueFlag appDirPath(parser, "appdir", "Path to target AppDir", {"appdir"}); args::ValueFlagList sharedLibraryPaths(parser, "library", "Shared library to deploy", {'l', "library"}); args::ValueFlagList executablePaths(parser, "executable", "Executable to deploy", {'e', "executable"}); args::ValueFlagList deployDepsOnlyPaths(parser, "path", "Path to ELF file or directory containing such files (libraries or executables) in the AppDir whose dependencies shall be deployed by linuxdeploy without copying them into the AppDir", {"deploy-deps-only"}); args::ValueFlagList desktopFilePaths(parser, "desktop file", "Desktop file to deploy", {'d', "desktop-file"}); args::Flag createDesktopFile(parser, "", "Create basic desktop file that is good enough for some tests", {"create-desktop-file"}); args::ValueFlagList iconPaths(parser, "icon file", "Icon to deploy", {'i', "icon-file"}); args::ValueFlag iconTargetFilename(parser, "filename", "Filename all icons passed via -i should be renamed to", {"icon-filename"}); args::ValueFlag customAppRunPath(parser, "AppRun path", "Path to custom AppRun script (linuxdeploy will not create a symlink but copy this file instead)", {"custom-apprun"}); args::Flag listPlugins(parser, "", "Search for plugins, print them to stdout and exit", {"list-plugins"}); args::ValueFlagList inputPlugins(parser, "name", "Input plugins to run (check whether they are available with --list-plugins)", {'p', "plugin"}); args::ValueFlagList outputPlugins(parser, "name", "Output plugins to run (check whether they are available with --list-plugins)", {'o', "output"}); try { parser.ParseCLI(argc, argv); } catch (args::Help&) { std::cerr << parser; // license information std::cerr << std::endl << "===== library information =====" << std::endl << std::endl << "This software uses the great CImg library, as well as libjpeg and libpng as well as various Boost libraries and cpp-subprocess." << std::endl << std::endl << "libjpeg license information: this software is based in part on the work of the Independent JPEG Group" << std::endl << std::endl << "CImg license information: This software is governed either by the CeCILL or the CeCILL-C " "license under French law and abiding by the rules of distribution of free software. You can " "use, modify and or redistribute the software under the terms of the CeCILL or CeCILL-C " "licenses as circulated by CEA, CNRS and INRIA at the following URL: " "\"http://cecill.info\"." << std::endl; return 0; } catch (args::ParseError& e) { std::cerr << e.what() << std::endl; std::cerr << parser; return 1; } // always show version statement std::cerr << "linuxdeploy version " << LD_VERSION << " (git commit ID " << LD_GIT_COMMIT << "), " << LD_BUILD_NUMBER << " built on " << LD_BUILD_DATE << std::endl; // if only the version should be shown, we can exit now if (showVersion) return 0; // set verbosity if (verbosity) { ldLog::setVerbosity((LD_LOGLEVEL) verbosity.Get()); } auto foundPlugins = linuxdeploy::plugin::findPlugins(); if (listPlugins) { ldLog() << "Available plugins:" << std::endl; for (const auto& plugin : foundPlugins) { ldLog() << plugin.first << LD_NO_SPACE << ":" << plugin.second->path() << "(type:" << plugin.second->pluginTypeString() << LD_NO_SPACE << "," << "API level:" << plugin.second->apiLevel() << LD_NO_SPACE << ")" << std::endl; } return 0; } if (!appDirPath) { ldLog() << LD_ERROR << "--appdir parameter required" << std::endl; std::cerr << std::endl << parser; return 1; } appdir::AppDir appDir(appDirPath.Get()); // allow disabling copyright files deployment via environment variable if (getenv("DISABLE_COPYRIGHT_FILES_DEPLOYMENT") != nullptr) { ldLog() << std::endl << LD_WARNING << "Copyright files deployment disabled" << std::endl; appDir.setDisableCopyrightFilesDeployment(true); } // initialize AppDir with common directories ldLog() << std::endl << "-- Creating basic AppDir structure --" << std::endl; if (!appDir.createBasicStructure()) { ldLog() << LD_ERROR << "Failed to create basic AppDir structure" << std::endl; return 1; } ldLog() << std::endl << "-- Deploying dependencies for existing files in AppDir --" << std::endl; if (!appDir.deployDependenciesForExistingFiles()) { ldLog() << LD_ERROR << "Failed to deploy dependencies for existing files" << std::endl; return 1; } // deploy shared libraries to usr/lib, and deploy their dependencies to usr/lib if (sharedLibraryPaths) { ldLog() << std::endl << "-- Deploying shared libraries --" << std::endl; for (const auto& libraryPath : sharedLibraryPaths.Get()) { if (!bf::exists(libraryPath)) { ldLog() << LD_ERROR << "No such file or directory: " << libraryPath << std::endl; return 1; } if (!appDir.forceDeployLibrary(libraryPath)) { ldLog() << LD_ERROR << "Failed to deploy library: " << libraryPath << std::endl; return 1; } } } // deploy executables to usr/bin, and deploy their dependencies to usr/lib if (executablePaths) { ldLog() << std::endl << "-- Deploying executables --" << std::endl; for (const auto& executablePath : executablePaths.Get()) { if (!bf::exists(executablePath)) { ldLog() << LD_ERROR << "No such file or directory: " << executablePath << std::endl; return 1; } if (!appDir.deployExecutable(executablePath)) { ldLog() << LD_ERROR << "Failed to deploy executable: " << executablePath << std::endl; return 1; } } } // deploy executables to usr/bin, and deploy their dependencies to usr/lib if (deployDepsOnlyPaths) { ldLog() << std::endl << "-- Deploying dependencies only for ELF files --" << std::endl; for (const auto& path : deployDepsOnlyPaths.Get()) { if (bf::is_directory(path)) { ldLog() << "Deploying files in directory" << path << std::endl; for (auto it = bf::directory_iterator{path}; it != bf::directory_iterator{}; ++it) { if (!bf::is_regular_file(*it)) { continue; } if (!appDir.deployDependenciesOnlyForElfFile(*it, true)) { ldLog() << LD_WARNING << "Failed to deploy dependencies for ELF file" << *it << LD_NO_SPACE << ", skipping" << std::endl; return 1; } } } else if (bf::is_regular_file(path)) { if (!appDir.deployDependenciesOnlyForElfFile(path)) { ldLog() << LD_ERROR << "Failed to deploy dependencies for ELF file: " << path << std::endl; return 1; } } else { ldLog() << LD_ERROR << "No such file or directory: " << path << std::endl; return 1; } } } // perform deferred copy operations before running input plugins to make sure all files the plugins might expect // are in place ldLog() << std::endl << "-- Copying files into AppDir --" << std::endl; if (!appDir.executeDeferredOperations()) { return 1; } // run input plugins before deploying icons and desktop files // the input plugins might even fetch these resources somewhere into the AppDir, and this way, the user can make use of that if (inputPlugins) { for (const auto& pluginName : inputPlugins.Get()) { auto it = foundPlugins.find(std::string(pluginName)); ldLog() << std::endl << "-- Running input plugin:" << pluginName << "--" << std::endl; if (it == foundPlugins.end()) { ldLog() << LD_ERROR << "Could not find plugin:" << pluginName; return 1; } auto plugin = it->second; if (plugin->pluginType() != linuxdeploy::plugin::INPUT_TYPE) { if (plugin->pluginType() == linuxdeploy::plugin::OUTPUT_TYPE) { ldLog() << LD_ERROR << "Plugin" << pluginName << "is an output plugin, please use like --output" << pluginName << std::endl; } else { ldLog() << LD_ERROR << "Plugin" << pluginName << "has unkown type:" << plugin->pluginType() << std::endl; } return 1; } auto retcode = plugin->run(appDir.path()); if (retcode != 0) { ldLog() << LD_ERROR << "Failed to run plugin:" << pluginName << "(exit code:" << retcode << LD_NO_SPACE << ")" << std::endl; return 1; } } } if (iconPaths) { ldLog() << std::endl << "-- Deploying icons --" << std::endl; for (const auto& iconPath : iconPaths.Get()) { if (!bf::exists(iconPath)) { ldLog() << LD_ERROR << "No such file or directory: " << iconPath << std::endl; return 1; } bool iconDeployedSuccessfully; if (iconTargetFilename) { iconDeployedSuccessfully = appDir.deployIcon(iconPath, iconTargetFilename.Get()); } else { iconDeployedSuccessfully = appDir.deployIcon(iconPath); } if (!iconDeployedSuccessfully) { ldLog() << LD_ERROR << "Failed to deploy icon: " << iconPath << std::endl; return 1; } } } if (desktopFilePaths) { ldLog() << std::endl << "-- Deploying desktop files --" << std::endl; for (const auto& desktopFilePath : desktopFilePaths.Get()) { if (!bf::exists(desktopFilePath)) { ldLog() << LD_ERROR << "No such file or directory: " << desktopFilePath << std::endl; return 1; } desktopfile::DesktopFile desktopFile(desktopFilePath); if (!appDir.deployDesktopFile(desktopFile)) { ldLog() << LD_ERROR << "Failed to deploy desktop file: " << desktopFilePath << std::endl; return 1; } } } // perform deferred copy operations before creating other files here before trying to copy the files to the AppDir root ldLog() << std::endl << "-- Copying files into AppDir --" << std::endl; if (!appDir.executeDeferredOperations()) { return 1; } if (createDesktopFile) { if (!executablePaths) { ldLog() << LD_ERROR << "--create-desktop-file requires at least one executable to be passed" << std::endl; return 1; } ldLog() << std::endl << "-- Creating desktop file --" << std::endl; ldLog() << LD_WARNING << "Please beware the created desktop file is of low quality and should be edited or replaced before using it for production releases!" << std::endl; auto executableName = bf::path(executablePaths.Get().front()).filename().string(); auto desktopFilePath = appDir.path() / "usr/share/applications" / (executableName + ".desktop"); if (bf::exists(desktopFilePath)) { ldLog() << LD_WARNING << "Working on existing desktop file:" << desktopFilePath << std::endl; } else { ldLog() << "Creating new desktop file:" << desktopFilePath << std::endl; } desktopfile::DesktopFile desktopFile; if (!addDefaultKeys(desktopFile, executableName)) { ldLog() << LD_WARNING << "Tried to overwrite existing entries in desktop file:" << desktopFilePath << std::endl; } if (!desktopFile.save(desktopFilePath.string())) { ldLog() << LD_ERROR << "Failed to save desktop file:" << desktopFilePath << std::endl; return 1; } } if (!linuxdeploy::deployAppDirRootFiles(desktopFilePaths.Get(), customAppRunPath.Get(), appDir)) return 1; if (outputPlugins) { for (const auto& pluginName : outputPlugins.Get()) { auto it = foundPlugins.find(std::string(pluginName)); ldLog() << std::endl << "-- Running output plugin:" << pluginName << "--" << std::endl; if (it == foundPlugins.end()) { ldLog() << LD_ERROR << "Could not find plugin:" << pluginName; return 1; } auto plugin = it->second; if (plugin->pluginType() != linuxdeploy::plugin::OUTPUT_TYPE) { if (plugin->pluginType() == linuxdeploy::plugin::INPUT_TYPE) { ldLog() << LD_ERROR << "Plugin" << pluginName << "is an input plugin, please use like --plugin" << pluginName << std::endl; } else { ldLog() << LD_ERROR << "Plugin" << pluginName << "has unknown type:" << plugin->pluginType() << std::endl; } return 1; } auto retcode = plugin->run(appDir.path()); if (retcode != 0) { ldLog() << LD_ERROR << "Failed to run plugin:" << pluginName << "(exit code:" << retcode << LD_NO_SPACE << ")" << std::endl; return 1; } } } return 0; }