mirror of
https://github.com/audacity/linuxdeploy.git
synced 2025-12-11 05:46:48 -06:00
Initial commit
This commit is contained in:
commit
e598536173
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
cmake-build-*/
|
||||
*build*/
|
||||
.idea/
|
||||
9
.gitmodules
vendored
Normal file
9
.gitmodules
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
[submodule "lib/cpp-subprocess"]
|
||||
path = lib/cpp-subprocess
|
||||
url = https://github.com/arun11299/cpp-subprocess.git
|
||||
[submodule "lib/args"]
|
||||
path = lib/args
|
||||
url = https://github.com/Taywee/args.git
|
||||
[submodule "lib/cpp-feather-ini-parser"]
|
||||
path = lib/cpp-feather-ini-parser
|
||||
url = https://github.com/Turbine1991/cpp-feather-ini-parser.git
|
||||
13
CMakeLists.txt
Normal file
13
CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
|
||||
project(linuxdeploy C CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(LINUXDEPLOY_VERSION 0.1-alpha-1)
|
||||
add_definitions(-DLINUXDEPLOY_VERSION="${LINUXDEPLOY_VERSION}")
|
||||
|
||||
add_subdirectory(lib)
|
||||
|
||||
add_subdirectory(src)
|
||||
19
LICENSE.txt
Normal file
19
LICENSE.txt
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright 2018 TheAssassin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# linuxdeploy
|
||||
|
||||
AppDir creation and maintenance tool.
|
||||
|
||||
**More info will follow soon!**
|
||||
71
include/linuxdeploy/core/appdir.h
Normal file
71
include/linuxdeploy/core/appdir.h
Normal file
@ -0,0 +1,71 @@
|
||||
// system includes
|
||||
#include <string>
|
||||
|
||||
// library includes
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
// local includes
|
||||
#include "linuxdeploy/core/desktopfile.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace linuxdeploy {
|
||||
namespace core {
|
||||
namespace appdir {
|
||||
/*
|
||||
* Base class for AppDirs.
|
||||
*/
|
||||
class AppDir {
|
||||
private:
|
||||
// private data class pattern
|
||||
class PrivateData;
|
||||
PrivateData* d;
|
||||
|
||||
public:
|
||||
// default constructor
|
||||
// construct AppDir from given path
|
||||
// the directory will be created if it doesn't exist
|
||||
explicit AppDir(const boost::filesystem::path& path);
|
||||
|
||||
~AppDir();
|
||||
|
||||
// alternative constructor
|
||||
// shortcut for using a normal string instead of a path
|
||||
explicit AppDir(const std::string& path);
|
||||
|
||||
// creates basic directory structure of an AppDir in "FHS" mode
|
||||
bool createBasicStructure();
|
||||
|
||||
// deploy shared library
|
||||
bool deployLibrary(const boost::filesystem::path& path);
|
||||
|
||||
// deploy executable
|
||||
bool deployExecutable(const boost::filesystem::path& path);
|
||||
|
||||
// deploy desktop file
|
||||
bool deployDesktopFile(const desktopfile::DesktopFile& desktopFile);
|
||||
|
||||
// deploy icon
|
||||
bool deployIcon(const boost::filesystem::path& path);
|
||||
|
||||
// execute deferred copy operations
|
||||
bool executeDeferredOperations();
|
||||
|
||||
// return path to AppDir
|
||||
boost::filesystem::path path();
|
||||
|
||||
// create a list of all icon paths in the AppDir
|
||||
std::vector<boost::filesystem::path> deployedIconPaths();
|
||||
|
||||
// create a list of all executable paths in the AppDir
|
||||
std::vector<boost::filesystem::path> deployedExecutablePaths();
|
||||
|
||||
// create a list of all desktop file paths in the AppDir
|
||||
std::vector<desktopfile::DesktopFile> deployedDesktopFiles();
|
||||
|
||||
// create symlinks for AppRun, desktop file and icon in the AppDir root directory
|
||||
bool createLinksInAppDirRoot(const desktopfile::DesktopFile& desktopFile);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
71
include/linuxdeploy/core/desktopfile.h
Normal file
71
include/linuxdeploy/core/desktopfile.h
Normal file
@ -0,0 +1,71 @@
|
||||
// system includes
|
||||
|
||||
// library includes
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace linuxdeploy {
|
||||
namespace core {
|
||||
namespace desktopfile {
|
||||
/*
|
||||
* Parse and read desktop files.
|
||||
*/
|
||||
class DesktopFile {
|
||||
private:
|
||||
// private data class pattern
|
||||
class PrivateData;
|
||||
PrivateData* d;
|
||||
|
||||
public:
|
||||
// default constructor
|
||||
DesktopFile();
|
||||
|
||||
// construct from existing desktop file
|
||||
// file must exist
|
||||
explicit DesktopFile(const boost::filesystem::path& path);
|
||||
|
||||
// read desktop file
|
||||
// sets path associated with this file
|
||||
bool read(const boost::filesystem::path& path);
|
||||
|
||||
// get path associated with this file
|
||||
boost::filesystem::path path() const;
|
||||
|
||||
// sets the path associated with this desktop file
|
||||
// used to e.g., save the desktop file
|
||||
void setPath(const boost::filesystem::path& path);
|
||||
|
||||
// clear contents of desktop file
|
||||
void clear();
|
||||
|
||||
// save desktop file
|
||||
bool save() const;
|
||||
|
||||
// save desktop file to path
|
||||
// does not change path associated with desktop file
|
||||
bool save(const boost::filesystem::path& path) const;
|
||||
|
||||
// check if entry exists in given section and key
|
||||
bool entryExists(const std::string& section, const std::string& key) const;
|
||||
|
||||
// get key from desktop file
|
||||
// an std::string passed as value parameter will be populated with the contents
|
||||
// returns true (and populates value) if the key exists, false otherwise
|
||||
bool getEntry(const std::string& section, const std::string& key, std::string& value) const;
|
||||
|
||||
// add key to section in desktop file
|
||||
// the section will be created if it doesn't exist already
|
||||
// returns true if an existing key was overwritten, false otherwise
|
||||
bool setEntry(const std::string& section, const std::string& key, const std::string& value);
|
||||
|
||||
// create common application entries in desktop file
|
||||
// returns false if one of the keys exists and was left unmodified
|
||||
bool addDefaultKeys(const std::string& executableFileName);
|
||||
|
||||
// validate desktop file
|
||||
bool validate() const;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
40
include/linuxdeploy/core/elf.h
Normal file
40
include/linuxdeploy/core/elf.h
Normal file
@ -0,0 +1,40 @@
|
||||
// system includes
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
// library includes
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace linuxdeploy {
|
||||
namespace core {
|
||||
namespace elf {
|
||||
class ElfFile {
|
||||
private:
|
||||
class PrivateData;
|
||||
PrivateData* d;
|
||||
|
||||
public:
|
||||
explicit ElfFile(const boost::filesystem::path& path);
|
||||
~ElfFile();
|
||||
|
||||
public:
|
||||
// recursively trace dynamic library dependencies of a given ELF file
|
||||
// this works for both libraries and executables
|
||||
// the resulting vector consists of absolute paths to the libraries determined by the same methods a system's
|
||||
// linker would use
|
||||
std::vector<boost::filesystem::path> traceDynamicDependencies();
|
||||
|
||||
// fetch rpath stored in binary
|
||||
// it appears that according to the ELF standard, the rpath is ignored in libraries, therefore if the path
|
||||
// points to an executable, an empty string is returned
|
||||
std::string getRPath();
|
||||
|
||||
// set rpath in ELF file
|
||||
// returns true on success, false otherwise
|
||||
bool setRPath(const std::string& value);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
69
include/linuxdeploy/core/log.h
Normal file
69
include/linuxdeploy/core/log.h
Normal file
@ -0,0 +1,69 @@
|
||||
// system includes
|
||||
#include <iostream>
|
||||
|
||||
// library includes
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace linuxdeploy {
|
||||
namespace core {
|
||||
namespace log {
|
||||
enum LD_LOGLEVEL {
|
||||
LD_DEBUG = 0,
|
||||
LD_INFO,
|
||||
LD_WARNING,
|
||||
LD_ERROR
|
||||
};
|
||||
|
||||
enum LD_STREAM_CONTROL {
|
||||
LD_NOOP = 0,
|
||||
LD_NO_SPACE,
|
||||
};
|
||||
|
||||
class ldLog {
|
||||
private:
|
||||
// this is the type of std::cout
|
||||
typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
|
||||
|
||||
// this is the function signature of std::endl
|
||||
typedef CoutType& (* stdEndlType)(CoutType&);
|
||||
|
||||
private:
|
||||
static LD_LOGLEVEL verbosity;
|
||||
|
||||
private:
|
||||
bool prependSpace;
|
||||
bool logLevelSet;
|
||||
CoutType& stream = std::cout;
|
||||
|
||||
LD_LOGLEVEL currentLogLevel;
|
||||
|
||||
private:
|
||||
// advanced behavior
|
||||
ldLog(bool prependSpace, bool logLevelSet, LD_LOGLEVEL logLevel);
|
||||
|
||||
void checkPrependSpace();
|
||||
|
||||
bool checkVerbosity();
|
||||
|
||||
public:
|
||||
static void setVerbosity(LD_LOGLEVEL verbosity);
|
||||
|
||||
public:
|
||||
// public constructor
|
||||
// does not implement the advanced behavior -- see private constructors for that
|
||||
ldLog();
|
||||
|
||||
public:
|
||||
ldLog operator<<(const std::string& message);
|
||||
ldLog operator<<(const char* message);
|
||||
ldLog operator<<(const boost::filesystem::path& path);
|
||||
ldLog operator<<(const double val);
|
||||
ldLog operator<<(stdEndlType strm);
|
||||
ldLog operator<<(const LD_LOGLEVEL logLevel);
|
||||
ldLog operator<<(const LD_STREAM_CONTROL streamControl);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
57
include/linuxdeploy/core/util.h
Normal file
57
include/linuxdeploy/core/util.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace linuxdeploy {
|
||||
namespace core {
|
||||
namespace util {
|
||||
static inline bool ltrim(std::string& s, char to_trim = ' ') {
|
||||
// TODO: find more efficient way to check whether elements have been removed
|
||||
size_t initialLength = s.length();
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [to_trim](int ch) {
|
||||
return ch != to_trim;
|
||||
}));
|
||||
return s.length() < initialLength;
|
||||
}
|
||||
|
||||
static inline bool rtrim(std::string& s, char to_trim = ' ') {
|
||||
// TODO: find more efficient way to check whether elements have been removed
|
||||
auto initialLength = s.length();
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(), [to_trim](int ch) {
|
||||
return ch != to_trim;
|
||||
}).base(), s.end());
|
||||
return s.length() < initialLength;
|
||||
}
|
||||
|
||||
static inline bool trim(std::string& s, char to_trim = ' ') {
|
||||
// returns true if either modifies s
|
||||
auto ltrim_result = ltrim(s, to_trim);
|
||||
return rtrim(s, to_trim) && ltrim_result;
|
||||
}
|
||||
|
||||
static std::vector<std::string> split(const std::string& s, char delim = ' ') {
|
||||
std::vector<std::string> result;
|
||||
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
|
||||
while (std::getline(ss, item, delim)) {
|
||||
result.push_back(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<std::string> splitLines(const std::string& s) {
|
||||
return split(s, '\n');
|
||||
}
|
||||
|
||||
static inline std::string strLower(std::string s) {
|
||||
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
lib/CMakeLists.txt
Normal file
15
lib/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
add_library(subprocess INTERFACE)
|
||||
target_sources(subprocess INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/cpp-subprocess/subprocess.hpp)
|
||||
target_include_directories(subprocess INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/cpp-subprocess)
|
||||
|
||||
add_library(args INTERFACE)
|
||||
target_sources(args INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/args/args.hxx)
|
||||
target_include_directories(args INTERFACE args)
|
||||
|
||||
add_library(cpp-feather-ini-parser INTERFACE)
|
||||
target_sources(cpp-feather-ini-parser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/cpp-feather-ini-parser/INI.h)
|
||||
target_include_directories(cpp-feather-ini-parser INTERFACE cpp-feather-ini-parser)
|
||||
|
||||
add_executable(test_cpp_feather_ini_parser EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/cpp-feather-ini-parser/example/example.cpp)
|
||||
target_link_libraries(test_cpp_feather_ini_parser PRIVATE cpp-feather-ini-parser)
|
||||
add_test(test_cpp_feather_ini_parser test_cpp_feather_ini_parser)
|
||||
1
lib/args
Submodule
1
lib/args
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit bd0429e91f5bb140271870d5421e412bf78b9f31
|
||||
1
lib/cpp-feather-ini-parser
Submodule
1
lib/cpp-feather-ini-parser
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 2dff628f921047dbee4b1795f40875b06ae27b50
|
||||
1
lib/cpp-subprocess
Submodule
1
lib/cpp-subprocess
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 05c76a531180298a8404bdf5fccb71137c62cd76
|
||||
4
src/CMakeLists.txt
Normal file
4
src/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
# globally include own includes
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
|
||||
add_subdirectory(core)
|
||||
27
src/core/CMakeLists.txt
Normal file
27
src/core/CMakeLists.txt
Normal file
@ -0,0 +1,27 @@
|
||||
# 3.5 is required for imported boost targets
|
||||
# 3.6 is required for the PkgConfig module's IMPORTED_TARGET library feature
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
|
||||
# include headers to make CLion happy
|
||||
file(GLOB HEADERS ${PROJECT_SOURCE_DIR}/include/linuxdeploy/core/*.h)
|
||||
|
||||
find_package(Boost REQUIRED COMPONENTS filesystem regex)
|
||||
find_package(Threads)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(magick++ REQUIRED IMPORTED_TARGET Magick++)
|
||||
|
||||
message(STATUS "Generating excludelist")
|
||||
execute_process(
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/generate-excludelist.sh
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
add_library(core elf.cpp log.cpp appdir.cpp desktopfile.cpp ${HEADERS})
|
||||
target_link_libraries(core Boost::filesystem Boost::regex subprocess cpp-feather-ini-parser PkgConfig::magick++ ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_include_directories(core PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
add_executable(linuxdeploy main.cpp)
|
||||
target_link_libraries(linuxdeploy core args)
|
||||
|
||||
set_target_properties(linuxdeploy PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
538
src/core/appdir.cpp
Normal file
538
src/core/appdir.cpp
Normal file
@ -0,0 +1,538 @@
|
||||
// library headers
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <Magick++.h>
|
||||
#include <fnmatch.h>
|
||||
#include <dirent.h>
|
||||
#include <fts.h>
|
||||
#include <subprocess.hpp>
|
||||
|
||||
// local headers
|
||||
#include "linuxdeploy/core/appdir.h"
|
||||
#include "linuxdeploy/core/elf.h"
|
||||
#include "linuxdeploy/core/log.h"
|
||||
#include "linuxdeploy/core/util.h"
|
||||
#include "excludelist.h"
|
||||
|
||||
using namespace linuxdeploy::core;
|
||||
using namespace linuxdeploy::core::log;
|
||||
|
||||
namespace bf = boost::filesystem;
|
||||
|
||||
namespace linuxdeploy {
|
||||
namespace core {
|
||||
namespace appdir {
|
||||
class AppDir::PrivateData {
|
||||
public:
|
||||
bf::path appDirPath;
|
||||
std::map<bf::path, bf::path> copyOperations;
|
||||
std::vector<bf::path> setElfRPathOperations;
|
||||
|
||||
public:
|
||||
PrivateData() {
|
||||
this->copyOperations = {};
|
||||
};
|
||||
|
||||
public:
|
||||
// actually copy file
|
||||
// mimics cp command behavior
|
||||
bool copyFile(const bf::path& from, bf::path to) {
|
||||
ldLog() << "Copying file" << from << "to" << to << std::endl;
|
||||
|
||||
try {
|
||||
if (!to.parent_path().empty() && !bf::is_directory(to.parent_path()) && !bf::create_directories(to.parent_path())) {
|
||||
ldLog() << LD_ERROR << "Failed to create parent directory" << to.parent_path() << "for path" << to << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*(to.string().end() - 1) == '/' || bf::is_directory(to))
|
||||
to /= from.filename();
|
||||
|
||||
bf::copy_file(from, to, bf::copy_option::overwrite_if_exists);
|
||||
} catch (const bf::filesystem_error& e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// create symlink
|
||||
bool symlinkFile(bf::path target, bf::path symlink, const bool useRelativePath = true) {
|
||||
ldLog() << "Creating symlink for file" << target << "in/as" << symlink << std::endl;
|
||||
|
||||
/*try {
|
||||
if (!symlink.parent_path().empty() && !bf::is_directory(symlink.parent_path()) && !bf::create_directories(symlink.parent_path())) {
|
||||
ldLog() << LD_ERROR << "Failed to create parent directory" << symlink.parent_path() << "for path" << symlink << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*(symlink.string().end() - 1) == '/' || bf::is_directory(symlink))
|
||||
symlink /= target.filename();
|
||||
|
||||
if (bf::exists(symlink) || bf::symbolic_link_exists(symlink))
|
||||
bf::remove(symlink);
|
||||
|
||||
if (relativeDirectory != "") {
|
||||
// TODO
|
||||
}
|
||||
|
||||
bf::create_symlink(target, symlink);
|
||||
} catch (const bf::filesystem_error& e) {
|
||||
return false;
|
||||
}*/
|
||||
|
||||
if (!useRelativePath) {
|
||||
ldLog() << LD_ERROR << "Not implemented" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
subprocess::Popen proc({"ln", "-f", "-s", "--relative", target.c_str(), symlink.c_str()},
|
||||
subprocess::output(subprocess::PIPE),
|
||||
subprocess::error(subprocess::PIPE)
|
||||
);
|
||||
|
||||
auto outputs = proc.communicate();
|
||||
|
||||
if (proc.retcode() != 0) {
|
||||
ldLog() << LD_ERROR << "ln subprocess failed:" << std::endl
|
||||
<< outputs.first.buf << std::endl << outputs.second.buf << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool checkDuplicate(const bf::path& path) {
|
||||
// FIXME: use more efficient search (e.g., binary search)
|
||||
// a linear search is not _really_ efficient
|
||||
//return std::binary_search(copyOperations.begin(), copyOperations.end(), path);
|
||||
for (const auto& pair : copyOperations) {
|
||||
if (pair.first == path) {
|
||||
ldLog() << LD_DEBUG << "Duplicate:" << pair.first << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// execute deferred copy operations registered with the deploy* functions
|
||||
bool executeDeferredOperations() {
|
||||
bool success = true;
|
||||
|
||||
while (!copyOperations.empty()) {
|
||||
const auto& pair = *(copyOperations.begin());
|
||||
const auto& from = pair.first;
|
||||
const auto& to = pair.second;
|
||||
|
||||
if (!copyFile(from, to)) {
|
||||
ldLog() << LD_ERROR << "Failed to copy file" << from << "to" << to << std::endl;
|
||||
success = false;
|
||||
}
|
||||
|
||||
copyOperations.erase(copyOperations.begin());
|
||||
}
|
||||
|
||||
if (success) {
|
||||
while (!setElfRPathOperations.empty()) {
|
||||
static const auto rpath = "$ORIGIN/../lib";
|
||||
const auto& elfFilePath = *(setElfRPathOperations.begin());
|
||||
|
||||
ldLog() << "Setting rpath in ELF file" << elfFilePath << "to" << rpath << std::endl;
|
||||
if (!elf::ElfFile(elfFilePath).setRPath(rpath)) {
|
||||
ldLog() << LD_ERROR << "Failed to set rpath in ELF file:" << elfFilePath << std::endl;
|
||||
success = false;
|
||||
}
|
||||
|
||||
setElfRPathOperations.erase(setElfRPathOperations.begin());
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// register copy operation that will be executed later
|
||||
// by compiling a list of files to copy instead of just copying everything, one can ensure that
|
||||
// the files are touched once only
|
||||
void deployFile(const bf::path& from, bf::path to) {
|
||||
ldLog() << LD_DEBUG << "Deploying file" << from << "to" << to << std::endl;
|
||||
|
||||
// not sure whether this is 100% bullet proof, but it simulates the cp command behavior
|
||||
if (to.string().back() == '/' || bf::is_directory(to)) {
|
||||
to /= from.filename();
|
||||
}
|
||||
|
||||
copyOperations[from] = to;
|
||||
}
|
||||
|
||||
bool deployElfDependencies(const bf::path& path) {
|
||||
ldLog() << "Deploying dependencies for ELF file" << path << std::endl;
|
||||
|
||||
for (const auto& dependencyPath : elf::ElfFile(path).traceDynamicDependencies()) {
|
||||
if (!deployLibrary(dependencyPath))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool deployLibrary(const bf::path& path) {
|
||||
if (checkDuplicate(path)) {
|
||||
ldLog() << LD_DEBUG << "Skipping duplicate deployment of shared library" << path << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
static auto isInExcludelist = [](const bf::path& fileName) {
|
||||
for (const auto& excludePattern : generatedExcludelist) {
|
||||
// simple string match is faster than using fnmatch
|
||||
if (excludePattern == fileName)
|
||||
return true;
|
||||
|
||||
auto fnmatchResult = fnmatch(excludePattern.c_str(), fileName.string().c_str(), FNM_PATHNAME);
|
||||
switch (fnmatchResult) {
|
||||
case 0:
|
||||
return true;
|
||||
case FNM_NOMATCH:
|
||||
break;
|
||||
default:
|
||||
ldLog() << LD_ERROR << "fnmatch() reported error:" << fnmatchResult << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (isInExcludelist(path.filename())) {
|
||||
ldLog() << "Skipping deployment of blacklisted library" << path << std::endl;
|
||||
return true;
|
||||
} else {
|
||||
ldLog() << "Deploying shared library" << path << std::endl;
|
||||
}
|
||||
|
||||
deployFile(path, appDirPath / "usr/lib/");
|
||||
|
||||
setElfRPathOperations.push_back(appDirPath / "usr/lib" / path.filename());
|
||||
|
||||
if (!deployElfDependencies(path))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool deployExecutable(const bf::path& path) {
|
||||
if (checkDuplicate(path)) {
|
||||
ldLog() << LD_DEBUG << "Skipping duplicate deployment of executable" << path << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
ldLog() << "Deploying executable" << path << std::endl;
|
||||
|
||||
// FIXME: make executables executable
|
||||
|
||||
deployFile(path, appDirPath / "usr/bin/");
|
||||
|
||||
setElfRPathOperations.push_back(appDirPath / "usr/bin" / path.filename());
|
||||
|
||||
if (!deployElfDependencies(path))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool deployDesktopFile(const desktopfile::DesktopFile& desktopFile) {
|
||||
if (checkDuplicate(desktopFile.path())) {
|
||||
ldLog() << LD_DEBUG << "Skipping duplicate deployment of desktop file" << desktopFile.path() << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (desktopFile.validate()) {
|
||||
ldLog() << LD_ERROR << "Failed to verify desktop file:" << desktopFile.path() << std::endl;
|
||||
}
|
||||
|
||||
ldLog() << "Deploying desktop file" << desktopFile.path() << std::endl;
|
||||
|
||||
deployFile(desktopFile.path(), appDirPath / "usr/share/applications/");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool deployIcon(const bf::path& path) {
|
||||
if (checkDuplicate(path)) {
|
||||
ldLog() << LD_DEBUG << "Skipping duplicate deployment of icon" << path << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
ldLog() << "Deploying icon" << path << std::endl;
|
||||
|
||||
Magick::Image image;
|
||||
|
||||
try {
|
||||
image.read(path.string());
|
||||
} catch (const Magick::Exception& error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto xRes = image.columns();
|
||||
auto yRes = image.rows();
|
||||
|
||||
if (xRes != yRes) {
|
||||
ldLog() << LD_WARNING << "x and y resolution of icon are not equal:" << path;
|
||||
}
|
||||
|
||||
auto resolution = std::to_string(xRes) + "x" + std::to_string(yRes);
|
||||
|
||||
auto format = image.format();
|
||||
|
||||
// if file is a vector image, use "scalable" directory
|
||||
if (util::strLower(bf::extension(path)) == "svg") {
|
||||
resolution = "scalable";
|
||||
} else {
|
||||
// otherwise, test resolution against "known good" values, and reject invalid ones
|
||||
const auto knownResolutions = {8, 16, 20, 22, 24, 32, 48, 64, 72, 96, 128, 192, 256, 512};
|
||||
|
||||
// assume invalid
|
||||
bool invalidXRes = true, invalidYRes = true;
|
||||
|
||||
for (const auto res : knownResolutions) {
|
||||
if (xRes == res)
|
||||
invalidXRes = false;
|
||||
if (yRes == res)
|
||||
invalidYRes = false;
|
||||
}
|
||||
|
||||
if (invalidXRes) {
|
||||
ldLog() << LD_ERROR << "Icon" << path << "has invalid x resolution:" << xRes;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (invalidYRes) {
|
||||
ldLog() << LD_ERROR << "Icon" << path << "has invalid x resolution:" << xRes;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
deployFile(path, appDirPath / "usr/share/icons/hicolor" / resolution / "apps/");
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
AppDir::AppDir(const bf::path& path) {
|
||||
d = new PrivateData();
|
||||
|
||||
d->appDirPath = path;
|
||||
}
|
||||
|
||||
AppDir::~AppDir() {
|
||||
delete d;
|
||||
}
|
||||
|
||||
AppDir::AppDir(const std::string& path) : AppDir(bf::path(path)) {}
|
||||
|
||||
bool AppDir::createBasicStructure() {
|
||||
std::vector<std::string> dirPaths = {
|
||||
"usr/bin/",
|
||||
"usr/lib/",
|
||||
"usr/share/applications/",
|
||||
"usr/share/icons/hicolor/",
|
||||
};
|
||||
|
||||
for (const std::string& resolution : {"16x16", "32x32", "64x64", "128x128", "256x256", "scalable"}) {
|
||||
auto iconPath = "usr/share/icons/hicolor/" + resolution + "/apps/";
|
||||
dirPaths.push_back(iconPath);
|
||||
}
|
||||
|
||||
for (const auto& dirPath : dirPaths) {
|
||||
auto fullDirPath = d->appDirPath / dirPath;
|
||||
|
||||
ldLog() << "Creating directory" << fullDirPath << std::endl;
|
||||
|
||||
// skip directory if it exists
|
||||
if (bf::is_directory(fullDirPath))
|
||||
continue;
|
||||
|
||||
try {
|
||||
bf::create_directories(fullDirPath);
|
||||
} catch (const bf::filesystem_error&) {
|
||||
ldLog() << LD_ERROR << "Failed to create directory" << fullDirPath;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppDir::deployLibrary(const bf::path& path) {
|
||||
return d->deployLibrary(path);
|
||||
}
|
||||
|
||||
bool AppDir::deployExecutable(const bf::path& path) {
|
||||
return d->deployExecutable(path);
|
||||
}
|
||||
|
||||
bool AppDir::deployDesktopFile(const desktopfile::DesktopFile& desktopFile) {
|
||||
return d->deployDesktopFile(desktopFile);
|
||||
}
|
||||
|
||||
bool AppDir::deployIcon(const bf::path& path) {
|
||||
return d->deployIcon(path);
|
||||
}
|
||||
|
||||
bool AppDir::executeDeferredOperations() {
|
||||
return d->executeDeferredOperations();
|
||||
}
|
||||
|
||||
boost::filesystem::path AppDir::path() {
|
||||
return d->appDirPath;
|
||||
}
|
||||
|
||||
static std::vector<bf::path> listFilesInDirectory(const bf::path& path, const bool recursive = true) {
|
||||
std::vector<bf::path> foundPaths;
|
||||
|
||||
std::vector<char> pathBuf(path.string().size() + 1, '\0');
|
||||
strcpy(pathBuf.data(), path.string().c_str());
|
||||
|
||||
std::vector<char*> searchPaths = {pathBuf.data(), nullptr};
|
||||
|
||||
if (recursive) {
|
||||
// reset errno
|
||||
errno = 0;
|
||||
|
||||
auto* fts = fts_open(searchPaths.data(), FTS_NOCHDIR | FTS_NOSTAT, nullptr);
|
||||
|
||||
int error = errno;
|
||||
if (fts == nullptr || error != 0) {
|
||||
ldLog() << LD_ERROR << "fts() failed:" << strerror(error) << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
FTSENT* ent;
|
||||
while ((ent = fts_read(fts)) != nullptr) {
|
||||
// FIXME: use ent's fts_info member instead of boost::filesystem
|
||||
if (bf::is_regular_file(ent->fts_path)) {
|
||||
foundPaths.push_back(ent->fts_path);
|
||||
}
|
||||
};
|
||||
error = errno;
|
||||
if (error != 0) {
|
||||
ldLog() << LD_ERROR << "fts_read() failed:" << strerror(error) << std::endl;
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
DIR* dir;
|
||||
if ((dir = opendir(path.string().c_str())) == NULL) {
|
||||
auto error = errno;
|
||||
ldLog() << LD_ERROR << "opendir() failed:" << strerror(error);
|
||||
}
|
||||
|
||||
struct dirent* ent;
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
auto fullPath = path / bf::path(ent->d_name);
|
||||
if (bf::is_regular_file(fullPath)) {
|
||||
foundPaths.push_back(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundPaths;
|
||||
}
|
||||
|
||||
std::vector<bf::path> AppDir::deployedIconPaths() {
|
||||
return listFilesInDirectory(path() / "/usr/share/icons/");
|
||||
}
|
||||
|
||||
std::vector<bf::path> AppDir::deployedExecutablePaths() {
|
||||
auto paths = listFilesInDirectory(path() / "usr/bin/", false);
|
||||
|
||||
paths.erase(std::remove_if(paths.begin(), paths.end(), [](const bf::path& path) {
|
||||
return !bf::is_regular_file(path);
|
||||
}), paths.end());
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::vector<desktopfile::DesktopFile> AppDir::deployedDesktopFiles() {
|
||||
std::vector<desktopfile::DesktopFile> desktopFiles;
|
||||
|
||||
auto paths = listFilesInDirectory(path() / "usr/share/applications/", false);
|
||||
paths.erase(std::remove_if(paths.begin(), paths.end(), [](const bf::path& path) {
|
||||
return path.extension() != ".desktop";
|
||||
}), paths.end());
|
||||
|
||||
for (const auto& path : paths) {
|
||||
desktopFiles.push_back(desktopfile::DesktopFile(path));
|
||||
}
|
||||
|
||||
return desktopFiles;
|
||||
}
|
||||
|
||||
bool AppDir::createLinksInAppDirRoot(const desktopfile::DesktopFile& desktopFile) {
|
||||
ldLog() << "Deploying desktop file to AppDir root:" << desktopFile.path() << std::endl;
|
||||
|
||||
// copy desktop file to root directory
|
||||
if (!d->symlinkFile(desktopFile.path(), path())) {
|
||||
ldLog() << LD_ERROR << "Failed to create link to desktop file in AppDir root:" << desktopFile.path() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// look for suitable icon
|
||||
std::string iconName;
|
||||
|
||||
if (!desktopFile.getEntry("Desktop Entry", "Icon", iconName)) {
|
||||
ldLog() << LD_ERROR << "Icon entry missing in desktop file:" << desktopFile.path() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto foundIconPaths = deployedIconPaths();
|
||||
|
||||
if (foundIconPaths.empty()) {
|
||||
ldLog() << LD_ERROR << "Could not find suitable executable for Exec entry:" << iconName << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& iconPath : foundIconPaths) {
|
||||
ldLog() << LD_DEBUG << "Icon found:" << iconPath << std::endl;
|
||||
|
||||
if (iconPath.stem() == iconName) {
|
||||
ldLog() << "Deploying icon to AppDir root:" << iconPath << std::endl;
|
||||
|
||||
if (!d->symlinkFile(iconPath, path())) {
|
||||
ldLog() << LD_ERROR << "Failed to create symlink for icon in AppDir root:" << iconPath << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// look for suitable binary to create AppRun symlink
|
||||
std::string executableName;
|
||||
|
||||
if (!desktopFile.getEntry("Desktop Entry", "Exec", executableName)) {
|
||||
ldLog() << LD_ERROR << "Exec entry missing in desktop file:" << desktopFile.path() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto foundExecutablePaths = deployedExecutablePaths();
|
||||
|
||||
if (foundExecutablePaths.empty()) {
|
||||
ldLog() << LD_ERROR << "Could not find suitable executable for Exec entry:" << iconName << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& executablePath : foundExecutablePaths) {
|
||||
ldLog() << LD_DEBUG << "Executable found:" << executablePath << std::endl;
|
||||
|
||||
if (executablePath.stem() == iconName) {
|
||||
ldLog() << "Deploying AppRun symlink for executable in AppDir root:" << executablePath << std::endl;
|
||||
|
||||
if (!d->symlinkFile(executablePath, path() / "AppRun")) {
|
||||
ldLog() << LD_ERROR << "Failed to create AppRun symlink for executable in AppDir root:" << executablePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
130
src/core/desktopfile.cpp
Normal file
130
src/core/desktopfile.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
// library headers
|
||||
#include <INI.h>
|
||||
|
||||
// local headers
|
||||
#include "linuxdeploy/core/desktopfile.h"
|
||||
#include "linuxdeploy/core/log.h"
|
||||
|
||||
using namespace linuxdeploy::core;
|
||||
using namespace linuxdeploy::core::log;
|
||||
|
||||
namespace bf = boost::filesystem;
|
||||
|
||||
namespace linuxdeploy {
|
||||
namespace core {
|
||||
namespace desktopfile {
|
||||
class DesktopFile::PrivateData {
|
||||
public:
|
||||
bf::path path;
|
||||
INI<> ini;
|
||||
|
||||
public:
|
||||
PrivateData() : path(), ini("", false) {};
|
||||
};
|
||||
|
||||
DesktopFile::DesktopFile() {
|
||||
d = new PrivateData();
|
||||
}
|
||||
|
||||
DesktopFile::DesktopFile(const bf::path& path) : DesktopFile() {
|
||||
if (!read(path))
|
||||
throw std::runtime_error("Failed to read desktop file");
|
||||
};
|
||||
|
||||
bool DesktopFile::read(const boost::filesystem::path& path) {
|
||||
setPath(path);
|
||||
|
||||
clear();
|
||||
|
||||
// nothing to do
|
||||
if (!bf::exists(path))
|
||||
return true;
|
||||
|
||||
std::ifstream ifs(path.string());
|
||||
if (!ifs)
|
||||
return false;
|
||||
|
||||
d->ini.parse(ifs);
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::filesystem::path DesktopFile::path() const {
|
||||
return d->path;
|
||||
}
|
||||
|
||||
void DesktopFile::setPath(const boost::filesystem::path& path) {
|
||||
d->path = path;
|
||||
}
|
||||
|
||||
void DesktopFile::clear() {
|
||||
d->ini.clear();
|
||||
}
|
||||
|
||||
bool DesktopFile::save() const {
|
||||
return save(d->path);
|
||||
}
|
||||
|
||||
bool DesktopFile::save(const boost::filesystem::path& path) const {
|
||||
return d->ini.save(path.string());
|
||||
}
|
||||
|
||||
bool DesktopFile::entryExists(const std::string& section, const std::string& key) const {
|
||||
if (!d->ini.select(section))
|
||||
return false;
|
||||
|
||||
std::string absolutelyUnlikeValue = "<>!§$%&/()=?+'#-.,_:;'*¹²³½¬{[]}^°|";
|
||||
|
||||
auto value = d->ini.get<std::string, std::string, std::string>(section, key, absolutelyUnlikeValue);
|
||||
|
||||
return value != absolutelyUnlikeValue;
|
||||
}
|
||||
|
||||
bool DesktopFile::setEntry(const std::string& section, const std::string& key, const std::string& value) {
|
||||
// check if value exists -- used for return value
|
||||
auto rv = entryExists(section, key);
|
||||
|
||||
// set key
|
||||
d->ini[section][key] = value;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool DesktopFile::getEntry(const std::string& section, const std::string& key, std::string& value) const {
|
||||
if (!entryExists(section, key))
|
||||
return false;
|
||||
|
||||
if (!d->ini.select(section))
|
||||
return false;
|
||||
|
||||
value = d->ini.get(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DesktopFile::addDefaultKeys(const std::string& executableFileName) {
|
||||
auto rv = true;
|
||||
|
||||
auto setDefault = [&rv, this](const std::string& section, const std::string& key, const std::string& value) {
|
||||
if (setEntry(section, key, value)) {
|
||||
rv = false;
|
||||
}
|
||||
};
|
||||
|
||||
setDefault("Desktop Entry", "Name", executableFileName);
|
||||
setDefault("Desktop Entry", "Exec", executableFileName);
|
||||
setDefault("Desktop Entry", "Icon", executableFileName);
|
||||
setDefault("Desktop Entry", "Type", "Application");
|
||||
setDefault("Desktop Entry", "Categories", "Utility;");
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool DesktopFile::validate() const {
|
||||
// FIXME: call desktop-file-validate
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
124
src/core/elf.cpp
Normal file
124
src/core/elf.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
// library includes
|
||||
#include <boost/regex.hpp>
|
||||
#include <subprocess.hpp>
|
||||
|
||||
// local headers
|
||||
#include "linuxdeploy/core/elf.h"
|
||||
#include "linuxdeploy/core/log.h"
|
||||
#include "linuxdeploy/core/util.h"
|
||||
|
||||
using namespace linuxdeploy::core::log;
|
||||
|
||||
namespace bf = boost::filesystem;
|
||||
|
||||
namespace linuxdeploy {
|
||||
namespace core {
|
||||
namespace elf {
|
||||
class ElfFile::PrivateData {
|
||||
public:
|
||||
const bf::path path;
|
||||
|
||||
public:
|
||||
explicit PrivateData(const bf::path& path) : path(path) {}
|
||||
};
|
||||
|
||||
ElfFile::ElfFile(const boost::filesystem::path& path) {
|
||||
d = new PrivateData(path);
|
||||
}
|
||||
|
||||
ElfFile::~ElfFile() {
|
||||
delete d;
|
||||
}
|
||||
|
||||
std::vector<bf::path> ElfFile::traceDynamicDependencies() {
|
||||
// this method's purpose is to abstract this process
|
||||
// the caller doesn't care _how_ it's done, after all
|
||||
|
||||
// for now, we use the same ldd based method linuxdeployqt uses
|
||||
|
||||
std::vector<bf::path> paths;
|
||||
|
||||
subprocess::Popen lddProc(
|
||||
{"ldd", d->path.string().c_str()},
|
||||
subprocess::output{subprocess::PIPE},
|
||||
subprocess::error{subprocess::PIPE}
|
||||
);
|
||||
|
||||
auto lddOutput = lddProc.communicate();
|
||||
auto& lddStdout = lddOutput.first;
|
||||
auto& lddStderr = lddOutput.second;
|
||||
|
||||
if (lddProc.retcode() != 0) {
|
||||
ldLog() << LD_ERROR << "Call to ldd failed:" << std::endl << lddStderr.buf.data() << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string lddStdoutContents(lddStdout.buf.data());
|
||||
|
||||
const boost::regex expr(R"(\s*(.+)\s+\=>\s+(.+)\s+\((.+)\)\s*)");
|
||||
boost::smatch what;
|
||||
|
||||
for (const auto& line : util::splitLines(lddStdoutContents)) {
|
||||
if (boost::regex_search(line, what, expr)) {
|
||||
auto libraryPath = what[2].str();
|
||||
util::trim(libraryPath);
|
||||
paths.push_back(bf::absolute(libraryPath));
|
||||
} else {
|
||||
ldLog() << LD_DEBUG << "Invalid ldd output: " << line << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::string ElfFile::getRPath() {
|
||||
subprocess::Popen patchelfProc(
|
||||
{"patchelf", "--print-rpath", d->path.c_str()},
|
||||
subprocess::output(subprocess::PIPE),
|
||||
subprocess::error(subprocess::PIPE)
|
||||
);
|
||||
|
||||
auto patchelfOutput = patchelfProc.communicate();
|
||||
auto& patchelfStdout = patchelfOutput.first;
|
||||
auto& patchelfStderr = patchelfOutput.second;
|
||||
|
||||
if (patchelfProc.retcode() != 0) {
|
||||
std::string errStr(patchelfStderr.buf.data());
|
||||
|
||||
// if file is not an ELF executable, there is no need for a detailed error message
|
||||
if (patchelfProc.retcode() == 1 && errStr.find("not an ELF executable")) {
|
||||
return "";
|
||||
} else {
|
||||
ldLog() << LD_ERROR << "Call to patchelf failed:" << std::endl << errStr;
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string retval = patchelfStdout.buf.data();
|
||||
util::trim(retval, '\n');
|
||||
util::trim(retval);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool ElfFile::setRPath(const std::string& value) {
|
||||
subprocess::Popen patchelfProc(
|
||||
{"patchelf", "--set-rpath", value.c_str(), d->path.c_str()},
|
||||
subprocess::output(subprocess::PIPE),
|
||||
subprocess::error(subprocess::PIPE)
|
||||
);
|
||||
|
||||
auto patchelfOutput = patchelfProc.communicate();
|
||||
auto& patchelfStdout = patchelfOutput.first;
|
||||
auto& patchelfStderr = patchelfOutput.second;
|
||||
|
||||
if (patchelfProc.retcode() != 0) {
|
||||
ldLog() << LD_ERROR << "Call to patchelf failed:" << std::endl << patchelfStderr.buf;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/core/generate-excludelist.sh
Normal file
51
src/core/generate-excludelist.sh
Normal file
@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2018 Alexander Gottwald (https://github.com/ago1024)
|
||||
# Copyright 2018 TheAssassin (https://github.com/TheAssassin)
|
||||
#
|
||||
# Dual-licensed under the terms of the GPLv3 and LGPL v3 licenses as part of
|
||||
# linuxdeployqt (https://github.com/probonopd/linuxdeployqt).
|
||||
#
|
||||
# Changed to use C++ standard library containers instead of Qt ones.
|
||||
|
||||
set -e
|
||||
|
||||
# download excludelist
|
||||
blacklisted=($(wget --quiet https://raw.githubusercontent.com/probonopd/AppImages/master/excludelist -O - | sort | uniq | grep -v "^#.*" | grep "[^-\s]"))
|
||||
|
||||
# sanity check
|
||||
if [ "$blacklisted" == "" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
filename=excludelist.h
|
||||
|
||||
# overwrite existing source file
|
||||
cat > "$filename" <<EOF
|
||||
/*
|
||||
* List of libraries to exclude for different reasons.
|
||||
*
|
||||
* Automatically generated from
|
||||
* https://raw.githubusercontent.com/probonopd/AppImages/master/excludelist
|
||||
*
|
||||
* This file shall be committed by the developers occassionally,
|
||||
* otherwise systems without access to the internet won't be able to build
|
||||
* fully working versions of linuxdeployqt.
|
||||
*
|
||||
* See https://github.com/probonopd/linuxdeployqt/issues/274 for more
|
||||
* information.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static const std::vector<std::string> generatedExcludelist = {
|
||||
EOF
|
||||
|
||||
# Create array
|
||||
for item in ${blacklisted[@]:0:${#blacklisted[@]}-1}; do
|
||||
echo -e ' "'"$item"'",' >> "$filename"
|
||||
done
|
||||
echo -e ' "'"${blacklisted[$((${#blacklisted[@]}-1))]}"'"' >> "$filename"
|
||||
|
||||
echo "};" >> "$filename"
|
||||
119
src/core/log.cpp
Normal file
119
src/core/log.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
// local includes
|
||||
#include "linuxdeploy/core/log.h"
|
||||
|
||||
namespace linuxdeploy {
|
||||
namespace core {
|
||||
namespace log {
|
||||
LD_LOGLEVEL ldLog::verbosity = LD_INFO;
|
||||
|
||||
void ldLog::setVerbosity(LD_LOGLEVEL verbosity) {
|
||||
ldLog::verbosity = verbosity;
|
||||
}
|
||||
|
||||
ldLog::ldLog() {
|
||||
prependSpace = false;
|
||||
currentLogLevel = LD_INFO;
|
||||
logLevelSet = false;
|
||||
};
|
||||
|
||||
ldLog::ldLog(bool prependSpace, bool logLevelSet, LD_LOGLEVEL logLevel) {
|
||||
this->prependSpace = prependSpace;
|
||||
this->currentLogLevel = logLevel;
|
||||
this->logLevelSet = logLevelSet;
|
||||
}
|
||||
|
||||
void ldLog::checkPrependSpace() {
|
||||
if (prependSpace) {
|
||||
stream << " ";
|
||||
prependSpace = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ldLog::checkVerbosity() {
|
||||
// std::cerr << "current: " << currentLogLevel << " verbosity: " << verbosity << std::endl;
|
||||
return (currentLogLevel >= verbosity);
|
||||
}
|
||||
|
||||
ldLog ldLog::operator<<(const std::string& message) {
|
||||
if (checkVerbosity()) {
|
||||
checkPrependSpace();
|
||||
stream << message;
|
||||
}
|
||||
|
||||
return ldLog(true, logLevelSet, currentLogLevel);
|
||||
}
|
||||
ldLog ldLog::operator<<(const char* message) {
|
||||
if (checkVerbosity()) {
|
||||
checkPrependSpace();
|
||||
stream << message;
|
||||
}
|
||||
|
||||
return ldLog(true, logLevelSet, currentLogLevel);
|
||||
}
|
||||
|
||||
ldLog ldLog::operator<<(const boost::filesystem::path& path) {
|
||||
if (checkVerbosity()) {
|
||||
checkPrependSpace();
|
||||
stream << path.string();
|
||||
}
|
||||
|
||||
return ldLog(true, logLevelSet, currentLogLevel);
|
||||
}
|
||||
|
||||
ldLog ldLog::operator<<(const double val) {
|
||||
return ldLog::operator<<(std::to_string(val));
|
||||
}
|
||||
|
||||
ldLog ldLog::operator<<(stdEndlType strm) {
|
||||
if (checkVerbosity()) {
|
||||
checkPrependSpace();
|
||||
stream << strm;
|
||||
}
|
||||
|
||||
return ldLog(false, logLevelSet, currentLogLevel);
|
||||
}
|
||||
|
||||
ldLog ldLog::operator<<(const LD_LOGLEVEL logLevel) {
|
||||
if (logLevelSet) {
|
||||
throw std::runtime_error(
|
||||
"log level must be first element passed via the stream insertion operator");
|
||||
}
|
||||
|
||||
logLevelSet = true;
|
||||
currentLogLevel = logLevel;
|
||||
|
||||
if (checkVerbosity()) {
|
||||
switch (logLevel) {
|
||||
case LD_DEBUG:
|
||||
stream << "DEBUG: ";
|
||||
break;
|
||||
case LD_WARNING:
|
||||
stream << "WARNING: ";
|
||||
break;
|
||||
case LD_ERROR:
|
||||
stream << "ERROR: ";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ldLog(false, logLevelSet, currentLogLevel);
|
||||
}
|
||||
|
||||
ldLog ldLog::operator<<(const LD_STREAM_CONTROL streamControl) {
|
||||
bool prependSpace = true;
|
||||
|
||||
switch (streamControl) {
|
||||
case LD_NO_SPACE:
|
||||
prependSpace = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ldLog(prependSpace, logLevelSet, currentLogLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
204
src/core/main.cpp
Normal file
204
src/core/main.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
// system headers
|
||||
#include <glob.h>
|
||||
#include <iostream>
|
||||
|
||||
// library headers
|
||||
#include <args.hxx>
|
||||
|
||||
// local headers
|
||||
#include "linuxdeploy/core/appdir.h"
|
||||
#include "linuxdeploy/core/desktopfile.h"
|
||||
#include "linuxdeploy/core/elf.h"
|
||||
#include "linuxdeploy/core/log.h"
|
||||
|
||||
using namespace linuxdeploy::core;
|
||||
using namespace linuxdeploy::core::log;
|
||||
|
||||
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, 2 = warning, 3 = error)", {'v', "verbosity"});
|
||||
|
||||
args::Flag initAppDir(parser, "", "Create basic AppDir structure", {"init-appdir"});
|
||||
args::ValueFlag<std::string> appDirPath(parser, "appdir", "Path to target AppDir", {"appdir"});
|
||||
args::ValueFlag<std::string> appName(parser, "app-name", "Application name (used to initialize desktop file and name icons etc.)", {'n', "app-name"});
|
||||
|
||||
args::ValueFlagList<std::string> sharedLibraryPaths(parser, "library", "Shared library to deploy", {'l', "lib", "library"});
|
||||
|
||||
args::ValueFlagList<std::string> executablePaths(parser, "executable", "Executable to deploy", {'e', "executable"});
|
||||
|
||||
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"});
|
||||
|
||||
try {
|
||||
parser.ParseCLI(argc, argv);
|
||||
} catch (args::Help&) {
|
||||
std::cerr << parser;
|
||||
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 " << LINUXDEPLOY_VERSION << std::endl;
|
||||
|
||||
// set verbosity
|
||||
if (verbosity) {
|
||||
ldLog::setVerbosity((LD_LOGLEVEL) verbosity.Get());
|
||||
}
|
||||
|
||||
if (showVersion)
|
||||
return 0;
|
||||
|
||||
if (!appDirPath) {
|
||||
std::cerr << "--appdir parameter required" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
appdir::AppDir appDir(appDirPath.Get());
|
||||
|
||||
if (appName) {
|
||||
ldLog() << std::endl << "-- Deploying application \"" << LD_NO_SPACE << appName.Get() << LD_NO_SPACE << "\" --" << std::endl;
|
||||
}
|
||||
|
||||
// initialize AppDir with common directories on request
|
||||
if (initAppDir) {
|
||||
ldLog() << std::endl << "-- Creating basic AppDir structure --" << std::endl;
|
||||
|
||||
if (!appDir.createBasicStructure())
|
||||
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)) {
|
||||
std::cerr << "No such file or directory: " << libraryPath << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!appDir.deployLibrary(libraryPath)) {
|
||||
std::cerr << "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)) {
|
||||
std::cerr << "No such file or directory: " << executablePath << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!appDir.deployExecutable(executablePath)) {
|
||||
std::cerr << "Failed to deploy executable: " << executablePath << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iconPaths) {
|
||||
ldLog() << std::endl << "-- Deploying icons --" << std::endl;
|
||||
|
||||
for (const auto& iconPath : iconPaths.Get()) {
|
||||
if (!bf::exists(iconPath)) {
|
||||
std::cerr << "No such file or directory: " << iconPath << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!appDir.deployIcon(iconPath)) {
|
||||
std::cerr << "Failed to deploy desktop file: " << 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)) {
|
||||
std::cerr << "No such file or directory: " << desktopFilePath << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
desktopfile::DesktopFile desktopFile(desktopFilePath);
|
||||
|
||||
if (!appDir.deployDesktopFile(desktopFile)) {
|
||||
std::cerr << "Failed to deploy desktop file: " << desktopFilePath << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// perform deferred copy operations before creating other files here or 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;
|
||||
|
||||
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(desktopFilePath);
|
||||
if (!desktopFile.addDefaultKeys(executableName)) {
|
||||
ldLog() << LD_WARNING << "Tried to overwrite existing entries in desktop file" << std::endl;
|
||||
}
|
||||
|
||||
if (!desktopFile.save()) {
|
||||
ldLog() << LD_ERROR << "Failed to save desktop file:" << desktopFilePath << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// search for desktop file and deploy it to AppDir root
|
||||
{
|
||||
ldLog() << std::endl << "-- Deploying files into AppDir root directory --" << std::endl;
|
||||
|
||||
auto deployedDesktopFiles = appDir.deployedDesktopFiles();
|
||||
|
||||
if (deployedDesktopFiles.empty()) {
|
||||
ldLog() << LD_WARNING << "Could not find desktop file in AppDir, cannot create links for AppRun, desktop file and icon in AppDir root" << std::endl;
|
||||
} else {
|
||||
auto& desktopFile = deployedDesktopFiles[0];
|
||||
|
||||
ldLog() << "Deploying desktop file:" << desktopFile.path();
|
||||
|
||||
if (!appDir.createLinksInAppDirRoot(desktopFile))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user