#!/bin/bash # [DEPLOY_QT=1] deploy-linux.sh # (Simplified) bash re-implementation of [linuxdeploy](https://github.com/linuxdeploy). # Reads [executable] and copies required libraries to [AppDir]/usr/lib # Copies the desktop and svg icon to [AppDir] # Respects the AppImage excludelist # # Unlike linuxdeploy, this does not: # - Copy any icon other than svg (too lazy to add that without a test case) # - Do any verification on the desktop file # - Run any linuxdeploy plugins # - *Probably other things I didn't know linuxdeploy can do* # #~ set -x export _PREFIX="/usr" export _LIB_DIRS="lib64 lib" export _QT_PLUGIN_PATH="${_PREFIX}/lib64/qt6/plugins" export _EXCLUDES="ld-linux.so.2 \ ld-linux-x86-64.so.2 \ ld-linux-aarch64.so.1 \ libasound.so.2 \ libc.so.6 \ libdl.so.2 \ libgcc_s.so.1 \ libm.so.6 \ libpthread.so.0 \ librt.so.1 \ libstdc++.so.6 \ libEGL.so.1 \ libfreetype.so.6 \ libGL.so.1 \ libfribidi.so.0 \ libGLX.so.0 \ libgbm.so.1 \ libGLdispatch.so.0 \ libgdk-3.so.0 \ libICE.so.6 \ libgdk_pixbuf-2.0.so.0 \ libOpenGL.so.0 \ libgio-2.0.so.0 \ libQt6EglFSDeviceIntegration.so.6 \ libgpg-error.so.0 \ libQt6OpenGL.so.6 \ libgtk-3.so.0 \ libSM.so.6 \ libharfbuzz.so.0 \ libX11.so.6 \ libXcomposite.so.1 \ libXcursor.so.1 \ libXdamage.so.1 \ libinput.so.10 \ libXext.so.6 \ libmount.so.1 \ libXfixes.so.3 \ libmtdev.so.1 \ libXi.so.6 \ libpango-1.0.so.0 \ libXinerama.so.1 \ libpangocairo-1.0.so.0 \ libXrandr.so.2 \ libpangoft2-1.0.so.0 \ libXrender.so.1 \ libpcre2-8.so.0 \ libatk-1.0.so.0 \ libpixman-1.so.0 \ libatk-bridge-2.0.so.0 \ libresolv.so.2 \ libatspi.so.0 \ libselinux.so.1 \ libblkid.so.1 \ libcairo-gobject.so.2 \ libuuid.so.1 \ libcairo.so.2 \ libthai.so.0 \ libcom_err.so.2 \ libts.so.0 \ libdatrie.so.1 \ libwacom.so.2 \ libdrm.so.2 \ libxcb-icccm.so.4 \ libepoxy.so.0 \ libxcb-randr.so.0 \ libevdev.so.2 \ libxcb.so.1 \ libexpat.so.1 \ libfontconfig.so.1 \ libz.so.1" export _EXECUTABLE="$1" # Get possible system library paths export _SYSTEM_PATHS=$(echo -n $PATH | tr ":" " ") export _SEARCH_PATHS= for i in ${_LIB_DIRS}; do for j in ${_SYSTEM_PATHS}; do _TRY_PATH="$(readlink -e "$j/../$i" || true)" if [[ -n "${_TRY_PATH}" ]]; then _SEARCH_PATHS="${_SEARCH_PATHS} ${_TRY_PATH}" fi done done _SEARCH_PATHS="${_SEARCH_PATHS} $(patchelf --print-rpath $_EXECUTABLE | tr ':' ' ')" # Get a list of only unique ones _SEARCH_PATHS=$(echo -n "${_SEARCH_PATHS}" | sed 's/ /\n/g' | sort -u) # find_library [library] # Finds the full path of partial name [library] in _SEARCH_PATHS # This is a time-consuming function. _NOT_FOUND="" function find_library { local _PATH="" for i in ${_SEARCH_PATHS}; do _PATH=$(find $i -regex ".*$(echo -n $1 | tr '+' '.')" -print -quit) if [ "$_PATH" != "" ]; then break fi done if [ "$_PATH" != "" ]; then echo -n $(readlink -e $_PATH) fi } # get_dep_names [object] # Returns a space-separated list of all required libraries needed by [object]. function get_dep_names { echo -n $(patchelf --print-needed $1) } # get_deps [object] [library_path] # Finds and installs all libraries required by [object] to [library_path]. # This is a recursive function that also depends on find_library. function get_deps { local _DEST=$2 for i in $(get_dep_names $1); do _EXCL=`echo "$_EXCLUDES" | tr ' ' '\n' | grep $i` if [ "$_EXCL" != "" ]; then #>&2 echo "$i is on the exclude list... skipping" continue fi if [ -f $_DEST/$i ]; then continue fi local _LIB=$(find_library $i) if [ -z $_LIB ]; then echo -n "$i:" continue fi >&2 cp -v $_LIB $_DEST/$i & get_deps $_LIB $_DEST done } export -f get_deps export -f get_dep_names export -f find_library _ERROR=0 if [ -z "$_EXECUTABLE" ]; then _ERROR=1 fi if [ "$_ERROR" -eq 1 ]; then >&2 echo "usage: $0 [AppDir]" exit 1 fi LIB_DIR="$(readlink -m $(dirname $_EXECUTABLE)/../lib)" mkdir -p $LIB_DIR _NOT_FOUND=$(get_deps $_EXECUTABLE $LIB_DIR) if [ "${DEPLOY_QT}" == "1" ]; then # Find Qt path from search paths for i in ${_SEARCH_PATHS}; do _QT_CORE_LIB=$(find ${i} -type f -regex '.*/libQt6Core\.so.*' | head -n 1) if [ -n "${_QT_CORE_LIB}" ]; then _QT_PATH=$(dirname ${_QT_CORE_LIB})/../ break fi done _QT_PLUGIN_PATH=$(readlink -e $(find ${_QT_PATH} -type d -regex '.*/plugins/platforms' | head -n 1)/../) for i in audio bearer imageformats mediaservice platforminputcontexts platformthemes xcbglintegrations platforms wayland-decoration-client wayland-graphics-integration-client wayland-graphics-integration-server wayland-shell-integration; do mkdir -p ${LIB_DIR}/../plugins/${i} cp -rnv ${_QT_PLUGIN_PATH}/${i}/*.so ${LIB_DIR}/../plugins/${i} find ${LIB_DIR}/../plugins/ -type f -regex '.*\.so' -exec patchelf --set-rpath '$ORIGIN/../../lib:$ORIGIN' {} ';' # Find any remaining libraries needed for Qt libraries _NOT_FOUND+=$(find ${LIB_DIR}/../plugins/${i} -type f -exec bash -c "get_deps {} $LIB_DIR" ';') done _QT_CONF=${LIB_DIR}/../bin/qt.conf echo "[Paths]" > ${_QT_CONF} echo "Prefix = ../" >> ${_QT_CONF} echo "Plugins = plugins" >> ${_QT_CONF} echo "Imports = qml" >> ${_QT_CONF} echo "Qml2Imports = qml" >> ${_QT_CONF} fi # Fix rpath of libraries and executable so they can find the packaged libraries find ${LIB_DIR} -type f -exec patchelf --set-rpath '$ORIGIN' {} ';' patchelf --set-rpath '$ORIGIN/../lib' $_EXECUTABLE _APPDIR=$2 cd ${_APPDIR} cp -nvs $(find -type f -regex '.*/icons/.*\.svg' || head -n 1) ./ cp -nvs $(find -type f -regex '.*/applications/.*\.desktop' || head -n 1) ./ if [ "${_NOT_FOUND}" != "" ]; then >&2 echo "WARNING: failed to find the following libraries:" >&2 echo "$(echo -n $_NOT_FOUND | tr ':' '\n' | sort -u)" fi