305 lines
13 KiB
Bash
Executable File
305 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# test-sdk.sh <sdk-root> <platform> [expected-version] [android-abi]
|
|
#
|
|
# Verify a freshly-built target-platform SDK is usable by consumers, before it is
|
|
# zipped and published to a GitHub Release. Runs on the same host as the SDK:
|
|
# win32 -> bin/eve.exe (Windows runner)
|
|
# linux -> bin/eve (Linux runner)
|
|
# macosx-> bin/eve (macOS runner)
|
|
# android -> packaged APK assembled from the installed SDK
|
|
# ios -> installed device app bundle validated as an arm64 Mach-O
|
|
#
|
|
# [expected-version] (e.g. "0.1.0") makes the version checks exact: the
|
|
# share/eve/VERSION marker must equal it and `eve -v` must contain it.
|
|
#
|
|
# Hard gate (fails CI on error):
|
|
# 1. SDK layout + markers (bin runtime, include/eve headers, lib, cmake config,
|
|
# platform template, share/eve/TARGET_PLATFORM == expected platform).
|
|
# 2. Host runtime executes: `eve -v` prints a version.
|
|
# 3. `eve zip` compresses a sample game into a .eve archive.
|
|
# 4. `eve package --sdk` assembles a runnable game folder containing the runtime
|
|
# + game.eve (and, on Windows, the runtime DLLs).
|
|
#
|
|
# Best-effort (WARN only, never blocks the release): try to run the desktop packaged game
|
|
# from memory and check for the game's startup marker. Window/Vulkan init depends
|
|
# on a display / software ICD, so failures there are reported, not fatal.
|
|
set -euo pipefail
|
|
|
|
SDK="${1:?usage: test-sdk.sh <sdk-root> <platform> [expected-version]}"
|
|
# find_package(EVEngine) does not resolve a relative EVEngine_DIR; normalize
|
|
# the SDK root to an absolute path so the consumer configure is reproducible.
|
|
case "$SDK" in
|
|
/*) ;;
|
|
*) SDK="$(cd "$(dirname "$SDK")" && pwd)/$(basename "$SDK")" ;;
|
|
esac
|
|
PLAT="${2:?usage: test-sdk.sh <sdk-root> <platform> [expected-version]}"
|
|
EXPECTED="${3:-}"
|
|
ANDROID_PACKAGE_ABI="${4:-arm64-v8a}"
|
|
|
|
case "$PLAT" in
|
|
win32) RUNTIME="eve.exe" ;;
|
|
linux|macosx) RUNTIME="eve" ;;
|
|
android|ios) RUNTIME="" ;; # verified by target-specific gates below
|
|
*) echo "SKIP: no host runtime to test for platform '$PLAT'"; exit 0 ;;
|
|
esac
|
|
|
|
fail() { echo "ERROR: $*" >&2; exit 1; }
|
|
|
|
WORK="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
# --- Android: assemble an APK from the SDK packaging template ----------------
|
|
# Hard gate: the template + SDK libs + a minimal game must produce an APK.
|
|
if [ "$PLAT" = "android" ]; then
|
|
if [ -z "${ANDROID_SDK:-}" ] || [ -z "${JAVA_HOME:-}" ]; then
|
|
fail "android APK smoke needs ANDROID_SDK + JAVA_HOME in the environment"
|
|
fi
|
|
[ -f "$SDK/share/eve/TARGET_PLATFORM" ] || fail "missing share/eve/TARGET_PLATFORM"
|
|
[ "$(cat "$SDK/share/eve/TARGET_PLATFORM")" = "android" ] || fail "TARGET_PLATFORM != android"
|
|
VERSION_FILE="$(cat "$SDK/share/eve/VERSION")"
|
|
if [ -n "$EXPECTED" ] && [ "$VERSION_FILE" != "$EXPECTED" ]; then
|
|
fail "share/eve/VERSION '$VERSION_FILE' != expected '$EXPECTED'"
|
|
fi
|
|
TEMPLATE="$SDK/platform/apk"
|
|
[ -f "$TEMPLATE/gradlew" ] || [ -f "$TEMPLATE/gradlew.bat" ] || fail "android SDK missing gradle template"
|
|
# The packaged game must launch the game host when the user taps the icon.
|
|
# EVTestActivity is the unit-test host and is started explicitly (am start);
|
|
# if it leaks back into the launcher slot the game never starts.
|
|
MANIFEST="$TEMPLATE/app/src/main/AndroidManifest.xml"
|
|
if ! sed -n '/android:name="\.EVEngineActivity"/,/<\/activity>/p' "$MANIFEST" \
|
|
| grep -q 'android.intent.action.MAIN'; then
|
|
fail "android template launcher must be EVEngineActivity (MAIN intent missing)"
|
|
fi
|
|
if sed -n '/android:name="\.EVTestActivity"/,/<\/activity>/p' "$MANIFEST" \
|
|
| grep -q 'android.intent.action.MAIN'; then
|
|
fail "android template launcher must be EVEngineActivity (EVTestActivity has MAIN)"
|
|
fi
|
|
|
|
JNI="$TEMPLATE/app/src/main/jniLibs/$ANDROID_PACKAGE_ABI"
|
|
mkdir -p "$JNI"
|
|
[ -f "$SDK/lib/libmain.so" ] || fail "missing lib/libmain.so"
|
|
cp "$SDK/lib/libmain.so" "$JNI/"
|
|
for lib in libSDL2.so libc++_shared.so; do
|
|
[ -f "$SDK/lib/$lib" ] || fail "missing lib/$lib"
|
|
cp "$SDK/lib/$lib" "$JNI/"
|
|
done
|
|
[ ! -f "$SDK/lib/libhidapi.so" ] || cp "$SDK/lib/libhidapi.so" "$JNI/"
|
|
|
|
GAME="$TEMPLATE/app/src/main/assets/game"
|
|
mkdir -p "$GAME"
|
|
cat > "$GAME/config.nut" <<'EOF'
|
|
config = { width=320 height=240 title="sdk-test" debug=false hotReload=false };
|
|
EOF
|
|
cat > "$GAME/main.nut" <<'EOF'
|
|
eve_init = function() { print("SDK_TEST_GAME_OK\n"); };
|
|
eve_update = function(dt) {};
|
|
EOF
|
|
printf 'sdk.dir=%s\n' "$ANDROID_SDK" > "$TEMPLATE/local.properties"
|
|
|
|
echo "== [test-sdk] assembling APK from $TEMPLATE =="
|
|
if ! ( cd "$TEMPLATE" && bash gradlew :app:assembleDebug \
|
|
-PevengineAbi="$ANDROID_PACKAGE_ABI" --no-daemon --console=plain ) \
|
|
>"$WORK/apk-build.log" 2>&1; then
|
|
tail -n 40 "$WORK/apk-build.log"
|
|
fail "gradle assembleDebug failed"
|
|
fi
|
|
APK="$TEMPLATE/app/build/outputs/apk/debug/app-debug.apk"
|
|
[ -f "$APK" ] || fail "assembleDebug did not produce app-debug.apk"
|
|
echo "OK: android APK assembled: $(basename "$APK") ($(du -h "$APK" | cut -f1))"
|
|
echo "== [test-sdk] PASS (android) =="
|
|
exit 0
|
|
fi
|
|
|
|
# --- iOS: validate the installed device SDK app bundle -----------------------
|
|
# The release SDK contains an iphoneos app and cannot run in a simulator. The
|
|
# separate ios-sim workflow job is the runtime gate; this is the artifact gate
|
|
# that catches a missing executable, wrong architecture, malformed bundle, or
|
|
# version/target marker before the SDK is zipped.
|
|
if [ "$PLAT" = "ios" ]; then
|
|
[ -f "$SDK/share/eve/TARGET_PLATFORM" ] || fail "missing share/eve/TARGET_PLATFORM"
|
|
[ "$(cat "$SDK/share/eve/TARGET_PLATFORM")" = "ios" ] || fail "TARGET_PLATFORM != ios"
|
|
[ -f "$SDK/share/eve/VERSION" ] || fail "missing share/eve/VERSION"
|
|
VERSION_FILE="$(cat "$SDK/share/eve/VERSION")"
|
|
if [ -n "$EXPECTED" ] && [ "$VERSION_FILE" != "$EXPECTED" ]; then
|
|
fail "share/eve/VERSION '$VERSION_FILE' != expected '$EXPECTED'"
|
|
fi
|
|
|
|
APP="$SDK/bin/eve.app"
|
|
PLIST="$APP/Info.plist"
|
|
[ -d "$APP" ] || fail "ios SDK missing bin/eve.app"
|
|
[ -f "$PLIST" ] || fail "ios SDK missing eve.app/Info.plist"
|
|
command -v /usr/libexec/PlistBuddy >/dev/null 2>&1 || fail "PlistBuddy unavailable"
|
|
EXECUTABLE="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$PLIST")"
|
|
[ -n "$EXECUTABLE" ] || fail "CFBundleExecutable is empty"
|
|
BINARY="$APP/$EXECUTABLE"
|
|
[ -f "$BINARY" ] || fail "ios SDK missing app executable '$EXECUTABLE'"
|
|
file "$BINARY" | grep -q 'Mach-O' || fail "ios app executable is not Mach-O"
|
|
lipo -archs "$BINARY" | tr ' ' '\n' | grep -qx arm64 || fail "ios app executable has no arm64 slice"
|
|
[ -d "$SDK/include/eve" ] || fail "ios SDK missing include/eve"
|
|
[ -d "$SDK/platform" ] || fail "ios SDK missing platform template"
|
|
echo "OK: ios device SDK bundle is complete (executable=$EXECUTABLE, arch=arm64)"
|
|
echo "== [test-sdk] PASS (ios) =="
|
|
exit 0
|
|
fi
|
|
|
|
echo "== [test-sdk] platform=$PLAT sdk=$SDK runtime=$RUNTIME =="
|
|
|
|
# --- 1. SDK layout + markers ---------------------------------------------------
|
|
[ -x "$SDK/bin/$RUNTIME" ] || fail "missing runtime bin/$RUNTIME"
|
|
[ -d "$SDK/include/eve" ] || fail "missing include/eve"
|
|
[ "$(find "$SDK/include/eve" -name '*.h' | wc -l)" -gt 0 ] || fail "no headers under include/eve"
|
|
[ -f "$SDK/include/zeroerr/assert.h" ] || fail "missing public dependency include/zeroerr/assert.h"
|
|
case "$PLAT" in
|
|
win32|android)
|
|
# Windows ships eve.lib for plugins; Android ships libmain.so + packaging .so.
|
|
[ -d "$SDK/lib" ] || fail "missing lib/"
|
|
;;
|
|
# linux/macosx have no engine import libs: plugins are built by the consumer.
|
|
esac
|
|
[ -f "$SDK/cmake/EVEngineConfig.cmake" ] || fail "missing cmake/EVEngineConfig.cmake"
|
|
[ -f "$SDK/cmake/EVEnginePlugin.cmake" ] || fail "missing cmake/EVEnginePlugin.cmake"
|
|
[ -d "$SDK/platform" ] || fail "missing platform/"
|
|
[ -f "$SDK/share/eve/TARGET_PLATFORM" ] || fail "missing share/eve/TARGET_PLATFORM"
|
|
[ -f "$SDK/share/eve/VERSION" ] || fail "missing share/eve/VERSION"
|
|
[ -f "$SDK/share/eve/examples/basic/config.nut" ] || fail "missing share/eve/examples/basic/config.nut"
|
|
[ -f "$SDK/share/eve/examples/basic/main.nut" ] || fail "missing share/eve/examples/basic/main.nut"
|
|
TP="$(cat "$SDK/share/eve/TARGET_PLATFORM")"
|
|
[ "$TP" = "$PLAT" ] || fail "TARGET_PLATFORM '$TP' != expected '$PLAT'"
|
|
echo "OK: SDK layout + markers"
|
|
|
|
VERSION_FILE="$(cat "$SDK/share/eve/VERSION")"
|
|
[ -n "$VERSION_FILE" ] || fail "share/eve/VERSION is empty"
|
|
if [ -n "$EXPECTED" ]; then
|
|
[ "$VERSION_FILE" = "$EXPECTED" ] || \
|
|
fail "share/eve/VERSION '$VERSION_FILE' != expected '$EXPECTED'"
|
|
fi
|
|
echo "OK: SDK version marker -> $VERSION_FILE"
|
|
|
|
# --- 2. Host runtime runs ------------------------------------------------------
|
|
VER="$("$SDK/bin/$RUNTIME" -v 2>&1)" || true
|
|
echo " eve -v -> ${VER:-<empty>}"
|
|
if [ -n "$EXPECTED" ]; then
|
|
echo "$VER" | grep -qF "$EXPECTED" || \
|
|
fail "eve -v did not contain expected version '$EXPECTED' (output: $VER)"
|
|
else
|
|
echo "$VER" | grep -qE '[0-9]+(\.[0-9]+)+' || fail "eve -v did not print a version (output: $VER)"
|
|
fi
|
|
echo "OK: host runtime executes"
|
|
|
|
# --- 3 + 4. zip + package a sample game ----------------------------------------
|
|
GAME="$WORK/sample"
|
|
mkdir -p "$GAME/assets"
|
|
cat > "$GAME/config.nut" <<'EOF'
|
|
config = { width=320 height=240 title="sdk-test" debug=false hotReload=false };
|
|
EOF
|
|
cat > "$GAME/main.nut" <<'EOF'
|
|
eve_init = function() { print("SDK_TEST_GAME_OK\n"); };
|
|
eve_update = function(dt) {};
|
|
EOF
|
|
echo "asset-data" > "$GAME/assets/data.txt"
|
|
|
|
"$SDK/bin/$RUNTIME" zip "$GAME" >/dev/null 2>&1 || fail "eve zip failed"
|
|
[ -f "$GAME.eve" ] || fail "eve zip did not produce $GAME.eve"
|
|
echo "OK: eve zip -> $(basename "$GAME.eve")"
|
|
|
|
PKG="$WORK/sample-pkg"
|
|
"$SDK/bin/$RUNTIME" package "$GAME" -o "$PKG" --sdk "$SDK" >/dev/null 2>&1 || fail "eve package failed"
|
|
[ -f "$PKG/$RUNTIME" ] || fail "package missing runtime $RUNTIME"
|
|
[ -f "$PKG/game.eve" ] || fail "package missing game.eve"
|
|
if [ "$PLAT" = "win32" ]; then
|
|
[ "$(find "$PKG" -maxdepth 1 -name '*.dll' | wc -l)" -gt 0 ] || fail "win32 package has no runtime DLLs"
|
|
fi
|
|
echo "OK: eve package produced runnable game folder:"
|
|
ls -1 "$PKG" | sed 's/^/ /'
|
|
|
|
# --- 5. Native C++ plugin compiles against the SDK -----------------------------
|
|
# Hard gate: find_package(EVEngine) + add_eve_plugin() is the SDK's primary
|
|
# consumer path; a plugin must configure, compile and link on the host.
|
|
PLUGIN="$WORK/plugin"
|
|
mkdir -p "$PLUGIN"
|
|
cat > "$PLUGIN/CMakeLists.txt" <<'EOF'
|
|
cmake_minimum_required(VERSION 3.21)
|
|
project(eve_sdk_plugin LANGUAGES CXX)
|
|
find_package(EVEngine REQUIRED)
|
|
add_eve_plugin(eve_sdk_plugin SOURCES plugin.cpp)
|
|
EOF
|
|
cat > "$PLUGIN/plugin.cpp" <<'EOF'
|
|
#include "common/Export.h"
|
|
#include "common/Module.h"
|
|
|
|
#include <simplesquirrel/simplesquirrel.hpp>
|
|
|
|
namespace eve::sdktest {
|
|
class SdkTestPlugin : public Module {
|
|
public:
|
|
Module_REG(SdkTestPlugin);
|
|
SdkTestPlugin() = default;
|
|
~SdkTestPlugin() override = default;
|
|
};
|
|
Module_IMPL(SdkTestPlugin, new SdkTestPlugin());
|
|
void SdkTestPlugin::expose(ssq::Table& table) {
|
|
auto cls = table.addClass(name, SdkTestPlugin::create, false);
|
|
expose(cls);
|
|
}
|
|
void SdkTestPlugin::expose(ssq::Class& cls) {
|
|
cls.addFunc("getName", &SdkTestPlugin::getName);
|
|
}
|
|
} // namespace eve::sdktest
|
|
|
|
EVE_PLUGIN_EXPORT int eve_plugin_init(void) { return 0; }
|
|
EOF
|
|
|
|
CMAKE_ARGS=(-S "$PLUGIN" -B "$PLUGIN/build" -DEVEngine_DIR="$SDK/cmake" -DCMAKE_BUILD_TYPE=Release)
|
|
if [ "$PLAT" = "win32" ]; then
|
|
# Default generator: GitHub-hosted windows images ship VS 2026, local dev
|
|
# machines may have 2022/2026. Let CMake pick the installed instance.
|
|
CMAKE_ARGS+=(-A x64)
|
|
fi
|
|
cmake "${CMAKE_ARGS[@]}" >"$WORK/plugin-cmake.log" 2>&1 || {
|
|
tail -n 30 "$WORK/plugin-cmake.log"
|
|
fail "plugin cmake configure failed"
|
|
}
|
|
cmake --build "$PLUGIN/build" --config Release --parallel 2 >"$WORK/plugin-build.log" 2>&1 || {
|
|
tail -n 40 "$WORK/plugin-build.log"
|
|
fail "plugin cmake build failed"
|
|
}
|
|
case "$PLAT" in
|
|
win32) PLUGIN_OUT="$(find "$PLUGIN/build" -type f -name 'eve_sdk_plugin.dll' | head -1)" ;;
|
|
macosx) PLUGIN_OUT="$(find "$PLUGIN/build" -type f -name 'eve_sdk_plugin.dylib' | head -1)" ;;
|
|
*) PLUGIN_OUT="$(find "$PLUGIN/build" -type f -name 'eve_sdk_plugin.so' | head -1)" ;;
|
|
esac
|
|
[ -n "$PLUGIN_OUT" ] || fail "plugin shared library not produced"
|
|
echo "OK: native C++ plugin compiled: $(basename "$PLUGIN_OUT")"
|
|
|
|
# --- Best-effort: run the packaged game from memory -----------------------------
|
|
echo "== best-effort in-memory run =="
|
|
if [ "$PLAT" = "linux" ] && [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ] \
|
|
&& ! command -v xvfb-run >/dev/null 2>&1; then
|
|
echo "SKIP: headless Linux without xvfb; run gate covered by zip/package steps"
|
|
else
|
|
LOG="$WORK/run.log"
|
|
RUN_DIR="$PKG"
|
|
if command -v xvfb-run >/dev/null 2>&1 && [ "$PLAT" = "linux" ]; then
|
|
xvfb-run -a "$PKG/$RUNTIME" > "$LOG" 2>&1 &
|
|
else
|
|
( cd "$RUN_DIR" && exec "$PKG/$RUNTIME" > "$LOG" 2>&1 ) &
|
|
fi
|
|
RUNPID=$!
|
|
sleep 15
|
|
# Terminate the game (bounded; never block the gate). WARN-only.
|
|
kill "$RUNPID" 2>/dev/null || true
|
|
sleep 1
|
|
kill -9 "$RUNPID" 2>/dev/null || true
|
|
sleep 1
|
|
|
|
if grep -q "SDK_TEST_GAME_OK" "$LOG"; then
|
|
echo "OK: packaged game ran from memory (scripts loaded from game.eve)"
|
|
else
|
|
echo "WARN: could not confirm in-memory run (window/Vulkan/headless). Logs:"
|
|
sed 's/^/ /' "$LOG" | head -12
|
|
fi
|
|
fi
|
|
|
|
echo "== [test-sdk] PASS ($PLAT) =="
|
|
|