3 Commits

Author SHA1 Message Date
TheAssassin 8e0fdc80b0 Fix linking issues, mk. 2 2018-11-28 11:07:20 +01:00
TheAssassin 3a96c5e880 Fix linking issues 2018-11-28 11:03:08 +01:00
TheAssassin 0bc12dfd95 Use std::regex library instead of boost one
Works equally fine for us, and allows for removing a dependency.
2018-11-28 10:59:09 +01:00
79 changed files with 2869 additions and 2031 deletions
+21 -6
View File
@@ -1,12 +1,12 @@
[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/boost-filesystem"]
path = lib/boost-filesystem
url = https://github.com/boostorg/filesystem.git
[submodule "lib/boost-regex"]
path = lib/boost-regex
url = https://github.com/boostorg/regex.git
[submodule "lib/boost-system"]
path = lib/boost-system
url = https://github.com/boostorg/system.git
@@ -73,9 +73,24 @@
[submodule "lib/googletest"]
path = lib/googletest
url = https://github.com/google/googletest
[submodule "lib/boost-lexical_cast"]
path = lib/boost-lexical_cast
url = https://github.com/boostorg/lexical_cast
[submodule "lib/boost-concept_check"]
path = lib/boost-concept_check
url = https://github.com/boostorg/concept_check
[submodule "lib/boost-numeric_conversion"]
path = lib/boost-numeric_conversion
url = https://github.com/boostorg/numeric_conversion
[submodule "lib/boost-array"]
path = lib/boost-array
url = https://github.com/boostorg/array
[submodule "lib/boost-container"]
path = lib/boost-container
url = https://github.com/boostorg/container
[submodule "lib/linuxdeploy-desktopfile"]
path = lib/linuxdeploy-desktopfile
url = https://github.com/linuxdeploy/linuxdeploy-desktopfile
[submodule "lib/boost-move"]
path = lib/boost-move
url = https://github.com/boostorg/move
[submodule "lib/boost-math"]
path = lib/boost-math
url = https://github.com/boostorg/math
+29 -25
View File
@@ -1,10 +1,9 @@
language: cpp
sudo: required
matrix:
include:
- name: Ubuntu bionic x86_64 release build
env: ARCH=x86_64
dist: bionic
- env: ARCH=x86_64
addons:
apt:
update: true
@@ -13,15 +12,8 @@ matrix:
- libjpeg-dev
- libpng-dev
- cimg-dev
script: travis/build.sh
after_success:
- if [ "$TRAVIS_BRANCH" != "$TRAVIS_TAG" ] && [ "$TRAVIS_BRANCH" != "master" ]; then export TRAVIS_EVENT_TYPE=pull_request; fi
- wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh
- bash upload.sh linuxdeploy-"$ARCH".AppImage*
- name: Ubuntu bionic i386 build
env: ARCH=i386
dist: bionic
- automake # required for patchelf
- env: ARCH=i386
addons:
apt:
update: true
@@ -31,17 +23,10 @@ matrix:
- libpng-dev:i386
- gcc-multilib
- g++-multilib
- automake # required for patchelf
- libfuse2:i386
- libcairo2:i386
script: travis/build.sh
after_success:
- if [ "$TRAVIS_BRANCH" != "$TRAVIS_TAG" ] && [ "$TRAVIS_BRANCH" != "master" ]; then export TRAVIS_EVENT_TYPE=pull_request; fi
- wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh
- bash upload.sh linuxdeploy-"$ARCH".AppImage*
- name: Ubuntu xenial x86_64 coverage build (no binaries uploaded)
env: ARCH=x86_64 BUILD_TYPE=coverage
dist: xenial
- env: ARCH=x86_64 BUILD_TYPE=coverage
addons:
apt:
update: true
@@ -54,10 +39,7 @@ matrix:
- sudo pip install gcovr
script: travis/test-coverage.sh
after_success: true # also, we don't intend to upload release binaries
- name: Ubuntu xenial i386 coverage build (no binaries uploaded)
env: ARCH=i386 BUILD_TYPE=coverage
dist: xenial
- env: ARCH=i386 BUILD_TYPE=coverage
addons:
apt:
update: true
@@ -74,6 +56,28 @@ matrix:
script: travis/test-coverage.sh
after_success: true # also, we don't intend to upload release binaries
install:
- git clone https://github.com/NixOS/patchelf.git -b 0.8
- cd patchelf
- ./bootstrap.sh
- if [ "$ARCH" == "i386" ]; then ./configure --prefix=/usr --build=i686-pc-linux-gnu CFLAGS=-m32 CXXFLAGS=-m32 LDFLAGS=-m32; fi
- if [ "$ARCH" == "x86_64" ]; then ./configure --prefix=/usr; fi
- make -j$(nproc)
- sudo make install
- cd ..
- rm -rf patchelf
script:
- travis/build.sh
after_success:
- ls -lh
# make sure only pushes to rewrite create a new release, otherwise pretend PR and upload to transfer.sh
- if [ "$TRAVIS_BRANCH" != "$TRAVIS_TAG" ] && [ "$TRAVIS_BRANCH" != "master" ]; then export TRAVIS_EVENT_TYPE=pull_request; fi
- wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh
- bash upload.sh linuxdeploy-"$ARCH".AppImage*
branches:
except:
- # Do not build tags that we create when we upload to GitHub Releases
+65 -11
View File
@@ -14,22 +14,60 @@ linuxdeploy is designed to be an AppDir maintenance tool. It provides extensive
linuxdeploy was greatly influenced by [linuxdeployqt](https://github.com/probonopd/linuxdeployqt), and while it employs stricter rules on AppDirs, it's more flexible in use. If you use linuxdeployqt at the moment, consider switching to linuxdeploy today!
## User guides and examples
## Usage
Please see the [linuxdeploy user guide](https://docs.appimage.org/packaging-guide/from-source/linuxdeploy-user-guide.html) and the [native binaries packaging guide](https://docs.appimage.org/packaging-guide/from-source/native-binaries.html) in the [AppImage documentation](https://docs.appimage.org). There's also an [examples section](https://docs.appimage.org/packaging-guide/from-source/native-binaries.html#examples).
linuxdeploy, being a tool for creating AppDirs and eventually AppImages, is released as an AppImage itself. Please download and use the AppImage for normal use. If you encounter errors, e.g., when building the tool from source, please try with the AppImage first before [creating an issue](https://github.com/TheAssassin/linuxdeploy/issues/new).
There are two main ways to use linuxdeploy: Provide the paths to all files you want to bundle manually, or create an FHS-like directory and run linuxdeploy on it.
## Projects using linuxdeploy
Most build systems provide some kind of `make install` functionality that is capable of producing FHS-like directory trees that can be turned into AppDirs:
This is an incomplete list of projects using linuxdeploy. You might want to read their build scripts to see how they use linuxdeploy.
```sh
# automake
./configure --prefix=/usr
make install DESTDIR=AppDir
- [Pext](https://github.com/Pext/Pext)
- [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher)
- [OpenRCT2](https://github.com/OpenRCT2/OpenRCT2)
- [AppImageUpdate](https://github.com/AppImage/AppImageUpdate)
- [appimaged](https://github.com/AppImage/appimaged)
- [MediaElch](https://github.com/Komet/MediaElch/)
- [CPU-X](https://github.com/X0rg/CPU-X) (details available [here](https://github.com/AppImage/AppImageKit/wiki/Bundling-GTK3-apps#fully-automated-deployment-from-sources-with-github-actions-cpu-x))
# cmake
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=/usr
make install DESTDIR=AppDir
# qmake
qmake CONFIG+=release PREFIX=/usr .
make install INSTALL_ROOT=AppDir
```
Next, download the latest linuxdeploy AppImage. It's available on the [release page](https://github.com/linuxdeploy/linuxdeploy/releases). Make sure to pick the right one for your (target) architecture.
Now, simply run linuxdeploy like `./linuxdeploy*.AppImage --appdir AppDir`.
Please use `./linuxdeploy*.AppImage --help` to get a list of options supported by linuxdeploy.
If your build system cannot produce such install trees or you prefer to bundle everything manually, you can also specify the paths to the resources yourself. linuxdeploy provides the following options for this use case:
```
-l[library...],
--lib=[library...],
--library=[library...] Shared library to deploy
-e[executable...],
--executable=[executable...] Executable to deploy
-d[desktop file...],
--desktop-file=[desktop file...] Desktop file to deploy
enough for some tests
-i[icon file...],
--icon-file=[icon file...] Icon to deploy
```
An example run could look like this:
```bash
./linuxdeploy*.AppImage --appdir AppDir -e myapp -d myapp.desktop -i myapp_64x64.png
```
Of course both approaches can be combined, e.g., you can bundle additional executables with your main app.
linuxdeploy doesn't mind being run on an AppDir more than once, as it recognize previous runs, and should not break files within the AppDir. This is called an "iterative workflow". In case of errors, you can simply fix them, and re-run linuxdeploy afterwards.
If you ever encounter issues when running linuxdeploy on an existing AppDir, please let us know by [creating an issue](https://github.com/TheAssassin/linuxdeploy/issues/new).
## Plugins
@@ -55,6 +93,22 @@ If you want to use a plugin to bundle additional resources, please add `./linuxd
**Note:** If you want to suggest a plugin for a specific framework, language etc., please feel free to [create a new issue](https://github.com/linuxdeploy/linuxdeploy/issues/new). Current plugin requests can be found [here](https://github.com/linuxdeploy/linuxdeploy/issues?utf8=%E2%9C%93&q=label%3A%22plugin+request%22).
## User guides and examples
Please see the [linuxdeploy user guide](https://docs.appimage.org/packaging-guide/linuxdeploy-user-guide.html) and the [native binaries packaging guide](https://docs.appimage.org/packaging-guide/native-binaries.html) in the [AppImage documentation](https://docs.appimage.org). There's also an [examples section](https://docs.appimage.org/packaging-guide/native-binaries.html#examples).
## Projects using linuxdeploy
This is an incomplete list of projects using linuxdeploy. You might want to read their build scripts to see how they use linuxdeploy.
- [Pext](https://github.com/Pext/Pext)
- [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher)
- [OpenRCT2](https://github.com/OpenRCT2/OpenRCT2)
- [AppImageUpdate](https://github.com/AppImage/AppImageUpdate)
- [appimaged](https://github.com/AppImage/appimaged)
## Troubleshooting
> I bundled additional resources, but when I try to run them, either the system binary is called or the file is not found.
+5 -41
View File
@@ -1,35 +1,8 @@
# required for PNG imported target
cmake_minimum_required(VERSION 3.6)
cmake_minimum_required(VERSION 3.5)
find_package(PkgConfig)
pkg_check_modules(libpng REQUIRED IMPORTED_TARGET libpng)
# build custom static version of libjpeg for static builds
if(STATIC_BUILD)
include(ExternalProject)
if($ENV{ARCH} MATCHES "i[356]86")
set(configure_command_prefix env CFLAGS=-m32 CXXFLAGS=-m32)
endif()
ExternalProject_Add(libjpeg_static_extproj
URL https://www.ijg.org/files/jpegsrc.v9d.tar.gz
URL_HASH SHA256=99cb50e48a4556bc571dadd27931955ff458aae32f68c4d9c39d624693f69c32
BUILD_IN_SOURCE ON
EXCLUDE_FROM_ALL ON
CONFIGURE_COMMAND ${configure_command_prefix} ./configure --prefix=/usr
INSTALL_COMMAND ""
)
ExternalProject_Get_property(libjpeg_static_extproj SOURCE_DIR)
add_library(libjpeg_static INTERFACE IMPORTED)
set_property(TARGET libjpeg_static PROPERTY INTERFACE_LINK_LIBRARIES ${SOURCE_DIR}/.libs/libjpeg.a)
add_dependencies(libjpeg_static libjpeg_static_extproj)
set(JPEG_LIBRARIES libjpeg_static)
else()
find_package(JPEG REQUIRED)
endif()
find_package(PNG REQUIRED)
find_package(JPEG REQUIRED)
if(NOT USE_SYSTEM_CIMG)
message(STATUS "Using bundled CImg library")
@@ -49,16 +22,7 @@ else()
endif()
endif()
set(PNG_INCLUDE_DIR ${libpng_INCLUDE_DIRS})
if(NOT STATIC_BUILD)
set(PNG_LIBRARY ${libpng_LIBRARIES})
else()
set(PNG_LIBRARY ${libpng_STATIC_LIBRARIES})
endif()
#message(FATAL_ERROR "${PNG_LIBRARY}")
add_library(CImg INTERFACE)
set_property(TARGET CImg PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CIMG_H_DIR};${PNG_INCLUDE_DIR};${JPEG_INCLUDE_DIR}")
set_property(TARGET CImg PROPERTY INTERFACE_LINK_LIBRARIES "${PNG_LIBRARY};${JPEG_LIBRARIES}")
set_property(TARGET CImg PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CIMG_H_DIR};${JPEG_INCLUDE_DIR}")
set_property(TARGET CImg PROPERTY INTERFACE_LINK_LIBRARIES "PNG::PNG;${JPEG_LIBRARIES}")
set_property(TARGET CImg PROPERTY INTERFACE_COMPILE_DEFINITIONS "cimg_display=0;cimg_use_png=1;cimg_use_jpeg=1")
+14 -30
View File
@@ -1,12 +1,11 @@
// system includes
#include <memory>
#include <string>
// library includes
#include <boost/filesystem.hpp>
// local includes
#include "linuxdeploy/desktopfile/desktopfile.h"
#include "linuxdeploy/core/desktopfile/desktopfile.h"
#pragma once
@@ -20,7 +19,7 @@ namespace linuxdeploy {
private:
// private data class pattern
class PrivateData;
std::shared_ptr<PrivateData> d;
PrivateData* d;
public:
// default constructor
@@ -28,18 +27,14 @@ namespace linuxdeploy {
// the directory will be created if it doesn't exist
explicit AppDir(const boost::filesystem::path& path);
// we don't want any copy/move(-assignment) behavior, therefore we delete those operators/constructors
AppDir(const AppDir&) = delete;
AppDir(AppDir&&) = delete;
void operator=(const AppDir&) = delete;
void operator=(AppDir&&) = delete;
~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() const;
bool createBasicStructure();
// deploy shared library
//
@@ -57,59 +52,48 @@ namespace linuxdeploy {
// deploy executable
bool deployExecutable(const boost::filesystem::path& path, const boost::filesystem::path& destination = "");
// deploy dependencies for ELF file in AppDir, without copying it into the library dir
//
// the dependencies end up in the regular location
bool deployDependenciesOnlyForElfFile(const boost::filesystem::path& elfFilePath, bool failSilentForNonElfFile = false);
// deploy desktop file
bool deployDesktopFile(const desktopfile::DesktopFile& desktopFile);
// deploy icon
bool deployIcon(const boost::filesystem::path& path);
// deploy icon, changing its name to <target filename>.<ext>
bool deployIcon(const boost::filesystem::path& path, const std::string& targetFilename);
// deploy arbitrary file
boost::filesystem::path deployFile(const boost::filesystem::path& from, const boost::filesystem::path& to);
// copy arbitrary file (immediately)
bool copyFile(const boost::filesystem::path& from, const boost::filesystem::path& to, bool overwrite = false) const;
void deployFile(const boost::filesystem::path& from, const boost::filesystem::path& to);
// create an <AppDir> relative symlink to <target> at <symlink>.
bool createRelativeSymlink(const boost::filesystem::path& target, const boost::filesystem::path& symlink) const;
bool createRelativeSymlink(const boost::filesystem::path& target, const boost::filesystem::path& symlink);
// execute deferred copy operations
bool executeDeferredOperations();
// return path to AppDir
boost::filesystem::path path() const;
boost::filesystem::path path();
// create a list of all icon paths in the AppDir
std::vector<boost::filesystem::path> deployedIconPaths() const;
std::vector<boost::filesystem::path> deployedIconPaths();
// create a list of all executable paths in the AppDir
std::vector<boost::filesystem::path> deployedExecutablePaths() const;
std::vector<boost::filesystem::path> deployedExecutablePaths();
// create a list of all desktop file paths in the AppDir
std::vector<desktopfile::DesktopFile> deployedDesktopFiles() const;
std::vector<desktopfile::DesktopFile> deployedDesktopFiles();
// create symlinks for AppRun, desktop file and icon in the AppDir root directory
bool setUpAppDirRoot(const desktopfile::DesktopFile& desktopFile, boost::filesystem::path customAppRunPath = "");
bool createLinksInAppDirRoot(const desktopfile::DesktopFile& desktopFile, boost::filesystem::path customAppRunPath = "");
// list all executables in <AppDir>/usr/bin
// this function does not perform a recursive search, but only searches the bin directory
std::vector<boost::filesystem::path> listExecutables() const;
std::vector<boost::filesystem::path> listExecutables();
// list all shared libraries in <AppDir>/usr/lib
// this function recursively searches the entire lib directory for shared libraries
std::vector<boost::filesystem::path> listSharedLibraries() const;
std::vector<boost::filesystem::path> listSharedLibraries();
// search for executables and libraries and deploy their dependencies
// calling this function can turn sure file trees created by make install commands into working
// AppDirs
bool deployDependenciesForExistingFiles() const;
bool deployDependenciesForExistingFiles();
// disable deployment of copyright files for this instance
void setDisableCopyrightFilesDeployment(bool disable);
@@ -0,0 +1,126 @@
// system includes
#include <unordered_map>
// library includes
#include <boost/filesystem.hpp>
// local includes
#include "desktopfileentry.h"
#pragma once
namespace linuxdeploy {
namespace core {
namespace desktopfile {
/*
* Parse and read desktop files.
*/
class DesktopFile {
public:
// describes a single section
typedef std::unordered_map<std::string, DesktopFileEntry> section_t;
// describes all sections in the desktop file
typedef std::unordered_map<std::string, section_t> sections_t;
private:
// private data class pattern
class PrivateData;
std::shared_ptr<PrivateData> d;
// (in)equality operators are implemented outside this class
friend bool operator==(const DesktopFile& first, const DesktopFile& second);
friend bool operator!=(const DesktopFile& first, const DesktopFile& second);
public:
// default constructor
DesktopFile();
// construct from existing desktop file
// if the file exists, it will be read using DesktopFileReader
// if reading fails, exceptions will be thrown (see DesktopFileReader for more information)
explicit DesktopFile(const boost::filesystem::path& path);
// construct by reading an existing stream
// file must exist, otherwise std::runtime_error is thrown
explicit DesktopFile(std::istream& is);
// copy constructor
DesktopFile(const DesktopFile& other);
// copy assignment constructor
DesktopFile& operator=(const DesktopFile& other);
// move assignment operator
DesktopFile& operator=(DesktopFile&& other) noexcept;
public:
// returns true if a file has been loaded, false otherwise
bool isEmpty() const;
// read desktop file
// sets path associated with this file
// throws exceptions in case of issues, see DesktopFileReader for more information
void read(const boost::filesystem::path& path);
// read desktop file from existing stream
// throws exceptions in case of issues, see DesktopFileReader for more information
void read(std::istream& is);
// 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
// throws exceptions in case of errors, see DesktopFileWriter::save(...) for more information
bool save(const boost::filesystem::path& path) const;
// save desktop file to ostream
// does not change path associated with desktop file
// throws exceptions in case of errors, see DesktopFileWriter::save(...) for more information
bool save(std::ostream& os) 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, DesktopFileEntry& 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 DesktopFileEntry& entry);
// 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, DesktopFileEntry&& entry);
// 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;
};
// DesktopFile equality operator
bool operator==(const DesktopFile& first, const DesktopFile& second);
// DesktopFile inequality operator
bool operator!=(const DesktopFile& first, const DesktopFile& second);
}
}
}
@@ -0,0 +1,71 @@
#pragma once
// system headers
#include <memory>
#include <string>
// library headers
#include <boost/lexical_cast.hpp>
namespace linuxdeploy {
namespace core {
namespace desktopfile {
class DesktopFileEntry {
private:
// opaque data class pattern
class PrivateData;
std::shared_ptr<PrivateData> d;
public:
// default constructor
DesktopFileEntry();
// construct from key and value
explicit DesktopFileEntry(std::string key, std::string value);
// copy constructor
DesktopFileEntry(const DesktopFileEntry& other);
// copy assignment constructor
DesktopFileEntry& operator=(const DesktopFileEntry& other);
// move assignment operator
DesktopFileEntry& operator=(DesktopFileEntry&& other) noexcept;
// equality operator
bool operator==(const DesktopFileEntry& other) const;
// inequality operator
bool operator!=(const DesktopFileEntry& other) const;
public:
// checks whether a key and value have been set
bool isEmpty() const;
// return entry's key
const std::string& key() const;
// return entry's value
const std::string& value() const;
public:
// convert value to integer
// throws boost::bad_lexical_cast in case of type errors
int32_t asInt() const;
// convert value to long
// throws boost::bad_lexical_cast in case of type errors
int64_t asLong() const;
// convert value to double
// throws boost::bad_lexical_cast in case of type errors
double asDouble() const;
// split CSV list value into vector
// the separator used to split the string is a semicolon as per desktop file spec
std::vector<std::string> parseStringList() const;
};
}
}
}
@@ -0,0 +1,40 @@
#pragma once
// system includes
#include <stdexcept>
#include <string>
namespace linuxdeploy {
namespace core {
namespace desktopfile {
/**
* Desktop file library's base exception.
*/
class DesktopFileError : public std::runtime_error {
public:
explicit DesktopFileError(const std::string& message = "unknown desktop file error") : runtime_error(message) {};
};
/**
* Exception thrown by DesktopFileReader on parsing errors.
*/
class ParseError : public DesktopFileError {
public:
explicit ParseError(const std::string& message = "unknown parse error") : DesktopFileError(message) {};
};
/**
* I/O exception, thrown if files cannot be opened, reading or writing fails etc.
*/
class IOError : public DesktopFileError {
public:
explicit IOError(const std::string& message = "unknown I/O error") : DesktopFileError(message) {};
};
class UnknownSectionError : public DesktopFileError {
public:
explicit UnknownSectionError(const std::string& section) : DesktopFileError("unknown section: " + section) {};
};
}
}
}
-2
View File
@@ -65,8 +65,6 @@ namespace linuxdeploy {
ldLog operator<<(stdEndlType strm);
ldLog operator<<(const LD_LOGLEVEL logLevel);
ldLog operator<<(const LD_STREAM_CONTROL streamControl);
void write(const char* s, const size_t n);
};
}
}
+138 -22
View File
@@ -2,19 +2,15 @@
#include <set>
#include <string>
#include <vector>
#include <fcntl.h>
#include <poll.h>
// library headers
#include <boost/filesystem.hpp>
#include <fnmatch.h>
#include <thread>
#include <subprocess.hpp>
// local headers
#include "linuxdeploy/core/log.h"
#include "linuxdeploy/util/util.h"
#include "linuxdeploy/subprocess/process.h"
#include "linuxdeploy/plugin/plugin_process_handler.h"
#pragma once
@@ -42,27 +38,20 @@ namespace linuxdeploy {
apiLevel = getApiLevelFromExecutable();
pluginType = getPluginTypeFromExecutable();
boost::cmatch res;
boost::regex_match(path.filename().c_str(), res, PLUGIN_EXPR);
std::smatch res;
std::regex_match(path.filename().string(), res, PLUGIN_EXPR);
name = res[1].str();
};
private:
int getApiLevelFromExecutable() {
const auto arg = "--plugin-api-version";
const subprocess::subprocess proc({pluginPath.string(), arg});
const auto stdoutOutput = proc.check_output();
if (stdoutOutput.empty()) {
ldLog() << LD_WARNING << "received empty response from plugin" << pluginPath << "while trying to fetch data for" << "--plugin-api-version" << std::endl;
return -1;
}
auto output = subprocess::check_output({pluginPath.c_str(), "--plugin-api-version"});
std::string stdoutOutput = output.buf.data();
try {
auto apiLevel = std::stoi(stdoutOutput);
return apiLevel;
} catch (const std::exception&) {
} catch (const std::invalid_argument&) {
return -1;
}
}
@@ -73,8 +62,9 @@ namespace linuxdeploy {
// check whether plugin implements --plugin-type
try {
const subprocess::subprocess proc({pluginPath.c_str(), "--plugin-type"});
const auto stdoutOutput = proc.check_output();
auto output = subprocess::check_output({pluginPath.c_str(), "--plugin-type"});
std::string stdoutOutput = output.buf.data();
// the specification requires a single line, but we'll silently accept more than that, too
if (std::count(stdoutOutput.begin(), stdoutOutput.end(), '\n') >= 1) {
@@ -85,7 +75,7 @@ namespace linuxdeploy {
else if (firstLine == "output")
type = OUTPUT_TYPE;
}
} catch (const std::logic_error&) {}
} catch (const subprocess::CalledProcessError&) {}
return type;
}
@@ -136,8 +126,134 @@ namespace linuxdeploy {
template<int API_LEVEL>
int PluginBase<API_LEVEL>::run(const boost::filesystem::path& appDirPath) {
plugin_process_handler handler(d->name, path());
return handler.run(appDirPath);
auto pluginPath = path();
auto args = {pluginPath.c_str(), "--appdir", appDirPath.string().c_str()};
auto log = ldLog();
log << "Running process:";
for (const auto& arg : args) {
log << "" << arg;
}
log << std::endl;
auto process = subprocess::Popen(args, subprocess::output{subprocess::PIPE}, subprocess::error{subprocess::PIPE});
std::vector<pollfd> pfds(2);
auto* opfd = &pfds[0];
auto* epfd = &pfds[1];
opfd->fd = fileno(process.output());
opfd->events = POLLIN;
epfd->fd = fileno(process.error());
epfd->events = POLLIN;
for (const auto& fd : {process.output(), process.error()}) {
auto flags = fcntl(fileno(fd), F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fileno(fd), F_SETFL, flags);
}
auto printOutput = [&pfds, opfd, epfd, this, &process]() {
poll(pfds.data(), pfds.size(), -1);
auto printUntilLastLine = [this](std::vector<char>& buf, size_t& bufSize, const std::string& streamType) {
std::ostringstream oss;
while (true) {
const auto firstLineFeed = std::find(buf.begin(), buf.end(), '\n');
const auto firstCarriageReturn = std::find(buf.begin(), buf.end(), '\r');
if (firstLineFeed == buf.end() && firstCarriageReturn == buf.end())
break;
const auto endOfLine = std::min(firstLineFeed, firstCarriageReturn);
std::string line(buf.begin(), endOfLine+1);
oss << "[" << d->name << "/" << streamType << "] " << line;
bufSize -= std::distance(buf.begin(), endOfLine+1);
buf.erase(buf.begin(), endOfLine+1);
}
auto messages = oss.str();
if (!messages.empty())
ldLog() << messages;
};
std::vector<char> stdoutBuf(16 * 1024);
std::vector<char> stderrBuf(16 * 1024);
size_t stdoutBufSize = 0;
size_t stderrBufSize = 0;
if (opfd->revents & POLLIN) {
if (stdoutBufSize >= stdoutBuf.size())
throw std::runtime_error("Buffer overflow");
while (true) {
auto bytesRead = fread(
reinterpret_cast<void*>(stdoutBuf.data() + stdoutBufSize),
sizeof(char),
static_cast<size_t>(stdoutBuf.size() - stdoutBufSize),
process.output()
);
if (bytesRead == 0)
break;
stdoutBufSize += bytesRead;
printUntilLastLine(stdoutBuf, stdoutBufSize, "stdout");
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
if (epfd->revents & POLLIN) {
if (stderrBufSize >= stderrBuf.size())
throw std::runtime_error("Buffer overflow");
while (true) {
auto bytesRead = fread(
reinterpret_cast<void*>(stderrBuf.data() + stderrBufSize),
sizeof(char),
static_cast<size_t>(stderrBuf.size() - stderrBufSize),
process.error()
);
if (bytesRead == 0)
break;
stderrBufSize += bytesRead;
printUntilLastLine(stderrBuf, stderrBufSize, "stderr");
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
return true;
};
int retcode;
do {
if (!printOutput()) {
ldLog() << LD_ERROR << "Failed to communicate with process" << std::endl;
process.kill();
return -1;
}
} while ((retcode = process.poll()) < 0);
if (!printOutput()) {
ldLog() << LD_ERROR << "Failed to communicate with process" << std::endl;
process.kill();
return -1;
}
return retcode;
}
}
}
+2 -5
View File
@@ -1,10 +1,7 @@
// system includes
#include <map>
#include <string>
// library includes
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>
#include <regex>
// local includes
#include "linuxdeploy/core/log.h"
@@ -22,7 +19,7 @@ namespace linuxdeploy {
/*
* Official regular expression to check filenames for plugins
*/
static const boost::regex PLUGIN_EXPR(R"(^linuxdeploy-plugin-([^\s\.-]+)(?:-[^\.]+)?(?:\..+)?$)");
static const std::regex PLUGIN_EXPR(R"(^linuxdeploy-plugin-([^\s\.-]+)(?:-[^\.]+)?(?:\..+)?$)");
/*
* Plugin interface.
@@ -1,19 +0,0 @@
#pragma once
// library headers
#include <boost/filesystem/path.hpp>
namespace linuxdeploy {
namespace plugin {
class plugin_process_handler {
private:
std::string name_;
boost::filesystem::path path_;
public:
plugin_process_handler(std::string name, boost::filesystem::path path);
int run(const boost::filesystem::path& appDir) const;
};
}
}
@@ -1,36 +0,0 @@
#pragma once
#include <string>
#include <set>
#include <vector>
#include <poll.h>
/**
* Reads from a pipe when data is available, and hands data to registered callbacks.
*/
class pipe_reader {
private:
const int pipe_fd_;
public:
/**
* Construct new instance from pipe file descriptor.
* @param pipe_fd file descriptor for pipe we will read from (e.g., a subprocess's stdout, stderr pipes)
*/
explicit pipe_reader(int pipe_fd);
/**
* Read from pipe file descriptor and store data in buffer.
* This method is non-blocking, i.e., if there is no data to read, it returns immediately.
* Otherwise, it will read until either of the following conditions is met:
*
* - no more data left in the pipe to be read
* - buffer is completely filled
*
* On errors, a subprocess_error is thrown.
*
* @param buffer buffer to store read data into
* @returns amount of characters read from the pipe
*/
size_t read(std::vector<std::string::value_type>& buffer) const;
};
-86
View File
@@ -1,86 +0,0 @@
// system headers
#include <unordered_map>
#include <vector>
#include <signal.h>
// local headers
#include "linuxdeploy/subprocess/subprocess.h"
namespace linuxdeploy {
namespace subprocess {
class process {
private:
// child process ID
int child_pid_ = -1;
// pipes to child process's stdout/stderr
int stdout_fd_ = -1;
int stderr_fd_ = -1;
// process exited
bool exited_ = false;
// exit code -- will be initialized by close()
int exit_code_ = -1;
// these constants help make the pipe code more readable
static constexpr int READ_END_ = 0, WRITE_END_ = 1;
static std::vector<char*> make_args_vector_(const std::vector<std::string>& args);
static std::vector<char*> make_env_vector_(const subprocess_env_map_t& env);
static int check_waitpid_status_(int status);
public:
/**
* Create a child process.
* @param args parameters for process
* @param env additional environment variables (current environment will be copied)
*/
process(std::initializer_list<std::string> args, const subprocess_env_map_t& env);
/**
* Create a child process.
* @param args parameters for process
* @param env additional environment variables (current environment will be copied)
*/
process(const std::vector<std::string>& args, const subprocess_env_map_t& env);
~process();
/**
* @return child process's ID
*/
int pid() const;
/**
* @return child process's stdout file descriptor ID
*/
int stdout_fd() const;
/**
* @return child process's stderr file descriptor ID
*/
int stderr_fd() const;
/**
* Close all pipes and wait for process to exit.
* If process is not running any more, just returns exit code.
* @return child process's exit code
*/
int close();
/**
* Kill underlying process with given signal. By default, SIGTERM is used to end the process.
*/
void kill(int signal = SIGTERM) const;
/**
* Check whether process is still alive. Use close() to fetch exit code.
* @return true while process is alive, false otherwise
*/
bool is_running();
};
}
}
@@ -1,32 +0,0 @@
#pragma once
// system headers
#include <cstdio>
#include <unordered_map>
#include <string>
#include <utility>
#include <vector>
// local headers
#include "subprocess_result.h"
namespace linuxdeploy {
namespace subprocess {
typedef std::unordered_map<std::string, std::string> subprocess_env_map_t;
class subprocess {
private:
std::vector<std::string> args_{};
std::unordered_map<std::string, std::string> env_{};
public:
subprocess(std::initializer_list<std::string> args, subprocess_env_map_t env = {});
explicit subprocess(std::vector<std::string> args, subprocess_env_map_t env = {});
subprocess_result run() const;
std::string check_output() const;
};
}
}
@@ -1,37 +0,0 @@
#pragma once
// system headers
#include <cstdlib>
#include <string>
#include <utility>
#include <vector>
namespace linuxdeploy {
namespace subprocess {
typedef std::vector<std::string::value_type> subprocess_result_buffer_t;
/**
* Result of subprocess execution. Follows Value Object design pattern.
*/
class subprocess_result {
private:
int exit_code_;
subprocess_result_buffer_t stdout_contents_;
subprocess_result_buffer_t stderr_contents_;
public:
subprocess_result(int exit_code, subprocess_result_buffer_t stdout_contents,
subprocess_result_buffer_t stderr_contents);
int exit_code() const;
const subprocess_result_buffer_t& stdout_contents() const;
std::string stdout_string() const;
const subprocess_result_buffer_t& stderr_contents() const;
std::string stderr_string() const;
};
}
}
-17
View File
@@ -1,17 +0,0 @@
#pragma once
// system headers
#include <stdexcept>
namespace linuxdeploy {
namespace util {
namespace assert {
template<typename T>
void assert_not_empty(T container) {
if (container.empty()) {
throw std::invalid_argument{"container must not be empty"};
}
}
}
}
}
-31
View File
@@ -1,6 +1,5 @@
#pragma once
// system headers
#include <algorithm>
#include <climits>
#include <cstring>
@@ -9,9 +8,6 @@
#include <unistd.h>
#include <vector>
// libraries
#include <boost/filesystem.hpp>
namespace linuxdeploy {
namespace util {
namespace misc {
@@ -91,33 +87,6 @@ namespace linuxdeploy {
return buf.data();
}
// very simple but for our purposes good enough which like algorithm to find binaries in $PATH
static boost::filesystem::path which(const std::string& name) {
const auto* path = getenv("PATH");
namespace bf = boost::filesystem;
if (path == nullptr)
return "";
for (const auto& binDir : split(path, ':')) {
if (!bf::is_directory(binDir)) {
continue;
}
for (bf::directory_iterator it(binDir); it != bf::directory_iterator{}; ++it) {
const auto binary = it->path();
if (binary.filename() == name) {
// TODO: check if file is executable (skip otherwise)
return binary;
}
}
}
return {};
};
}
}
}
+19
View File
@@ -0,0 +1,19 @@
/**
* Wrapper for cpp-subprocess. Provides additional convenience functions.
*/
#pragma once
// library includes
#include <subprocess.hpp>
namespace linuxdeploy {
namespace util {
namespace subprocess {
using namespace ::subprocess;
// Reads output channels of existing Popen object into buffers and returns the contents
std::pair<std::string, std::string> check_output_error(Popen& proc);
}
}
}
+2 -3
View File
@@ -1,11 +1,10 @@
// local includes
#include "linuxdeploy/util/assert.h"
#include "linuxdeploy/util/misc.h"
#include "misc.h"
#include "subprocess.h"
// import functions from misc module for convenience
namespace linuxdeploy {
namespace util {
using namespace misc;
using namespace assert;
}
}

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