diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 985fc09df399..c4ba6e51430a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -2,32 +2,26 @@ "version": 1, "isRoot": true, "tools": { - "dotnet-format": { - "version": "3.1.37601", - "commands": [ - "dotnet-format" - ] - }, "jetbrains.resharper.globaltools": { - "version": "2020.3.2", + "version": "2023.3.3", "commands": [ "jb" ] }, "nvika": { - "version": "2.2.0", + "version": "3.0.0", "commands": [ "nvika" ] }, "codefilesanity": { - "version": "0.0.36", + "version": "0.0.37", "commands": [ "CodeFileSanity" ] }, "ppy.localisationanalyser.tools": { - "version": "2021.1210.0", + "version": "2024.802.0", "commands": [ "localisation" ] diff --git a/.editorconfig b/.editorconfig index be5652954b47..7aecde95eee9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,17 @@ # EditorConfig is awesome: http://editorconfig.org root = true +[*.{csproj,props,targets}] +charset = utf-8-bom +end_of_line = crlf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[g_*.cs] +generated_code = true + [*.cs] end_of_line = crlf insert_final_newline = true @@ -8,8 +19,19 @@ indent_style = space indent_size = 4 trim_trailing_whitespace = true +#license header +file_header_template = Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\nSee the LICENCE file in the repository root for full licence text. + #Roslyn naming styles +#PascalCase for public and protected members +dotnet_naming_style.pascalcase.capitalization = pascal_case +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.public_members_pascalcase.severity = error +dotnet_naming_rule.public_members_pascalcase.symbols = public_members +dotnet_naming_rule.public_members_pascalcase.style = pascalcase + #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case @@ -172,23 +194,16 @@ csharp_style_prefer_index_operator = false:silent csharp_style_prefer_range_operator = false:silent csharp_style_prefer_switch_expression = false:none -#Supressing roslyn built-in analyzers -# Suppress: EC112 - -#Private method is unused -dotnet_diagnostic.IDE0051.severity = silent -#Private member is unused -dotnet_diagnostic.IDE0052.severity = silent - -#Rules for disposable -dotnet_diagnostic.IDE0067.severity = none -dotnet_diagnostic.IDE0068.severity = none -dotnet_diagnostic.IDE0069.severity = none +csharp_style_namespace_declarations = block_scoped:warning -#Disable operator overloads requiring alternate named methods -dotnet_diagnostic.CA2225.severity = none +#Style - C# 12 features +csharp_style_prefer_primary_constructors = false -# Banned APIs -dotnet_diagnostic.RS0030.severity = error +[*.{yaml,yml}] +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +dotnet_diagnostic.OLOC001.words_in_name = 5 dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text. diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000000..d35d4be41239 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,10 @@ +# Normalize all the line endings +32a74f95a5c80a0ed18e693f13a47522099df5c3 +# Partial everything +7bc8908ca9c026fed1d831eb6e58df7624a8d614 +# Add a few more missing partial specs +212d78865a6b5f091173a347bad5686834d1d5fe +# Add partial specs in mobile projects too +00c11b2b4e389e48f3995d63484a6bc66a7afbdb +# Mass NRT enabling +0ab0c52ad577b3e7b406d09fa6056a56ff997c3e diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml new file mode 100644 index 000000000000..a8a5d5e64b20 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-issue.yml @@ -0,0 +1,75 @@ +name: Bug report +description: Report a very clearly broken issue. +body: + - type: markdown + attributes: + value: | + # osu! bug report + + Important to note that your issue may have already been reported before. Please check: + - Pinned issues, at the top of https://github.com/ppy/osu/issues. + - Current open `priority:0` issues, filterable [here](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0). + - And most importantly, search for your issue both in the [issue listing](https://github.com/ppy/osu/issues) and the [Q&A discussion listing](https://github.com/ppy/osu/discussions/categories/q-a). If you find that it already exists, respond with a reaction or add any further information that may be helpful. + + # ATTENTION LINUX USERS + + If you are having an issue and it is hardware related, **please open a [q&a discussion](https://github.com/ppy/osu/discussions/categories/q-a)** instead of an issue. There's a high chance your issue is due to your system configuration, and not our software. + + - type: dropdown + attributes: + label: Type + options: + - Crash to desktop + - Game behaviour + - Performance + - Cosmetic + - Other + validations: + required: true + - type: textarea + attributes: + label: Bug description + description: How did you find the bug? Any additional details that might help? + validations: + required: true + - type: textarea + attributes: + label: Screenshots or videos + description: Add screenshots or videos that show the bug here. + placeholder: Drag and drop the screenshots/videos into this box. + validations: + required: false + - type: input + attributes: + label: Version + description: The version you encountered this bug on. This is shown at the end of the settings overlay. + validations: + required: true + - type: markdown + attributes: + value: | + ## Logs + + Attaching log files is required for **every** issue, regardless of whether you deem them required or not. See instructions below on how to find them. + + ### Desktop platforms + + If the game has not yet been closed since you found the bug: + 1. Head on to game settings and click on "Export logs" + 2. Click the notification to locate the file + 3. Drag the generated `.zip` files into the github issue window + + ![export logs button](https://github.com/ppy/osu/assets/191335/cbfa5550-b7ed-4c5c-8dd0-8b87cc90ad9b) + + ### Mobile platforms + + The places to find the logs on mobile platforms are as follows: + - *On Android*, navigate to `Android/data/sh.ppy.osulazer/files/logs` using a file browser app. + - *On iOS*, connect your device to a PC and copy the `logs` directory from the app's document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer) + + - type: textarea + attributes: + label: Logs + placeholder: Drag and drop the log files into this box. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 47a6a4c3d374..ec57232126bd 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Help url: https://github.com/ppy/osu/discussions/categories/q-a - about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section! + about: osu! not working or performing as you'd expect? Not sure it's a bug? Check the Q&A section! - name: Suggestions or feature request url: https://github.com/ppy/osu/discussions/categories/ideas about: Got something you think should change or be added? Search for or start a new discussion! diff --git a/.github/workflows/_diffcalc_processor.yml b/.github/workflows/_diffcalc_processor.yml new file mode 100644 index 000000000000..4e221d05509a --- /dev/null +++ b/.github/workflows/_diffcalc_processor.yml @@ -0,0 +1,228 @@ +name: "🔒diffcalc (do not use)" + +on: + workflow_call: + inputs: + id: + type: string + head-sha: + type: string + pr-url: + type: string + pr-text: + type: string + dispatch-inputs: + type: string + outputs: + target: + description: The comparison target. + value: ${{ jobs.generator.outputs.target }} + sheet: + description: The comparison spreadsheet. + value: ${{ jobs.generator.outputs.sheet }} + secrets: + DIFFCALC_GOOGLE_CREDENTIALS: + required: true + +env: + GENERATOR_DIR: ${{ github.workspace }}/${{ inputs.id }} + GENERATOR_ENV: ${{ github.workspace }}/${{ inputs.id }}/.env + +defaults: + run: + shell: bash -euo pipefail {0} + +jobs: + generator: + name: Run + runs-on: self-hosted + timeout-minutes: 720 + + outputs: + target: ${{ steps.run.outputs.target }} + sheet: ${{ steps.run.outputs.sheet }} + + steps: + - name: Checkout diffcalc-sheet-generator + uses: actions/checkout@v4 + with: + path: ${{ inputs.id }} + repository: 'smoogipoo/diffcalc-sheet-generator' + + - name: Add base environment + env: + GOOGLE_CREDS_FILE: ${{ github.workspace }}/${{ inputs.id }}/google-credentials.json + VARS_JSON: ${{ (vars != null && toJSON(vars)) || '' }} + run: | + # Required by diffcalc-sheet-generator + cp '${{ env.GENERATOR_DIR }}/.env.sample' "${{ env.GENERATOR_ENV }}" + + # Add Google credentials + echo '${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }}' | base64 -d > "${{ env.GOOGLE_CREDS_FILE }}" + + # Add repository variables + echo "${VARS_JSON}" | jq -c '. | to_entries | .[]' | while read -r line; do + opt=$(jq -r '.key' <<< ${line}) + val=$(jq -r '.value' <<< ${line}) + + if [[ "${opt}" =~ ^DIFFCALC_ ]]; then + optNoPrefix=$(echo "${opt}" | cut -d '_' -f2-) + sed -i "s;^${optNoPrefix}=.*$;${optNoPrefix}=${val};" "${{ env.GENERATOR_ENV }}" + fi + done + + - name: Add HEAD environment + run: | + sed -i "s;^OSU_A=.*$;OSU_A=${{ inputs.head-sha }};" "${{ env.GENERATOR_ENV }}" + + - name: Add pull-request environment + if: ${{ inputs.pr-url != '' }} + run: | + sed -i "s;^OSU_B=.*$;OSU_B=${{ inputs.pr-url }};" "${{ env.GENERATOR_ENV }}" + + - name: Add comment environment + if: ${{ inputs.pr-text != '' }} + env: + PR_TEXT: ${{ inputs.pr-text }} + run: | + # Add comment environment + echo "${PR_TEXT}" | sed -r 's/\r$//' | { grep -E '^\w+=' || true; } | while read -r line; do + opt=$(echo "${line}" | cut -d '=' -f1) + sed -i "s;^${opt}=.*$;${line};" "${{ env.GENERATOR_ENV }}" + done + + - name: Add dispatch environment + if: ${{ inputs.dispatch-inputs != '' }} + env: + DISPATCH_INPUTS_JSON: ${{ inputs.dispatch-inputs }} + run: | + function get_input() { + echo "${DISPATCH_INPUTS_JSON}" | jq -r ".\"$1\"" + } + + osu_a=$(get_input 'osu-a') + osu_b=$(get_input 'osu-b') + ruleset=$(get_input 'ruleset') + generators=$(get_input 'generators') + difficulty_calculator_a=$(get_input 'difficulty-calculator-a') + difficulty_calculator_b=$(get_input 'difficulty-calculator-b') + score_processor_a=$(get_input 'score-processor-a') + score_processor_b=$(get_input 'score-processor-b') + converts=$(get_input 'converts') + ranked_only=$(get_input 'ranked-only') + + sed -i "s;^OSU_B=.*$;OSU_B=${osu_b};" "${{ env.GENERATOR_ENV }}" + sed -i "s/^RULESET=.*$/RULESET=${ruleset}/" "${{ env.GENERATOR_ENV }}" + sed -i "s/^GENERATORS=.*$/GENERATORS=${generators}/" "${{ env.GENERATOR_ENV }}" + + if [[ "${osu_a}" != 'latest' ]]; then + sed -i "s;^OSU_A=.*$;OSU_A=${osu_a};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${difficulty_calculator_a}" != 'latest' ]]; then + sed -i "s;^DIFFICULTY_CALCULATOR_A=.*$;DIFFICULTY_CALCULATOR_A=${difficulty_calculator_a};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${difficulty_calculator_b}" != 'latest' ]]; then + sed -i "s;^DIFFICULTY_CALCULATOR_B=.*$;DIFFICULTY_CALCULATOR_B=${difficulty_calculator_b};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${score_processor_a}" != 'latest' ]]; then + sed -i "s;^SCORE_PROCESSOR_A=.*$;SCORE_PROCESSOR_A=${score_processor_a};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${score_processor_b}" != 'latest' ]]; then + sed -i "s;^SCORE_PROCESSOR_B=.*$;SCORE_PROCESSOR_B=${score_processor_b};" "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${converts}" == 'true' ]]; then + sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=0/' "${{ env.GENERATOR_ENV }}" + else + sed -i 's/^NO_CONVERTS=.*$/NO_CONVERTS=1/' "${{ env.GENERATOR_ENV }}" + fi + + if [[ "${ranked_only}" == 'true' ]]; then + sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=1/' "${{ env.GENERATOR_ENV }}" + else + sed -i 's/^RANKED_ONLY=.*$/RANKED_ONLY=0/' "${{ env.GENERATOR_ENV }}" + fi + + - name: Query latest scores + id: query-scores + run: | + ruleset=$(cat ${{ env.GENERATOR_ENV }} | grep -E '^RULESET=' | cut -d '=' -f2-) + performance_data_name=$(curl -s "https://data.ppy.sh/" | grep "performance_${ruleset}_top_1000\b" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g') + + echo "TARGET_DIR=${{ env.GENERATOR_DIR }}/sql/${ruleset}" >> "${GITHUB_OUTPUT}" + echo "DATA_NAME=${performance_data_name}" >> "${GITHUB_OUTPUT}" + echo "DATA_PKG=${performance_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" + + - name: Restore score cache + id: restore-score-cache + uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 + with: + path: ${{ steps.query-scores.outputs.DATA_PKG }} + key: ${{ steps.query-scores.outputs.DATA_NAME }} + + - name: Download scores + if: steps.restore-score-cache.outputs.cache-hit != 'true' + run: | + wget -q -O "${{ steps.query-scores.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query-scores.outputs.DATA_PKG }}" + + - name: Extract scores + run: | + tar -I lbzip2 -xf "${{ steps.query-scores.outputs.DATA_PKG }}" + rm -r "${{ steps.query-scores.outputs.TARGET_DIR }}" + mv "${{ steps.query-scores.outputs.DATA_NAME }}" "${{ steps.query-scores.outputs.TARGET_DIR }}" + + - name: Query latest beatmaps + id: query-beatmaps + run: | + beatmaps_data_name=$(curl -s "https://data.ppy.sh/" | grep "osu_files" | tail -1 | awk -F "'" '{print $2}' | sed 's/\.tar\.bz2//g') + + echo "TARGET_DIR=${{ env.GENERATOR_DIR }}/beatmaps" >> "${GITHUB_OUTPUT}" + echo "DATA_NAME=${beatmaps_data_name}" >> "${GITHUB_OUTPUT}" + echo "DATA_PKG=${beatmaps_data_name}.tar.bz2" >> "${GITHUB_OUTPUT}" + + - name: Restore beatmap cache + id: restore-beatmap-cache + uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2 + with: + path: ${{ steps.query-beatmaps.outputs.DATA_PKG }} + key: ${{ steps.query-beatmaps.outputs.DATA_NAME }} + + - name: Download beatmap + if: steps.restore-beatmap-cache.outputs.cache-hit != 'true' + run: | + wget -q -O "${{ steps.query-beatmaps.outputs.DATA_PKG }}" "https://data.ppy.sh/${{ steps.query-beatmaps.outputs.DATA_PKG }}" + + - name: Extract beatmap + run: | + tar -I lbzip2 -xf "${{ steps.query-beatmaps.outputs.DATA_PKG }}" + rm -r "${{ steps.query-beatmaps.outputs.TARGET_DIR }}" + mv "${{ steps.query-beatmaps.outputs.DATA_NAME }}" "${{ steps.query-beatmaps.outputs.TARGET_DIR }}" + + - name: Run + id: run + run: | + # Add the GitHub token. This needs to be done here because it's unique per-job. + sed -i 's/^GH_TOKEN=.*$/GH_TOKEN=${{ github.token }}/' "${{ env.GENERATOR_ENV }}" + + cd "${{ env.GENERATOR_DIR }}" + + docker compose up --build --detach + docker compose logs --follow & + docker compose wait generator + + link=$(docker compose logs --tail 10 generator | grep 'http' | sed -E 's/^.*(http.*)$/\1/') + target=$(cat "${{ env.GENERATOR_ENV }}" | grep -E '^OSU_B=' | cut -d '=' -f2-) + + echo "target=${target}" >> "${GITHUB_OUTPUT}" + echo "sheet=${link}" >> "${GITHUB_OUTPUT}" + + - name: Shutdown + if: ${{ always() }} + run: | + cd "${{ env.GENERATOR_DIR }}" + docker compose down --volumes + rm -rf "${{ env.GENERATOR_DIR }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c52802cf653..d8645d728e0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,59 @@ 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) jobs: + inspect-code: + name: Code Quality + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install .NET 8.0.x + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.0.x" + + - name: Restore Tools + run: dotnet tool restore + + - name: Restore Packages + run: dotnet restore osu.Desktop.slnf + + - name: Restore inspectcode cache + uses: actions/cache@v4 + 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 + run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --no-build --output="inspectcodereport.xml" --caches-home="inspectcode" --verbosity=WARN + + - name: NVika + run: dotnet nvika parsereport "${{github.workspace}}/inspectcodereport.xml" --treatwarningsaserrors + test: name: Test runs-on: ${{matrix.os.fullname}} @@ -12,39 +64,31 @@ jobs: matrix: os: - { prettyname: Windows, fullname: windows-latest } - - { prettyname: macOS, fullname: macos-latest } + # 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: ['SingleThread', 'MultiThreaded'] - timeout-minutes: 60 + timeout-minutes: 120 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Install .NET 5.0.x - uses: actions/setup-dotnet@v1 + - name: Install .NET 8.0.x + uses: actions/setup-dotnet@v4 with: - dotnet-version: "5.0.x" - - # FIXME: libavformat is not included in Ubuntu. Let's fix that. - # https://github.com/ppy/osu-framework/issues/4349 - # Remove this once https://github.com/actions/virtual-environments/issues/3306 has been resolved. - - name: Install libavformat-dev - if: ${{matrix.os.fullname == 'ubuntu-latest'}} - run: | - sudo apt-get update && \ - sudo apt-get -y install libavformat-dev + dotnet-version: "8.0.x" - name: Compile run: dotnet build -c Debug -warnaserror osu.Desktop.slnf - name: Test - run: dotnet test $pwd/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" + run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" -- NUnit.ConsoleOut=0 shell: pwsh # Attempt to upload results even if test fails. # https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always - name: Upload Test Results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: ${{ always() }} with: name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} @@ -52,29 +96,31 @@ jobs: build-only-android: name: Build only (Android) - runs-on: macos-latest + runs-on: windows-latest timeout-minutes: 60 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - # Pin Xamarin.Android version to 11.2 for now to avoid build failures caused by a Xamarin-side regression. - # See: https://github.com/xamarin/xamarin-android/issues/6284 - # This can be removed/reverted when the fix makes it to upstream and is deployed on github runners. - - name: Set default Xamarin SDK version - run: | - $VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2 + - name: Setup JDK 11 + uses: actions/setup-java@v4 + with: + distribution: microsoft + java-version: 11 - - name: Install .NET 5.0.x - uses: actions/setup-dotnet@v1 + - name: Install .NET 8.0.x + uses: actions/setup-dotnet@v4 with: - dotnet-version: "5.0.x" + dotnet-version: "8.0.x" - # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono - # cannot accept .sln(f) files as arguments. - # Build just the main game for now. - - name: Build - run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug + - name: Install .NET workloads + # since windows image 20241113.3.0, not specifying a version here + # installs the .NET 7 version of android workload for very unknown reasons. + # revisit once we upgrade to .NET 9, it's probably fixed there. + run: dotnet workload install android --version (dotnet --version) + + - name: Compile + run: dotnet build -c Debug osu.Android.slnf build-only-ios: name: Build only (iOS) @@ -82,63 +128,15 @@ jobs: timeout-minutes: 60 steps: - name: Checkout - uses: actions/checkout@v2 - - - name: Install .NET 5.0.x - uses: actions/setup-dotnet@v1 - with: - dotnet-version: "5.0.x" - - # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono - # cannot accept .sln(f) files as arguments. - # Build just the main game for now. - - name: Build - run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug - - inspect-code: - name: Code Quality - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - # FIXME: Tools won't run in .NET 5.0 unless you install 3.1.x LTS side by side. - # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e - - name: Install .NET 3.1.x LTS - uses: actions/setup-dotnet@v1 - with: - dotnet-version: "3.1.x" + uses: actions/checkout@v4 - - name: Install .NET 5.0.x - uses: actions/setup-dotnet@v1 + - name: Install .NET 8.0.x + uses: actions/setup-dotnet@v4 with: - dotnet-version: "5.0.x" + dotnet-version: "8.0.x" - - name: Restore Tools - run: dotnet tool restore - - - name: Restore Packages - run: dotnet restore + - name: Install .NET Workloads + run: dotnet workload install ios --from-rollback-file https://raw.githubusercontent.com/ppy/osu-framework/refs/heads/master/workloads.json - - 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 - - # Temporarily disabled due to test failures, but it won't work anyway until the tool is upgraded. - # - name: .NET Format (Dry Run) - # run: dotnet format --dry-run --check - - - name: InspectCode - run: dotnet jb inspectcode $(pwd)/osu.Desktop.slnf --output=$(pwd)/inspectcodereport.xml --cachesDir=$(pwd)/inspectcode --verbosity=WARN - - - name: NVika - run: dotnet nvika parsereport "${{github.workspace}}/inspectcodereport.xml" --treatwarningsaserrors + - name: Build + run: dotnet build -c Debug osu.iOS diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 9e11ab6663e2..8461208a2ea3 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -1,206 +1,196 @@ -# Listens for new PR comments containing !pp check [id], and runs a diffcalc comparison against master. -# Usage: -# !pp check 0 | Runs only the osu! ruleset. -# !pp check 0 2 | Runs only the osu! and catch rulesets. +# ## Description # +# Uses [diffcalc-sheet-generator](https://github.com/smoogipoo/diffcalc-sheet-generator) to run two builds of osu and generate an SR/PP/Score comparison spreadsheet. +# +# ## Requirements +# +# Self-hosted runner with installed: +# - `docker >= 20.10.16` +# - `docker-compose >= 2.5.1` +# - `lbzip2` +# - `jq` +# +# ## Usage +# +# The workflow can be run in two ways: +# 1. Via workflow dispatch. +# 2. By an owner of the repository posting a pull request or issue comment containing `!diffcalc`. +# For pull requests, the workflow will assume the pull request as the target to compare against (i.e. the `OSU_B` variable). +# Any lines in the comment of the form `KEY=VALUE` are treated as variables for the generator. +# +# ## Google Service Account +# +# Spreadsheets are uploaded to a Google Service Account, and exposed with read-only permissions to the wider audience. +# +# 1. Create a project at https://console.cloud.google.com +# 2. Enable the `Google Sheets` and `Google Drive` APIs. +# 3. Create a Service Account +# 4. Generate a key in the JSON format. +# 5. Encode the key as base64 and store as an **actions secret** with name **`DIFFCALC_GOOGLE_CREDENTIALS`** +# +# ## Environment variables +# +# The default environment may be configured via **actions variables**. +# +# Refer to [the sample environment](https://github.com/smoogipoo/diffcalc-sheet-generator/blob/master/.env.sample), and prefix each variable with `DIFFCALC_` (e.g. `DIFFCALC_THREADS`, `DIFFCALC_INNODB_BUFFER_SIZE`, etc...). + +name: Run difficulty calculation comparison + +run-name: "${{ github.event_name == 'workflow_dispatch' && format('Manual run: {0}', inputs.osu-b) || 'Automatic comment trigger' }}" -name: Difficulty Calculation on: issue_comment: types: [ created ] + workflow_dispatch: + inputs: + osu-b: + description: "The target build of ppy/osu" + type: string + required: true + ruleset: + description: "The ruleset to process" + type: choice + required: true + options: + - osu + - taiko + - catch + - mania + converts: + description: "Include converted beatmaps" + type: boolean + required: false + default: true + ranked-only: + description: "Only ranked beatmaps" + type: boolean + required: false + default: true + generators: + description: "Comma-separated list of generators (available: [sr, pp, score])" + type: string + required: false + default: 'pp,sr' + osu-a: + description: "The source build of ppy/osu" + type: string + required: false + default: 'latest' + difficulty-calculator-a: + description: "The source build of ppy/osu-difficulty-calculator" + type: string + required: false + default: 'latest' + difficulty-calculator-b: + description: "The target build of ppy/osu-difficulty-calculator" + type: string + required: false + default: 'latest' + score-processor-a: + description: "The source build of ppy/osu-queue-score-statistics" + type: string + required: false + default: 'latest' + score-processor-b: + description: "The target build of ppy/osu-queue-score-statistics" + type: string + required: false + default: 'latest' + +permissions: + pull-requests: write env: - CONCURRENCY: 4 - ALLOW_DOWNLOAD: 1 - SAVE_DOWNLOADED: 1 - SKIP_INSERT_ATTRIBUTES: 1 + EXECUTION_ID: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} -jobs: - metadata: - name: Check for requests - runs-on: self-hosted - if: github.event.issue.pull_request && contains(github.event.comment.body, '!pp check') && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') - outputs: - matrix: ${{ steps.generate-matrix.outputs.matrix }} - continue: ${{ steps.generate-matrix.outputs.continue }} - steps: - - name: Construct build matrix - id: generate-matrix - run: | - if [[ "${{ github.event.comment.body }}" =~ "osu" ]] ; then - MATRIX_PROJECTS_JSON+='{ "name": "osu", "id": 0 },' - fi - if [[ "${{ github.event.comment.body }}" =~ "taiko" ]] ; then - MATRIX_PROJECTS_JSON+='{ "name": "taiko", "id": 1 },' - fi - if [[ "${{ github.event.comment.body }}" =~ "catch" ]] ; then - MATRIX_PROJECTS_JSON+='{ "name": "catch", "id": 2 },' - fi - if [[ "${{ github.event.comment.body }}" =~ "mania" ]] ; then - MATRIX_PROJECTS_JSON+='{ "name": "mania", "id": 3 },' - fi - - if [[ "${MATRIX_PROJECTS_JSON}" != "" ]]; then - MATRIX_JSON="{ \"ruleset\": [ ${MATRIX_PROJECTS_JSON} ] }" - echo "${MATRIX_JSON}" - CONTINUE="yes" - else - CONTINUE="no" - fi +defaults: + run: + shell: bash -euo pipefail {0} - echo "::set-output name=continue::${CONTINUE}" - echo "::set-output name=matrix::${MATRIX_JSON}" - diffcalc: - name: Run - runs-on: self-hosted - timeout-minutes: 1440 - if: needs.metadata.outputs.continue == 'yes' - needs: metadata - strategy: - matrix: ${{ fromJson(needs.metadata.outputs.matrix) }} +jobs: + check-permissions: + name: Check permissions + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') }} steps: - - name: Verify MySQL connection from host - run: | - mysql -e "SHOW DATABASES" - - - name: Drop previous databases + - name: Check permissions run: | - for db in osu_master osu_pr - do - mysql -e "DROP DATABASE IF EXISTS $db" + ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte tsunyoku stanriders) + for i in "${ALLOWED_USERS[@]}"; do + if [[ "${{ github.actor }}" == "$i" ]]; then + exit 0 + fi done + exit 1 + + run-diffcalc: + name: Run spreadsheet generator + needs: check-permissions + uses: ./.github/workflows/_diffcalc_processor.yml + with: + # Can't reference env... Why GitHub, WHY? + id: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }} + head-sha: https://github.com/${{ github.repository }}/commit/${{ github.event.pull_request.head.sha || github.sha }} + pr-url: ${{ github.event.issue.pull_request.html_url || '' }} + pr-text: ${{ github.event.comment.body || '' }} + dispatch-inputs: ${{ (github.event.type == 'workflow_dispatch' && toJSON(inputs)) || '' }} + secrets: + DIFFCALC_GOOGLE_CREDENTIALS: ${{ secrets.DIFFCALC_GOOGLE_CREDENTIALS }} + + create-comment: + name: Create PR comment + needs: check-permissions + runs-on: ubuntu-latest + if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }} + steps: + - name: Create comment + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 + with: + comment_tag: ${{ env.EXECUTION_ID }} + message: | + Difficulty calculation queued -- please wait! (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) - - name: Create directory structure - run: | - mkdir -p $GITHUB_WORKSPACE/master/ - mkdir -p $GITHUB_WORKSPACE/pr/ + *This comment will update on completion* - - name: Get upstream branch # https://akaimo.hatenablog.jp/entry/2020/05/16/101251 - id: upstreambranch - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + output-cli: + name: Info + needs: run-diffcalc + runs-on: ubuntu-latest + steps: + - name: Output info run: | - echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" - echo "::set-output name=repo::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')" - - # Checkout osu - - name: Checkout osu (master) - uses: actions/checkout@v2 - with: - path: 'master/osu' - - name: Checkout osu (pr) - uses: actions/checkout@v2 - with: - path: 'pr/osu' - repository: ${{ steps.upstreambranch.outputs.repo }} - ref: ${{ steps.upstreambranch.outputs.branchname }} - - - name: Checkout osu-difficulty-calculator (master) - uses: actions/checkout@v2 + echo "Target: ${{ needs.run-diffcalc.outputs.target }}" + echo "Spreadsheet: ${{ needs.run-diffcalc.outputs.sheet }}" + + update-comment: + name: Update PR comment + needs: [ create-comment, run-diffcalc ] + runs-on: ubuntu-latest + if: ${{ always() && needs.create-comment.result == 'success' }} + steps: + - name: Update comment on success + if: ${{ needs.run-diffcalc.result == 'success' }} + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: - repository: ppy/osu-difficulty-calculator - path: 'master/osu-difficulty-calculator' - - name: Checkout osu-difficulty-calculator (pr) - uses: actions/checkout@v2 + comment_tag: ${{ env.EXECUTION_ID }} + mode: recreate + message: | + Target: ${{ needs.run-diffcalc.outputs.target }} + Spreadsheet: ${{ needs.run-diffcalc.outputs.sheet }} + + - name: Update comment on failure + if: ${{ needs.run-diffcalc.result == 'failure' }} + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: - repository: ppy/osu-difficulty-calculator - path: 'pr/osu-difficulty-calculator' - - - name: Install .NET 5.0.x - uses: actions/setup-dotnet@v1 + comment_tag: ${{ env.EXECUTION_ID }} + mode: recreate + message: | + Difficulty calculation failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + - name: Update comment on cancellation + if: ${{ needs.run-diffcalc.result == 'cancelled' }} + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0 with: - dotnet-version: "5.0.x" - - # Sanity checks to make sure diffcalc is not run when incompatible. - - name: Build diffcalc (master) - run: | - cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator - ./UseLocalOsu.sh - dotnet build - - name: Build diffcalc (pr) - run: | - cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator - ./UseLocalOsu.sh - dotnet build - - - name: Download + import data - run: | - PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_top_1000 | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') - BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g') - - # Set env variable for further steps. - echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV - - cd $GITHUB_WORKSPACE - - echo "Downloading database dump $PERFORMANCE_DATA_NAME.." - wget -q -nc https://data.ppy.sh/$PERFORMANCE_DATA_NAME.tar.bz2 - echo "Extracting.." - tar -xf $PERFORMANCE_DATA_NAME.tar.bz2 - - echo "Downloading beatmap dump $BEATMAPS_DATA_NAME.." - wget -q -nc https://data.ppy.sh/$BEATMAPS_DATA_NAME.tar.bz2 - echo "Extracting.." - tar -xf $BEATMAPS_DATA_NAME.tar.bz2 - - cd $PERFORMANCE_DATA_NAME - - for db in osu_master osu_pr - do - echo "Setting up database $db.." - - mysql -e "CREATE DATABASE $db" - - echo "Importing beatmaps.." - cat osu_beatmaps.sql | mysql $db - echo "Importing beatmapsets.." - cat osu_beatmapsets.sql | mysql $db - - echo "Creating table structure.." - mysql $db -e 'CREATE TABLE `osu_beatmap_difficulty` ( - `beatmap_id` int unsigned NOT NULL, - `mode` tinyint NOT NULL DEFAULT 0, - `mods` int unsigned NOT NULL, - `diff_unified` float NOT NULL, - `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`beatmap_id`,`mode`,`mods`), - KEY `diff_sort` (`mode`,`mods`,`diff_unified`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;' - done - - - name: Run diffcalc (master) - env: - DB_NAME: osu_master - run: | - cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator/osu.Server.DifficultyCalculator - dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }} - - name: Run diffcalc (pr) - env: - DB_NAME: osu_pr - run: | - cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator/osu.Server.DifficultyCalculator - dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }} - - - name: Print diffs - run: | - mysql -e " - SELECT - m.beatmap_id, - m.mods, - b.filename, - m.diff_unified as 'sr_master', - p.diff_unified as 'sr_pr', - (p.diff_unified - m.diff_unified) as 'diff' - FROM osu_master.osu_beatmap_difficulty m - JOIN osu_pr.osu_beatmap_difficulty p - ON m.beatmap_id = p.beatmap_id - AND m.mode = p.mode - AND m.mods = p.mods - JOIN osu_pr.osu_beatmaps b - ON b.beatmap_id = p.beatmap_id - WHERE abs(m.diff_unified - p.diff_unified) > 0.1 - ORDER BY abs(m.diff_unified - p.diff_unified) - DESC - LIMIT 10000;" - - # Todo: Run ppcalc + comment_tag: ${{ env.EXECUTION_ID }} + mode: delete + message: '.' # Appears to be required by this action for non-error status code. diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml index 358cbda17a45..14f0208fc8b1 100644 --- a/.github/workflows/report-nunit.yml +++ b/.github/workflows/report-nunit.yml @@ -5,29 +5,40 @@ name: Annotate CI run with test results on: workflow_run: - workflows: ["Continuous Integration"] + workflows: [ "Continuous Integration" ] types: - completed + +permissions: + contents: read + actions: read + checks: write + jobs: annotate: name: Annotate CI run with test results runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion != 'cancelled' }} - strategy: - fail-fast: false - matrix: - os: - - { prettyname: Windows } - - { prettyname: macOS } - - { prettyname: Linux } - threadingMode: ['SingleThread', 'MultiThreaded'] timeout-minutes: 5 steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ github.event.workflow_run.repository.full_name }} + ref: ${{ github.event.workflow_run.head_sha }} + + - name: Download results + uses: actions/download-artifact@v4 + with: + pattern: osu-test-results-* + merge-multiple: true + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + - name: Annotate CI run with test results - uses: dorny/test-reporter@v1.4.2 + uses: dorny/test-reporter@v1.8.0 with: - artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} - name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}) + name: Results path: "*.trx" reporter: dotnet-trx list-suites: 'failed' diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml new file mode 100644 index 000000000000..be104d0fd3f2 --- /dev/null +++ b/.github/workflows/sentry-release.yml @@ -0,0 +1,29 @@ +name: Add Release to Sentry + +on: + push: + tags: + - '*' + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + sentry_release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Create Sentry release + uses: getsentry/action-release@v1 + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ppy + SENTRY_PROJECT: osu + SENTRY_URL: https://sentry.ppy.sh/ + with: + environment: production + version: osu@${{ github.ref_name }} diff --git a/.github/workflows/update-web-mod-definitions.yml b/.github/workflows/update-web-mod-definitions.yml new file mode 100644 index 000000000000..b19f03ad7dd3 --- /dev/null +++ b/.github/workflows/update-web-mod-definitions.yml @@ -0,0 +1,53 @@ +name: Update osu-web mod definitions +on: + push: + tags: + - '*' + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + update-mod-definitions: + name: Update osu-web mod definitions + runs-on: ubuntu-latest + steps: + - name: Install .NET 8.0.x + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.0.x" + + - name: Checkout ppy/osu + uses: actions/checkout@v4 + with: + path: osu + + - name: Checkout ppy/osu-tools + uses: actions/checkout@v4 + with: + repository: ppy/osu-tools + path: osu-tools + + - name: Checkout ppy/osu-web + uses: actions/checkout@v4 + with: + repository: ppy/osu-web + path: osu-web + + - name: Setup local game checkout for tools + run: ./UseLocalOsu.sh + working-directory: ./osu-tools + + - name: Regenerate mod definitions + run: dotnet run --project PerformanceCalculator -- mods > ../osu-web/database/mods.json + working-directory: ./osu-tools + + - name: Create pull request with changes + uses: peter-evans/create-pull-request@v6 + with: + title: Update mod definitions + body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}." + branch: update-mod-definitions + commit-message: Update mod definitions + path: osu-web + token: ${{ secrets.OSU_WEB_PULL_REQUEST_PAT }} diff --git a/.gitignore b/.gitignore index 5b19270ab9e6..1fec94d82baa 100644 --- a/.gitignore +++ b/.gitignore @@ -265,6 +265,8 @@ __pycache__/ .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf +.idea/*/.idea/projectSettingsUpdater.xml +.idea/*/.idea/encodings.xml # Generated files .idea/**/contentModel.xml @@ -339,4 +341,6 @@ inspectcode # Fody (pulled in by Realm) - schema file FodyWeavers.xsd -**/FodyWeavers.xml + +.idea/.idea.osu.Desktop/.idea/misc.xml +.idea/.idea.osu.Android/.idea/deploymentTargetDropDown.xml diff --git a/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml deleted file mode 100644 index 4bb9f4d2a0dc..000000000000 --- a/.idea/.idea.osu.Android/.idea/projectSettingsUpdater.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/encodings.xml b/.idea/.idea.osu.Desktop/.idea/encodings.xml deleted file mode 100644 index 15a15b218a29..000000000000 --- a/.idea/.idea.osu.Desktop/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/misc.xml b/.idea/.idea.osu.Desktop/.idea/misc.xml deleted file mode 100644 index 1d8c84d0afb8..000000000000 --- a/.idea/.idea.osu.Desktop/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml b/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml deleted file mode 100644 index 4bb9f4d2a0dc..000000000000 --- a/.idea/.idea.osu.Desktop/.idea/projectSettingsUpdater.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml index 498a710df920..a7a6649a4ff6 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml @@ -1,8 +1,8 @@ -