Initial commit

This commit is contained in:
TheAssassin
2018-05-30 19:21:08 +02:00
commit e598536173
22 changed files with 1572 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
cmake-build-*/
*build*/
.idea/
+9
View 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
View 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
View 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
View File
@@ -0,0 +1,5 @@
# linuxdeploy
AppDir creation and maintenance tool.
**More info will follow soon!**
+71
View 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
View 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
View 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
View 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
View 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
View 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)
Submodule
+1
Submodule lib/args added at bd0429e91f
Submodule lib/cpp-subprocess added at 05c76a5311
+4
View File
@@ -0,0 +1,4 @@
# globally include own includes
include_directories(${PROJECT_SOURCE_DIR}/include)
add_subdirectory(core)
+27
View 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
View File
File diff suppressed because it is too large Load Diff
+130
View 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
View 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
View 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"

Some files were not shown because too many files have changed in this diff Show More