From ac7fb75ea33ad39307b6490616cdb53e2268cc15 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 2 Dec 2025 21:18:24 +0100 Subject: [PATCH] Add self-extracting launcher for Windows --- .github/workflows/windows.yml | 44 +++++++++++---- CMakeLists.txt | 17 +++++- misc/meta.nsh.in | 6 ++ misc/sfx-launcher.nsi | 100 ++++++++++++++++++++++++++++++++++ util/buildbot/buildwin32.sh | 6 +- util/buildbot/buildwin64.sh | 8 ++- util/ci/common.sh | 17 +++++- 7 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 misc/meta.nsh.in create mode 100644 misc/sfx-launcher.nsi diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 92ce538d9c..ceec2ab8ad 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -33,40 +33,64 @@ on: jobs: mingw: - name: "MinGW cross-compiler (${{ matrix.bits }}-bit)" - runs-on: ubuntu-22.04 + name: "MinGW cross-compiler (${{ matrix.config.bits }}-bit)" + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - bits: [32, 64] + config: + - { bits: 32, nsis: false } + - { bits: 64, nsis: true } steps: - uses: actions/checkout@v6 - name: Install compiler run: | - sudo dpkg --add-architecture i386 - sudo apt-get update - sudo apt-get install -y --no-install-recommends gettext wine wine${{ matrix.bits }} + source ./util/ci/common.sh + install_mingw_deps '${{matrix.config.bits}}' '${{matrix.config.nsis}}' + if '${{matrix.config.nsis}}'; then + echo 'CMAKE_CXX_COMPILER_LAUNCHER=ccache' >>$GITHUB_ENV + fi sudo ./util/buildbot/download_toolchain.sh /usr - name: Build run: | EXISTING_MINETEST_DIR=$PWD \ - ./util/buildbot/buildwin${{ matrix.bits }}.sh B + ./util/buildbot/buildwin${{ matrix.config.bits }}.sh B + mkdir -p artifact + cp -v B/build/*.zip artifact/ # Check that the resulting binary can run (DLLs etc.) - name: Runtime test run: | dest=$(mktemp -d) - unzip -q -d "$dest" B/build/*.zip + unzip -q -d "$dest" artifact/*.zip cd "$dest"/luanti-*-win* wine bin/luanti.exe --version + - name: Build for NSIS + run: | + EXISTING_MINETEST_DIR=$PWD \ + ./util/buildbot/buildwin${{ matrix.config.bits }}.sh B -DRUN_IN_PLACE=0 + dest=$(mktemp -d) + unzip -q -d "$dest" B/build/*.zip + cd "$dest"/luanti-*-nsis + makensis -WX -V3 sfx-launcher.nsi + cp -v "$dest"/*.exe $GITHUB_WORKSPACE/artifact/ + if: ${{ matrix.config.nsis }} + - uses: actions/upload-artifact@v5 with: - name: "mingw${{ matrix.bits }}" - path: B/build/*.zip + name: "mingw${{ matrix.config.bits }}" + path: artifact/*.zip if-no-files-found: error + - uses: actions/upload-artifact@v5 + with: + name: "mingw${{ matrix.config.bits }}-nsis" + path: artifact/*.exe + if-no-files-found: error + if: ${{ matrix.config.nsis }} + msvc: name: VS 2022 ${{ matrix.config.arch }} runs-on: windows-2025 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d69a3995d..87a3b6826e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -301,7 +301,7 @@ add_subdirectory(lib/tiniergltf) # Be sure to add all relevant definitions above this add_subdirectory(src) -# CPack +# Package generation set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A free open-source voxel game engine with easy modding and game creation.") set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR}) @@ -327,6 +327,21 @@ if(WIN32) set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win32") endif() + if(NOT RUN_IN_PLACE) + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-nsis") + install(FILES "misc/luanti-icon.ico" DESTINATION ".") + install(FILES "misc/sfx-launcher.nsi" DESTINATION ".") + # normalize to 0/1 + set(DEVELOPMENT_BUILD01 0) + if(DEVELOPMENT_BUILD) + set(DEVELOPMENT_BUILD01 1) + endif() + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/misc/meta.nsh.in" + "${CMAKE_CURRENT_BINARY_DIR}/misc/meta.nsh" @ONLY ESCAPE_QUOTES) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/misc/meta.nsh" DESTINATION ".") + endif() + + # we just generate a ZIP file and someone has to run makensis manually set(CPACK_GENERATOR ZIP) elseif(APPLE) set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0) diff --git a/misc/meta.nsh.in b/misc/meta.nsh.in new file mode 100644 index 0000000000..e38fdb49e3 --- /dev/null +++ b/misc/meta.nsh.in @@ -0,0 +1,6 @@ +!define PROJECT_NAME "@PROJECT_NAME@" +!define PROJECT_NAME_C "@PROJECT_NAME_CAPITALIZED@" +!define VERSION_STRING "@VERSION_STRING@" +!define DEVELOPMENT_BUILD @DEVELOPMENT_BUILD01@ +!define INPATH "." +!define ICONPATH "./luanti-icon.ico" diff --git a/misc/sfx-launcher.nsi b/misc/sfx-launcher.nsi new file mode 100644 index 0000000000..45369c7e21 --- /dev/null +++ b/misc/sfx-launcher.nsi @@ -0,0 +1,100 @@ +!include "meta.nsh" +# useful docs: + +OutFile "..\${PROJECT_NAME_C}-${VERSION_STRING}.exe" +Name "${PROJECT_NAME_C}" +Icon "${ICONPATH}" +SetCompressor /SOLID lzma +SetDateSave off +RequestExecutionLevel user + +VIAddVersionKey "CompanyName" "${PROJECT_NAME_C} community" +VIAddVersionKey "FileDescription" "${PROJECT_NAME_C} self-extracting launcher" +VIAddVersionKey "FileVersion" "${VERSION_STRING}" +VIAddVersionKey "ProductName" "${PROJECT_NAME_C}" +VIAddVersionKey "InternalName" "${PROJECT_NAME}" +VIAddVersionKey "LegalCopyright" "(c) 2010-2025 Perttu Ahola (celeron55) and contributors" +# these are required, but don't take arbitrary strings +VIProductVersion "0.0.0.0" +VIFileVersion "0.0.0.0" + +!if ${DEVELOPMENT_BUILD} == 1 +# since the version string needs to be unique for the "extract once" logic to work, +# and dev builds may not have one we choose to just always extract +!define TARGET_DIR "$TEMP\${PROJECT_NAME}-dev" +!else +!if ${DEVELOPMENT_BUILD} == 0 +!define TARGET_DIR "$LOCALAPPDATA\${PROJECT_NAME}\${VERSION_STRING}" +# signals successful extraction +!define DUMMY_FILE "${TARGET_DIR}\.extracted" +!else +!error invalid value for DEVELOPMENT_BUILD +!endif +!endif +!define EXE_FILE "${TARGET_DIR}\bin\${PROJECT_NAME}.exe" + +!include "LogicLib.nsh" +!include "FileFunc.nsh" + +# including MUI.nsh would cause a warning, so we have to use numbers :( +# these are LCIDs (Language Code Identifiers) +LangString BannerText 1033 "Extracting, please wait..." +LangString BannerText 1031 "Extrahiere, bitte warten..." +LangString ErrorText 1033 "An error occurred!" +LangString ErrorText 1031 "Ein Fehler ist aufgetreten!" + +Var needExtract + +Function .onInit + SetSilent silent + StrCpy $needExtract 1 + +!if ${DEVELOPMENT_BUILD} == 0 + # the *.* checks if a directory exists + ${If} ${FileExists} "${EXE_FILE}" + ${AndIf} ${FileExists} "${TARGET_DIR}\builtin\*.*" + ${AndIf} ${FileExists} "${TARGET_DIR}\client\*.*" + ${AndIf} ${FileExists} "${TARGET_DIR}\textures\*.*" + ${AndIf} ${FileExists} "${DUMMY_FILE}" + StrCpy $needExtract 0 + ${EndIf} +!endif +FunctionEnd + +Section + ClearErrors + ${If} $needExtract == 1 + Banner::show /NOUNLOAD "$(BannerText)" + +!if ${DEVELOPMENT_BUILD} == 1 + RMDir /r "${TARGET_DIR}" +!endif + CreateDirectory "${TARGET_DIR}" + SetOutPath "${TARGET_DIR}" + File /r /x *.nsi /x *.nsh "${INPATH}\*.*" + + ${If} ${Errors} + MessageBox MB_ICONSTOP "$(ErrorText)" + Abort + ${EndIf} + +!if ${DEVELOPMENT_BUILD} == 0 + FileOpen $0 "${DUMMY_FILE}" w + FileClose $0 + SetFileAttributes "${DUMMY_FILE}" HIDDEN +!endif + + Banner::destroy + ${EndIf} + +!if ${DEVELOPMENT_BUILD} == 0 + # replace shortcut with last-launched version regardless of if we had to extract + # (last param is the description text) + CreateShortcut /NoWorkingDir "$DESKTOP\${PROJECT_NAME_C}.lnk" "${EXE_FILE}" \ + "" "" "" "" "" "${PROJECT_NAME_C} ${VERSION_STRING}" + CreateShortcut /NoWorkingDir "$SMPROGRAMS\${PROJECT_NAME_C}.lnk" "${EXE_FILE}" \ + "" "" "" "" "" "${PROJECT_NAME_C} ${VERSION_STRING}" +!endif + + Exec '"${EXE_FILE}"' +SectionEnd diff --git a/util/buildbot/buildwin32.sh b/util/buildbot/buildwin32.sh index 34767f7070..7b30917cd0 100755 --- a/util/buildbot/buildwin32.sh +++ b/util/buildbot/buildwin32.sh @@ -2,11 +2,12 @@ set -e topdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -if [ $# -ne 1 ]; then - echo "Usage: $0 " +if [ $# -lt 1 ]; then + echo "Usage: $0 [extra cmake args...]" exit 1 fi builddir=$1 +shift mkdir -p $builddir builddir="$( cd "$builddir" && pwd )" libdir=$builddir/libs @@ -63,6 +64,7 @@ cmake_args=( -DENABLE_LEVELDB=1 ) add_cmake_libs +cmake_args+=("$@") cmake -S $sourcedir -B build "${cmake_args[@]}" cmake --build build -j$(nproc) diff --git a/util/buildbot/buildwin64.sh b/util/buildbot/buildwin64.sh index c63a18901a..3f516280db 100755 --- a/util/buildbot/buildwin64.sh +++ b/util/buildbot/buildwin64.sh @@ -2,11 +2,12 @@ set -e topdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -if [ $# -ne 1 ]; then - echo "Usage: $0 " +if [ $# -lt 1 ]; then + echo "Usage: $0 [extra cmake args...]" exit 1 fi builddir=$1 +shift mkdir -p $builddir builddir="$( cd "$builddir" && pwd )" libdir=$builddir/libs @@ -44,7 +45,7 @@ download "$libhost/llvm/libjpeg-$libjpeg_version-win64.zip" download "$libhost/llvm/libpng-$libpng_version-win64.zip" download "$libhost/llvm/sdl2-$sdl2_version-win64.zip" -# Set source dir, downloading Minetest as needed +# Set source dir, downloading Luanti as needed get_sources # Build the thing @@ -63,6 +64,7 @@ cmake_args=( -DENABLE_LEVELDB=1 ) add_cmake_libs +cmake_args+=("$@") cmake -S $sourcedir -B build "${cmake_args[@]}" cmake --build build -j$(nproc) diff --git a/util/ci/common.sh b/util/ci/common.sh index 899982965d..5a62e3dc24 100644 --- a/util/ci/common.sh +++ b/util/ci/common.sh @@ -32,6 +32,19 @@ install_linux_deps() { fi } +# Linux -> win32 cross-compiling only +install_mingw_deps() { + local bits=$1 + local nsis=$2 + + local pkgs=(gettext wine wine$bits) + [[ "$nsis" == "true" ]] && pkgs+=(nsis ccache) + + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install -y --no-install-recommends "${pkgs[@]}" +} + # macOS build only install_macos_brew_deps() { # Uninstall the bundled cmake, it is outdated, and brew does not want to install the newest version with this one present since they are from different taps. @@ -50,8 +63,8 @@ install_macos_brew_deps() { } install_macos_precompiled_deps() { - osver=$1 - arch=$2 + local osver=$1 + local arch=$2 local pkgs=( cmake wget