Merge pull request #423 from EVEngine/codex/release-v0.5.2-mobile-sdk-gates

ci(release): gate Android and iOS SDK usability
This commit is contained in:
西风逍遥游 authored and GitHub committed 2026-09-19 09:42:31 +08:00
commit cf9e678c2b
7 files changed
+163 -39

No files matched your search

+37 -5
View File
@@ -6,7 +6,8 @@
# win32 -> bin/eve.exe (Windows runner)
# linux -> bin/eve (Linux runner)
# macosx-> bin/eve (macOS runner)
# android/ios -> skipped (no host runtime binary)
# 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.
@@ -19,7 +20,7 @@
# 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 packaged game
# 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
@@ -37,7 +38,7 @@ EXPECTED="${3:-}"
case "$PLAT" in
win32) RUNTIME="eve.exe" ;;
linux|macosx) RUNTIME="eve" ;;
android) RUNTIME="" ;; # verified by the APK smoke below
android|ios) RUNTIME="" ;; # verified by target-specific gates below
*) echo "SKIP: no host runtime to test for platform '$PLAT'"; exit 0 ;;
esac
@@ -50,8 +51,7 @@ trap 'rm -rf "$WORK"' EXIT
# 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
echo "SKIP: android APK smoke needs ANDROID_SDK + JAVA_HOME in the environment"
exit 0
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"
@@ -108,6 +108,38 @@ EOF
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 ---------------------------------------------------
+55 -23
View File
@@ -322,6 +322,12 @@ jobs:
ANDROID_JOBS="$JOBS"
fi
- name: Test ios SDK artifact usability
timeout-minutes: 10
env:
TAG: ${{ inputs.tag }}
run: bash .github/scripts/test-sdk.sh dist/eve-sdk/ios ios "${TAG#v}"
- name: Package SDK zip
timeout-minutes: 10
env:
@@ -651,6 +657,14 @@ jobs:
export VK_ICD_FILENAMES="$(ls /usr/share/vulkan/icd.d/lvp_icd*.json 2>/dev/null | head -1)"
xvfb-run -a bash .github/scripts/consumer-test.sh "$HOST" "$TAG" "$RUNNER_TEMP/consumer"
- name: Upload android APK
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: android-apk-${{ env.HOST }}
path: ${{ runner.temp }}/consumer/eve-android-${{ env.HOST }}.apk
if-no-files-found: error
retention-days: 7
- name: Upload cross win32 package (produced on linux)
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@@ -696,6 +710,14 @@ jobs:
timeout-minutes: 90
run: bash .github/scripts/consumer-test.sh "$HOST" "$TAG" "$RUNNER_TEMP/consumer"
- name: Upload android APK
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: android-apk-${{ env.HOST }}
path: ${{ runner.temp }}/consumer/eve-android-${{ env.HOST }}.apk
if-no-files-found: error
retention-days: 7
- name: Upload cross win32 package (produced on macosx)
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@@ -843,21 +865,21 @@ jobs:
exit 1
fi
# ---- Android emulator run (best-effort first; promote to hard gate later) --
# The official SDK ships arm64-v8a only, so the APK needs an arm64 emulator:
# macOS (arm64) runners run these natively.
# ---- Android emulator run (release hard gate) ------------------------------
# Every desktop consumer builds an APK from the official Android SDK. Run all
# three APKs on an arm64 emulator so no host-specific consumer path can regress.
run-android:
name: Run APK on arm64 emulator (best-effort)
name: Run all consumer APKs on arm64 emulator
needs: [consumer-win32, consumer-linux, consumer-macosx]
runs-on: macos-14
timeout-minutes: 45
continue-on-error: true
steps:
- name: Download android APK
- name: Download all consumer Android APKs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: android-apk-linux
pattern: android-apk-*
path: apk
merge-multiple: true
- name: Boot arm64 emulator, install and launch the game
timeout-minutes: 40
@@ -868,32 +890,42 @@ jobs:
target: google_apis
disable-animations: true
script: |
adb logcat -c
adb install -r apk/*.apk
adb shell am start -n com.evengine.example/.EVEngineActivity
ok=0
for i in $(seq 1 90); do
if adb logcat -d | grep -q EVE_CI_GAME_OK; then ok=1; break; fi
sleep 1
found=0
for apk_file in apk/*.apk; do
[ -f "$apk_file" ] || continue
found=$((found + 1))
apk_name=$(basename "$apk_file")
echo "Testing $apk_name"
adb logcat -c
adb install -r "$apk_file"
adb shell am force-stop com.evengine.example || true
adb shell am start -W -n com.evengine.example/.EVEngineActivity
ok=0
for i in $(seq 1 90); do
if adb logcat -d | grep -q EVE_CI_GAME_OK; then ok=1; break; fi
sleep 1
done
if [ "$ok" -ne 1 ]; then
echo "ERROR: $apk_name did not print EVE_CI_GAME_OK (logcat tail):"
adb logcat -d | tail -n 60 || true
exit 1
fi
echo "OK: $apk_name printed EVE_CI_GAME_OK on the emulator"
done
if [ "$ok" -eq 1 ]; then
echo "OK: APK printed EVE_CI_GAME_OK on the emulator"
else
echo "WARN: game marker not seen (logcat tail):"
adb logcat -d | tail -n 60 || true
if [ "$found" -ne 3 ]; then
echo "ERROR: expected 3 consumer APKs, found $found"
exit 1
fi
# ---- iOS: engine runs a game on the simulator (best-effort) ---------------
# ---- iOS: engine runs a game on the simulator (release hard gate) ----------
# The released ios SDK is a device app; the simulator cannot execute device
# slices, so this builds the game app for iphonesimulator from the release
# source and boots it. Soft gate: report, never block the release.
# source and boots it. A failure blocks formal release promotion.
ios-sim:
name: iOS simulator run (best-effort)
name: iOS simulator run
needs: publish
runs-on: macos-latest
timeout-minutes: 120
continue-on-error: true
steps:
- name: Checkout
timeout-minutes: 10
+8 -1
View File
@@ -409,7 +409,14 @@ function(check_third_party_project name repo)
)
endif()
target_include_directories(eve_engine_includes INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/build/${name}-binary/${TP_BUILD_PATH}/include)
${CMAKE_CURRENT_SOURCE_DIR}/build/${name}-binary/${TP_BUILD_PATH}/include
# SDL2 and FreeType install headers below their own include roots.
# These directories do not exist yet during a clean configure because
# the ExternalProject install happens in the later `deps` build. They
# must still be registered now; testing IS_DIRECTORY here makes clean
# iOS/simulator builds miss <SDL.h> or <ft2build.h>.
${CMAKE_CURRENT_SOURCE_DIR}/build/${name}-binary/${TP_BUILD_PATH}/include/SDL2
${CMAKE_CURRENT_SOURCE_DIR}/build/${name}-binary/${TP_BUILD_PATH}/include/freetype2)
eve_external_include(
${CMAKE_CURRENT_SOURCE_DIR}/build/${name}-binary/${TP_BUILD_PATH}/include)
target_link_directories(eve_engine_includes INTERFACE
+9 -7
View File
@@ -68,10 +68,11 @@ WARN,不自动改写。
(best-effort,保留相对路径便于溯源)。
- **模块头文件**:按 `cmake/module_manifest.cmake` 的 `EVE_ENABLED_MODULES` 自动导出
当前构建启用的模块,SDK API 面与目标运行时一致,不再手维护白名单。
- **可用性测试**(`test-sdk.sh`,桌面三端硬门槛):SDK 布局 + 精确版本断言 +
- **可用性测试**(`test-sdk.sh`,五平台硬门槛):SDK 布局 + 精确版本断言 +
`eve zip`/`eve package` + **C++ 插件编译**(`find_package(EVEngine)` +
`add_eve_plugin()`);Android 额外跑模板 APK 组装冒烟
(`gradlew :app:assembleDebug`)。
(`gradlew :app:assembleDebug`),iOS 额外校验 device app bundle、Mach-O 可执行文件和
arm64 slice。移动端所需工具链缺失也是硬失败,不得跳过。
- **完整性**:publish 前逐个校验 zip 的 `share/eve/VERSION` == tag,并生成
`SHA256SUMS` 随 SDK 一起上传(可选开启 Artifact Attestations)。
@@ -90,13 +91,14 @@ SDK 上传到 Release 之后,`sdk-release.yml` 继续以“用户机器上什
linux 主机出 win32 包,macosx 主机出 win32 + linux 包;产物上传后由
`run-win32-cross` / `run-linux-cross` 在**目标平台**(SwiftShader /
Lavapipe 软件 Vulkan)真实运行并 grep 标记。
- **Android 模拟器**(`run-android`,软门槛,best-effort):APK 装进 arm64
模拟器(macOS arm64 runner)启动 `EVEngineActivity`,logcat grep 标记;
连续稳定后再转硬门槛。
- **iOS 模拟器**(`ios-sim`,软门槛,best-effort):从 release 源码构建
- **Android 模拟器**(`run-android`,硬门槛):收集 `consumer-win32`、
`consumer-linux`、`consumer-macosx` 使用真实 release Android SDK 各自产出的三份
APK,逐个装进 arm64 模拟器(macOS arm64 runner),启动 `EVEngineActivity` 并要求
每份 APK 的 logcat 都出现启动标记;缺少任一产物或任一运行失败都会阻止发布。
- **iOS 模拟器**(`ios-sim`,硬门槛):从 release 源码构建
iphonesimulator 游戏 app(`make build/ios-sim-debug`,无需签名),simctl
安装启动后 grep 标记。发布版 iOS SDK 是 device app,无法在模拟器跑,
因此这步是“引擎在 iOS 上能跑游戏”的源码级验证。
因此这步与 iOS SDK 本体的 bundle/arm64 检查共同构成发布门禁。
消费侧依赖的产物特性:win32 SDK 的 `bin/` 内置 `vulkan-1.dll` + VC 运行库
(`cmake/win32_bundle_runtime.cmake`),所以任意主机都能打出自包含的 win32
+2 -2
View File
@@ -30,8 +30,8 @@ make run/<platform>-debug GAME=examples/pcg-runtime-orchestration
**任务队列**:`add(0.25)` → `tick(0.25)` → `getReadyTaskId()` 必须等于 `add` 返回的 id →
`resolveReady(true)` 收尾。
另外打印宿主系统探测行 `PCG_HOST_SYSTEM limitFrame=... type=...`,并设置
`eve.bootBench <- true` 供启动基准使用。
另外打印宿主系统探测行 `PCG_HOST_SYSTEM limitFrame=... type=...`。示例保持正常帧循环,
以便 `scripts/smoke_examples.sh` 能验证它在启动后持续存活。
成功标记:`PCG_RUNTIME_ORCHESTRATION_PASS stamp=6 progress=0.35 queue=0`
@@ -40,7 +40,6 @@ eve_init = function() {
gfx.setBackgroundColor(0.025, 0.055, 0.09, 1.0);
print("PCG_HOST_SYSTEM limitFrame="+("limitFrame" in system)+" type="+typeof system+"\n");
print("PCG_RUNTIME_ORCHESTRATION_PASS stamp=6 progress=0.35 queue=0\n");
eve.bootBench <- true;
};
eve_update = function(dt) {};
@@ -0,0 +1,52 @@
"""Static contracts for release-blocking Android and iOS SDK validation."""
from pathlib import Path
import unittest
ROOT = Path(__file__).resolve().parents[2]
WORKFLOW = ROOT / ".github" / "workflows" / "sdk-release.yml"
SDK_TEST = ROOT / ".github" / "scripts" / "test-sdk.sh"
def job_block(workflow: str, job: str, next_job: str | None = None) -> str:
start = workflow.index(f" {job}:\n")
if next_job is None:
return workflow[start:]
end = workflow.index(f" {next_job}:\n", start)
return workflow[start:end]
class MobileSdkReleaseGateTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.workflow = WORKFLOW.read_text(encoding="utf-8")
cls.sdk_test = SDK_TEST.read_text(encoding="utf-8")
def test_android_emulator_is_a_hard_gate_for_every_consumer_host(self) -> None:
block = job_block(self.workflow, "run-android", "ios-sim")
self.assertNotIn("continue-on-error:", block)
self.assertIn("needs: [consumer-win32, consumer-linux, consumer-macosx]", block)
self.assertIn("pattern: android-apk-*", block)
self.assertIn("merge-multiple: true", block)
self.assertIn("for apk_file in apk/*.apk", block)
self.assertIn('if [ "$found" -ne 3 ]', block)
self.assertEqual(3, self.workflow.count("name: android-apk-${{ env.HOST }}"))
def test_ios_artifact_and_simulator_are_hard_gates(self) -> None:
ios_build = job_block(self.workflow, "ios", "linux")
self.assertIn("test-sdk.sh dist/eve-sdk/ios ios", ios_build)
simulator = job_block(self.workflow, "ios-sim")
self.assertNotIn("continue-on-error:", simulator)
self.assertIn("make build/ios-sim-debug", simulator)
self.assertIn("EVE_CI_GAME_OK", simulator)
def test_mobile_sdk_checks_do_not_skip_when_prerequisites_are_missing(self) -> None:
self.assertNotIn("SKIP: android APK smoke needs", self.sdk_test)
self.assertIn('fail "android APK smoke needs', self.sdk_test)
self.assertIn('if [ "$PLAT" = "ios" ]', self.sdk_test)
self.assertIn("lipo -archs", self.sdk_test)
if __name__ == "__main__":
unittest.main()