Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 34 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Lander CI, shaped after Augmentinel and blast_radius.
#
# Every PR builds and tests on Windows and macOS, runs a smoke test, and
# uploads the game as a downloadable artifact (Lander-windows, Lander-macos).
# Every PR builds and tests on Windows and macOS, runs a smoke test, checks the
# game needs nothing installed, and uploads the Windows build as an artifact.
# Tag pushes (v*) do the same, sign the Windows exe when the eSigner secrets
# exist, and publish it to itch.io. macOS signing, notarisation and publishing
# run locally (scripts/build_release.sh). See the Releasing section of README.md.
Expand Down Expand Up @@ -30,9 +30,6 @@ concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

env:
SDL2_VERSION: 2.32.10

jobs:
# Emit the build matrix from the dispatch input.
setup:
Expand Down Expand Up @@ -76,23 +73,15 @@ jobs:
steps:
- uses: actions/checkout@v5

# The official SDL2 development package for MSVC (x64), with its CMake
# config; SDL2.dll ships beside lander.exe.
- name: Install SDL2 (Windows)
# Builds match what ships: SDL2 built from source and linked statically,
# with the static C runtime on Windows and a universal binary on macOS
- name: Configure (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$zip = Join-Path $env:RUNNER_TEMP "SDL2.zip"
Invoke-WebRequest "https://github.com/libsdl-org/SDL/releases/download/release-$env:SDL2_VERSION/SDL2-devel-$env:SDL2_VERSION-VC.zip" -OutFile $zip
Expand-Archive $zip -DestinationPath $env:RUNNER_TEMP -Force
"CMAKE_PREFIX_PATH=$env:RUNNER_TEMP\SDL2-$env:SDL2_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
run: cmake -B build -DLANDER_VENDOR_SDL2=ON

- name: Install SDL2 (macOS)
- name: Configure (macOS, universal)
if: runner.os == 'macOS'
run: brew install sdl2

- name: Configure
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DLANDER_VENDOR_SDL2=ON "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64"

- name: Build
run: cmake --build build --config Release -j
Expand Down Expand Up @@ -126,6 +115,30 @@ jobs:
path: ${{ runner.temp }}/smoke/smoke.png
if-no-files-found: ignore

# Players must not need anything installed (such as the Visual C++
# redistributable): lander.exe may only import DLLs that ship with Windows
- name: Check dependencies (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$dumpbin = & $vswhere -latest -find "VC\Tools\MSVC\**\bin\Hostx64\x64\dumpbin.exe" | Select-Object -First 1
$imports = & $dumpbin /dependents build\Release\lander.exe | Where-Object { $_ -match '^\s+\S+\.dll\s*$' } | ForEach-Object { $_.Trim() }
Write-Host "lander.exe imports: $($imports -join ', ')"
$bad = $imports | Where-Object { $_ -match '^(vcruntime|msvcp|ucrtbase|api-ms-win-crt|sdl)' }
if ($bad) { Write-Error "lander.exe depends on DLLs players won't have: $($bad -join ', ')"; exit 1 }

- name: Check dependencies (macOS)
if: runner.os == 'macOS'
run: |
lipo -archs build/lander
for a in arm64 x86_64; do
lipo -archs build/lander | tr ' ' '\n' | grep -qx "$a" || { echo "ERROR: missing the $a slice"; exit 1; }
done
echo "minos $(otool -l build/lander | awk '/minos/ { print $2; exit }')"
bad="$(otool -L -arch all build/lander | awk 'NR > 1 && !/architecture/ { print $1 }' | grep -vE '^(/System/|/usr/lib/)' || true)"
[ -z "$bad" ] || { echo "ERROR: lander links libraries players won't have:"; echo "$bad"; exit 1; }

# The .rc embeds the version block; fail if it is missing.
- name: Check version resource (Windows)
if: runner.os == 'Windows'
Expand Down Expand Up @@ -195,14 +208,14 @@ jobs:
Write-Host "Signature status: $($sig.Status); signer: $($sig.SignerCertificate.Subject)"
if ($sig.Status -ne 'Valid') { Write-Error "lander.exe signature is not Valid: $($sig.Status)"; exit 1 }

# lander.exe needs SDL2.dll and the sounds beside it.
# lander.exe is self-contained apart from the sounds beside it.
- name: Package (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$out = "dist\Lander-windows"
New-Item -ItemType Directory -Force -Path $out | Out-Null
foreach ($item in "lander.exe", "SDL2.dll", "sounds") {
foreach ($item in "lander.exe", "sounds") {
Copy-Item "build\Release\$item" $out -Recurse
}
Get-ChildItem $out -Recurse -File | ForEach-Object { Write-Host $_.FullName.Substring((Resolve-Path $out).Path.Length + 1) }
Expand Down
45 changes: 39 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
cmake_minimum_required(VERSION 3.16)
project(lander VERSION 1.2.0 LANGUAGES CXX)

# Release builds build SDL2 from source and link it statically, so the game
# needs nothing installed: a single exe on Windows, a self-contained app on
# macOS. Development builds can use an installed SDL2 (e.g. Homebrew's).
option(LANDER_VENDOR_SDL2 "Build SDL2 from source and link it statically" OFF)

# Self-contained macOS builds run on macOS 11 and later (this must be set
# before project() to take effect)
if(APPLE AND LANDER_VENDOR_SDL2 AND NOT CMAKE_OSX_DEPLOYMENT_TARGET)
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum macOS version")
endif()

project(lander VERSION 1.2.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
Expand All @@ -10,6 +22,29 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

# Link the C/C++ runtime statically, so players don't need the Visual C++
# redistributable
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")

if(LANDER_VENDOR_SDL2)
include(FetchContent)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
set(SDL_TEST OFF CACHE BOOL "" FORCE)
set(SDL_FORCE_STATIC_VCRT ON CACHE BOOL "" FORCE)
set(SDL2_DISABLE_INSTALL ON CACHE BOOL "" FORCE)
FetchContent_Declare(SDL2
URL https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-2.32.10.tar.gz
URL_HASH SHA256=5f5993c530f084535c65a6879e9b26ad441169b3e25d789d83287040a9ca5165
DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
# Before our warning flags, which are for our own code
FetchContent_MakeAvailable(SDL2)
set(LANDER_SDL2_LIBRARIES SDL2::SDL2main SDL2::SDL2)
else()
find_package(SDL2 REQUIRED)
set(LANDER_SDL2_LIBRARIES ${SDL2_LIBRARIES})
endif()

if(MSVC)
add_compile_options(/W3)
# M_PI in <cmath>, and portable C stdio without "unsafe" warnings
Expand All @@ -22,8 +57,6 @@ else()
add_compile_options(-fwrapv)
endif()

find_package(SDL2 REQUIRED)

# =============================================================================
# Game
# =============================================================================
Expand Down Expand Up @@ -51,7 +84,7 @@ add_library(lander_core STATIC
src/settings.cpp
)
target_include_directories(lander_core PUBLIC src ${SDL2_INCLUDE_DIRS})
target_link_libraries(lander_core PUBLIC ${SDL2_LIBRARIES})
target_link_libraries(lander_core PUBLIC ${LANDER_SDL2_LIBRARIES})

add_executable(lander src/main.cpp)
target_link_libraries(lander PRIVATE lander_core)
Expand All @@ -71,8 +104,8 @@ if(WIN32)
target_sources(lander PRIVATE ${CMAKE_BINARY_DIR}/generated/lander.rc)
endif()

if(MSVC)
# Windows: Copy SDL2.dll to output directory
if(MSVC AND NOT LANDER_VENDOR_SDL2)
# Windows with an installed SDL2: copy SDL2.dll to the output directory
add_custom_command(TARGET lander POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:SDL2::SDL2>
Expand Down
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,20 @@ ctest --output-on-failure
The version comes from `project(lander VERSION ...)` in `CMakeLists.txt`; the
release scripts stamp it into the app bundle and `lander.exe`.

**macOS** (`scripts/build_release.sh`): builds `dist/Lander.app`, signs it with
the Developer ID certificate (hardened runtime, timestamped), notarises it with
the `lineage-notary` notarytool profile, staples the ticket and zips it to
`dist/Lander-macOS.zip`. Override with `SIGN_IDENTITY` / `NOTARY_PROFILE`, or
use `SKIP_NOTARIZE=1` for a quick signed-only build. Without the certificate
it produces an ad-hoc signed `Lander-macOS-unsigned.zip`.
**Self-contained builds:** releases build SDL2 from source and link it
statically (`-DLANDER_VENDOR_SDL2=ON`), so players need nothing installed. On
Windows that is a single `lander.exe` with the C runtime linked in too; on
macOS a universal (Apple Silicon and Intel) app for macOS 11 or later.
Everyday builds can use an installed SDL2, such as Homebrew's.

**macOS** (`scripts/build_release.sh`): builds `dist/Lander.app` self-contained
and universal, checks it links only system libraries, signs it with the
Developer ID certificate (hardened runtime, timestamped), launches it once as a
smoke test, notarises it with the `lineage-notary` notarytool profile, staples
the ticket and zips it to `dist/Lander-macOS.zip`. Override with
`SIGN_IDENTITY` / `NOTARY_PROFILE`, or use `SKIP_NOTARIZE=1` for a quick
signed-only build. Without the certificate it produces an ad-hoc signed
`Lander-macOS-unsigned.zip`.

**Windows** (GitHub Actions, `.github/workflows/ci.yml`): every PR builds,
tests and smoke-tests on Windows and macOS. Pushing a `vX.Y.Z` tag (matching
Expand Down
1 change: 1 addition & 0 deletions docs/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
| - | Authentic controls: exact port of mouse polar conversion, original steering damping per physics step, original friction | 3c15e3b |
| - | Authentic rocks and gravity: rocks drop in front of the ship as in the original; gravity rises with score | 1346b21 |
| - | v1.2.0 release: GitHub Actions CI (build, test, smoke test, Windows signing and itch.io publishing) | 7a4c13d |
| - | v1.2.1 release: self-contained builds (static SDL2 and C runtime; universal macOS 11+ app) | bbe3f22 |

## Summary

Expand Down
52 changes: 17 additions & 35 deletions scripts/build_release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,32 @@ rm -rf "$ICONSET_DIR"

echo "==> Build release binary (fresh configure, so a stale cache can't leak in)"
rm -rf "$BUILD_DIR/CMakeCache.txt" "$BUILD_DIR/CMakeFiles"
cmake -S "$PROJECT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release
# SDL2 is built from source and linked statically, as a universal binary for
# Apple Silicon and Intel Macs running macOS 11 or later
cmake -S "$PROJECT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release \
-DLANDER_VENDOR_SDL2=ON "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64"
cmake --build "$BUILD_DIR" --target lander -j "$(sysctl -n hw.ncpu)"

echo "==> Check the binary is self-contained"
for arch in arm64 x86_64; do
lipo -archs "$BUILD_DIR/lander" | tr ' ' '\n' | grep -qx "$arch" \
|| { echo "ERROR: lander is missing the $arch slice"; exit 1; }
done
NON_SYSTEM="$(otool -L -arch all "$BUILD_DIR/lander" | awk 'NR > 1 && !/architecture/ { print $1 }' \
| grep -vE '^(/System/|/usr/lib/)' || true)"
[ -z "$NON_SYSTEM" ] || { echo "ERROR: lander links libraries players won't have:"; echo "$NON_SYSTEM"; exit 1; }

echo "==> Assemble app bundle"
rm -rf "$APP_BUNDLE"
mkdir -p "$APP_BUNDLE/Contents/MacOS" "$APP_BUNDLE/Contents/Resources" "$APP_BUNDLE/Contents/Frameworks"
mkdir -p "$APP_BUNDLE/Contents/MacOS" "$APP_BUNDLE/Contents/Resources"
BINARY="$APP_BUNDLE/Contents/MacOS/lander"
cp "$BUILD_DIR/lander" "$BINARY"
cp "$DIST_DIR/lander.icns" "$APP_BUNDLE/Contents/Resources/"
cp -R "$PROJECT_DIR/sounds" "$APP_BUNDLE/Contents/Resources/"
printf 'APPL????' > "$APP_BUNDLE/Contents/PkgInfo"

# Info.plist takes its version from CMakeLists.txt, and its minimum macOS
# version from the binary (Homebrew SDL2 targets the build machine's macOS)
# version from the binary
PLIST="$APP_BUNDLE/Contents/Info.plist"
cp "$PROJECT_DIR/macos/Info.plist" "$PLIST"
MIN_MACOS="$(otool -l "$BINARY" | awk '/minos/ { print $2; exit }')"
Expand All @@ -84,43 +96,13 @@ MIN_MACOS="$(otool -l "$BINARY" | awk '/minos/ { print $2; exit }')"
-c "Set :LSMinimumSystemVersion $MIN_MACOS" \
"$PLIST"

echo "==> Bundle SDL2"
SDL2_PATH="$(otool -L "$BINARY" | grep -o '/.*libSDL2.*\.dylib' | head -1)"
if [ -z "$SDL2_PATH" ]; then
echo "ERROR: Could not find SDL2 library path"
exit 1
fi
SDL2_DYLIB="$APP_BUNDLE/Contents/Frameworks/$(basename "$SDL2_PATH")"
cp "$SDL2_PATH" "$SDL2_DYLIB"
chmod u+w "$SDL2_DYLIB"
install_name_tool -change "$SDL2_PATH" "@executable_path/../Frameworks/$(basename "$SDL2_PATH")" "$BINARY"
install_name_tool -id "@executable_path/../Frameworks/$(basename "$SDL2_PATH")" "$SDL2_DYLIB"

# Homebrew's sdl2 is now sdl2-compat, which implements SDL2 on top of SDL3 and
# loads SDL3 when it starts, looking first for libSDL3.dylib beside itself.
# Without it the app shows an error dialog and never starts.
SDL3_DYLIB=""
if grep -qa "sdl2-compat" "$SDL2_DYLIB"; then
echo "==> Bundle SDL3 (SDL2 is sdl2-compat)"
SDL3_PATH="$(brew --prefix sdl3)/lib/libSDL3.0.dylib"
[ -f "$SDL3_PATH" ] || { echo "ERROR: sdl2-compat needs SDL3, not found at $SDL3_PATH"; exit 1; }
SDL3_DYLIB="$APP_BUNDLE/Contents/Frameworks/libSDL3.dylib"
cp "$SDL3_PATH" "$SDL3_DYLIB"
chmod u+w "$SDL3_DYLIB"
install_name_tool -id "@loader_path/libSDL3.dylib" "$SDL3_DYLIB"
fi
FRAMEWORK_DYLIBS=("$SDL2_DYLIB")
[ -n "$SDL3_DYLIB" ] && FRAMEWORK_DYLIBS+=("$SDL3_DYLIB")

# Inner code first, then the bundle. Hardened runtime and a secure timestamp
# are required for notarisation; no entitlements are needed.
# Hardened runtime and a secure timestamp are required for notarisation; no
# entitlements are needed
if [ "$UNSIGNED" = "1" ]; then
echo "==> Codesign (ad-hoc, UNSIGNED build)"
codesign --force --sign - "${FRAMEWORK_DYLIBS[@]}"
codesign --force --sign - "$APP_BUNDLE"
else
echo "==> Codesign ($SIGN_IDENTITY)"
codesign --force --options runtime --timestamp --sign "$SIGN_IDENTITY" "${FRAMEWORK_DYLIBS[@]}"
codesign --force --options runtime --timestamp --sign "$SIGN_IDENTITY" "$APP_BUNDLE"
fi
codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE"
Expand Down
Loading