diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b32b334..01976a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,233 +9,287 @@ jobs: runs-on: ${{ matrix.os }} strategy: fail-fast: false + max-parallel: 2 matrix: include: + # Linux - os: ubuntu-latest name: Linux x64 toolchain: clang + artifact_name: rps-linux-x64 + archive_ext: tar.gz + vcpkg_triplet: x64-linux + + - os: ubuntu-24.04-arm + name: Linux arm64 + toolchain: clang + artifact_name: rps-linux-arm64 + archive_ext: tar.gz + vcpkg_triplet: arm64-linux + + # macOS + # - os: macos-15-large + # name: macOS Intel + # toolchain: clang + # artifact_name: rps-macos-intel + # archive_ext: zip + # vcpkg_triplet: x64-osx-release + - os: macos-15 name: macOS Apple Silicon toolchain: clang -# - os: windows-latest -# name: Windows MSVC -# toolchain: msvc + artifact_name: rps-macos-arm64 + archive_ext: zip + vcpkg_triplet: arm64-osx-release + + # Windows - os: windows-latest - name: Windows Clang + name: Windows x64 toolchain: clang + artifact_name: rps-win-x64 + archive_ext: zip + vcpkg_triplet: x64-windows-static + msvc_arch: x64 + + - os: windows-11-arm + name: Windows arm64 + toolchain: clang + artifact_name: rps-win-arm64 + archive_ext: zip + vcpkg_triplet: arm64-windows-static + msvc_arch: arm64 steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Build Tools (Linux) - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y cmake ninja-build g++ clang pkg-config - - - name: Install Build Tools (macOS) - if: runner.os == 'macOS' - run: | - brew install cmake ninja pkg-config - - - name: Setup ccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - key: ${{ runner.os }}-${{ matrix.toolchain }}-ccache - - - name: Setup MSVC Developer Command Prompt - if: runner.os == 'Windows' - uses: ilammy/msvc-dev-cmd@v1 - - - name: Select Windows storage for vcpkg - if: runner.os == 'Windows' - id: win_storage - shell: pwsh - run: | - $candidates = @() - foreach ($p in @($env:RUNNER_TEMP, $env:GITHUB_WORKSPACE, 'D:\', 'C:\')) { - if (-not $p) { continue } - if (-not (Test-Path $p)) { continue } - $resolved = (Resolve-Path $p).Path - $qualifier = Split-Path -Qualifier $resolved - $driveName = $qualifier.TrimEnd('\').TrimEnd(':') - $drive = Get-PSDrive -Name $driveName -ErrorAction SilentlyContinue - if ($drive) { - $candidates += [pscustomobject]@{ - Root = $qualifier - Free = [int64]$drive.Free + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Build Tools (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build g++ clang pkg-config + + - name: Install Build Tools (macOS) + if: runner.os == 'macOS' + run: | + brew install cmake ninja pkg-config + + - name: Setup ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ runner.os }}-${{ matrix.toolchain }}-ccache + + - name: Setup MSVC Developer Command Prompt + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.msvc_arch || 'x64' }} + + - name: Select Windows storage for vcpkg + if: runner.os == 'Windows' + id: win_storage + shell: pwsh + run: | + $candidates = @() + foreach ($p in @($env:RUNNER_TEMP, $env:GITHUB_WORKSPACE, 'D:\', 'C:\')) { + if (-not $p) { continue } + if (-not (Test-Path $p)) { continue } + $resolved = (Resolve-Path $p).Path + $qualifier = Split-Path -Qualifier $resolved + $driveName = $qualifier.TrimEnd('\').TrimEnd(':') + $drive = Get-PSDrive -Name $driveName -ErrorAction SilentlyContinue + if ($drive) { + $candidates += [pscustomobject]@{ + Root = $qualifier + Free = [int64]$drive.Free + } } } - } - - if (-not $candidates) { - throw "Could not determine a writable storage location for vcpkg." - } - - $best = $candidates | Sort-Object Free -Descending | Select-Object -First 1 - $base = Join-Path $best.Root "vcpkg-cache" - - $archives = Join-Path $base "archives" - $downloads = Join-Path $base "downloads" - $buildtrees = Join-Path $base "buildtrees" - $packages = Join-Path $base "packages" - - New-Item -ItemType Directory -Force -Path $archives, $downloads, $buildtrees, $packages | Out-Null - - "VCPKG_DEFAULT_BINARY_CACHE=$archives" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - "VCPKG_DOWNLOADS=$downloads" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - "vcpkg_cache_dir=$archives" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - "vcpkg_downloads_dir=$downloads" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - - # --- THE FIX: Create Junctions to redirect C: to D: --- - # 1. Remove existing directories if GitHub pre-created them - Remove-Item -Path "C:\vcpkg\buildtrees" -Recurse -Force -ErrorAction SilentlyContinue - Remove-Item -Path "C:\vcpkg\packages" -Recurse -Force -ErrorAction SilentlyContinue - - # 2. Create the junctions - New-Item -ItemType Junction -Path "C:\vcpkg\buildtrees" -Target $buildtrees | Out-Null - New-Item -ItemType Junction -Path "C:\vcpkg\packages" -Target $packages | Out-Null - - # --- PROOF OF REDIRECTION --- - Write-Host "Testing the Junctions..." - Set-Content -Path "C:\vcpkg\buildtrees\proof.txt" -Value "This is actually on the D drive!" - if (Test-Path "$buildtrees\proof.txt") { - Write-Host "SUCCESS: C:\vcpkg\buildtrees is successfully routing to the D: drive!" - } else { - Write-Error "FAILURE: Junction didn't work." - } - - $bestGb = [math]::Round($best.Free / 1GB, 2) - Write-Host "Selected vcpkg storage root: $($best.Root)" - Write-Host "Selected drive free space: $bestGb GB" - - - name: Restore vcpkg binary archives (Unix) - if: runner.os != 'Windows' - id: vcpkg_cache_restore_unix - uses: actions/cache/restore@v4 - with: - path: ~/.cache/vcpkg/archives - key: vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }} - restore-keys: | - vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}- - - - name: Restore vcpkg binary archives (Windows) - if: runner.os == 'Windows' - id: vcpkg_cache_restore_windows - uses: actions/cache/restore@v4 - with: - path: ${{ steps.win_storage.outputs.vcpkg_cache_dir }} - key: vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }} - restore-keys: | - vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}- - - - name: Ensure vcpkg baseline commit is available (Unix) - if: runner.os != 'Windows' - run: | - BASELINE=$(python3 -c "import json; print(json.load(open('vcpkg.json', encoding='utf-8'))['builtin-baseline'])") - git -C "$VCPKG_INSTALLATION_ROOT" fetch --no-tags origin "$BASELINE" || git -C "$VCPKG_INSTALLATION_ROOT" fetch --tags --prune origin - git -C "$VCPKG_INSTALLATION_ROOT" cat-file -e "${BASELINE}^{commit}" - - - name: Ensure vcpkg baseline commit is available (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - $baseline = (Get-Content vcpkg.json | ConvertFrom-Json).'builtin-baseline' - git -C "$env:VCPKG_INSTALLATION_ROOT" fetch --no-tags origin $baseline - if ($LASTEXITCODE -ne 0) { - git -C "$env:VCPKG_INSTALLATION_ROOT" fetch --tags --prune origin - } - git -C "$env:VCPKG_INSTALLATION_ROOT" cat-file -e "$baseline`^{commit}" - - - name: Configure CMake (Unix) - if: runner.os != 'Windows' - run: | - if [ "$RUNNER_OS" == "Linux" ]; then - TRIPLET="x64-linux" # vcpkg's x64-linux defaults to static libraries - else - TRIPLET="arm64-osx-release" # Force static linking on macOS - fi - - cmake -G Ninja -B build \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_C_COMPILER=clang \ - -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" \ - -DVCPKG_TARGET_TRIPLET=$TRIPLET - - - name: Save vcpkg binary archives (Unix) - if: ${{ runner.os != 'Windows' && always() && steps.vcpkg_cache_restore_unix.outputs.cache-hit != 'true' }} - uses: actions/cache/save@v4 - with: - path: ~/.cache/vcpkg/archives - key: vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }} - - - name: Build (Unix) - if: runner.os != 'Windows' - run: cmake --build build --config Release - - - name: Configure CMake (Windows MSVC) - if: runner.os == 'Windows' && matrix.toolchain == 'msvc' - run: cmake -G Ninja -B build -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DRPS_MSVC_STATIC_RUNTIME=ON - - - name: Configure CMake (Windows Clang) - if: runner.os == 'Windows' && matrix.toolchain == 'clang' - run: cmake -G Ninja -B build -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DRPS_MSVC_STATIC_RUNTIME=ON - - - name: Save vcpkg binary archives (Windows) - if: ${{ runner.os == 'Windows' && always() && steps.vcpkg_cache_restore_windows.outputs.cache-hit != 'true' }} - uses: actions/cache/save@v4 - with: - path: ${{ steps.win_storage.outputs.vcpkg_cache_dir }} - key: vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }} - - - name: Build (Windows) - if: runner.os == 'Windows' - run: cmake --build build --config Release - - - name: Stage & Zip Binaries (Unix) - if: runner.os != 'Windows' - run: | - mkdir -p release_stage - cp build/apps/rps-server/rps-server release_stage/ - cp build/apps/rps-standalone/rps-standalone release_stage/ - cp build/apps/rps-pluginscanner/rps-pluginscanner release_stage/ - cp build/apps/rps-vstscannermaster/vstscannermaster release_stage/ - cp build/examples/cpp/rps-example-client release_stage/ - cd release_stage - zip -r "../rps-binaries-${{ matrix.name }}.zip" ./* - - - name: Stage & Zip Binaries (Windows MSVC) - if: runner.os == 'Windows' && matrix.toolchain == 'msvc' - run: | - New-Item -ItemType Directory -Force -Path release_stage - Copy-Item build\apps\rps-server\rps-server.exe release_stage\ - Copy-Item build\apps\rps-standalone\rps-standalone.exe release_stage\ - Copy-Item build\apps\rps-pluginscanner\rps-pluginscanner.exe release_stage\ - Copy-Item build\apps\rps-vstscannermaster\vstscannermaster.exe release_stage\ - Copy-Item build\examples\cpp\rps-example-client.exe release_stage\ - Compress-Archive -Path release_stage\* -DestinationPath "rps-binaries-${{ matrix.name }}.zip" - - - name: Stage & Zip Binaries (Windows Clang) - if: runner.os == 'Windows' && matrix.toolchain == 'clang' - run: | - New-Item -ItemType Directory -Force -Path release_stage - Copy-Item build\apps\rps-server\rps-server.exe release_stage\ - Copy-Item build\apps\rps-standalone\rps-standalone.exe release_stage\ - Copy-Item build\apps\rps-pluginscanner\rps-pluginscanner.exe release_stage\ - Copy-Item build\apps\rps-vstscannermaster\vstscannermaster.exe release_stage\ - Copy-Item build\examples\cpp\rps-example-client.exe release_stage\ - Compress-Archive -Path release_stage\* -DestinationPath "rps-binaries-${{ matrix.name }}.zip" - - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: rps-binaries-${{ matrix.name }} - path: rps-binaries-${{ matrix.name }}.zip - retention-days: 7 + + if (-not $candidates) { + throw "Could not determine a writable storage location for vcpkg." + } + + $best = $candidates | Sort-Object Free -Descending | Select-Object -First 1 + $base = Join-Path $best.Root "vcpkg-cache" + + $archives = Join-Path $base "archives" + $downloads = Join-Path $base "downloads" + $buildtrees = Join-Path $base "buildtrees" + $packages = Join-Path $base "packages" + + New-Item -ItemType Directory -Force -Path $archives, $downloads, $buildtrees, $packages | Out-Null + + "VCPKG_DEFAULT_BINARY_CACHE=$archives" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "VCPKG_DOWNLOADS=$downloads" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + "vcpkg_cache_dir=$archives" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + "vcpkg_downloads_dir=$downloads" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + + # --- THE FIX: Create Junctions to redirect C: to D: --- + # 1. Remove existing directories if GitHub pre-created them + Remove-Item -Path "C:\vcpkg\buildtrees" -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -Path "C:\vcpkg\packages" -Recurse -Force -ErrorAction SilentlyContinue + + # 2. Create the junctions + New-Item -ItemType Junction -Path "C:\vcpkg\buildtrees" -Target $buildtrees | Out-Null + New-Item -ItemType Junction -Path "C:\vcpkg\packages" -Target $packages | Out-Null + + # --- PROOF OF REDIRECTION --- + Write-Host "Testing the Junctions..." + Set-Content -Path "C:\vcpkg\buildtrees\proof.txt" -Value "This is actually on the D drive!" + if (Test-Path "$buildtrees\proof.txt") { + Write-Host "SUCCESS: C:\vcpkg\buildtrees is successfully routing to the D: drive!" + } else { + Write-Error "FAILURE: Junction didn't work." + } + + $bestGb = [math]::Round($best.Free / 1GB, 2) + Write-Host "Selected vcpkg storage root: $($best.Root)" + Write-Host "Selected drive free space: $bestGb GB" + + # --- VCPKG NINJA BOOTSTRAP FIX --- + # VCPKG on ARM64 struggles to extract ninja correctly due to 7zip architectural bugs. + # Pre-populating the exact directory VCPKG expects circumvents the broken inner downloader. + if ("${{ matrix.msvc_arch }}" -eq "arm64") { + $ninjaDir = Join-Path $downloads "tools\ninja-1.13.2-windows" + New-Item -ItemType Directory -Force -Path $ninjaDir | Out-Null + Invoke-WebRequest -Uri "https://github.com/ninja-build/ninja/releases/download/v1.13.2/ninja-winarm64.zip" -OutFile "$downloads\ninja.zip" + Expand-Archive "$downloads\ninja.zip" -DestinationPath $ninjaDir -Force + $ninjaDir | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + Write-Host "Bootstrapped ARM64 Ninja directly into $ninjaDir" + } + + - name: Restore vcpkg binary archives (Unix) + if: runner.os != 'Windows' + id: vcpkg_cache_restore_unix + uses: actions/cache/restore@v4 + with: + path: ~/.cache/vcpkg/archives + key: vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }} + restore-keys: | + vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}- + + - name: Restore vcpkg binary archives (Windows) + if: runner.os == 'Windows' + id: vcpkg_cache_restore_windows + uses: actions/cache/restore@v4 + with: + path: ${{ steps.win_storage.outputs.vcpkg_cache_dir }} + key: vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }} + restore-keys: | + vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}- + + - name: Ensure vcpkg registry and baseline commit are updated (Unix) + if: runner.os != 'Windows' + run: | + sudo git config --global --add safe.directory "$VCPKG_INSTALLATION_ROOT" + git -C "$VCPKG_INSTALLATION_ROOT" fetch origin master + git -C "$VCPKG_INSTALLATION_ROOT" checkout -f master + git -C "$VCPKG_INSTALLATION_ROOT" pull --rebase + BASELINE=$(python3 -c "import json; print(json.load(open('vcpkg.json', encoding='utf-8'))['builtin-baseline'])") + git -C "$VCPKG_INSTALLATION_ROOT" fetch --no-tags origin "$BASELINE" || git -C "$VCPKG_INSTALLATION_ROOT" fetch --tags --prune origin + git -C "$VCPKG_INSTALLATION_ROOT" cat-file -e "${BASELINE}^{commit}" + + - name: Ensure vcpkg registry and baseline commit are updated (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + git -C "$env:VCPKG_INSTALLATION_ROOT" fetch origin master + git -C "$env:VCPKG_INSTALLATION_ROOT" checkout -f master + git -C "$env:VCPKG_INSTALLATION_ROOT" pull --rebase + $baseline = (Get-Content vcpkg.json | ConvertFrom-Json).'builtin-baseline' + git -C "$env:VCPKG_INSTALLATION_ROOT" fetch --no-tags origin $baseline + if ($LASTEXITCODE -ne 0) { + git -C "$env:VCPKG_INSTALLATION_ROOT" fetch --tags --prune origin + } + git -C "$env:VCPKG_INSTALLATION_ROOT" cat-file -e "$baseline`^{commit}" + + - name: Configure CMake (Unix) + if: runner.os != 'Windows' + run: | + cmake -G Ninja -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" \ + -DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg_triplet }} + + - name: Save vcpkg binary archives (Unix) + if: ${{ runner.os != 'Windows' && always() && steps.vcpkg_cache_restore_unix.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v4 + with: + path: ~/.cache/vcpkg/archives + key: vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }} + + - name: Build (Unix) + if: runner.os != 'Windows' + run: cmake --build build --config Release + + - name: Configure CMake (Windows MSVC) + if: runner.os == 'Windows' && matrix.toolchain == 'msvc' + run: cmake -G Ninja -B build -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DRPS_MSVC_STATIC_RUNTIME=ON + + - name: Configure CMake (Windows Clang) + if: runner.os == 'Windows' && matrix.toolchain == 'clang' + run: cmake -G Ninja -B build -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg_triplet }} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DRPS_MSVC_STATIC_RUNTIME=ON + + - name: Save vcpkg binary archives (Windows) + if: ${{ runner.os == 'Windows' && always() && steps.vcpkg_cache_restore_windows.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v4 + with: + path: ${{ steps.win_storage.outputs.vcpkg_cache_dir }} + key: vcpkg-archives-${{ runner.os }}-${{ matrix.toolchain }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }} + + - name: Build (Windows) + if: runner.os == 'Windows' + run: cmake --build build --config Release + + - name: Stage & Archive Binaries (Unix) + if: runner.os != 'Windows' + run: | + mkdir -p release_stage + cp build/apps/rps-server/rps-server release_stage/ + cp build/apps/rps-standalone/rps-standalone release_stage/ + cp build/apps/rps-pluginscanner/rps-pluginscanner release_stage/ + cp build/apps/rps-vstscannermaster/vstscannermaster release_stage/ + cp build/examples/cpp/rps-example-client release_stage/ + cd release_stage + if [ "${{ matrix.archive_ext }}" = "tar.gz" ]; then + tar -czvf "../${{ matrix.artifact_name }}.tar.gz" ./* + else + zip -r "../${{ matrix.artifact_name }}.zip" ./* + fi + + - name: Stage & Zip Binaries (Windows MSVC) + if: runner.os == 'Windows' && matrix.toolchain == 'msvc' + run: | + New-Item -ItemType Directory -Force -Path release_stage + Copy-Item build\apps\rps-server\rps-server.exe release_stage\ + Copy-Item build\apps\rps-standalone\rps-standalone.exe release_stage\ + Copy-Item build\apps\rps-pluginscanner\rps-pluginscanner.exe release_stage\ + Copy-Item build\apps\rps-vstscannermaster\vstscannermaster.exe release_stage\ + Copy-Item build\examples\cpp\rps-example-client.exe release_stage\ + Compress-Archive -Path release_stage\* -DestinationPath "rps-binaries-${{ matrix.name }}.zip" + + - name: Stage & Archive Binaries (Windows Clang) + if: runner.os == 'Windows' && matrix.toolchain == 'clang' + run: | + New-Item -ItemType Directory -Force -Path release_stage + Copy-Item build\apps\rps-server\rps-server.exe release_stage\ + Copy-Item build\apps\rps-standalone\rps-standalone.exe release_stage\ + Copy-Item build\apps\rps-pluginscanner\rps-pluginscanner.exe release_stage\ + Copy-Item build\apps\rps-vstscannermaster\vstscannermaster.exe release_stage\ + Copy-Item build\examples\cpp\rps-example-client.exe release_stage\ + Compress-Archive -Path release_stage\* -DestinationPath "${{ matrix.artifact_name }}.zip" + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact_name }} + path: ${{ matrix.artifact_name }}.${{ matrix.archive_ext }} + retention-days: 7 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 95cc2f9..5b9f107 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,6 +1,6 @@ # RPS Developer Guide -Welcome to the **Reliable Plugin Scanner (RPS)** project! This guide explains the architecture, design choices, and implementation details of what we have built so far, serving as an onboarding manual for new developers. +Welcome to the **RPS** project! This guide explains the architecture, design choices, and implementation details of what we have built so far, serving as an onboarding manual for new developers. --- diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..6f089df --- /dev/null +++ b/TODO.md @@ -0,0 +1,45 @@ +# RPS - TODO List + +This document tracks identified architectural improvements and known bugs across the project, ordered by priority. + +## 🔴 Critical Priority (Bugs & Stability) + +### 1. Fix `EXC_BAD_ACCESS` (Double-Free) in AudioUnit Scanner +- **File:** `apps/rps-pluginscanner/src/scanners/AuScanner.mm` +- **Description:** Around line 191, the code calls `CFRelease(paramInfo.cfNameString);`. Under CoreAudio's memory management rules (the "Get Rule"), the caller does not own this string reference. Releasing it causes an over-release, which can lead to hard crashes when scanning well-behaved Audio Units. +- **Action:** Remove the `CFRelease` call. + +## 🟠 High Priority (Architecture & Performance) + +### 2. Replace Internal IPC Mechanism (JSON/Named Queues -> Protobuf/Anonymous Pipes) +- **Files:** `libs/rps-ipc/src/Connection.cpp`, `libs/rps-ipc/include/rps/ipc/Connection.hpp` +- **Description:** Currently, communication between the Orchestrator and the Worker processes uses `boost::interprocess::message_queue` combined with Boost.JSON serialization. + - **Issue 1:** Named queues can remain orphaned in the OS if the orchestrator is hard-killed (e.g., `SIGKILL`), causing conflicts on subsequent runs. + - **Issue 2:** JSON serialization is relatively slow and memory-intensive for large plugins with thousands of parameters. +- **Action:** Since the project already uses Protobuf for the external gRPC API, switch the internal IPC to also use Protobuf (or FlatBuffers). Migrate the transport layer from named queues to anonymous pipes (Standard I/O streams using `boost::process`) to guarantee OS-level cleanup upon process termination. + +## 🟡 Medium Priority (Enhancements) + +### 3. Exhaustive AudioUnit Parameter Extraction +- **File:** `apps/rps-pluginscanner/src/scanners/AuScanner.mm` +- **Description:** The AU scanner currently only queries `kAudioUnitScope_Global` for parameters. Many plugins (especially multi-bus effects and synths) register automatable parameters under `kAudioUnitScope_Input` or `kAudioUnitScope_Output`. +- **Action:** Iterate over input/output scopes and their respective buses to ensure all plugin parameters are captured. + +### 4. Dynamic AudioUnit Registration for Arbitrary Paths +- **File:** `apps/rps-pluginscanner/src/scanners/AuScanner.mm` +- **Description:** `AudioComponentFindNext` only searches for components that have already been registered by macOS (typically residing in `/Library/Audio/Plug-Ins/Components`). If a user attempts to scan an unregistered `.component` bundle located elsewhere (e.g., `~/Downloads/`), the instantiation will fail. +- **Action:** If `AudioComponentFindNext` fails, dynamically register the bundle for the duration of the process using `AudioComponentRegister()` before attempting to find it again. + +### 5. FourCC Parsing Fallback in AU `Info.plist` +- **File:** `apps/rps-pluginscanner/src/scanners/AuScanner.mm` +- **Description:** The `fourccToUInt32` lambda expects the string representation of a type/subtype to be exactly 4 characters long. Some legacy or poorly-formed plugins provide these codes as raw base-10 integers in the `Info.plist`. In these cases, the string conversion yields a numeric string (e.g., `"1096107074"`), which fails the length check. +- **Action:** Add a fallback mechanism to check if the string can be parsed as a raw integer (e.g., `[str longLongValue]`) if the length is not 4. + +## 🔵 Epic / Long-Term Vision + +### 6. Expand to Out-of-Process Plugin Engine (RPE) +- **Description:** Evolve the scanner into a full execution engine. Allow clients (via gRPC) to not only scan plugins but remotely instantiate them, stream parameter changes, and process audio. +- **Implementation Notes:** + - **Audio Data Plane:** Do not use gRPC for real-time audio buffer streaming due to jitter/latency. Establish a Shared Memory Ring Buffer (local) or fast UDP socket (network) negotiated via gRPC. + - **UI Management:** Use SDL3 in the worker process to spawn floating UI windows for the plugins. Avoid attempting complex OS-level window reparenting/embedding initially. + - **Plugin Chains:** Ensure entire effects chains (e.g., EQ -> Compressor) are hosted within a single worker process to prevent IPC context-switching overhead during the audio loop. diff --git a/examples/java/README.md b/examples/java/README.md index 0057649..5c0dfb1 100644 --- a/examples/java/README.md +++ b/examples/java/README.md @@ -1,6 +1,6 @@ # RPS Java Client Example -This is a professional Java client for the Reliable Plugin Scanner (RPS) gRPC service. It demonstrates how to connect to the server, start a scan, and handle streaming scan events using a modern Terminal UI (TUI). +This is a professional Java client for the RPS gRPC service. It demonstrates how to connect to the server, start a scan, and handle streaming scan events using a modern Terminal UI (TUI). ## Prerequisites