Skip to content

fix: resolve three CI build/quality errors #2637

fix: resolve three CI build/quality errors

fix: resolve three CI build/quality errors #2637

Workflow file for this run

on: [push, pull_request]
name: Continuous Integration
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read # to fetch code (actions/checkout)
packages: read # to restore NuGet packages from GitHub Packages
security-events: write # for reporting InspectCode issues
jobs:
resource-budget-source:
name: Resource Budget (Source Overrides)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install ffmpeg
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg
- name: Optimize local override media
run: |
python3 scripts/optimize_resource_overrides.py \
--root osu.Game/Resources \
--config .github/resource-optimizer/config.json \
--report-file resource-budget/optimizer-report.json \
--summary-file resource-budget/optimizer-summary.md
- name: Run source override resource budget checks
run: |
python3 scripts/check_resource_budgets.py \
--mode source \
--source-root osu.Game/Resources \
--budget-file .github/resource-budgets/source-overrides.json \
--report-file resource-budget/source-report.json \
--summary-file resource-budget/source-summary.md
- name: Publish source optimization summary
if: ${{ !cancelled() }}
run: |
cat resource-budget/optimizer-summary.md >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
cat resource-budget/source-summary.md >> "$GITHUB_STEP_SUMMARY"
- name: Upload source resource budget reports
uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: resource-budget-source
path: resource-budget/*
inspect-code:
name: Code Quality
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install .NET 10.0.x
uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"
- name: Cache NuGet packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Authenticate to GitHub Packages
run: dotnet nuget update source winnerspiros-github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text
- name: Restore Tools
run: dotnet tool restore
- name: Restore Packages
run: dotnet restore osu.Desktop.slnf
- name: Restore inspectcode cache
uses: actions/cache@v5
with:
path: ${{ github.workspace }}/inspectcode
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
- name: Dotnet code style
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf -p:EnforceCodeStyleInBuild=true
- name: CodeFileSanity
run: |
# TODO: Add ignore filters and GitHub Workflow Command Reporting in CFS. That way we don't have to do this workaround.
# FIXME: Suppress warnings from templates project
exit_code=0
while read -r line; do
if [[ ! -z "$line" ]]; then
echo "::error::$line"
exit_code=1
fi
done <<< $(dotnet codefilesanity)
exit $exit_code
- name: InspectCode
uses: JetBrains/ReSharper-InspectCode@v0.13
with:
tool-version: 2026.1.0.1
# this is WTF tier but if you don't specify *both* of these the defaults assume `build: true`
build: false
no-build: true
solution: ./osu.Desktop.slnf
caches-home: inspectcode
verbosity: WARN
test:
name: Test
runs-on: ${{matrix.os.fullname}}
env:
OSU_EXECUTION_MODE: ${{matrix.threadingMode}}
strategy:
fail-fast: false
matrix:
os:
- { prettyname: Windows, fullname: windows-2025-vs2026 }
# macOS runner performance has gotten unbearably slow so let's turn them off temporarily.
# - { prettyname: macOS, fullname: macos-latest }
- { prettyname: Linux, fullname: ubuntu-latest }
threadingMode: ['MultiThreaded']
# Split tests into two parallel groups so Windows runners (which are ~2-3× slower than
# Linux) finish each group in ~60 min rather than timing out on the full suite at 120 min.
testSuite:
- name: game
dlls: >-
osu.Game.Tests/bin/Debug/**/osu.Game.Tests.dll
osu.Game.Tournament.Tests/bin/Debug/**/osu.Game.Tournament.Tests.dll
Templates/**/*.Tests/bin/Debug/**/*.Tests.dll
- name: rulesets
dlls: >-
osu.Game.Rulesets.Osu.Tests/bin/Debug/**/osu.Game.Rulesets.Osu.Tests.dll
osu.Game.Rulesets.Taiko.Tests/bin/Debug/**/osu.Game.Rulesets.Taiko.Tests.dll
osu.Game.Rulesets.Catch.Tests/bin/Debug/**/osu.Game.Rulesets.Catch.Tests.dll
osu.Game.Rulesets.Mania.Tests/bin/Debug/**/osu.Game.Rulesets.Mania.Tests.dll
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install .NET 10.0.x
uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"
- name: Cache NuGet packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Authenticate to GitHub Packages
run: dotnet nuget update source winnerspiros-github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text
- name: Compile
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
- name: Test
run: >
dotnet test
${{matrix.testSuite.dlls}}
--logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}-${{matrix.testSuite.name}}.trx"
--
NUnit.ConsoleOut=0
# Upload results even when the test step fails (non-cancellation failure still satisfies !cancelled()).
# https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#cancelled
- name: Upload Test Results
uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}-${{matrix.testSuite.name}}
path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}-${{matrix.testSuite.name}}.trx
test-results:
name: Test results
runs-on: ubuntu-latest
# we want to wait for the `test` job to complete, but run regardless of whether it succeeds or fails
# https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#example-not-requiring-successful-dependent-jobs
if: ${{ !cancelled() }}
needs: test
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download results
uses: actions/download-artifact@v8
with:
pattern: osu-test-results-*
merge-multiple: true
- name: Add test results summary to workflow run
uses: dorny/test-reporter@v3.0.0
with:
name: Results
path: "*.trx"
reporter: dotnet-trx
list-suites: 'failed'
list-tests: 'failed'
use-actions-summary: 'true'
build-only-android:
name: Build only (Android)
runs-on: windows-2025-vs2026
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup JDK 11
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 11
- name: Install .NET 10.0.x
uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"
- name: Cache NuGet packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Authenticate to GitHub Packages
run: dotnet nuget update source winnerspiros-github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text
- name: Install .NET workloads
run: dotnet workload install android
- name: Compile
run: dotnet build -c Debug osu.Android.slnf
build-only-ios:
name: Build only (iOS)
runs-on: macos-26
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install .NET 10.0.x
uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"
- name: Cache NuGet packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Authenticate to GitHub Packages
run: dotnet nuget update source winnerspiros-github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text
# https://github.com/dotnet/macios/issues/19157
# https://github.com/actions/runner-images/issues/12758
- name: Use Xcode 26.5
run: |
# Pin to Xcode 26.5 — the .NET iOS workload (net10.0_26.5) requires exactly Xcode 26.5.
# Fix: MacOSX.sdk in Xcode 26.5 is a minimal stub. Replace it with a symlink to the
# real versioned SDK found across Xcode installs. Real SDKs have hundreds of headers;
# stubs have ≤1.
PREFERRED_XCODE="/Applications/Xcode_26.5.app"
REAL_SDK=""
REAL_XCODE=""
# Search ALL Xcode_26.x.app installs for versioned MacOSX[N].sdk.
# Validate by usr/include header count: stubs ≤1 file, real SDKs have hundreds.
for xapp in $(ls -d /Applications/Xcode_26.*.app 2>/dev/null | sort -rV); do
sdk_dir="$xapp/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs"
[ -d "$sdk_dir" ] || continue
for sdk in $(ls "$sdk_dir" 2>/dev/null | grep '^MacOSX[0-9].*\.sdk$' | sort -rV); do
cand="$sdk_dir/$sdk"
cand_real=$(python3 -c "import os, sys; print(os.path.realpath(sys.argv[1]))" "$cand" 2>/dev/null || echo "$cand")
# Skip candidates that resolve back to the unversioned MacOSX.sdk alias.
# Re-linking MacOSX.sdk to itself creates a broken self-referential symlink.
[ "$(basename "$cand_real")" = "MacOSX.sdk" ] && continue
hdr_count=$(ls "$cand_real/usr/include" 2>/dev/null | wc -l | tr -d ' ')
if [ "${hdr_count:-0}" -gt 5 ]; then
REAL_SDK="$cand_real"
REAL_XCODE="$xapp"
break 2
fi
done
done
# Fallback: CommandLineTools
if [ -z "$REAL_SDK" ]; then
for sdk in $(ls /Library/Developer/CommandLineTools/SDKs 2>/dev/null | grep '^MacOSX[0-9].*\.sdk$' | sort -rV); do
cand="/Library/Developer/CommandLineTools/SDKs/$sdk"
cand_real=$(python3 -c "import os, sys; print(os.path.realpath(sys.argv[1]))" "$cand" 2>/dev/null || echo "$cand")
[ "$(basename "$cand_real")" = "MacOSX.sdk" ] && continue
hdr_count=$(ls "$cand_real/usr/include" 2>/dev/null | wc -l | tr -d ' ')
if [ "${hdr_count:-0}" -gt 5 ]; then
REAL_SDK="$cand_real"
break
fi
done
fi
# Switch to preferred Xcode if it exists, otherwise the one that has the real SDK.
if [ -d "$PREFERRED_XCODE" ]; then
sudo xcode-select -switch "$PREFERRED_XCODE"
elif [ -n "$REAL_XCODE" ]; then
sudo xcode-select -switch "$REAL_XCODE"
echo "Note: Xcode_26.5.app not found; using $REAL_XCODE for xcode-select"
fi
# Replace stub MacOSX.sdk in the active Xcode with a symlink to the real versioned SDK.
if [ -n "$REAL_SDK" ]; then
ACTIVE_DEV="$(xcode-select -p)"
ACTIVE_SDKS_DIR="$ACTIVE_DEV/Platforms/MacOSX.platform/Developer/SDKs"
MACOS_SDK="$ACTIVE_SDKS_DIR/MacOSX.sdk"
# Resolve REAL_SDK to its canonical (symlink-free) path to prevent ELOOP errors.
REAL_SDK_CANON=$(python3 -c "import os, sys; print(os.path.realpath(sys.argv[1]))" "$REAL_SDK" 2>/dev/null || echo "$REAL_SDK")
if [ "$REAL_SDK_CANON" = "$MACOS_SDK" ]; then
echo "WARNING: resolved SDK points to active MacOSX.sdk alias; skipping relink to avoid self-referential symlink"
else
sudo rm -rf "$MACOS_SDK"
sudo ln -sfn "$REAL_SDK_CANON" "$MACOS_SDK"
echo "Created MacOSX.sdk symlink -> $REAL_SDK_CANON"
fi
else
echo "WARNING: no valid macOS SDK found; build may fail"
fi
- name: Install .NET Workloads
run: dotnet workload install ios
- name: Build
run: dotnet build -c Debug osu.iOS.slnf