diff --git a/.travis.yml b/.travis.yml index 1b9efa0..61ea43c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,7 @@ deploy: - build/${PACKAGING_INSTALLER_NAME}-${TRAVIS_TAG}.exe - build/${PACKAGING_INSTALLER_NAME}-${TRAVIS_TAG}-macOS.zip - build/${PACKAGING_INSTALLER_NAME}-${TRAVIS_TAG}-winportable.zip + - build/${PACKAGING_INSTALLER_NAME}-${TRAVIS_TAG}.AppImage skip_cleanup: true on: tags: true diff --git a/README.md b/README.md index 0e9a510..d55cd7c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ This repository contains a bare development environment for creating a new mod/game on the [OpenRA](https://github.com/OpenRA/OpenRA) engine. -These scripts and support files wrap and automatically manage a copy of the OpenRA game engine and common files, and provide entrypoints to run development versions of your project and to generate platform-specific installers for your players. +These scripts and support files wrap and automatically manage a copy of the OpenRA game engine and common files during development, and generates Windows installers, macOS .app bundles, and Linux [AppImages](https://appimage.org/) for distribution. The key scripts in this SDK are: diff --git a/mod.config b/mod.config index b95ead9..f4c362d 100644 --- a/mod.config +++ b/mod.config @@ -41,6 +41,7 @@ TRAVIS_TEST_PACKAGING="False" # The prefix used for the installer filenames. # - Windows installers will be named as {PACKAGING_INSTALLER_NAME}-{TAG}.exe # - macOS installers will be named as {PACKAGING_INSTALLER_NAME}-{TAG}.zip +# - Linux .appimages will be named as {PACKAGING_INSTALLER_NAME}-${TAG}.AppImage PACKAGING_INSTALLER_NAME="ExampleMod" # The human-readable name for this project. @@ -53,6 +54,7 @@ PACKAGING_INSTALLER_NAME="ExampleMod" # - Windows start menu # - Windows desktop shortcut # - Windows "Programs and Features" list +# - Linux launcher shortcut PACKAGING_DISPLAY_NAME="Example Mod" # The URL for the project homepage. @@ -81,6 +83,9 @@ PACKAGING_WINDOWS_INSTALL_DIR_NAME="OpenRA Example Mod" # This should not contain spaces or special characters. PACKAGING_WINDOWS_REGISTRY_KEY="OpenRAExampleMod" +# The git tag to use for the AppImage dependencies. +PACKAGING_APPIMAGE_DEPENDENCIES_TAG="20180408" + ############################################################################## # Advanced Configuration # @@ -105,3 +110,9 @@ PACKAGING_OSX_LAUNCHER_SOURCE="https://github.com/OpenRA/OpenRALauncherOSX/relea # Temporary file name used when downloading the OpenRA macOS launcher files. PACKAGING_OSX_LAUNCHER_TEMP_ARCHIVE_NAME="launcher.zip" + +# The url to download the OpenRA AppImage dependencies. +PACKAGING_APPIMAGE_DEPENDENCIES_SOURCE="https://github.com/OpenRA/AppImageSupport/releases/download/${PACKAGING_APPIMAGE_DEPENDENCIES_TAG}/libs.tar.bz2" + +# Temporary file name used when downloading the OpenRA AppImage dependencies. +PACKAGING_APPIMAGE_DEPENDENCIES_TEMP_ARCHIVE_NAME="libs.tar.bz2" diff --git a/packaging/linux/buildpackage.sh b/packaging/linux/buildpackage.sh new file mode 100755 index 0000000..d4efa9a --- /dev/null +++ b/packaging/linux/buildpackage.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# OpenRA packaging script for Linux (AppImage) +set -e + +command -v make >/dev/null 2>&1 || { echo >&2 "The OpenRA mod template requires make."; exit 1; } +command -v python >/dev/null 2>&1 || { echo >&2 "The OpenRA mod template requires python."; exit 1; } +command -v tar >/dev/null 2>&1 || { echo >&2 "The OpenRA mod template requires tar."; exit 1; } +command -v curl >/dev/null 2>&1 || { echo >&2 "The OpenRA mod template requires curl."; exit 1; } + +if [ $# -eq "0" ]; then + echo "Usage: `basename $0` version [outputdir]" + exit 1 +fi + +PACKAGING_DIR=$(python -c "import os; print(os.path.dirname(os.path.realpath('$0')))") +TEMPLATE_ROOT="${PACKAGING_DIR}/../../" + +# shellcheck source=mod.config +. "${TEMPLATE_ROOT}/mod.config" + +if [ -f "${TEMPLATE_ROOT}/user.config" ]; then + # shellcheck source=user.config + . "${TEMPLATE_ROOT}/user.config" +fi + +if [ "${INCLUDE_DEFAULT_MODS}" = "True" ]; then + echo "Cannot generate installers while INCLUDE_DEFAULT_MODS is enabled." + echo "Make sure that this setting is disabled in both your mod.config and user.config." + exit 1 +fi + +TAG="$1" +if [ $# -eq "1" ]; then + OUTPUTDIR=$(python -c "import os; print(os.path.realpath('.'))") +else + OUTPUTDIR=$(python -c "import os; print(os.path.realpath('$2'))") +fi + +BUILTDIR="${PACKAGING_DIR}/${PACKAGING_INSTALLER_NAME}.appdir" + +# Set the working dir to the location of this script +cd "${PACKAGING_DIR}" + +pushd "${TEMPLATE_ROOT}" > /dev/null + +if [ ! -f "${ENGINE_DIRECTORY}/Makefile" ]; then + echo "Required engine files not found." + echo "Run \`make\` in the mod directory to fetch and build the required files, then try again."; + exit 1 +fi + +if [ ! -d "${OUTPUTDIR}" ]; then + echo "Output directory '${OUTPUTDIR}' does not exist."; + exit 1 +fi + +echo "Building core files" + +make version VERSION="${TAG}" + +pushd "${ENGINE_DIRECTORY}" > /dev/null +make linux-dependencies +make core SDK="-sdk:4.5" +make install-engine prefix="usr" DESTDIR="${BUILTDIR}/" +make install-common-mod-files prefix="usr" DESTDIR="${BUILTDIR}/" + +popd > /dev/null +popd > /dev/null + +# Add native libraries +echo "Downloading dependencies" +curl -s -L -o "${PACKAGING_APPIMAGE_DEPENDENCIES_TEMP_ARCHIVE_NAME}" -O "${PACKAGING_APPIMAGE_DEPENDENCIES_SOURCE}" || exit 3 +tar xf "${PACKAGING_APPIMAGE_DEPENDENCIES_TEMP_ARCHIVE_NAME}" + +curl -s -L -O https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage +chmod a+x appimagetool-x86_64.AppImage + +echo "Building AppImage" + +# Add mod files +cp -r "${TEMPLATE_ROOT}/mods/"* "${BUILTDIR}/usr/lib/openra/mods" + +install -Dm 0755 libSDL2.so "${BUILTDIR}/usr/lib/openra/" +install -Dm 0644 include/SDL2-CS.dll.config "${BUILTDIR}/usr/lib/openra/" +install -Dm 0755 libopenal.so "${BUILTDIR}/usr/lib/openra/" +install -Dm 0644 include/OpenAL-CS.dll.config "${BUILTDIR}/usr/lib/openra/" +install -Dm 0755 liblua.so "${BUILTDIR}/usr/lib/openra/" +install -Dm 0644 include/Eluant.dll.config "${BUILTDIR}/usr/lib/openra/" + +# Add launcher and icons +sed "s/{MODID}/${MOD_ID}/g" include/AppRun.in | sed "s/{MODNAME}/${PACKAGING_DISPLAY_NAME}/g" > AppRun.temp +install -m 0755 AppRun.temp "${BUILTDIR}/AppRun" + +sed "s/{MODID}/${MOD_ID}/g" include/mod.desktop.in | sed "s/{MODNAME}/${PACKAGING_DISPLAY_NAME}/g" | sed "s/{TAG}/${TAG}/g" > temp.desktop +install -Dm 0755 temp.desktop "${BUILTDIR}/usr/share/applications/openra-${MOD_ID}.desktop" +install -m 0755 temp.desktop "${BUILTDIR}/openra-${MOD_ID}.desktop" + +sed "s/{MODID}/${MOD_ID}/g" include/mod-mimeinfo.xml.in | sed "s/{TAG}/${TAG}/g" > temp.xml +install -Dm 0755 temp.xml "${BUILTDIR}/usr/share/mime/packages/openra-${MOD_ID}.xml" + +if [ -f "${PACKAGING_DIR}/mod_scalable.svg" ]; then + install -Dm644 "${PACKAGING_DIR}/mod_scalable.svg" "${BUILTDIR}/usr/share/icons/hicolor/scalable/apps/openra-${MOD_ID}.svg" +fi + +for i in 16x16 32x32 48x48 64x64 128x128 256x256 512x512 1024x1024; do + if [ -f "${PACKAGING_DIR}/mod_${i}.png" ]; then + install -Dm644 "${PACKAGING_DIR}/mod_${i}.png" "${BUILTDIR}/usr/share/icons/hicolor/${i}/apps/openra-${MOD_ID}.png" + install -m644 "${PACKAGING_DIR}/mod_${i}.png" "${BUILTDIR}/openra-${MOD_ID}.png" + fi +done + +install -d "${BUILTDIR}/usr/bin" + +sed "s/{MODID}/${MOD_ID}/g" include/mod.in | sed "s/{TAG}/${TAG}/g" | sed "s/{MODNAME}/${PACKAGING_DISPLAY_NAME}/g" | sed "s/{MODINSTALLERNAME}/${PACKAGING_INSTALLER_NAME}/g" | sed "s|{MODFAQURL}|${PACKAGING_FAQ_URL}|g" > openra-mod.temp +install -m 0755 openra-mod.temp "${BUILTDIR}/usr/bin/openra-${MOD_ID}" + +sed "s/{MODID}/${MOD_ID}/g" include/mod-server.in > openra-mod-server.temp +install -m 0755 openra-mod-server.temp "${BUILTDIR}/usr/bin/openra-${MOD_ID}-server" + +# travis-ci doesn't support mounting FUSE filesystems so extract and run the contents manually +./appimagetool-x86_64.AppImage --appimage-extract +ARCH=x86_64 ./squashfs-root/AppRun "${BUILTDIR}" "${OUTPUTDIR}/${PACKAGING_INSTALLER_NAME}-${TAG}.AppImage" + +# Clean up +rm -rf openra-mod.temp openra-mod-server.temp temp.desktop temp.xml AppRun.temp libSDL2.so libopenal.so liblua.so appimagetool-x86_64.AppImage squashfs-root "${PACKAGING_APPIMAGE_DEPENDENCIES_TEMP_ARCHIVE_NAME}" "${BUILTDIR}" diff --git a/packaging/linux/include/AppRun.in b/packaging/linux/include/AppRun.in new file mode 100755 index 0000000..778e706 --- /dev/null +++ b/packaging/linux/include/AppRun.in @@ -0,0 +1,36 @@ +#!/bin/sh + +# Make sure the user has a sufficiently recent version of mono on the PATH +MINIMUM_MONO_VERSION="4.2" + +make_version() { + echo "$1" | tr '.' '\n' | head -n 4 | xargs printf "%03d%03d%03d%03d"; +} + +mono_missing_or_old() { + command -v mono >/dev/null 2>&1 || return 0 + MONO_VERSION=$(mono --version | head -n1 | cut -d' ' -f5) + [ "$(make_version "${MONO_VERSION}")" -lt "$(make_version "${MINIMUM_MONO_VERSION}")" ] && return 0 + return 1 +} + +if mono_missing_or_old; then + if command -v zenity > /dev/null + then + zenity --no-wrap --error --title "{MODNAME}" --text "{MODNAME} requires Mono ${MINIMUM_MONO_VERSION} or greater.\nPlease install Mono using your system package manager." + else + printf "{MODNAME} requires Mono %s or greater.\nPlease install Mono using your system package manager.\n" "${MINIMUM_MONO_VERSION}" + fi + exit 1 +fi + +# Run the game or server +HERE="$(dirname "$(readlink -f "${0}")")" +export PATH="${HERE}"/usr/bin/:"${PATH}" +export XDG_DATA_DIRS="${HERE}"/usr/share/:"${XDG_DATA_DIRS}" + +if [ -n "$1" ] && [ "$1" = "--server" ]; then + exec "openra-{MODID}-server" "$@" +else + exec "openra-{MODID}" "$@" +fi diff --git a/packaging/linux/include/Eluant.dll.config b/packaging/linux/include/Eluant.dll.config new file mode 100644 index 0000000..561d8e9 --- /dev/null +++ b/packaging/linux/include/Eluant.dll.config @@ -0,0 +1,3 @@ + + + diff --git a/packaging/linux/include/OpenAL-CS.dll.config b/packaging/linux/include/OpenAL-CS.dll.config new file mode 100644 index 0000000..43cedc7 --- /dev/null +++ b/packaging/linux/include/OpenAL-CS.dll.config @@ -0,0 +1,4 @@ + + + + diff --git a/packaging/linux/include/SDL2-CS.dll.config b/packaging/linux/include/SDL2-CS.dll.config new file mode 100644 index 0000000..a2aa928 --- /dev/null +++ b/packaging/linux/include/SDL2-CS.dll.config @@ -0,0 +1,4 @@ + + + + diff --git a/packaging/linux/include/mod-mimeinfo.xml.in b/packaging/linux/include/mod-mimeinfo.xml.in new file mode 100644 index 0000000..a1e8c19 --- /dev/null +++ b/packaging/linux/include/mod-mimeinfo.xml.in @@ -0,0 +1,8 @@ + + + + + Join OpenRA server + + + diff --git a/packaging/linux/include/mod-server.in b/packaging/linux/include/mod-server.in new file mode 100755 index 0000000..a2798c2 --- /dev/null +++ b/packaging/linux/include/mod-server.in @@ -0,0 +1,6 @@ +#!/bin/sh + +HERE="$(dirname "$(readlink -f "${0}")")" +cd "${HERE}/../lib/openra" || exit 1 + +mono --debug OpenRA.Server.exe Game.Mod={MODID} "$@" diff --git a/packaging/linux/include/mod.desktop.in b/packaging/linux/include/mod.desktop.in new file mode 100644 index 0000000..05f6885 --- /dev/null +++ b/packaging/linux/include/mod.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=OpenRA - {MODNAME} +GenericName=Real Time Strategy Game +Icon=openra-{MODID} +Exec=openra-{MODID} %u +Terminal=false +Categories=Game;StrategyGame; +StartupWMClass=openra-{MODID} +MimeType=x-scheme-handler/openra-{MODID}-{TAG}; diff --git a/packaging/linux/include/mod.in b/packaging/linux/include/mod.in new file mode 100755 index 0000000..1e3785e --- /dev/null +++ b/packaging/linux/include/mod.in @@ -0,0 +1,46 @@ +#!/bin/sh + +LAUNCHER=$(readlink -f "${0}") +HERE="$(dirname "${LAUNCHER}")" +cd "${HERE}/../lib/openra" || exit 1 + +# APPIMAGE is an environment variable set by the runtime +# defining the absolute path to the .AppImage file +if [ ! -z "${APPIMAGE}" ]; then + LAUNCHER=${APPIMAGE} + + # appimaged doesn't update the mime cache database when registering AppImages. + # Run update-desktop-database ourselves if we detect that the desktop file has + # been installed but the handler is not yet present in the cache + if command -v update-desktop-database > /dev/null; then + APPIMAGEID=$(printf "file://%s" "${APPIMAGE}" | md5sum | cut -d' ' -f1) + LAUNCHER_NAME="appimagekit_${APPIMAGEID}-openra-{MODID}.desktop" + LAUNCHER_PATH="${HOME}/.local/share/applications/${LAUNCHER_NAME}" + MIMECACHE_PATH="${HOME}/.local/share/applications/mimeinfo.cache" + SCHEME="x-scheme-handler/openra-{MODID}-{TAG}" + if [ -f "${LAUNCHER_PATH}" ] && ! grep -qs "${SCHEME}=" "${MIMECACHE_PATH}"; then + update-desktop-database "${HOME}/.local/share/applications" + fi + fi +fi + +# Search for server connection +PROTOCOL_PREFIX="openra-{MODID}-{TAG}://" +JOIN_SERVER="" +if [ "${1#${PROTOCOL_PREFIX}}" != "${1}" ]; then + JOIN_SERVER="Launch.Connect=${1#${PROTOCOL_PREFIX}}" +fi + +# Run the game +export SDL_VIDEO_X11_WMCLASS="openra-{MODID}" +mono --debug OpenRA.Game.exe Game.Mod={MODID} Engine.LaunchPath="${LAUNCHER}" "${JOIN_SERVER}" "$@" + +# Show a crash dialog if something went wrong +if [ $? != 0 ] && [ $? != 1 ]; then + if command -v zenity > /dev/null; then + zenity --no-wrap --question --title "{MODNAME}" --text "{MODNAME} has encountered a fatal error.\nLog Files are available in ~/.openra." --ok-label "Quit" --cancel-label "View FAQ" || xdg-open "{MODFAQURL}" + else + printf "{MODNAME} has encountered a fatal error.\n -> Log Files are available in ~/.openra\n -> FAQ is available at {MODFAQURL}\n" + fi + exit 1 +fi diff --git a/packaging/linux/mod_128x128.png b/packaging/linux/mod_128x128.png new file mode 100644 index 0000000..ed1b458 Binary files /dev/null and b/packaging/linux/mod_128x128.png differ diff --git a/packaging/linux/mod_16x16.png b/packaging/linux/mod_16x16.png new file mode 100644 index 0000000..c31b0fc Binary files /dev/null and b/packaging/linux/mod_16x16.png differ diff --git a/packaging/linux/mod_256x256.png b/packaging/linux/mod_256x256.png new file mode 100644 index 0000000..e05a6f8 Binary files /dev/null and b/packaging/linux/mod_256x256.png differ diff --git a/packaging/linux/mod_32x32.png b/packaging/linux/mod_32x32.png new file mode 100644 index 0000000..155dafd Binary files /dev/null and b/packaging/linux/mod_32x32.png differ diff --git a/packaging/linux/mod_48x48.png b/packaging/linux/mod_48x48.png new file mode 100644 index 0000000..e5cc191 Binary files /dev/null and b/packaging/linux/mod_48x48.png differ diff --git a/packaging/linux/mod_64x64.png b/packaging/linux/mod_64x64.png new file mode 100644 index 0000000..d780c5a Binary files /dev/null and b/packaging/linux/mod_64x64.png differ diff --git a/packaging/package-all.sh b/packaging/package-all.sh index d667159..8183b92 100755 --- a/packaging/package-all.sh +++ b/packaging/package-all.sh @@ -32,4 +32,14 @@ if [ $? -ne 0 ]; then echo "macOS package build failed." fi +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "Linux AppImage packaging requires a Linux host." +else + echo "Building Linux AppImage package" + ${PACKAGING_DIR}/linux/buildpackage.sh "${TAG}" "${OUTPUTDIR}" + if [ $? -ne 0 ]; then + echo "Linux AppImage package build failed." + fi +fi + echo "Package build done."