2 Commits

Author SHA1 Message Date
TheAssassin 6a952b57f9 Make custom AppRun files executable
This eliminates the need for users to make the files executable
before running linuxdeploy.
2019-06-28 21:40:14 +02:00
TheAssassin 8fbe191d7a Fix APPIMAGE_EXTRACT_AND_RUN support for plugins, mk. 1 2019-06-28 20:18:51 +02:00
90 changed files with 1265 additions and 2681 deletions
-107
View File
@@ -1,107 +0,0 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
schedule:
# build at least once a month
- cron: '0 0 1 * *'
jobs:
build-and-test:
strategy:
matrix:
ARCH: [x86_64, i386, aarch64]
BUILD_TYPE: [appimage, coverage]
fail-fast: false
name: ${{ matrix.BUILD_TYPE }} ${{ matrix.ARCH }}
runs-on: ubuntu-latest
env:
ARCH: ${{ matrix.ARCH }}
BUILD_TYPE: ${{ matrix.BUILD_TYPE }}
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies (x86_64)
if: matrix.ARCH == 'x86_64'
run: |
sudo apt-get update
sudo apt-get install -y gcovr libmagic-dev libjpeg-dev libpng-dev libboost-filesystem-dev libboost-regex-dev cimg-dev
- name: Install dependencies (i386)
if: matrix.ARCH == 'i386'
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y gcovr libmagic-dev:i386 libjpeg-dev:i386 libpng-dev:i386 libboost-filesystem-dev:i386 libboost-regex-dev:i386 cimg-dev gcc-multilib g++-multilib libfuse2:i386
- name: Test coverage (native)
run: bash -ex ci/test-coverage.sh
if: matrix.ARCH != 'aarch64' && matrix.BUILD_TYPE == 'coverage'
- name: Test coverage (VM)
if: matrix.ARCH != 'x86_64' && matrix.ARCH != 'i386' && matrix.BUILD_TYPE == 'coverage'
uses: uraimo/run-on-arch-action@v2.2.0
with:
arch: ${{ matrix.ARCH }}
distro: ubuntu20.04
githubToken: ${{ github.token }}
dockerRunArgs: |
--volume "${PWD}:/build"
install: |
apt-get update
apt-get install -y build-essential wget git pkg-config autoconf libmagic-dev libjpeg-dev libpng-dev cimg-dev libboost-filesystem-dev libboost-regex-dev gcovr
run: |
cd /build
ARCH=$(uname -m) ci/test-coverage.sh
- name: Build, test and build AppImage (native)
if: matrix.ARCH != 'aarch64' && matrix.BUILD_TYPE != 'coverage'
run: bash -ex ci/build.sh
- name: Build, test and build AppImage (VM)
if: matrix.ARCH != 'x86_64' && matrix.ARCH != 'i386' && matrix.BUILD_TYPE != 'coverage'
uses: uraimo/run-on-arch-action@v2.2.0
with:
arch: ${{ matrix.ARCH }}
distro: ubuntu20.04
githubToken: ${{ github.token }}
dockerRunArgs: |
--volume "${PWD}:/build"
install: |
apt-get update
apt-get install -y build-essential wget git pkg-config autoconf libmagic-dev libjpeg-dev libpng-dev cimg-dev libboost-filesystem-dev libboost-regex-dev gcovr
run: |
cd /build
ARCH=$(uname -m) ci/build.sh
- name: Archive artifacts
uses: actions/upload-artifact@v2
if: matrix.BUILD_TYPE != 'coverage'
with:
name: AppImage
path: linuxdeploy*.AppImage*
upload:
name: Create release and upload artifacts
needs:
- build-and-test
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v2
- name: Inspect directory after downloading artifacts
run: ls -alFR
- name: Create release and upload artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
wget -q https://github.com/TheAssassin/pyuploadtool/releases/download/continuous/pyuploadtool-x86_64.AppImage
chmod +x pyuploadtool-x86_64.AppImage
./pyuploadtool-x86_64.AppImage **/linuxdeploy*.AppImage*
-1
View File
@@ -3,4 +3,3 @@ cmake-build-*/
.idea/
squashfs-root/
*.AppImage
*.swp
+75 -3
View File
@@ -1,12 +1,84 @@
[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
[submodule "lib/boost-config"]
path = lib/boost-config
url = https://github.com/boostorg/config.git
[submodule "lib/boost-utility"]
path = lib/boost-utility
url = https://github.com/boostorg/utility.git
[submodule "lib/boost-predef"]
path = lib/boost-predef
url = https://github.com/boostorg/predef.git
[submodule "lib/boost-assert"]
path = lib/boost-assert
url = https://github.com/boostorg/assert.git
[submodule "lib/boost-core"]
path = lib/boost-core
url = https://github.com/boostorg/core.git
[submodule "lib/boost-type_traits"]
path = lib/boost-type_traits
url = https://github.com/boostorg/type_traits.git
[submodule "lib/boost-iterator"]
path = lib/boost-iterator
url = https://github.com/boostorg/iterator.git
[submodule "lib/boost-mpl"]
path = lib/boost-mpl
url = https://github.com/boostorg/mpl.git
[submodule "lib/boost-preprocessor"]
path = lib/boost-preprocessor
url = https://github.com/boostorg/preprocessor.git
[submodule "lib/boost-static_assert"]
path = lib/boost-static_assert
url = https://github.com/boostorg/static_assert.git
[submodule "lib/boost-detail"]
path = lib/boost-detail
url = https://github.com/boostorg/detail.git
[submodule "lib/boost-smart_ptr"]
path = lib/boost-smart_ptr
url = https://github.com/boostorg/smart_ptr.git
[submodule "lib/boost-exception"]
path = lib/boost-exception
url = https://github.com/boostorg/exception.git
[submodule "lib/boost-throw_exception"]
path = lib/boost-throw_exception
url = https://github.com/boostorg/throw_exception.git
[submodule "lib/boost-io"]
path = lib/boost-io
url = https://github.com/boostorg/io.git
[submodule "lib/boost-functional"]
path = lib/boost-functional
url = https://github.com/boostorg/functional.git
[submodule "lib/boost-container_hash"]
path = lib/boost-container_hash
url = https://github.com/boostorg/container_hash.git
[submodule "lib/boost-range"]
path = lib/boost-range
url = https://github.com/boostorg/range.git
[submodule "lib/boost-integer"]
path = lib/boost-integer
url = https://github.com/boostorg/integer.git
[submodule "lib/CImg"]
path = lib/CImg
url = https://github.com/dtschump/CImg.git
[submodule "lib/googletest"]
path = lib/googletest
url = https://github.com/google/googletest
[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/cmake-scripts"]
path = lib/cmake-scripts
url = https://github.com/linuxdeploy/cmake-scripts.git
+91
View File
@@ -0,0 +1,91 @@
language: cpp
sudo: required
matrix:
include:
- env: ARCH=x86_64
addons:
apt:
update: true
packages:
- libmagic-dev
- libjpeg-dev
- libpng-dev
- cimg-dev
- automake # required for patchelf
- env: ARCH=i386
addons:
apt:
update: true
packages:
- libmagic-dev:i386
- libjpeg-dev:i386
- libpng-dev:i386
- gcc-multilib
- g++-multilib
- automake # required for patchelf
- libfuse2:i386
- libcairo2:i386
- env: ARCH=x86_64 BUILD_TYPE=coverage
addons:
apt:
update: true
packages:
- libmagic-dev
- libjpeg-dev
- libpng-dev
- cimg-dev
before_script:
- sudo pip install gcovr
script: travis/test-coverage.sh
after_success: true # also, we don't intend to upload release binaries
- env: ARCH=i386 BUILD_TYPE=coverage
addons:
apt:
update: true
packages:
- libmagic-dev:i386
- libjpeg-dev:i386
- libpng-dev:i386
- gcc-multilib
- g++-multilib
- libfuse2:i386
- libcairo2:i386
before_script:
- sudo pip install gcovr
script: travis/test-coverage.sh
after_success: true # also, we don't intend to upload release binaries
- env: ARCH=x86_64 BUILD_TYPE=centos6
services:
- docker
script: travis/build-centos6-docker.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-centos6-"$ARCH".AppImage*
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
- /^(?i:continuous)$/
+9 -8
View File
@@ -7,6 +7,14 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/Modules/")
set(USE_SYSTEM_BOOST OFF CACHE BOOL "Set to ON to use system boost libraries instead of building up to date boost libraries from source")
set(USE_SYSTEM_CIMG ON CACHE BOOL "Set to OFF to use CImg library bundled in lib directory")
if (EXISTS "${PROJECT_SOURCE_DIR}/lib/cpp-subprocess/subprocess.hpp")
else()
message (FATAL_ERROR "Missing submodule(s), please 'git submodule update --init --recursive'...")
endif()
# support for ccache
# call CMake with -DUSE_CCACHE=ON to make use of it
set(USE_CCACHE ON CACHE BOOL "")
@@ -40,18 +48,11 @@ if(ENABLE_COVERAGE)
setup_target_for_coverage_gcovr_text(NAME coverage_text EXECUTABLE "${command}")
endif()
include(CTest)
if(BUILD_TESTING)
# including this before including lib/ makes sure that the top level project's gtest is used everywhere
include(${PROJECT_SOURCE_DIR}/lib/cmake-scripts/include-or-build-gtest.cmake)
endif()
add_subdirectory(lib)
add_subdirectory(src)
include(CTest)
if(BUILD_TESTING)
enable_testing()
add_subdirectory(tests)
endif()
+1 -3
View File
@@ -16,7 +16,7 @@ linuxdeploy was greatly influenced by [linuxdeployqt](https://github.com/probono
## User guides and examples
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).
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
@@ -28,8 +28,6 @@ This is an incomplete list of projects using linuxdeploy. You might want to read
- [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))
## Plugins
-59
View File
@@ -1,59 +0,0 @@
#! /bin/bash
set -e
set -x
INSTALL_DESTDIR="$1"
if [[ "$INSTALL_DESTDIR" == "" ]]; then
echo "Error: build dir $BUILD_DIR does not exist" 1>&2
exit 1
fi
# support cross-compilation for 32-bit ISAs
case "$ARCH" in
"x86_64"|"amd64"|"armhf"|"aarch64")
;;
"i386"|"i586"|"i686")
export CFLAGS="-m32"
export CXXFLAGS="-m32"
;;
*)
echo "Error: unsupported architecture: $ARCH"
exit 1
;;
esac
# use RAM disk if possible
if [ "$CI" == "" ] && [ -d /dev/shm ]; then
TEMP_BASE=/dev/shm
else
TEMP_BASE=/tmp
fi
cleanup () {
if [ -d "$BUILD_DIR" ]; then
rm -rf "$BUILD_DIR"
fi
}
trap cleanup EXIT
BUILD_DIR=$(mktemp -d -p "$TEMP_BASE" linuxdeploy-build-XXXXXX)
pushd "$BUILD_DIR"
# fetch source code
wget http://mirrors.kernel.org/gnu/binutils/binutils-2.35.tar.xz -O- | tar xJ --strip-components=1
# configure static build
# inspired by https://github.com/andrew-d/static-binaries/blob/master/binutils/build.sh
./configure --prefix=/usr --disable-nls --enable-static-link --disable-shared-plugins --disable-dynamicplugin --disable-tls --disable-pie
# citing the script linked above: "This strange dance is required to get things to be statically linked."
make -j "$(nproc)"
make clean
make -j "$(nproc)" LDFLAGS="-all-static"
# install into user-specified destdir
make install DESTDIR="$(readlink -f "$INSTALL_DESTDIR")"
-62
View File
@@ -1,62 +0,0 @@
#! /bin/bash
set -e
set -x
INSTALL_DESTDIR="$1"
if [[ "$INSTALL_DESTDIR" == "" ]]; then
echo "Error: build dir $BUILD_DIR does not exist" 1>&2
exit 1
fi
# support cross-compilation for 32-bit ISAs
case "$ARCH" in
"x86_64"|"amd64"|"armhf"|"aarch64")
;;
"i386"|"i586"|"i686")
export CFLAGS="-m32"
export CXXFLAGS="-m32"
;;
*)
echo "Error: unsupported architecture: $ARCH"
exit 1
;;
esac
# use RAM disk if possible
if [ "$CI" == "" ] && [ -d /dev/shm ]; then
TEMP_BASE=/dev/shm
else
TEMP_BASE=/tmp
fi
cleanup () {
if [ -d "$BUILD_DIR" ]; then
rm -rf "$BUILD_DIR"
fi
}
trap cleanup EXIT
BUILD_DIR=$(mktemp -d -p "$TEMP_BASE" linuxdeploy-build-XXXXXX)
pushd "$BUILD_DIR"
# fetch source code
git clone https://github.com/NixOS/patchelf.git .
# cannot use -b since it's not supported in really old versions of git
git checkout 0.8
# prepare configure script
./bootstrap.sh
# configure static build
env LDFLAGS="-static -static-libgcc -static-libstdc++" ./configure --prefix=/usr
# build binary
make -j "$(nproc)"
# install into user-specified destdir
make install DESTDIR="$INSTALL_DESTDIR"
-9
View File
@@ -1,9 +0,0 @@
#! /bin/bash
# get a compiler that allows for using modern-ish C++ (>= 11) on a distro that doesn't normally support it
# before you ask: yes, the binaries will work on CentOS 6 even without devtoolset (they somehow partially link C++
# things statically while using others from the system...)
# so, basically, it's magic!
source /opt/rh/devtoolset-*/enable
exec "$@"
+19 -88
View File
@@ -1,97 +1,28 @@
#[=======================================================================[.rst:
FindCImg
-------
# required for PNG imported target
cmake_minimum_required(VERSION 3.5)
Finds the CImg library.
find_package(PNG REQUIRED)
find_package(JPEG REQUIRED)
Imported Targets
^^^^^^^^^^^^^^^^
if(NOT USE_SYSTEM_CIMG)
message(STATUS "Using bundled CImg library")
This module provides the following imported targets, if found:
``CImg``
The CImg library
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables:
``CImg_FOUND``
True if the system has the CImg library.
``CImg_VERSION``
The version of the CImg library which was found.
``CImg_INCLUDE_DIRS``
Include directories needed to use CImg.
``CImg_LIBRARIES``
Libraries needed to link to CImg.
Cache Variables
^^^^^^^^^^^^^^^
The following cache variables may also be set:
``CImg_INCLUDE_DIR``
The directory containing ``CImg.h``.
#]=======================================================================]
# 3.6 required for PNG imported target
# 3.9 required for CMAKE_MATCH_<n>
cmake_minimum_required(VERSION 3.9)
find_path(CIMG_H_DIR
NAMES CImg.h
HINTS ${CMAKE_INSTALL_PREFIX} ${CIMG_DIR}
PATH_SUFFIXES include include/linux
)
if(NOT CIMG_H_DIR)
set(CImg_FOUND FALSE)
set(CIMG_H_DIR "${PROJECT_SOURCE_DIR}/lib/CImg/")
else()
set(CImg_FOUND TRUE)
message(STATUS "Searching for CImg")
find_package(PkgConfig)
find_path(CIMG_H_DIR
NAMES CImg.h
HINTS ${CMAKE_INSTALL_PREFIX}
PATH_SUFFIXES include include/linux
)
pkg_check_modules(libpng REQUIRED libpng)
pkg_check_modules(libjpeg REQUIRED libjpeg)
set(PNG_INCLUDE_DIR ${libpng_INCLUDE_DIRS})
set(JPEG_INCLUDE_DIR ${libjpeg_INCLUDE_DIRS})
if(STATIC_BUILD)
set(PNG_LIBRARY ${libpng_STATIC_LIBRARIES})
set(JPEG_LIBRARIES ${libjpeg_STATIC_LIBRARIES})
else()
set(PNG_LIBRARY ${libpng_LIBRARIES})
set(JPEG_LIBRARIES ${libjpeg_LIBRARIES})
endif()
set(CImg_INCLUDE_DIR ${CIMG_H_DIR} CACHE STRING "")
set(CImg_INCLUDE_DIRS ${CImg_INCLUDE_DIR})
set(CImg_LIBRARIES "${PNG_LIBRARY};${JPEG_LIBRARIES}")
set(CImg_DEFINITIONS "cimg_display=0;cimg_use_png=1;cimg_use_jpeg=1")
file(READ "${CIMG_H_DIR}/CImg.h" header)
string(REGEX MATCH "#define cimg_version ([0-9a-zA-Z\.]+)" _ "${header}")
set(CImg_VERSION "${CMAKE_MATCH_1}")
# CImg_VERSION
if(NOT TARGET CImg)
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 "${CImg_LIBRARIES}")
set_property(TARGET CImg PROPERTY INTERFACE_COMPILE_DEFINITIONS "${CImg_DEFINITIONS}")
if(NOT CIMG_H_DIR)
message(FATAL_ERROR "CImg.h not found")
endif()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(CImg
FOUND_VAR CImg_FOUND
REQUIRED_VARS
CImg_INCLUDE_DIRS
CImg_LIBRARIES
CImg_DEFINITIONS
VERSION_VAR CImg_VERSION
)
add_library(CImg INTERFACE)
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")
+10 -24
View File
@@ -38,11 +38,8 @@ namespace linuxdeploy {
// shortcut for using a normal string instead of a path
explicit AppDir(const std::string& path);
// Set additional shared library name patterns to be excluded from deployment.
void setExcludeLibraryPatterns(const std::vector<std::string> &excludeLibraryPatterns);
// creates basic directory structure of an AppDir in "FHS" mode
bool createBasicStructure() const;
bool createBasicStructure();
// deploy shared library
//
@@ -60,59 +57,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;
// 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);
@@ -11,7 +11,7 @@
namespace linuxdeploy {
namespace core {
namespace elf_file {
namespace elf {
// thrown by constructor if file is not an ELF file
class ElfFileParseError : public std::runtime_error {
public:
@@ -64,12 +64,6 @@ namespace linuxdeploy {
// return OS ABI
uint8_t getElfABI();
// check if this file is a debug symbols file
bool isDebugSymbolsFile();
// check whether the file contains a dynsym section
bool isDynamicallyLinked();
};
}
}
-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);
};
}
}
+149 -21
View File
@@ -2,19 +2,16 @@
#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
@@ -49,20 +46,19 @@ namespace linuxdeploy {
private:
int getApiLevelFromExecutable() {
const auto arg = "--plugin-api-version";
auto output = subprocess::check_output({pluginPath.c_str(), "--plugin-api-version"});
std::string stdoutOutput = output.buf.data();
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;
}
// only interpret last line (fixes issues with --appimage-extract-and-run, where the runtime
// prints all the paths it extracts out to stdout)
const auto& lines = util::splitLines(stdoutOutput);
const auto& lastLine = lines.back();
try {
auto apiLevel = std::stoi(stdoutOutput);
return apiLevel;
} catch (const std::exception&) {
const auto pluginApiLevel = std::stoi(lastLine);
return pluginApiLevel;
} catch (const std::invalid_argument&) {
ldLog() << LD_DEBUG << "Failed to understand plugin response:" << lastLine << std::endl;
return -1;
}
}
@@ -73,8 +69,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 +82,7 @@ namespace linuxdeploy {
else if (firstLine == "output")
type = OUTPUT_TYPE;
}
} catch (const std::logic_error&) {}
} catch (const subprocess::CalledProcessError&) {}
return type;
}
@@ -97,7 +94,12 @@ namespace linuxdeploy {
if (d->apiLevel != API_LEVEL) {
std::stringstream msg;
msg << "This class only supports API level " << API_LEVEL << ", not " << d->apiLevel;
if (d->apiLevel < 0) {
msg << "Could not read plugin API level";
} else {
msg << "This class only supports API level " << API_LEVEL << ", not " << d->apiLevel;
}
throw WrongApiLevelError(msg.str());
}
}
@@ -136,8 +138,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;
}
}
}
@@ -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;
};
-88
View File
@@ -1,88 +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);
static void close_pipe_fd_(int fd);
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"};
}
}
}
}
}

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