Maelstrom: Added GitHub workflows

From d0d15508548a26d4739c76296b45a6223bb1063e Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 8 Mar 2026 12:05:58 -0700
Subject: [PATCH] Added GitHub workflows

---
 .github/workflows/main.yml        |  69 ++++++++++++++++++
 CMakeLists.txt                    | 113 +++++++++++++++++++++++-------
 cmake/CPackProjectConfig.cmake.in |  23 ++++++
 utils/files.c                     |  10 ++-
 4 files changed, 188 insertions(+), 27 deletions(-)
 create mode 100644 .github/workflows/main.yml
 create mode 100644 cmake/CPackProjectConfig.cmake.in

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..ad7caa2c
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,69 @@
+name: Build
+
+on: [push, pull_request]
+
+jobs:
+  Build:
+    name: ${{ matrix.platform.name }}
+    runs-on: ${{ matrix.platform.os }}
+
+    defaults:
+      run:
+        shell: ${{ matrix.platform.shell }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+        - { name: Windows (MSVC),    os: windows-latest, shell: sh,  msvc: 1, artifact: 'Maelstrom-windows',
+            cmake: '-GNinja -DCMAKE_POLICY_DEFAULT_CMP0141=NEW -DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=ProgramDatabase -DCMAKE_EXE_LINKER_FLAGS=-DEBUG -DCMAKE_SHARED_LINKER_FLAGS=-DEBUG' }
+        - { name: Linux,             os: ubuntu-latest,  shell: sh,  linux: 1, cmake: '-GNinja', artifact: 'Maelstrom-linux' }
+        - { name: macOS,             os: macos-latest,   shell: sh,  artifact: 'Maelstrom-macos',
+            cmake: '-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0' }
+
+    steps:
+    - uses: actions/checkout@v6
+      with:
+        submodules: ${{ 'recursive' }}
+    - uses: ilammy/msvc-dev-cmd@v1
+      if: ${{ matrix.platform.msvc }}
+      with:
+        arch: x64
+    - name: 'Install Linux dependencies'
+      if: ${{ matrix.platform.linux }}
+      run: |
+        sudo apt-get update -y
+        sudo apt-get install -y \
+          gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev libjack-dev libsndio-dev \
+          libusb-1.0-0-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev \
+          libxss-dev libwayland-dev libxtst-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
+          libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev
+    - name: Configure (CMake)
+      run: |
+        export CMAKE_CONFIGURATION_TYPES=Release
+        cmake -B build \
+          -DCMAKE_BUILD_TYPE=Release \
+          -DCMAKE_INSTALL_PREFIX=prefix_cmake \
+          -DCMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE=$PWD/build \
+          -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=$PWD/build \
+          ${{ matrix.platform.cmake }}
+    - name: Build (CMake)
+      id: build
+      run: cmake --build build/ --config Release --parallel --verbose
+    - name: Install (CMake)
+      if: ${{ always() && steps.build.outcome == 'success' }}
+      run: |
+        set -eu
+        rm -fr DESTDIR-cmake
+        cmake --install build/ --config Release
+        echo "Maelstrom=$(pwd)/prefix_cmake" >> $GITHUB_ENV
+        ( cd prefix_cmake; find . ) | LC_ALL=C sort -u
+    - name: Package (CPack)
+      if: ${{ always() && steps.build.outcome == 'success' }}
+      run: |
+        cmake --build build/ --target package
+    - name: Upload artifacts
+      uses: actions/upload-artifact@v6
+      with:
+        name: "${{ matrix.platform.artifact }}"
+        path: '${{ github.workspace }}/build/dist/Maelstrom*'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 38dbae82..b6ba107c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,13 +1,13 @@
 cmake_minimum_required(VERSION 3.0...4.0)
-project(Maelstrom C CXX)
 
-include(GNUInstallDirs)
+set(MAJOR_VERSION 4)
+set(MINOR_VERSION 0)
+set(MICRO_VERSION 0)
 
-if(WIN32)
-    set(GAME_INSTALLDIR ".")
-else()
-    set(GAME_INSTALLDIR "${CMAKE_INSTALL_PREFIX}/games/${PROJECT_NAME}")
-endif()
+project(Maelstrom
+    LANGUAGES C CXX
+    VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}"
+)
 
 # set the output directory for built objects.
 # This makes sure that the dynamic library goes into the build directory automatically.
@@ -18,6 +18,9 @@ add_subdirectory(maclib)
 add_subdirectory(external/SDL EXCLUDE_FROM_ALL)
 add_subdirectory(external/SDL_net EXCLUDE_FROM_ALL)
 
