Skip to content

Commit 70a56ab

Browse files
SyncFileContentsSyncFileContents
authored andcommitted
Sync .github\workflows\dotnet.yml
1 parent 60483d8 commit 70a56ab

1 file changed

Lines changed: 249 additions & 1 deletion

File tree

.github/workflows/dotnet.yml

Lines changed: 249 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ jobs:
113113
if [ "${{ github.event.repository.name }}" != "KtsuBuild" ]; then
114114
EXCLUSIONS="$EXCLUSIONS,**/KtsuBuild/**"
115115
fi
116+
# NativeExports.cs is intentionally unsafe C ABI boundary code; exclude from all analysis.
117+
EXCLUSIONS="$EXCLUSIONS,**/NativeExports.cs"
116118
echo "SONAR_EXCLUSIONS=$EXCLUSIONS" >> $GITHUB_ENV
117119
118120
- name: Begin SonarQube
@@ -121,7 +123,7 @@ jobs:
121123
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
122124
shell: powershell
123125
run: |
124-
.\.sonar\scanner\dotnet-sonarscanner begin /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" /o:"${{ github.repository_owner }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths="coverage/coverage.xml" /d:sonar.coverage.exclusions="**/*Test*.cs,**/*.Tests.cs,**/*.Tests/**/*,**/obj/**/*,**/*.dll" /d:sonar.cs.vstest.reportsPaths="coverage/TestResults/**/*.trx" /d:sonar.exclusions="${{ env.SONAR_EXCLUSIONS }}"
126+
.\.sonar\scanner\dotnet-sonarscanner begin /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" /o:"${{ github.repository_owner }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths="coverage/coverage.xml" /d:sonar.coverage.exclusions="**/*Test*.cs,**/*.Tests.cs,**/*.Tests/**/*,**/obj/**/*,**/*.dll,**/NativeExports.cs" /d:sonar.cs.vstest.reportsPaths="coverage/TestResults/**/*.trx" /d:sonar.exclusions="${{ env.SONAR_EXCLUSIONS }}"
125127
126128
- name: Clone KtsuBuild (Latest Tag)
127129
run: |
@@ -154,6 +156,7 @@ jobs:
154156
}
155157
156158
& dotnet run --project "${{ runner.temp }}/KtsuBuild/KtsuBuild.CLI" -- @args
159+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
157160
158161
# Set outputs for downstream jobs
159162
$version = (Get-Content "${{ github.workspace }}/VERSION.md" -Raw).Trim()
@@ -188,6 +191,251 @@ jobs:
188191
./coverage/*
189192
retention-days: 7
190193

194+
ios-build:
195+
# Compile-only verification that the net10.0-ios stub keeps building on Mac.
196+
# No tests — Apple Simulator boot from CI is a separate problem (see plan §5 Task 2).
197+
# Runs in parallel with `build`; failure here is independent and does not gate release.
198+
name: Build net10.0-ios (macOS)
199+
runs-on: macos-14
200+
timeout-minutes: 20
201+
permissions:
202+
contents: read
203+
204+
steps:
205+
- name: Checkout Repository
206+
uses: actions/checkout@v4
207+
with:
208+
fetch-depth: 1
209+
lfs: true
210+
submodules: recursive
211+
persist-credentials: false
212+
213+
- name: Setup .NET SDK ${{ env.DOTNET_VERSION }}
214+
uses: actions/setup-dotnet@v4
215+
with:
216+
dotnet-version: ${{ env.DOTNET_VERSION }}.x
217+
218+
- name: Install iOS workload
219+
# No caching yet — get the install path working first; caching workload manifests
220+
# is a known-finicky follow-up (manifests need both the file tree and a metadata
221+
# registration to "count" as installed).
222+
run: dotnet workload install ios
223+
224+
# The csproj cross-targets net10.0;net9.0;net8.0;net10.0-ios on macOS AND ktsu.Sdk
225+
# forces RuntimeIdentifiers=win-x64;win-x86;win-arm64;osx-x64;osx-arm64;linux-x64;linux-arm64
226+
# on every project. The combination asks NuGet for the matching
227+
# Microsoft.NETCore.App.Runtime.Mono.<rid> pack at the SDK version — those packs are
228+
# workload-delivered in .NET 10 and no longer on nuget.org, so restore 404s. Windows
229+
# CI gets away with it because the Windows runner has the Mono packs bundled with
230+
# its dotnet install; setup-dotnet on the Mac runner does not. Scoping the restore
231+
# to net10.0-ios alone AND clearing the RID matrix sidesteps the issue — we're only
232+
# compile-checking the IL, no RID-specific output needed.
233+
- name: Restore ImGui.App (net10.0-ios, no RID matrix)
234+
run: dotnet restore ImGui.App/ImGui.App.csproj -p:TargetFrameworks=net10.0-ios -p:RuntimeIdentifiers=
235+
236+
# EnforceCodeStyleInBuild=false bypasses the IDE0055 formatting analyser: the repo's
237+
# working-tree files are LF, .editorconfig demands CRLF, and Windows checkouts get
238+
# auto-converted to CRLF by git's core.autocrlf — macOS checkouts don't, so IDE0055
239+
# fires here on baseline code that's untouched by this PR. The Windows job is the
240+
# authoritative formatter; the macOS job is a compile-check.
241+
- name: Build ImGui.App (net10.0-ios)
242+
run: dotnet build ImGui.App/ImGui.App.csproj -c Release -p:TargetFrameworks=net10.0-ios -p:RuntimeIdentifiers= -p:EnforceCodeStyleInBuild=false --no-restore
243+
244+
ios-simulator:
245+
# Runtime verification: build the headless smoke app AND the curated ImGuiAppDemo.iOS for the
246+
# simulator, boot a sim, launch each, and assert the lifecycle ticks (the app prints a marker and
247+
# exits after N frames via the IMGUIAPP_IOS_SMOKE_FRAMES hook). Runs in parallel; does not gate
248+
# release. Building+launching two apps after a cold cimgui cache approaches the old 30-min cap, so
249+
# the budget is 50 min (a warm-cache run is ~20).
250+
name: iOS Simulator Smoke Test
251+
# The .NET 10 iOS workload (Microsoft.iOS 26.5) requires Xcode 26.5 to build a runnable .app.
252+
# macos-15 only carries up to Xcode 26.3; macos-26 (Tahoe) ships the matching Xcode 26.5+.
253+
# (The compile-only ios-build job needs no Xcode, so it stays on macos-14.)
254+
runs-on: macos-26
255+
timeout-minutes: 50
256+
# HARD GATE: the .NET 10 + Xcode 26 symlinked-developer-dir bug that broke native linking
257+
# (`xcodebuild -find` -> errno=Invalid argument) is worked around by the "Resolve and pin the
258+
# real Xcode" step below (readlink -f + xcode-select/DEVELOPER_DIR pin; dotnet/macios#21762), so
259+
# this runtime smoke test now reliably passes and gates the PR like any other required check.
260+
permissions:
261+
contents: read
262+
263+
steps:
264+
- name: Checkout Repository
265+
uses: actions/checkout@v4
266+
with:
267+
fetch-depth: 1
268+
lfs: true
269+
submodules: recursive
270+
persist-credentials: false
271+
272+
- name: Setup .NET SDK ${{ env.DOTNET_VERSION }}
273+
uses: actions/setup-dotnet@v4
274+
with:
275+
dotnet-version: ${{ env.DOTNET_VERSION }}.x
276+
277+
- name: Install iOS workload
278+
run: dotnet workload install ios
279+
280+
# Diagnostics: this job can only be debugged from CI (no local Mac), so dump the runner's
281+
# actual SDK, workload, iOS runtime-pack, Xcode, and simulator state up front. Never fails.
282+
- name: iOS toolchain diagnostics
283+
run: |
284+
set -x
285+
dotnet --info || true
286+
dotnet workload list || true
287+
echo "--- installed packs ---"
288+
ls -1 "$HOME/.dotnet/packs" 2>/dev/null | grep -i ios || true
289+
ls -1 /usr/local/share/dotnet/packs 2>/dev/null | grep -i ios || true
290+
echo "--- installed Xcodes ---"
291+
ls -d /Applications/Xcode*.app 2>/dev/null || true
292+
echo "--- xcode / sdk ---"
293+
xcodebuild -version || true
294+
xcrun --sdk iphonesimulator --show-sdk-version || true
295+
echo "--- simulator device types ---"
296+
xcrun simctl list devicetypes | grep -i iphone || true
297+
echo "--- simulator runtimes ---"
298+
xcrun simctl list runtimes || true
299+
continue-on-error: true
300+
301+
# ROOT CAUSE: the hosted Xcode_26.5.0.app is a SYMLINK, and on .NET 10 + Xcode 26 hosted runners
302+
# `xcodebuild -find` intermittently fails to resolve the toolchain through a symlinked developer
303+
# dir - dying with `errno=Invalid argument` (NOT "No such file or directory"; the SDK exists).
304+
# The managed build and IL strip never call `xcrun -find` so they pass, but native clang linking
305+
# (and `-find actool`) do, so they die with clang exit 72. Fix: resolve the .app symlink to its
306+
# REAL path with `readlink -f` and pin BOTH xcode-select and DEVELOPER_DIR to it. `readlink -f`
307+
# is a no-op on an already-real path, so this is safe either way; writing DEVELOPER_DIR to
308+
# $GITHUB_ENV overrides any workflow/job-level value for later steps.
309+
# Refs: dotnet/macios#21762, actions/runner-images#13347.
310+
- name: Resolve and pin the real Xcode 26.5 path (symlink workaround)
311+
run: |
312+
set -uxo pipefail
313+
XC=/Applications/Xcode_26.5.app
314+
[ -d "$XC" ] || XC=/Applications/Xcode_26.5.0.app
315+
[ -d "$XC" ] || XC="$(dirname "$(dirname "$(xcode-select -p)")")"
316+
XC="$(readlink -f "$XC")"
317+
echo "Resolved real Xcode: $XC"
318+
sudo xcode-select -switch "$XC/Contents/Developer"
319+
echo "DEVELOPER_DIR=$XC/Contents/Developer" >> "$GITHUB_ENV"
320+
xcodebuild -version
321+
# Informational: confirm the host link tool now resolves through the real path.
322+
xcrun --find install_name_tool || echo "WARN: install_name_tool did not resolve via $XC"
323+
324+
# Hexa.NET.ImGui ships no native cimgui for iOS, so build it from source (Dear ImGui 1.92.2b,
325+
# matching Hexa 2.2.9) into a simulator static lib that ImGui.App statically links via a
326+
# NativeReference. Cached by the build script's hash so it only rebuilds when the recipe changes.
327+
# Runs after the Xcode pin so xcrun resolves the toolchain; before the build so the
328+
# NativeReference's Exists() condition is satisfied when ImGui.App compiles.
329+
- name: Cache native cimgui (iOS simulator)
330+
uses: actions/cache@v4
331+
with:
332+
path: ImGui.App/Platform/iOS/native
333+
key: cimgui-sim-arm64-dylib-imgui1.92.3-${{ hashFiles('scripts/build-cimgui-ios.sh') }}
334+
335+
- name: Build native cimgui for the simulator
336+
run: |
337+
set -euxo pipefail
338+
bash scripts/build-cimgui-ios.sh
339+
# Stash the dylib outside the repo tree: the in-tree native/ dir is gitignored and gets
340+
# wiped (by ktsu.Sdk/dotnet cleaning untracked files) before the embed step runs, so the
341+
# embed step copies from this stable location instead.
342+
cp ImGui.App/Platform/iOS/native/cimgui.dylib "$RUNNER_TEMP/cimgui.dylib"
343+
344+
# The smoke app is Microsoft.NET.Sdk, but it references ImGui.App (ktsu.Sdk), which forces a
345+
# desktop RuntimeIdentifiers matrix whose Mono RID packs 404 on macOS (see the ios-build job).
346+
# Clearing RuntimeIdentifiers and pinning the single simulator RID sidesteps that while still
347+
# producing a runnable .app bundle. The install step provisions the iossimulator-x64 Mono
348+
# runtime, so we target that RID; workload restore pulls the matching Microsoft.iOS runtime pack.
349+
- name: Restore iOS workload packs for the smoke app
350+
run: dotnet workload restore tests/ImGui.App.iOS.SmokeTest/ImGui.App.iOS.SmokeTest.csproj
351+
continue-on-error: true
352+
353+
# The referenced ImGui.App is a *library*; in .NET 10 libraries default to the OLDEST iOS
354+
# platform version (_26.0), whose runtime packs aren't installed on the runner (only the
355+
# latest _26.5 packs are) — hence NU1102 on ImGui.App.csproj. UseFloatingTargetPlatformVersion
356+
# makes the library use the latest platform version (26.5) like the executable, aligning both
357+
# on the installed runtime packs. See https://learn.microsoft.com/dotnet/ios/building-apps/build-properties#usefloatingtargetplatformversion
358+
# ktsu.Sdk pins RuntimeFrameworkVersion=10.0.0 (for the desktop .NETCore.App runtime). On iOS
359+
# that bogus version is inherited by the Apple runtime pack (Microsoft.iOS.Runtime.*, actually
360+
# versioned 26.5.x), causing NU1102. Clearing RuntimeFrameworkVersion lets the iOS workload
361+
# resolve the real pack version. Combined with UseFloatingTargetPlatformVersion (library ->
362+
# latest _26.5 packs, which are the ones installed on the runner).
363+
- name: Restore smoke app (net10.0-ios, simulator RID)
364+
run: dotnet restore tests/ImGui.App.iOS.SmokeTest/ImGui.App.iOS.SmokeTest.csproj -p:TargetFrameworks=net10.0-ios -p:RuntimeIdentifiers= -p:RuntimeFrameworkVersion= -p:UseFloatingTargetPlatformVersion=true -r iossimulator-arm64
365+
366+
- name: Build smoke app (.app for simulator)
367+
run: dotnet build tests/ImGui.App.iOS.SmokeTest/ImGui.App.iOS.SmokeTest.csproj -c Release -f net10.0-ios -r iossimulator-arm64 -p:RuntimeIdentifiers= -p:RuntimeFrameworkVersion= -p:UseFloatingTargetPlatformVersion=true -p:EnforceCodeStyleInBuild=false --no-restore
368+
369+
- name: Locate built .app bundle
370+
id: findapp
371+
run: |
372+
set -euxo pipefail
373+
APP=$(find tests/ImGui.App.iOS.SmokeTest/bin -type d -name "*.app" | head -1)
374+
test -n "$APP"
375+
echo "app=$APP" >> "$GITHUB_OUTPUT"
376+
377+
# Embed cimgui.dylib into the built .app. Hexa.NET.ImGui ships no native cimgui for iOS, and a
378+
# NativeReference did not link it into this Microsoft.NET.Sdk app, so copy the dylib into the
379+
# bundle directly; the runtime resolver dlopens it by bundle path. The simulator does not enforce
380+
# code signing, so an unsigned bundled dylib loads.
381+
- name: Embed cimgui.dylib into the .app bundle
382+
run: cp "$RUNNER_TEMP/cimgui.dylib" "${{ steps.findapp.outputs.app }}/cimgui.dylib"
383+
384+
- name: Boot simulator
385+
run: |
386+
set -euxo pipefail
387+
UDID=$(xcrun simctl create imguiapp-smoke "iPhone 15" 2>/dev/null \
388+
|| xcrun simctl create imguiapp-smoke com.apple.CoreSimulator.SimDeviceType.iPhone-15)
389+
echo "SIM_UDID=$UDID" >> "$GITHUB_ENV"
390+
xcrun simctl boot "$UDID"
391+
xcrun simctl bootstatus "$UDID" -b
392+
393+
- name: Install, launch, and assert lifecycle ticked
394+
run: |
395+
set -euxo pipefail
396+
xcrun simctl install "$SIM_UDID" "${{ steps.findapp.outputs.app }}"
397+
# simctl forwards SIMCTL_CHILD_* env vars (prefix stripped) to the launched app.
398+
SIMCTL_CHILD_IMGUIAPP_IOS_SMOKE_FRAMES=30 \
399+
xcrun simctl launch --console-pty --terminate-running-process "$SIM_UDID" dev.ktsu.imguiapp.smoketest 2>&1 | tee /tmp/smoke.log
400+
grep -q "IMGUIAPP_IOS_SMOKE_OK" /tmp/smoke.log
401+
402+
# --- Curated iOS demo (examples/ImGuiAppDemo.iOS) ---
403+
# A richer showcase than the headless smoke app: widgets, Unicode/emoji, a GPU texture, animation,
404+
# and the input/IO surface, all using the iOS-safe non-variadic ImGui subset. It reuses this job's
405+
# booted simulator, cimgui dylib, and the IMGUIAPP_IOS_SMOKE_FRAMES hook (baked into ImGui.App's
406+
# view controller), so the same minimal SDK + restore flags as the smoke app apply.
407+
- name: Restore demo app (net10.0-ios, simulator RID)
408+
run: dotnet restore examples/ImGuiAppDemo.iOS/ImGuiAppDemo.iOS.csproj -p:TargetFrameworks=net10.0-ios -p:RuntimeIdentifiers= -p:RuntimeFrameworkVersion= -p:UseFloatingTargetPlatformVersion=true -r iossimulator-arm64
409+
410+
- name: Build demo app (.app for simulator)
411+
run: dotnet build examples/ImGuiAppDemo.iOS/ImGuiAppDemo.iOS.csproj -c Release -f net10.0-ios -r iossimulator-arm64 -p:RuntimeIdentifiers= -p:RuntimeFrameworkVersion= -p:UseFloatingTargetPlatformVersion=true -p:EnforceCodeStyleInBuild=false --no-restore
412+
413+
- name: Locate built demo .app bundle
414+
id: finddemo
415+
run: |
416+
set -euxo pipefail
417+
APP=$(find examples/ImGuiAppDemo.iOS/bin -type d -name "*.app" | head -1)
418+
test -n "$APP"
419+
echo "app=$APP" >> "$GITHUB_OUTPUT"
420+
421+
- name: Embed cimgui.dylib into the demo .app bundle
422+
run: cp "$RUNNER_TEMP/cimgui.dylib" "${{ steps.finddemo.outputs.app }}/cimgui.dylib"
423+
424+
- name: Install, launch, and assert the demo ticked + uploaded its texture
425+
run: |
426+
set -euxo pipefail
427+
xcrun simctl install "$SIM_UDID" "${{ steps.finddemo.outputs.app }}"
428+
SIMCTL_CHILD_IMGUIAPP_IOS_SMOKE_FRAMES=30 \
429+
xcrun simctl launch --console-pty --terminate-running-process "$SIM_UDID" dev.ktsu.imguiapp.demo 2>&1 | tee /tmp/demo.log
430+
# The lifecycle ticked N frames without crashing in the Metal pipeline...
431+
grep -q "IMGUIAPP_IOS_SMOKE_OK" /tmp/demo.log
432+
# ...and the bundled image decoded (ImageSharp) + uploaded on Metal — PR-2 textures end-to-end.
433+
grep -q "IMGUIAPP_DEMO logo loaded" /tmp/demo.log
434+
435+
- name: Cleanup simulator
436+
if: always()
437+
run: xcrun simctl delete "${SIM_UDID:-imguiapp-smoke}" || true
438+
191439
winget:
192440
name: Update Winget Manifests
193441
needs: build

0 commit comments

Comments
 (0)