From 788708ed8cb953e26fbd3c088316ef1b6f80783a Mon Sep 17 00:00:00 2001 From: Ralph Sennhauser Date: Wed, 25 Jun 2025 10:06:38 +0200 Subject: [PATCH] Add AppImage support Add a Dockerfile having all needed tooling. Using a container also means those are the libs that will end up being used in the AppImage. Then add a script for building pyrogenesis either form git or from extracted source tarballs and for assembling the AppImage and finally creating the AppImage. Add building the AppImage to the "bundles" pipeline. Also add a wrapper script to use podman to drive the whole process for convenience for local builds. Ref: #7577 Signed-off-by: Ralph Sennhauser --- .gitignore | 2 + .../dockerfiles/debian-12-appimage.Dockerfile | 40 ++++++++ build/jenkins/pipelines/bundles.Jenkinsfile | 53 +++++++++- source/tools/LICENSE.txt | 1 + source/tools/dist/build-appimage-podman.sh | 7 ++ source/tools/dist/build-appimage.sh | 99 +++++++++++++++++++ 6 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 build/jenkins/dockerfiles/debian-12-appimage.Dockerfile create mode 100755 source/tools/dist/build-appimage-podman.sh create mode 100755 source/tools/dist/build-appimage.sh diff --git a/.gitignore b/.gitignore index d9243f1d09..4b10a3065c 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ libraries/source/spirv-reflect/* !libraries/source/**/patches/ # Tools +appimage-build/ archives/ build/bin/ source/tools/spirv/rules.*.json @@ -68,6 +69,7 @@ binaries/data/mods/public/gui/credits/texts/translators.json *.pot # Packaged mods and tarballs +*.AppImage *.pyromod *.zip *.tar diff --git a/build/jenkins/dockerfiles/debian-12-appimage.Dockerfile b/build/jenkins/dockerfiles/debian-12-appimage.Dockerfile new file mode 100644 index 0000000000..201adc2d7c --- /dev/null +++ b/build/jenkins/dockerfiles/debian-12-appimage.Dockerfile @@ -0,0 +1,40 @@ +FROM debian-12 + +# minisign only needed for signing in pipeline +RUN apt-get -qqy update \ + && apt-get upgrade -qqy \ + && apt-get install -qqy --no-install-recommends \ + cimg-dev \ + desktop-file-utils \ + file \ + git \ + libgcrypt20-dev \ + libgpgme-dev \ + libjpeg-dev \ + minisign \ + patchelf \ + squashfs-tools \ + wget \ + && apt-get clean + +RUN \ + git clone --depth 1 --branch 1-alpha-20250213-2 https://github.com/linuxdeploy/linuxdeploy --recurse-submodules && \ + cd linuxdeploy && cp src/core/copyright/copyright.h src/core && \ + cmake -B build -S . \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DBUILD_TESTING=OFF \ + -DUSE_CCACHE=OFF \ + && cmake --build build --target install && \ + cd .. && \ + rm -rf linuxdeploy + +RUN \ + git clone --depth 1 --branch continuous https://github.com/AppImage/appimagetool.git && \ + cd appimagetool && \ + cmake -B build -S . \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local \ + && cmake --build build --target install && \ + cd .. && \ + rm -rf appimagetool diff --git a/build/jenkins/pipelines/bundles.Jenkinsfile b/build/jenkins/pipelines/bundles.Jenkinsfile index fb4eb91c69..357ac2b108 100644 --- a/build/jenkins/pipelines/bundles.Jenkinsfile +++ b/build/jenkins/pipelines/bundles.Jenkinsfile @@ -140,24 +140,67 @@ pipeline { stage('Create Windows Installer & Tarballs') { steps { sh "BUNDLE_VERSION=${params.BUNDLE_VERSION} DO_GZIP=${params.DO_GZIP} source/tools/dist/build-unix-win32.sh" + stash(name: 'unix-sources', includes: '*.tar.gz') + } + } + + stage('Create AppImage') { + stages { + stage('Docker Setup') { + agent { + node { + label 'LinuxAgent' + customWorkspace 'workspace/appimage' + } + } + steps { + checkout scm + sh 'git clean -dxf' + sh 'docker build -t debian-12 -f build/jenkins/dockerfiles/debian-12.Dockerfile .' + } + } + + stage('Build AppImage') { + agent { + dockerfile { + label 'LinuxAgent' + customWorkspace 'workspace/appimage' + dir 'build/jenkins/dockerfiles' + filename 'debian-12-appimage.Dockerfile' + // Prevent Jenkins from running commands with the UID of the host's jenkins user + // https://stackoverflow.com/a/42822143 + args '-u root' + } + } + + steps { + unstash('unix-sources') + untar(dir: 'appimage-build', file: "0ad-${params.BUNDLE_VERSION}-unix-build.tar.gz", keepPermissions: true) + untar(dir: 'appimage-build', file: "0ad-${params.BUNDLE_VERSION}-unix-data.tar.gz", keepPermissions: true) + + sh "source/tools/dist/build-appimage.sh --version ${params.BUNDLE_VERSION} --root appimage-build/0ad-${params.BUNDLE_VERSION}" + stash(name: 'appimage', includes: '*AppImage') + } + } } } stage('Generate Signatures and Checksums') { steps { + unstash('appimage') withCredentials([sshUserPrivateKey(credentialsId: 'minisign-releases-key', keyFileVariable: 'MINISIGN_KEY', passphraseVariable: 'MINISIGN_PASS')]) { - sh 'echo ${MINISIGN_PASS} | minisign -s ${MINISIGN_KEY} -Sm *.{dmg,exe,tar.gz,tar.xz}' + sh 'echo ${MINISIGN_PASS} | minisign -s ${MINISIGN_KEY} -Sm *.{AppImage,dmg,exe,tar.gz,tar.xz}' } - sh 'for file in *.{dmg,exe,tar.gz,tar.xz}; do md5sum "${file}" > "${file}".md5sum; done' - sh 'for file in *.{dmg,exe,tar.gz,tar.xz}; do sha1sum "${file}" > "${file}".sha1sum; done' - sh 'for file in *.{dmg,exe,tar.gz,tar.xz}; do sha256sum "${file}" > "${file}".sha256sum; done' + sh 'for file in *.{AppImage,dmg,exe,tar.gz,tar.xz}; do md5sum "${file}" > "${file}".md5sum; done' + sh 'for file in *.{AppImage,dmg,exe,tar.gz,tar.xz}; do sha1sum "${file}" > "${file}".sha1sum; done' + sh 'for file in *.{AppImage,dmg,exe,tar.gz,tar.xz}; do sha256sum "${file}" > "${file}".sha256sum; done' } } } post { success { - archiveArtifacts '*.dmg,*.exe,*.tar.gz,*.tar.xz,*.minisig,*.md5sum,*.sha1sum,*.sha256sum' + archiveArtifacts '*AppImage,*.dmg,*.exe,*.tar.gz,*.tar.xz,*.minisig,*.md5sum,*.sha1sum,*.sha256sum' } cleanup { sh 'svn revert -R .' diff --git a/source/tools/LICENSE.txt b/source/tools/LICENSE.txt index 6aa382dd4b..fe663aa199 100644 --- a/source/tools/LICENSE.txt +++ b/source/tools/LICENSE.txt @@ -15,6 +15,7 @@ in particular, let us know and we can try to clarify it. MIT dist + GPL-2.0-or-later MIT zlib/libpng (FileAssociation.nsh) diff --git a/source/tools/dist/build-appimage-podman.sh b/source/tools/dist/build-appimage-podman.sh new file mode 100755 index 0000000000..30ea567726 --- /dev/null +++ b/source/tools/dist/build-appimage-podman.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +podman build -t 0ad-debian-12 -f build/jenkins/dockerfiles/debian-12.Dockerfile +podman build -t 0ad-debian-12-appimage --build-context debian-12=container-image://0ad-debian-12 -f build/jenkins/dockerfiles/debian-12-appimage.Dockerfile + +podman run --mount=type=bind,source=.,destination=/mnt --workdir /mnt 0ad-debian-12-appimage source/tools/dist/build-appimage.sh "$@" diff --git a/source/tools/dist/build-appimage.sh b/source/tools/dist/build-appimage.sh new file mode 100755 index 0000000000..b886a33527 --- /dev/null +++ b/source/tools/dist/build-appimage.sh @@ -0,0 +1,99 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +# shellcheck disable=SC2120 +die() +{ + [ $# -ne 0 ] && echo ERROR: "$*" + exit 1 +} + +doins() +{ + mkdir -p "$2" + cp -a "$1" "$2" || die +} + +JOBS=${JOBS#-j} +njobs=${JOBS:-1} +arch=$(uname -m) +version=9999 +while [ "$#" -gt 0 ]; do + case "$1" in + --jobs) + njobs=$2 + shift + ;; + --root) + root=$2 + shift + ;; + --skip-prepacking) + skip_prepacking="yes" + ;; + --version) + version=$2 + shift + ;; + *) + printf "Unknown option: %s\n\n" "$1" + exit 1 + ;; + esac + shift +done + +TOP_SRC_DIR=$(realpath .) +APPDIR="${TOP_SRC_DIR}/appimage-build/AppDir" +APPIMAGE="${TOP_SRC_DIR}/0ad-${version}-${arch}.AppImage" +ROOT="${TOP_SRC_DIR}" +if [ -n "${root}" ]; then + ROOT=$(realpath "${root}") +fi + +cd "${ROOT}" || die + +./libraries/build-source-libs.sh "-j${njobs}" || die +./build/workspaces/update-workspaces.sh || die +make -C build/workspaces/gcc "-j${njobs}" || die +if [ -z "${root}" ] && [ -z "${skip_prepacking}" ]; then + ./source/tools/i18n/get-nightly-translations.sh || die + ./source/tools/spirv/get-nightly-shaders.sh || die + # TODO: Fails at check for root user currently when generating mods archives + #./source/tools/dist/build-archives.sh +fi + +rm -rf "${APPDIR}" "${APPIMAGE}" + +doins binaries/system/pyrogenesis "${APPDIR}/usr/bin" +ln -rs "${APPDIR}/usr/bin/pyrogenesis" "${APPDIR}/usr/bin/0ad" || die +for lib in \ + libmozjs*-release.so \ + libnvcore.so \ + libnvimage.so \ + libnvmath.so \ + libnvtt.so; do + patchelf --set-rpath "${lib}:${ROOT}/binaries/system" "${APPDIR}/usr/bin/pyrogenesis" || die +done + +# dlopen lib +doins binaries/system/libCollada.so "${APPDIR}/usr/lib" + +doins binaries/data/config/default.cfg "${APPDIR}/usr/data/config" +doins binaries/data/l10n "${APPDIR}/usr/data" +doins binaries/data/tools "${APPDIR}/usr/data" + +doins binaries/data/mods/mod "${APPDIR}/usr/data/mods" +doins binaries/data/mods/public "${APPDIR}/usr/data/mods" + +doins build/resources/0ad.appdata.xml "${APPDIR}/usr/share/metainfo" +doins build/resources/0ad.png "${APPDIR}/usr/share/pixmaps" + +linuxdeploy \ + --appdir "${APPDIR}" \ + --executable "${APPDIR}/usr/bin/pyrogenesis" \ + --desktop-file "${ROOT}/build/resources/0ad.desktop" \ + --icon-file "${ROOT}/build/resources/0ad.png" \ + --icon-filename 0ad || die + +appimagetool --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 20 "${APPDIR}" "${APPIMAGE}" || die