+SDL_DetectTargetCPUArchitectures(SDL_CPU_NAMES)
+SDL_DetectCMakePlatform()
+
 set(MAELSTROM_SOURCES
 	game/about.cpp
 	game/about.h
@@ -162,33 +165,93 @@ dep_option(STEAM "Build with Steam support" ON "WIN32 OR LINUX OR MACOS" OFF)
 
 if(STEAM)
     target_compile_definitions(Maelstrom PRIVATE ENABLE_STEAM)
-	target_include_directories(Maelstrom PRIVATE "${CMAKE_SOURCE_DIR}/external/SteamworksSDK/public")
-	if(WIN32)
-		target_link_directories(Maelstrom PRIVATE "${CMAKE_SOURCE_DIR}/external/SteamworksSDK/redistributable_bin/win64")
-		target_link_libraries(Maelstrom PRIVATE steam_api64)
-	elseif(LINUX)
-		target_link_directories(Maelstrom PRIVATE "${CMAKE_SOURCE_DIR}/external/SteamworksSDK/redistributable_bin/linux64")
-		target_link_libraries(Maelstrom PRIVATE steam_api)
-	elseif(MACOS)
-		target_link_directories(Maelstrom PRIVATE "${CMAKE_SOURCE_DIR}/external/SteamworksSDK/redistributable_bin/osx")
-		target_link_libraries(Maelstrom PRIVATE steam_api)
-	endif()
+    add_library(SteamworksSDK::steam_api SHARED IMPORTED)
+    set_property(TARGET SteamworksSDK::steam_api PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${PROJECT_SOURCE_DIR}/external/SteamworksSDK/public")
+    if(WIN32)
+        set_property(TARGET SteamworksSDK::steam_api PROPERTY IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/external/SteamworksSDK/redistributable_bin/win64/steam_api64.dll")
+        set_property(TARGET SteamworksSDK::steam_api PROPERTY IMPORTED_IMPLIB   "${PROJECT_SOURCE_DIR}/external/SteamworksSDK/redistributable_bin/win64/steam_api64.lib")
+    elseif(LINUX)
+        if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+            set_property(TARGET SteamworksSDK::steam_api PROPERTY IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/external/SteamworksSDK/redistributable_bin/linux32/libsteam_api.so")
+        else()
+            set_property(TARGET SteamworksSDK::steam_api PROPERTY IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/external/SteamworksSDK/redistributable_bin/linux64/libsteam_api.so")
+        endif()
+    elseif(MACOS)
+        set_property(TARGET SteamworksSDK::steam_api PROPERTY IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/external/SteamworksSDK/redistributable_bin/osx/libsteam_api.dylib")
+    endif()
+    target_link_libraries(Maelstrom PRIVATE SteamworksSDK::steam_api)
 endif()
 
 target_link_libraries(Maelstrom PRIVATE SDLmac)
 target_link_libraries(Maelstrom PRIVATE SDL3::SDL3)
 target_link_libraries(Maelstrom PRIVATE SDL3_net::SDL3_net)
 
-install(TARGETS Maelstrom DESTINATION "${GAME_INSTALLDIR}")
-install(DIRECTORY Data DESTINATION "${GAME_INSTALLDIR}")
+option(STANDALONE_INSTALL "Build Maelstrom installed into a single directory" TRUE)
+
+if(STANDALONE_INSTALL)
+    set(CMAKE_INSTALL_BINDIR ".")
+    set(CMAKE_INSTALL_LIBDIR ".")
+    set(CMAKE_INSTALL_DOCDIR "Docs")
+    set(GAME_INSTALL_DATADIR ".")
+    if(APPLE)
+        set_property(TARGET Maelstrom SDL3-shared SDL3_net-shared PROPERTY INSTALL_RPATH "@executable_path")
+    else()
+        set_property(TARGET Maelstrom SDL3-shared SDL3_net-shared PROPERTY INSTALL_RPATH "$ORIGIN")
+    endif()
+else()
+    include(GNUInstallDirs)
+    set(GAME_INSTALL_DATADIR "${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}")
+
+    target_compile_definitions(Maelstrom PRIVATE MAELSTROM_DATA=\"${CMAKE_INSTALL_PREFIX}/${GAME_INSTALL_DATADIR}/Data/\")
+endif()
+
+install(TARGETS Maelstrom SDL3-shared SDL3_net-shared LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" NAMELINK_SKIP RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+if(STEAM)
+    install(IMPORTED_RUNTIME_ARTIFACTS SteamworksSDK::steam_api)
+endif()
+install(DIRECTORY Data DESTINATION "${GAME_INSTALL_DATADIR}")
 
 file(GLOB docs "Docs/*.txt" "README*")
-install(FILES ${docs} "COPYING" DESTINATION "${GAME_INSTALLDIR}/Docs")
+install(FILES ${docs} "COPYING" TYPE DOC)
+
+#
+# Uninstall
+#
+configure_file(cmake/cmake_uninstall.cmake.in cmake_uninstall.cmake IMMEDIATE @ONLY)
 
-if(NOT TARGET uninstall)
-  configure_file(cmake/cmake_uninstall.cmake.in cmake_uninstall.cmake IMMEDIATE @ONLY)
+add_custom_target(uninstall
+  COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
 
-  add_custom_target(uninstall
-      COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
+#
+# Source package
+#
+find_package(Git QUIET)
+
+if(GIT_FOUND)
+    add_custom_target(package_sources
+      COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/dist
+      COMMAND ${GIT_EXECUTABLE} archive HEAD -o "${PROJECT_BINARY_DIR}/dist/${PROJECT_NAME}-${PROJECT_VERSION}.zip" --prefix "${PROJECT_NAME}-${PROJECT_VERSION}/"
+      COMMAND ${GIT_EXECUTABLE} archive HEAD -o "${PROJECT_BINARY_DIR}/dist/${PROJECT_NAME}-${PROJECT_VERSION}.tar.gz" --prefix "${PROJECT_NAME}-${PROJECT_VERSION}/"
+      WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+      COMMENT "Creating source archive..."
+      VERBATIM
+    )
+else()
+    message(WARNING "Git not found, 'package_sources' target will not be available.")
 endif()
 
+#
+# Binary package
+#
+
+if(MSVC)
+    set(CPACK_GENERATOR "ZIP")
+else()
+    set(CPACK_GENERATOR "TGZ")
+endif()
+configure_file(cmake/CPackProjectConfig.cmake.in CPackProjectConfig.cmake @ONLY)
+set(CPACK_PROJECT_CONFIG_FILE "${PROJECT_BINARY_DIR}/CPackProjectConfig.cmake")
+# CPACK_SOURCE_PACKAGE_FILE_NAME must end with "-src" (so we can block creating a source archive)
+set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-src")
+set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}/dist")
+include(CPack)
diff --git a/cmake/CPackProjectConfig.cmake.in b/cmake/CPackProjectConfig.cmake.in
new file mode 100644
index 00000000..8b8045df
--- /dev/null
+++ b/cmake/CPackProjectConfig.cmake.in
@@ -0,0 +1,23 @@
+if(CPACK_PACKAGE_FILE_NAME MATCHES ".*-src$")
+    message(FATAL_ERROR "Creating source archives is not supported.")
+endif()
+
+set(PROJECT_NAME "@PROJECT_NAME@")
+set(PROJECT_VERSION "@PROJECT_VERSION@")
+set(PROJECT_SOURCE_DIR "@PROJECT_SOURCE_DIR@")
+set(SDL_CMAKE_PLATFORM "@SDL_CMAKE_PLATFORM@")
+set(SDL_CPU_NAMES "@SDL_CPU_NAMES@")
+list(SORT SDL_CPU_NAMES)
+
+string(TOLOWER "${SDL_CMAKE_PLATFORM}" SDL_CMAKE_PLATFORM)
+string(TOLOWER "${SDL_CPU_NAMES}" SDL_CPU_NAMES)
+if(lower_sdl_cmake_platform STREQUAL lower_sdl_cpu_names)
+    set(SDL_CPU_NAMES_WITH_DASHES)
+endif()
+
+string(REPLACE ";" "-" SDL_CPU_NAMES_WITH_DASHES "${SDL_CPU_NAMES}")
+if(SDL_CPU_NAMES_WITH_DASHES)
+    set(SDL_CPU_NAMES_WITH_DASHES "-${SDL_CPU_NAMES_WITH_DASHES}")
+endif()
+
+set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-${SDL_CMAKE_PLATFORM}${SDL_CPU_NAMES_WITH_DASHES}")
diff --git a/utils/files.c b/utils/files.c
index fcb60395..1f057ff7 100644
--- a/utils/files.c
+++ b/utils/files.c
@@ -30,7 +30,6 @@ static char datapath[PATH_MAX];
 
 bool InitFilesystem(const char *org, const char *app)
 {
-	const char *basepath;
 	const char *env = SDL_getenv("MAELSTROM_DATA");
 
 	storage_org = org;
@@ -41,7 +40,12 @@ bool InitFilesystem(const char *org, const char *app)
 		return true;
 	}
 
-	basepath = SDL_GetBasePath();
+#ifdef MAELSTROM_DATA
+	SDL_strlcpy(datapath, MAELSTROM_DATA, sizeof(datapath));
+	return true;
+
+#else
+	const char *basepath = SDL_GetBasePath();
 	if (basepath) {
 		SDL_snprintf(datapath, sizeof(datapath), "%sData/", basepath);
 		if (SDL_GetPathInfo(datapath, NULL)) {
@@ -61,6 +65,8 @@ bool InitFilesystem(const char *org, const char *app)
 
 	SDL_strlcpy(datapath, "./", sizeof(datapath));
 	return true;
+
+#endif // MAELSTROM_DATA
 }
 
 SDL_IOStream *OpenRead(const char *file)