Files
linuxdeploy/src/main.cpp
2020-08-07 21:22:58 +02:00

351 lines
15 KiB
C++

// system headers
#include <glob.h>
#include <iostream>
// library headers
#include <args.hxx>
// 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<int> verbosity(parser, "verbosity", "Verbosity of log output (0 = debug, 1 = info (default), 2 = warning, 3 = error)", {'v', "verbosity"});
args::ValueFlag<std::string> appDirPath(parser, "appdir", "Path to target AppDir", {"appdir"});
args::ValueFlagList<std::string> sharedLibraryPaths(parser, "library", "Shared library to deploy", {'l', "library"});
args::ValueFlagList<std::string> executablePaths(parser, "executable", "Executable to deploy", {'e', "executable"});
args::ValueFlagList<std::string> 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<std::string> 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<std::string> iconPaths(parser, "icon file", "Icon to deploy", {'i', "icon-file"});
args::ValueFlag<std::string> iconTargetFilename(parser, "filename", "Filename all icons passed via -i should be renamed to", {"icon-filename"});
args::ValueFlag<std::string> 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<std::string> inputPlugins(parser, "name", "Input plugins to run (check whether they are available with --list-plugins)", {'p', "plugin"});
args::ValueFlagList<std::string> 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;
}