.NET Workflow #347
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: .NET Workflow | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths-ignore: | |
| ["**.md", ".github/ISSUE_TEMPLATE/**", ".github/pull_request_template.md"] | |
| pull_request: | |
| paths-ignore: | |
| ["**.md", ".github/ISSUE_TEMPLATE/**", ".github/pull_request_template.md"] | |
| schedule: | |
| - cron: "0 23 * * *" # Daily at 11 PM UTC | |
| workflow_dispatch: # Allow manual triggers | |
| inputs: | |
| version-bump: | |
| description: 'Version bump type' | |
| required: false | |
| default: 'auto' | |
| type: choice | |
| options: | |
| - auto | |
| - patch | |
| - minor | |
| - major | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Default permissions | |
| permissions: | |
| contents: read | |
| env: | |
| DOTNET_VERSION: "10.0" # Only needed for actions/setup-dotnet | |
| jobs: | |
| build: | |
| name: Build, Test & Release | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: write # For creating releases and committing metadata | |
| packages: write # For publishing packages | |
| outputs: | |
| version: ${{ steps.pipeline.outputs.version }} | |
| release_hash: ${{ steps.pipeline.outputs.release_hash }} | |
| should_release: ${{ steps.pipeline.outputs.should_release }} | |
| env: | |
| # SixLabors.ImageSharp 4.0 requires a build-time license key (ImGui.App is a | |
| # direct dependency). Directory.Build.props maps this into the | |
| # SixLaborsLicenseKey MSBuild property. Set the SIXLABORS_LICENSE_KEY repo secret. | |
| SIXLABORS_LICENSE_KEY: ${{ secrets.SIXLABORS_LICENSE_KEY }} | |
| steps: | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: 17 | |
| distribution: "zulu" # Alternative distribution options are available. | |
| - name: Checkout Repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Full history for versioning | |
| fetch-tags: true | |
| lfs: true | |
| submodules: recursive | |
| persist-credentials: true | |
| - name: Setup .NET SDK ${{ env.DOTNET_VERSION }} | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }}.x | |
| cache: true | |
| cache-dependency-path: "**/*.csproj" | |
| # Ensure NuGet packages directory exists for caching (prevents error when pipeline exits early) | |
| - name: Ensure NuGet cache directory exists | |
| run: New-Item -Path "$env:USERPROFILE\.nuget\packages" -ItemType Directory -Force | |
| shell: pwsh | |
| - name: Cache SonarQube Cloud packages | |
| if: ${{ env.SONAR_TOKEN != '' }} | |
| uses: actions/cache@v4 | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| with: | |
| path: ~\sonar\cache | |
| key: ${{ runner.os }}-sonar | |
| restore-keys: ${{ runner.os }}-sonar | |
| - name: Cache SonarQube Cloud scanner | |
| if: ${{ env.SONAR_TOKEN != '' }} | |
| id: cache-sonar-scanner | |
| uses: actions/cache@v4 | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| with: | |
| path: .\.sonar\scanner | |
| key: ${{ runner.os }}-sonar-scanner | |
| restore-keys: ${{ runner.os }}-sonar-scanner | |
| - name: Install SonarQube Cloud scanner | |
| if: ${{ env.SONAR_TOKEN != '' && steps.cache-sonar-scanner.outputs.cache-hit != 'true' }} | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| shell: powershell | |
| run: | | |
| New-Item -Path .\.sonar\scanner -ItemType Directory | |
| dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner | |
| - name: Configure SonarQube exclusions | |
| shell: bash | |
| run: | | |
| EXCLUSIONS="_temp/**,_actions/**" | |
| if [ "${{ github.event.repository.name }}" != "KtsuBuild" ]; then | |
| EXCLUSIONS="$EXCLUSIONS,**/KtsuBuild/**" | |
| fi | |
| # NativeExports.cs is intentionally unsafe C ABI boundary code; exclude from all analysis. | |
| EXCLUSIONS="$EXCLUSIONS,**/NativeExports.cs" | |
| echo "SONAR_EXCLUSIONS=$EXCLUSIONS" >> $GITHUB_ENV | |
| - name: Begin SonarQube | |
| if: ${{ env.SONAR_TOKEN != '' }} | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| shell: powershell | |
| run: | | |
| .\.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 }}" | |
| - name: Clone KtsuBuild (Latest Tag) | |
| run: | | |
| LATEST_TAG=$(git ls-remote --tags https://github.com/ktsu-dev/KtsuBuild.git | grep -o 'refs/tags/v[0-9]*\.[0-9]*\.[0-9]*$' | sed 's/refs\/tags\///' | sort -V | tail -1 || true) | |
| if [ -z "$LATEST_TAG" ]; then | |
| echo "No version tags found, falling back to HEAD" | |
| git clone --depth 1 https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild" | |
| else | |
| echo "Cloning KtsuBuild at tag: $LATEST_TAG" | |
| git clone --depth 1 --branch "$LATEST_TAG" https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild" | |
| fi | |
| shell: bash | |
| - name: Run KtsuBuild CI Pipeline | |
| id: pipeline | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| NUGET_API_KEY: ${{ secrets.NUGET_KEY }} | |
| KTSU_PACKAGE_KEY: ${{ secrets.KTSU_PACKAGE_KEY }} | |
| EXPECTED_OWNER: ktsu-dev | |
| run: | | |
| # Run the CI pipeline | |
| $versionBump = "${{ github.event.inputs.version-bump }}" | |
| # Build arguments array - only add --version-bump if explicitly set (for backward compatibility during bootstrap) | |
| $args = @("ci", "--workspace", "${{ github.workspace }}", "--verbose") | |
| if (![string]::IsNullOrEmpty($versionBump) -and $versionBump -ne "auto") { | |
| $args += @("--version-bump", $versionBump) | |
| } | |
| & dotnet run --project "${{ runner.temp }}/KtsuBuild/KtsuBuild.CLI" -- @args | |
| if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } | |
| # Set outputs for downstream jobs | |
| $version = (Get-Content "${{ github.workspace }}/VERSION.md" -Raw).Trim() | |
| "version=$version" >> $env:GITHUB_OUTPUT | |
| $releaseHash = git rev-parse HEAD | |
| "release_hash=$releaseHash" >> $env:GITHUB_OUTPUT | |
| # Compute should_release (same logic as BuildConfigurationProvider) | |
| $isMain = "${{ github.ref }}" -eq "refs/heads/main" | |
| $isTagged = [bool](git tag --points-at "${{ github.sha }}" 2>$null) | |
| $isFork = "${{ github.event.repository.fork }}" -eq "true" | |
| $isExpectedOwner = "${{ github.repository_owner }}" -eq "ktsu-dev" | |
| $isOfficial = (-not $isFork) -and $isExpectedOwner | |
| $shouldRelease = $isMain -and (-not $isTagged) -and $isOfficial | |
| "should_release=$($shouldRelease.ToString().ToLower())" >> $env:GITHUB_OUTPUT | |
| - name: End SonarQube | |
| if: env.SONAR_TOKEN != '' | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| shell: powershell | |
| run: | | |
| .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" | |
| - name: Upload Coverage Report | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: coverage-report | |
| path: | | |
| ./coverage/* | |
| retention-days: 7 | |
| ios-build: | |
| # Compile-only verification that the net10.0-ios stub keeps building on Mac. | |
| # No tests — Apple Simulator boot from CI is a separate problem (see plan §5 Task 2). | |
| # Runs in parallel with `build`; failure here is independent and does not gate release. | |
| name: Build net10.0-ios (macOS) | |
| runs-on: macos-14 | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: read | |
| env: | |
| # ImGui.App directly references SixLabors.ImageSharp 4.0 on net10.0-ios too, | |
| # so this compile-only job needs the license key as well. | |
| SIXLABORS_LICENSE_KEY: ${{ secrets.SIXLABORS_LICENSE_KEY }} | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| lfs: true | |
| submodules: recursive | |
| persist-credentials: false | |
| - name: Setup .NET SDK ${{ env.DOTNET_VERSION }} | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }}.x | |
| - name: Install iOS workload | |
| # No caching yet — get the install path working first; caching workload manifests | |
| # is a known-finicky follow-up (manifests need both the file tree and a metadata | |
| # registration to "count" as installed). | |
| run: dotnet workload install ios | |
| # The csproj cross-targets net10.0;net9.0;net8.0;net10.0-ios on macOS AND ktsu.Sdk | |
| # forces RuntimeIdentifiers=win-x64;win-x86;win-arm64;osx-x64;osx-arm64;linux-x64;linux-arm64 | |
| # on every project. The combination asks NuGet for the matching | |
| # Microsoft.NETCore.App.Runtime.Mono.<rid> pack at the SDK version — those packs are | |
| # workload-delivered in .NET 10 and no longer on nuget.org, so restore 404s. Windows | |
| # CI gets away with it because the Windows runner has the Mono packs bundled with | |
| # its dotnet install; setup-dotnet on the Mac runner does not. Scoping the restore | |
| # to net10.0-ios alone AND clearing the RID matrix sidesteps the issue — we're only | |
| # compile-checking the IL, no RID-specific output needed. | |
| - name: Restore ImGui.App (net10.0-ios, no RID matrix) | |
| run: dotnet restore ImGui.App/ImGui.App.csproj -p:TargetFrameworks=net10.0-ios -p:RuntimeIdentifiers= | |
| # EnforceCodeStyleInBuild=false bypasses the IDE0055 formatting analyser: the repo's | |
| # working-tree files are LF, .editorconfig demands CRLF, and Windows checkouts get | |
| # auto-converted to CRLF by git's core.autocrlf — macOS checkouts don't, so IDE0055 | |
| # fires here on baseline code that's untouched by this PR. The Windows job is the | |
| # authoritative formatter; the macOS job is a compile-check. | |
| - name: Build ImGui.App (net10.0-ios) | |
| run: dotnet build ImGui.App/ImGui.App.csproj -c Release -p:TargetFrameworks=net10.0-ios -p:RuntimeIdentifiers= -p:EnforceCodeStyleInBuild=false --no-restore | |
| ios-simulator: | |
| # Runtime verification: build the headless smoke app AND the curated ImGuiAppDemo.iOS for the | |
| # simulator, boot a sim, launch each, and assert the lifecycle ticks (the app prints a marker and | |
| # exits after N frames via the IMGUIAPP_IOS_SMOKE_FRAMES hook). Runs in parallel; does not gate | |
| # release. Building+launching two apps after a cold cimgui cache approaches the old 30-min cap, so | |
| # the budget is 50 min (a warm-cache run is ~20). | |
| name: iOS Simulator Smoke Test | |
| # The .NET 10 iOS workload (Microsoft.iOS 26.5) requires Xcode 26.5 to build a runnable .app. | |
| # macos-15 only carries up to Xcode 26.3; macos-26 (Tahoe) ships the matching Xcode 26.5+. | |
| # (The compile-only ios-build job needs no Xcode, so it stays on macos-14.) | |
| runs-on: macos-26 | |
| timeout-minutes: 50 | |
| # HARD GATE: the .NET 10 + Xcode 26 symlinked-developer-dir bug that broke native linking | |
| # (`xcodebuild -find` -> errno=Invalid argument) is worked around by the "Resolve and pin the | |
| # real Xcode" step below (readlink -f + xcode-select/DEVELOPER_DIR pin; dotnet/macios#21762), so | |
| # this runtime smoke test now reliably passes and gates the PR like any other required check. | |
| permissions: | |
| contents: read | |
| env: | |
| # The smoke app and ImGuiAppDemo.iOS build ImGui.App (a direct SixLabors.ImageSharp | |
| # 4.0 dependency) and decode an image via ImageSharp, so this job needs the key too. | |
| SIXLABORS_LICENSE_KEY: ${{ secrets.SIXLABORS_LICENSE_KEY }} | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| lfs: true | |
| submodules: recursive | |
| persist-credentials: false | |
| - name: Setup .NET SDK ${{ env.DOTNET_VERSION }} | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }}.x | |
| - name: Install iOS workload | |
| run: dotnet workload install ios | |
| # Diagnostics: this job can only be debugged from CI (no local Mac), so dump the runner's | |
| # actual SDK, workload, iOS runtime-pack, Xcode, and simulator state up front. Never fails. | |
| - name: iOS toolchain diagnostics | |
| run: | | |
| set -x | |
| dotnet --info || true | |
| dotnet workload list || true | |
| echo "--- installed packs ---" | |
| ls -1 "$HOME/.dotnet/packs" 2>/dev/null | grep -i ios || true | |
| ls -1 /usr/local/share/dotnet/packs 2>/dev/null | grep -i ios || true | |
| echo "--- installed Xcodes ---" | |
| ls -d /Applications/Xcode*.app 2>/dev/null || true | |
| echo "--- xcode / sdk ---" | |
| xcodebuild -version || true | |
| xcrun --sdk iphonesimulator --show-sdk-version || true | |
| echo "--- simulator device types ---" | |
| xcrun simctl list devicetypes | grep -i iphone || true | |
| echo "--- simulator runtimes ---" | |
| xcrun simctl list runtimes || true | |
| continue-on-error: true | |
| # ROOT CAUSE: the hosted Xcode_26.5.0.app is a SYMLINK, and on .NET 10 + Xcode 26 hosted runners | |
| # `xcodebuild -find` intermittently fails to resolve the toolchain through a symlinked developer | |
| # dir - dying with `errno=Invalid argument` (NOT "No such file or directory"; the SDK exists). | |
| # The managed build and IL strip never call `xcrun -find` so they pass, but native clang linking | |
| # (and `-find actool`) do, so they die with clang exit 72. Fix: resolve the .app symlink to its | |
| # REAL path with `readlink -f` and pin BOTH xcode-select and DEVELOPER_DIR to it. `readlink -f` | |
| # is a no-op on an already-real path, so this is safe either way; writing DEVELOPER_DIR to | |
| # $GITHUB_ENV overrides any workflow/job-level value for later steps. | |
| # Refs: dotnet/macios#21762, actions/runner-images#13347. | |
| - name: Resolve and pin the real Xcode 26.5 path (symlink workaround) | |
| run: | | |
| set -uxo pipefail | |
| XC=/Applications/Xcode_26.5.app | |
| [ -d "$XC" ] || XC=/Applications/Xcode_26.5.0.app | |
| [ -d "$XC" ] || XC="$(dirname "$(dirname "$(xcode-select -p)")")" | |
| XC="$(readlink -f "$XC")" | |
| echo "Resolved real Xcode: $XC" | |
| sudo xcode-select -switch "$XC/Contents/Developer" | |
| echo "DEVELOPER_DIR=$XC/Contents/Developer" >> "$GITHUB_ENV" | |
| xcodebuild -version | |
| # Informational: confirm the host link tool now resolves through the real path. | |
| xcrun --find install_name_tool || echo "WARN: install_name_tool did not resolve via $XC" | |
| # Hexa.NET.ImGui ships no native cimgui for iOS, so build it from source (Dear ImGui 1.92.2b, | |
| # matching Hexa 2.2.9) into a simulator static lib that ImGui.App statically links via a | |
| # NativeReference. Cached by the build script's hash so it only rebuilds when the recipe changes. | |
| # Runs after the Xcode pin so xcrun resolves the toolchain; before the build so the | |
| # NativeReference's Exists() condition is satisfied when ImGui.App compiles. | |
| - name: Cache native cimgui (iOS simulator) | |
| uses: actions/cache@v4 | |
| with: | |
| path: ImGui.App/Platform/iOS/native | |
| key: cimgui-sim-arm64-dylib-imgui1.92.3-${{ hashFiles('scripts/build-cimgui-ios.sh') }} | |
| - name: Build native cimgui for the simulator | |
| run: | | |
| set -euxo pipefail | |
| bash scripts/build-cimgui-ios.sh | |
| # Stash the dylib outside the repo tree: the in-tree native/ dir is gitignored and gets | |
| # wiped (by ktsu.Sdk/dotnet cleaning untracked files) before the embed step runs, so the | |
| # embed step copies from this stable location instead. | |
| cp ImGui.App/Platform/iOS/native/cimgui.dylib "$RUNNER_TEMP/cimgui.dylib" | |
| # The smoke app is Microsoft.NET.Sdk, but it references ImGui.App (ktsu.Sdk), which forces a | |
| # desktop RuntimeIdentifiers matrix whose Mono RID packs 404 on macOS (see the ios-build job). | |
| # Clearing RuntimeIdentifiers and pinning the single simulator RID sidesteps that while still | |
| # producing a runnable .app bundle. The install step provisions the iossimulator-x64 Mono | |
| # runtime, so we target that RID; workload restore pulls the matching Microsoft.iOS runtime pack. | |
| - name: Restore iOS workload packs for the smoke app | |
| run: dotnet workload restore tests/ImGui.App.iOS.SmokeTest/ImGui.App.iOS.SmokeTest.csproj | |
| continue-on-error: true | |
| # The referenced ImGui.App is a *library*; in .NET 10 libraries default to the OLDEST iOS | |
| # platform version (_26.0), whose runtime packs aren't installed on the runner (only the | |
| # latest _26.5 packs are) — hence NU1102 on ImGui.App.csproj. UseFloatingTargetPlatformVersion | |
| # makes the library use the latest platform version (26.5) like the executable, aligning both | |
| # on the installed runtime packs. See https://learn.microsoft.com/dotnet/ios/building-apps/build-properties#usefloatingtargetplatformversion | |
| # ktsu.Sdk pins RuntimeFrameworkVersion=10.0.0 (for the desktop .NETCore.App runtime). On iOS | |
| # that bogus version is inherited by the Apple runtime pack (Microsoft.iOS.Runtime.*, actually | |
| # versioned 26.5.x), causing NU1102. Clearing RuntimeFrameworkVersion lets the iOS workload | |
| # resolve the real pack version. Combined with UseFloatingTargetPlatformVersion (library -> | |
| # latest _26.5 packs, which are the ones installed on the runner). | |
| - name: Restore smoke app (net10.0-ios, simulator RID) | |
| 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 | |
| - name: Build smoke app (.app for simulator) | |
| 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 | |
| - name: Locate built .app bundle | |
| id: findapp | |
| run: | | |
| set -euxo pipefail | |
| APP=$(find tests/ImGui.App.iOS.SmokeTest/bin -type d -name "*.app" | head -1) | |
| test -n "$APP" | |
| echo "app=$APP" >> "$GITHUB_OUTPUT" | |
| # Embed cimgui.dylib into the built .app. Hexa.NET.ImGui ships no native cimgui for iOS, and a | |
| # NativeReference did not link it into this Microsoft.NET.Sdk app, so copy the dylib into the | |
| # bundle directly; the runtime resolver dlopens it by bundle path. The simulator does not enforce | |
| # code signing, so an unsigned bundled dylib loads. | |
| - name: Embed cimgui.dylib into the .app bundle | |
| run: cp "$RUNNER_TEMP/cimgui.dylib" "${{ steps.findapp.outputs.app }}/cimgui.dylib" | |
| - name: Boot simulator | |
| run: | | |
| set -euxo pipefail | |
| UDID=$(xcrun simctl create imguiapp-smoke "iPhone 15" 2>/dev/null \ | |
| || xcrun simctl create imguiapp-smoke com.apple.CoreSimulator.SimDeviceType.iPhone-15) | |
| echo "SIM_UDID=$UDID" >> "$GITHUB_ENV" | |
| xcrun simctl boot "$UDID" | |
| xcrun simctl bootstatus "$UDID" -b | |
| - name: Install, launch, and assert lifecycle ticked | |
| run: | | |
| set -euxo pipefail | |
| xcrun simctl install "$SIM_UDID" "${{ steps.findapp.outputs.app }}" | |
| # simctl forwards SIMCTL_CHILD_* env vars (prefix stripped) to the launched app. | |
| SIMCTL_CHILD_IMGUIAPP_IOS_SMOKE_FRAMES=30 \ | |
| xcrun simctl launch --console-pty --terminate-running-process "$SIM_UDID" dev.ktsu.imguiapp.smoketest 2>&1 | tee /tmp/smoke.log | |
| grep -q "IMGUIAPP_IOS_SMOKE_OK" /tmp/smoke.log | |
| # --- Curated iOS demo (examples/ImGuiAppDemo.iOS) --- | |
| # A richer showcase than the headless smoke app: widgets, Unicode/emoji, a GPU texture, animation, | |
| # and the input/IO surface, all using the iOS-safe non-variadic ImGui subset. It reuses this job's | |
| # booted simulator, cimgui dylib, and the IMGUIAPP_IOS_SMOKE_FRAMES hook (baked into ImGui.App's | |
| # view controller), so the same minimal SDK + restore flags as the smoke app apply. | |
| - name: Restore demo app (net10.0-ios, simulator RID) | |
| run: dotnet restore examples/ImGuiAppDemo.iOS/ImGuiAppDemo.iOS.csproj -p:TargetFrameworks=net10.0-ios -p:RuntimeIdentifiers= -p:RuntimeFrameworkVersion= -p:UseFloatingTargetPlatformVersion=true -r iossimulator-arm64 | |
| - name: Build demo app (.app for simulator) | |
| 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 | |
| - name: Locate built demo .app bundle | |
| id: finddemo | |
| run: | | |
| set -euxo pipefail | |
| APP=$(find examples/ImGuiAppDemo.iOS/bin -type d -name "*.app" | head -1) | |
| test -n "$APP" | |
| echo "app=$APP" >> "$GITHUB_OUTPUT" | |
| - name: Embed cimgui.dylib into the demo .app bundle | |
| run: cp "$RUNNER_TEMP/cimgui.dylib" "${{ steps.finddemo.outputs.app }}/cimgui.dylib" | |
| - name: Install, launch, and assert the demo ticked + uploaded its texture | |
| run: | | |
| set -euxo pipefail | |
| xcrun simctl install "$SIM_UDID" "${{ steps.finddemo.outputs.app }}" | |
| SIMCTL_CHILD_IMGUIAPP_IOS_SMOKE_FRAMES=30 \ | |
| xcrun simctl launch --console-pty --terminate-running-process "$SIM_UDID" dev.ktsu.imguiapp.demo 2>&1 | tee /tmp/demo.log | |
| # The lifecycle ticked N frames without crashing in the Metal pipeline... | |
| grep -q "IMGUIAPP_IOS_SMOKE_OK" /tmp/demo.log | |
| # ...and the bundled image decoded (ImageSharp) + uploaded on Metal — PR-2 textures end-to-end. | |
| grep -q "IMGUIAPP_DEMO logo loaded" /tmp/demo.log | |
| - name: Cleanup simulator | |
| if: always() | |
| run: xcrun simctl delete "${SIM_UDID:-imguiapp-smoke}" || true | |
| winget: | |
| name: Update Winget Manifests | |
| needs: build | |
| if: needs.build.outputs.should_release == 'true' | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout Release Commit | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.build.outputs.release_hash }} | |
| fetch-depth: 0 # Full history for better auto-detection | |
| - name: Setup .NET SDK ${{ env.DOTNET_VERSION }} | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }}.x | |
| - name: Clone KtsuBuild (Latest Tag) | |
| run: | | |
| LATEST_TAG=$(git ls-remote --tags https://github.com/ktsu-dev/KtsuBuild.git | grep -o 'refs/tags/v[0-9]*\.[0-9]*\.[0-9]*$' | sed 's/refs\/tags\///' | sort -V | tail -1 || true) | |
| if [ -z "$LATEST_TAG" ]; then | |
| echo "No version tags found, falling back to HEAD" | |
| git clone --depth 1 https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild" | |
| else | |
| echo "Cloning KtsuBuild at tag: $LATEST_TAG" | |
| git clone --depth 1 --branch "$LATEST_TAG" https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild" | |
| fi | |
| shell: bash | |
| - name: Update Winget Manifests | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| dotnet run --project "${{ runner.temp }}/KtsuBuild/KtsuBuild.CLI" -- winget generate --version "${{ needs.build.outputs.version }}" --workspace "${{ github.workspace }}" --verbose | |
| - name: Upload Updated Manifests | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: winget-manifests-${{ needs.build.outputs.version }} | |
| path: winget/*.yaml | |
| retention-days: 30 | |
| security: | |
| name: Security Scanning | |
| needs: build | |
| if: needs.build.outputs.should_release == 'true' | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| id-token: write # For dependency submission | |
| contents: write # For dependency submission | |
| steps: | |
| - name: Checkout Release Commit | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.build.outputs.release_hash }} | |
| - name: Detect Dependencies | |
| uses: advanced-security/component-detection-dependency-submission-action@v0.0.2 |