#!/bin/bash
#
#   GSApp2AppImage.sh - Version 2026-03-11, by Josh Freeman & Simon Peter
#
#   Debian-based Linux script to bundle a GNUstep GUI app & its dependencies into a standalone AppImage
#
#   Usage: GSApp2AppImage.sh /path/to/a/GNUstep.app
#
#   Reads the following optional environment variables:
#
#     GS_LIBRARY_DIRS_TO_INCLUDE
#     [List of directories to copy to the AppImage from $GNUSTEP_SYSTEM_LIBRARY & $GNUSTEP_LOCAL_LIBRARY; Default: $DEFAULT_GS_LIBRARY_DIRS_TO_INCLUDE]
#
#     GS_TOOLS_TO_INCLUDE
#     [List of tools to copy to the AppImage from $GNUSTEP_SYSTEM_TOOLS & $GNUSTEP_LOCAL_TOOLS; Default: $DEFAULT_GS_TOOLS_TO_INCLUDE]
#
#     APPIMAGE_BUILD_NUMBER
#     [Number appended to the app's version number in the AppImage's filename]
#
#     GNUSTEP_CONFIG_FILE
#     [Path of the GNUstep.conf config file; Default: Search in ~/etc/GNUstep, /usr/local/etc/GNUstep, & /etc/GNUstep]
#

DEFAULT_GS_LIBRARY_DIRS_TO_INCLUDE="Bundles ColorPickers Fonts Frameworks Images KeyBindings Libraries PostScript"
# GS Library Dirs: Applications ApplicationSupport Bundles ColorPickers Colors DocTemplates Documentation DTDs
#                   Fonts Frameworks Headers Images KeyBindings Libraries Makefiles PostScript Services Sounds
#                   Themes Tools

DEFAULT_GS_TOOLS_TO_INCLUDE="gdnc gpbs make_services"
# GS Tools: autogsdoc classes cvtenc debugapp defaults gclose gcloseall gdnc gdomap gnustep-config gnustep-tests
#           gopen gpbs gspath HTMLLinker make_services make_strings openapp opentool pl pl2link pldes plget
#           plmerge plparse plser plutil set_show_service sfparse xmlparse

DEFAULT_APP_VERSION="1.0"


GetUserApprovalOrExit() {
    while true; do
        read -p "$1 [y/n] " yn
        case $yn in
            [Yy]* ) break;;
            [Nn]* ) exit;;
            * ) echo "Please answer yes or no.";;
        esac
    done
}

set -e

# VERIFY APP PARAMETER
APP_BUNDLE=$1

if [ -z "$APP_BUNDLE" ]; then
    echo "Usage: GSApp2AppImage.sh /path/to/a/GNUstep.app"
    exit 1
fi

REGEX_APP_SUFFIX='\.app$'
if [[ ! "$APP_BUNDLE" =~ $REGEX_APP_SUFFIX ]]; then
    echo "ERROR: Application path doesn't have an .app extension: '$APP_BUNDLE'"
    exit 1
fi

if [ ! -d $APP_BUNDLE ]; then
    echo "ERROR: Application path isn't a directory: '$APP_BUNDLE'"
    exit 1
fi

APP_BUNDLE=`readlink -f $APP_BUNDLE`
APP_NAME=`echo "$APP_BUNDLE" | sed -E 's|^.*/([^/]*).app$|\1|'`

# LOCATE APP'S INFO-GNUSTEP.PLIST FILE
APP_INFO_PLIST_PATH="${APP_BUNDLE}/Resources/Info-gnustep.plist"

if [ ! -f $APP_INFO_PLIST_PATH ]; then
    echo "ERROR: Missing Info-gnustep.plist file in '`dirname ${APP_INFO_PLIST_PATH}`'."
    exit 1
fi

# LOCATE APP'S DESKTOP FILE
APP_DESKTOP_FILE="${APP_BUNDLE}/Resources/${APP_NAME}.desktop"

if [ ! -f $APP_DESKTOP_FILE ]; then
    echo "ERROR: Missing ${APP_NAME}.desktop file in '`dirname ${APP_DESKTOP_FILE}`'."
    exit 1
fi

