diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 329fedd0..c0e1071d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,58 +12,85 @@ jobs: build-and-test: strategy: matrix: - ARCH: [x86_64, i386] - BUILD_TYPE: [appimage, coverage] + include: + # regular builds + - ARCH: x86_64 + RUNS_ON: ubuntu-24.04 + BUILD_TYPE: appimage + - ARCH: i386 + RUNS_ON: ubuntu-24.04 + BUILD_TYPE: appimage + - ARCH: armhf + RUNS_ON: ubuntu-24.04-arm + BUILD_TYPE: appimage + - ARCH: aarch64 + RUNS_ON: ubuntu-24.04-arm + BUILD_TYPE: appimage + + # test build + - ARCH: x86_64 + RUNS_ON: ubuntu-24.04 + BUILD_TYPE: coverage + fail-fast: false name: ${{ matrix.BUILD_TYPE }} ${{ matrix.ARCH }} - runs-on: ubuntu-20.04 + runs-on: ${{ matrix.RUNS_ON }} env: ARCH: ${{ matrix.ARCH }} BUILD_TYPE: ${{ matrix.BUILD_TYPE }} + # make sure to always(!) pull the base image + UPDATE: 1 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 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 cimg-dev libfuse2 - - - 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 cimg-dev gcc-multilib g++-multilib libfuse2:i386 - - - name: Test coverage - run: bash -ex ci/test-coverage.sh - if: matrix.BUILD_TYPE == 'coverage' - - - name: Build, test and build AppImage - run: bash -ex ci/build.sh - if: matrix.BUILD_TYPE != 'coverage' + - name: Build + run: bash ci/build-in-docker.sh - name: Archive artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: matrix.BUILD_TYPE != 'coverage' with: - name: AppImage + name: AppImage ${{ matrix.ARCH }}${{ matrix.USE_STATIC_RUNTIME}} path: linuxdeploy*.AppImage* + freebsd-build-and-test: + runs-on: ubuntu-latest + steps: + - name: Repository checkout + uses: actions/checkout@v6 + with: + submodules: recursive + - uses: cross-platform-actions/action@v0.32.0 + with: + operating_system: 'freebsd' + version: '15.0' + architecture: 'x86_64' + run: | + # Use latest package set + sudo mkdir -p /usr/local/etc/pkg/repos/ + sudo cp /etc/pkg/FreeBSD.conf /usr/local/etc/pkg/repos/FreeBSD.conf + sudo sed -i.bak -e 's|/quarterly|/latest|' /usr/local/etc/pkg/repos/FreeBSD.conf + + # Install deps + sudo -E pkg install -y \ + bash cmake cimg googletest ninja patchelf pkgconf wget + + # Run the build, skipping plugins as these aren't ready yet + bash ci/build.sh --skip-plugins + upload: name: Create release and upload artifacts needs: - build-and-test - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Download artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 - name: Inspect directory after downloading artifacts run: ls -alFR - name: Create release and upload artifacts @@ -72,4 +99,4 @@ jobs: 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* + ./pyuploadtool-x86_64.AppImage ./**/linuxdeploy*.AppImage* diff --git a/CMakeLists.txt b/CMakeLists.txt index 14d7fcf4..6a070223 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ include(FetchContent) -cmake_minimum_required(VERSION 3.2) +cmake_minimum_required(VERSION 3.6) project(linuxdeploy C CXX) diff --git a/ci/build-in-docker.sh b/ci/build-in-docker.sh new file mode 100755 index 00000000..7b403ba8 --- /dev/null +++ b/ci/build-in-docker.sh @@ -0,0 +1,121 @@ +#! /bin/bash + +log_message() { + color="$1" + shift + if [ -t 0 ]; then tput setaf "$color"; fi + if [ -t 0 ]; then tput bold; fi + echo "$@" + if [ -t 0 ]; then tput sgr0; fi +} +info() { + log_message 2 "[info] $*" +} +warning() { + log_message 3 "[warning] $*" +} +error() { + log_message 1 "[error] $*" +} + +if [[ "$ARCH" == "" ]]; then + error "Usage: env ARCH=... bash $0" + exit 2 +fi +set -euo pipefail + +this_dir="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")" + +case "$ARCH" in + x86_64) + docker_platform=linux/amd64 + ;; + i386) + docker_platform=linux/386 + ;; + armhf) + docker_platform=linux/arm/v7 + ;; + aarch64) + docker_platform=linux/arm64/v8 + ;; + *) + echo "Unsupported \$ARCH: $ARCH" + exit 3 + ;; +esac + +# first, we need to build the image +# we always attempt to build it, it will only be rebuilt if Docker detects changes +# optionally, we'll pull the base image beforehand +info "Building Docker image for $ARCH (Docker platform: $docker_platform)" + +build_args=() +if [[ "${UPDATE:-}" == "" ]]; then + warning "\$UPDATE not set, base image will not be pulled!" +else + build_args+=("--pull") +fi + +image_tag="linuxdeploy-build" + +docker build \ + --build-arg ARCH="$ARCH" \ + --platform "$docker_platform" \ + "${build_args[@]}" \ + -t "$image_tag" \ + "$this_dir"/docker + +docker_args=() +# only if there's more than 1G of free space in RAM, we can build in a RAM disk +if [[ "${GITHUB_ACTIONS:-}" != "" ]]; then + warning "Building on GitHub actions, which does not support --tmpfs flag -> building on regular disk" +elif [[ "$(free -m | grep "Mem:" | awk '{print $4}')" -gt 1024 ]]; then + info "Host system has enough free memory -> building in RAM disk" + docker_args+=( + "--tmpfs" + "/docker-ramdisk:exec,mode=777" + ) +else + warning "Host system does not have enough free memory -> building on regular disk" +fi + +if [[ "${BUILD_TYPE:-}" == "coverage" ]]; then + build_script="ci/test-coverage.sh" +else + build_script="ci/build.sh" +fi + + +if [ -t 1 ]; then + # needed on unixoid platforms to properly terminate the docker run process with Ctrl-C + docker_args+=("-t") +fi + +DOCKER_OPTS=() +# fix for https://stackoverflow.com/questions/51195528/rcc-error-in-resource-qrc-cannot-find-file-png +if [ "${CI:-}" != "" ]; then + docker_args+=( + "--security-opt" + "seccomp:unconfined" + ) +fi + +# run the build with the current user to +# a) make sure root is not required for builds +# b) allow the build scripts to "mv" the binaries into the /out directory +uid="${UID:-"$(id -u)"}" +info "Running build with uid $uid" +docker run \ + --rm \ + -i \ + -e GITHUB_RUN_NUMBER \ + -e ARCH \ + -e BUILD_TYPE \ + -e CI \ + --user "$uid" \ + "${docker_args[@]}" \ + -v "$(readlink -f "$this_dir"/..):/ws" \ + -w /ws \ + "$image_tag" \ + bash -xc "$build_script" diff --git a/ci/build-static-patchelf.sh b/ci/build-static-patchelf.sh deleted file mode 100755 index 366a4990..00000000 --- a/ci/build-static-patchelf.sh +++ /dev/null @@ -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") - ;; - "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.15.0 - -# 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" diff --git a/ci/build.sh b/ci/build.sh index fdd1dc3a..2eb7dcdd 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -1,81 +1,88 @@ #! /bin/bash -set -e -set -x +set -euxo pipefail # use RAM disk if possible -if [ "$CI" == "" ] && [ -d /dev/shm ]; then - TEMP_BASE=/dev/shm +if [ -d /docker-ramdisk ]; then + TEMP_BASE=/docker-ramdisk else TEMP_BASE=/tmp fi -BUILD_DIR=$(mktemp -d -p "$TEMP_BASE" linuxdeploy-build-XXXXXX) +build_dir=$(mktemp -d -p "$TEMP_BASE" linuxdeploy-build-XXXXXX) cleanup () { - if [ -d "$BUILD_DIR" ]; then - rm -rf "$BUILD_DIR" + if [ -d "$build_dir" ]; then + rm -rf "$build_dir" fi } trap cleanup EXIT # store repo root as variable -REPO_ROOT=$(readlink -f $(dirname $(dirname $0))) -OLD_CWD=$(readlink -f .) - -pushd "$BUILD_DIR" - -if [ "$ARCH" == "x86_64" ]; then - EXTRA_CMAKE_ARGS=() -elif [ "$ARCH" == "i386" ]; then - EXTRA_CMAKE_ARGS=("-DCMAKE_TOOLCHAIN_FILE=$REPO_ROOT/cmake/toolchains/i386-linux-gnu.cmake" "-DUSE_SYSTEM_CIMG=OFF") -else - echo "Architecture not supported: $ARCH" 1>&2 - exit 1 +repo_root="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")"/.. +old_cwd="$(readlink -f "$PWD")" + +pushd "$build_dir" + +# work around ninja colors bug +extra_cmake_args=() +if [ -t 0 ]; then + extra_cmake_args+=( + "-DCMAKE_C_FLAGS=-fdiagnostics-color=always" + "-DCMAKE_CXX_FLAGS=-fdiagnostics-color=always" + ) fi -# fetch up-to-date CMake -mkdir cmake-prefix -wget -O- https://github.com/Kitware/CMake/releases/download/v3.18.1/cmake-3.18.1-Linux-x86_64.tar.gz | tar -xz -C cmake-prefix --strip-components=1 -export PATH="$(readlink -f cmake-prefix/bin):$PATH" -cmake --version - # configure build for AppImage release -cmake "$REPO_ROOT" -DSTATIC_BUILD=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo "${EXTRA_CMAKE_ARGS[@]}" - -make -j"$(nproc)" +cmake \ + -G Ninja \ + "$repo_root" \ + -DSTATIC_BUILD=ON \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + "${extra_cmake_args[@]}" -# build patchelf -"$REPO_ROOT"/ci/build-static-patchelf.sh "$(readlink -f out/)" -patchelf_path="$(readlink -f out/usr/bin/patchelf)" +nprocs="$(nproc)" +[[ "${CI:-}" == "" ]] && [[ "$nprocs" -gt 2 ]] && nprocs="$(nproc --ignore=1)" -# build custom strip -"$REPO_ROOT"/ci/build-static-binutils.sh "$(readlink -f out/)" -strip_path="$(readlink -f out/usr/bin/strip)" - -# use tools we just built for linuxdeploy test run -export PATH="$(readlink -f out/usr/bin):$PATH" +ninja -j"$nprocs" -v ## Run Unit Tests ctest -V # args are used more than once -LINUXDEPLOY_ARGS=("--appdir" "AppDir" "-e" "bin/linuxdeploy" "-i" "$REPO_ROOT/resources/linuxdeploy.png" "-d" "$REPO_ROOT/resources/linuxdeploy.desktop" "-e" "$patchelf_path" "-e" "$strip_path") +patchelf_path="$(which patchelf)" +strip_path="$(which strip)" +linuxdeploy_args=( + --appdir AppDir + -e bin/linuxdeploy + -i "$repo_root/resources/linuxdeploy.png" + -d "$repo_root/resources/linuxdeploy.desktop" + -e "$patchelf_path" + -e "$strip_path" +) # deploy patchelf which is a dependency of linuxdeploy -bin/linuxdeploy "${LINUXDEPLOY_ARGS[@]}" +bin/linuxdeploy "${linuxdeploy_args[@]}" # bundle AppImage plugin mkdir -p AppDir/plugins -wget https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-"$ARCH".AppImage -chmod +x linuxdeploy-plugin-appimage-"$ARCH".AppImage -./linuxdeploy-plugin-appimage-"$ARCH".AppImage --appimage-extract -mv squashfs-root/ AppDir/plugins/linuxdeploy-plugin-appimage +if [ $# -ge 1 ] && [ "$1" = "--skip-plugins" ]; then + exit 0 +fi + +# build linuxdeploy-plugin-appimage instead of using prebuilt versions +# this prevents a circular dependency +# the other repository provides a script for this purpose that builds a bundle we can use +git clone --recursive https://github.com/linuxdeploy/linuxdeploy-plugin-appimage +bash linuxdeploy-plugin-appimage/ci/build-bundle.sh +mv linuxdeploy-plugin-appimage-bundle AppDir/plugins/linuxdeploy-plugin-appimage -ln -s ../../plugins/linuxdeploy-plugin-appimage/AppRun AppDir/usr/bin/linuxdeploy-plugin-appimage +ln -s ../../plugins/linuxdeploy-plugin-appimage/usr/bin/linuxdeploy-plugin-appimage AppDir/usr/bin/linuxdeploy-plugin-appimage +# interpreted by linuxdeploy-plugin-appimage export UPD_INFO="gh-releases-zsync|linuxdeploy|linuxdeploy|continuous|linuxdeploy-$ARCH.AppImage.zsync" export OUTPUT="linuxdeploy-$ARCH.AppImage" @@ -85,10 +92,14 @@ AppDir/usr/bin/linuxdeploy-plugin-appimage --appdir AppDir/ # rename AppImage to avoid "Text file busy" issues when using it to create another one mv "$OUTPUT" test.AppImage +# qemu is not happy about the AppImage type 2 magic bytes, so we need to "fix" that +dd if=/dev/zero bs=1 count=3 seek=8 conv=notrunc of=test.AppImage + # verify that the resulting AppImage works -./test.AppImage "${LINUXDEPLOY_ARGS[@]}" +./test.AppImage "${linuxdeploy_args[@]}" # check whether AppImage plugin is found and works -./test.AppImage "${LINUXDEPLOY_ARGS[@]}" --output appimage +./test.AppImage "${linuxdeploy_args[@]}" --output appimage -mv "$OUTPUT"* "$OLD_CWD"/ +# using a glob because we want to include the .zsync file +mv "$OUTPUT"* "$old_cwd"/ diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile new file mode 100644 index 00000000..7a603dc0 --- /dev/null +++ b/ci/docker/Dockerfile @@ -0,0 +1,58 @@ +# generic Dockerfile for all architectures +# used to "cache" prebuilt binaries of tools we use internally and installed dependencies +# needs to be re-run in CI every time as we cannot store auto-built Docker images due to GitHub's strict quota +# it will save a lot of time in local development environments, though + +# we'll just use Debian as a base image for now, mainly because it produces less headache than Ubuntu with arm +# a big pro is that they ship an up to date CMake in their stable distribution +# also, they still provide IA-32 builds for some reason... +# some people in the AppImage community do not (want to) realize i386 is dead for good +# we are going to drop i686 in the future! +FROM debian:stable + +# variables that need to be availabe during build and runtime must(!) be repeated after FROM +ARG ARCH + +ENV APPIMAGE_EXTRACT_AND_RUN=1 + +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + file \ + xz-utils \ + wget \ + make \ + ca-certificates \ + ninja-build \ + gcc \ + g++ \ + gcovr \ + libmagic-dev \ + libjpeg-dev \ + libpng-dev \ + git \ + autoconf \ + automake \ + cimg-dev \ + cmake \ + googletest \ + gdb \ + libc6-dev \ + build-essential \ + python3-minimal \ + python-is-python3 && \ + apt-get clean + +# install into separate destdir to avoid polluting the $PATH with tools like ld that will break things +ENV TOOLS_DIR=/tools + +COPY install-static-binutils.sh / +RUN bash /install-static-binutils.sh + +COPY install-static-patchelf.sh / +RUN bash /install-static-patchelf.sh + +# make patchelf and strip available in $PATH +# they are static binaries, so we can just copy them +RUN cp "$TOOLS_DIR"/usr/bin/patchelf /usr/local/bin && \ + cp "$TOOLS_DIR"/usr/bin/strip /usr/local/bin diff --git a/ci/build-static-binutils.sh b/ci/docker/install-static-binutils.sh similarity index 54% rename from ci/build-static-binutils.sh rename to ci/docker/install-static-binutils.sh index 42150c1d..684d0e5a 100755 --- a/ci/build-static-binutils.sh +++ b/ci/docker/install-static-binutils.sh @@ -3,34 +3,6 @@ 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") - ;; - "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" @@ -55,5 +27,5 @@ make -j "$(nproc)" make clean make -j "$(nproc)" LDFLAGS="-all-static" -# install into user-specified destdir -make install DESTDIR="$(readlink -f "$INSTALL_DESTDIR")" +# install into separate destdir to avoid polluting the $PATH with tools like ld that will break things +make install DESTDIR="${TOOLS_DIR}" diff --git a/ci/docker/install-static-patchelf.sh b/ci/docker/install-static-patchelf.sh new file mode 100755 index 00000000..02a993f5 --- /dev/null +++ b/ci/docker/install-static-patchelf.sh @@ -0,0 +1,34 @@ +#! /bin/bash + +set -e +set -x + +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.15.0 + +# 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 separate destdir to avoid polluting the $PATH with tools like ld that will break things +make install DESTDIR="${TOOLS_DIR}" diff --git a/ci/entrypoint.sh b/ci/entrypoint.sh deleted file mode 100755 index e39f2ad4..00000000 --- a/ci/entrypoint.sh +++ /dev/null @@ -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 "$@" diff --git a/ci/test-coverage.sh b/ci/test-coverage.sh index af0ac4fe..b6cec5a7 100755 --- a/ci/test-coverage.sh +++ b/ci/test-coverage.sh @@ -1,11 +1,10 @@ #! /bin/bash -set -e -set -x +set -euxo pipefail # use RAM disk if possible -if [ "$CI" == "" ] && [ -d /dev/shm ]; then - TEMP_BASE=/dev/shm +if [ -d /docker-ramdisk ]; then + TEMP_BASE=/docker-ramdisk else TEMP_BASE=/tmp fi @@ -21,21 +20,14 @@ cleanup () { trap cleanup EXIT # store repo root as variable -REPO_ROOT=$(readlink -f $(dirname $(dirname $0))) -OLD_CWD=$(readlink -f .) +REPO_ROOT="$(readlink -f "$(dirname "$(dirname "${BASH_SOURCE[0]}")")")" pushd "$BUILD_DIR" -if [ "$ARCH" == "x86_64" ]; then - EXTRA_CMAKE_ARGS=() -elif [ "$ARCH" == "i386" ]; then - EXTRA_CMAKE_ARGS=("-DCMAKE_TOOLCHAIN_FILE=$REPO_ROOT/cmake/toolchains/i386-linux-gnu.cmake" "-DUSE_SYSTEM_CIMG=OFF") -else - echo "Architecture not supported: $ARCH" 1>&2 - exit 1 -fi +cmake "$REPO_ROOT" -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON -cmake "$REPO_ROOT" -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON "${EXTRA_CMAKE_ARGS[@]}" +nprocs="$(nproc)" +[[ "${CI:-}" == "" ]] && [[ "$nprocs" -gt 2 ]] && nprocs="$(nproc --ignore=1)" # build, run tests and show coverage report -make -j$(nproc) coverage_text +make -j"$nprocs" coverage_text diff --git a/cmake/Modules/FindCImg.cmake b/cmake/Modules/FindCImg.cmake index 343a1ae7..0ab720a0 100644 --- a/cmake/Modules/FindCImg.cmake +++ b/cmake/Modules/FindCImg.cmake @@ -70,6 +70,7 @@ else() 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_LIBRARY_DIRS "${libpng_LIBRARY_DIRS};${libjpeg_LIBRARY_DIRS}") set(CImg_DEFINITIONS "cimg_display=0;cimg_use_png=1;cimg_use_jpeg=1") file(READ "${CIMG_H_DIR}/CImg.h" header) @@ -82,6 +83,7 @@ else() 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_LINK_DIRECTORIES "${CImg_LIBRARY_DIRS}") set_property(TARGET CImg PROPERTY INTERFACE_COMPILE_DEFINITIONS "${CImg_DEFINITIONS}") endif() endif() diff --git a/include/linuxdeploy/core/elf_file.h b/include/linuxdeploy/core/elf_file.h index 9ffebc4d..3ced6a23 100644 --- a/include/linuxdeploy/core/elf_file.h +++ b/include/linuxdeploy/core/elf_file.h @@ -46,7 +46,7 @@ namespace linuxdeploy { // 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 traceDynamicDependencies(); + std::vector traceDynamicDependencies(const std::vector& excludeLibraryPatterns={}); // fetch rpath stored in binary // it appears that according to the ELF standard, the rpath is ignored in libraries, therefore if the path diff --git a/include/linuxdeploy/core/log.h b/include/linuxdeploy/core/log.h deleted file mode 100644 index 575a8260..00000000 --- a/include/linuxdeploy/core/log.h +++ /dev/null @@ -1,71 +0,0 @@ -// system includes -#include -#include - -#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 > 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 std::filesystem::path& path); - ldLog operator<<(const int val); - ldLog operator<<(const size_t val); - ldLog operator<<(const double val); - 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); - }; - } - } -} diff --git a/include/linuxdeploy/log/log.h b/include/linuxdeploy/log/log.h new file mode 100644 index 00000000..ca2476be --- /dev/null +++ b/include/linuxdeploy/log/log.h @@ -0,0 +1,67 @@ +// system includes +#include +#include + +#pragma once + +namespace linuxdeploy::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 > 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 std::filesystem::path& path); + ldLog operator<<(const int val); + ldLog operator<<(const size_t val); + ldLog operator<<(const double val); + 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); + }; +} diff --git a/include/linuxdeploy/plugin/base.h b/include/linuxdeploy/plugin/base.h index 9042a630..7c7b1da9 100644 --- a/include/linuxdeploy/plugin/base.h +++ b/include/linuxdeploy/plugin/base.h @@ -3,7 +3,7 @@ #include // local includes -#include "linuxdeploy/core/log.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/plugin/plugin.h" #pragma once diff --git a/include/linuxdeploy/plugin/base_impl.h b/include/linuxdeploy/plugin/base_impl.h index b0c722b1..6bb2d593 100644 --- a/include/linuxdeploy/plugin/base_impl.h +++ b/include/linuxdeploy/plugin/base_impl.h @@ -11,7 +11,7 @@ #include // local headers -#include "linuxdeploy/core/log.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/util/util.h" #include "linuxdeploy/subprocess/process.h" #include "linuxdeploy/plugin/plugin_process_handler.h" @@ -23,7 +23,7 @@ namespace linuxdeploy { namespace plugin { namespace base { - using namespace linuxdeploy::core::log; + using namespace linuxdeploy::log; template class PluginBase::PrivateData { @@ -39,6 +39,8 @@ namespace linuxdeploy { throw PluginError("No such file or directory: " + path.string()); } + ldLog() << LD_DEBUG << "Probing plugin" << path.string() << std::endl; + apiLevel = getApiLevelFromExecutable(); pluginType = getPluginTypeFromExecutable(); diff --git a/include/linuxdeploy/plugin/plugin.h b/include/linuxdeploy/plugin/plugin.h index 0f8dd366..c3046e3c 100644 --- a/include/linuxdeploy/plugin/plugin.h +++ b/include/linuxdeploy/plugin/plugin.h @@ -5,7 +5,7 @@ #include // local includes -#include "linuxdeploy/core/log.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/plugin/exceptions.h" #pragma once diff --git a/include/linuxdeploy/util/misc.h b/include/linuxdeploy/util/misc.h index f954ce7f..eceac1f8 100644 --- a/include/linuxdeploy/util/misc.h +++ b/include/linuxdeploy/util/misc.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -135,7 +136,37 @@ namespace linuxdeploy { } return {}; - }; + } + + // returns a string vector splitted from envVar + static std::vector splitEnv(const char *envVar, char delimiter) { + std::vector result; + const auto ret = getenv(envVar); + if (ret) { + result = split(ret, delimiter); + } + return result; + } + + static bool isInExcludelist(const std::filesystem::path& fileName, const std::vector &excludeList) { + for (const auto& excludePattern : excludeList) { + // 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: + return false; + } + } + + return false; + } } } } diff --git a/lib/linuxdeploy-desktopfile b/lib/linuxdeploy-desktopfile index aa742353..bea49b10 160000 --- a/lib/linuxdeploy-desktopfile +++ b/lib/linuxdeploy-desktopfile @@ -1 +1 @@ -Subproject commit aa7423539d6ecdb6eb3bc6c6607247d3f7ff8bb3 +Subproject commit bea49b10f7ce1992b096209447e5d4afd7ee51c5 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f24325e8..d01478ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ execute_process( add_subdirectory(util) add_subdirectory(plugin) +add_subdirectory(log) add_subdirectory(subprocess) add_subdirectory(core) @@ -52,7 +53,7 @@ endif() add_executable(plugin_test plugin_test_main.cpp) -target_link_libraries(plugin_test linuxdeploy_plugin) +target_link_libraries(plugin_test linuxdeploy_plugin CImg) set_target_properties(plugin_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") add_executable(appdir_test appdir_test_main.cpp ../include/linuxdeploy/util/assert.h) diff --git a/src/core.cpp b/src/core.cpp index 2800472e..a8a0706b 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -6,11 +6,11 @@ // local headers #include -#include +#include #include "core.h" using namespace linuxdeploy::core; -using namespace linuxdeploy::core::log; +using namespace linuxdeploy::log; using namespace linuxdeploy::desktopfile; namespace fs = std::filesystem; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 11dad5fb..0632c709 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.6) # include headers to make CLion happy file(GLOB HEADERS ${PROJECT_SOURCE_DIR}/include/linuxdeploy/core/*.h) @@ -34,14 +34,11 @@ execute_process( WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) -add_library(linuxdeploy_core_log STATIC log.cpp) -target_include_directories(linuxdeploy_core_log PUBLIC ${PROJECT_SOURCE_DIR}/include) - add_subdirectory(copyright) add_library(linuxdeploy_core STATIC elf_file.cpp appdir.cpp ${HEADERS} appdir_root_setup.cpp) target_link_libraries(linuxdeploy_core PUBLIC - linuxdeploy_plugin linuxdeploy_core_log linuxdeploy_util linuxdeploy_desktopfile_static + linuxdeploy_plugin linuxdeploy_log linuxdeploy_util linuxdeploy_desktopfile_static CImg ${CMAKE_THREAD_LIBS_INIT} ) target_link_libraries(linuxdeploy_core PRIVATE linuxdeploy_core_copyright) diff --git a/src/core/appdir.cpp b/src/core/appdir.cpp index d6b510d1..8f31102d 100644 --- a/src/core/appdir.cpp +++ b/src/core/appdir.cpp @@ -8,17 +8,15 @@ // library headers #include -#include // local headers #include "linuxdeploy/core/appdir.h" #include "linuxdeploy/core/elf_file.h" -#include "linuxdeploy/core/log.h" -#include "linuxdeploy/desktopfile/desktopfileentry.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/util/util.h" #include "linuxdeploy/subprocess/subprocess.h" -#include "copyright.h" +#include "copyright/copyright.h" // auto-generated headers #include "excludelist.h" @@ -26,7 +24,7 @@ using namespace linuxdeploy::core; using namespace linuxdeploy::desktopfile; -using namespace linuxdeploy::core::log; +using namespace linuxdeploy::log; using namespace cimg_library; @@ -124,9 +122,11 @@ namespace linuxdeploy { bool disableCopyrightFilesDeployment = false; public: - PrivateData() : copyOperationsStorage(), stripOperations(), setElfRPathOperations(), visitedFiles(), appDirPath(), excludeLibraryPatterns() { + PrivateData() : copyOperationsStorage(), stripOperations(), setElfRPathOperations(), visitedFiles(), appDirPath() { copyrightFilesManager = copyright::ICopyrightFilesManager::getInstance(); - }; + + excludeLibraryPatterns = util::misc::splitEnv("LINUXDEPLOY_EXCLUDED_LIBRARIES", ';'); + } public: // calculate library directory name for given ELF file, taking system architecture into account @@ -158,7 +158,7 @@ namespace linuxdeploy { return false; } - if (*(to.string().end() - 1) == '/' || fs::is_directory(to)) + if (to.string().back() == '/' || fs::is_directory(to)) to /= from.filename(); if (!overwrite && fs::exists(to)) { @@ -356,9 +356,16 @@ namespace linuxdeploy { } bool deployElfDependencies(const fs::path& path) { + elf_file::ElfFile elfFile(path); + + if (!elfFile.isDynamicallyLinked()) { + ldLog() << LD_WARNING << "ELF file" << path << "is not dynamically linked, skipping" << std::endl; + return true; + } + ldLog() << "Deploying dependencies for ELF file" << path << std::endl; try { - for (const auto &dependencyPath : elf_file::ElfFile(path).traceDynamicDependencies()) + for (const auto &dependencyPath : elfFile.traceDynamicDependencies(excludeLibraryPatterns)) if (!deployLibrary(dependencyPath, false, false)) return false; } catch (const elf_file::DependencyNotFoundError& e) { @@ -402,28 +409,7 @@ namespace linuxdeploy { return false; } - static auto isInExcludelist = [](const fs::path& fileName, const std::vector &excludeList) { - for (const auto& excludePattern : excludeList) { - // 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 (!forceDeploy && (isInExcludelist(path.filename(), generatedExcludelist) || isInExcludelist(path.filename(), excludeLibraryPatterns))) { + if (!forceDeploy && (util::isInExcludelist(path.filename(), generatedExcludelist) || util::isInExcludelist(path.filename(), excludeLibraryPatterns))) { ldLog() << "Skipping deployment of blacklisted library" << path << std::endl; // mark file as visited @@ -650,7 +636,7 @@ namespace linuxdeploy { AppDir::AppDir(const std::string& path) : AppDir(fs::path(path)) {} void AppDir::setExcludeLibraryPatterns(const std::vector &excludeLibraryPatterns) { - d->excludeLibraryPatterns = excludeLibraryPatterns; + d->excludeLibraryPatterns.insert(d->excludeLibraryPatterns.end(), excludeLibraryPatterns.begin(), excludeLibraryPatterns.end()); } bool AppDir::createBasicStructure() const { @@ -659,6 +645,7 @@ namespace linuxdeploy { "usr/lib/", "usr/share/applications/", "usr/share/icons/hicolor/", + "usr/share/pixmaps/", }; for (const std::string& resolution : {"16x16", "32x32", "64x64", "128x128", "256x256", "scalable"}) { @@ -718,37 +705,52 @@ namespace linuxdeploy { return d->appDirPath; } - static std::vector listFilesInDirectory(const fs::path& path, const bool recursive = true) { - std::vector foundPaths; - + template + static void forEachInDirectory(const fs::path& path, const bool recursive, Consumer&& consumer) { // directory_iterators throw exceptions if the directory doesn't exist if (!fs::is_directory(path)) { ldLog() << LD_DEBUG << "No such directory:" << path << std::endl; - return {}; + return; } if (recursive) { - for (fs::recursive_directory_iterator i(path); i != fs::recursive_directory_iterator(); ++i) { - if (fs::is_regular_file(*i)) { - foundPaths.push_back((*i).path()); - } - } + std::for_each(fs::recursive_directory_iterator(path), fs::recursive_directory_iterator(), consumer); } else { - for (fs::directory_iterator i(path); i != fs::directory_iterator(); ++i) { - if (fs::is_regular_file(*i)) { - foundPaths.push_back((*i).path()); - } - } + std::for_each(fs::directory_iterator(path), fs::directory_iterator(), consumer); } + } + static std::vector listFilesInDirectory(const fs::path& path, const bool recursive = true) { + std::vector foundPaths; + forEachInDirectory(path, recursive, [&foundPaths](const fs::directory_entry& dirEntry) { + if (fs::is_regular_file(dirEntry.status())) + foundPaths.push_back(dirEntry.path()); + }); return foundPaths; } - std::vector AppDir::deployedIconPaths() const { - auto icons = listFilesInDirectory(path() / "usr/share/icons/"); - auto pixmaps = listFilesInDirectory(path() / "usr/share/pixmaps/", false); - icons.reserve(pixmaps.size()); - std::copy(pixmaps.begin(), pixmaps.end(), std::back_inserter(icons)); + std::vector AppDir::deployedIconPaths() const + { + // Rough equivalent in shell: + // appIconDirs=`ls -d $APPDIR/usr/share/icons/hicolor/*/apps/ $APPDIR/usr/share/pixmaps/` + std::vector appIconDirs; + forEachInDirectory(path() / "usr/share/icons/hicolor/", false, + [&appIconDirs](const fs::directory_entry &dirEntry) { + if (fs::is_directory(dirEntry.status())) + appIconDirs.emplace_back(dirEntry.path() / "apps/"); + }); + appIconDirs.emplace_back(path() / "usr/share/pixmaps/"); + + // for dirEntry in $appIconDirs; do icons="$icons `ls $dirEntry/*.{svg,png,xpm}`"; done + std::vector icons; + for (const auto& dir : appIconDirs) { + forEachInDirectory(dir, false, [&icons](const fs::directory_entry& dirEntry) { + const auto extension = util::strLower(dirEntry.path().extension().string()); + if ((extension == ".svg" || extension == ".png" || extension == ".xpm") + && fs::is_regular_file(dirEntry.status())) + icons.emplace_back(dirEntry.path()); + }); + } return icons; } diff --git a/src/core/appdir_root_setup.cpp b/src/core/appdir_root_setup.cpp index cca32184..5a058773 100644 --- a/src/core/appdir_root_setup.cpp +++ b/src/core/appdir_root_setup.cpp @@ -3,7 +3,7 @@ // local headers #include -#include +#include #include "appdir_root_setup.h" namespace fs = std::filesystem; @@ -34,6 +34,30 @@ namespace linuxdeploy { ); } + public: + static int getIconPreference(const fs::path &iconPath) { + int iconWidth = 0; + try { + // Assuming that iconPath ends with WxH[@D]/apps/*.*, pick the W component, taking + // the D component into account if it's there + const auto dirName = iconPath.parent_path().parent_path().filename().string(); + const auto dpiPos = dirName.rfind('@'); + iconWidth = std::stoi(dirName) + * (dpiPos != std::string::npos ? std::stoi(dirName.substr(dpiPos + 1)) : 1); + } catch (const std::logic_error &) { + ldLog() << LD_WARNING << "Icon size of" << iconPath << "could not be determined" << std::endl; + // size remains zero + } + + // Preference takes values from 0 to 100, with the highest value reached for 64x64 icons, going down + // as width goes from 64 either way. For "equally remote" sizes (32x32 and 128x128, e.g.) the larger + // icons are preferred (preference value for 32x32 is 49 while 128x128 are at 50). + // For other examples, 96x96 yields 66, 48x48 gets 73, and so on. + // Also, since size cannot be determined for icons in pixmaps/, preference for these is 0; in other + // words, icons in /usr/share/icons/hicolor are preferred, in alignment with the icon naming spec + return iconWidth < 64 ? 100 * iconWidth / 65 : 6400 / iconWidth; + } + public: bool deployDesktopFileAndIcon(const DesktopFile& desktopFile) const { ldLog() << "Deploying desktop file to AppDir root:" << desktopFile.path() << std::endl; @@ -47,46 +71,109 @@ namespace linuxdeploy { // look for suitable icon DesktopFileEntry iconEntry; - if (!desktopFile.getEntry("Desktop Entry", "Icon", iconEntry)) { + if (!desktopFile.getEntry("Desktop Entry", "Icon", iconEntry) || iconEntry.value().empty()) { ldLog() << LD_ERROR << "Icon entry missing in desktop file:" << desktopFile.path() << std::endl; return false; } - bool iconDeployed = false; + const auto iconName = iconEntry.value(); - const auto foundIconPaths = appDir.deployedIconPaths(); + auto foundIconPaths = appDir.deployedIconPaths(); if (foundIconPaths.empty()) { ldLog() << LD_ERROR << "Could not find icon executable for Icon entry:" << iconEntry.value() << std::endl; return false; } - for (const auto& iconPath : foundIconPaths) { - ldLog() << LD_DEBUG << "Icon found:" << iconPath << std::endl; + // There's no way of knowing the target environment where an AppImage icon will be shown + // therefore: + // - SVG is prefered over raster icons + // - 64x64 is picked as a reasonable best size for raster icons; + // the farther the icon dimensions are from that, the less preferred the icon is + auto bestIcon = foundIconPaths.end(); + for (auto iconPath = foundIconPaths.begin(); iconPath != foundIconPaths.end(); ++iconPath) { + if (iconPath->string().size() < iconName.size()) + continue; // No chance to match anything + + if (iconName.front() == '/') { // Full path to the icon specified + const auto iconPathStr = iconPath->string(); + // Check if the current icon path ends with the path specified in the desktop entry + // (strictly speaking, it should also start with $DESTDIR; this is left for another time) + if (std::equal(iconName.rbegin(), iconName.rend(), iconPathStr.rbegin())) { + bestIcon = iconPath; + break; + } + continue; + } - const bool matchesFilenameWithExtension = iconPath.filename() == iconEntry.value(); + const bool matchesFilenameWithExtension = iconPath->filename() == iconName; - if (iconPath.stem() == iconEntry.value() || matchesFilenameWithExtension) { - if (matchesFilenameWithExtension) { - ldLog() << LD_WARNING << "Icon= entry filename contains extension" << std::endl; - } + if (iconPath->stem() != iconEntry.value() && !matchesFilenameWithExtension) + continue; - ldLog() << "Deploying icon to AppDir root:" << iconPath << std::endl; + ldLog() << LD_DEBUG << "Icon found:" << *iconPath << std::endl; - if (!appDir.createRelativeSymlink(iconPath, appDir.path())) { - ldLog() << LD_ERROR << "Failed to create symlink for icon in AppDir root:" << iconPath << std::endl; - return false; - } + if (matchesFilenameWithExtension) { + ldLog() << LD_WARNING << "Icon= entry filename contains extension" << std::endl; + } + if (bestIcon == foundIconPaths.end()) { + bestIcon = iconPath; + continue; + } - iconDeployed = true; - break; + // From here, the code comparing the current icon and the so far best icon begins + + const auto currentExtension = util::strLower(iconPath->extension().string()); + const auto bestIconExtension = util::strLower(bestIcon->extension().string()); + // SVGs are preferred, and (normally) only come in scalable/apps/; process them early + if (currentExtension == ".svg") { + // There's only one spec-compliant place for an SVG icon (icons/scalable/apps); but if + // a full filename is used in the desktop file (Icon=a.svg) then two SVG icons can + // match it: scalable/apps/a.svg and scalable/apps/a.svg.svg; in this case a.svg wins + if (matchesFilenameWithExtension || bestIconExtension != ".svg") + bestIcon = iconPath; + + break; // Further icons can't be better than what bestIcon has now. } + + // As of here, the _current_ icon is a raster one (PNG or XPM) + + if (bestIconExtension == ".svg" // SVG is always better + || (!matchesFilenameWithExtension && bestIcon->filename() == iconName)) // Better filename match + continue; + + // Both icons are raster + + if (matchesFilenameWithExtension && bestIcon->filename() != iconName) { // The other way around + bestIcon = iconPath; + continue; + } + + // Get preferences (declared) sizes of the icons from the directory name and compare. + // + // The code diverts from Icon Naming Spec here, since the spec relies on reading + // index.theme data and taking Threshold and MinSize/MaxSize values from there. Instead, + // merely figure which size is "logarithmically further" from the sweet spot of 64, + // preferring larger icons in case of a tie (see getIconPreference() implementation); + // as a last resort, if the preference is the same (e.g. two icons deployed to pixmaps/), + // PNGs win over XPMs. + const auto currentPreference = getIconPreference(*iconPath); + const auto bestPreference = getIconPreference(*bestIcon); + if (currentPreference > bestPreference + || (currentPreference == bestPreference && currentExtension < bestIconExtension)) + bestIcon = iconPath; } - if (!iconDeployed) { + if (bestIcon == foundIconPaths.end()) { ldLog() << LD_ERROR << "Could not find suitable icon for Icon entry:" << iconEntry.value() << std::endl; return false; } + ldLog() << "Deploying icon to AppDir root:" << *bestIcon << std::endl; + + if (!appDir.createRelativeSymlink(*bestIcon, appDir.path())) { + ldLog() << LD_ERROR << "Failed to create symlink for icon in AppDir root:" << *bestIcon << std::endl; + return false; + } return true; } diff --git a/src/core/copyright/CMakeLists.txt b/src/core/copyright/CMakeLists.txt index be512044..113eb2c6 100644 --- a/src/core/copyright/CMakeLists.txt +++ b/src/core/copyright/CMakeLists.txt @@ -1,7 +1,5 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.6) add_library(linuxdeploy_core_copyright STATIC copyright.cpp copyright.h copyright_dpkgquery.cpp copyright_dpkgquery.h) target_link_libraries(linuxdeploy_core_copyright PUBLIC linuxdeploy_util) - -target_include_directories(linuxdeploy_core_copyright PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/core/copyright/copyright.cpp b/src/core/copyright/copyright.cpp index 1bf55962..2d61ed70 100644 --- a/src/core/copyright/copyright.cpp +++ b/src/core/copyright/copyright.cpp @@ -1,5 +1,5 @@ // local includes -#include "linuxdeploy/core/log.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/util/util.h" #include "copyright.h" diff --git a/src/core/copyright/copyright_dpkgquery.cpp b/src/core/copyright/copyright_dpkgquery.cpp index f3a2fe77..853bdb55 100644 --- a/src/core/copyright/copyright_dpkgquery.cpp +++ b/src/core/copyright/copyright_dpkgquery.cpp @@ -1,6 +1,6 @@ // local includes #include "copyright_dpkgquery.h" -#include "linuxdeploy/core/log.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/util/util.h" #include "linuxdeploy/subprocess/subprocess.h" @@ -21,7 +21,8 @@ namespace linuxdeploy { auto result = proc.run(); if (result.exit_code() != 0 || result.stdout_contents().empty()) { - ldLog() << LD_WARNING << "Could not find copyright files for file" << path << "using dpkg-query" << std::endl; + ldLog() << LD_WARNING << "Could not find copyright files for file" << path << "using dpkg-query" + << std::endl; return {}; } @@ -34,7 +35,8 @@ namespace linuxdeploy { return {copyrightFilePath}; } } else { - ldLog() << LD_WARNING << "Could not find copyright files for file" << path << "using dpkg-query" << std::endl; + ldLog() << LD_WARNING << "Could not find copyright files for file" << path << "using dpkg-query" + << std::endl; } return {}; diff --git a/src/core/elf_file.cpp b/src/core/elf_file.cpp index aa1c6257..c8772ac3 100644 --- a/src/core/elf_file.cpp +++ b/src/core/elf_file.cpp @@ -9,11 +9,11 @@ // local headers #include "linuxdeploy/core/elf_file.h" -#include "linuxdeploy/core/log.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/util/util.h" #include "linuxdeploy/subprocess/subprocess.h" -using namespace linuxdeploy::core::log; +using namespace linuxdeploy::log; namespace fs = std::filesystem; @@ -183,12 +183,15 @@ namespace linuxdeploy { delete d; } - std::vector ElfFile::traceDynamicDependencies() { + std::vector ElfFile::traceDynamicDependencies(const std::vector& excludeLibraryPatterns) { // 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 + // of course, it makes no sense to call this method on statically linked binaries + assert(isDynamicallyLinked()); + std::vector paths; auto env = subprocess::get_environment(); @@ -244,7 +247,10 @@ namespace linuxdeploy { missingLib.erase(missingLib.find(pattern), pattern.size()); util::trim(missingLib); util::trim(missingLib, '\t'); - throw DependencyNotFoundError("Could not find dependency: " + missingLib); + if (!util::isInExcludelist(missingLib, excludeLibraryPatterns)) { + throw DependencyNotFoundError("Could not find dependency: " + missingLib); + } + ldLog() << LD_WARNING << resolvedPath.string() << "depends on excluded library:" << missingLib << std::endl; } else { ldLog() << LD_DEBUG << "Invalid ldd output: " << line << std::endl; } diff --git a/src/core/generate-excludelist.sh b/src/core/generate-excludelist.sh index d92cb130..8e03fc10 100644 --- a/src/core/generate-excludelist.sh +++ b/src/core/generate-excludelist.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Copyright 2018 Alexander Gottwald (https://github.com/ago1024) # Copyright 2018 TheAssassin (https://github.com/TheAssassin) diff --git a/src/core/log.cpp b/src/core/log.cpp deleted file mode 100644 index 90f30eaf..00000000 --- a/src/core/log.cpp +++ /dev/null @@ -1,131 +0,0 @@ -// 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 std::filesystem::path& path) { - if (checkVerbosity()) { - checkPrependSpace(); - stream << path.string(); - } - - return ldLog(true, logLevelSet, currentLogLevel); - } - - ldLog ldLog::operator<<(const int val) { - return ldLog::operator<<(std::to_string(val)); - } - - ldLog ldLog::operator<<(const size_t val) { - return ldLog::operator<<(std::to_string(val)); - } - - 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); - } - - void ldLog::write(const char* s, const size_t n) { - stream.write(s, n); - } - } - } -} diff --git a/src/log/CMakeLists.txt b/src/log/CMakeLists.txt new file mode 100644 index 00000000..a02c94cf --- /dev/null +++ b/src/log/CMakeLists.txt @@ -0,0 +1,7 @@ +set(headers_dir ${PROJECT_SOURCE_DIR}/include/linuxdeploy/log) + +add_library(linuxdeploy_log OBJECT + log.cpp + ${headers_dir}/log.h +) +target_include_directories(linuxdeploy_log PUBLIC ${PROJECT_SOURCE_DIR}/include) diff --git a/src/log/log.cpp b/src/log/log.cpp new file mode 100644 index 00000000..65c2f634 --- /dev/null +++ b/src/log/log.cpp @@ -0,0 +1,127 @@ +// local includes +#include "linuxdeploy/log/log.h" + +namespace linuxdeploy::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 std::filesystem::path& path) { + if (checkVerbosity()) { + checkPrependSpace(); + stream << path.string(); + } + + return ldLog(true, logLevelSet, currentLogLevel); + } + + ldLog ldLog::operator<<(const int val) { + return ldLog::operator<<(std::to_string(val)); + } + + ldLog ldLog::operator<<(const size_t val) { + return ldLog::operator<<(std::to_string(val)); + } + + 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); + } + + void ldLog::write(const char* s, const size_t n) { + stream.write(s, n); + } +} diff --git a/src/main.cpp b/src/main.cpp index b6dd749a..bb99ee24 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ // system headers -#include #include // library headers @@ -8,15 +7,14 @@ // local headers #include "linuxdeploy/core/appdir.h" #include "linuxdeploy/desktopfile/desktopfile.h" -#include "linuxdeploy/core/elf_file.h" -#include "linuxdeploy/core/log.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/plugin/plugin.h" #include "linuxdeploy/util/util.h" #include "core.h" using namespace linuxdeploy; using namespace linuxdeploy::core; -using namespace linuxdeploy::core::log; +using namespace linuxdeploy::log; using namespace linuxdeploy::util; namespace fs = std::filesystem; @@ -37,7 +35,7 @@ int main(int argc, char** argv) { args::ValueFlagList executablePaths(parser, "executable", "Executable to deploy", {'e', "executable"}); - args::ValueFlagList deployDepsOnlyPaths(parser, "path", "Path to ELF file or directory containing such files (libraries or executables) in the AppDir whose dependencies shall be deployed by linuxdeploy without copying them into the AppDir", {"deploy-deps-only"}); + args::ValueFlagList deployDepsOnlyPaths(parser, "path", "Path to ELF file or directory containing such files (libraries or executables) already present in the AppDir whose dependencies shall be deployed by linuxdeploy without copying them again into the AppDir. rpath for these libraries or executables will be updated accordingly.", {"deploy-deps-only"}); args::ValueFlagList 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"}); @@ -212,7 +210,7 @@ int main(int argc, char** argv) { ldLog() << std::endl << "-- Running input plugin:" << pluginName << "--" << std::endl; if (it == foundPlugins.end()) { - ldLog() << LD_ERROR << "Could not find plugin:" << pluginName; + ldLog() << LD_ERROR << "Could not find plugin:" << pluginName << std::endl; return 1; } @@ -338,7 +336,7 @@ int main(int argc, char** argv) { ldLog() << std::endl << "-- Running output plugin:" << pluginName << "--" << std::endl; if (it == foundPlugins.end()) { - ldLog() << LD_ERROR << "Could not find plugin:" << pluginName; + ldLog() << LD_ERROR << "Could not find plugin:" << pluginName << std::endl; return 1; } diff --git a/src/plugin/CMakeLists.txt b/src/plugin/CMakeLists.txt index 9617b2bd..073eb07d 100644 --- a/src/plugin/CMakeLists.txt +++ b/src/plugin/CMakeLists.txt @@ -16,7 +16,4 @@ add_library(linuxdeploy_plugin STATIC ) target_link_libraries(linuxdeploy_plugin PUBLIC linuxdeploy_core linuxdeploy_subprocess) -unset(headers) -unset(headers_dir) - install(TARGETS linuxdeploy_plugin) diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp index aac34399..d774069f 100644 --- a/src/plugin/plugin.cpp +++ b/src/plugin/plugin.cpp @@ -3,20 +3,14 @@ #include #include #include -#include - -// library headers -#include // local headers -#include "linuxdeploy/core/log.h" -#include "linuxdeploy/plugin/base.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/plugin/plugin.h" #include "linuxdeploy/util/util.h" #include "plugin_type0.h" -using namespace linuxdeploy::core; -using namespace linuxdeploy::core::log; +using namespace linuxdeploy::log; namespace fs = std::filesystem; @@ -54,7 +48,11 @@ namespace linuxdeploy { // also, look for plugins in current working directory // could be useful in a "use linuxdeploy centrally, but download plugins into project directory" scenario +#ifdef _GNU_SOURCE std::shared_ptr cwd(get_current_dir_name(), free); +#else + std::shared_ptr cwd(getcwd(nullptr, 0), free); +#endif paths.emplace_back(cwd.get()); for (const auto& dir : paths) { diff --git a/src/plugin/plugin_process_handler.cpp b/src/plugin/plugin_process_handler.cpp index 07c41aae..49bfb1b7 100644 --- a/src/plugin/plugin_process_handler.cpp +++ b/src/plugin/plugin_process_handler.cpp @@ -1,21 +1,21 @@ // system headers +#include #include #include -#include #include // local headers #include #include #include -#include +#include #include namespace fs = std::filesystem; namespace linuxdeploy { namespace plugin { - using namespace core::log; + using namespace log; plugin_process_handler::plugin_process_handler(std::string name, fs::path path) : name_(std::move(name)), path_(std::move(path)) {} diff --git a/src/plugin/plugin_type0.cpp b/src/plugin/plugin_type0.cpp index bb16c16d..d1cfee91 100644 --- a/src/plugin/plugin_type0.cpp +++ b/src/plugin/plugin_type0.cpp @@ -1,19 +1,8 @@ // system headers #include -#include -#include -#include - -// library headers -#include - -// local headers -#include "linuxdeploy/core/log.h" -#include "linuxdeploy/plugin/plugin.h" #include "plugin_type0.h" -using namespace linuxdeploy::core; -using namespace linuxdeploy::core::log; +using namespace linuxdeploy::log; namespace fs = std::filesystem; diff --git a/src/plugin/plugin_type0.h b/src/plugin/plugin_type0.h index 23c4fd19..c7f0e8a8 100644 --- a/src/plugin/plugin_type0.h +++ b/src/plugin/plugin_type0.h @@ -3,7 +3,7 @@ #include // local headers -#include "linuxdeploy/core/log.h" +#include "linuxdeploy/log/log.h" #include "linuxdeploy/plugin/base.h" #pragma once diff --git a/src/subprocess/CMakeLists.txt b/src/subprocess/CMakeLists.txt index 3e93a309..4092cf4b 100644 --- a/src/subprocess/CMakeLists.txt +++ b/src/subprocess/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(linuxdeploy_subprocess STATIC ${headers_dir}/util.h ) target_include_directories(linuxdeploy_subprocess PUBLIC ${PROJECT_SOURCE_DIR}/include) +target_link_libraries(linuxdeploy_subprocess PUBLIC linuxdeploy_log) add_executable(subprocess_demo subprocess_demo.cpp) target_link_libraries(subprocess_demo PUBLIC linuxdeploy_subprocess) diff --git a/src/subprocess/pipe_reader.cpp b/src/subprocess/pipe_reader.cpp index f9c64101..df4ffbe5 100644 --- a/src/subprocess/pipe_reader.cpp +++ b/src/subprocess/pipe_reader.cpp @@ -56,4 +56,6 @@ pipe_reader::result pipe_reader::read(std::vector& buff // this is a should-never-ever-happen case, a return value not handled by the lines above is actually not possible throw std::runtime_error{"unexpected return value from pollfd"}; } + + throw std::runtime_error("code should be unreachable"); } diff --git a/src/subprocess/process.cpp b/src/subprocess/process.cpp index 72f05bc3..01ba67b8 100644 --- a/src/subprocess/process.cpp +++ b/src/subprocess/process.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include // local headers diff --git a/src/subprocess/subprocess.cpp b/src/subprocess/subprocess.cpp index 92e0d803..d7114d65 100644 --- a/src/subprocess/subprocess.cpp +++ b/src/subprocess/subprocess.cpp @@ -1,20 +1,26 @@ // system headers +#include #include +#include #include #include #include #include #include #include +#include // local headers #include "linuxdeploy/subprocess/subprocess.h" #include "linuxdeploy/subprocess/process.h" #include "linuxdeploy/subprocess/pipe_reader.h" +#include "linuxdeploy/subprocess/subprocess_result.h" #include "linuxdeploy/util/assert.h" +#include "linuxdeploy/log/log.h" namespace linuxdeploy { namespace subprocess { + using namespace log; subprocess::subprocess(std::initializer_list args) : subprocess(std::vector(args), get_environment()) {} @@ -37,7 +43,7 @@ namespace linuxdeploy { class PipeState { public: pipe_reader reader; - subprocess_result_buffer_t buffer; + subprocess_result_buffer_t buffer; bool eof = false; explicit PipeState(int fd) : reader(fd) {} @@ -52,18 +58,19 @@ namespace linuxdeploy { }; for (;;) { - for (auto& pipe_state : buffers) { + for (auto& pipe_state: buffers) { // read some bytes into smaller intermediate buffer to prevent either of the pipes to overflow // the results are immediately appended to the main buffer subprocess_result_buffer_t intermediate_buffer(4096); // (try to) read all available data from pipe - for (; !pipe_state.eof; ) { + for (; !pipe_state.eof;) { switch (pipe_state.reader.read(intermediate_buffer)) { case pipe_reader::result::SUCCESS: { // append to main buffer pipe_state.buffer.reserve(pipe_state.buffer.size() + intermediate_buffer.size()); - std::copy(intermediate_buffer.begin(), intermediate_buffer.end(), std::back_inserter(pipe_state.buffer)); + std::copy(intermediate_buffer.begin(), intermediate_buffer.end(), + std::back_inserter(pipe_state.buffer)); break; } case pipe_reader::result::END_OF_FILE: { @@ -102,7 +109,28 @@ namespace linuxdeploy { const auto result = run(); if (result.exit_code() != 0) { - throw std::logic_error{"subprocess failed (exit code " + std::to_string(result.exit_code()) + ")"}; + std::ostringstream out; + + out << "subprocess "; + for (const auto& arg: args_) { + out << arg << " "; + } + out << "failed with exit code " << std::to_string(result.exit_code()); + + auto stdoutString = result.stdout_string(); + if (stdoutString.empty()) { + stdoutString = "(no output)"; + } + auto stderrString = result.stderr_string(); + if (stderrString.empty()) { + stderrString = "(no output)"; + } + + ldLog() << LD_DEBUG << out.str() << std::endl; + ldLog() << LD_DEBUG << "stdout:" << stdoutString << std::endl; + ldLog() << LD_DEBUG << "stderr:" << stderrString << std::endl; + + throw std::logic_error{out.str()}; } return result.stdout_string(); diff --git a/src/subprocess/util.cpp b/src/subprocess/util.cpp index d3619c99..d254f8cd 100644 --- a/src/subprocess/util.cpp +++ b/src/subprocess/util.cpp @@ -5,6 +5,13 @@ #include "linuxdeploy/subprocess/util.h" #include "linuxdeploy/util/misc.h" +#ifdef __FreeBSD__ +// On FreeBSD environ has to be declared in the consumer code +extern "C" { + extern char** environ; +} +#endif + namespace linuxdeploy::subprocess { subprocess_env_map_t get_environment() { subprocess_env_map_t result; diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 79220324..869496d2 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.6) set(headers_dir ${PROJECT_SOURCE_DIR}/include/linuxdeploy/util/) diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 408e5f7b..b7b5faf2 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -15,7 +15,8 @@ function(ld_core_add_test_executable NAME) -DSIMPLE_EXECUTABLE_STATIC_PATH="$" -DSIMPLE_DESKTOP_ENTRY_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/simple_app.desktop" - -DSIMPLE_ICON_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/simple_icon.svg" + -DSIMPLE_ICON_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/simple_icon.png" + -DSIMPLE_ICON_PATH2="${CMAKE_CURRENT_SOURCE_DIR}/../data/simple_icon.svg" -DSIMPLE_FILE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/simple_file.txt" -DREADONLY_FILE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../data/readonly-file.txt" ) diff --git a/tests/core/test_appdir.cpp b/tests/core/test_appdir.cpp index ff30b9df..1b75e559 100644 --- a/tests/core/test_appdir.cpp +++ b/tests/core/test_appdir.cpp @@ -77,6 +77,7 @@ namespace AppDirTest { "usr/share/icons/hicolor/64x64", "usr/share/icons/hicolor/64x64/apps", "usr/share/applications", + "usr/share/pixmaps", "usr/lib", }; @@ -127,10 +128,13 @@ namespace AppDirTest { TEST_F(AppDirUnitTestsFixture, deployIcon) { appDir.deployIcon(SIMPLE_ICON_PATH); + appDir.deployIcon(SIMPLE_ICON_PATH2); ASSERT_TRUE(appDir.executeDeferredOperations()); - const auto targetPath = tmpAppDir / "usr/share/icons/hicolor/scalable/apps" / path(SIMPLE_ICON_PATH).filename(); + const auto targetPath = tmpAppDir / "usr/share/icons/hicolor/16x16/apps" / path(SIMPLE_ICON_PATH).filename(); + const auto targetPath2 = tmpAppDir / "usr/share/icons/hicolor/scalable/apps" / path(SIMPLE_ICON_PATH2).filename(); assertIsRegularFile(targetPath); + assertIsRegularFile(targetPath2); } TEST_F(AppDirUnitTestsFixture, deployFileToDirectory) { diff --git a/tests/core/test_linuxdeploy.cpp b/tests/core/test_linuxdeploy.cpp index 68eb1066..656f5ec7 100644 --- a/tests/core/test_linuxdeploy.cpp +++ b/tests/core/test_linuxdeploy.cpp @@ -19,7 +19,7 @@ namespace LinuxDeployTest { fs::path target_desktop_path; fs::path source_icon_path; - fs::path target_icon_path; + std::vector target_icon_paths; fs::path source_apprun_path; fs::path target_apprun_path; @@ -32,7 +32,13 @@ namespace LinuxDeployTest { source_desktop_path = SIMPLE_DESKTOP_ENTRY_PATH; target_desktop_path = tmpAppDir / "usr/share/applications" / source_desktop_path.filename(); source_icon_path = SIMPLE_ICON_PATH; - target_icon_path = tmpAppDir / "usr/share/icons/hicolor/scalable/apps" / source_icon_path.filename(); + if (source_icon_path.extension() == ".svg") { + target_icon_paths.push_back(tmpAppDir / "usr/share/icons/hicolor/scalable/apps" / source_icon_path.filename()); + } else { + target_icon_paths.push_back(tmpAppDir / "usr/share/icons/hicolor/32x32/apps" / source_icon_path.filename()); + target_icon_paths.push_back(tmpAppDir / "usr/share/icons/hicolor/128x128/apps" / source_icon_path.filename()); + target_icon_paths.push_back(tmpAppDir / "usr/share/pixmaps" / source_icon_path.filename()); + } source_apprun_path = SIMPLE_FILE_PATH; target_apprun_path = tmpAppDir / "AppRun"; } @@ -70,8 +76,11 @@ namespace LinuxDeployTest { } void add_icon() const { - create_directories(target_icon_path.parent_path()); - copy_file(source_icon_path, target_icon_path); + for (const auto &target_icon_path : target_icon_paths) { + create_directories(target_icon_path.parent_path()); + // NB: In case of PNG, the same icon is installed to all paths + copy_file(source_icon_path, target_icon_path); + } } void add_apprun() const { diff --git a/tests/data/simple_icon.png b/tests/data/simple_icon.png new file mode 100644 index 00000000..8665e9db Binary files /dev/null and b/tests/data/simple_icon.png differ