ci: 加固移动端冒烟并固定 iOS workload - iOS 固定 .NET SDK 10.0.100 并用 SDK 自带 iOS workload(--skip-manifest-update), 避免 workload 更新到 27.1 后要求 runner 尚未提供的 Xcode 27.1 - Android 超时或失败时保存完整 logcat 并随 APK 一起上传,便于定位启动崩溃 - smoke.json 写入失败不再吞掉结论,Android/iOS 入口补 SMOKE_START/SMOKE_FAIL 日志大石头 authored at 2026-10-24 13:17:12
diff --git a/.github/workflows/mobile.yml b/.github/workflows/mobile.yml
index bae9f14..8780850 100644
--- a/.github/workflows/mobile.yml
+++ b/.github/workflows/mobile.yml
@@ -50,7 +50,9 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: mobile-android
- path: Bin/MobileSmoke/**/*.apk
+ path: |
+ Bin/MobileSmoke/**/*.apk
+ Bin/MobileSmoke/artifacts/**
if-no-files-found: ignore
# iOS 模拟器:构建 .app 并安装运行,读取应用容器内的 smoke.json。
@@ -65,12 +67,13 @@ jobs:
- name: Setup dotNET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: 10.0.x
+ # macos-26 默认 Xcode 26.6;固定 10.0.100 并使用 SDK 自带 iOS workload(26.5),
+ # 避免更新到 27.1 后要求尚未提供的 Xcode 27.1
+ dotnet-version: 10.0.100
- name: Show Xcode
- # 不再硬切到指定 Xcode:iOS workload 与镜像默认 Xcode 是配套的,强行切换容易版本不匹配导致构建失败
run: xcodebuild -version
- name: Install iOS workload
- run: dotnet workload install ios
+ run: dotnet workload install ios --skip-manifest-update
- name: Run iOS smoke
run: bash ./Samples/MobileSmoke/run-ios.sh
- name: Upload iOS artifacts
diff --git a/Samples/MobileSmoke/AppDelegate.cs b/Samples/MobileSmoke/AppDelegate.cs
index 99e7c3c..606eaca 100644
--- a/Samples/MobileSmoke/AppDelegate.cs
+++ b/Samples/MobileSmoke/AppDelegate.cs
@@ -22,8 +22,16 @@ public class AppDelegate : UIApplicationDelegate
/// <returns>是否继续启动</returns>
public override Boolean FinishedLaunching(UIApplication application, NSDictionary? launchOptions)
{
- var (pass, fail, _) = Smoke.Run();
- Console.WriteLine(fail == 0 ? $"SMOKE_OK pass={pass}" : $"SMOKE_FAIL pass={pass} fail={fail}");
+ Console.WriteLine("SMOKE_START");
+ try
+ {
+ var (pass, fail, _) = Smoke.Run();
+ Console.WriteLine(fail == 0 ? $"SMOKE_OK pass={pass}" : $"SMOKE_FAIL pass={pass} fail={fail}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"SMOKE_FAIL {ex}");
+ }
return true;
}
}
diff --git a/Samples/MobileSmoke/MainActivity.cs b/Samples/MobileSmoke/MainActivity.cs
index 4469d8d..427ee2b 100644
--- a/Samples/MobileSmoke/MainActivity.cs
+++ b/Samples/MobileSmoke/MainActivity.cs
@@ -15,11 +15,19 @@ public class MainActivity : Activity
{
base.OnCreate(savedInstanceState);
- var (pass, fail, _) = Smoke.Run();
- if (fail == 0)
- Log.Info("MobileSmoke", $"SMOKE_OK pass={pass}");
- else
- Log.Error("MobileSmoke", $"SMOKE_FAIL pass={pass} fail={fail}");
+ Log.Info("MobileSmoke", "SMOKE_START");
+ try
+ {
+ var (pass, fail, _) = Smoke.Run();
+ if (fail == 0)
+ Log.Info("MobileSmoke", $"SMOKE_OK pass={pass}");
+ else
+ Log.Error("MobileSmoke", $"SMOKE_FAIL pass={pass} fail={fail}");
+ }
+ catch (Exception ex)
+ {
+ Log.Error("MobileSmoke", $"SMOKE_FAIL {ex}");
+ }
}
}
#endif
diff --git a/Samples/MobileSmoke/run-android.ps1 b/Samples/MobileSmoke/run-android.ps1
index 38ec724..5ccda4f 100644
--- a/Samples/MobileSmoke/run-android.ps1
+++ b/Samples/MobileSmoke/run-android.ps1
@@ -8,6 +8,8 @@ $ErrorActionPreference = "Stop"
$root = Resolve-Path "$PSScriptRoot/../.."
$project = Join-Path $PSScriptRoot "MobileSmoke.csproj"
$package = "com.newlife.smoke.remoting"
+$artifacts = Join-Path $root "Bin/MobileSmoke/artifacts"
+New-Item -ItemType Directory -Force -Path $artifacts | Out-Null
# 项目是 android+ios 双目标,必须把目标框架收窄到单一平台;
# 否则在 Linux/macOS 上只跑一端时,还原期仍会解析另一平台的 workload,缺失时直接 NETSDK1178
@@ -33,11 +35,14 @@ while ((Get-Date) -lt $deadline) {
if ($hit) {
Write-Host $hit.Line
if ($hit.Line -match "SMOKE_OK") { exit 0 }
+ adb logcat -d | Out-File (Join-Path $artifacts "android-logcat.txt") -Encoding utf8
adb logcat -d -s MobileSmoke:* | Out-Host
exit 1
}
Start-Sleep -Seconds 2
}
+# 超时多为应用启动失败或崩溃;完整 logcat 比过滤标签更能定位根因
+adb logcat -d | Out-File (Join-Path $artifacts "android-logcat.txt") -Encoding utf8
adb logcat -d -s MobileSmoke:* | Out-Host
throw "Android smoke timed out"
diff --git a/Samples/MobileSmoke/Smoke.cs b/Samples/MobileSmoke/Smoke.cs
index f9f92b2..83b3b1c 100644
--- a/Samples/MobileSmoke/Smoke.cs
+++ b/Samples/MobileSmoke/Smoke.cs
@@ -35,9 +35,6 @@ public static class Smoke
if (CountTypes(asm) == 0) throw new Exception("no exported types");
});
Check("ApiClient ctor", () => { using var client = new NewLife.Remoting.ApiClient("http://127.0.0.1:5000"); });
- var dir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
- Directory.CreateDirectory(dir);
- var file = System.IO.Path.Combine(dir, "smoke.json");
var json = JsonSerializer.Serialize(new
{
pass,
@@ -46,7 +43,23 @@ public static class Smoke
os = System.Runtime.InteropServices.RuntimeInformation.OSDescription,
log,
});
- File.WriteAllText(file, json);
+
+ // 结果文件写入失败不应吞掉冒烟结论;记录失败原因后照常返回,Android 仍可通过 logcat 判定
+ var file = String.Empty;
+ try
+ {
+ var dir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ if (String.IsNullOrEmpty(dir)) dir = System.IO.Path.GetTempPath();
+ Directory.CreateDirectory(dir);
+ file = System.IO.Path.Combine(dir, "smoke.json");
+ File.WriteAllText(file, json);
+ }
+ catch (Exception ex)
+ {
+ log.Add($"FAIL write smoke.json: {ex.GetType().Name}: {ex.Message}");
+ fail++;
+ }
+
return (pass, fail, file);
}