# LOCATE GNUSTEP'S CONFIG FILE
if [ -z "$GNUSTEP_CONFIG_FILE" ] || [ ! -f $GNUSTEP_CONFIG_FILE ]; then
    GNUSTEP_CONFIG_FILE=`find {~/etc/GNUstep,/usr/local/etc/GNUstep,/etc/GNUstep} -name "GNUstep.conf" 2>/dev/null | head -n 1`

    if [ -z "$GNUSTEP_CONFIG_FILE" ] || [ ! -f $GNUSTEP_CONFIG_FILE ]; then
        echo "ERROR: Couldn't locate GNUstep.conf file."
        exit 1
    fi
fi

# LOAD GNUSTEP CONFIG PATHS
GNUSTEP_MAKEFILES=`grep ^GNUSTEP_MAKEFILES= $GNUSTEP_CONFIG_FILE | sed -E 's/.*=(.*$)/\1/'`

if [ -z "$GNUSTEP_MAKEFILES" ]; then
    echo "ERROR: Unable to parse GNUSTEP_MAKEFILES variable from '$GNUSTEP_CONFIG_FILE'."
    exit 1
fi

GNUSTEP_SH="$GNUSTEP_MAKEFILES/GNUstep.sh"

if [ ! -f $GNUSTEP_SH ]; then
    echo "ERROR: Missing GNUstep.sh file in '$GNUSTEP_MAKEFILES'."
    exit 1
fi

GNUSTEP_SH_EXPORT_ALL_VARIABLES=yes
. "$GNUSTEP_SH"
GNUSTEP_SH_EXPORT_ALL_VARIABLES=""

# VERIFY OR INSTALL NEEDED TOOLS
if [ -z `which convert` ]; then PKGS_TO_INSTALL="imagemagick" ; fi
if [ -z `which patchelf` ]; then PKGS_TO_INSTALL="$PKGS_TO_INSTALL patchelf" ; fi
if [ -z `which wget` ]; then PKGS_TO_INSTALL="$PKGS_TO_INSTALL wget" ; fi

if [ ! -z "$PKGS_TO_INSTALL" ]; then
    echo -e "\nMissing one or more needed tools."
    GetUserApprovalOrExit  "OK TO INSTALL PACKAGES FROM DEBIAN REPOSITORY ($PKGS_TO_INSTALL)?"

    sudo apt-get update
    sudo apt-get install -y $PKGS_TO_INSTALL
fi

# PARSE APP'S VERSION NUMBER FROM INFO-GNUSTEP.PLIST
APP_VERSION=`grep ApplicationRelease $APP_INFO_PLIST_PATH | sed -n -E 's/.*= "([^"]*)";/\1/p' | sed -E "s/ /_/g"`

if [ -z "$APP_VERSION" ]; then
    echo "WARNING: Couldn't parse ${APP_NAME}'s version number from '${APP_INFO_PLIST_PATH}'"
    echo "Using default version number: $DEFAULT_APP_VERSION"
    echo "To fix warning, insert the line 'ApplicationRelease = \"<version-number>\";' into ${APP_NAME}Info.plist & rebuild the app."
    APP_VERSION="$DEFAULT_APP_VERSION"
fi

# APPEND OPTIONAL APPIMAGE BUILD NUMBER TO APP VERSION NUMBER
if [ ! -z "$APPIMAGE_BUILD_NUMBER" ]; then
    APP_VERSION="${APP_VERSION}_${APPIMAGE_BUILD_NUMBER}"
fi

# CREATE APPIMAGE BUILD DIR
APPIMAGE_BUILD_DIR_NAME="${APP_NAME}-${APP_VERSION}-AppImage-build"

if [ -e $APPIMAGE_BUILD_DIR_NAME ]; then
    echo -e "\nBuild directory already exists: `pwd`/$APPIMAGE_BUILD_DIR_NAME"
    GetUserApprovalOrExit "OK TO OVERWRITE BUILD DIRECTORY?"

    rm -r $APPIMAGE_BUILD_DIR_NAME
fi

mkdir $APPIMAGE_BUILD_DIR_NAME
cd $APPIMAGE_BUILD_DIR_NAME
APPIMAGE_BUILD_DIR=`pwd`

