mirror of
https://github.com/citron-neo/emulator.git
synced 2026-07-05 15:21:57 -07:00
62542c8a50
- Add AppImageBuilder/package-citron-linux.sh: full AppImage packaging script with debloating (strips Qt Multimedia shared FFmpeg ~100 MB), and produces a tar.zst portable archive alongside the AppImage. - Add AppImageBuilder/quick-sharun.sh: sharun-based launcher helper. - Add dist/org.citron_emu.citron.png: application icon for AppImage. build-citron-linux.sh: - Add CITRON_USE_QT_MULTIMEDIA=OFF and QT_NO_PRIVATE_MODULE_WARNING=ON to common cmake args, preventing Qt Multimedia's shared FFmpeg from being linked at all. - Guard -mtls-dialect=gnu2 behind an x86_64 check so aarch64 builds no longer pass an unsupported flag. - Expand system dependency setup across all package managers (apt, pacman, dnf, yum, zypper, emerge) to include: libudev, libGL/OpenGL headers (required by aqt Qt6 cmake even for Vulkan-only builds), VAAPI/VDPAU hw-accel libs, and X11/XCB libs required by SDL2 and Qt. CMakeModules/qt_download.cmake: - Add aarch64 Linux aqt target (linux_arm64 / linux_gcc_arm64) so Qt is downloaded for the correct host arch on arm64 builders. - Remove qtmultimedia from aqt --modules download; it is permanently dead code under Qt6 and was causing recurring CI install warnings. CMakeLists.txt: - Make Qt6 Multimedia an optional find_package component, only included when CITRON_USE_QT_MULTIMEDIA is ON, so the cmake flag is functional. externals/libusb/CMakeLists.txt: - Resolve LIBUSB_SRC_DIR via CPM cache variables (LIBUSB_CPM_SOURCE_DIR or libusb_src_SOURCE_DIR) before falling back to the submodule path, fixing CPM-sourced libusb builds on Linux.
4197 lines
125 KiB
Bash
4197 lines
125 KiB
Bash
#!/bin/sh
|
|
|
|
# wrapper script for sharun that simplifies deployment to simple one liners
|
|
# Will try to detect and force deployment of GTK, QT, OpenGL, etc
|
|
# You can also force their deployment by setting the respective env variables
|
|
# for example set DEPLOY_OPENGL=1 to force opengl to be deployed
|
|
|
|
# Set ADD_HOOKS var to deploy the several hooks of this repository
|
|
# Example: ADD_HOOKS="self-updater.hook:fix-namespaces.hook" ./quick-sharun.sh
|
|
# Using the hooks automatically downloads a generic AppRun if no AppRun is present
|
|
|
|
# Set DESKTOP and ICON to the path of top level .desktop and icon to deploy them
|
|
|
|
set -e
|
|
|
|
if [ "$QUICK_SHARUN_DEBUG" = 1 ]; then
|
|
set -x
|
|
fi
|
|
|
|
APPIMAGE_ARCH=$(uname -m)
|
|
ARCH=${ARCH:-$APPIMAGE_ARCH}
|
|
TMPDIR=${TMPDIR:-/tmp}
|
|
APPDIR=${APPDIR:-$PWD/AppDir}
|
|
APPENV=$APPDIR/.env
|
|
DIRICON=$APPDIR/.DirIcon
|
|
DST_LIB_DIR=$APPDIR/lib
|
|
DST_BIN_DIR=$APPDIR/bin
|
|
SHARUN_BIN_DIR=$APPDIR/shared/bin
|
|
MAIN_BIN=${MAIN_BIN##*/}
|
|
|
|
SHARUN_LINK=${SHARUN_LINK:-https://github.com/pkgforge-dev/sharun/releases/download/1.0.0/sharun-$APPIMAGE_ARCH}
|
|
ONELF_LINK=${ONELF_LINK:-https://github.com/QaidVoid/onelf/releases/latest/download/onelf-$APPIMAGE_ARCH-linux}
|
|
HOOKSRC=${HOOKSRC:-https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/hooks}
|
|
LD_PRELOAD_OPEN=${LD_PRELOAD_OPEN:-https://github.com/VHSgunzo/pathmap.git}
|
|
|
|
OUTPATH=${OUTPATH:-$PWD}
|
|
DWARFS_COMP="${DWARFS_COMP:-zstd:level=22 -S26 -B6}"
|
|
OPTIMIZE_LAUNCH=${OPTIMIZE_LAUNCH:-0}
|
|
|
|
APPIMAGETOOL_LINK=${APPIMAGETOOL_LINK:-https://github.com/pkgforge-dev/appimagetool/releases/download/0.3.2/appimagetool-$APPIMAGE_ARCH-linux}
|
|
APPIMAGETOOL=${APPIMAGETOOL:-$TMPDIR/appimagetool}
|
|
|
|
ANYLINUX_LIB=${ANYLINUX_LIB:-1}
|
|
ANYLINUX_LIB_SOURCE=${ANYLINUX_LIB_SOURCE:-https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/lib/anylinux.c}
|
|
GTK_CLASS_FIX=${GTK_CLASS_FIX:-0}
|
|
GTK_CLASS_FIX_SOURCE=${GTK_CLASS_FIX_SOURCE:-https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/lib/gtk-class-fix.c}
|
|
|
|
DEPLOY_DATADIR=${DEPLOY_DATADIR:-1}
|
|
DEPLOY_LOCALE=${DEPLOY_LOCALE:-1}
|
|
DEBLOAT_LOCALE=${DEBLOAT_LOCALE:-1}
|
|
LOCALE_DIR=${LOCALE_DIR:-/usr/share/locale}
|
|
|
|
STRACE_MODE=${STRACE_MODE:-1}
|
|
STRACE_TIME=${STRACE_TIME:-5}
|
|
|
|
DEPENDENCIES="
|
|
awk
|
|
cc
|
|
cp
|
|
find
|
|
grep
|
|
ldd
|
|
mv
|
|
patchelf
|
|
rm
|
|
sleep
|
|
strings
|
|
tr
|
|
"
|
|
|
|
# libraries whose dependencies should not be collected via ldd
|
|
# we skip libqgtk3.so by default to prevent deploying GTK in Qt apps
|
|
# Qt works fine by doing this, the libqgtk3.so plugin even works with alpine
|
|
# linux gtk3 without issue, if the plugin fails to load Qt does not crash either
|
|
QUICK_SHARUN_SKIP_DEPS_FOR="
|
|
$QUICK_SHARUN_SKIP_DEPS_FOR
|
|
libqgtk3.so
|
|
"
|
|
|
|
# prevent Qt from dlopening libqtgtk3.so via strace mode
|
|
export QT_QPA_PLATFORMTHEME=${QT_QPA_PLATFORMTHEME:-fusion}
|
|
|
|
# check if the _tmp_* vars have not be declared already
|
|
# likely to happen if this script run more than once
|
|
PATH_MAPPING_SCRIPT=$DST_BIN_DIR/01-path-mapping-hardcoded.hook
|
|
|
|
if [ -f "$PATH_MAPPING_SCRIPT" ]; then
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
_tmp_*) eval "$line";;
|
|
esac
|
|
done < "$PATH_MAPPING_SCRIPT"
|
|
fi
|
|
|
|
regex='A-Za-z0-9_=-'
|
|
_tmp_bin="${_tmp_bin:-$(tr -dc "$regex" < /dev/urandom | head -c 3)}"
|
|
_tmp_lib="${_tmp_lib:-$(tr -dc "$regex" < /dev/urandom | head -c 3)}"
|
|
_tmp_share="${_tmp_share:-$(tr -dc "$regex" < /dev/urandom | head -c 5)}"
|
|
|
|
if [ "$DEPLOY_PYTHON" = 1 ]; then
|
|
DEPLOY_SYS_PYTHON=1
|
|
fi
|
|
|
|
if [ "$DEPLOY_SYS_PYTHON" = 1 ]; then
|
|
if [ "$DEBLOAT_PYTHON" = 0 ]; then
|
|
DEBLOAT_SYS_PYTHON=${DEBLOAT_SYS_PYTHON:-0}
|
|
fi
|
|
DEBLOAT_SYS_PYTHON=${DEBLOAT_SYS_PYTHON:-1}
|
|
fi
|
|
|
|
# github actions doesn't set USER and XDG_RUNTIME_DIR
|
|
# causing some apps crash when running xvfb-run
|
|
export USER="${LOGNAME:-${USER:-${USERNAME:-yomama}}}"
|
|
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp}"
|
|
|
|
# apps often need this to work
|
|
export $(dbus-launch 2>/dev/null || echo 'NO_DBUS=1')
|
|
|
|
# CI containers often run as root which prevents
|
|
# web apps from running with LD_DEBUG strace mode
|
|
export ELECTRON_DISABLE_SANDBOX=1
|
|
export WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1
|
|
export QTWEBENGINE_DISABLE_SANDBOX=1
|
|
|
|
_echo() {
|
|
printf '\033[1;92m%s\033[0m\n' " $*"
|
|
}
|
|
|
|
_err_msg(){
|
|
>&2 printf '\033[1;31m%s\033[0m\n' " $*"
|
|
}
|
|
|
|
_is_cmd() {
|
|
for cmd do
|
|
command -v "$cmd" 1>/dev/null || return 1
|
|
done
|
|
return 0
|
|
}
|
|
|
|
_is_elf() {
|
|
head -c 4 "$1" 2>/dev/null | grep -qa 'ELF'
|
|
}
|
|
|
|
_is_script() {
|
|
shebang=$(head -c 2 "$1" 2>/dev/null)
|
|
[ "$shebang" = '#!' ]
|
|
}
|
|
|
|
_is_so() {
|
|
case "${1##*/}" in
|
|
*.so|*.so.[0-9]*)
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
_lib4bin_ldd_libs() {
|
|
ldd "$1" 2>/dev/null | awk '/=>/{print $3}' | sort -u
|
|
}
|
|
|
|
_download() {
|
|
if _is_cmd wget; then
|
|
DOWNLOAD_CMD="wget"
|
|
set -- -qO "$@"
|
|
elif _is_cmd curl; then
|
|
DOWNLOAD_CMD="curl"
|
|
set -- -Lso "$@"
|
|
else
|
|
_err_msg "ERROR: we need wget or curl to download $1"
|
|
exit 1
|
|
fi
|
|
COUNT=0
|
|
while [ "$COUNT" -lt 5 ]; do
|
|
if "$DOWNLOAD_CMD" "$@"; then
|
|
return 0
|
|
fi
|
|
_err_msg "Download failed! Trying again..."
|
|
COUNT=$((COUNT + 1))
|
|
sleep 5
|
|
done
|
|
_err_msg "ERROR: Failed to download 5 times!"
|
|
return 1
|
|
}
|
|
|
|
_help_msg() {
|
|
cat <<-EOF
|
|
USAGE: ${0##*/} /path/to/binaries_and_libraries
|
|
|
|
DESCRIPTION:
|
|
POSIX shell script wrapper for sharun that simplifies the deployment
|
|
of AppImages to simple oneliners. It automates detection and deployment of common
|
|
libraries such as GTK, Qt, OpenGL, Vulkan, Pipewire, GStreamer, etc.
|
|
|
|
Features:
|
|
- Automatic detection and forced deployment of libraries.
|
|
- Support for environment-based configuration to force deployment, e.g., DEPLOY_OPENGL=1
|
|
- Deployment of app-specific hooks, desktop entries, icons, locale data and more.
|
|
- Automatic patching of hardcoded paths in binaries and libraries.
|
|
|
|
OPTIONS / ENVIRONMENT VARIABLES:
|
|
ADD_HOOKS List of hooks (colon-separated) to deploy with the application.
|
|
DESKTOP Path or URL to a .desktop file to include.
|
|
ICON Path or URL to an icon file to include.
|
|
OUTPUT_APPIMAGE Set to 1 to turn the deployed AppDir into an AppImage.
|
|
DEPLOY_QT Set to 1 to force deployment of Qt. Will determine to deploy
|
|
QtWebEngine and Qml as well, these can be controlled with
|
|
DEPLOY_QT_WEB_ENGINE and DEPLOY_QML. Set to 1 enable, 0 disable
|
|
Set QT_DIR if the system Qt directory in LIB_DIR has a different name.
|
|
DEPLOY_SDL Set to 1 to force deployment of SDL.
|
|
DEPLOY_GTK Set to 1 to force deployment of GTK.
|
|
DEPLOY_GDK Set to 1 to force deployment of gdk-pixbuf.
|
|
DEPLOY_GLYCIN Set to 1 to force deployment of Glycin.
|
|
DEPLOY_OPENGL Set to 1 to force deployment of OpenGL.
|
|
DEPLOY_VULKAN Set to 1 to force deployment of Vulkan.
|
|
DEPLOY_IMAGEMAGICK Set to 1 to force deployment of ImageMagick.
|
|
DEPLOY_LIBHEIF Set to 1 to force deployment of libheif.
|
|
DEPLOY_GEGL Set to 1 to force deployment of GEGL.
|
|
DEPLOY_BABL Set to 1 to force deployment of babl.
|
|
DEPLOY_P11KIT Set to 1 to force deployment of p11-kit.
|
|
DEPLOY_PIPEWIRE Set to 1 to force deployment of Pipewire.
|
|
DEPLOY_PULSE Set to 1 to force deployment of pulseaudio.
|
|
DEPLOY_GSTREAMER Set to 1 to force deployment of GStreamer. By default
|
|
several gstreamer plugins are removed, set DEPLOY_GSTREAMER_ALL=1
|
|
if you can to deploy ALL Gstreamer plugins. (Very bloated).
|
|
DEPLOY_LOCALE Set to 1 to deploy locale data.
|
|
DEPLOY_PYTHON Set to 1 to deploy system Python. Will remove all pycache
|
|
files, set DEBLOAT_PYTHON to 0 to prevent this.
|
|
|
|
LIB_DIR Set source library directory if autodetection fails.
|
|
NO_STRIP Disable stripping binaries and libraries if set.
|
|
APPDIR Destination AppDir (default: ./AppDir).
|
|
ANYLINUX_LIB Preloads a library that unsets environment variables known to
|
|
cause problems to child processes. Set to 0 to disable.
|
|
Additionally you can set ANYLINUX_DO_NOT_LOAD_LIBS to a
|
|
list of colon separated libraries to prevent from being
|
|
dlopened, the entries support simple globbing, example:
|
|
export ANYLINUX_DO_NOT_LOAD_LIBS='libpipewire-0.3.so*'
|
|
Useful for applications that will try to dlopen several
|
|
optional dependencies that you do not want to include.
|
|
|
|
ALWAYS_SOFTWARE Set to 1 to enable. Sets several env variables to make
|
|
applications use software rendering, use this option when
|
|
you do not want hardware acceleration.
|
|
Will fail if the application makes use of mesa during deployment.
|
|
|
|
PATH_MAPPING Configures and preloads pathmap.
|
|
Set this variable if the application is hardcoded to look
|
|
into /usr and similar locations, example:
|
|
export PATH_MAPPING='
|
|
/usr/lib/myapp_libs:\${SHARUN_DIR}/lib/myapp_libs
|
|
/etc/myapp.conf:\${SHARUN_DIR}/etc/myapp.conf
|
|
'
|
|
\${SHARUN_DIR} here must NOT expand!
|
|
The braces in the variable are mandatory!
|
|
|
|
NOTE:
|
|
Several of these options get turned on automatically based on what is being deployed.
|
|
|
|
EXAMPLES:
|
|
DEPLOY_OPENGL=1 ./quick-sharun.sh /path/to/myapp
|
|
DESKTOP=/path/to/app.desktop ICON=/path/to/icon.png ./quick-sharun.sh /path/to/myapp
|
|
ADD_HOOKS="self-updater.hook:fix-namespaces.hook" ./quick-sharun.sh /path/to/myapp
|
|
|
|
SEE ALSO:
|
|
sharun (https://github.com/VHSgunzo/sharun)
|
|
pathmap (https://github.com/VHSgunzo/pathmap)
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
_get_icon() {
|
|
if [ -f "$DIRICON" ]; then
|
|
return 0
|
|
fi
|
|
|
|
icon_name=$(awk -F'=' '/^Icon=/{print $2; exit}' "$DESKTOP_ENTRY")
|
|
icon_name=${icon_name##*/}
|
|
|
|
if [ "$ICON" = "DUMMY" ]; then
|
|
if [ -z "$icon_name" ]; then
|
|
_err_msg "ERROR: Cannot get icon name from $DESKTOP_ENTRY"
|
|
_err_msg "Make sure it contains a valid 'Icon=' key!"
|
|
exit 1
|
|
fi
|
|
_echo "* Adding dummy $icon_name icon to $APPDIR..."
|
|
:> "$APPDIR"/"$icon_name".png
|
|
:> "$DIRICON"
|
|
elif [ -f "$ICON" ]; then
|
|
_echo "* Adding $ICON to $APPDIR..."
|
|
cp -v "$ICON" "$APPDIR"
|
|
cp -v "$ICON" "$DIRICON"
|
|
elif echo "$ICON" | grep -q 'http'; then
|
|
_echo "* Downloading $ICON to $APPDIR..."
|
|
dst=$APPDIR/${ICON##*/}
|
|
_download "$dst" "$ICON"
|
|
cp -v "$dst" "$DIRICON"
|
|
elif [ -n "$ICON" ]; then
|
|
_err_msg "$ICON is NOT a valid path!"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$DIRICON" ]; then
|
|
# try the first top level .png or .svg before searching
|
|
set -- "$APPDIR"/*.png "$APPDIR"/*.svg
|
|
for i do
|
|
if [ -f "$i" ]; then
|
|
cp -v "$i" "$DIRICON"
|
|
return 0
|
|
fi
|
|
done
|
|
set --
|
|
|
|
# Now search deeper
|
|
if [ -n "$icon_name" ]; then
|
|
sizes='256x256 512x512 192x192 128x128 scalable'
|
|
for s in $sizes; do
|
|
set -- "$@" "$APPDIR"/share/icons/hicolor/"$s"/apps/"$icon_name"*
|
|
done
|
|
for s in $sizes; do
|
|
set -- "$@" /usr/share/icons/hicolor/"$s"/apps/"$icon_name"*
|
|
done
|
|
for i do
|
|
if [ -f "$i" ]; then
|
|
case "$i" in
|
|
*.png|*.svg)
|
|
cp -v "$i" "$APPDIR"
|
|
cp -v "$i" "$DIRICON"
|
|
break
|
|
;;
|
|
esac
|
|
fi
|
|
done
|
|
set --
|
|
fi
|
|
fi
|
|
|
|
if [ ! -f "$DIRICON" ]; then
|
|
_err_msg "ERROR: Missing '$DIRICON'!"
|
|
_err_msg "Could not find icon listed in $DESKTOP_ENTRY either"
|
|
_err_msg "Set ICON env variable to the location/url of the icon"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
_sanity_check() {
|
|
for d in $DEPENDENCIES; do
|
|
if ! _is_cmd "$d"; then
|
|
_err_msg "ERROR: Missing dependency '$d'!"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if ! mkdir -p "$APPDIR"/share "$DST_LIB_DIR" "$DST_BIN_DIR" "$SHARUN_BIN_DIR"; then
|
|
_err_msg "ERROR: Cannot create '$APPDIR' directory!"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -n "$PATH_MAPPING" ] && ! echo "$PATH_MAPPING" | grep -q 'SHARUN_DIR'; then
|
|
_err_msg 'ERROR: PATH_MAPPING must contain unexpanded ${SHARUN_DIR} variable'
|
|
_err_msg 'Example:'
|
|
_err_msg "'PATH_MAPPING=/etc:\${SHARUN_DIR}/etc'"
|
|
_err_msg 'NOTE: The braces in the variable are needed!'
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$STRACE_MODE" = 1 ]; then
|
|
if _is_cmd xvfb-run; then
|
|
XVFB_CMD="xvfb-run -a --"
|
|
else
|
|
_err_msg "WARNING: xvfb-run was not detected on the system"
|
|
_err_msg "xvfb-run is used in strace mode to provide a display"
|
|
_err_msg "for apps to run to find dlopened libraries."
|
|
_err_msg "GUI apps will not run without display! We cannot check for dlopened libs!"
|
|
XVFB_CMD=""
|
|
sleep 5
|
|
fi
|
|
fi
|
|
|
|
unset LIB32
|
|
if [ -z "$LIB_DIR" ]; then
|
|
if [ -d "/usr/lib/$APPIMAGE_ARCH-linux-gnu" ]; then
|
|
LIB_DIR="/usr/lib/$APPIMAGE_ARCH-linux-gnu"
|
|
elif [ -d "/usr/lib" ]; then
|
|
LIB_DIR="/usr/lib"
|
|
else
|
|
_err_msg "ERROR: there is no /usr/lib directory in this system"
|
|
_err_msg "set the LIB_DIR variable to where you have libraries"
|
|
exit 1
|
|
fi
|
|
elif [ "$LIB_DIR" = /usr/lib32 ] || [ "$LIB_DIR" = /usr/lib/i386-linux-gnu ]; then
|
|
LIB32=1
|
|
fi
|
|
|
|
set -- lib
|
|
if [ "$LIB32" = 1 ]; then
|
|
DST_LIB_DIR=$APPDIR/lib32
|
|
_err_msg "WARNING: 32bit deployment is experimental!"
|
|
set -- "$@" lib32
|
|
fi
|
|
|
|
for d do
|
|
[ -L "$APPDIR"/shared/"$d" ] || rm -rf "$APPDIR"/shared/"$d"
|
|
[ -L "$APPDIR"/shared/"$d" ] || ln -sf ../"$d" "$APPDIR"/shared/"$d"
|
|
done
|
|
}
|
|
|
|
# do a basic test to make sure at least the application is not totally broken
|
|
# like when libraries are missing symbols and similar stuff
|
|
_test_appimage() {
|
|
if [ -z "$1" ]; then
|
|
_err_msg "ERROR: Missing application to run!"
|
|
exit 1
|
|
elif ! _is_cmd xvfb-run; then
|
|
_err_msg "ERROR: --test requires 'xvfb-run'!"
|
|
exit 1
|
|
fi
|
|
|
|
APP=$1
|
|
shift
|
|
|
|
_echo "------------------------------------------------------------"
|
|
_echo "Testing '$APP'..."
|
|
_echo "------------------------------------------------------------"
|
|
|
|
# Allow host vulkan for vulkan-swrast since there is no GPU in the CI
|
|
export SHARUN_ALLOW_SYS_VKICD=1
|
|
|
|
# since there is no fuse available in CI and userns are also broken
|
|
# the appimage may not run if it is bigger than 400 MiB due to a restriction
|
|
# in the uruntime, so we will have to always force it to extract and run
|
|
export APPIMAGE_TARGET_DIR="$PWD"/_test-app
|
|
export APPIMAGE_EXTRACT_AND_RUN=1
|
|
|
|
set -m
|
|
xvfb-run -a -- "$APP" "$@" &
|
|
pid=$!
|
|
set +m
|
|
|
|
# let the app run for 12 seconds, if it exits early it means something is wrong
|
|
COUNT=0
|
|
while kill -0 $pid 2>/dev/null && [ "$COUNT" -lt 12 ]; do
|
|
sleep 1
|
|
COUNT=$((COUNT + 1))
|
|
done
|
|
|
|
set +e
|
|
if kill -0 $pid 2>/dev/null; then
|
|
_echo "------------------------------------------------------------"
|
|
_echo "Test went OK."
|
|
_echo "------------------------------------------------------------"
|
|
kill -TERM -$pid 2>/dev/null || :
|
|
sleep 1
|
|
kill -KILL -$pid 2>/dev/null || :
|
|
exit 0
|
|
else
|
|
# process exited before timeout, something went wrong.
|
|
wait $pid
|
|
status=$?
|
|
_err_msg "------------------------------------------------------------"
|
|
_err_msg "ERROR: '$APP' failed in ${COUNT} seconds with code $status"
|
|
_err_msg "------------------------------------------------------------"
|
|
# wait 20 seconds before failing, this way for example if we have a Ci run
|
|
# for x86_64 and aarch64, if one fails it does not instantly stop the other
|
|
# and people are left wondering if the problem affects both matrix or just one
|
|
sleep 20
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# if full test is not possible lets at least check some possible issues
|
|
_simple_test_appimage() {
|
|
log="$TMPDIR"/simple-test.log
|
|
APP=$1
|
|
shift
|
|
|
|
_echo "------------------------------------------------------------"
|
|
_echo "Doing simple test '$APP'..."
|
|
_echo "------------------------------------------------------------"
|
|
|
|
set -m
|
|
"$APP" "$@" 2>"$log" &
|
|
pid=$!
|
|
set +m
|
|
|
|
sleep 7
|
|
kill -TERM -$pid 2>/dev/null || :
|
|
sleep 1
|
|
kill -KILL -$pid 2>/dev/null || :
|
|
|
|
test="$(cat "$log")"
|
|
case "$test" in
|
|
*'symbol lookup error'*|\
|
|
*'error while loading shared libraries'*)
|
|
>&2 echo "$test"
|
|
_err_msg "------------------------------------------------------------"
|
|
_err_msg "ERROR: '$APP' failed simple test!"
|
|
_err_msg "------------------------------------------------------------"
|
|
sleep 20
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
_echo "------------------------------------------------------------"
|
|
_echo "Test went OK."
|
|
_echo "------------------------------------------------------------"
|
|
exit 0
|
|
}
|
|
|
|
# POSIX shell doesn't support arrays we use awk to save it into a variable
|
|
# then with 'eval set -- $var' we add it to the positional array
|
|
# see https://unix.stackexchange.com/questions/421158/how-to-use-pseudo-arrays-in-posix-shell-script
|
|
_save_array() {
|
|
LC_ALL=C awk -v q="'" '
|
|
BEGIN{
|
|
for (i=1; i<ARGC; i++) {
|
|
gsub(q, q "\\" q q, ARGV[i])
|
|
printf "%s ", q ARGV[i] q
|
|
}
|
|
print ""
|
|
}' "$@"
|
|
}
|
|
|
|
_remove_empty_dirs() {
|
|
find "$1" -type d \
|
|
-exec rmdir -p --ignore-fail-on-non-empty {} + 2>/dev/null || true
|
|
}
|
|
|
|
# skip non executable binaries and .node binaries
|
|
# these are actually libraries and cannot be wrapped with sharun
|
|
_is_deployable_binary() {
|
|
if [ -x "$1" ]; then
|
|
case "$1" in
|
|
*.node) :;;
|
|
*) return 0;;
|
|
esac
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
_is_bun_binary() {
|
|
grep -aq -m 1 '__bun_' "$1"
|
|
}
|
|
|
|
_is_pyinstaller_binary() {
|
|
grep -aq -m 1 'pydata' "$1"
|
|
}
|
|
|
|
_determine_what_to_deploy() {
|
|
for bin do
|
|
# ignore flags
|
|
case "$bin" in
|
|
--) break ;;
|
|
-*) continue;;
|
|
esac
|
|
|
|
if [ ! -e "$bin" ]; then
|
|
_err_msg "'$bin' is NOT present!"
|
|
exit 1
|
|
fi
|
|
|
|
# if the argument is a directory save it to later it copy it
|
|
if [ -d "$bin" ]; then
|
|
ADD_DIR="
|
|
$ADD_DIR
|
|
$bin
|
|
"
|
|
elif [ -x "$bin" ]; then
|
|
# some apps may dlopen pulseaudio instead of linking directly
|
|
if grep -aoq -m 1 'libpulse.so' "$bin"; then
|
|
DEPLOY_PULSE=${DEPLOY_PULSE:-1}
|
|
fi
|
|
if grep -aoq -m 1 'disable-gpu-sandbox' "$bin" \
|
|
&& grep -aoq -m 1 'no-zygote-sandbox' "$bin"; then
|
|
DEPLOY_ELECTRON=${DEPLOY_ELECTRON:-1}
|
|
ELECTRON_BIN=$(readlink -f "$bin")
|
|
fi
|
|
fi
|
|
|
|
# check if what we are doing to deploy is not fucking broken
|
|
if _is_elf "$bin" && ldd "$bin" | grep "not found"; then
|
|
_err_msg "$bin is missing libraries! Aborting..."
|
|
exit 1
|
|
fi
|
|
|
|
NEEDED_LIBS="$(ldd "$bin" 2>/dev/null | awk '{print $3}') $NEEDED_LIBS"
|
|
|
|
# bin may be a shared library, in that case add it as well
|
|
case "$bin" in
|
|
*.so*) NEEDED_LIBS="$bin $NEEDED_LIBS";;
|
|
esac
|
|
|
|
# check linked libraries and enable each mode accordingly
|
|
for lib in $NEEDED_LIBS; do
|
|
case "$lib" in
|
|
*libQt5Core.so*)
|
|
DEPLOY_QT=${DEPLOY_QT:-1}
|
|
QT_DIR=${QT_DIR:-qt5}
|
|
;;
|
|
*libQt6Core.so*)
|
|
DEPLOY_QT=${DEPLOY_QT:-1}
|
|
QT_DIR=${QT_DIR:-qt6}
|
|
;;
|
|
*libQt*Qml*.so*)
|
|
DEPLOY_QML=${DEPLOY_QML:-1}
|
|
;;
|
|
*libQt*WebEngineCore.so*)
|
|
DEPLOY_QT_WEB_ENGINE=${DEPLOY_QT_WEB_ENGINE:-1}
|
|
DEPLOY_ELECTRON=${DEPLOY_ELECTRON:-1}
|
|
;;
|
|
*libgtk-x11-*.so*)
|
|
DEPLOY_GTK=${DEPLOY_GTK:-1}
|
|
GTK_DIR=gtk-2.0
|
|
;;
|
|
*libgtk-3*.so*)
|
|
DEPLOY_GTK=${DEPLOY_GTK:-1}
|
|
GTK_DIR=gtk-3.0
|
|
;;
|
|
*libgtk-4*.so*)
|
|
DEPLOY_GTK=${DEPLOY_GTK:-1}
|
|
GTK_DIR=gtk-4.0
|
|
;;
|
|
*libgdk_pixbuf*.so*)
|
|
DEPLOY_GDK=${DEPLOY_GDK:-1}
|
|
;;
|
|
*libglycin*.so*)
|
|
# glycin-ng needs no special handling
|
|
# it works out of the box
|
|
case " $NEEDED_LIBS " in
|
|
*"libglycin_ng.so"*)
|
|
DEPLOY_GLYCIN=0
|
|
continue
|
|
;;
|
|
*)
|
|
DEPLOY_GLYCIN=${DEPLOY_GLYCIN:-1}
|
|
GTK_CLASS_FIX=1
|
|
GNOME_GLYCIN=1
|
|
;;
|
|
esac
|
|
;;
|
|
*libwebkit*gtk-*.so*)
|
|
DEPLOY_WEBKIT2GTK=${DEPLOY_WEBKIT2GTK:-1}
|
|
_webkit_dir=${lib##*/} # get basename
|
|
_webkit_dir=${_webkit_dir#lib} # strip lib
|
|
_webkit_dir=${_webkit_dir%.so*} # strip .so
|
|
WEBKIT2GTK_DIR=${WEBKIT2GTK_DIR:-${lib%/*}/$_webkit_dir}
|
|
;;
|
|
*libsoup-*.so*)
|
|
DEPLOY_GLIB_NETWORKING=${DEPLOY_GLIB_NETWORKING:-1}
|
|
;;
|
|
*libSDL*.so*)
|
|
DEPLOY_SDL=${DEPLOY_SDL:-1}
|
|
;;
|
|
*libflutter*linux*.so*)
|
|
DEPLOY_FLUTTER=${DEPLOY_FLUTTER:-1}
|
|
FLUTTER_LIB=$lib
|
|
;;
|
|
*libpipewire*.so*)
|
|
DEPLOY_PIPEWIRE=${DEPLOY_PIPEWIRE:-1}
|
|
;;
|
|
*libgstreamer*.so*)
|
|
DEPLOY_GSTREAMER=${DEPLOY_GSTREAMER:-1}
|
|
;;
|
|
*libMagick*.so*)
|
|
DEPLOY_IMAGEMAGICK=${DEPLOY_IMAGEMAGICK:-1}
|
|
;;
|
|
*libImlib2.so*)
|
|
DEPLOY_IMLIB2=${DEPLOY_IMLIB2:-1}
|
|
;;
|
|
*libgegl*.so*)
|
|
DEPLOY_GEGL=${DEPLOY_GEGL:-1}
|
|
;;
|
|
*libbabl*.so*)
|
|
DEPLOY_BABL=${DEPLOY_BABL:-1}
|
|
;;
|
|
*libheif.so*)
|
|
DEPLOY_LIBHEIF=${DEPLOY_LIBHEIF:-1}
|
|
;;
|
|
*libgs.so*)
|
|
DEPLOY_GHOSTSCRIPT=${DEPLOY_GHOSTSCRIPT:-1}
|
|
;;
|
|
*libp11-kit.so*)
|
|
DEPLOY_P11KIT=${DEPLOY_P11KIT:-1}
|
|
;;
|
|
# libc.so.6 is glibc only, musl does not have it
|
|
*libc.so.6* |\
|
|
*libdl.so* |\
|
|
*libpthread.so*|\
|
|
*ld-linux*.so* |\
|
|
*librt.so* )
|
|
DEPLOY_GLIBC=${DEPLOY_GLIBC:-1}
|
|
;;
|
|
esac
|
|
done
|
|
done
|
|
|
|
if [ "$DEPLOY_QT" = 1 ] && [ -z "$QT_DIR" ]; then
|
|
_err_msg
|
|
_err_msg "WARNING: Qt deployment was forced but we do not know"
|
|
_err_msg "what version of Qt needs to be deployed!"
|
|
_err_msg "Defaulting to Qt6, if you do not want that set"
|
|
_err_msg "QT_DIR to the name of the Qt dir in $LIB_DIR"
|
|
_err_msg
|
|
QT_DIR=qt6
|
|
fi
|
|
|
|
if [ "$DEPLOY_GTK" = 1 ] && [ -z "$GTK_DIR" ]; then
|
|
_err_msg
|
|
_err_msg "WARNING: GTK deployment was forced but we do not know"
|
|
_err_msg "what version of GTK needs to be deployed!"
|
|
_err_msg "Defaulting to gtk-3.0, if you do not want that set"
|
|
_err_msg "GTK_DIR to the name of the gtk dir in $LIB_DIR"
|
|
_err_msg
|
|
GTK_DIR=gtk-3.0
|
|
fi
|
|
}
|
|
|
|
_make_deployment_array() {
|
|
if [ "$DEPLOY_GLIBC" = 1 ]; then
|
|
_echo "* Deploying glibc"
|
|
# ancient glibc libs still needed for compat (nvidia drivers, old binaries, etc.)
|
|
set -- "$@" \
|
|
"$LIB_DIR"/libpthread.so* \
|
|
"$LIB_DIR"/libdl.so* \
|
|
"$LIB_DIR"/librt.so* \
|
|
"$LIB_DIR"/libm.so* \
|
|
"$LIB_DIR"/libutil.so* \
|
|
"$LIB_DIR"/libresolv.so* \
|
|
"$LIB_DIR"/libnsl.so*
|
|
# nss libs, not all apps need this but it is very hard to determine this
|
|
set -- "$@" \
|
|
"$LIB_DIR"/libnss_dns.so* \
|
|
"$LIB_DIR"/libnss_files.so* \
|
|
"$LIB_DIR"/libnss_resolve.so* \
|
|
"$LIB_DIR"/libnss_mymachines.so*
|
|
# gconv is always deployed, removing it only saves ~30 KiB
|
|
# in the final appimage size and not worth the hassle
|
|
# It also causes hard to spot issues when needed and not present
|
|
#
|
|
# https://github.com/pkgforge-dev/Dolphin-emu-AppImage/issues/20
|
|
# https://github.com/pkgforge-dev/Anylinux-AppImages/pull/410
|
|
set -- "$@" \
|
|
"$LIB_DIR"/gconv/UTF*.so* \
|
|
"$LIB_DIR"/gconv/ANSI*.so* \
|
|
"$LIB_DIR"/gconv/CP*.so* \
|
|
"$LIB_DIR"/gconv/LATIN*.so* \
|
|
"$LIB_DIR"/gconv/UNICODE*.so* \
|
|
"$LIB_DIR"/gconv/ISO8859*.so*
|
|
fi
|
|
if [ "$ALWAYS_SOFTWARE" = 1 ]; then
|
|
DEPLOY_OPENGL=0
|
|
DEPLOY_VULKAN=0
|
|
echo 'GSK_RENDERER=cairo' >> "$APPENV"
|
|
echo 'GDK_DISABLE=gl,vulkan' >> "$APPENV"
|
|
echo 'GDK_GL=disable' >> "$APPENV"
|
|
echo 'QT_QUICK_BACKEND=software' >> "$APPENV"
|
|
export GSK_RENDERER=cairo
|
|
export GDK_DISABLE=gl,vulkan
|
|
export GDK_GL=disable
|
|
export QT_QUICK_BACKEND=software
|
|
|
|
ANYLINUX_DO_NOT_LOAD_LIBS="libgallium-*:libvulkan*:libGLX_mesa.so*:libGLX_indirect.so*${ANYLINUX_DO_NOT_LOAD_LIBS:+:$ANYLINUX_DO_NOT_LOAD_LIBS}"
|
|
fi
|
|
if [ "$DEPLOY_PYTHON" = 1 ]; then
|
|
_echo "* Deploying system python"
|
|
fi
|
|
if [ "$DEPLOY_QT" = 1 ]; then
|
|
DEPLOY_OPENGL=${DEPLOY_OPENGL:-1}
|
|
DEPLOY_COMMON_LIBS=${DEPLOY_COMMON_LIBS:-1}
|
|
|
|
_echo "* Deploying $QT_DIR"
|
|
|
|
if [ -d "$QT_LOCATION" ]; then
|
|
plugindir="$QT_LOCATION"/plugins
|
|
else
|
|
# some distros have a qt dir rather than qt6 or qt5 dir
|
|
if [ ! -d "$LIB_DIR"/"$QT_DIR" ]; then
|
|
QT_DIR=qt
|
|
fi
|
|
plugindir="$LIB_DIR"/"$QT_DIR"/plugins
|
|
fi
|
|
|
|
for lib in $NEEDED_LIBS; do
|
|
case "$lib" in
|
|
*libQt*Gui.so*)
|
|
set -- "$@" \
|
|
"$plugindir"/imageformats/* \
|
|
"$plugindir"/iconengines/* \
|
|
"$plugindir"/styles/* \
|
|
"$plugindir"/platform*/* \
|
|
"$plugindir"/wayland-*/* \
|
|
"$plugindir"/xcbglintegrations/*
|
|
;;
|
|
*libQt*Network.so*)
|
|
set -- "$@" \
|
|
"$plugindir"/tls/* \
|
|
"$plugindir"/bearer/*
|
|
;;
|
|
*libQt*Sql.so*)
|
|
set -- "$@" "$plugindir"/sqldrivers/*
|
|
;;
|
|
*libQt*Multimedia.so*)
|
|
set -- "$@" "$plugindir"/multimedia/*
|
|
;;
|
|
*libQt*PrintSupport*)
|
|
set -- "$@" "$plugindir"/printsupport/*
|
|
;;
|
|
*libQt*Positioning.so*)
|
|
set -- "$@" "$plugindir"/position/*
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ "$DEPLOY_QT_WEB_ENGINE" = 1 ]; then
|
|
if ! enginebin=$(find "${QT_LOCATION:-$LIB_DIR}" -type f \
|
|
-name 'QtWebEngineProcess' -print -quit 2>/dev/null); then
|
|
_err_msg "Cannot find QtWebEngineProcess!"
|
|
exit 1
|
|
fi
|
|
set -- "$@" "$enginebin"
|
|
fi
|
|
|
|
if [ "$DEPLOY_QML" = 1 ]; then
|
|
_echo "* Deploying qml"
|
|
qmldir="${QT_LOCATION:-$LIB_DIR/$QT_DIR}"/qml
|
|
ADD_DIR="
|
|
$ADD_DIR
|
|
$qmldir
|
|
"
|
|
fi
|
|
fi
|
|
if [ "$DEPLOY_GTK" = 1 ]; then
|
|
_echo "* Deploying $GTK_DIR"
|
|
DEPLOY_GDK=${DEPLOY_GDK:-1}
|
|
DEPLOY_COMMON_LIBS=${DEPLOY_COMMON_LIBS:-1}
|
|
set -- "$@" \
|
|
"$LIB_DIR"/"$GTK_DIR"/*/immodules/* \
|
|
"$LIB_DIR"/gvfs/libgvfscommon.so \
|
|
"$LIB_DIR"/gio/modules/libgvfsdbus.so \
|
|
"$LIB_DIR"/gio/modules/libdconfsettings.so
|
|
|
|
case "$GTK_DIR" in
|
|
*4*)
|
|
DEPLOY_OPENGL=${DEPLOY_OPENGL:-1}
|
|
echo 'GSETTINGS_BACKEND=keyfile' >> "$APPENV"
|
|
;;
|
|
esac
|
|
|
|
if [ "$DEPLOY_WEBKIT2GTK" = 1 ]; then
|
|
_echo "* Deploying webkit2gtk"
|
|
DEPLOY_OPENGL=${DEPLOY_OPENGL:-1}
|
|
DEPLOY_P11KIT=${DEPLOY_P11KIT:-1}
|
|
DEPLOY_GLIB_NETWORKING=${DEPLOY_GLIB_NETWORKING:-1}
|
|
set -- "$@" "$LIB_DIR"/libnss_mdns*minimal.so*
|
|
if b=$(command -v bwrap); then set -- "$@" "$b"; fi
|
|
if b=$(command -v xdg-dbus-proxy); then set -- "$@" "$b"; fi
|
|
if [ ! -d "$WEBKIT2GTK_DIR" ]; then
|
|
_err_msg "Unable to find $WEBKIT2GTK_DIR directory"
|
|
_err_msg "Please set the WEBKIT2GTK_DIR variable to its location"
|
|
exit 1
|
|
fi
|
|
ADD_DIR="
|
|
$ADD_DIR
|
|
$WEBKIT2GTK_DIR
|
|
"
|
|
fi
|
|
|
|
if [ "$DEPLOY_GLIB_NETWORKING" = 1 ]; then
|
|
_echo "* Deploying Glib-Netwroking"
|
|
DEPLOY_P11KIT=${DEPLOY_P11KIT:-1}
|
|
set -- "$@" \
|
|
"$LIB_DIR"/gio/modules/libgiognutls.so \
|
|
"$LIB_DIR"/gio/modules/libgiolibproxy.so \
|
|
"$LIB_DIR"/gio/modules/libgiognomeproxy.so
|
|
fi
|
|
|
|
if [ "$DEPLOY_SYS_PYTHON" = 1 ]; then
|
|
set -- "$@" "$LIB_DIR"/libgirepository*.so*
|
|
fi
|
|
fi
|
|
if [ "$DEPLOY_GDK" = 1 ]; then
|
|
_echo "* Deploying gdk-pixbuf"
|
|
gdkdir="$(echo "$LIB_DIR"/gdk-pixbuf-*/*/loaders)"
|
|
|
|
set -- "$@" "$gdkdir"/*svg*.so*
|
|
for lib in $NEEDED_LIBS; do
|
|
case "$lib" in
|
|
*libjxl.so*) set -- "$@" "$gdkdir"/*jxl*.so* ;;
|
|
*libavif.so*) set -- "$@" "$gdkdir"/*avif*.so*;;
|
|
*libheif.so*) set -- "$@" "$gdkdir"/*heif*.so*;;
|
|
esac
|
|
done
|
|
fi
|
|
if [ "$DEPLOY_SDL" = 1 ]; then
|
|
_echo "* Deploying SDL"
|
|
DEPLOY_PULSE=${DEPLOY_PULSE:-1}
|
|
DEPLOY_COMMON_LIBS=${DEPLOY_COMMON_LIBS:-1}
|
|
set -- "$@" \
|
|
"$LIB_DIR"/libSDL*.so* \
|
|
"$LIB_DIR"/libudev.so* \
|
|
"$LIB_DIR"/libusb-1*.so* \
|
|
"$LIB_DIR"/libdecor*.so*
|
|
fi
|
|
if [ "$DEPLOY_GLYCIN" = 1 ]; then
|
|
_echo "* Deploying GNOME glycin"
|
|
set -- "$@" "$LIB_DIR"/glycin-loaders/*/*
|
|
if b=$(command -v bwrap); then set -- "$@" "$b"; fi
|
|
fi
|
|
if [ "$DEPLOY_FLUTTER" = 1 ]; then
|
|
DEPLOY_COMMON_LIBS=${DEPLOY_COMMON_LIBS:-1}
|
|
DEPLOY_OPENGL=${DEPLOY_OPENGL:-1}
|
|
fi
|
|
if [ "$DEPLOY_ELECTRON" = 1 ] || [ "$DEPLOY_CHROMIUM" = 1 ]; then
|
|
_echo "* Deploying electron/chromium"
|
|
DEPLOY_COMMON_LIBS=${DEPLOY_COMMON_LIBS:-1}
|
|
DEPLOY_P11KIT=${DEPLOY_P11KIT:-1}
|
|
DEPLOY_OPENGL=${DEPLOY_OPENGL:-1}
|
|
DEPLOY_VULKAN=${DEPLOY_VULKAN:-1}
|
|
set -- "$@" \
|
|
"$LIB_DIR"/libnss*.so* \
|
|
"$LIB_DIR"/libsoftokn3.so* \
|
|
"$LIB_DIR"/libfreeblpriv3.so* \
|
|
"$LIB_DIR"/libnss_mdns*_minimal.so*
|
|
# electron has a resources directory that may have binaries
|
|
d="${ELECTRON_BIN%/*}"/resources
|
|
if [ -d "$d" ]; then
|
|
for f in $(find "$d" -type f ! -name '*.so*'); do
|
|
if _is_deployable_binary "$f"; then
|
|
set -- "$@" "$f"
|
|
fi
|
|
done
|
|
fi
|
|
# electron bundled libs always need to load first
|
|
# for example libcef.so may need to read a icudtl.dat next to it
|
|
echo 'SHARUN_EXTRA_LIBRARY_PATH=${SHARUN_DIR}/bin:${SHARUN_EXTRA_LIBRARY_PATH}' >> "$APPENV"
|
|
fi
|
|
if [ "$DEPLOY_OPENGL" = 1 ] || [ "$DEPLOY_VULKAN" = 1 ]; then
|
|
DEPLOY_COMMON_LIBS=${DEPLOY_COMMON_LIBS:-1}
|
|
set -- "$@" \
|
|
"$LIB_DIR"/dri/* \
|
|
"$LIB_DIR"/gbm/* \
|
|
"$LIB_DIR"/vdpau/* \
|
|
"$LIB_DIR"/libgbm.so* \
|
|
"$LIB_DIR"/libvdpau.so* \
|
|
"$LIB_DIR"/libpci.so* \
|
|
"$LIB_DIR"/libva.so* \
|
|
"$LIB_DIR"/libva-*.so* \
|
|
"$LIB_DIR"/libdrm*.so* \
|
|
"$LIB_DIR"/libxcb-dri*.so* \
|
|
"$LIB_DIR"/libxcb-glx.so* \
|
|
"$LIB_DIR"/libgallium*.so*
|
|
if [ "$DEPLOY_OPENGL" = 1 ]; then
|
|
_echo "* Deploying OpenGL"
|
|
set -- "$@" \
|
|
"$LIB_DIR"/libEGL*.so* \
|
|
"$LIB_DIR"/libGLX*.so* \
|
|
"$LIB_DIR"/libGL.so* \
|
|
"$LIB_DIR"/libOpenGL.so* \
|
|
"$LIB_DIR"/libGLESv2.so*
|
|
fi
|
|
if [ "$DEPLOY_VULKAN" = 1 ]; then
|
|
_echo "* Deploying vulkan"
|
|
set -- "$@" \
|
|
"$LIB_DIR"/libvulkan*.so* \
|
|
"$LIB_DIR"/libVkLayer*.so*
|
|
ADD_HOOKS="${ADD_HOOKS:+$ADD_HOOKS:}vulkan-check.hook"
|
|
fi
|
|
fi
|
|
if [ "$DEPLOY_PIPEWIRE" = 1 ]; then
|
|
_echo "* Deploying pipewire"
|
|
DEPLOY_PULSE=${DEPLOY_PULSE:-1}
|
|
set -- "$@" \
|
|
"$LIB_DIR"/pipewire-*/* \
|
|
"$LIB_DIR"/spa-*/* \
|
|
"$LIB_DIR"/spa-*/*/* \
|
|
"$LIB_DIR"/alsa-lib/*pipewire*.so*
|
|
fi
|
|
if [ "$DEPLOY_PULSE" = 1 ]; then
|
|
set -- "$@" \
|
|
"$LIB_DIR"/libpulse.so* \
|
|
"$LIB_DIR"/alsa-lib/libasound*pulse*.so*
|
|
fi
|
|
# deploy a minimal set of alsa plugins, we don't have a deploy alsa mode
|
|
# it would make no sense to ship an application with only support for alsa
|
|
#
|
|
# but we end up with libasound.so bundled as a transitive dependency of
|
|
# pipewire or pulseaudio which means people on alsa only setups will run
|
|
# into issues if the alsa plugins are missing
|
|
if [ "$DEPLOY_PULSE" = 1 ] || [ "$DEPLOY_PIPEWIRE" = 1 ]; then
|
|
set -- "$@" \
|
|
"$LIB_DIR"/alsa-lib/libasound*pcm_upmix.so* \
|
|
"$LIB_DIR"/alsa-lib/libasound*pcm_vdownmix.so* \
|
|
"$LIB_DIR"/alsa-lib/libasound*pcm_speex.so* \
|
|
"$LIB_DIR"/alsa-lib/libasound*pcm_usb_stream.so* \
|
|
"$LIB_DIR"/alsa-lib/libasound*rate_speexrate*.so* \
|
|
"$LIB_DIR"/alsa-lib/libasound*rate_samplerate*.so*
|
|
fi
|
|
if [ "$DEPLOY_GSTREAMER_ALL" = 1 ] || [ "$DEPLOY_GSTREAMER" = 1 ]; then
|
|
GST_DIR=$(echo "$LIB_DIR"/gstreamer-*)
|
|
if [ "$DEPLOY_GSTREAMER_ALL" = 1 ]; then
|
|
_echo "* Deploying all gstreamer"
|
|
elif [ "$DEPLOY_GSTREAMER" = 1 ]; then
|
|
_echo "* Deploying minimal gstreamer"
|
|
|
|
# we need to delete the plugins on the host because copying
|
|
# the libs to a different place and pointing to that dir
|
|
# does not work, all the plugins still end up being deployed
|
|
|
|
# check we have write access to the directory
|
|
# and make sure we are in a container since someone could
|
|
# run this script in their personal PC with elevated rights...
|
|
if [ -w "$GST_DIR" ] && [ -n "$CI" ]; then
|
|
# gstreamer has a lot of plugins
|
|
# remove the following since they pull a lot of deps:
|
|
|
|
# has a dependency to libicudata (30 MIB lib)
|
|
rm -f "$GST_DIR"/*gstladspa*
|
|
# gstx265 has a dependency to libx265, massive library
|
|
rm -f "$GST_DIR"/*gstx265*
|
|
# gstsvt-hevc video encoder, rarely needed
|
|
rm -f "$GST_DIR"/*gstsvthevcenc*
|
|
# Apparently this is only useful in windows?
|
|
rm -f "$GST_DIR"/*gstopenmpt*
|
|
# Never heard of this format before lol
|
|
rm -f "$GST_DIR"/*gstopenexr*
|
|
# used to scan barcodes
|
|
rm -f "$GST_DIR"/*gstzxing*
|
|
# dvd playback
|
|
rm -f "$GST_DIR"/*gstdvdspu*
|
|
rm -f "$GST_DIR"/*gstresindvd*
|
|
# only needed for recording with some capture card
|
|
rm -f "$GST_DIR"/*gstdecklink*
|
|
# mpeg2 video encoder
|
|
rm -f "$GST_DIR"/*gstmpeg2enc*
|
|
# wtf is this?
|
|
rm -f "$GST_DIR"/*gstmplex*
|
|
# gstreamer already has png and svg plugins
|
|
# so it is unlikely that we also need gdkpixbuf
|
|
rm -f "$GST_DIR"/*libgstgdkpixbuf*
|
|
# Apprently this can be used by some video players
|
|
# but I cannot find a single one that uses it lol
|
|
rm -f "$GST_DIR"/*libgstcairo*
|
|
# text to speech
|
|
rm -f "$GST_DIR"/*libgstfestival*
|
|
# gstvulkan pulls vulkan, remove unless vulkan is deployed
|
|
if [ "$DEPLOY_VULKAN" != 1 ]; then
|
|
rm -f "$GST_DIR"/*gstvulkan*
|
|
fi
|
|
# also make sure to delete gstreamer plugins
|
|
# that are missing libraries, otherwise they
|
|
# will load libraries from the host and crash
|
|
for plugin in "$GST_DIR"/*.so*; do
|
|
if ldd "$plugin" | grep -q 'not found'; then
|
|
rm -f "$plugin"
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
set -- "$@" \
|
|
"$GST_DIR"/*.so* \
|
|
"$GST_DIR"/gst*helper \
|
|
"$GST_DIR"/gst*scanner
|
|
# On ubuntu and alpine the gstreamer binaries are on a different dir
|
|
if [ ! -f "$GST_DIR"/gst-plugin-scanner ]; then
|
|
gst_bin_path=$(find /usr/lib* -type f \
|
|
-name 'gst-plugin-scanner' -print -quit)
|
|
gst_bin_dir=${gst_bin_path%/*}
|
|
set -- "$@" \
|
|
"$gst_bin_dir"/gst*scanner \
|
|
"$gst_bin_dir"/gst*helper
|
|
fi
|
|
fi
|
|
if [ "$DEPLOY_IMAGEMAGICK" = 1 ]; then
|
|
_echo "* Deploying ImageMagick"
|
|
set -- "$@" "$LIB_DIR"/libMagick*.so*
|
|
if b=$(command -v magick); then set -- "$@" "$b"; fi
|
|
if b=$(command -v convert); then set -- "$@" "$b"; fi
|
|
# imagemagick optionally requires potrace to convert png to svg
|
|
if b=$(command -v potrace); then set -- "$@" "$b"; fi
|
|
|
|
magickdir=$(echo "$LIB_DIR"/ImageMagick*)
|
|
ADD_DIR="
|
|
$ADD_DIR
|
|
$magickdir
|
|
"
|
|
fi
|
|
if [ "$DEPLOY_IMLIB2" = 1 ]; then
|
|
_echo "* Deploying Imlib2"
|
|
set -- "$@" \
|
|
"$LIB_DIR"/libImlib2.so* \
|
|
"$LIB_DIR"/imlib2/filters/* \
|
|
"$LIB_DIR"/imlib2/loaders/*
|
|
fi
|
|
if [ "$DEPLOY_SYS_PYTHON" = 1 ]; then
|
|
if b=$(command -v python); then set -- "$@" "$b"*
|
|
elif b=$(command -v python3); then set -- "$@" "$b"*
|
|
fi
|
|
|
|
d=$(set -- "$LIB_DIR"/python* && echo "$1")
|
|
if [ ! -d "$d" ]; then
|
|
_err_msg "ERROR: Cannot find python installation in $LIB_DIR"
|
|
exit 1
|
|
fi
|
|
mkdir -p "$DST_LIB_DIR"
|
|
cp -r "$d" "$DST_LIB_DIR"
|
|
(
|
|
if [ "$DEBLOAT_SYS_PYTHON" = 1 ]; then
|
|
cd "$DST_LIB_DIR"/"${d##*/}"
|
|
find ./ -type f -name '*.a' -delete || :
|
|
for f in $(find ./ -type f -name '*.pyc' -print); do
|
|
case "$f" in
|
|
*/"$MAIN_BIN"*) :;;
|
|
*) [ ! -f "$f" ] || rm -f "$f";;
|
|
esac
|
|
done
|
|
fi
|
|
)
|
|
_fix_cpython_ldconfig_mess
|
|
fi
|
|
if [ "$DEPLOY_GEGL" = 1 ]; then
|
|
_echo "* Deploying gegl"
|
|
set -- "$@" "$LIB_DIR"/gegl-*/*
|
|
if b=$(command -v gegl); then set -- "$@" "$b"; fi
|
|
if b=$(command -v gegl-imgcmp); then set -- "$@" "$b"; fi
|
|
fi
|
|
if [ "$DEPLOY_BABL" = 1 ]; then
|
|
_echo "* Deploying babl"
|
|
set -- "$@" "$LIB_DIR"/babl-*/*
|
|
fi
|
|
if [ "$DEPLOY_LIBHEIF" = 1 ]; then
|
|
_echo "* Deploying libheif"
|
|
|
|
if [ -d "$LIB_DIR"/libheif/plugins ]; then
|
|
heifdir="$LIB_DIR"/libheif/plugins
|
|
elif [ -d "$LIB_DIR"/libheif ]; then
|
|
heifdir="$LIB_DIR"/libheif
|
|
fi
|
|
|
|
# do not add the ffmpeg plugin by default
|
|
# only do it if libavcodec is already required
|
|
for p in "$heifdir"/*; do
|
|
case "$p" in
|
|
*ffmpeg*) continue;;
|
|
*) set -- "$@" "$p";;
|
|
esac
|
|
done
|
|
for lib in $NEEDED_LIBS; do
|
|
case "$lib" in
|
|
*libavcodec.so*) set -- "$@" "$heifdir"/*ffmpeg*.so*;;
|
|
esac
|
|
done
|
|
fi
|
|
if [ "$DEPLOY_GHOSTSCRIPT" = 1 ]; then
|
|
_echo "* Deploying ghostscript"
|
|
set -- "$@" "$LIB_DIR"/libgs.so*
|
|
if b=$(command -v gs); then set -- "$@" "$b"; fi
|
|
fi
|
|
if [ "$DEPLOY_P11KIT" = 1 ]; then
|
|
_echo "* Deploying p11kit"
|
|
set -- "$@" "$LIB_DIR"/pkcs11/*
|
|
fi
|
|
if [ "$DEPLOY_DOTNET" = 1 ]; then
|
|
_echo "* Deploying dotnet"
|
|
DEPLOY_COMMON_LIBS=${DEPLOY_COMMON_LIBS:-1}
|
|
if [ -z "$DOTNET_DIR" ]; then
|
|
if [ -d /usr/lib/dotnet ]; then
|
|
DOTNET_DIR=/usr/lib/dotnet
|
|
elif [ -d /usr/share/dotnet ]; then
|
|
DOTNET_DIR=/usr/share/dotnet
|
|
fi
|
|
fi
|
|
if [ ! -d "$DOTNET_DIR" ]; then
|
|
_err_msg "Cannot find dotnet installation, searched for"
|
|
_err_msg "/usr/lib/dotnet and /usr/share/dotnet"
|
|
_err_msg "Set DOTNET_DIR variable if it is somewhere else"
|
|
exit 1
|
|
fi
|
|
set -- "$@" \
|
|
"$(command -v dotnet)" \
|
|
$(find "$DOTNET_DIR"/shared -type f -name '*.so*' -print)
|
|
cp -r "$DOTNET_DIR"/shared "$DST_BIN_DIR"
|
|
cp -r "$DOTNET_DIR"/host "$DST_BIN_DIR"
|
|
echo 'DOTNET_ROOT=${SHARUN_DIR}/bin' >> "$APPENV"
|
|
fi
|
|
# these are needed by several toolkits
|
|
if [ "$DEPLOY_COMMON_LIBS" = 1 ]; then
|
|
set -- "$@" \
|
|
"$LIB_DIR"/libXi.so* \
|
|
"$LIB_DIR"/libXcursor.so* \
|
|
"$LIB_DIR"/libXtst.so* \
|
|
"$LIB_DIR"/libxcb-ewmh.so* \
|
|
"$LIB_DIR"/libxcb-icccm.so* \
|
|
"$LIB_DIR"/libxkbcommon.so* \
|
|
"$LIB_DIR"/libxkbcommon-x11.so* \
|
|
"$LIB_DIR"/libXext.so* \
|
|
"$LIB_DIR"/libXfixes.so* \
|
|
"$LIB_DIR"/libXinerama.so* \
|
|
"$LIB_DIR"/libXrandr.so* \
|
|
"$LIB_DIR"/libXss.so* \
|
|
"$LIB_DIR"/libX11-xcb.so* \
|
|
"$LIB_DIR"/libwayland-egl.so* \
|
|
"$LIB_DIR"/libwayland-cursor.so* \
|
|
"$LIB_DIR"/libwayland-client.so*
|
|
fi
|
|
|
|
# also pass all the files in the directories to add to lib4bin
|
|
# so we deploy any possible library and binary in the directories
|
|
# later on the binaries in lib will be wrapped with sharun
|
|
if [ -n "$ADD_DIR" ]; then
|
|
_echo "* Deploying directories:"
|
|
while read -r d; do
|
|
if [ -d "$d" ]; then
|
|
_echo " - $d"
|
|
for f in \
|
|
"$d"/* \
|
|
"$d"/*/* \
|
|
"$d"/*/*/* \
|
|
"$d"/*/*/*/* \
|
|
"$d"/*/*/*/*/* \
|
|
"$d"/*/*/*/*/*/*; do
|
|
|
|
if [ ! -f "$f" ]; then
|
|
continue
|
|
fi
|
|
|
|
case "$f" in
|
|
*.so*)
|
|
set -- "$@" "$f"
|
|
;;
|
|
*)
|
|
if _is_deployable_binary "$f"; then
|
|
set -- "$@" "$f"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
fi
|
|
done <<-EOF
|
|
$ADD_DIR
|
|
EOF
|
|
fi
|
|
|
|
TO_DEPLOY_ARRAY=$(_save_array "$@")
|
|
}
|
|
|
|
_get_sharun() {
|
|
if [ -x "$APPDIR"/sharun ]; then
|
|
return 0
|
|
fi
|
|
_echo "Downloading sharun..."
|
|
_download "$APPDIR"/sharun "$SHARUN_LINK"
|
|
if _is_elf "$APPDIR"/sharun; then
|
|
chmod +x "$APPDIR"/sharun
|
|
else
|
|
_err_msg "ERROR: What was downloaded is not sharun!"
|
|
_err_msg "This is usually caused by network issues"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
_deploy_libs() {
|
|
# now merge the deployment array
|
|
eval set -- "$TO_DEPLOY_ARRAY" "$@"
|
|
_lib4bin_main "$@"
|
|
}
|
|
|
|
# compute destination path in DST_LIB_DIR from a source file path
|
|
_lib4bin_get_lib_dst_dir() {
|
|
p=$(readlink -f "${1%/*}" | sed \
|
|
-e "s|^$LIB_DIR||" \
|
|
-e 's|^/usr||' \
|
|
-e 's|^/opt||' \
|
|
-e 's|^/lib64||' \
|
|
-e 's|^/lib32||' \
|
|
-e 's|^/lib||' \
|
|
-e 's|^/[^/]*-linux-gnu||'
|
|
)
|
|
echo "$DST_LIB_DIR"/"$p"
|
|
}
|
|
|
|
# collect ldd library dependencies
|
|
_lib4bin_collect_ldd() {
|
|
libs=""
|
|
for b do
|
|
[ -f "$b" ] || continue
|
|
_is_elf "$b" || continue
|
|
|
|
skip=""
|
|
# do not deploy dependencies for libs in QUICK_SHARUN_SKIP_DEPS_FOR
|
|
while read -r d; do
|
|
if [ "$d" = "${b##*/}" ]; then
|
|
skip=1
|
|
break
|
|
fi
|
|
done <<-EOF
|
|
$QUICK_SHARUN_SKIP_DEPS_FOR
|
|
EOF
|
|
|
|
if [ -z "$skip" ]; then
|
|
libs=$(printf '%s\n%s' "$libs" "$(_lib4bin_ldd_libs "$b")")
|
|
fi
|
|
|
|
if _is_so "$b"; then
|
|
libs=$(printf '%s\n%s' "$libs" "$b")
|
|
fi
|
|
done
|
|
echo "$libs" | sort -u | sed '/^$/d'
|
|
}
|
|
|
|
# collect dlopen libraries via LD_DEBUG=libs
|
|
# STRACE_BINARY=space/newline-separated binary names to trace (default: all)
|
|
_lib4bin_collect_strace() {
|
|
[ "$STRACE_MODE" = 1 ] || return 0
|
|
|
|
libs=""
|
|
for b do
|
|
[ -f "$b" ] || continue
|
|
_is_elf "$b" || continue
|
|
[ -x "$b" ] || continue
|
|
if _is_so "$b"; then
|
|
continue
|
|
fi
|
|
|
|
if [ -n "$STRACE_BINARY" ]; then
|
|
match=""
|
|
flags=""
|
|
for strace_bin in $STRACE_BINARY; do
|
|
if [ "$strace_bin" = "${b##*/}" ]; then
|
|
match=1
|
|
flags=$STRACE_FLAGS
|
|
break
|
|
fi
|
|
done
|
|
[ -n "$match" ] || continue
|
|
fi
|
|
|
|
dlopened=$TMPDIR/libs.$$
|
|
|
|
_echo "STRACE: [$b] ..."
|
|
set -m
|
|
if [ -n "$XVFB_CMD" ]; then
|
|
$XVFB_CMD LD_DEBUG=libs "$b" $flags >/dev/null 2>"$dlopened" &
|
|
else
|
|
LD_DEBUG=libs "$b" $flags >/dev/null 2>"$dlopened" &
|
|
fi
|
|
pid=$!
|
|
set +m
|
|
|
|
sleep "$STRACE_TIME"
|
|
kill -TERM -$pid 2>/dev/null || :
|
|
sleep 1
|
|
kill -KILL -$pid 2>/dev/null || :
|
|
wait $pid 2>/dev/null || :
|
|
|
|
out=$(awk '/calling init/{print $NF}' "$dlopened" | sed \
|
|
-e '/nvidia/d' \
|
|
-e '/libcuda/d' \
|
|
-e '/lib-dynload/d' \
|
|
-e '/_internal/d'
|
|
)
|
|
rm -f "$dlopened"
|
|
[ -n "$out" ] || continue
|
|
libs=$(printf '%s\n%s' "$libs" "$out")
|
|
done
|
|
echo "$libs" | sort -u | sed '/^$/d'
|
|
}
|
|
|
|
# deploy shared libraries to DST_LIB_DIR
|
|
_lib4bin_deploy_shared_libs() {
|
|
for l do
|
|
[ -f "$l" ] || continue
|
|
if ! _is_elf "$l"; then
|
|
_echo "SKIPPED: [$l] not shared object!"
|
|
continue
|
|
fi
|
|
|
|
l_name=${l##*/}
|
|
|
|
if [ -L "$l" ]; then
|
|
readlink_path=$(readlink -f "$l") || continue
|
|
readlink_path_name=${readlink_path##*/}
|
|
dst_dir=$(_lib4bin_get_lib_dst_dir "$readlink_path")
|
|
else
|
|
readlink_path_name=""
|
|
dst_dir=$(_lib4bin_get_lib_dst_dir "$l")
|
|
fi
|
|
|
|
if [ -n "$readlink_path_name" ]; then
|
|
dst=$dst_dir/$readlink_path_name
|
|
else
|
|
dst=$dst_dir/$l_name
|
|
fi
|
|
|
|
mkdir -p "$dst_dir"
|
|
[ -f "$dst" ] || cp -fv "$l" "$dst"
|
|
|
|
# symlink may land in a "wrong" subdir if it targets a relative dir
|
|
# example: libfltk.so.1.3.11 -> fltk1.3/libfltk.so.1.3.11 on archlinux
|
|
# but sharun adds all subdirs to --library-path, so nothing should break
|
|
# note: original lib4bin actually made broken symlinks when this happened
|
|
if [ -n "$readlink_path_name" ] \
|
|
&& [ "$l_name" != "$readlink_path_name" ]; then
|
|
ln -sf "$readlink_path_name" "$dst_dir"/"$l_name"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# deploy binaries, download sharun if needed
|
|
_lib4bin_deploy_binaries() {
|
|
seen=""
|
|
for b do
|
|
echo "$seen" | grep -Fxq "$b" && continue
|
|
seen=$(printf '%s\n%s' "$seen" "$b")
|
|
|
|
[ -f "$b" ] || continue
|
|
|
|
if _is_script "$b"; then
|
|
dst=$DST_BIN_DIR/${b##*/}
|
|
mkdir -p "$DST_BIN_DIR"
|
|
if [ ! -f "$dst" ]; then
|
|
cp -fv "$b" "$dst"
|
|
chmod 755 "$dst"
|
|
fi
|
|
for i in python bash sh ash zsh fish dash perl ruby go node; do
|
|
if grep -qo "^#!.*bin/$i" "$dst"; then
|
|
sed -i "1s|^#!.*bin/$i|#!/usr/bin/env $i|" "$dst"
|
|
break
|
|
fi
|
|
done
|
|
continue
|
|
fi
|
|
|
|
_is_elf "$b" || continue
|
|
[ -x "$b" ] || continue
|
|
if _is_so "$b"; then
|
|
continue
|
|
fi
|
|
|
|
[ -x "$APPDIR/sharun" ] || _get_sharun
|
|
|
|
b_name=${b##*/}
|
|
|
|
if [ -L "$b" ]; then
|
|
readlink_path=$(readlink -f "$b") || continue
|
|
readlink_path_name=${readlink_path##*/}
|
|
else
|
|
readlink_path_name=""
|
|
fi
|
|
|
|
if [ -n "$readlink_path_name" ]; then
|
|
dst=$SHARUN_BIN_DIR/$readlink_path_name
|
|
else
|
|
dst=$SHARUN_BIN_DIR/$b_name
|
|
fi
|
|
|
|
mkdir -p "$SHARUN_BIN_DIR"
|
|
if [ ! -f "$dst" ]; then
|
|
cp -fv "$b" "$dst"
|
|
chmod +x "$dst"
|
|
fi
|
|
|
|
if [ -n "$readlink_path_name" ] \
|
|
&& [ "$b_name" != "$readlink_path_name" ]; then
|
|
ln -sf "$readlink_path_name" "$SHARUN_BIN_DIR"/"$b_name"
|
|
fi
|
|
|
|
# hardlink in bin/ -> ../sharun
|
|
mkdir -p "$DST_BIN_DIR" && (
|
|
cd "$DST_BIN_DIR"
|
|
ln -f ../sharun "$b_name"
|
|
if [ -n "$readlink_path_name" ] \
|
|
&& [ "$b_name" != "$readlink_path_name" ]; then
|
|
ln -f ../sharun "$readlink_path_name"
|
|
fi
|
|
) || exit 1
|
|
done
|
|
}
|
|
|
|
_lib4bin_main() {
|
|
_echo "Collecting dependencies..."
|
|
ldd_libs=$(_lib4bin_collect_ldd "$@")
|
|
|
|
strace_libs=''
|
|
if [ "$STRACE_MODE" = 1 ]; then
|
|
_echo "Collecting dlopen libraries via LD_DEBUG=libs..."
|
|
strace_libs="$(_lib4bin_collect_strace "$@")"
|
|
fi
|
|
|
|
all_libs=$(printf '%s\n%s' "$ldd_libs" "$strace_libs" | sort -u | sed '/^$/d')
|
|
|
|
_echo "Deploying shared libraries..."
|
|
_lib4bin_deploy_shared_libs $all_libs
|
|
|
|
_echo "Deploying binaries..."
|
|
_lib4bin_deploy_binaries "$@"
|
|
|
|
_echo "Generating lib.path..."
|
|
if [ -x "$APPDIR/sharun" ]; then
|
|
"$APPDIR/sharun" -g
|
|
else
|
|
_err_msg "ERROR: sharun binary not found at $APPDIR/sharun!"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
_handle_bins_scripts() {
|
|
# check for gstreamer binaries these need to be in the gstreamer libdir
|
|
# since sharun will set the following vars to that location:
|
|
# GST_PLUGIN_PATH
|
|
# GST_PLUGIN_SYSTEM_PATH
|
|
# GST_PLUGIN_SYSTEM_PATH_1_0
|
|
# GST_PLUGIN_SCANNER
|
|
set -- "$DST_LIB_DIR"/gstreamer-*
|
|
if [ -d "$1" ]; then
|
|
gstlibdir="$1"
|
|
set -- "$SHARUN_BIN_DIR"/gst-*
|
|
for bin do
|
|
if [ -f "$bin" ]; then
|
|
ln "$APPDIR"/sharun "$gstlibdir"/"${bin##*/}"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ "$DEPLOY_QT_WEB_ENGINE" = 1 ]; then
|
|
src_res=/usr/share/$QT_DIR/resources
|
|
dst_res=$DST_LIB_DIR/$QT_DIR/resources
|
|
if [ -d "$src_res" ] && [ ! -d "$dst_res" ]; then
|
|
mkdir -p "${dst_res%/*}"
|
|
cp -r "$src_res" "$dst_res"
|
|
fi
|
|
fi
|
|
|
|
# handle shell scripts
|
|
set -- "$DST_BIN_DIR"/*
|
|
for s do
|
|
if ! head -c 20 "$s" | grep -q '#!.*sh'; then
|
|
continue
|
|
fi
|
|
# some very very old distros do not have /usr/bin/env
|
|
# so it is better to always use #!/bin/sh shebang instead
|
|
sed -i -e 's|/usr/bin/env sh|/bin/sh|' "$s"
|
|
|
|
# patch away hardcoded paths from dotnet scripts
|
|
if grep -q 'dotnet' "$s"; then
|
|
sed -i -e '/^#/!s|/usr|"$APPDIR"|g' "$s"
|
|
fi
|
|
done
|
|
|
|
}
|
|
|
|
_add_anylinux_lib() {
|
|
cfile=$APPDIR/.anylinux.c
|
|
target=$DST_LIB_DIR/anylinux.so
|
|
|
|
if [ "$ANYLINUX_LIB" != 1 ]; then
|
|
return 0
|
|
elif [ ! -f "$target" ]; then
|
|
_echo "* Building anylinux.so..."
|
|
_download "$cfile" "$ANYLINUX_LIB_SOURCE"
|
|
|
|
set -- -shared -fPIC -O2 "$cfile" -o "$target"
|
|
if [ "$LIB32" = 1 ]; then
|
|
set -- -m32 "$@"
|
|
fi
|
|
cc "$@"
|
|
fi
|
|
|
|
if ! grep -q 'anylinux.so' "$APPDIR"/.preload 2>/dev/null; then
|
|
echo "anylinux.so" >> "$APPDIR"/.preload
|
|
fi
|
|
|
|
_echo "* anylinux.so successfully added!"
|
|
}
|
|
|
|
_add_gtk_class_fix() {
|
|
cfile=$APPDIR/.gtk-class-fix.c
|
|
target=$DST_LIB_DIR/gtk-class-fix.so
|
|
|
|
if [ "$GTK_CLASS_FIX" != 1 ]; then
|
|
return 0
|
|
elif [ ! -f "$DESKTOP_ENTRY" ]; then
|
|
_err_msg "ERROR: Using GTK_CLASS_FIX requires a desktop entry in $APPDIR"
|
|
exit 1
|
|
elif [ "$ANYLINUX_LIB" != 1 ]; then
|
|
_err_msg "ERROR: GTK_CLASS_FIX requires ANYLINUX_LIB=1"
|
|
exit 1
|
|
fi
|
|
|
|
_echo "* Building gtk-class-fix.so"
|
|
_download "$cfile" "$GTK_CLASS_FIX_SOURCE"
|
|
|
|
set -- -shared -fPIC -O2 "$cfile" -o "$target" -ldl
|
|
if [ "$LIB32" = 1 ]; then
|
|
set -- -m32 "$@"
|
|
fi
|
|
cc "$@"
|
|
|
|
# _check_window_class will make sure StartupWMClass is added to desktop entry
|
|
# for this to work in wayland, the class needs to have one dot in its name
|
|
if ! grep -q 'StartupWMClass=.*\..*' "$DESKTOP_ENTRY"; then
|
|
sed -i -e 's/\(StartupWMClass=.*\)/\1.anylinux/' "$DESKTOP_ENTRY"
|
|
fi
|
|
|
|
class=$(awk -F'=| ' '/^StartupWMClass=/{print $2; exit}' "$DESKTOP_ENTRY")
|
|
|
|
echo "GTK_WINDOW_CLASS=$class" >> "$APPDIR"/.env
|
|
echo "gtk-class-fix.so" >> "$APPDIR"/.preload
|
|
_echo "* gtk-class-fix.so successfully added!"
|
|
}
|
|
|
|
_check_always_software() {
|
|
if [ "$ALWAYS_SOFTWARE" != 1 ]; then
|
|
return 0
|
|
fi
|
|
set -- "$DST_LIB_DIR"/libgallium-*.so*
|
|
if [ -f "$1" ]; then
|
|
_err_msg "ALWAYS_SOFTWARE was enabled but mesa was deployed!"
|
|
_err_msg "Likely this application needs hardware acceleration."
|
|
_err_msg "Do not use this option or find a way to make sure"
|
|
_err_msg "the application does not dlopen mesa when running!"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
_add_p11kit_cert_hook() {
|
|
cert_check=$DST_BIN_DIR/01-check-ca-certs.hook
|
|
if [ -f "$cert_check" ]; then
|
|
return 0
|
|
fi
|
|
|
|
cat <<-'EOF' > "$cert_check"
|
|
#!/bin/sh
|
|
|
|
_possible_certs='
|
|
/etc/ssl/certs/ca-certificates.crt
|
|
/etc/pki/tls/cert.pem
|
|
/etc/pki/tls/cacert.pem
|
|
/etc/ssl/cert.pem
|
|
/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
|
|
/var/lib/ca-certificates/ca-bundle.pem
|
|
'
|
|
|
|
for c in $_possible_certs; do
|
|
if [ -f "$c" ]; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -f "$c" ]; then
|
|
# With p11kit we have to make a symlink in /tmp because the meme
|
|
# library does not check any of these variables set by sharun:
|
|
#
|
|
# REQUESTS_CA_BUNDLE
|
|
# CURL_CA_BUNDLE
|
|
# SSL_CERT_FILE
|
|
#
|
|
# So we had to patch it to a path in /tmp and now symlink to the
|
|
# found certificate at runtime...
|
|
_host_cert=/tmp/.___host-certs/ca-certificates.crt
|
|
if [ -d "$APPDIR"/lib/pkcs11 ] && [ ! -f "$_host_cert" ]; then
|
|
mkdir -p /tmp/.___host-certs || :
|
|
ln -sfn "$c" "$_host_cert" || :
|
|
fi
|
|
fi
|
|
EOF
|
|
chmod +x "$cert_check"
|
|
}
|
|
|
|
_map_paths_ld_preload_open() {
|
|
# format new line entries in PATH_MAPPING into comma separated
|
|
# entries for sharun, pathmap accepts new lines in the variable
|
|
# but the .env library used by sharun does not
|
|
if [ -n "$PATH_MAPPING" ] && [ ! -f "$DST_LIB_DIR"/path-mapping.so ]; then
|
|
PATH_MAPPING=$(echo "$PATH_MAPPING" \
|
|
| tr '\n' ',' | tr -d '[:space:]' | sed 's/,*$//; s/^,*//'
|
|
)
|
|
|
|
deps="git make"
|
|
if ! _is_cmd $deps; then
|
|
_err_msg "ERROR: Using PATH_MAPPING requires $deps"
|
|
exit 1
|
|
fi
|
|
|
|
_echo "* Building $LD_PRELOAD_OPEN..."
|
|
|
|
rm -rf "$TMPDIR"/ld-preload-open
|
|
git clone "$LD_PRELOAD_OPEN" "$TMPDIR"/ld-preload-open && (
|
|
cd "$TMPDIR"/ld-preload-open
|
|
make all
|
|
)
|
|
|
|
mv -v "$TMPDIR"/ld-preload-open/path-mapping.so "$DST_LIB_DIR"
|
|
echo "path-mapping.so" >> "$APPDIR"/.preload
|
|
echo "PATH_MAPPING=$PATH_MAPPING" >> "$APPENV"
|
|
_echo "* PATH_MAPPING successfully added!"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
_map_paths_binary_patch() {
|
|
if [ "$PATH_MAPPING_HARDCODED" = 1 ]; then
|
|
set -- "$SHARUN_BIN_DIR"/*
|
|
for bin do
|
|
_patch_away_usr_bin_dir "$bin"
|
|
_patch_away_usr_lib_dir "$bin"
|
|
_patch_away_usr_share_dir "$bin"
|
|
done
|
|
elif [ -n "$PATH_MAPPING_HARDCODED" ]; then
|
|
set -f
|
|
set -- $PATH_MAPPING_HARDCODED
|
|
set +f
|
|
_echo "* Patching files listed in PATH_MAPPING_HARDCODED..."
|
|
# only search for files to patch in the lib and bin dirs
|
|
path1=$SHARUN_BIN_DIR
|
|
path2=$DST_LIB_DIR
|
|
for f do
|
|
file=$(find -L "$path1"/ "$path2"/ -type f -name "$f")
|
|
if [ -n "$file" ]; then
|
|
for found in $file; do
|
|
_patch_away_usr_bin_dir "$found" || :
|
|
_patch_away_usr_lib_dir "$found" || :
|
|
_patch_away_usr_share_dir "$found" || :
|
|
done
|
|
else
|
|
_err_msg "ERROR: Could not find $f in $APPDIR"
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
_deploy_datadir() {
|
|
if [ "$DEPLOY_DATADIR" = 1 ]; then
|
|
# find if there is a datadir that matches bundled binary name
|
|
set -- "$DST_BIN_DIR"/*
|
|
for bin do
|
|
if [ ! -f "$bin" ] || [ ! -x "$bin" ]; then
|
|
continue
|
|
fi
|
|
bin="${bin##*/}"
|
|
|
|
# skip already handled cases
|
|
case "$bin" in
|
|
dotnet) continue;;
|
|
esac
|
|
|
|
for datadir in /usr/local/share/* /usr/share/*; do
|
|
if echo "${datadir##*/}" | grep -qi "$bin"; then
|
|
_echo "* Adding datadir $datadir..."
|
|
# fallback to cp -r if cp -Lr fails
|
|
# due to broken symlinks in datadir
|
|
cp -Lr "$datadir" "$APPDIR"/share \
|
|
|| cp -r "$datadir" "$APPDIR"/share
|
|
break
|
|
fi
|
|
done
|
|
done
|
|
|
|
set -- "$APPDIR"/*.desktop
|
|
|
|
# Some apps have a datadir that does not match the binary name
|
|
# in that case we need to get it by reading the binary
|
|
if [ -f "$1" ]; then
|
|
|
|
bin=$(awk -F'=| ' '/^Exec=/{print $2; exit}' "$1")
|
|
bin=${bin##*/}
|
|
possible_dirs=$(
|
|
strings "$SHARUN_BIN_DIR"/"$bin" \
|
|
| grep -v '[;:,.(){}?<>*]' \
|
|
| tr '/' '\n'
|
|
)
|
|
|
|
for datadir in $possible_dirs; do
|
|
# skip dirs not wanted or handled by sharun
|
|
case "$datadir" in
|
|
alsa |\
|
|
applications|\
|
|
awk |\
|
|
bash |\
|
|
clang |\
|
|
dbus-1 |\
|
|
defaults |\
|
|
doc |\
|
|
dotnet |\
|
|
drirc.d |\
|
|
et |\
|
|
factory |\
|
|
file |\
|
|
fish |\
|
|
fonts |\
|
|
fontconfig |\
|
|
ghostscript |\
|
|
git |\
|
|
glib-* |\
|
|
glvnd |\
|
|
glycin* |\
|
|
gtk-doc |\
|
|
gtksource* |\
|
|
gvfs |\
|
|
help |\
|
|
i18n |\
|
|
icons |\
|
|
info |\
|
|
java |\
|
|
libdrm |\
|
|
libthai |\
|
|
locale |\
|
|
man |\
|
|
misc |\
|
|
mime |\
|
|
model |\
|
|
p11-kit |\
|
|
pipewire |\
|
|
pixmaps |\
|
|
qt |\
|
|
qt4 |\
|
|
qt5 |\
|
|
qt6 |\
|
|
qt7 |\
|
|
ss |\
|
|
swift |\
|
|
systemd |\
|
|
tabset |\
|
|
terminfo |\
|
|
themes |\
|
|
vala |\
|
|
vulkan |\
|
|
wayland |\
|
|
WebP |\
|
|
X11 |\
|
|
xcb |\
|
|
zoneinfo |\
|
|
zsh )
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
for path in /usr/local/share /usr/share; do
|
|
|
|
src_datadir="$path"/"$datadir"
|
|
dst_datadir="$APPDIR"/share/"$datadir"
|
|
|
|
if [ -d "$src_datadir" ] \
|
|
&& [ ! -d "$dst_datadir" ]; then
|
|
_echo "* Adding datadir $src_datadir..."
|
|
# cp can fail here if src_datadir contains broken links
|
|
if ! cp -Lr "$src_datadir" "$dst_datadir"; then
|
|
rm -rf "$dst_datadir"
|
|
cp -r "$src_datadir" "$dst_datadir"
|
|
fi
|
|
break
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
|
|
# try to find and deploy a dbus service that matches .desktop
|
|
desktopname="${1%.desktop}"
|
|
desktopname="${desktopname##*/}"
|
|
dst_dbus_dir="$APPDIR"/share/dbus-1/services
|
|
for f in /usr/share/dbus-1/services/*; do
|
|
case "${f##*/}" in
|
|
*"$desktopname"*)
|
|
_echo "* Adding dbus service $f"
|
|
mkdir -p "$dst_dbus_dir"
|
|
cp -L "$f" "$dst_dbus_dir"
|
|
;;
|
|
esac
|
|
done
|
|
sed -i -e 's|/usr/.*/||g' "$dst_dbus_dir"/* 2>/dev/null || :
|
|
fi
|
|
}
|
|
|
|
_deploy_locale() {
|
|
if [ ! -d /usr/share/locale ]; then
|
|
_err_msg "This system does not have /usr/share/locale"
|
|
return 0
|
|
fi
|
|
|
|
set -- "$SHARUN_BIN_DIR"/*
|
|
for bin do
|
|
if grep -Eaoq -m 1 "/usr/share/locale" "$bin"; then
|
|
DEPLOY_LOCALE=1
|
|
_patch_away_usr_share_dir "$bin" || true
|
|
fi
|
|
done
|
|
set --
|
|
|
|
if [ "$DEPLOY_LOCALE" = 1 ]; then
|
|
_echo "* Adding locales..."
|
|
cp -r "$LOCALE_DIR" "$APPDIR"/share
|
|
if [ "$DEBLOAT_LOCALE" = 1 ]; then
|
|
_echo "* Removing unneeded locales..."
|
|
for f in "$SHARUN_BIN_DIR"/* "$DST_BIN_DIR"/*; do
|
|
if [ -f "$f" ]; then
|
|
f=${f##*/}
|
|
set -- "$@" ! -name "*$f*"
|
|
fi
|
|
done
|
|
find "$APPDIR"/share/locale "$@" \( -type f -o -type l \) -delete
|
|
_remove_empty_dirs "$APPDIR"/share/locale
|
|
fi
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
_get_desktop() {
|
|
DESKTOP_ENTRY=$(echo "$APPDIR"/*.desktop)
|
|
if [ -f "$DESKTOP_ENTRY" ]; then
|
|
return 0
|
|
fi
|
|
|
|
if [ "$DESKTOP" = "DUMMY" ]; then
|
|
if [ -z "$MAIN_BIN" ]; then
|
|
_err_msg "ERROR: DESKTOP=DUMMY needs MAIN_BIN to be set"
|
|
exit 1
|
|
fi
|
|
_echo "* Adding dummy $MAIN_BIN desktop entry to $APPDIR..."
|
|
cat <<-EOF > "$APPDIR"/"$MAIN_BIN".desktop
|
|
[Desktop Entry]
|
|
Name=$MAIN_BIN
|
|
Exec=$MAIN_BIN
|
|
Comment=Dummy made by quick-sharun
|
|
Type=Application
|
|
Hidden=true
|
|
Categories=Utility
|
|
Icon=$MAIN_BIN
|
|
EOF
|
|
elif [ -f "$DESKTOP" ]; then
|
|
_echo "* Adding $DESKTOP to $APPDIR..."
|
|
cp -v "$DESKTOP" "$APPDIR"
|
|
elif echo "$DESKTOP" | grep -q 'http'; then
|
|
_echo "* Downloading $DESKTOP to $APPDIR..."
|
|
_download "$APPDIR"/"${DESKTOP##*/}" "$DESKTOP"
|
|
elif [ -n "$DESKTOP" ]; then
|
|
_err_msg "$DESKTOP is NOT a valid path!"
|
|
exit 1
|
|
fi
|
|
|
|
# make sure desktop entry ends with .desktop
|
|
if [ ! -f "$APPDIR"/*.desktop ] && [ -f "$APPDIR"/*.desktop* ]; then
|
|
filename="${DESKTOP##*/}"
|
|
mv "$APPDIR"/*.desktop* "$APPDIR"/"${filename%.desktop*}".desktop
|
|
fi
|
|
|
|
DESKTOP_ENTRY=$(echo "$APPDIR"/*.desktop)
|
|
if [ ! -f "$DESKTOP_ENTRY" ]; then
|
|
_err_msg "ERROR: No top level .desktop file found in $APPDIR"
|
|
_err_msg "Note there cannot be more than one .desktop file in that location"
|
|
exit 1
|
|
fi
|
|
|
|
# strip DBusActivatable=true
|
|
# this leads to broken apps since no appimage manager installs dbus services
|
|
sed -i -e '/DBusActivatable=/d' "$DESKTOP_ENTRY"
|
|
}
|
|
|
|
_check_window_class() {
|
|
set -- "$APPDIR"/*.desktop
|
|
|
|
# do not bother if no desktop entry or class is declared already
|
|
if [ ! -f "$1" ] || grep -q 'StartupWMClass=' "$1"; then
|
|
return 0
|
|
fi
|
|
|
|
if [ -z "$STARTUPWMCLASS" ]; then
|
|
_err_msg "WARNING: '$1' is missing StartupWMClass!"
|
|
_err_msg "We will fix it using the name of the binary but this"
|
|
_err_msg "may be wrong so please add the correct value if so"
|
|
_err_msg "set STARTUPWMCLASS so I can set that instead"
|
|
bin="$(awk -F'=| ' '/^Exec=/{print $2; exit}' "$1")"
|
|
bin=${bin##*/}
|
|
if [ -z "$bin" ]; then
|
|
_err_msg "ERROR: Unable to determine name of binary"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
class=${STARTUPWMCLASS:-$bin}
|
|
sed -i -e "/\[Desktop Entry\]/a\StartupWMClass=$class" "$1"
|
|
}
|
|
|
|
_add_bwrap_wrapper() {
|
|
cfile=$APPDIR/.anylinux-bwrap-wrapper.c
|
|
target=$DST_BIN_DIR/bwrap
|
|
realbwrap=$DST_BIN_DIR/bwrap.wrapped
|
|
|
|
|
|
if [ -f "$realbwrap" ]; then
|
|
return 0 # We wrapped it already
|
|
elif [ -f "$target" ]; then
|
|
# rename the real bwrap
|
|
mv "$target" "$realbwrap"
|
|
# rename the real binary as well
|
|
mv "$SHARUN_BIN_DIR"/"${target##*/}" "$SHARUN_BIN_DIR"/"${realbwrap##*/}"
|
|
else
|
|
# this should never happen, we are gonna exit without error however
|
|
# since maybe older versions of webkit2gtk can be installed without bwrap?
|
|
_err_msg "Something went very wrong here because bwrap was not deployed"
|
|
return 0
|
|
fi
|
|
|
|
cat <<-'EOF' > "$cfile"
|
|
/*
|
|
* anylinux-bwrap-wrapper — intercepts bubblewrap to inject essential bind mounts
|
|
* and remap hardcoded paths inside the sandbox.
|
|
*
|
|
* Many applications sandbox themselves via bwrap (e.g. WebKitGTK) but dont
|
|
* account for AppImage paths. Symlinks created by quick-sharun in /tmp become
|
|
* unresolvable inside the sandbox. This wrapper injects:
|
|
* --bind $APPDIR $APPDIR so the AppImage mount is visible inside
|
|
* --bind /tmp /tmp so sharun's /tmp symlinks stay valid
|
|
* --setenv SHARUN_DIR ... so child processes know the symlink prefix
|
|
* --setenv APPDIR ... so the AppDir path survives into the sandbox
|
|
* --setenv PATH ... so binaries in $APPDIR/bin get executed always
|
|
* --proc /proc so that sharun can read /proc/self/exe and work
|
|
*
|
|
* It also rewrites hardcoded command paths (e.g. /usr/bin/xdg-dbus-proxy) to
|
|
* their AppDir equivalents when found, so the AppImage's bundled binaries are
|
|
* used instead of the host.
|
|
*
|
|
* Two codepaths: "--args N" (options passed through a pipe) and plain argv.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
|
|
/* ---- Option tables ------------------------------------------------ */
|
|
/*
|
|
* bwrap options and how many trailing arguments they consume.
|
|
* Used by find_cmd_idx() to skip over values and not mistake them
|
|
* for the command to execute.
|
|
*/
|
|
static int opt_arg_count(const char *arg)
|
|
{
|
|
static const struct { const char *name; int n; } table[] = {
|
|
{ "--overlay", 3 }, /* RWSRC WORKDIR DEST */
|
|
{ "--bind", 2 }, { "--ro-bind", 2 },
|
|
{ "--bind-try", 2 }, { "--ro-bind-try", 2 },
|
|
{ "--dev-bind", 2 }, { "--dev-bind-try", 2 },
|
|
{ "--bind-data", 2 }, { "--ro-bind-data", 2 },
|
|
{ "--file", 2 }, { "--ro-file", 2 },
|
|
{ "--dev-mknod", 2 }, { "--symlink", 2 },
|
|
{ "--chmod", 2 }, { "--bind-fd", 2 },
|
|
{ "--ro-bind-fd", 2 }, { "--setenv", 2 },
|
|
{ "--tmpfs", 1 }, { "--proc", 1 },
|
|
{ "--dev", 1 }, { "--devpts", 1 },
|
|
{ "--mqueue", 1 }, { "--hostname", 1 },
|
|
{ "--seccomp", 1 }, { "--block-fd", 1 },
|
|
{ "--userns", 1 }, { "--uid", 1 },
|
|
{ "--gid", 1 }, { "--chdir", 1 },
|
|
{ "--unsetenv", 1 }, { "--lock-file", 1 },
|
|
{ "--sync-fd", 1 }, { "--info-fd", 1 },
|
|
{ "--json-status-fd", 1 }, { "--add-seccomp-fd", 1 },
|
|
{ "--add-feature", 1 }, { "--args", 1 },
|
|
{ "--dir", 1 }, { "--remount-ro", 1 },
|
|
{ "--perms", 1 }, { "--size", 1 },
|
|
{ "--argv0", 1 }, { "--overlay-src", 1 },
|
|
{ "--tmp-overlay", 1 }, { "--ro-overlay", 1 },
|
|
{ "--exec-label", 1 }, { "--file-label", 1 },
|
|
{ "--userns-block-fd", 1 }, { "--pidns", 1 },
|
|
{ NULL, 0 }
|
|
};
|
|
for (int i = 0; table[i].name; i++)
|
|
if (strcmp(arg, table[i].name) == 0) return table[i].n;
|
|
return 0;
|
|
}
|
|
|
|
/* ---- Helpers ------------------------------------------------------ */
|
|
|
|
/* Split a NUL-separated buffer into a null-terminated string array. */
|
|
static int parse_content(const char *buf, size_t len, char ***out)
|
|
{
|
|
int cap = 64, n = 0;
|
|
char **arr = calloc(cap, sizeof(char *));
|
|
if (!arr) return -1;
|
|
|
|
for (size_t i = 0, start = 0; i <= len; i++) {
|
|
if (i == len || buf[i] == '\0') {
|
|
size_t slen = i - start;
|
|
if (slen == 0) { start = i + 1; continue; }
|
|
if (n >= cap) {
|
|
cap *= 2;
|
|
char **tmp = realloc(arr, cap * sizeof(char *));
|
|
if (!tmp) { for (int k = 0; k < n; k++) free(arr[k]); free(arr); return -1; }
|
|
arr = tmp;
|
|
}
|
|
char *s = malloc(slen + 1);
|
|
memcpy(s, buf + start, slen);
|
|
s[slen] = '\0';
|
|
arr[n++] = s;
|
|
start = i + 1;
|
|
}
|
|
}
|
|
*out = arr;
|
|
return n;
|
|
}
|
|
|
|
/* Read all data from a file descriptor until EOF. */
|
|
static char *read_fd(int fd, size_t *out_len)
|
|
{
|
|
size_t cap = 4096, len = 0;
|
|
char *buf = malloc(cap);
|
|
if (!buf) return NULL;
|
|
ssize_t n;
|
|
while ((n = read(fd, buf + len, cap - len)) > 0) {
|
|
len += n;
|
|
if (len == cap) {
|
|
cap *= 2;
|
|
char *tmp = realloc(buf, cap);
|
|
if (!tmp) { free(buf); return NULL; }
|
|
buf = tmp;
|
|
}
|
|
}
|
|
if (n < 0) { free(buf); return NULL; }
|
|
buf[len] = '\0';
|
|
*out_len = len;
|
|
return buf;
|
|
}
|
|
|
|
/* Serialize a string array as NUL-separated bytes into a new pipe; return the read fd. */
|
|
static int serialize_to_pipe(char **args, int n)
|
|
{
|
|
size_t len = 0;
|
|
for (int i = 0; i < n; i++) len += strlen(args[i]) + 1;
|
|
|
|
char *buf = malloc(len);
|
|
if (!buf) return -1;
|
|
size_t pos = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
size_t sl = strlen(args[i]);
|
|
memcpy(buf + pos, args[i], sl);
|
|
pos += sl;
|
|
buf[pos++] = '\0';
|
|
}
|
|
|
|
int fds[2];
|
|
if (pipe(fds) != 0) { free(buf); return -1; }
|
|
fcntl(fds[1], F_SETFD, FD_CLOEXEC); /* write end only */
|
|
size_t off = 0;
|
|
while (off < len) {
|
|
ssize_t w = write(fds[1], buf + off, len - off);
|
|
if (w <= 0) break;
|
|
off += w;
|
|
}
|
|
close(fds[1]);
|
|
free(buf);
|
|
return fds[0];
|
|
}
|
|
|
|
/*
|
|
* Walk bwrap args to find where the command starts.
|
|
* Returns the index of "--" or the first non-option item,
|
|
* or n if all items are options.
|
|
*/
|
|
static int find_cmd_idx(char **args, int n)
|
|
{
|
|
for (int i = 0; i < n; i++) {
|
|
if (strcmp(args[i], "--") == 0) return i;
|
|
if (args[i][0] == '-') { i += opt_arg_count(args[i]); continue; }
|
|
return i;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Build the array of --bind/--setenv options we inject into bwrap.
|
|
* Returns the count of items placed in *out, or -1 on failure.
|
|
*/
|
|
static int build_injections(const char *appdir, const char *sharun_dir,
|
|
const char *path, char ***out)
|
|
{
|
|
struct { const char *flag, *a, *b; } entries[] = {
|
|
{ "--proc", "/proc", NULL }, /* sharun needs /proc for /proc/self/exe to resolve symlinks */
|
|
{ "--bind", appdir, appdir }, /* AppDir visible inside */
|
|
{ "--bind", "/tmp", "/tmp" }, /* must follow webkit's --tmpfs /tmp */
|
|
{ "--setenv", "SHARUN_DIR", sharun_dir },
|
|
{ "--setenv", "APPDIR", appdir },
|
|
{ "--setenv", "PATH", path },
|
|
};
|
|
int nentries = (int)(sizeof(entries) / sizeof(entries[0]));
|
|
|
|
/* Count how many slots we need (skip entries with NULL values for 'a') */
|
|
int cap = 0;
|
|
for (int i = 0; i < nentries; i++) {
|
|
if (!entries[i].a) continue;
|
|
cap += entries[i].b ? 3 : 2;
|
|
}
|
|
|
|
char **arr = calloc(cap + 1, sizeof(char *));
|
|
if (!arr) return -1;
|
|
|
|
int j = 0;
|
|
for (int i = 0; i < nentries; i++) {
|
|
if (!entries[i].a) continue;
|
|
arr[j++] = strdup(entries[i].flag);
|
|
arr[j++] = strdup(entries[i].a);
|
|
if (entries[i].b)
|
|
arr[j++] = strdup(entries[i].b);
|
|
}
|
|
arr[j] = NULL;
|
|
*out = arr;
|
|
return j;
|
|
}
|
|
|
|
/*
|
|
* Try execvp of bwrap.wrapped, falling back to the same directory
|
|
* as this wrapper (shared/bin/).
|
|
*/
|
|
static void exec_binary(char **argv)
|
|
{
|
|
execvp("bwrap.wrapped", argv);
|
|
char self[PATH_MAX];
|
|
ssize_t len = readlink("/proc/self/exe", self, sizeof(self) - 1);
|
|
if (len > 0 && (size_t)len < sizeof(self) - 14) {
|
|
self[len] = '\0';
|
|
char *slash = strrchr(self, '/');
|
|
if (slash) {
|
|
*slash = '\0';
|
|
char path[PATH_MAX + 16];
|
|
snprintf(path, sizeof(path), "%s/bwrap.wrapped", self);
|
|
execv(path, argv);
|
|
}
|
|
}
|
|
fprintf(stderr, "anylinux-bwrap-wrapper: failed to exec bwrap.wrapped: %s\n",
|
|
strerror(errno));
|
|
_exit(1);
|
|
}
|
|
|
|
/*
|
|
* If a path looks like a hardcoded system binary, check whether the AppDir
|
|
* ships it. Returns a malloc'd AppDir path, or NULL if no match found.
|
|
*/
|
|
static char *try_remap_path(const char *path, const char *appdir)
|
|
{
|
|
if (!appdir || !path || path[0] != '/') return NULL;
|
|
const char *base = strrchr(path, '/');
|
|
if (!base || !base[1]) return NULL;
|
|
base++;
|
|
|
|
char buf[PATH_MAX];
|
|
const char *dirs[] = { "bin", "lib", "libexec", NULL };
|
|
for (int i = 0; dirs[i]; i++) {
|
|
snprintf(buf, sizeof(buf), "%s/%s/%s", appdir, dirs[i], base);
|
|
if (access(buf, X_OK) == 0) return strdup(buf);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Scan exec argv for the command and replace it with an AppDir equivalent
|
|
* when one exists (e.g. /usr/bin/xdg-dbus-proxy → $APPDIR/bin/xdg-dbus-proxy).
|
|
*/
|
|
static void remap_argv_command(char **new_argv, const char *appdir)
|
|
{
|
|
if (!appdir) return;
|
|
for (int i = 1; new_argv[i]; i++) {
|
|
if (strcmp(new_argv[i], "--") == 0) {
|
|
if (new_argv[i + 1]) {
|
|
char *r = try_remap_path(new_argv[i + 1], appdir);
|
|
if (r) { free(new_argv[i + 1]); new_argv[i + 1] = r; }
|
|
}
|
|
return;
|
|
}
|
|
if (new_argv[i][0] == '-') { i += opt_arg_count(new_argv[i]); continue; }
|
|
char *r = try_remap_path(new_argv[i], appdir);
|
|
if (r) { free(new_argv[i]); new_argv[i] = r; }
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* ---- Main --------------------------------------------------------- */
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
const char *appdir = getenv("APPDIR");
|
|
const char *sharun_dir = getenv("SHARUN_DIR");
|
|
const char *path = getenv("PATH");
|
|
|
|
if (argc < 2) {
|
|
fprintf(stderr, "Usage: anylinux-bwrap-wrapper [bwrap options...]\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Build the options we will inject */
|
|
char **injections;
|
|
int inject_count = build_injections(appdir, sharun_dir, path, &injections);
|
|
if (inject_count < 0) {
|
|
fprintf(stderr, "anylinux-bwrap-wrapper: malloc failed\n");
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Check if this is a "--args N" invocation (webkit passes its
|
|
* complex option list through a pipe). If so, the options are
|
|
* in the pipe and argv just holds --args N -- <command>.
|
|
* We must rewrite the pipe content with our binds appended.
|
|
*/
|
|
int args_fd = -1, args_idx = -1;
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "--args") == 0 && i + 1 < argc) {
|
|
args_fd = atoi(argv[i + 1]);
|
|
args_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (args_fd >= 0) {
|
|
/* ---- --args N path ---- */
|
|
|
|
/* Read the NUL-separated options from the fd */
|
|
size_t content_len;
|
|
char *content = read_fd(args_fd, &content_len);
|
|
if (!content) {
|
|
fprintf(stderr, "anylinux-bwrap-wrapper: failed to read --args fd %d\n", args_fd);
|
|
return 1;
|
|
}
|
|
close(args_fd);
|
|
|
|
char **fd_args;
|
|
int n = parse_content(content, content_len, &fd_args);
|
|
free(content);
|
|
if (n < 0) {
|
|
fprintf(stderr, "anylinux-bwrap-wrapper: parse_content failed\n");
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Find where the command begins. Items before cmd_idx are
|
|
* bwrap options and go (with injections appended) to a new
|
|
* pipe. Items at/after cmd_idx (-- / command / args) go to
|
|
* the exec argv instead.
|
|
*/
|
|
int cmd_idx = find_cmd_idx(fd_args, n);
|
|
int opts_seccomp = 0;
|
|
for (int i = 0; i < cmd_idx; i++) {
|
|
if (strcmp(fd_args[i], "--seccomp") == 0) {
|
|
opts_seccomp++;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/* Build options array (original options minus --seccomp plus injections) */
|
|
int opt_n = cmd_idx - 2 * opts_seccomp;
|
|
int total_opt_n = opt_n + inject_count;
|
|
char **opt_args = calloc(total_opt_n + 1, sizeof(char *));
|
|
if (!opt_args) return 1;
|
|
int oi = 0;
|
|
for (int i = 0; i < cmd_idx; i++) {
|
|
if (strcmp(fd_args[i], "--seccomp") == 0) {
|
|
i++; /* skip fd number */
|
|
continue;
|
|
}
|
|
opt_args[oi++] = fd_args[i];
|
|
}
|
|
for (int i = 0; i < inject_count; i++)
|
|
opt_args[oi++] = injections[i];
|
|
opt_args[total_opt_n] = NULL;
|
|
|
|
int new_fd = serialize_to_pipe(opt_args, total_opt_n);
|
|
free(opt_args);
|
|
if (new_fd < 0) {
|
|
fprintf(stderr, "anylinux-bwrap-wrapper: pipe failed\n");
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Build exec argv:
|
|
* bwrap.wrapped --args NEWFD [original argv minus --args N] [fd command]
|
|
*/
|
|
int new_argc = argc + (n - cmd_idx);
|
|
char **new_argv = calloc(new_argc + 1, sizeof(char *));
|
|
if (!new_argv) return 1;
|
|
|
|
char fd_str[32];
|
|
snprintf(fd_str, sizeof(fd_str), "%d", new_fd);
|
|
|
|
new_argv[0] = "bwrap.wrapped";
|
|
int j = 1;
|
|
for (int i = 1; i < argc; i++) {
|
|
if (i == args_idx) {
|
|
new_argv[j++] = strdup("--args");
|
|
new_argv[j++] = strdup(fd_str);
|
|
i++; /* skip the old fd number */
|
|
} else {
|
|
new_argv[j++] = strdup(argv[i]);
|
|
}
|
|
}
|
|
for (int i = cmd_idx; i < n; i++)
|
|
new_argv[j++] = fd_args[i];
|
|
new_argv[j] = NULL;
|
|
|
|
remap_argv_command(new_argv, appdir);
|
|
exec_binary(new_argv);
|
|
/* not reached */
|
|
}
|
|
|
|
/* ---- Direct argv path (no --args) ---- */
|
|
|
|
/* Build argv without --seccomp N (this blocks lstat and breaks sharun) */
|
|
int st_argc = 0;
|
|
char **st_argv = calloc(argc + 1, sizeof(char *));
|
|
if (!st_argv) return 1;
|
|
st_argv[st_argc++] = argv[0];
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "--seccomp") == 0) {
|
|
i++; /* skip fd number */
|
|
continue;
|
|
}
|
|
st_argv[st_argc++] = argv[i];
|
|
}
|
|
st_argv[st_argc] = NULL;
|
|
|
|
/*
|
|
* Find where to insert our binds in the stripped argv:
|
|
* before "--" or before the first non-option argument (the command).
|
|
*/
|
|
int insert_at = 1 + find_cmd_idx(st_argv + 1, st_argc - 1);
|
|
|
|
/* Build exec argv with injections inserted at insert_at */
|
|
int new_argc = st_argc + inject_count;
|
|
char **new_argv = calloc(new_argc + 1, sizeof(char *));
|
|
if (!new_argv) return 1;
|
|
|
|
new_argv[0] = "bwrap.wrapped";
|
|
int j = 1;
|
|
for (int i = 1; i < st_argc; i++) {
|
|
if (i == insert_at) {
|
|
for (int k = 0; k < inject_count; k++)
|
|
new_argv[j++] = injections[k];
|
|
}
|
|
new_argv[j++] = strdup(st_argv[i]);
|
|
}
|
|
/* If no suitable insertion point was found, tack on at the end */
|
|
if (insert_at == st_argc) {
|
|
for (int k = 0; k < inject_count; k++)
|
|
new_argv[j++] = injections[k];
|
|
}
|
|
new_argv[j] = NULL;
|
|
|
|
free(st_argv);
|
|
|
|
remap_argv_command(new_argv, appdir);
|
|
exec_binary(new_argv);
|
|
return 1;
|
|
}
|
|
EOF
|
|
|
|
cc -Wall -Wextra -O2 -o "$SHARUN_BIN_DIR"/"${target##*/}" "$cfile"
|
|
ln "$APPDIR"/sharun "$target"
|
|
chmod +x "$target"
|
|
_echo "* added bwrap wrapper!"
|
|
}
|
|
|
|
_fix_cpython_ldconfig_mess() {
|
|
# cpython runs ldconfig -p to determine library names, this is
|
|
# super flawed because ldconfig -p is going to print host libraries
|
|
# and not our bundled libraries, it also only works in glibc systems
|
|
#
|
|
# it also hardcodes /sbin/ldconfig and resets PATH variable
|
|
# so we have to do a lot of patches here to fix this mess
|
|
#
|
|
# we will patch /sbin/ldconfig for _ldconfig to avoid conflicts, see:
|
|
# https://github.com/pkgforge-dev/ghostty-appimage/issues/122
|
|
|
|
set -- "$DST_LIB_DIR"/python*/ctypes/util.py
|
|
ldconfig=$DST_BIN_DIR/_ldconfig
|
|
if [ -x "$ldconfig" ]; then
|
|
return 0
|
|
elif [ ! -f "$1" ]; then
|
|
return 0 # exit without error if ctypes is not present
|
|
fi
|
|
pythonlib=$1
|
|
|
|
# patch ctypes lib
|
|
sed -i \
|
|
-e 's|/sbin/ldconfig|_ldconfig|g' \
|
|
-e 's|env={.*}||' \
|
|
"$pythonlib"
|
|
|
|
cat <<-'EOF' > "$ldconfig"
|
|
#!/bin/sh
|
|
|
|
# wrapper that makes ldconfig -p print our bundled libraries
|
|
export LC_ALL=C
|
|
export LANG=C
|
|
# some distros don't include /sbin in PATH
|
|
export PATH="$PATH:/usr/sbin:/sbin"
|
|
|
|
if [ -z "$APPDIR" ]; then
|
|
APPDIR=$(cd "${0%/*}"/../ && echo "$PWD")
|
|
fi
|
|
|
|
_list_libs() {
|
|
echo "69420 libs found in cache \`/etc/ld.so.cache'"
|
|
|
|
case "$(uname -m)" in
|
|
aarch64) arch=AArch64;;
|
|
*) arch=x86-64;;
|
|
esac
|
|
|
|
for f in "$APPDIR"/lib*/*.so* "$APPDIR"/lib*/*/*.so*; do
|
|
echo " ${f##*/} (libc6,$arch) => $f"
|
|
done
|
|
|
|
echo "Cache generated by: ldconfig (GNU libc) stable release version 2.42"
|
|
}
|
|
|
|
# lets try to use the real thing
|
|
case "$1" in
|
|
-p|--print-cache)
|
|
_list_libs
|
|
;;
|
|
*)
|
|
exec ldconfig "$@"
|
|
;;
|
|
esac
|
|
EOF
|
|
chmod +x "$ldconfig"
|
|
|
|
_echo "* patched cpython /sbin/ldconfig for _ldconfig wrapper"
|
|
|
|
# This python library ships a certificate with no way to override!
|
|
# https://github.com/certifi/python-certifi/issues/271
|
|
# https://github.com/certifi/python-certifi/issues/200
|
|
#
|
|
# some distros replace it with a symlink to the host certs, we have to
|
|
# make sure to ship the actual certificate since there is no override...
|
|
#
|
|
set -- "$DST_LIB_DIR"/python*/site-packages/certifi/cacert.pem
|
|
if [ -L "$1" ] && c=$(readlink -f "$1"); then
|
|
rm -f "$1"
|
|
cp "$c" "$1"
|
|
fi
|
|
|
|
# pysdl is even more broken
|
|
set -- "$DST_LIB_DIR"/python*/site-packages/sdl3/__init__.py
|
|
[ -f "$1" ] || return 0
|
|
sed -i \
|
|
-e 's|if os.path.exists(path) and SDL|if SDL|' \
|
|
-e 's|binaryMap\[module\] =.*|binaryMap[module] = ctypes.CDLL(path)|' \
|
|
"$1"
|
|
_echo "* fixed pysdl broken mess... this may not work always!"
|
|
}
|
|
|
|
_add_path_mapping_hardcoded() {
|
|
if [ -f "$PATH_MAPPING_SCRIPT" ]; then
|
|
return 0
|
|
fi
|
|
cat <<-'EOF' > "$PATH_MAPPING_SCRIPT"
|
|
#!/bin/sh
|
|
|
|
# this script makes symnlinks to hardcoded random dirs that
|
|
# were patched away by quick-sharun when hardcoded paths are
|
|
# detected or when 'PATH_MAPPING_HARDCODED' is used
|
|
|
|
_tmp_bin=""
|
|
_tmp_lib=""
|
|
_tmp_share=""
|
|
|
|
if [ -n "$_tmp_bin" ]; then
|
|
LC_ALL=C ln -sfn "$APPDIR"/bin /tmp/"$_tmp_bin" || :
|
|
fi
|
|
if [ -n "$_tmp_lib" ]; then
|
|
LC_ALL=C ln -sfn "$APPDIR"/lib /tmp/"$_tmp_lib" || :
|
|
fi
|
|
if [ -n "$_tmp_share" ]; then
|
|
LC_ALL=C ln -sfn "$APPDIR"/share /tmp/"$_tmp_share" || :
|
|
fi
|
|
EOF
|
|
_echo "* Added $PATH_MAPPING_SCRIPT"
|
|
}
|
|
|
|
_patch_away_usr_bin_dir() {
|
|
set -- "$(readlink -f "$1")"
|
|
if ! grep -Eaoq -m 1 "/usr/bin" "$1"; then
|
|
return 1
|
|
fi
|
|
|
|
sed -i -e "s|/usr/bin|/tmp/$_tmp_bin|g" "$1"
|
|
|
|
_echo "* patched away /usr/bin from $1"
|
|
_add_path_mapping_hardcoded || exit 1
|
|
|
|
sed -i -e "s|_tmp_bin=.*|_tmp_bin=$_tmp_bin|g" "$PATH_MAPPING_SCRIPT"
|
|
}
|
|
|
|
_patch_away_usr_lib_dir() {
|
|
set -- "$(readlink -f "$1")"
|
|
if ! grep -Eaoq -m 1 "/usr/lib" "$1"; then
|
|
return 1
|
|
fi
|
|
|
|
sed -i -e "s|/usr/lib|/tmp/$_tmp_lib|g" "$1"
|
|
|
|
_echo "* patched away /usr/lib from $1"
|
|
_add_path_mapping_hardcoded || exit 1
|
|
|
|
sed -i -e "s|_tmp_lib=.*|_tmp_lib=$_tmp_lib|g" "$PATH_MAPPING_SCRIPT"
|
|
}
|
|
|
|
_patch_away_usr_share_dir() {
|
|
set -- "$(readlink -f "$1")"
|
|
if ! grep -Eaoq -m 1 "/usr/share" "$1"; then
|
|
return 1
|
|
fi
|
|
|
|
sed -i -e "s|/usr/share|/tmp/$_tmp_share|g" "$1"
|
|
|
|
_echo "* patched away /usr/share from $1"
|
|
_add_path_mapping_hardcoded || exit 1
|
|
|
|
sed -i -e "s|_tmp_share=.*|_tmp_share=$_tmp_share|g" "$PATH_MAPPING_SCRIPT"
|
|
}
|
|
|
|
_check_hardcoded_lib_dirs() {
|
|
# check for hardcoded path to any other possibly bundled library dir
|
|
set -- "$DST_LIB_DIR"/*
|
|
for d do
|
|
[ -d "$d" ] || continue
|
|
d=${d##*/}
|
|
# skip directories we already handle here or in sharun
|
|
case "$d" in
|
|
alsa-lib |\
|
|
dri |\
|
|
gbm |\
|
|
gconv |\
|
|
gdk-pixbuf* |\
|
|
gio |\
|
|
gtk* |\
|
|
gstreamer* |\
|
|
gvfs |\
|
|
ImageMagick*|\
|
|
imlib2 |\
|
|
libproxy |\
|
|
locale |\
|
|
pipewire* |\
|
|
pulseaudio |\
|
|
qt* |\
|
|
spa* |\
|
|
vdpau )
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
for f in "$DST_LIB_DIR"/*.so* "$SHARUN_BIN_DIR"/*; do
|
|
if [ ! -f "$f" ]; then
|
|
continue
|
|
elif grep -aoq -m 1 "$LIB_DIR"/"$d" "$f"; then
|
|
_echo "* Detected hardcoded path to $LIB_DIR/$d in $f"
|
|
_patch_away_usr_lib_dir "$f" || :
|
|
fi
|
|
done
|
|
done
|
|
}
|
|
|
|
_check_hardcoded_data_dirs() {
|
|
# first check for hardcoded path to /usr/share/fonts and copy if so
|
|
src_fonts=/usr/share/fonts
|
|
dst_fonts="$APPDIR"/share/fonts
|
|
if grep -aoq -m 1 "$src_fonts" "$SHARUN_BIN_DIR"/*; then
|
|
if [ -d "$src_fonts" ] && [ ! -d "$dst_fonts" ]; then
|
|
mkdir -p "$dst_fonts"
|
|
for d in "$src_fonts"/*; do
|
|
if [ "${d##*/}" = "Adwaita" ]; then
|
|
continue
|
|
fi
|
|
if [ -e "$d" ]; then
|
|
cp -vr "$d" "$dst_fonts"
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
|
|
# now check if any of the bundled datadirs need to be patched
|
|
set -- "$APPDIR"/share/*
|
|
for d do
|
|
[ -d "$d" ] || continue
|
|
d=${d##*/}
|
|
# skip directories we already handle here or in sharun
|
|
case "$d" in
|
|
alsa |\
|
|
drirc.d |\
|
|
file |\
|
|
glib-* |\
|
|
glvnd |\
|
|
icons |\
|
|
libdrm |\
|
|
libthai |\
|
|
locale |\
|
|
pipewire*|\
|
|
terminfo |\
|
|
vulkan |\
|
|
X11 )
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
for f in "$DST_LIB_DIR"/*.so* "$SHARUN_BIN_DIR"/*; do
|
|
if [ ! -f "$f" ]; then
|
|
continue
|
|
elif grep -aoq -m 1 /usr/share/"$d" "$f"; then
|
|
_echo "* Detected hardcoded path to /usr/share/$d in $f"
|
|
_patch_away_usr_share_dir "$f" || :
|
|
fi
|
|
done
|
|
done
|
|
}
|
|
|
|
_sort_env_file() {
|
|
# make sure the .env has all the "unset" last, due to a bug in the dotenv
|
|
# library used by sharun all the unsets have to be declared last in the .env
|
|
# also deduplicate since the same var may be set multiple times
|
|
if [ -f "$APPDIR"/.env ]; then
|
|
sorted_env="$(LC_ALL=C awk '
|
|
{
|
|
if ($0 ~ /^unset/) {
|
|
unset_array[++u] = $0
|
|
} else {
|
|
if (!seen[$0]++) print
|
|
}
|
|
}
|
|
END {
|
|
for (i = 1; i <= u; i++) {
|
|
print unset_array[i]
|
|
}
|
|
}' "$APPDIR"/.env
|
|
)"
|
|
echo "$sorted_env" > "$APPDIR"/.env
|
|
fi
|
|
}
|
|
|
|
_strip_bins_and_libs() {
|
|
if [ "$NO_STRIP" = 1 ]; then
|
|
return 0
|
|
elif ! _is_cmd strip; then
|
|
_err_msg "Skipping strip since 'strip' is NOT installed!"
|
|
sleep 5
|
|
return 0
|
|
fi
|
|
|
|
if [ "$NO_STRIP" != 'libraries' ]; then
|
|
find "$APPDIR" -type f -name '*.so*' \
|
|
-exec strip -s -R .comment --strip-unneeded {} \; || :
|
|
_echo "* stripped libraries"
|
|
fi
|
|
|
|
if [ "$NO_STRIP" != 'binaries' ]; then
|
|
while IFS="" read -r f; do
|
|
if [ ! -x "$f" ]; then
|
|
continue
|
|
elif _is_bun_binary "$f"; then
|
|
continue # bun binaries are delicate
|
|
elif _is_pyinstaller_binary "$f"; then
|
|
continue # same story as bun binaries
|
|
fi
|
|
case "$f" in
|
|
*/python*) continue;; # python interpreter also breaks
|
|
esac
|
|
strip -s -R .comment --strip-unneeded "$f" || :
|
|
done <<-EOF
|
|
$(find "$SHARUN_BIN_DIR"/ "$APPDIR"/lib*/ -type f)
|
|
EOF
|
|
_echo "* stripped binaries"
|
|
fi
|
|
}
|
|
|
|
_add_apprun() {
|
|
# sharun needs to be the AppRun while our AppRun is named AppRun.sh, sharun will
|
|
# then execute AppRun.sh with whatever shell it can find on the system or AppDir
|
|
# this allows AppImages to work on systems without /bin/sh or /usr/bin/env
|
|
ln -f "$APPDIR"/sharun "$APPDIR"/AppRun
|
|
|
|
f=$APPDIR/AppRun.sh
|
|
if [ -f "$f" ]; then
|
|
return 0
|
|
fi
|
|
_echo "Adding '$f'..."
|
|
cat <<-'EOF' > "$f"
|
|
#!/bin/sh
|
|
|
|
# Example AppRun for using the hooks of this repository.
|
|
# NOTE: It is meant to be used with sharun which uses a top level bin dir
|
|
|
|
if [ "$APPRUN_DEBUG" = 1 ]; then
|
|
set -x
|
|
fi
|
|
|
|
set -e
|
|
|
|
APPDIR=$(cd "${0%/*}" && echo "$PWD")
|
|
MAIN_BIN=@MAIN_BIN@
|
|
ARG0="${ARGV0:-$0}"
|
|
|
|
unset ARGV0
|
|
|
|
export APPIMAGE_ARCH=@APPIMAGE_ARCH@
|
|
export HOSTPATH=$PATH
|
|
export PATH=$APPDIR/bin:$PATH
|
|
export ARG0 APPDIR PATH
|
|
|
|
# Allow users to set env variables for specific AppImage
|
|
# This feature only works with the uruntime
|
|
if [ "$1" = '--appimage-add-env' ]; then
|
|
shift
|
|
for v do
|
|
echo "$v" >> "$APPIMAGE".env
|
|
>&2 echo "Added '$v' to $APPIMAGE.env"
|
|
done
|
|
exit 0
|
|
fi
|
|
|
|
if [ -f "$APPDIR"/AppRun.lib ]; then
|
|
. "$APPDIR"/AppRun.lib
|
|
for hook in $APPDIR/bin/*.hook; do
|
|
[ -e "$hook" ] || continue
|
|
. "$hook"
|
|
done
|
|
fi
|
|
|
|
# Check if ARG0 matches a binary, fallback to $1, then binary in .desktop
|
|
if [ -f "$APPDIR"/bin/"${ARG0##*/}" ]; then
|
|
TO_LAUNCH=$APPDIR/bin/${ARG0##*/}
|
|
elif [ -f "$APPDIR"/bin/"$1" ]; then
|
|
TO_LAUNCH=$APPDIR/bin/$1
|
|
shift
|
|
else
|
|
TO_LAUNCH=$APPDIR/bin/$MAIN_BIN
|
|
fi
|
|
|
|
set -- "$TO_LAUNCH" "$@"
|
|
|
|
# If LD_DEBUG=libs is set outside the AppImage the output is not helpful
|
|
# because it will include the libs of sh, grep, cat, etc from the hooks
|
|
# with this var we can set LD_DEBUG=libs for the bundled application only
|
|
if [ "$APPIMAGE_DEBUG" = 1 ]; then
|
|
cat /etc/os-release >"$PWD"/"${APPIMAGE##*/}"-debug.log || :
|
|
export LD_DEBUG=libs
|
|
export VK_LOADER_DEBUG=all
|
|
export LIBGL_DEBUG=verbose
|
|
export EGL_LOG_LEVEL=debug
|
|
export LC_ALL=C
|
|
export SHARUN_PRINTENV=1
|
|
"$@" 2>>"$PWD"/"${APPIMAGE##*/}"-debug.log || :
|
|
>&2 echo "Debug log at: '$PWD/${APPIMAGE##*/}-debug.log'"
|
|
else
|
|
exec "$@"
|
|
fi
|
|
EOF
|
|
|
|
chmod +x "$f"
|
|
|
|
sed -i \
|
|
-e "s|@MAIN_BIN@|$MAIN_BIN|" \
|
|
-e "s|@APPIMAGE_ARCH@|$APPIMAGE_ARCH|" \
|
|
"$f"
|
|
|
|
_echo "* Added $f"
|
|
}
|
|
|
|
_add_hooks_library() {
|
|
f=$APPDIR/AppRun.lib
|
|
if [ -f "$f" ]; then
|
|
return 0
|
|
fi
|
|
_echo "Adding '$f'..."
|
|
cat <<-'EOF' > "$f"
|
|
#!/bin/sh
|
|
|
|
HOST_HOME=${REAL_HOME:-$HOME}
|
|
HOST_XDG_CONFIG_HOME=${REAL_XDG_CONFIG_HOME:-${XDG_CONFIG_HOME:-$HOST_HOME/.config}}
|
|
HOST_XDG_DATA_HOME=${REAL_XDG_DATA_HOME:-${XDG_DATA_HOME:-$HOST_HOME/.local/share}}
|
|
HOST_XDG_CACHE_HOME=${REAL_XDG_CACHE_HOME:-${XDG_CACHE_HOME:-$HOST_HOME/.cache}}
|
|
HOST_XDG_STATE_HOME=${REAL_XDG_STATE_HOME:-${XDG_STATE_HOME:-$HOST_HOME/.local/state}}
|
|
|
|
export HOST_HOME HOST_XDG_CONFIG_HOME HOST_XDG_DATA_HOME HOST_XDG_CACHE_HOME HOST_XDG_STATE_HOME
|
|
|
|
BINDIR=${XDG_BIN_HOME:-~/.local/bin}
|
|
DATADIR=${XDG_DATA_HOME:-~/.local/share}
|
|
CONFIGDIR=${XDG_CONFIG_HOME:-~/.config}
|
|
CACHEDIR=${XDG_CACHE_HOME:-~/.cache}
|
|
STATEDIR=${XDG_STATE_HOME:-~/.local/state}
|
|
|
|
# always change XDG_CACHE_HOME to our own dedicated location
|
|
# using the host XDG_CACHE_HOME has been a source of issues
|
|
# See: https://github.com/pkgforge-dev/Anylinux-AppImages/issues/657
|
|
if [ "$USE_HOST_XDG_CACHE_HOME" != 1 ] && [ -n "$APPIMAGE" ]; then
|
|
case "$XDG_CACHE_HOME" in
|
|
*"$APPIMAGE"*) # make sure we are not using the portable cache first
|
|
:
|
|
;;
|
|
*)
|
|
_cache_dir=$CACHEDIR/AppImage-Cache
|
|
if [ -d "$_cache_dir" ] || mkdir -p "$_cache_dir" 2>/dev/null; then
|
|
export XDG_CACHE_HOME="$_cache_dir"
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
err_msg(){
|
|
>&2 printf '\033[1;31m%s\033[0m\n' " $*"
|
|
}
|
|
|
|
is_cmd() {
|
|
if [ "$1" = '--any' ]; then
|
|
shift
|
|
for cmd do
|
|
if command -v "$cmd" 1>/dev/null; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
else
|
|
for cmd do
|
|
command -v "$cmd" 1>/dev/null || return 1
|
|
done
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
run_gui_sudo() {
|
|
if [ "$(id -u)" = 0 ]; then _sudocmd=""
|
|
elif _sudocmd=$(command -v pkexec); then :
|
|
elif _sudocmd=$(command -v lxqt-sudo); then :
|
|
elif _sudocmd=$(command -v run0); then set -- --via-shell "$@"
|
|
fi
|
|
if [ "$1" = --check ]; then
|
|
[ -n "$_sudocmd" ] || [ "$(id -u)" = 0 ] || return 1
|
|
return 0
|
|
else
|
|
if [ -z "$_sudocmd" ] && [ "$(id -u)" != 0 ]; then
|
|
err_msg "We need 'pkexec' or 'lxqt-sudo' or 'run0' to perform this operation"
|
|
return 1
|
|
fi
|
|
fi
|
|
$_sudocmd "$@"
|
|
}
|
|
|
|
download() {
|
|
if _download_cmd=$(command -v wget); then set -- -O "$@"
|
|
elif _download_cmd=$(command -v curl); then set -- -Lo "$@"
|
|
else
|
|
err_msg "We need 'wget' or 'curl' to download $1"
|
|
return 1
|
|
fi
|
|
log=${TMPDIR:-/tmp}/._download.log
|
|
if ! "$_download_cmd" "$@" 2>"$log"; then
|
|
cat "$log"
|
|
err_msg "Download failed!"
|
|
return 1
|
|
fi
|
|
rm -f "$log"
|
|
}
|
|
|
|
|
|
# the following function are used by notify
|
|
|
|
# display functions, these might return non 0 depending on user input
|
|
_display_info() {
|
|
set -- "INFO: $*"
|
|
if is_cmd kdialog; then kdialog --msgbox "$*"
|
|
elif is_cmd qarma; then qarma --info --text "$*"
|
|
elif is_cmd yad; then yad --info --text "$*"
|
|
elif is_cmd zenity; then zenity --info --text "$*"
|
|
elif is_cmd gxmessage; then gxmessage -center "$*"
|
|
elif is_cmd xmessage; then xmessage -center "$*"
|
|
else _notification=0 _display_with_host_term "$*"
|
|
fi
|
|
}
|
|
|
|
_display_error() {
|
|
set -- "ERROR: $*"
|
|
if is_cmd kdialog; then kdialog --error "$*"
|
|
elif is_cmd qarma; then qarma --error --text "$*"
|
|
elif is_cmd yad; then yad --error --text "$*"
|
|
elif is_cmd zenity; then zenity --error --text "$*"
|
|
elif is_cmd gxmessage; then gxmessage -center "$*"
|
|
elif is_cmd xmessage; then xmessage -center "$*"
|
|
else _notification=0 _display_with_host_term "$*"
|
|
fi
|
|
}
|
|
|
|
_display_warning() {
|
|
set -- "WARNING: $*"
|
|
if is_cmd kdialog; then kdialog --sorry "$*"
|
|
elif is_cmd qarma; then qarma --warning --text "$*"
|
|
elif is_cmd yad; then yad --warning --text "$*"
|
|
elif is_cmd zenity; then zenity --warning --text "$*"
|
|
elif is_cmd gxmessage; then gxmessage -center "$*"
|
|
elif is_cmd xmessage; then xmessage -center "$*"
|
|
else _notification=0 _display_with_host_term "$*"
|
|
fi
|
|
}
|
|
|
|
_display_question() {
|
|
set -- "QUESTION: $*"
|
|
if is_cmd kdialog; then kdialog --yesno "$*"
|
|
elif is_cmd qarma; then qarma --question --text "$*"
|
|
elif is_cmd yad; then yad --question --text "$*"
|
|
elif is_cmd zenity; then zenity --question --text "$*"
|
|
elif is_cmd gxmessage; then gxmessage -center -buttons "Yes:0,No:1" "$*"
|
|
elif is_cmd xmessage; then xmessage -center -buttons "Yes:0,No:1" "$*"
|
|
else _notification=0 _display_with_host_term "$*"
|
|
fi
|
|
}
|
|
|
|
# notify functions, these will always return 0 unless there are no deps
|
|
_notify_info() {
|
|
set -- "INFO: $*"
|
|
if is_cmd notify-send; then notify-send "$*" || :
|
|
elif is_cmd qarma; then qarma --info --text "$*" || :
|
|
elif is_cmd kdialog; then kdialog --passivepopup "$*" || :
|
|
elif is_cmd yad; then yad --window-type=notification --text "$*" || :
|
|
elif is_cmd zenity; then zenity --info --text "$*" || :
|
|
elif is_cmd xmessage; then xmessage -center "$*" || :
|
|
elif is_cmd gxmessage; then gxmessage -center "$*" || :
|
|
else _notification=1 _display_with_host_term "$*"
|
|
fi
|
|
}
|
|
|
|
_notify_error() {
|
|
set -- "ERROR: $*"
|
|
if is_cmd notify-send; then notify-send -u critical "$*" || :
|
|
elif is_cmd kdialog; then kdialog --error "$*" || :
|
|
elif is_cmd qarma; then qarma --error --text "$*" || :
|
|
elif is_cmd yad; then yad --window-type=notification --text "$*" || :
|
|
elif is_cmd zenity; then zenity --error --text "$*" || :
|
|
elif is_cmd xmessage; then xmessage -center "$*" || :
|
|
elif is_cmd gxmessage; then gxmessage -center "$*" || :
|
|
else _notification=1 _display_with_host_term "$*"
|
|
fi
|
|
}
|
|
|
|
_notify_warning() {
|
|
set -- "WARNING: $*"
|
|
if is_cmd notify-send; then notify-send -u critical "$*" || :
|
|
elif is_cmd kdialog; then kdialog --sorry "$*" || :
|
|
elif is_cmd qarma; then qarma --warning --text "$*" || :
|
|
elif is_cmd yad; then yad --window-type=notification --text "$*" || :
|
|
elif is_cmd zenity; then zenity --warning --text "$*" || :
|
|
elif is_cmd gxmessage; then gxmessage -center "$*" || :
|
|
elif is_cmd xmessage; then xmessage -center "$*" || :
|
|
else _notification=1 _display_with_host_term "$*"
|
|
fi
|
|
}
|
|
|
|
# extreme measure
|
|
_display_with_host_term() {
|
|
_message=$*
|
|
_tmpfile=${TMPDIR:-/tmp}/.${0##*/}-no-gui-fallback
|
|
|
|
cmd_notification="echo '$_message'; read yn"
|
|
cmd_display="
|
|
trap 'echo 0 > \"$_tmpfile\"; exit' HUP TERM
|
|
echo '$_message'
|
|
printf '\n%s'' (Yes/No)?: ';
|
|
while :; do
|
|
read yn
|
|
case \$yn in
|
|
Y*|y*) echo 1 > '$_tmpfile'; break;;
|
|
N*|n*) echo 0 > '$_tmpfile'; break;;
|
|
*) echo 'Please type Yes or No' ;;
|
|
esac
|
|
done
|
|
"
|
|
|
|
if [ "$_notification" = 1 ]; then
|
|
tcmd="$cmd_notification"
|
|
else
|
|
tcmd="$cmd_display"
|
|
fi
|
|
|
|
# normal terminals
|
|
if is_cmd alacritty; then alacritty -e sh -c "$tcmd" &
|
|
elif is_cmd wezterm; then wezterm -e sh -c "$tcmd" &
|
|
elif is_cmd konsole; then konsole -e sh -c "$tcmd" &
|
|
elif is_cmd lxterminal; then lxterminal -e sh -c "$tcmd" &
|
|
elif is_cmd kitty; then kitty -e sh -c "$tcmd" &
|
|
elif is_cmd urxvt; then urxvt -e sh -c "$tcmd" &
|
|
elif is_cmd xterm; then xterm -e sh -c "$tcmd" &
|
|
# mmmm
|
|
elif is_cmd gnome-terminal; then gnome-terminal -- sh -c "$tcmd" &
|
|
# these need extra quotes for some reason
|
|
elif is_cmd ptyxis; then ptyxis -x "sh -c \"$tcmd\"" &
|
|
elif is_cmd qterminal; then qterminal -e "sh -c \"$tcmd\"" &
|
|
elif is_cmd mate-terminal; then mate-terminal -e "sh -c \"$tcmd\"" &
|
|
elif is_cmd xfce4-terminal; then xfce4-terminal -e "sh -c \"$tcmd\"" &
|
|
else
|
|
err_msg "Cannot find suitable binary to perform operation!"
|
|
return 127
|
|
fi
|
|
|
|
if [ "$_notification" = 1 ]; then
|
|
return 0
|
|
fi
|
|
|
|
_elapsed=0
|
|
_timeout=150 # 15 seconds
|
|
while :; do
|
|
if [ -f "$_tmpfile" ] || [ "$_elapsed" -ge "$_timeout" ]; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
_elapsed=$(( _elapsed + 1 ))
|
|
done
|
|
|
|
read -r _reply < "$_tmpfile"
|
|
rm -f "$_tmpfile"
|
|
|
|
if [ "$_reply" = "1" ]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
notify() {
|
|
case "$1" in
|
|
--display-info|-di) shift; _display_info "$@";;
|
|
--display-error|-de) shift; _display_error "$@";;
|
|
--display-warning|-dw) shift; _display_warning "$@";;
|
|
--display-question|-dq) shift; _display_question "$@";;
|
|
--notify-info|-ni) shift; _notify_info "$@";;
|
|
--notify-error|-ne) shift; _notify_error "$@";;
|
|
--notify-warning|-nw) shift; _notify_warning "$@";;
|
|
# act as notify-send ARG wrapper when no flag is given
|
|
*) _notify_info "$@";;
|
|
esac
|
|
}
|
|
EOF
|
|
chmod +x "$f"
|
|
_echo "* Added $f"
|
|
}
|
|
|
|
_handle_nested_bins() {
|
|
# wrap any executable in lib with sharun
|
|
for b in $(find "$DST_LIB_DIR"/ -type f ! -name '*.so*'); do
|
|
if [ -x "$b" ] && [ -x "$SHARUN_BIN_DIR"/"${b##*/}" ]; then
|
|
rm -f "$b"
|
|
ln "$APPDIR"/sharun "$b"
|
|
_echo "* Wrapped lib executable '$b' with sharun"
|
|
fi
|
|
done
|
|
|
|
# do the same for possible nested binaries in bin
|
|
for b in $(find "$DST_BIN_DIR"/*/ -type f ! -name '*.so*' 2>/dev/null); do
|
|
if [ -x "$b" ] && [ -x "$SHARUN_BIN_DIR"/"${b##*/}" ]; then
|
|
rm -f "$b"
|
|
ln "$APPDIR"/sharun "$b"
|
|
_echo "* Wrapped nested bin executable '$b' with sharun"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# sometimes developers add stuff like /bin/sh or env as the Exec= key of the
|
|
# desktop entry, 99.99% of the time this is not wanted, so we have to error that
|
|
_check_main_bin() {
|
|
if [ -z "$MAIN_BIN" ]; then
|
|
MAIN_BIN=$(awk -F'=| ' '/^Exec=/{print $2; exit}' "$DESKTOP_ENTRY" | tr -d "\"'")
|
|
MAIN_BIN=${MAIN_BIN##*/}
|
|
case "$MAIN_BIN" in
|
|
env|sh|bash)
|
|
_err_msg "Main binary is '$MAIN_BIN', it is unlikely you"
|
|
_err_msg "are actually going to package '$MAIN_BIN'"
|
|
_err_msg "as an appimage, bailing out..."
|
|
_err_msg "set MAIN_BIN=$MAIN_BIN if you want to do this."
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
if [ -f "$DST_BIN_DIR"/"$MAIN_BIN" ]; then
|
|
return 0
|
|
fi
|
|
|
|
_err_msg "Main binary is set to '$MAIN_BIN', but this file is NOT present"
|
|
_err_msg "This is the default binary to be launched in this application"
|
|
_err_msg "Please make sure to bundle $MAIN_BIN"
|
|
_err_msg "By default the main binary is taken from the top level desktop"
|
|
_err_msg "entry in '$APPDIR', make sure to add the correct desktop entry"
|
|
exit 1
|
|
}
|
|
|
|
_make_static_bin() (
|
|
ONELF=$TMPDIR/onelf
|
|
if [ ! -x "$ONELF" ]; then
|
|
_echo "Downloading onelf..."
|
|
_download "$ONELF" "$ONELF_LINK"
|
|
chmod +x "$ONELF"
|
|
fi
|
|
|
|
mkdir -p "$DST_BIN_DIR"
|
|
_echo "------------------------------------------------------------"
|
|
for bin do
|
|
b=${bin##*/}
|
|
_echo "Packing $bin as a static binary with onelf..."
|
|
|
|
_tmpdir=$TMPDIR/.onelf_build_$$_$b
|
|
rm -rf "$_tmpdir"
|
|
mkdir -p "$_tmpdir"
|
|
|
|
"$ONELF" bundle-libs "$_tmpdir" --from-binary "$bin" --strip --scan-dlopen
|
|
"$ONELF" pack "$_tmpdir" -o "$DST_BIN_DIR"/"$b" --command bin/"$b"
|
|
rm -rf "$_tmpdir"
|
|
done
|
|
_echo "------------------------------------------------------------"
|
|
)
|
|
|
|
_make_appimage() {
|
|
_echo "------------------------------------------------------------"
|
|
_echo "Making AppImage..."
|
|
_echo "------------------------------------------------------------"
|
|
|
|
if [ ! -d "$APPDIR" ]; then
|
|
_err_msg "ERROR: No $APPDIR directory found"
|
|
_err_msg "Set APPDIR if you have it at another location"
|
|
exit 1
|
|
elif [ ! -f "$APPDIR"/AppRun ]; then
|
|
_err_msg "ERROR: No $APPDIR/AppRun file found!"
|
|
exit 1
|
|
fi
|
|
chmod +x "$APPDIR"/AppRun
|
|
_get_desktop
|
|
_get_icon
|
|
_sort_env_file
|
|
|
|
_echo "------------------------------------------------------------"
|
|
if [ -z "$UPINFO" ]; then
|
|
echo "No update information given, trying to guess it..."
|
|
if [ -n "$GITHUB_REPOSITORY" ]; then
|
|
UPINFO="gh-releases-zsync|${GITHUB_REPOSITORY%/*}|${GITHUB_REPOSITORY#*/}|latest|*$ARCH.AppImage.zsync"
|
|
_echo "Guessed $UPINFO as the update information"
|
|
_echo "It may be wrong so please set the UPINFO instead"
|
|
else
|
|
_err_msg "We were not able to guess the update information"
|
|
_err_msg "Please add it if you will distribute the AppImage"
|
|
fi
|
|
fi
|
|
_echo "------------------------------------------------------------"
|
|
|
|
if [ "$DEVEL_RELEASE" = 1 ]; then
|
|
if ! grep -q '^Name=.*Nightly' "$DESKTOP_ENTRY"; then
|
|
>&2 echo "Adding Nightly to desktop entry name"
|
|
sed -i -e 's/^\(Name=.*\)$/\1 Nightly/' "$DESKTOP_ENTRY"
|
|
fi
|
|
# also change UPINFO to use nightly tag
|
|
if [ -n "$UPINFO" ]; then
|
|
UPINFO=$(echo "$UPINFO" | sed 's/|latest|/|nightly|/')
|
|
fi
|
|
fi
|
|
|
|
if ! mkdir -p "$OUTPATH"; then
|
|
_err_msg "ERROR: Cannot create output directory: '$OUTPATH'"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -x "$APPIMAGETOOL" ]; then
|
|
_echo "Downloading appimagetool from $APPIMAGETOOL_LINK"
|
|
_download "$APPIMAGETOOL" "$APPIMAGETOOL_LINK"
|
|
chmod +x "$APPIMAGETOOL"
|
|
fi
|
|
|
|
_echo "------------------------------------------------------------"
|
|
_echo "Making AppImage..."
|
|
_echo "------------------------------------------------------------"
|
|
|
|
if ! "$APPIMAGETOOL"; then
|
|
_err_msg "ERROR: Something went wrong making the AppImage!"
|
|
exit 1
|
|
fi
|
|
|
|
set -- "$OUTPATH"/*.AppImage
|
|
if [ ! -f "$1" ]; then
|
|
_err_msg "ERROR: No AppImage was produced??"
|
|
exit 1
|
|
else
|
|
chmod +x "$1"
|
|
fi
|
|
|
|
_echo "------------------------------------------------------------"
|
|
_echo "All done! AppImage at: $1"
|
|
_echo "------------------------------------------------------------"
|
|
exit 0
|
|
}
|
|
|
|
case "$1" in
|
|
--help)
|
|
_help_msg
|
|
;;
|
|
--make-appimage)
|
|
_make_appimage
|
|
;;
|
|
--test)
|
|
shift
|
|
_test_appimage "$@"
|
|
;;
|
|
--simple-test)
|
|
shift
|
|
_simple_test_appimage "$@"
|
|
;;
|
|
--make-static-bin)
|
|
shift
|
|
_make_static_bin "$@"
|
|
exit 0
|
|
;;
|
|
'')
|
|
_help_msg
|
|
;;
|
|
esac
|
|
|
|
_sanity_check
|
|
_get_desktop
|
|
_get_icon
|
|
|
|
_echo "------------------------------------------------------------"
|
|
_echo "Starting deployment, checking if extra libraries need to be added..."
|
|
echo ""
|
|
|
|
_determine_what_to_deploy "$@"
|
|
_make_deployment_array
|
|
|
|
echo ""
|
|
_echo "Now jumping to deploying..."
|
|
_echo "------------------------------------------------------------"
|
|
|
|
_get_sharun
|
|
_deploy_libs "$@"
|
|
_check_always_software
|
|
_handle_bins_scripts
|
|
|
|
if [ "$DEPLOY_FLUTTER" = 1 ]; then
|
|
if [ -z "$FLUTTER_LIB" ]; then
|
|
_err_msg "Flutter deployment was forced but looks like the"
|
|
_err_msg "the application does not link to libflutter at all"
|
|
_err_msg "If you see this message please open a bug report!"
|
|
exit 1
|
|
fi
|
|
|
|
# flutter apps need to have a relative lib and data directory
|
|
# we need to find the directory that contains libapp.so
|
|
if libapp=$(cd "$DST_BIN_DIR" \
|
|
&& find ../lib/ -type f -name 'libapp.so' -print -quit); then
|
|
d=${libapp%/*}
|
|
if [ ! -d "$DST_BIN_DIR"/"${d##*/}" ]; then
|
|
ln -s "$d" "$DST_BIN_DIR"/"${d##*/}"
|
|
fi
|
|
else
|
|
_err_msg "Cannot find libapp.so in $APPDIR"
|
|
_err_msg "include it for flutter deployment to work"
|
|
fi
|
|
|
|
dst_flutter_dir=$DST_BIN_DIR/data
|
|
if [ ! -d "$dst_flutter_dir" ]; then
|
|
if [ -z "$FLUTTER_DATA_DIR" ]; then
|
|
d=${FLUTTER_LIB%/*.so*}
|
|
# find data dir, we assume it is relative to
|
|
# where libflutter*.so came from
|
|
if [ -d "$d"/../data ]; then
|
|
FLUTTER_DATA_DIR="$d"/../data
|
|
elif [ -d "$d"/../../data ]; then
|
|
FLUTTER_DATA_DIR="$d"/../../data
|
|
else
|
|
_err_msg "Cannot find data directory of $FLUTTER_LIB"
|
|
_err_msg "Please set FLUTTER_DATA_DIR to its location"
|
|
exit 1
|
|
fi
|
|
fi
|
|
cp -rv "$FLUTTER_DATA_DIR" "$dst_flutter_dir"
|
|
_echo "* Copied flutter data directory"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
_echo "------------------------------------------------------------"
|
|
echo ""
|
|
|
|
_check_main_bin
|
|
_map_paths_ld_preload_open
|
|
_map_paths_binary_patch
|
|
_add_anylinux_lib
|
|
_check_window_class
|
|
_add_gtk_class_fix
|
|
|
|
echo ""
|
|
_echo "------------------------------------------------------------"
|
|
_echo "Finished deployment! Starting post deployment hooks..."
|
|
_echo "------------------------------------------------------------"
|
|
echo ""
|
|
|
|
# It is common for libraries to have optional dependencies to pipewire they will
|
|
# try to dlopen pipewire and if isn't available they fallback to pulseaudio or alsa
|
|
# given that it is possible to deploy an application without pipewire we do not
|
|
# want that application then dlopen the pipewire of the host and crash
|
|
set -- "$DST_LIB_DIR"/libpipewire-0.3.so*
|
|
[ -f "$1" ] || no_pipewire=1
|
|
|
|
set -- \
|
|
"$DST_LIB_DIR"/*.so* \
|
|
"$DST_LIB_DIR"/*/*.so* \
|
|
"$DST_LIB_DIR"/*/*/*.so* \
|
|
"$DST_LIB_DIR"/*/*/*/*.so* \
|
|
"$DST_LIB_DIR"/*/*/*/*/*.so* \
|
|
"$DST_LIB_DIR"/*/*/*/*/*/*.so*
|
|
|
|
for lib do
|
|
[ -f "$lib" ] || continue
|
|
# make sure to remove any full rpath from the libs
|
|
if patchelf --print-rpath "$lib" | grep -q '^/'; then
|
|
patchelf --remove-rpath "$lib"
|
|
_echo "* removed full rpath from $lib"
|
|
fi
|
|
|
|
# also remove full paths from needed libs, for example
|
|
# a library may depend on /usr/lib/libkek.so instead of libkek.so
|
|
patchelf --print-needed "$lib" | while IFS="" read -r l; do
|
|
case "$l" in
|
|
/*)
|
|
patchelf --replace-needed "$l" "${l##*/}" "$lib"
|
|
_echo "* removed full needed lib path $l from $lib"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ "$no_pipewire" = 1 ] && grep -aq -m 1 'libpipewire-0.3.so' "$lib"; then
|
|
sed -i -e 's|libpipewire-0.3.so|no-pipewire-kek.so|g' "$lib"
|
|
fi
|
|
done
|
|
|
|
# now start the post deployment hooks
|
|
for lib do case "$lib" in
|
|
*/gio/modules/*.so*)
|
|
src_gio_cache=$LIB_DIR/gio/modules/giomodule.cache
|
|
dst_gio_cache=$DST_LIB_DIR/gio/modules/giomodule.cache
|
|
if [ -f "$src_gio_cache" ] && [ ! -f "$dst_gio_cache" ]; then
|
|
cp -v "$src_gio_cache" "$dst_gio_cache"
|
|
_echo "* added $src_gio_cache"
|
|
fi
|
|
;;
|
|
*/libgio-*.so*)
|
|
f=$DST_BIN_DIR/gio-launch-desktop
|
|
if [ ! -x "$f" ]; then
|
|
cat <<-'EOF' > "$f"
|
|
#!/bin/sh
|
|
export GIO_LAUNCHED_DESKTOP_FILE_PID=$$
|
|
exec "$@"
|
|
EOF
|
|
chmod +x "$f"
|
|
_echo "* added $f wrapper"
|
|
fi
|
|
;;
|
|
*/libglib-*.so*)
|
|
_glibver=$(echo "$lib" | awk -F'-' '{print $NF}' | sed "s|\.so.*||")
|
|
src_glib_schema_dir=/usr/share/glib-$_glibver/schemas
|
|
dst_glib_schema_dir=$APPDIR/share/glib-$_glibver/schemas
|
|
if [ -d "$src_glib_schema_dir" ] && [ ! -d "$dst_glib_schema_dir" ]; then
|
|
mkdir -p "$dst_glib_schema_dir"
|
|
cp -r "$src_glib_schema_dir"/* "$dst_glib_schema_dir"
|
|
_echo "* added $src_glib_schema_dir"
|
|
fi
|
|
|
|
# apps may crash when the host has no mime database
|
|
src_mime_dir=/usr/share/mime
|
|
dst_mime_dir=$APPDIR/share/mime
|
|
if [ -d "$src_mime_dir" ] && [ ! -d "$dst_mime_dir" ]; then
|
|
cp -r "$src_mime_dir" "$dst_mime_dir"
|
|
rm -rf "$dst_mime_dir"/packages # bloat
|
|
_echo "* added $src_mime_dir"
|
|
fi
|
|
;;
|
|
*/gdk-pixbuf-*/*/loaders/*.so*)
|
|
src_gdkpixbuf_cache=$(echo "$LIB_DIR"/gdk-pixbuf-*/*/loaders.cache)
|
|
dst_gdkpixbuf_cache=${lib%/*}.cache
|
|
if [ -f "$src_gdkpixbuf_cache" ] && [ ! -f "$dst_gdkpixbuf_cache" ]; then
|
|
cp -v "$src_gdkpixbuf_cache" "$dst_gdkpixbuf_cache"
|
|
sed -i -e 's|/usr/lib/.*/loaders/||g' "$dst_gdkpixbuf_cache"
|
|
_echo "* added $src_gdkpixbuf_cache"
|
|
fi
|
|
;;
|
|
*/gtk-*/*/immodules/*.so)
|
|
_gtkver=$(echo "$lib" | tr '/' '\n' | grep '^gtk-')
|
|
src_gtk_immodule_cache=$(echo "$LIB_DIR"/"$_gtkver"/*/immodules.cache)
|
|
dst_gtk_immodule_cache=${lib%/*}.cache
|
|
if [ -f "$src_gtk_immodule_cache" ] && [ ! -f "$dst_gtk_immodule_cache" ]; then
|
|
cp -v "$src_gtk_immodule_cache" "$dst_gtk_immodule_cache"
|
|
sed -i -e 's|/usr/lib/.*/immodules/||g' "$dst_gtk_immodule_cache"
|
|
_echo "* added $src_gtk_immodule_cache"
|
|
fi
|
|
;;
|
|
*/libglycin*.so*)
|
|
if [ "$GNOME_GLYCIN" != 1 ]; then
|
|
continue # only GNOME glycin needs handling
|
|
fi
|
|
_add_bwrap_wrapper
|
|
src_glycin_conf_dir=/usr/share/glycin-loaders
|
|
dst_glycin_conf_dir=$APPDIR/share/glycin-loaders
|
|
if [ -d "$src_glycin_conf_dir" ] && [ ! -d "$dst_glycin_conf_dir" ]; then
|
|
cp -r "$src_glycin_conf_dir" "$dst_glycin_conf_dir"
|
|
sed -i -e 's|/usr/lib.*/||g' "$dst_glycin_conf_dir"/*/*/*.conf
|
|
_echo "* added $src_glycin_conf_dir"
|
|
fi
|
|
;;
|
|
*/libgtksourceview-*.so*)
|
|
_gtk_srcview_ver=$(echo "$lib" | awk -F'-' '{print $NF}' | sed "s|\.so.*||")
|
|
src_gtk_srcview_dir=/usr/share/gtksourceview-$_gtk_srcview_ver
|
|
dst_gtk_srcview_dir=$APPDIR/share/gtksourceview-$_gtk_srcview_ver
|
|
if [ -d "$src_gtk_srcview_dir" ] && [ ! -d "$dst_gtk_srcview_dir" ]; then
|
|
cp -r "$src_gtk_srcview_dir" "$dst_gtk_srcview_dir"
|
|
_echo "* added $src_gtk_srcview_dir"
|
|
fi
|
|
;;
|
|
*/libfontconfig.so*)
|
|
src_fontconfig_config=/etc/fonts/fonts.conf
|
|
dst_fontconfig_config=$APPDIR/etc/fonts/fonts.conf
|
|
if [ -f "$src_fontconfig_config" ] && [ ! -f "$dst_fontconfig_config" ]; then
|
|
mkdir -p "${dst_fontconfig_config%/*}"
|
|
cp -v "$src_fontconfig_config" "$dst_fontconfig_config"
|
|
_echo "* added $src_fontconfig_config"
|
|
fi
|
|
;;
|
|
*/libfolks*.so*)
|
|
src_folks_dir=$LIB_DIR/folks
|
|
dst_folks_dir=$DST_LIB_DIR/folks
|
|
if [ -d "$src_folks_dir" ] && [ ! -d "$dst_folks_dir" ]; then
|
|
cp -r "$src_folks_dir" "$dst_folks_dir"
|
|
_echo "* added $src_folks_dir"
|
|
fi
|
|
;;
|
|
*/libthai*.so*)
|
|
src_libthai_dir=/usr/share/libthai
|
|
dst_libthai_dir=$APPDIR/share/libthai
|
|
if [ -d "$src_libthai_dir" ] && [ ! -d "$dst_libthai_dir" ]; then
|
|
cp -r "$src_libthai_dir" "$dst_libthai_dir"
|
|
_echo "* added $src_libthai_dir"
|
|
fi
|
|
;;
|
|
*/libasound*.so*)
|
|
src_alsaconf_dir=/usr/share/alsa
|
|
dst_alsaconf_dir=$APPDIR/share/alsa
|
|
if [ -d "$src_alsaconf_dir" ] && [ ! -d "$dst_alsaconf_dir" ]; then
|
|
cp -r "$src_alsaconf_dir" "$dst_alsaconf_dir"
|
|
_echo "* added $src_alsaconf_dir"
|
|
fi
|
|
# Adding alsa config dir is not enough, the file is harcoded
|
|
# to load additional files on the host
|
|
f=$APPDIR/share/alsa/alsa.conf
|
|
if [ -f "$f" ] && ! grep -q 'SHARUN_DIR' "$f"; then
|
|
sed -i -e \
|
|
's|"/etc/alsa/conf.d"|"/etc/alsa/conf.d"\n\t\t\t{ @func concat strings [ { @func getenv vars [ SHARUN_DIR ] default "" } "/share/alsa/alsa.conf.d" ] }|' \
|
|
"$f"
|
|
fi
|
|
;;
|
|
*/libxkbcommon*.so*)
|
|
src_xkb_dir=/usr/share/X11/xkb
|
|
dst_xkb_dir=$APPDIR/share/X11/xkb
|
|
if [ -d "$src_xkb_dir" ] && [ ! -d "$dst_xkb_dir" ]; then
|
|
mkdir -p "$dst_xkb_dir"
|
|
cp -r "$src_xkb_dir"/* "$dst_xkb_dir"
|
|
_echo "* added $src_xkb_dir"
|
|
fi
|
|
;;
|
|
*/libX11.so*)
|
|
src_xlocale_dir=/usr/share/X11/locale
|
|
dst_xlocale_dir=$APPDIR/share/X11/locale
|
|
if [ -d "$src_xlocale_dir" ] && [ ! -d "$dst_xlocale_dir" ]; then
|
|
mkdir -p "$dst_xlocale_dir"
|
|
cp -r "$src_xlocale_dir"/* "$dst_xlocale_dir"
|
|
_echo "* added $src_xlocale_dir"
|
|
fi
|
|
;;
|
|
*/libgbm.so*) # This hook should never be hit since OpenGL deployment already handles this
|
|
src_gbm_backends_dir=$LIB_DIR/gbm
|
|
dst_gbm_backends_dir=$DST_LIB_DIR/gbm
|
|
if [ -d "$src_gbm_backends_dir" ] && [ ! -d "$dst_gbm_backends_dir" ]; then
|
|
cp -r "$src_gbm_backends_dir" "$dst_gbm_backends_dir"
|
|
_echo "* added $src_gbm_backends_dir"
|
|
fi
|
|
;;
|
|
*/libEGL_mesa.so*)
|
|
src_glvnd_dir=/usr/share/glvnd/egl_vendor.d
|
|
dst_glvnd_dir=$APPDIR/share/glvnd/egl_vendor.d
|
|
if [ -d "$src_glvnd_dir" ] && [ ! -d "$dst_glvnd_dir" ]; then
|
|
mkdir -p "$dst_glvnd_dir"
|
|
cp -v "$src_glvnd_dir"/*.json "$dst_glvnd_dir"
|
|
sed -i -e 's|/usr/lib.*/||g' "$dst_glvnd_dir"/*.json
|
|
_echo "* added $src_glvnd_dir"
|
|
fi
|
|
|
|
src_drirc_dir=/usr/share/drirc.d
|
|
dst_drirc_dir=$APPDIR/share/drirc.d
|
|
if [ -d "$src_drirc_dir" ] && [ ! -d "$dst_drirc_dir" ]; then
|
|
cp -r "$src_drirc_dir" "$dst_drirc_dir"
|
|
_echo "* added $src_drirc_dir"
|
|
fi
|
|
;;
|
|
*/libdrm_amdgpu.so*)
|
|
src_libdrm_dir=/usr/share/libdrm
|
|
dst_libdrm_dir=$APPDIR/share/libdrm
|
|
if [ -d "$src_libdrm_dir" ] && [ ! -d "$dst_libdrm_dir" ]; then
|
|
cp -r "$src_libdrm_dir" "$dst_libdrm_dir"
|
|
_echo "* added $src_libdrm_dir"
|
|
fi
|
|
;;
|
|
*/libvulkan.so*)
|
|
src_vulkan_dir=/usr/share/vulkan/icd.d
|
|
dst_vulkan_dir=$APPDIR/share/vulkan/icd.d
|
|
if [ -d "$src_vulkan_dir" ] && [ ! -d "$dst_vulkan_dir" ]; then
|
|
mkdir -p "$dst_vulkan_dir"
|
|
cp -v "$src_vulkan_dir"/*.json "$dst_vulkan_dir"
|
|
sed -i -e 's|/usr/lib.*/||g' "$dst_vulkan_dir"/*.json
|
|
_echo "* added $src_vulkan_dir"
|
|
fi
|
|
;;
|
|
*/libVkLayer*.so*)
|
|
# find vulkan layer icd file
|
|
src_vklayer_icd=$(grep -r "${lib##*/}" /usr/share/vulkan/* | awk -F':' '{print $1; exit}')
|
|
dst_vklayer_icd=$APPDIR/${src_vklayer_icd#/usr/}
|
|
if [ -f "$src_vklayer_icd" ] && [ ! -f "$dst_vklayer_icd" ]; then
|
|
mkdir -p "${dst_vklayer_icd%/*}"
|
|
cp -vL "$src_vklayer_icd" "$dst_vklayer_icd"
|
|
sed -i -e 's|/usr/lib.*/||g' "$dst_vklayer_icd"
|
|
_echo "* added vulkan layer icd: $src_vklayer_icd"
|
|
fi
|
|
;;
|
|
# this hook is a common false positive
|
|
# because a lot of applications execute commands thru the system shell
|
|
# and that often links to this library, causing overdeployment of terminfo files
|
|
*/libncursesw.so*|*/libcursesw.so*|*/libcurses.so*)
|
|
src_terminfo_dir=/usr/share/terminfo
|
|
dst_terminfo_dir=$APPDIR/share/terminfo
|
|
if [ -d "$src_terminfo_dir" ] && [ ! -d "$dst_terminfo_dir" ]; then
|
|
cp -r "$src_terminfo_dir" "$dst_terminfo_dir"
|
|
_echo "* added $src_terminfo_dir"
|
|
fi
|
|
|
|
src_tabset_dir=/usr/share/tabset
|
|
dst_tabset_dir=$APPDIR/share/tabset
|
|
if [ -d "$src_tabset_dir" ] && [ ! -d "$dst_tabset_dir" ]; then
|
|
cp -r "$src_tabset_dir" "$dst_tabset_dir"
|
|
_echo "* added $src_tabset_dir"
|
|
fi
|
|
;;
|
|
*/qt*/plugins/*.so)
|
|
f=$DST_BIN_DIR/qt.conf
|
|
if [ ! -f "$f" ]; then
|
|
_qtdir=${lib#$DST_LIB_DIR/} # leaves qt*
|
|
_qtdir=${_qtdir%%/*} # gets basename
|
|
_libdir=${DST_LIB_DIR##*/} # libdir basename (lib or lib32)
|
|
cat <<-EOF > "$f"
|
|
[Paths]
|
|
Prefix = ../$_libdir/$_qtdir
|
|
Plugins = plugins
|
|
Imports = qml
|
|
Qml2Imports = qml
|
|
EOF
|
|
_echo "* added $f "
|
|
fi
|
|
|
|
# deploy translation files
|
|
src_qt_trans=/usr/share/$QT_DIR/translations
|
|
dst_qt_trans=$DST_LIB_DIR/$QT_DIR/translations
|
|
if [ -d "$src_qt_trans" ] && [ ! -d "$dst_qt_trans" ]; then
|
|
mkdir -p "${dst_qt_trans%/*}"
|
|
# debloat a bit since we don't need all of them
|
|
cp -r "$src_qt_trans" "$dst_qt_trans"
|
|
rm -f "$dst_qt_trans"/assistant*.qm
|
|
rm -f "$dst_qt_trans"/designer*.qm
|
|
rm -f "$dst_qt_trans"/linguist*.qm
|
|
_echo "* added $src_qt_trans"
|
|
fi
|
|
;;
|
|
*/libgs.so*)
|
|
src_gs_dir=/usr/share/ghostscript
|
|
dst_gs_dir=$APPDIR/share/ghostscript
|
|
if [ -d "$src_gs_dir" ] && [ ! -d "$dst_gs_dir" ]; then
|
|
cp -r "$src_gs_dir" "$dst_gs_dir"
|
|
_echo "* added $src_gs_dir"
|
|
fi
|
|
;;
|
|
*/libmagic.so*)
|
|
# sharun only checks for $SHARUN_DIR/share/file/misc/magic.mgc
|
|
# but on ubuntu for example, the file is located in /usr/share/file/magic.mgc
|
|
# so we need to find the magic.mgc file and copy it to dst
|
|
src_magic_file=$(find -L /usr/share/file -type f -name magic.mgc -print -quit) || :
|
|
dst_magic_file=$APPDIR/share/file/misc/magic.mgc
|
|
if [ -f "$src_magic_file" ] && [ ! -f "$dst_magic_file" ]; then
|
|
mkdir -p "${dst_magic_file%/*}"
|
|
cp -vL "$src_magic_file" "$dst_magic_file"
|
|
_echo "* added $src_magic_file"
|
|
fi
|
|
;;
|
|
*/libgirepository-*.so*)
|
|
_girver=$(echo "$lib" | awk -F'-' '{print $NF}' | sed "s|\.so.*||")
|
|
src_girepository_dir=$LIB_DIR/girepository-$_girver
|
|
dst_girepository_dir=$DST_LIB_DIR/girepository-$_girver
|
|
if [ -d "$src_girepository_dir" ] && [ ! -d "$dst_girepository_dir" ]; then
|
|
cp -r "$src_girepository_dir" "$dst_girepository_dir"
|
|
_echo "* added $src_girepository_dir"
|
|
|
|
# there might be more .typelib files around, we need to copy them
|
|
_typelibfiles=$(find "$LIB_DIR"/*/* -type f -name '*.typelib' 2>/dev/null \
|
|
| grep -v "$src_girepository_dir" | grep girepository-"$_girver"
|
|
) || :
|
|
for f in $_typelibfiles; do
|
|
[ -f "$f" ] || continue
|
|
cp -v "$f" "$dst_girepository_dir"
|
|
done
|
|
if [ -n "$_typelibfiles" ]; then
|
|
_echo "* added additional .typelib files"
|
|
fi
|
|
fi
|
|
;;
|
|
*/gconv/*.so)
|
|
src_gconvm_file=$LIB_DIR/gconv/gconv-modules
|
|
dst_gconvm_file=$DST_LIB_DIR/gconv/gconv-modules
|
|
if [ -f "$src_gconvm_file" ] && [ ! -f "$dst_gconvm_file" ]; then
|
|
mkdir -p "${dst_gconvm_file%/*}"
|
|
cp -v "$src_gconvm_file" "$dst_gconvm_file"
|
|
_echo "* added $src_gconvm_file"
|
|
fi
|
|
;;
|
|
*/libc.so*)
|
|
src_c_locale_dir=/usr/lib/locale/C.utf8
|
|
dst_c_locale_dir=$DST_LIB_DIR/locale/C.utf8
|
|
mkdir -p "$DST_LIB_DIR"/locale
|
|
if [ -d "$src_c_locale_dir" ] && [ ! -d "$dst_c_locale_dir" ]; then
|
|
cp -r "$src_c_locale_dir" "$dst_c_locale_dir"
|
|
_echo "* added C.UTF-8 locale"
|
|
fi
|
|
# C.UTF-8 is not enough, some apps may crash when this locale is used
|
|
# so we need to ship en_US.UTF-8 so we can guarantee applications
|
|
# will launch in systems without glibc locales like alpine linux
|
|
#
|
|
# Because distros use a locale-archive these days, we have to compile it
|
|
#
|
|
dst_en_locale_dir=$DST_LIB_DIR/locale/en_US.utf8
|
|
if [ ! -d "$dst_en_locale_dir" ] && _is_cmd localedef; then
|
|
mkdir -p /tmp/usr/lib/locale
|
|
localedef --prefix /tmp --no-archive -i en_US -f UTF-8 en_US.UTF-8 || :
|
|
if cp -r /tmp/usr/lib/locale/en_US.utf8 "$DST_LIB_DIR"/locale; then
|
|
_echo "* added en_US.UTF-8 locale"
|
|
fi
|
|
fi
|
|
;;
|
|
*/ld-linux*.so*|*/ld-musl*.so*)
|
|
# patch away the dynamic linker /etc to disable /etc/ld.so.preload
|
|
if grep -qa -m 1 '/etc' "$lib"; then
|
|
sed -i -e 's|/etc/ld.so.preload|/XXX/ld.so.preload|g' "$lib"
|
|
_echo "* patched away ${lib##*/} /etc/ld.so.preload"
|
|
fi
|
|
;;
|
|
*/libgegl*.so*)
|
|
src_gegl_dir=$(echo "$LIB_DIR"/gegl-*)
|
|
dst_gegl_dir=$DST_LIB_DIR/${src_gegl_dir##*/}
|
|
if [ -d "$src_gegl_dir" ] && [ -d "$dst_gegl_dir" ]; then
|
|
if cp "$src_gegl_dir"/*.json "$dst_gegl_dir"; then
|
|
_echo "* added $src_gegl_dir .json files"
|
|
fi
|
|
fi
|
|
# GEGL_PATH is problematic so we avoid it
|
|
# patch the lib directly to load its plugins instead
|
|
_patch_away_usr_lib_dir "$lib" || continue
|
|
echo 'unset GEGL_PATH' >> "$APPENV"
|
|
;;
|
|
*/libMagick*.so*)
|
|
src_magick_config_dir=$(echo /etc/ImageMagick*)
|
|
dst_magick_config_dir=$APPDIR/etc/${src_magick_config_dir##*/}
|
|
if [ -d "$src_magick_config_dir" ] && [ ! -d "$dst_magick_config_dir" ]; then
|
|
mkdir -p "$dst_magick_config_dir"
|
|
(
|
|
# imagemagick has a ton of .xml config files that need
|
|
# to be added, they can all be copied to one location
|
|
set -- \
|
|
/usr/share/ImageMagick*/* \
|
|
"$src_magick_config_dir"/* \
|
|
"$LIB_DIR"/ImageMagick*/config*/*.xml
|
|
for f do
|
|
if [ -f "$f" ]; then
|
|
_copy=1
|
|
cp "$f" "$dst_magick_config_dir"
|
|
fi
|
|
done
|
|
if [ "$_copy" = 1 ]; then
|
|
_echo "* added ImageMagick config files..."
|
|
fi
|
|
# MAGICK_HOME is all that needs to be set
|
|
echo 'MAGICK_HOME=${SHARUN_DIR}' >> "$APPENV"
|
|
# however MAGICK_HOME only works when compiled with a specific flag
|
|
# we can still make this relocatable by setting these other env variables
|
|
# which will always work even when not compiled with MAGICK_HOME support
|
|
cd "$APPDIR"
|
|
set -- lib*/ImageMagick-*/modules*/coders
|
|
if [ -d "$1" ]; then
|
|
echo "MAGICK_CODER_MODULE_PATH=\${SHARUN_DIR}/$1" >> "$APPENV"
|
|
fi
|
|
set -- lib*/ImageMagick-*/modules*/filters
|
|
if [ -d "$1" ]; then
|
|
# checking the code it seems that MAGICK_FILTER_MODULE_PATH
|
|
# is NOT USED in the code and seems to be an error!!! the variable
|
|
# that modules.c references is MAGICK_CODER_FILTER_PATH
|
|
# we will still be set both just in case
|
|
echo "MAGICK_CODER_FILTER_PATH=\${SHARUN_DIR}/$1" >> "$APPENV"
|
|
echo "MAGICK_FILTER_MODULE_PATH=\${SHARUN_DIR}/$1" >> "$APPENV"
|
|
fi
|
|
set -- etc/ImageMagick*
|
|
if [ -d "$1" ]; then
|
|
echo "MAGICK_CONFIGURE_PATH=\${SHARUN_DIR}/$1" >> "$APPENV"
|
|
fi
|
|
)
|
|
fi
|
|
;;
|
|
*/libp11-kit.so*)
|
|
src_p11kit_config_dir=/usr/share/p11-kit
|
|
dst_p11kit_config_dir=$APPDIR/share/p11-kit
|
|
if [ -d "$src_p11kit_config_dir" ] && [ ! -d "$dst_p11kit_config_dir" ]; then
|
|
cp -r "$src_p11kit_config_dir" "$dst_p11kit_config_dir"
|
|
_echo "* added $src_p11kit_config_dir"
|
|
fi
|
|
_patch_away_usr_lib_dir "$lib" || :
|
|
_patch_away_usr_share_dir "$lib" || :
|
|
;;
|
|
*/p11-kit-trust.so*)
|
|
# Because OpenSUSE had to ruin this, we will have to patch the
|
|
# the certificates to a path in /tmp that we will later make
|
|
# a symlink that points to the real host certs location
|
|
|
|
# Originally we just patch to etc/ssl/certs/ca-certificates.crt
|
|
# See https://github.com/kem-a/AppManager/issues/39
|
|
|
|
# string has to be same length
|
|
problem_path="/usr/share/ca-certificates/trust-source"
|
|
ssl_path_fix="/tmp/.___host-certs/ca-certificates.crt"
|
|
|
|
if grep -Eaoq -m 1 "$ssl_path_fix" "$lib"; then
|
|
continue # all good nothing to fix
|
|
elif grep -Eaoq -m 1 "$problem_path" "$lib"; then
|
|
sed -i -e "s|$problem_path|$ssl_path_fix|g" "$lib"
|
|
else
|
|
continue # TODO add more possible problematic paths
|
|
fi
|
|
|
|
_add_p11kit_cert_hook
|
|
|
|
_echo "* fixed path to /etc/ssl/certs in $lib"
|
|
_patch_away_usr_share_dir "$lib" || continue
|
|
;;
|
|
*/libcrypto.so*)
|
|
# Apps may fail to connect to internet if they use the host ssl config
|
|
# see: https://github.com/pkgforge-dev/Viber-AppImage-Enhanced/issues/16
|
|
dst_ssl_conf=$APPDIR/etc/ssl/openssl.cnf
|
|
if [ ! -f "$dst_ssl_conf" ]; then
|
|
mkdir -p "${dst_ssl_conf%/*}"
|
|
# make a minimal ssl config instead of copying the hosts
|
|
cat <<-'EOF' > "$dst_ssl_conf"
|
|
[openssl_conf]
|
|
openssl_conf = openssl_init
|
|
|
|
[openssl_init]
|
|
providers = provider_sect
|
|
|
|
[provider_sect]
|
|
default = default_sect
|
|
|
|
[default_sect]
|
|
activate = 1
|
|
EOF
|
|
_echo "* added minimal ssl config"
|
|
fi
|
|
;;
|
|
*/libgimpwidgets*)
|
|
_patch_away_usr_share_dir "$lib" || continue
|
|
;;
|
|
*/libmlt*.so*)
|
|
src_mlt_data_dir=$(echo /usr/share/mlt-*)
|
|
dst_mlt_data_dir=$APPDIR/share/${src_mlt_data_dir##*/}
|
|
|
|
if [ -d "$src_mlt_data_dir" ] && [ ! -d "$dst_mlt_data_dir" ]; then
|
|
cp -r "$src_mlt_data_dir" "$dst_mlt_data_dir"
|
|
_echo "* added $src_mlt_data_dir"
|
|
fi
|
|
;;
|
|
*/libMangoHud*.so*)
|
|
src_mangohud_layer=$(echo /usr/share/vulkan/implicit_layer.d/MangoHud*.json)
|
|
dst_mangohud_layer="$APPDIR"/share/vulkan/implicit_layer.d/"${src_mangohud_layer##*/}"
|
|
if [ -f "$src_mangohud_layer" ] && [ ! -f "$dst_mangohud_layer" ]; then
|
|
mkdir -p "$APPDIR"/share/vulkan/implicit_layer.d
|
|
cp -v "$src_mangohud_layer" "$dst_mangohud_layer"
|
|
sed -i 's|/.*/mangohud/||' "$dst_mangohud_layer"
|
|
|
|
if [ ! -f "$DST_BIN_DIR"/mangohud ] \
|
|
&& command -v mangohud 1>/dev/null; then
|
|
cp -v "$(command -v mangohud)" "$DST_BIN_DIR"
|
|
fi
|
|
|
|
sed -i \
|
|
-e 's|/usr/.*/||' \
|
|
-e '1a\export SHARUN_ALLOW_LD_PRELOAD=1' \
|
|
-e 's|#!.*|#!/bin/sh|' \
|
|
"$DST_BIN_DIR"/mangohud || :
|
|
|
|
_echo "Copied over mangohud layer and patched mangohud"
|
|
fi
|
|
;;
|
|
*/libwebkit*gtk-*.so*)
|
|
_add_bwrap_wrapper
|
|
# now do better path map to the libs
|
|
_patch_away_usr_lib_dir "$lib" || :
|
|
_patch_away_usr_bin_dir "$lib" || :
|
|
|
|
# check if webkit2gtk was compiled relocatable
|
|
if grep -aq -m 1 'WEBKIT_EXEC_PATH' "$lib" \
|
|
&& ! grep -q WEBKIT_EXEC_PATH "$APPENV"; then
|
|
(
|
|
set -- "$DST_BIN_DIR"/WebKit*
|
|
if [ -f "$1" ]; then
|
|
echo 'WEBKIT_EXEC_PATH=${SHARUN_DIR}/bin' >> "$APPENV"
|
|
fi
|
|
)
|
|
fi
|
|
;;
|
|
*/libwebkit*gtkinjectedbundle.so*)
|
|
# WEBKIT_INJECTED_BUNDLE_PATH always works
|
|
# It is not guarded behind a compiled flag unlike WEBKIT_EXEC_PATH
|
|
if ! grep -q 'WEBKIT_INJECTED_BUNDLE_PATH' "$APPENV"; then
|
|
cp -v "$lib" "$DST_BIN_DIR"
|
|
echo 'WEBKIT_INJECTED_BUNDLE_PATH=${SHARUN_DIR}/bin' >> "$APPENV"
|
|
fi
|
|
;;
|
|
*/libdecor*.so*)
|
|
ADD_HOOKS="${ADD_HOOKS:+$ADD_HOOKS:}fix-gnome-csd.hook"
|
|
;;
|
|
*/libSDL*.so*)
|
|
# SDL may be bundled without libdecor since it maybe missing from the CI runner
|
|
# or the application makes of GTK/Qt + SDL, in which case we do not need libdecor
|
|
# at all, make sure SDL does not attempt to load libdecor in these cases
|
|
if [ -f "$DST_LIB_DIR"/libdecor-0.so.0 ]; then
|
|
continue
|
|
elif grep -aoq -m 1 'libdecor-0.so.0' "$lib"; then
|
|
sed -i -e 's|libdecor-0.so.0|fuck-gnome.so.X|g' "$lib"
|
|
fi
|
|
;;
|
|
*/xpm.so)
|
|
f=/usr/share/imlib2/rgb.txt
|
|
if [ -f "$f" ]; then
|
|
mkdir -p "$APPDIR"/share/imlib2
|
|
cp -v "$f" "$APPDIR"/share/imlib2
|
|
_patch_away_usr_share_dir "$lib" || continue
|
|
_echo "Copied and patched imlib2 xpm loader"
|
|
fi
|
|
;;
|
|
*/7z.so)
|
|
# the 7z binaries need the lib next to them
|
|
cp -v "$lib" "$DST_BIN_DIR"
|
|
;;
|
|
*/libpipewire-*.so*)
|
|
src_pipewire_config_dir=/usr/share/pipewire
|
|
dst_pipewire_config_dir=$APPDIR/share/pipewire
|
|
if [ -d "$src_pipewire_config_dir" ] && [ ! -d "$dst_pipewire_config_dir" ]; then
|
|
cp -r "$src_pipewire_config_dir" "$dst_pipewire_config_dir"
|
|
|
|
cat <<-'EOF' > "$DST_BIN_DIR"/01-pipewire-config.hook
|
|
_pipewire_dir=$APPDIR/share/pipewire
|
|
if [ ! -d /usr/share/pipewire ] && [ -d "$_pipewire_dir" ]; then
|
|
export PIPEWIRE_CONFIG_DIR="$_pipewire_dir"
|
|
fi
|
|
EOF
|
|
fi
|
|
;;
|
|
*/libtesseract.so*)
|
|
src_tess_data_dir=/usr/share/tessdata
|
|
dst_tess_data_dir=$APPDIR/share/tessdata
|
|
if [ -d "$src_tess_data_dir" ] && [ ! -d "$dst_tess_data_dir" ]; then
|
|
cp -r "$src_tess_data_dir" "$dst_tess_data_dir"
|
|
_echo "* added $src_tess_data_dir"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
_deploy_datadir
|
|
|
|
# copy the entire hicolor icons dir
|
|
# by default the hicolor icon theme ships no icons, this
|
|
# means any present icon is likely needed by the application
|
|
if [ -d /usr/share/icons/hicolor ]; then
|
|
mkdir -p "$APPDIR"/share/icons
|
|
cp -r /usr/share/icons/hicolor "$APPDIR"/share/icons
|
|
_remove_empty_dirs "$APPDIR"/share/icons/hicolor
|
|
fi
|
|
|
|
_deploy_locale
|
|
|
|
# make the lib.path file. Very important for sharun to discover bundled libs!
|
|
"$APPDIR"/sharun -g
|
|
|
|
# on debian some libs may hardcode paths like /usr/lib/x86_64-linux-gnu
|
|
# make a compat symlink so patched paths resolve to bundled libs
|
|
d=$APPIMAGE_ARCH-linux-gnu
|
|
case "$LIB_DIR" in
|
|
*/"$d"*)
|
|
( cd "$DST_LIB_DIR" && ln -s . "$d" 2>/dev/null || : )
|
|
;;
|
|
esac
|
|
|
|
_strip_bins_and_libs
|
|
_check_hardcoded_lib_dirs
|
|
_check_hardcoded_data_dirs
|
|
|
|
# patch away any hardcoded path to /usr/share or /usr/lib in bins...
|
|
set -- "$SHARUN_BIN_DIR"/*
|
|
for bin do
|
|
if p=$(grep -ao -m 1 '/usr/share/.*/' "$bin"); then
|
|
_echo "* Detected hardcoded path to $p in $bin"
|
|
_patch_away_usr_share_dir "$bin" || :
|
|
fi
|
|
if p=$(grep -ao -m 1 '/usr/lib/.*/' "$bin"); then
|
|
_echo "* Detected hardcoded path to $p in $bin"
|
|
_patch_away_usr_lib_dir "$bin" || :
|
|
fi
|
|
if _is_bun_binary "$bin" || _is_pyinstaller_binary "$bin"; then
|
|
# bun/pyisntaller binaries cannot be executed with the
|
|
# dynamic linker directly, so we will change PT_INTERP to
|
|
# /tmp/.ld-sharun.so.67, sharun will copy it there at runtime
|
|
patchelf --set-interpreter /tmp/.ld-sharun.so.67 "$bin"
|
|
_echo "* Set interpreter to /tmp/.ld-sharun.so.67 for $bin"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
_echo "------------------------------------------------------------"
|
|
echo ""
|
|
|
|
if [ -n "$ADD_HOOKS" ]; then
|
|
old_ifs="$IFS"
|
|
IFS=':'
|
|
set -- $ADD_HOOKS
|
|
IFS="$old_ifs"
|
|
hook_dst=$DST_BIN_DIR
|
|
for hook do
|
|
# hooks used to be executed differently depending on the suffix
|
|
# this was dropped and now all hooks are sourced
|
|
# remove old suffixes so that we don't break existing scripts
|
|
hook=${hook%.bg.hook}
|
|
hook=${hook%.src.hook}
|
|
# also remove .hook before adding it again
|
|
# this allows declaring a hook without the suffix in ADD_HOOKS
|
|
hook=${hook%.hook}
|
|
hook=${hook}.hook
|
|
|
|
if [ -f "$hook_dst"/"$hook" ]; then
|
|
continue
|
|
elif _download "$hook_dst"/"$hook" "$HOOKSRC"/"$hook"; then
|
|
_echo "* Added $hook"
|
|
else
|
|
_err_msg "ERROR: Failed to download $hook, valid link?"
|
|
_err_msg "$HOOKSRC/$hook"
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
_add_hooks_library
|
|
_add_apprun
|
|
|
|
chmod +x "$APPDIR"/AppRun.sh "$APPDIR"/AppRun || :
|
|
|
|
# deploy directories
|
|
while read -r d; do
|
|
if [ -d "$d" ]; then
|
|
case "$d" in
|
|
"$LIB_DIR"/*)
|
|
if [ "$LIB32" = 1 ]; then
|
|
dst_path="$APPDIR"/lib32/"${d##*$LIB_DIR/}"
|
|
else
|
|
dst_path="$APPDIR"/lib/"${d##*$LIB_DIR/}"
|
|
fi
|
|
;;
|
|
*/share/*)
|
|
dst_path="$APPDIR"/share/"${d##*/share/}"
|
|
;;
|
|
*/etc/*)
|
|
dst_path="$APPDIR"/etc/"${d##*/etc/}"
|
|
;;
|
|
*/lib/*)
|
|
dst_path="$APPDIR"/lib/"${d##*/lib/}"
|
|
;;
|
|
*/lib32/*)
|
|
dst_path="$APPDIR"/lib32/"${d##*/lib32/}"
|
|
;;
|
|
"$APPDIR"/*|./"${APPDIR##*/}"/*|"${APPDIR##*/}"/*)
|
|
_err_msg "Skipping deployment of $d (already in '$APPDIR')"
|
|
continue
|
|
;;
|
|
*)
|
|
_err_msg "Skipping deployment of $d"
|
|
_err_msg "Valid directories to deploy are:"
|
|
_err_msg "Any dir from: $LIB_DIR"
|
|
_err_msg "Any dir with /lib/ in its path"
|
|
_err_msg "Any dir with /share/ in its path"
|
|
_err_msg "Any dir with /etc/ in its path"
|
|
continue
|
|
;;
|
|
esac
|
|
mkdir -p "${dst_path%/*}"
|
|
if cp -Lrn "$d"/. "$dst_path"; then
|
|
_echo "* Added $d to $dst_path"
|
|
else
|
|
# do not stop the script if the copy fails, because
|
|
# since lib4bin skips directories automatically we do
|
|
# not want CIs to fail because suddenly now we are
|
|
# trying to copy some directory that we did not have
|
|
# read access to that lib4bin was previously skipping
|
|
_err_msg "Failed to add $d to $dst_path/${d##*/}"
|
|
fi
|
|
fi
|
|
done <<-EOF
|
|
$ADD_DIR
|
|
EOF
|
|
|
|
_handle_nested_bins
|
|
|
|
if [ -n "$ANYLINUX_DO_NOT_LOAD_LIBS" ]; then
|
|
echo "ANYLINUX_DO_NOT_LOAD_LIBS=$ANYLINUX_DO_NOT_LOAD_LIBS:\${ANYLINUX_DO_NOT_LOAD_LIBS}" >> "$APPENV"
|
|
fi
|
|
|
|
# check if we have libjack.so in the AppImage, jack needs matching
|
|
# server and client library versions to work, instead we need to use
|
|
# pipewire-jack, which gives a libjack.so that does not have this limitation
|
|
libjackwarning="
|
|
------------------------------------------------------------
|
|
------------------------------------------------------------
|
|
|
|
WARNING: Detected libjack.so has been bundled in this application!
|
|
If this app is going to connect to a jack server it is not going to work!
|
|
jack needs matching library versions between clients and server to work!
|
|
|
|
The only solution is bundling libjack.so from pipewire-jack
|
|
package instead which does not have this issue.
|
|
|
|
NOTE: This is only a problem if the application has the option to connect
|
|
to a jack server, that is for example music players and music editing software
|
|
libjack.so can be bundled as linked dependency of another library like
|
|
ffmpeg and in that case this is not an issue.
|
|
|
|
------------------------------------------------------------
|
|
------------------------------------------------------------
|
|
"
|
|
set -- "$DST_LIB_DIR"/libjack.so*
|
|
if [ -f "$1" ]; then
|
|
if ! ldd "$1" | grep -q 'libpipewire'; then
|
|
_err_msg "$libjackwarning"
|
|
fi
|
|
fi
|
|
|
|
# also warn when several common qt theme plugins are missing, we only do this for qt6
|
|
if [ -d "$DST_LIB_DIR"/qt6 ]; then
|
|
for p in kvantum qtlxqt qt6ct; do
|
|
set -- "$DST_LIB_DIR"/qt6/plugins/*/*$p*
|
|
if [ ! -f "$1" ]; then
|
|
_err_msg "------------------------------------------------------------"
|
|
_err_msg "WARNING: Qt was deployed but there is no $p plugin!"
|
|
_err_msg "This means the application will lack proper theme support!"
|
|
_err_msg "Install the packages that provide theme support before deploying"
|
|
_err_msg "In archlinux those are: qt6ct kvantum lxqt-qtplugin"
|
|
_err_msg "------------------------------------------------------------"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# suggest people to use glycin-ng instead
|
|
if [ "$GNOME_GLYCIN" = 1 ]; then
|
|
_err_msg "------------------------------------------------------------"
|
|
_err_msg "WARNING: GNOME glycin has been deployed!"
|
|
_echo "There is a much better alternative called glycin-ng, features include:"
|
|
_echo "* 5 times smaller!"
|
|
_echo "* No bwrap dependency (uses landlock for sandbox instead)"
|
|
_echo "* No dbus dependency"
|
|
_echo "https://github.com/QaidVoid/glycin-ng"
|
|
_err_msg "------------------------------------------------------------"
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$OUTPUT_APPIMAGE" = 1 ]; then
|
|
_make_appimage
|
|
else
|
|
_sort_env_file
|
|
_echo "------------------------------------------------------------"
|
|
_echo "All done!"
|
|
_echo "------------------------------------------------------------"
|
|
fi
|