diff --git a/CMakeLists.txt b/CMakeLists.txt index bc89f251e..7d7fee79a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,9 +200,13 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_BINAR if("${SRB2_SDL2_EXE_NAME}" STREQUAL "") list(APPEND EXE_NAME_PARTS "blankart") - if(NOT "${SRB2_GIT_BRANCH}" STREQUAL "master") + if(NOT "${SRB2_GIT_BRANCH}" STREQUAL "master" AND NOT SRB2_CONFIG_COMPILEAPPIMAGE) list(APPEND EXE_NAME_PARTS ${SRB2_GIT_BRANCH}) endif() + + if(SRB2_CONFIG_COMPILEAPPIMAGE AND "${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + list(APPEND EXE_NAME_PARTS "appimagebuild") + endif() else() list(APPEND EXE_NAME_PARTS ${SRB2_SDL2_EXE_NAME}) endif() diff --git a/appimage_build.sh b/appimage_build.sh new file mode 100755 index 000000000..5349f513e --- /dev/null +++ b/appimage_build.sh @@ -0,0 +1,338 @@ +#!/bin/bash +set -e # Enable auto-exit for debugging :)) + +# Make Linux AppImage program data +# https://docs.appimage.org/reference/appdir.html + +# PWD: {repo_root}/build +# See deployer.sh for usage + +# Terminal stuff +RESET="$(tput sgr0)" #"\e[0m" # Resets terminal colors + +# Colors for various information types +FAILURE="$(tput setaf 1)" #"\e[91m" +SUCCESS="$(tput setaf 2)" #"\e[92m" +NOTICE="$(tput setaf 3)" #"\e[93m" +VERBOSE="$(tput setaf 4)" #"\e[94m" + +verbosity=0 # By default don't print anything unless it's *ultra* important. +quiet=0 # Also don't print anything if they supplied the quiet argument + +# URL from which to download appimagetool if there isn't one available in your $PATH +# at time of writing only aarch64, armhf, i686, and x86_64 versions are available via this method +# in those cases, and honestly even outside of those cases, it's better to just put a working one in your $PATH yourself.. +url="https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(uname -m).AppImage" + +# A whitelist of libraries to include into BlanKart's AppImage if BlanKart is dynamically linked. +# please don't add a newline at the end of this or i will cry :( +libraryWhitelist=" +libopenal +libgme +libopenmpt +libpng +libpng16 +libFLAC +libmpg123 +libmp3lame +libogg +libvorbis +libopus +libsndfile +libvorbisenc +libjson +libreadline +libwrap +libtinfo +libtirpc" + +# these kinda look like macros don't they +function print { (( "$quiet" == 0 )) && echo "$@" || :; } # echo if not quiet +function coloredText { printf "$1"; print "${@:2}"; printf "$RESET"; } # use print to inherit quiet +function success { coloredText "$SUCCESS" "$@"; } # use coloredText to inherit quiet +function important { coloredText "$NOTICE" "$@"; } # use coloredText to inherit quiet +function verboseOnly { (( "$verbosity" >= $1 )) && (coloredText "$VERBOSE" "${@:2}") || :; } # use coloredText to inherit quiet + +# Parses an argument with a single dash +function parseSingleDashArg() { + while read -n 1 char; do + if [[ "$char" == '-' || "$char" == '' ]]; then + : + elif [[ "$char" == 'h' ]]; then + helppassed=1 + elif [[ "$char" == 'q' ]]; then + quiet=1 + elif [[ "$char" == 'v' ]]; then + verbosity="$((verbosity + 1))" + elif [[ "$char" == 'd' ]]; then + dry=1 + elif [[ "$char" == 's' ]]; then + small=1 + else + echo "$0: invalid option -- '$1'" + echo "Try '$0 --help' for more information." + return 1 + fi + done <<<"$1" +} + +# Simple help argument checker. +currentarg=1; # Which argument is --help... + +# For $arg in the argument array... +for arg in "$@"; do + # Is it a help argument? + if [[ "$arg" == "--help" ]]; then + helppassed=1; # Help is passed, set $helppassed! + set -- "${@:1:$currentarg-1}" "${@:$currentarg+1}" # Remove argument from list + elif [[ "$arg" == "--quiet" ]]; then + quiet=1 + set -- "${@:1:$currentarg-1}" "${@:$currentarg+1}" + elif [[ "$arg" == "--verbose" ]]; then + verbosity="$((verbosity + 1))" # Increase verbosity! + set -- "${@:1:$currentarg-1}" "${@:$currentarg+1}" + elif [[ "$arg" == "--dry" ]]; then + dry=1; # Show parameters instead of using them. + set -- "${@:1:$currentarg-1}" "${@:$currentarg+1}" + elif [[ "$arg" == "--small" ]]; then + small=1; # Don't copy assets to save on bandwidth. + set -- "${@:1:$currentarg-1}" "${@:$currentarg+1}" + elif [[ ! "$arg" == *'--'* && "$arg" == *'-'* ]]; then + parseSingleDashArg "$arg" + + if [[ "$?" == 0 ]]; then + set -- "${@:1:$currentarg-1}" "${@:$currentarg+1}" # Remove argument from list + fi + elif [[ $arg == *'--'* ]]; then + print "$0: invalid option -- '$1'" + print "Try '$0 --help' for more information." + return 1 + else + currentarg="$((currentarg+1))" # Increment current argument number otherwise... + fi +done + +important "BlanKart AppImage Packager v1.0." +important "Modified by Team BlanKart." +important "Original for SRB2 my mazmazz and Golden." + +# Get root directory from the full path of this script. +__ROOT_DIR="$3" + +if [[ -z "$3" ]]; then + # finds out where this script is and goes to that directory and goes up one for the root + + scriptdir="$(dirname "${BASH_SOURCE[0]}")" + scriptbase="$(basename "${BASH_SOURCE[0]}")" + + __ROOT_DIR="$(cd "$scriptdir" && pwd -P)" +fi + +# If it starts with '.', put the current working directory behind it so things work. +if [[ "$__ROOT_DIR" == '.' || "$__ROOT_DIR" == '..' ]]; then + __ROOT_DIR="$PWD/$__ROOT_DIR" +fi + +# Set up defaults +__PROGRAM_NAME="${PROGRAM_NAME:-"BlanKart"}" +__PROGRAM_DESCRIPTION="${PROGRAM_DESCRIPTION:-"BlanKart"}" +__PROGRAM_FILENAME="${PROGRAM_FILENAME:-"blankart_appimagebuild"}" +__PROGRAM_ASSETS="${PROGRAM_ASSETS:-"$__ROOT_DIR/assets/installer"}" + +__BUILD_DIR="${1:-"$__ROOT_DIR/build/bin"}" +__OUTPUT_FILENAME="${2:-"$__PROGRAM_FILENAME.AppImage"}" + +__APPIMAGETOOL="${4:-"appimagetool"}" + +if (! command -v "$__APPIMAGETOOL" &> /dev/null); then + __APPIMAGETOOL= +fi + +# Stuff that prints and exits. + +# Prints setup text using method given by arguments. +# Example: printSetup "print" # print "Setup for AppImage... +function printSetup { + $@ "Setup for AppImage in BUILD_DIR: '$__BUILD_DIR'..." + $@ "With the OUTPUT_FILENAME: '$__OUTPUT_FILENAME'" + $@ "And BlanKart Repository Root ROOT_DIR: '$__ROOT_DIR'" + $@ "Using APPIMAGETOOL: '$([ -z "$__APPIMAGETOOL" ] && echo '(download)' || echo $__APPIMAGETOOL)'" + $@ "" + $@ "This should be run from a Makefile or from the directory of the build. If it is not, then change to that directory or specify that directory as an argument." + $@ "If ROOT_DIR isn't BlanKart's repository root, specify that as an argument too." + $@ "Make must have built the program before you run this script." + $@ "" + $@ "Make sure these Environment Variables are correct." + $@ "If not, then enter: export PROGRAM_VARIABLE=value" + $@ "PROGRAM_NAME: $__PROGRAM_NAME" + $@ "PROGRAM_DESCRIPTION: $__PROGRAM_DESCRIPTION" + $@ "PROGRAM_FILENAME: $__PROGRAM_FILENAME" + $@ "PROGRAM_ASSETS: $__PROGRAM_ASSETS" +} + +if [[ "$helppassed" ]]; then + print "Usage: $(basename "$0") [OPTION]... [BUILD_DIR] [OUTPUT_NAME] [ROOT_DIR] [APPIMAGETOOL]" + print "Packages a BlanKart binary into an AppImage." + print "OPTIONs may be included anywhere within the arguments." + print "" + print "Arguments:" + print " -h, --help display this help and exit." + print " -q, --quiet don't display any text." + print " -v, --verbose make commands more verbose, will always print." + print " -s, --small don't copy BlanKart assets to save on bandwidth." + print " parameters regardless of --dry." + print " -d, --dry print parameters and exit." + print "" + if (( "$verbosity" > 0 )) || [[ "$dry" ]]; then + printSetup "print" + else + print "This should be run from a Makefile or from the directory of the build." + print "Make must have built the program before you run this script." + fi + exit; +fi + +if (( "$verbosity" > 0 )) || [[ "$dry" ]]; then + printSetup "verboseOnly" "0" + verboseOnly 0 "" + + if [[ "$dry" ]]; then + set -n # Enable debug mode and verbose for dry run. + fi +fi + +# End stuff that prints and exits. + +if (( "$verbosity" >= 4 )); then + set -v # Really verbose? Print *everything*! +fi + +# Define AppDir structure +mkdir -p "$__BUILD_DIR/AppDir/usr/bin" +mkdir -p "$__BUILD_DIR/AppDir/lib" +mkdir -p "$__BUILD_DIR/AppDir/usr/share/applications" +mkdir -p "$__BUILD_DIR/AppDir/usr/share/icons/hicolor/256x256/apps" + +# Copy program data +if [[ ! $small ]]; then + verboseOnly 1 "Packaging program assets..." + cp -r "$__PROGRAM_ASSETS"/* "$__BUILD_DIR/AppDir/usr/bin/" + cp -r "$__BUILD_DIR/$__PROGRAM_FILENAME" "$__BUILD_DIR/AppDir/usr/bin" +else + verboseOnly 1 "Skipping program asset packing, --small or -s passed..." +fi + +verboseOnly 1 "Packaging BlanKart..." +verboseOnly 1 "Assuming executable name $__PROGRAM_FILENAME" +cp -r "$__BUILD_DIR/$__PROGRAM_FILENAME" "$__BUILD_DIR/AppDir/usr/bin" + +# Copy required dependencies, but only if the program is dynamically linked. +verboseOnly 1 "Testing if this build is dynamically linked..." + +set +e # Disable auto-exit for ldd. + +srb2Libraries="$(ldd "$__BUILD_DIR/$__PROGRAM_FILENAME")" +exitcode="$?" + +set -e # Enable it again! + +if (( $exitcode == 0 )); then + verboseOnly 1 "This build *is* dynamically linked! Packaging dependencies." + + # read the dynamic libraries of blankart, trim off beginning whitespace, and get the third field seperated by spaces + # this gives us a newline-seperated list of libraries with their full paths. + srb2Libraries="$(echo "$srb2Libraries" | cut -f 2 | cut -d ' ' -f 3)" + + # remove any extraneous newlines around the string + libraryWhitelist="$(echo $libraryWhitelist | head -c -1 | tr ' ' '\n')" + + # replace e.g. 'abc' with '(/abc\b)', and replace newlines with '|'. + # this converts it into a grep regular expression to check for any whitelisted library names. + libraryWhitelist="$(printf "%s" "$libraryWhitelist" | sed 's|.*|(/\0\\b)|' | tr '\n' '|')" + + # grab only the entries that match the regex + __LDD_LIST="$(printf "%s" "$srb2Libraries" | grep -E "$libraryWhitelist")" + + # use echo to convert the newline-seperated list into an argument list + __LDD_LIST="$(echo $__LDD_LIST)" + + # read into an array because i can't be bothered iterating the lines of a string today + # there's so many other things in this file that'd need to be adjusted to be POSIX-compliant anyway + IFS=' ' read -r -a paths <<< "$__LDD_LIST" + + for path in "${paths[@]}"; do + if [ -f "$path" ]; then + verboseOnly 2 "Packaging dependency $(basename "$path")..." + cp "$path" $__BUILD_DIR/AppDir/lib/ + else + verboseOnly 2 "Dependency $(basename "$path") not found" + fi + done +else + verboseOnly 1 "This BlanKart build is statically linked, skipping dependency packing." +fi + +cd "$__BUILD_DIR/AppDir" + +# Copy icons +verboseOnly 1 "Packaging resources..." +cp "$__ROOT_DIR/srb2.png" "./usr/share/icons/hicolor/256x256/apps/$__PROGRAM_FILENAME.png" +ln -sf "./usr/share/icons/hicolor/256x256/apps/$__PROGRAM_FILENAME.png" "./.DirIcon" +ln -sf "./usr/share/icons/hicolor/256x256/apps/$__PROGRAM_FILENAME.png" "./$__PROGRAM_FILENAME.png" + +# Make desktop descriptor +cat > "./usr/share/applications/$__PROGRAM_FILENAME.desktop" < ./AppRun +echo -e 'HERE="$(dirname "$(readlink -f "${0}")")"' >> ./AppRun +echo -e 'SRB2WADDIR=$HERE/usr/bin LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HERE/lib exec $HERE/usr/bin/'"$__PROGRAM_FILENAME"' "$@"' >> ./AppRun +chmod +x ./AppRun + +cd .. # now in $__BUILD_DIR + +# Package AppImage +if [ -z "$__APPIMAGETOOL" ]; then + verboseOnly 1 "No valid appimagetool path given or detected, downloading appimagetool..." + + # Print notice for internet connection. + important "If the command hangs here, check your internet connection, or download appimagetool and add it to your \$PATH and try compiling again." + + (( "$verbosity" >= 3 )) && wget "$url" || wget -q "$url" + + APPIMAGETOOL="./$(basename "$url")" + chmod a+x "$APPIMAGETOOL" +else + verboseOnly 1 "appimagetool path given or detected, no download required." + APPIMAGETOOL="$__APPIMAGETOOL" +fi + +verboseOnly 1 "Packing AppImage $__OUTPUT_FILENAME" + +if (( verbosity >= 3 )); then + "$APPIMAGETOOL" ./AppDir "$__OUTPUT_FILENAME" +elif (( verbosity >= 2 )); then + "$APPIMAGETOOL" ./AppDir "$__OUTPUT_FILENAME" >/dev/null +else + "$APPIMAGETOOL" ./AppDir "$__OUTPUT_FILENAME" >/dev/null 2>&1 +fi + +success "AppImage ready!" +verboseOnly 1 "Cleaning up files..." +rm -r ./AppDir + +if [ -z "$__APPIMAGETOOL" ]; then + rm "$APPIMAGETOOL" +fi + +cd "$__ROOT_DIR" # snap back to reality diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d4e256c7c..729c3a4d2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -176,6 +176,19 @@ endif() set(SRB2_CONFIG_STD23 OFF CACHE BOOL "Compiles the game under c23 and cxx23 instead c11 and cxx17.") +set(SRB2_CONFIG_COMPILEAPPIMAGE OFF CACHE BOOL + "Compiles the game then attempts turning it into an appimage.") + +if(SRB2_CONFIG_COMPILEAPPIMAGE AND "${CMAKE_SYSTEM_NAME}" MATCHES "Linux") +message("-- Generating Appimage at build time.") +add_custom_command( + TARGET SRB2SDL2 + POST_BUILD + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin/ + COMMAND ${CMAKE_SOURCE_DIR}/appimage_build.sh -s +) +endif() + if(SRB2_CONFIG_STD23) message("-- Compiling game with C23 and CXX23.") target_compile_features(SRB2SDL2 PRIVATE c_std_23 cxx_std_23)