# CREATE APPIMAGE'S APPDIR
cd $APPIMAGE_BUILD_DIR
mkdir AppDir
cd AppDir
#  Mirror Debian's top-level linked dirs: /bin, /lib, etc. link to their /usr counterparts (/bin -> /usr/bin, etc.)
for USR_DIR in bin lib lib32 lib64 sbin ; do
    if [ -d "/usr/$USR_DIR" ]; then
        mkdir -p "usr/$USR_DIR"
        ln -s "usr/$USR_DIR" .
    fi
done

# COPY GNUSTEP APP TO APPDIR
cd $APPIMAGE_BUILD_DIR
cp -r ${APP_BUNDLE}/* AppDir/usr/bin/
echo "Copied ${APP_BUNDLE}/* to $APPIMAGE_BUILD_DIR/AppDir/usr/bin/"

# SET UP APPIMAGE'S ICON
APP_ICON_FILENAME=`grep ^Icon= $APP_DESKTOP_FILE | sed -E 's/.*[/=]([^/=]+$)/\1/'`

if [ ! -z "$APP_ICON_FILENAME" ]; then
    if [ $APP_ICON_FILENAME = ${APP_ICON_FILENAME##*.} ]; then
        # if the app icon filename has no extension, assume it's a PNG & append an extension
        APP_ICON_FILENAME="${APP_ICON_FILENAME}.png"
    fi

    APP_ICON="$APP_BUNDLE/Resources/$APP_ICON_FILENAME"
else
    echo "WARNING: Couldn't parse ${APP_NAME}'s Icon image name from '${APP_DESKTOP_FILE}'."
    APP_ICON=""
fi

if [ ! -z "$APP_ICON" ] && [ -f $APP_ICON ]; then
    set +e
    identify $APP_ICON >/dev/null
    INVALID_ICON=$?
    set -e
else
    INVALID_ICON=1
fi

if [ $INVALID_ICON -ne 0 ]; then
    echo "WARNING: Invalid Icon file: '${APP_ICON}'"
    echo "Defaulting to GNUstep logo as the AppImage's icon."
    APP_ICON="${APPIMAGE_BUILD_DIR}/AppDir/${APP_NAME}.png"
    magick -size 600x600 xc:white -fill black -stroke black \
        -draw "path 'M 298.65625,49.999996 C 161.40808,49.999996 50,161.37552 50,298.62499 c 0,63.21343 23.65869,120.92593 62.5625,164.8125 l 102.59375,-0.5625 0.46875,-166.96875 166.5,0 0.90625,-164.68749 99.59375,0.25 C 437.13239,81.437336 371.55407,49.999996 298.65625,49.999996 z M 482.625,131.46875 c 40.15917,44.16586 64.65625,102.80469 64.65625,167.15624 0,137.24504 -111.37572,248.65626 -248.625,248.65626 -74.0333,0 -140.53204,-32.44655 -186.09375,-83.84376 L 51.375,463.78124 52.28125,550 550,549.53125 l -1.8125,-417.875 -65.5625,-0.1875 z'" \
        -colorspace sRGB $APP_ICON

    if [ ! -f $APP_ICON ]; then
        echo "ERROR: Unable to generate icon file, '${APP_ICON}'."
        exit 1
    fi
fi

cd "$APPIMAGE_BUILD_DIR/AppDir"
APPIMAGE_ICON_256_FILENAME="${APP_NAME}.png"
convert $APP_ICON -colorspace RGB -scale 256 -alpha set -background none -layers flatten -colorspace sRGB $APPIMAGE_ICON_256_FILENAME
mkdir -p usr/share/icons/hicolor/256x256/apps/
ln -sr $APPIMAGE_ICON_256_FILENAME usr/share/icons/hicolor/256x256/apps/

# SET UP APPIMAGE'S DESKTOP FILE
cd $APPIMAGE_BUILD_DIR
APPIMAGE_DESKTOP_FILE="AppDir/usr/share/applications/${APP_NAME}.desktop"
mkdir -p `dirname $APPIMAGE_DESKTOP_FILE`
cp $APP_DESKTOP_FILE $APPIMAGE_DESKTOP_FILE
sed -i -E "s/(^(Exec|Icon)=).*$/\1${APP_NAME}/" $APPIMAGE_DESKTOP_FILE

if [ -z `grep ^Icon= $APPIMAGE_DESKTOP_FILE` ]; then
    echo "Icon=$APP_NAME" >> $APPIMAGE_DESKTOP_FILE
fi

if [ -z `grep ^Name= $APPIMAGE_DESKTOP_FILE` ]; then
    echo "Name=$APP_NAME" >> $APPIMAGE_DESKTOP_FILE
fi

# COPY GNUSTEP LIBRARY DIRECTORIES TO APPDIR
cd $APPIMAGE_BUILD_DIR
APPIMAGE_GS_LOCAL_LIBRARY="AppDir/$GNUSTEP_LOCAL_LIBRARY"

if [ -z "$GS_LIBRARY_DIRS_TO_INCLUDE" ]; then
    GS_LIBRARY_DIRS_TO_INCLUDE=$DEFAULT_GS_LIBRARY_DIRS_TO_INCLUDE
fi

echo "GNUstep Library dirs to copy: $GS_LIBRARY_DIRS_TO_INCLUDE"

for LIBRARY_DIR in ${GS_LIBRARY_DIRS_TO_INCLUDE}; do
    LOCAL_LIBRARY_DIR_PATH="$GNUSTEP_LOCAL_LIBRARY/$LIBRARY_DIR"
    if [ -d $LOCAL_LIBRARY_DIR_PATH ]; then
        mkdir -p $APPIMAGE_GS_LOCAL_LIBRARY
        cp -rL $LOCAL_LIBRARY_DIR_PATH $APPIMAGE_GS_LOCAL_LIBRARY
        echo "Copied $LOCAL_LIBRARY_DIR_PATH to $APPIMAGE_GS_LOCAL_LIBRARY"
    fi
done

if [ $GNUSTEP_SYSTEM_LIBRARY != $GNUSTEP_LOCAL_LIBRARY ]; then
    APPIMAGE_GS_SYSTEM_LIBRARY="AppDir/$GNUSTEP_SYSTEM_LIBRARY"

    for LIBRARY_DIR in ${GS_LIBRARY_DIRS_TO_INCLUDE}; do
        SYSTEM_LIBRARY_DIR_PATH="$GNUSTEP_SYSTEM_LIBRARY/$LIBRARY_DIR"
        if [ -d $SYSTEM_LIBRARY_DIR_PATH ]; then
            mkdir -p $APPIMAGE_GS_SYSTEM_LIBRARY
            cp -rL $SYSTEM_LIBRARY_DIR_PATH $APPIMAGE_GS_SYSTEM_LIBRARY
            echo "Copied $SYSTEM_LIBRARY_DIR_PATH to $APPIMAGE_GS_SYSTEM_LIBRARY"
        fi
    done
fi

# COPY GNUSTEP TOOLS TO APPDIR
cd $APPIMAGE_BUILD_DIR

if [ -z "$GS_TOOLS_TO_INCLUDE" ]; then
    GS_TOOLS_TO_INCLUDE=$DEFAULT_GS_TOOLS_TO_INCLUDE
fi

echo "GNUstep tools to copy: $GS_TOOLS_TO_INCLUDE"

for TOOL in ${GS_TOOLS_TO_INCLUDE}; do
    TOOL_PATH=`find {$GNUSTEP_LOCAL_TOOLS,$GNUSTEP_SYSTEM_TOOLS,/usr/bin} -name "$TOOL" 2>/dev/null | head -n 1`

    if [ -z "$TOOL_PATH" ]; then
        echo "ERROR: Couldn't locate GNUstep tool: $TOOL"
        exit 1
    fi

    APPIMAGE_TOOL_PATH="AppDir/$TOOL_PATH"
    mkdir -p `dirname $APPIMAGE_TOOL_PATH`
    cp -L $TOOL_PATH $APPIMAGE_TOOL_PATH
    echo "Copied $TOOL_PATH to $APPIMAGE_TOOL_PATH"
    INTERPRETER="."`patchelf --print-interpreter $APPIMAGE_TOOL_PATH`
    patchelf --set-interpreter $INTERPRETER $APPIMAGE_TOOL_PATH
done

# SET UP APPIMAGE'S GNUSTEP.CONF FILE
cd $APPIMAGE_BUILD_DIR
APPIMAGE_GNUSTEP_CONFIG_FILE="AppDir/usr/lib/GNUstep/GNUstep.conf"
mkdir -p `dirname $APPIMAGE_GNUSTEP_CONFIG_FILE`
# config paths are relative to the location of the config file (AppDir/usr/lib/GNUstep); "../../.." points to AppDir
cat > $APPIMAGE_GNUSTEP_CONFIG_FILE <<EOF
GNUSTEP_USER_CONFIG_FILE=
GNUSTEP_USER_DEFAULTS_DIR=GNUstep/Defaults
GNUSTEP_SYSTEM_LIBRARIES=../../../${GNUSTEP_SYSTEM_LIBRARIES}
GNUSTEP_SYSTEM_LIBRARY=../../../${GNUSTEP_SYSTEM_LIBRARY}
GNUSTEP_SYSTEM_TOOLS=../../../${GNUSTEP_SYSTEM_TOOLS}
GNUSTEP_NETWORK_LIBRARIES=../../../${GNUSTEP_SYSTEM_LIBRARIES}
GNUSTEP_NETWORK_LIBRARY=../../../${GNUSTEP_SYSTEM_LIBRARY}
GNUSTEP_NETWORK_TOOLS=../../../${GNUSTEP_SYSTEM_TOOLS}
GNUSTEP_LOCAL_LIBRARIES=../../../${GNUSTEP_LOCAL_LIBRARIES}
GNUSTEP_LOCAL_LIBRARY=../../../${GNUSTEP_LOCAL_LIBRARY}
GNUSTEP_LOCAL_TOOLS=../../../${GNUSTEP_LOCAL_TOOLS}
GNUSTEP_USER_DIR_LIBRARIES=../../../${GNUSTEP_LOCAL_LIBRARIES}
GNUSTEP_USER_DIR_LIBRARY=GNUstep/Library
GNUSTEP_USER_DIR_TOOLS=../../../${GNUSTEP_LOCAL_TOOLS}
EOF
chmod g-w $APPIMAGE_GNUSTEP_CONFIG_FILE # GNUStep doesn't allow a group-writable config file

# GET GO-APPIMAGE TOOL
echo "Downloading go-appimage tool..."
cd $APPIMAGE_BUILD_DIR
ARCH=`uname -m`
wget -c https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases/expanded_assets/continuous -O - | grep "appimagetool-.*-${ARCH}.AppImage" | head -n 1 | cut -d '"' -f 2)
chmod +x appimagetool-*.AppImage

# DEPLOY APPDIR
echo "Deploying AppDir..."
cd $APPIMAGE_BUILD_DIR
./appimagetool-*.AppImage -s deploy $APPIMAGE_DESKTOP_FILE # Bundle EVERYTHING

# SET UP EXECUTABLE TO BE INVOKED DIRECTLY (INSTEAD OF THROUGH DYNAMIC LOADER)
#  Prepend a "." to the executable's interpreter path to make it a relative path
INTERPRETER="."`patchelf --print-interpreter "AppDir/usr/bin/$APP_NAME"`
patchelf --set-interpreter $INTERPRETER "AppDir/usr/bin/$APP_NAME"
#  Patch AppRun script:
#  - Set $GCONV_PATH to the correct path
sed -i -E 's|(^.*export GCONV_PATH=).*$|\1\$(find \$HERE -name gconv -type d)|' AppDir/AppRun
#  - Unset $LD_LIBRARY_PATH & set the current dir to $HERE before running binary (so the binary's relative interpreter-path works correctly)
sed -i -E '/^LD_LINUX=/a LD_LIBRARY_PATH=""\ncd \$HERE' AppDir/AppRun
#  - Remove the $LD_LINUX command from before $MAIN_BIN, so the binary executes directly
sed -i -E 's/(^.* exec )"\$\{LD_LINUX\}".*("\$\{MAIN_BIN\}".*$)/\1\2/' AppDir/AppRun

# BUILD APPIMAGE
echo "Building AppImage..."
cd $APPIMAGE_BUILD_DIR
VERSION=${APP_VERSION} ./appimagetool-*.AppImage ./AppDir # turn AppDir into AppImage
