ci(release): gate mobile SDK runtime usability
This commit is contained in:
1 parent
bfa8996a0d
commit
4ab841c047
5 files changed
+115
-22
No files matched your search
@@ -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 ---------------------------------------------------
|
||||
|
||||
@@ -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:
|
||||
@@ -843,20 +849,19 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- Android emulator run (best-effort first; promote to hard gate later) --
|
||||
# ---- Android emulator run (release hard gate) ------------------------------
|
||||
# The official SDK ships arm64-v8a only, so the APK needs an arm64 emulator:
|
||||
# macOS (arm64) runners run these natively.
|
||||
run-android:
|
||||
name: Run APK on arm64 emulator (best-effort)
|
||||
needs: [consumer-win32, consumer-linux, consumer-macosx]
|
||||
name: Run APK on arm64 emulator
|
||||
needs: consumer-win32
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 45
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Download android APK
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: android-apk-linux
|
||||
name: android-apk-win32
|
||||
path: apk
|
||||
|
||||
- name: Boot arm64 emulator, install and launch the game
|
||||
@@ -884,16 +889,15 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
+8
-7
@@ -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,13 @@ 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`,硬门槛):将真实 release Android SDK 产出的
|
||||
APK 装进 arm64 模拟器(macOS arm64 runner),启动 `EVEngineActivity` 并要求
|
||||
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
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""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_and_downloads_existing_artifact(self) -> None:
|
||||
block = job_block(self.workflow, "run-android", "ios-sim")
|
||||
self.assertNotIn("continue-on-error:", block)
|
||||
self.assertIn("needs: consumer-win32", block)
|
||||
self.assertIn("name: android-apk-win32", block)
|
||||
self.assertIn("name: android-apk-${{ env.HOST }}", self.workflow)
|
||||
|
||||
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()
|
||||
Reference in new issue
Block a user