From c4d22cd4a3fbb62cd9c87c2a1883a0c84a6422e4 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 11:20:19 -0700 Subject: [PATCH 01/12] perf(windows): optimize Windows CI build and bump GitHub Actions versions - Trim unused Qt6 modules in Windows CI to cut ~10-12 min download/extract time - Cache Vulkan SDK in C:\VulkanSDK to skip redundant 800MB Chocolatey downloads - Disable Windows Defender real-time scanning & add exclusions for build directories - Enable sccache disk persistence via actions/cache@v6 for C:\.sccache - Add MSVC compiler optimizations (/Zc:inline, /Gw) for faster linkage & smaller binaries - Bump fallback Vulkan SDK Git tag from v1.4.304 to v1.4.354 in vulkan.cmake - Modernize GitHub Actions ecosystem across all workflows: * actions/checkout to @v7 * actions/cache, cache/restore, cache/save to @v6 * actions/upload-artifact to @v7 * actions/download-artifact to @v8 * actions/setup-python to @v7 * actions/setup-node to @v7 * github/codeql-action to @v4 * mozilla-actions/sccache-action to @v0.0.11 - Grant explicit actions: write permissions for cache persistence Refs #44 Signed-off-by: Michael Oliver --- .github/actions/build-windows/action.yml | 52 +++++++++++++++++++----- .github/workflows/branch-build.yml | 30 +++++++------- .github/workflows/codeql.yml | 6 +-- .github/workflows/dev-build.yml | 45 ++++++++++---------- .github/workflows/pr-checks.yml | 10 ++--- .github/workflows/publish-packages.yml | 4 +- .github/workflows/release.yml | 49 +++++++++++----------- cmake/defaults/cxx_msvc_defaults.cmake | 2 + cmake/dependencies/vulkan.cmake | 6 +-- 9 files changed, 120 insertions(+), 84 deletions(-) diff --git a/.github/actions/build-windows/action.yml b/.github/actions/build-windows/action.yml index 88c46534..21d67e37 100644 --- a/.github/actions/build-windows/action.yml +++ b/.github/actions/build-windows/action.yml @@ -34,7 +34,7 @@ inputs: default: 'd3dcompiler_47 opengl32sw qt3d qtactiveqt qtbase qtconnectivity qtdeclarative qtgraphicaleffects qtimageformats qtlocation qtmultimedia qtquickcontrols qtquickcontrols2 qtremoteobjects qtscxml qtsensors qtsvg qttools qttranslations qtwebchannel qtwebsockets qtwebview qtwinextras qtxmlpatterns' windows_qt6_modules: required: false - default: 'debug_info qt3d qt5compat qtactiveqt qtcharts qtconnectivity qtdatavis3d qtgrpc qthttpserver qtimageformats qtlanguageserver qtlocation qtlottie qtmultimedia qtnetworkauth qtpdf qtpositioning qtquick3d qtquick3dphysics qtquickeffectmaker qtquicktimeline qtremoteobjects qtscxml qtsensors qtserialbus qtserialport qtshadertools qtspeech qtvirtualkeyboard qtwebchannel qtwebengine qtwebsockets qtwebview' + default: 'qt5compat qtimageformats qtmultimedia qtpdf qtshadertools' windows_qt6_archives: required: false default: 'd3dcompiler_47 opengl32sw qtbase qtdeclarative qtsvg qttools qttranslations' @@ -42,6 +42,12 @@ inputs: runs: using: "composite" steps: + - name: Disable Windows Defender Realtime Monitoring for Build Trees + shell: powershell + run: | + Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue + Add-MpPreference -ExclusionPath "${{ github.workspace }}", "C:\.sccache", "C:\Program Files\OpenUTVDeps", "C:\VulkanSDK" -ErrorAction SilentlyContinue + - name: Download and Install Prebuilt Dependencies shell: powershell env: @@ -191,7 +197,7 @@ runs: shell: powershell - name: Cache CMake for Windows - uses: actions/cache@v5 + uses: actions/cache@v6 id: windows-cmake-cache with: path: "C:/Program Files/CMake" @@ -202,10 +208,21 @@ runs: run: choco install cmake --version=${{ inputs.cmake-version }} --allow-downgrade --yes --no-progress shell: powershell - - name: Install build tools (Ninja, JOM, flex, bison, nasm, patch, pkg-config, Vulkan) + - name: Cache Vulkan SDK for Windows + uses: actions/cache@v6 + id: windows-vulkan-cache + with: + path: "C:/VulkanSDK" + key: ${{ runner.os }}-vulkan-sdk-1.4.350 + + - name: Install build tools (Ninja, flex, bison, nasm, patch, pkg-config, Vulkan) run: | choco feature enable -n allowEmptyChecksums - choco install ninja jom winflexbison3 nasm patch pkgconfiglite vulkan-sdk --yes --no-progress --allow-empty-checksums --ignore-checksums + $chocoPkgs = "ninja winflexbison3 nasm patch pkgconfiglite" + if ("${{ steps.windows-vulkan-cache.outputs.cache-hit }}" -ne "true") { + $chocoPkgs += " vulkan-sdk" + } + choco install $chocoPkgs --yes --no-progress --allow-empty-checksums --ignore-checksums "C:\ProgramData\chocolatey\bin" | Add-Content -Path $env:GITHUB_PATH $VulkanDir = Get-ChildItem -Path "C:\VulkanSDK" -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1 if ($VulkanDir) { @@ -294,22 +311,27 @@ runs: shell: powershell - name: Setup sccache - # Used for this run only (SCCACHE_GHA_ENABLED=false); not stored in Actions cache. - # For sccache to actually cache the Windows/MSVC build, CMake must use it as the compiler - # launcher, e.g. -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache - # in the Configure UTV step (and sccache must be on PATH when the build runs). - uses: mozilla-actions/sccache-action@v0.0.10 + uses: mozilla-actions/sccache-action@v0.0.11 with: version: "v0.15.0" - - name: Configure sccache for bash and powershell + - name: Configure sccache directory and cache run: | - # sccache is installed to cargo bin if (Test-Path "C:\Users\runneradmin\.cargo\bin") { "C:\Users\runneradmin\.cargo\bin" | Add-Content -Path $env:GITHUB_PATH } + "SCCACHE_DIR=C:\.sccache" | Add-Content -Path $env:GITHUB_ENV + "SCCACHE_CACHE_SIZE=5G" | Add-Content -Path $env:GITHUB_ENV shell: powershell + - name: Restore Sccache + uses: actions/cache/restore@v6 + with: + path: "C:/.sccache" + key: ${{ runner.os }}-sccache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-sccache- + - name: Configure UTV run: | $PrefixPaths = "C:/Qt/${{ inputs.qt-version }}/msvc2022_64" @@ -455,3 +477,11 @@ runs: } shell: powershell + - name: Save Sccache + if: always() + uses: actions/cache/save@v6 + with: + path: "C:/.sccache" + key: ${{ runner.os }}-sccache-${{ github.sha }} + + diff --git a/.github/workflows/branch-build.yml b/.github/workflows/branch-build.yml index 5c8a2fe2..fcf8b626 100644 --- a/.github/workflows/branch-build.yml +++ b/.github/workflows/branch-build.yml @@ -10,10 +10,12 @@ on: - 'CMakeLists.txt' - 'build.sh' - '.github/actions/build-windows/**' + - '.github/workflows/**' - 'requirements.txt' permissions: contents: read + actions: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -29,10 +31,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.14' @@ -49,14 +51,14 @@ jobs: runs-on: macos-26 steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 - name: Check out proprietary vendor SDKs continue-on-error: true - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: OpenUTV/openutv-vendor-sdks token: ${{ secrets.GH_TOKEN_DEPS_READ }} @@ -68,7 +70,7 @@ jobs: shell: bash - name: Restore Homebrew cache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: | ~/Library/Caches/Homebrew/downloads @@ -87,7 +89,7 @@ jobs: - name: Save Homebrew cache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: | ~/Library/Caches/Homebrew/downloads @@ -95,7 +97,7 @@ jobs: key: ${{ runner.os }}-brew-${{ steps.date.outputs.date }}-${{ hashFiles('**/build.sh') }} - name: Restore CCache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} @@ -112,7 +114,7 @@ jobs: - name: Save CCache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} @@ -129,7 +131,7 @@ jobs: shell: bash - name: Upload macOS Branch Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UTV-macOS-arm64 path: _dist/*.zip @@ -145,14 +147,14 @@ jobs: SCCACHE_GHA_ENABLED: "true" steps: - name: Check out repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 - name: Check out proprietary vendor SDKs continue-on-error: true - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: OpenUTV/openutv-vendor-sdks token: ${{ secrets.GH_TOKEN_DEPS_READ }} @@ -174,7 +176,7 @@ jobs: Compress-Archive -Path "utv-windows-x64" -DestinationPath "_dist\$ZipName" -CompressionLevel Optimal - name: Upload Windows Branch Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UTV-windows-x64 path: _dist/*.zip @@ -188,7 +190,7 @@ jobs: continue-on-error: true steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 @@ -213,7 +215,7 @@ jobs: - name: Upload Linux Branch Artifact if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UTV-linux-x64 path: _dist/*.tar.gz diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 807c9723..0f656eda 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,12 +15,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 02ea6910..3f9de16a 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -14,6 +14,7 @@ on: permissions: contents: write + actions: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -29,10 +30,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.14' @@ -52,7 +53,7 @@ jobs: short_sha: ${{ steps.ver.outputs.short_sha }} steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -74,14 +75,14 @@ jobs: runs-on: macos-26 steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 - name: Check out proprietary vendor SDKs continue-on-error: true - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: OpenUTV/openutv-vendor-sdks token: ${{ secrets.GH_TOKEN_DEPS_READ }} @@ -93,7 +94,7 @@ jobs: shell: bash - name: Restore Homebrew cache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: | ~/Library/Caches/Homebrew/downloads @@ -112,7 +113,7 @@ jobs: - name: Save Homebrew cache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: | ~/Library/Caches/Homebrew/downloads @@ -120,7 +121,7 @@ jobs: key: ${{ runner.os }}-brew-${{ steps.date.outputs.date }}-${{ hashFiles('**/build.sh') }} - name: Restore CCache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} @@ -137,7 +138,7 @@ jobs: - name: Save CCache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} @@ -151,7 +152,7 @@ jobs: shell: bash - name: Upload Unsigned App Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: utv-dev-unsigned-${{ needs.version.outputs.short_sha }} path: _build/stage/unsigned/UTV.app.tar.gz @@ -163,10 +164,10 @@ jobs: runs-on: macos-26 steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Download Unsigned App Artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: utv-dev-unsigned-${{ needs.version.outputs.short_sha }} path: _build/stage/unsigned @@ -256,7 +257,7 @@ jobs: shell: bash - name: Upload Signed macOS Dev Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UTV-dev-macOS-arm64 path: _build/stage/app/UTV-dev-macOS-arm64.zip @@ -281,14 +282,14 @@ jobs: SCCACHE_GHA_ENABLED: "true" steps: - name: Check out repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 - name: Check out proprietary vendor SDKs continue-on-error: true - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: OpenUTV/openutv-vendor-sdks token: ${{ secrets.GH_TOKEN_DEPS_READ }} @@ -309,7 +310,7 @@ jobs: Compress-Archive -Path "utv-windows-x64" -DestinationPath "_dist\$ZipName" -CompressionLevel Optimal - name: Upload Windows Dev Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UTV-dev-windows-x64 path: _dist/UTV-dev-windows-x64.zip @@ -322,7 +323,7 @@ jobs: continue-on-error: true steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 @@ -345,7 +346,7 @@ jobs: - name: Upload Linux Dev Artifact if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UTV-dev-linux-x64 path: _dist/UTV-dev-linux-x64.tar.gz @@ -364,7 +365,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -373,21 +374,21 @@ jobs: - name: Download macOS Dev Artifact continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UTV-dev-macOS-arm64 path: _dist - name: Download Windows Dev Artifact continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UTV-dev-windows-x64 path: _dist - name: Download Linux Dev Artifact continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UTV-dev-linux-x64 path: _dist diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index ac30f120..c82f96ab 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -42,12 +42,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: 20 @@ -63,10 +63,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.14' @@ -84,7 +84,7 @@ jobs: continue-on-error: true steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 9c62cfcb..67040472 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Download Release Assets env: @@ -152,7 +152,7 @@ jobs: runs-on: windows-latest steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Pack and Push to Chocolatey Community Repository shell: powershell diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07c6cffb..f8ea9ba2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,7 @@ on: permissions: contents: write pull-requests: read + actions: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" @@ -41,10 +42,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Setup Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.14' @@ -63,7 +64,7 @@ jobs: clean_version: ${{ steps.versioning.outputs.clean_version }} steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -99,14 +100,14 @@ jobs: runs-on: macos-26 steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 - name: Check out proprietary vendor SDKs continue-on-error: true - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: OpenUTV/openutv-vendor-sdks token: ${{ secrets.GH_TOKEN_DEPS_READ }} @@ -118,7 +119,7 @@ jobs: shell: bash - name: Restore Homebrew cache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: | ~/Library/Caches/Homebrew/downloads @@ -137,7 +138,7 @@ jobs: - name: Save Homebrew cache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: | ~/Library/Caches/Homebrew/downloads @@ -145,7 +146,7 @@ jobs: key: ${{ runner.os }}-brew-${{ steps.date.outputs.date }}-${{ hashFiles('**/build.sh') }} - name: Restore CCache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} @@ -162,7 +163,7 @@ jobs: - name: Save CCache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} @@ -176,7 +177,7 @@ jobs: shell: bash - name: Upload Unsigned App Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: utv-app-unsigned-${{ needs.version.outputs.version }} path: _build/stage/unsigned/UTV.app.tar.gz @@ -189,10 +190,10 @@ jobs: runs-on: macos-26 steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Download Unsigned App Artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: utv-app-unsigned-${{ needs.version.outputs.version }} path: _build/stage/unsigned @@ -355,7 +356,7 @@ jobs: shell: bash - name: Upload Signed Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UTV-${{ needs.version.outputs.version }}-macOS-arm64 path: _build/stage/app/UTV-${{ needs.version.outputs.version }}-macOS-arm64.zip @@ -381,14 +382,14 @@ jobs: SCCACHE_GHA_ENABLED: "true" steps: - name: Check out repository code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 - name: Check out proprietary vendor SDKs continue-on-error: true - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: OpenUTV/openutv-vendor-sdks token: ${{ secrets.GH_TOKEN_DEPS_READ }} @@ -411,7 +412,7 @@ jobs: Write-Host "Created _dist\$ZipName successfully." - name: Upload Windows Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UTV-${{ needs.version.outputs.version }}-windows-x64 path: _dist/UTV-${{ needs.version.outputs.version }}-windows-x64.zip @@ -424,7 +425,7 @@ jobs: runs-on: windows-2022 steps: - name: Download Windows Artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UTV-${{ needs.version.outputs.version }}-windows-x64 path: _dist @@ -445,7 +446,7 @@ jobs: continue-on-error: true steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 @@ -473,7 +474,7 @@ jobs: - name: Upload Linux Artifact if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: UTV-${{ needs.version.outputs.version }}-linux-x64 path: _dist/UTV-${{ needs.version.outputs.version }}-linux-x64.tar.gz @@ -493,7 +494,7 @@ jobs: dnf config-manager --set-enabled crb || true - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: recursive fetch-depth: 0 @@ -516,7 +517,7 @@ jobs: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) steps: - name: Check out repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -525,21 +526,21 @@ jobs: - name: Download macOS Artifact continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UTV-${{ needs.version.outputs.version }}-macOS-arm64 path: _dist - name: Download Windows Artifact continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UTV-${{ needs.version.outputs.version }}-windows-x64 path: _dist - name: Download Linux Artifact continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: UTV-${{ needs.version.outputs.version }}-linux-x64 path: _dist diff --git a/cmake/defaults/cxx_msvc_defaults.cmake b/cmake/defaults/cxx_msvc_defaults.cmake index 3d5b30e2..1d754a20 100644 --- a/cmake/defaults/cxx_msvc_defaults.cmake +++ b/cmake/defaults/cxx_msvc_defaults.cmake @@ -57,6 +57,8 @@ ADD_COMPILE_OPTIONS( -utf-8 -Zc:forScope -Zc:sizedDealloc- + -Zc:inline + -Gw -Z7 ) diff --git a/cmake/dependencies/vulkan.cmake b/cmake/dependencies/vulkan.cmake index 67aa410d..08cc0a3d 100644 --- a/cmake/dependencies/vulkan.cmake +++ b/cmake/dependencies/vulkan.cmake @@ -43,13 +43,13 @@ ELSE() FETCHCONTENT_DECLARE( vulkan_headers GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Headers.git - GIT_TAG v1.4.304 + GIT_TAG v1.4.354 GIT_SHALLOW TRUE ) FETCHCONTENT_DECLARE( vulkan_loader GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Loader.git - GIT_TAG v1.4.304 + GIT_TAG v1.4.354 GIT_SHALLOW TRUE ) @@ -69,7 +69,7 @@ ELSE() ) ENDIF() SET(RV_DEPS_VULKAN_VERSION - "1.4.304" + "1.4.354" ) ENDIF() From 003286fc21196e0e302ab1f37d46e5646c868452 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 11:28:16 -0700 Subject: [PATCH 02/12] docs: update README build status workflow badge and add tech stack & community flair Signed-off-by: Michael Oliver --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f55cbd8d..1057d163 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,18 @@

- Latest Release - Build Status - Python 3.14 + Latest Release + Development Build + Build Status + Platforms: macOS | Windows | Linux + License: Apache 2.0 +

+ +

+ C++20 + Qt 6.11 + Python 3.14 + PRs Welcome GitHub Stars

From 4752167f3c1cff1a9989909b864de55dabd14b7d Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 11:35:03 -0700 Subject: [PATCH 03/12] feat(ci): add post-build Windows Defender scan on staged application Unexcludes the workspace and triggers an on-demand scan with MpCmdRun.exe on _install to verify the final staged application and its runtime dependencies are free of malware and supply chain threats before archiving. Refs #44 Signed-off-by: Michael Oliver --- .github/actions/build-windows/action.yml | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/actions/build-windows/action.yml b/.github/actions/build-windows/action.yml index 21d67e37..15e71f65 100644 --- a/.github/actions/build-windows/action.yml +++ b/.github/actions/build-windows/action.yml @@ -469,6 +469,34 @@ runs: } shell: powershell + - name: Windows Defender Supply Chain Security Scan + shell: powershell + run: | + $installDir = Join-Path $env:GITHUB_WORKSPACE "_install" + if (Test-Path $installDir) { + Write-Host "Unexcluding staged application from Defender for security verification..." + Remove-MpPreference -ExclusionPath "${{ github.workspace }}" -ErrorAction SilentlyContinue + + $mpCmd = "C:\Program Files\Windows Defender\MpCmdRun.exe" + if (Test-Path $mpCmd) { + Write-Host "Scanning staged application with Windows Defender: $installDir" + & $mpCmd -Scan -ScanType 3 -File "$installDir" + if ($LASTEXITCODE -eq 2) { + Write-Error "Windows Defender detected malware or malicious threat in staged application!" + exit 1 + } elseif ($LASTEXITCODE -eq 0) { + Write-Host "Windows Defender scan clean. 0 threats detected." + } else { + Write-Warning "Windows Defender scan completed with exit code: $LASTEXITCODE" + } + } else { + Write-Host "Running PowerShell Start-MpScan on $installDir..." + Start-MpScan -ScanType CustomScan -ScanPath "$installDir" + } + } else { + Write-Warning "Directory _install not found for Defender scan." + } + - name: Sccache Statistics if: always() run: | From af7063c228558841bd2f046826a7b920b14d5adc Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 11:41:47 -0700 Subject: [PATCH 04/12] chore(ci): bump actions in newly merged Linux jobs to v7 and v6 Signed-off-by: Michael Oliver --- .github/workflows/branch-build.yml | 10 +++++----- .github/workflows/dev-build.yml | 10 +++++----- .github/workflows/release.yml | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/branch-build.yml b/.github/workflows/branch-build.yml index 7c4fb879..d5165112 100644 --- a/.github/workflows/branch-build.yml +++ b/.github/workflows/branch-build.yml @@ -195,7 +195,7 @@ jobs: - name: Check out proprietary vendor SDKs continue-on-error: true - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: OpenUTV/openutv-vendor-sdks token: ${{ secrets.GH_TOKEN_DEPS_READ }} @@ -207,7 +207,7 @@ jobs: shell: bash - name: Restore Homebrew cache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: | ~/.cache/Homebrew/downloads @@ -228,14 +228,14 @@ jobs: - name: Save Homebrew cache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: | ~/.cache/Homebrew/downloads key: ${{ runner.os }}-brew-${{ steps.date.outputs.date }}-${{ hashFiles('**/build.sh') }} - name: Restore CCache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} @@ -252,7 +252,7 @@ jobs: - name: Save CCache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index f555a4b2..a52edcc5 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -330,7 +330,7 @@ jobs: - name: Check out proprietary vendor SDKs continue-on-error: true - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: OpenUTV/openutv-vendor-sdks token: ${{ secrets.GH_TOKEN_DEPS_READ }} @@ -342,7 +342,7 @@ jobs: shell: bash - name: Restore Homebrew cache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: | ~/.cache/Homebrew/downloads @@ -363,14 +363,14 @@ jobs: - name: Save Homebrew cache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: | ~/.cache/Homebrew/downloads key: ${{ runner.os }}-brew-${{ steps.date.outputs.date }}-${{ hashFiles('**/build.sh') }} - name: Restore CCache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} @@ -387,7 +387,7 @@ jobs: - name: Save CCache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2cbbf13a..c6268017 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -453,7 +453,7 @@ jobs: - name: Check out proprietary vendor SDKs continue-on-error: true - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: OpenUTV/openutv-vendor-sdks token: ${{ secrets.GH_TOKEN_DEPS_READ }} @@ -465,7 +465,7 @@ jobs: shell: bash - name: Restore Homebrew cache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: | ~/.cache/Homebrew/downloads @@ -486,14 +486,14 @@ jobs: - name: Save Homebrew cache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: | ~/.cache/Homebrew/downloads key: ${{ runner.os }}-brew-${{ steps.date.outputs.date }}-${{ hashFiles('**/build.sh') }} - name: Restore CCache - uses: actions/cache/restore@v5 + uses: actions/cache/restore@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} @@ -510,7 +510,7 @@ jobs: - name: Save CCache if: always() - uses: actions/cache/save@v5 + uses: actions/cache/save@v6 with: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} From 48721f5ca5485a892f2812deff777b1b41f2372d Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 11:44:06 -0700 Subject: [PATCH 05/12] chore(scripts): remove emojis from changelog section headings Signed-off-by: Michael Oliver --- scripts/generate-changelog.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/scripts/generate-changelog.sh b/scripts/generate-changelog.sh index bf4357ea..ad409032 100755 --- a/scripts/generate-changelog.sh +++ b/scripts/generate-changelog.sh @@ -32,21 +32,21 @@ format_section() { fi } -format_section "โœจ Features" "feat(\([^)]+\))?:" -format_section "๐Ÿ› Bug Fixes" "fix(\([^)]+\))?:" -format_section "โšก Performance" "perf(\([^)]+\))?:" -format_section "โ™ป๏ธ Refactoring" "refactor(\([^)]+\))?:" -format_section "๐ŸŽจ Code Style" "style(\([^)]+\))?:" -format_section "๐Ÿ—๏ธ Build & Dependencies" "build(\([^)]+\))?:" -format_section "๐Ÿ”ง CI/CD" "ci(\([^)]+\))?:" -format_section "๐Ÿ“š Documentation" "docs(\([^)]+\))?:" -format_section "๐Ÿงช Tests" "test(\([^)]+\))?:" -format_section "๐Ÿ“ฆ Chores" "chore(\([^)]+\))?:" +format_section "Features" "feat(\([^)]+\))?:" +format_section "Bug Fixes" "fix(\([^)]+\))?:" +format_section "Performance Improvements" "perf(\([^)]+\))?:" +format_section "Refactoring" "refactor(\([^)]+\))?:" +format_section "Code Style" "style(\([^)]+\))?:" +format_section "Build & Dependencies" "build(\([^)]+\))?:" +format_section "CI/CD" "ci(\([^)]+\))?:" +format_section "Documentation" "docs(\([^)]+\))?:" +format_section "Tests" "test(\([^)]+\))?:" +format_section "Chores" "chore(\([^)]+\))?:" # Check for non-conventional or unclassified commits OTHER=$(git log --pretty=format:"* %s (%h)" "$RANGE" | grep -v -E "^\* (feat|fix|perf|refactor|style|build|ci|docs|test|chore)(\([^)]+\))?:" || true) if [ -n "$OTHER" ]; then - echo "### ๐Ÿชต Other Changes" + echo "### Other Changes" echo "$OTHER" echo "" fi From a241649086df5e2dfb3af2a07ad513a54e927474 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 11:50:51 -0700 Subject: [PATCH 06/12] fix(ci): separate chocolatey package installation commands in Windows CI Signed-off-by: Michael Oliver --- .github/actions/build-windows/action.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/actions/build-windows/action.yml b/.github/actions/build-windows/action.yml index 15e71f65..409612ac 100644 --- a/.github/actions/build-windows/action.yml +++ b/.github/actions/build-windows/action.yml @@ -218,11 +218,10 @@ runs: - name: Install build tools (Ninja, flex, bison, nasm, patch, pkg-config, Vulkan) run: | choco feature enable -n allowEmptyChecksums - $chocoPkgs = "ninja winflexbison3 nasm patch pkgconfiglite" + choco install ninja winflexbison3 nasm patch pkgconfiglite --yes --no-progress --allow-empty-checksums --ignore-checksums if ("${{ steps.windows-vulkan-cache.outputs.cache-hit }}" -ne "true") { - $chocoPkgs += " vulkan-sdk" + choco install vulkan-sdk --yes --no-progress --allow-empty-checksums --ignore-checksums } - choco install $chocoPkgs --yes --no-progress --allow-empty-checksums --ignore-checksums "C:\ProgramData\chocolatey\bin" | Add-Content -Path $env:GITHUB_PATH $VulkanDir = Get-ChildItem -Path "C:\VulkanSDK" -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1 if ($VulkanDir) { From 6b1ac7426849cdaaa2cb75f33e94cf22e0875f63 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 12:17:11 -0700 Subject: [PATCH 07/12] fix(ci): use native 7-Zip in Qt installer, add qtwebengine, and revert Vulkan tag to v1.4.304 - In cmake/dependencies/vulkan.cmake, revert fallback Vulkan tag to v1.4.304 to fix the CMake VERSION_GREATER configuration failure in Vulkan-Loader. - In .github/actions/build-windows/action.yml: * Add qtwebengine to windows_qt6_modules (required by cmake/dependencies/qt6.cmake) and remove unused qt5compat. * Ensure 7-Zip is added to PATH via PowerShell. * Pass extra: '--external 7z' to jurplel/install-qt-action to use native 7-Zip instead of py7zr, preventing Bad7zFile extraction errors and speeding up installs. Refs #44 Signed-off-by: Michael Oliver --- .github/actions/build-windows/action.yml | 14 +++++++------- cmake/dependencies/vulkan.cmake | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/actions/build-windows/action.yml b/.github/actions/build-windows/action.yml index 409612ac..675b0df7 100644 --- a/.github/actions/build-windows/action.yml +++ b/.github/actions/build-windows/action.yml @@ -34,7 +34,7 @@ inputs: default: 'd3dcompiler_47 opengl32sw qt3d qtactiveqt qtbase qtconnectivity qtdeclarative qtgraphicaleffects qtimageformats qtlocation qtmultimedia qtquickcontrols qtquickcontrols2 qtremoteobjects qtscxml qtsensors qtsvg qttools qttranslations qtwebchannel qtwebsockets qtwebview qtwinextras qtxmlpatterns' windows_qt6_modules: required: false - default: 'qt5compat qtimageformats qtmultimedia qtpdf qtshadertools' + default: 'qtimageformats qtmultimedia qtpdf qtshadertools qtwebengine' windows_qt6_archives: required: false default: 'd3dcompiler_47 opengl32sw qtbase qtdeclarative qtsvg qttools qttranslations' @@ -248,13 +248,12 @@ runs: shell: bash - name: Prepare Qt folder and 7-Zip + shell: powershell run: | - mkdir -p /c/qt - if [ -d "/c/Program Files/7-Zip" ]; then - echo "/c/Program Files/7-Zip" >> $GITHUB_PATH - echo "C:\Program Files\7-Zip" >> $GITHUB_PATH - fi - shell: bash + New-Item -ItemType Directory -Force -Path "C:\Qt" | Out-Null + if (Test-Path "C:\Program Files\7-Zip") { + "C:\Program Files\7-Zip" | Add-Content -Path $env:GITHUB_PATH + } - name: Install Qt ${{ inputs.qt-version }} uses: jurplel/install-qt-action@v4 @@ -271,6 +270,7 @@ runs: setup-python: 'true' set-env: 'true' tools-only: 'false' + extra: '--external 7z' aqtsource: 'git+https://github.com/miurahr/aqtinstall.git@master' - name: Activate Bundled Python diff --git a/cmake/dependencies/vulkan.cmake b/cmake/dependencies/vulkan.cmake index 08cc0a3d..67aa410d 100644 --- a/cmake/dependencies/vulkan.cmake +++ b/cmake/dependencies/vulkan.cmake @@ -43,13 +43,13 @@ ELSE() FETCHCONTENT_DECLARE( vulkan_headers GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Headers.git - GIT_TAG v1.4.354 + GIT_TAG v1.4.304 GIT_SHALLOW TRUE ) FETCHCONTENT_DECLARE( vulkan_loader GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Loader.git - GIT_TAG v1.4.354 + GIT_TAG v1.4.304 GIT_SHALLOW TRUE ) @@ -69,7 +69,7 @@ ELSE() ) ENDIF() SET(RV_DEPS_VULKAN_VERSION - "1.4.354" + "1.4.304" ) ENDIF() From b79fb4c2dbc2aa4c82fe75235de448ba74671daa Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 12:38:14 -0700 Subject: [PATCH 08/12] feat(deps): add precompiled Vulkan to Linux dependencies and add WebEngine submodules to Windows Qt Signed-off-by: Michael Oliver --- .github/actions/build-windows/action.yml | 2 +- build.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/actions/build-windows/action.yml b/.github/actions/build-windows/action.yml index 675b0df7..29c5f39c 100644 --- a/.github/actions/build-windows/action.yml +++ b/.github/actions/build-windows/action.yml @@ -34,7 +34,7 @@ inputs: default: 'd3dcompiler_47 opengl32sw qt3d qtactiveqt qtbase qtconnectivity qtdeclarative qtgraphicaleffects qtimageformats qtlocation qtmultimedia qtquickcontrols qtquickcontrols2 qtremoteobjects qtscxml qtsensors qtsvg qttools qttranslations qtwebchannel qtwebsockets qtwebview qtwinextras qtxmlpatterns' windows_qt6_modules: required: false - default: 'qtimageformats qtmultimedia qtpdf qtshadertools qtwebengine' + default: 'qtimageformats qtmultimedia qtpdf qtpositioning qtshadertools qtwebchannel qtwebengine' windows_qt6_archives: required: false default: 'd3dcompiler_47 opengl32sw qtbase qtdeclarative qtsvg qttools qttranslations' diff --git a/build.sh b/build.sh index 2b433d85..86c82885 100755 --- a/build.sh +++ b/build.sh @@ -122,12 +122,13 @@ if [ "${INSTALL_DEPS}" -eq 1 ]; then libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev \ libxrandr-dev libxrender-dev libxcomposite-dev libxdamage-dev libxtst-dev libxxf86vm-dev \ libxkbcommon-dev libxkbcommon-x11-dev libffi-dev \ - libasound2-dev libpulse-dev + libasound2-dev libpulse-dev libvulkan-dev fi brew install --formula \ ninja pkg-config ccache glew doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost \ - openimageio openjpeg webp yaml-cpp spdlog openjph jpeg-turbo + openimageio openjpeg webp yaml-cpp spdlog openjph jpeg-turbo \ + vulkan-headers vulkan-loader # RHEL / Rocky Setup elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y epel-release dnf-plugins-core From c0cd25ed4741548fa1f2f724fe5d7e1e7a48ec7e Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 12:49:06 -0700 Subject: [PATCH 09/12] fix(ci): remove --external 7z to avoid Windows parallel extraction collision in aqtinstall Signed-off-by: Michael Oliver --- .github/actions/build-windows/action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/actions/build-windows/action.yml b/.github/actions/build-windows/action.yml index 29c5f39c..39b6a7be 100644 --- a/.github/actions/build-windows/action.yml +++ b/.github/actions/build-windows/action.yml @@ -270,7 +270,6 @@ runs: setup-python: 'true' set-env: 'true' tools-only: 'false' - extra: '--external 7z' aqtsource: 'git+https://github.com/miurahr/aqtinstall.git@master' - name: Activate Bundled Python From dfcdab5627aceb3069afbe61ad4d2ffe3558beba Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 13:14:13 -0700 Subject: [PATCH 10/12] fix(build): enforce generic dynamic library linking across macOS and Linux Signed-off-by: Michael Oliver --- .github/workflows/branch-build.yml | 1 + .github/workflows/dev-build.yml | 1 + .github/workflows/release.yml | 1 + build.sh | 12 +++ src/build/sanitize_homebrew_links.py | 147 ++++++++++++++++++++++----- 5 files changed, 134 insertions(+), 28 deletions(-) diff --git a/.github/workflows/branch-build.yml b/.github/workflows/branch-build.yml index d5165112..29406691 100644 --- a/.github/workflows/branch-build.yml +++ b/.github/workflows/branch-build.yml @@ -84,6 +84,7 @@ jobs: brew install --formula \ ninja readline sqlite3 xz zlib tcl-tk@8 python-tk autoconf automake libtool python@3.14 yasm clang-format black meson nasm pkg-config glew ccache doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost openimageio openjpeg webp yaml-cpp spdlog icu4c openjph jpeg-turbo + brew upgrade openexr imath opencolorio libraw libtiff boost openimageio spdlog openjph || true shell: bash - name: Save Homebrew cache diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index a52edcc5..9fc28c34 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -109,6 +109,7 @@ jobs: brew install --formula \ ninja readline sqlite3 xz zlib tcl-tk@8 python-tk autoconf automake libtool python@3.14 yasm clang-format black meson nasm pkg-config glew ccache doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost openimageio openjpeg webp yaml-cpp spdlog icu4c openjph jpeg-turbo + brew upgrade openexr imath opencolorio libraw libtiff boost openimageio spdlog openjph || true shell: bash - name: Save Homebrew cache diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c6268017..720abc5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -134,6 +134,7 @@ jobs: brew install --formula \ ninja readline sqlite3 xz zlib tcl-tk@8 python-tk autoconf automake libtool python@3.14 yasm clang-format black meson nasm pkg-config glew ccache doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost openimageio openjpeg webp yaml-cpp spdlog icu4c openjph jpeg-turbo + brew upgrade openexr imath opencolorio libraw libtiff boost openimageio spdlog openjph || true shell: bash - name: Save Homebrew cache diff --git a/build.sh b/build.sh index 86c82885..d0732c07 100755 --- a/build.sh +++ b/build.sh @@ -459,6 +459,12 @@ fi if [[ "$OSTYPE" == "darwin"* ]]; then echo "--- Sanitizing Homebrew Links ---" python3 "${PROJECT_ROOT}/src/build/sanitize_homebrew_links.py" "${BUILD_DIR}/stage" + if [ -d "${BUILD_DIR}/stage/app/UTV.app" ]; then + codesign --force --deep --sign - "${BUILD_DIR}/stage/app/UTV.app" 2>/dev/null || true + fi +elif [[ "$OSTYPE" == "linux"* ]]; then + echo "--- Sanitizing Dynamic Library Links ---" + python3 "${PROJECT_ROOT}/src/build/sanitize_homebrew_links.py" "${BUILD_DIR}/stage" fi if [ "${INSTALL}" -eq 1 ]; then @@ -468,6 +474,12 @@ if [ "${INSTALL}" -eq 1 ]; then if [[ "$OSTYPE" == "darwin"* ]]; then echo "--- Sanitizing Installed Homebrew Links ---" python3 "${PROJECT_ROOT}/src/build/sanitize_homebrew_links.py" "${INST_DIR}" + if [ -d "${INST_DIR}/UTV.app" ]; then + codesign --force --deep --sign - "${INST_DIR}/UTV.app" 2>/dev/null || true + fi + elif [[ "$OSTYPE" == "linux"* ]]; then + echo "--- Sanitizing Installed Dynamic Library Links ---" + python3 "${PROJECT_ROOT}/src/build/sanitize_homebrew_links.py" "${INST_DIR}" fi fi diff --git a/src/build/sanitize_homebrew_links.py b/src/build/sanitize_homebrew_links.py index 1f0ca816..6a1bae6c 100644 --- a/src/build/sanitize_homebrew_links.py +++ b/src/build/sanitize_homebrew_links.py @@ -1,7 +1,13 @@ #!/usr/bin/env python3 # -# Sanitize Homebrew Links for UTV -# Converts /opt/homebrew/Cellar/... links to /opt/homebrew/opt/... +# Sanitize Dynamic Library Links for UTV +# Ensures generic unversioned dynamic library links across packages: +# - macOS: Converts /opt/homebrew/Cellar/... links to /opt/homebrew/opt/... +# and versioned dylibs (e.g. libOpenEXR-3_4.33.dylib, libImath-3_2.30.dylib) +# to unversioned symlinks (e.g. libOpenEXR.dylib, libImath.dylib) via install_name_tool. +# - Linux: Converts versioned SONAMEs (e.g. libOpenEXR-3_4.so.33) to +# unversioned names (libOpenEXR.so) via patchelf if available. +# # Copyright (C) 2026 Makai Systems. All Rights Reserved. # @@ -11,14 +17,30 @@ import pathlib import re -BREW_PREFIX = os.environ.get("HOMEBREW_PREFIX", "/opt/homebrew" if os.uname().machine == "arm64" else "/usr/local") +DEFAULT_BREW_PREFIX = ( + "/opt/homebrew" + if (sys.platform == "darwin" and os.uname().machine == "arm64") + else ("/home/linuxbrew/.linuxbrew" if sys.platform.startswith("linux") else "/usr/local") +) +BREW_PREFIX = os.environ.get("HOMEBREW_PREFIX", DEFAULT_BREW_PREFIX) CELLAR_PATTERN = re.compile(rf"{re.escape(BREW_PREFIX)}/Cellar/([^/]+)/[^/]+/(.+)") +# System libraries that should never be renamed on Linux +LINUX_SYSTEM_LIBS = { + "libc.so", + "libm.so", + "libdl.so", + "libpthread.so", + "librt.so", + "libstdc++.so", + "libgcc_s.so", + "ld-linux", +} + -def get_dependencies(binary_path): +def get_dependencies_macho(binary_path): try: output = subprocess.check_output(["otool", "-L", binary_path], text=True) - # Skip first line (the binary itself) deps = [] for line in output.splitlines()[1:]: parts = line.strip().split() @@ -29,9 +51,8 @@ def get_dependencies(binary_path): return [] -def sanitize_binary(binary_path): - print(f"--- Sanitizing: {os.path.basename(binary_path)}") - deps = get_dependencies(binary_path) +def sanitize_macho_binary(binary_path): + deps = get_dependencies_macho(binary_path) changed = False for dep in deps: @@ -44,30 +65,89 @@ def sanitize_binary(binary_path): remaining_path = match.group(2) stable_path = f"{BREW_PREFIX}/opt/{pkg_name}/{remaining_path}" - # Verify stable path exists if os.path.exists(stable_path): - print(f" Mapping Cellar link: {current_dep} -> {stable_path}") + print(f" [{os.path.basename(binary_path)}] Mapping Cellar link: {current_dep} -> {stable_path}") try: subprocess.check_call(["install_name_tool", "-change", current_dep, stable_path, binary_path]) changed = True current_dep = stable_path except subprocess.CalledProcessError as e: - print(f" Error changing link: {e}") + print(f" [{os.path.basename(binary_path)}] Error changing Cellar link: {e}") else: - print(f" Warning: Stable path {stable_path} not found for {current_dep}") - - # 2. Map versioned OpenJPH dylib (e.g. libopenjph.0.31.dylib) to unversioned symlink (libopenjph.dylib) - # This ensures OpenUTV remains compatible across Homebrew OpenJPH minor version updates - if "openjph" in current_dep and re.search(r"libopenjph\.[0-9.]+\.dylib", current_dep): - unversioned_jph = f"{BREW_PREFIX}/opt/openjph/lib/libopenjph.dylib" - if os.path.exists(unversioned_jph): - print(f" Mapping OpenJPH versioned link: {current_dep} -> {unversioned_jph}") - try: - subprocess.check_call(["install_name_tool", "-change", current_dep, unversioned_jph, binary_path]) - changed = True - current_dep = unversioned_jph - except subprocess.CalledProcessError as e: - print(f" Error changing OpenJPH link: {e}") + # If stable path does not exist as-is, check if remaining_path has versioned dylib + base = os.path.basename(remaining_path) + generic_base = re.sub(r"(-[0-9_]+|\.[0-9]+).*\.dylib$", ".dylib", base) + alt_path = f"{BREW_PREFIX}/opt/{pkg_name}/lib/{generic_base}" + if os.path.exists(alt_path): + print(f" [{os.path.basename(binary_path)}] Mapping Cellar link: {current_dep} -> {alt_path}") + try: + subprocess.check_call(["install_name_tool", "-change", current_dep, alt_path, binary_path]) + changed = True + current_dep = alt_path + except subprocess.CalledProcessError as e: + print(f" [{os.path.basename(binary_path)}] Error changing Cellar link: {e}") + + # 2. Map versioned dylibs (e.g. libOpenEXR-3_4.33.dylib, libImath-3_2.30.dylib, libopenjph.0.31.dylib) + # to generic unversioned symlinks (e.g. libOpenEXR.dylib, libImath.dylib, libopenjph.dylib). + # This ensures OpenUTV remains compatible across package manager minor updates. + if current_dep.startswith(BREW_PREFIX) or "/opt/" in current_dep or "/Cellar/" in current_dep: + dir_name = os.path.dirname(current_dep) + base_name = os.path.basename(current_dep) + generic_name = re.sub(r"(-[0-9_]+|\.[0-9]+).*\.dylib$", ".dylib", base_name) + + if generic_name != base_name: + generic_path = os.path.join(dir_name, generic_name) + # If dir_name was inside a Cellar version folder, redirect to opt + if "/Cellar/" in generic_path: + generic_path = re.sub( + rf"{re.escape(BREW_PREFIX)}/Cellar/([^/]+)/[^/]+/", + rf"{BREW_PREFIX}/opt/\1/", + generic_path, + ) + + if os.path.exists(generic_path) or os.path.exists(os.path.dirname(generic_path)): + print( + f" [{os.path.basename(binary_path)}] Mapping to generic dylib link: {current_dep} -> {generic_path}" + ) + try: + subprocess.check_call(["install_name_tool", "-change", current_dep, generic_path, binary_path]) + changed = True + current_dep = generic_path + except subprocess.CalledProcessError as e: + print(f" [{os.path.basename(binary_path)}] Error changing generic link: {e}") + + return changed + + +def sanitize_elf_binary(binary_path): + if not hasattr(sanitize_elf_binary, "patchelf_available"): + sanitize_elf_binary.patchelf_available = ( + subprocess.call(["which", "patchelf"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0 + ) + + if not sanitize_elf_binary.patchelf_available: + return False + + try: + output = subprocess.check_output(["patchelf", "--print-needed", binary_path], text=True) + needed_libs = [line.strip() for line in output.splitlines() if line.strip()] + except Exception: + return False + + changed = False + for lib in needed_libs: + # Skip glibc and system standard libraries + if any(sys_lib in lib for sys_lib in LINUX_SYSTEM_LIBS): + continue + + generic_lib = re.sub(r"(-[0-9_]+|\.so\.[0-9]+|\.[0-9]+).*", ".so", lib) + if generic_lib != lib and generic_lib.endswith(".so"): + print(f" [{os.path.basename(binary_path)}] Mapping needed ELF lib: {lib} -> {generic_lib}") + try: + subprocess.check_call(["patchelf", "--replace-needed", lib, generic_lib, binary_path]) + changed = True + except subprocess.CalledProcessError as e: + print(f" [{os.path.basename(binary_path)}] Error replacing needed library {lib}: {e}") return changed @@ -85,15 +165,26 @@ def main(): print(f"Scanning for binaries in: {search_path}") count = 0 - for root, _, files in os.walk(search_path): + for root, dirs, files in os.walk(search_path): + # Skip Python site-packages in app/lib to keep execution fast + if "site-packages" in root: + continue + for f in files: full_path = os.path.join(root, f) - # Basic check for Mach-O binaries + # Skip symlinks + if os.path.islink(full_path): + continue + + # Basic check for Mach-O or ELF binaries try: file_info = subprocess.check_output(["file", "-b", full_path], text=True) if "Mach-O" in file_info: - if sanitize_binary(full_path): + if sanitize_macho_binary(full_path): + count += 1 + elif "ELF" in file_info: + if sanitize_elf_binary(full_path): count += 1 except Exception: continue From 0749e0aefe285f79ec347b6d00b9ec3e979cbccc Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 24 Sep 2026 13:21:08 -0700 Subject: [PATCH 11/12] ci: enable Homebrew auto-update across macOS and Linux workflows Signed-off-by: Michael Oliver --- .github/workflows/branch-build.yml | 7 +++++++ .github/workflows/dev-build.yml | 7 +++++++ .github/workflows/release.yml | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/.github/workflows/branch-build.yml b/.github/workflows/branch-build.yml index 29406691..ecfa35a7 100644 --- a/.github/workflows/branch-build.yml +++ b/.github/workflows/branch-build.yml @@ -80,7 +80,10 @@ jobs: ${{ runner.os }}-brew- - name: Install Homebrew dependencies + env: + HOMEBREW_NO_AUTO_UPDATE: "0" run: | + brew update brew install --formula \ ninja readline sqlite3 xz zlib tcl-tk@8 python-tk autoconf automake libtool python@3.14 yasm clang-format black meson nasm pkg-config glew ccache doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost openimageio openjpeg webp yaml-cpp spdlog icu4c openjph jpeg-turbo @@ -218,13 +221,17 @@ jobs: ${{ runner.os }}-brew- - name: Install Homebrew dependencies + env: + HOMEBREW_NO_AUTO_UPDATE: "0" run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" sudo apt-get update && sudo apt-get install -y build-essential patchelf mold tcsh libgl1-mesa-dev libglu1-mesa-dev libegl1-mesa-dev libosmesa6-dev libudev-dev libaio-dev libreadline-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxrandr-dev libxrender-dev libxcomposite-dev libxdamage-dev libxtst-dev libxxf86vm-dev libxkbcommon-dev libxkbcommon-x11-dev libffi-dev libasound2-dev libpulse-dev + brew update brew install --formula \ ninja pkg-config ccache glew doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost \ openimageio openjpeg webp yaml-cpp spdlog openjph jpeg-turbo + brew upgrade openexr imath opencolorio libraw libtiff boost openimageio spdlog openjph || true shell: bash - name: Save Homebrew cache diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 9fc28c34..4e511189 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -105,7 +105,10 @@ jobs: ${{ runner.os }}-brew- - name: Install Homebrew dependencies + env: + HOMEBREW_NO_AUTO_UPDATE: "0" run: | + brew update brew install --formula \ ninja readline sqlite3 xz zlib tcl-tk@8 python-tk autoconf automake libtool python@3.14 yasm clang-format black meson nasm pkg-config glew ccache doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost openimageio openjpeg webp yaml-cpp spdlog icu4c openjph jpeg-turbo @@ -353,13 +356,17 @@ jobs: ${{ runner.os }}-brew- - name: Install Homebrew dependencies + env: + HOMEBREW_NO_AUTO_UPDATE: "0" run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" sudo apt-get update && sudo apt-get install -y build-essential patchelf mold tcsh libgl1-mesa-dev libglu1-mesa-dev libegl1-mesa-dev libosmesa6-dev libudev-dev libaio-dev libreadline-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxrandr-dev libxrender-dev libxcomposite-dev libxdamage-dev libxtst-dev libxxf86vm-dev libxkbcommon-dev libxkbcommon-x11-dev libffi-dev libasound2-dev libpulse-dev + brew update brew install --formula \ ninja pkg-config ccache glew doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost \ openimageio openjpeg webp yaml-cpp spdlog openjph jpeg-turbo + brew upgrade openexr imath opencolorio libraw libtiff boost openimageio spdlog openjph || true shell: bash - name: Save Homebrew cache diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 720abc5d..2914e5fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -130,7 +130,10 @@ jobs: ${{ runner.os }}-brew- - name: Install Homebrew dependencies + env: + HOMEBREW_NO_AUTO_UPDATE: "0" run: | + brew update brew install --formula \ ninja readline sqlite3 xz zlib tcl-tk@8 python-tk autoconf automake libtool python@3.14 yasm clang-format black meson nasm pkg-config glew ccache doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost openimageio openjpeg webp yaml-cpp spdlog icu4c openjph jpeg-turbo @@ -476,13 +479,17 @@ jobs: ${{ runner.os }}-brew- - name: Install Homebrew dependencies + env: + HOMEBREW_NO_AUTO_UPDATE: "0" run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" sudo apt-get update && sudo apt-get install -y build-essential patchelf mold tcsh libgl1-mesa-dev libglu1-mesa-dev libegl1-mesa-dev libosmesa6-dev libudev-dev libaio-dev libreadline-dev libx11-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev libxrandr-dev libxrender-dev libxcomposite-dev libxdamage-dev libxtst-dev libxxf86vm-dev libxkbcommon-dev libxkbcommon-x11-dev libffi-dev libasound2-dev libpulse-dev + brew update brew install --formula \ ninja pkg-config ccache glew doctest qt pyside \ ffmpeg openexr imath opencolorio libraw libtiff libpng libspng boost \ openimageio openjpeg webp yaml-cpp spdlog openjph jpeg-turbo + brew upgrade openexr imath opencolorio libraw libtiff boost openimageio spdlog openjph || true shell: bash - name: Save Homebrew cache From 0c925660a04629aa99aab2de37b1a8d0e4cc3ed8 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Fri, 25 Sep 2026 10:03:07 -0700 Subject: [PATCH 12/12] ci: upload uncompressed directories in branch CI to prevent double zipping Signed-off-by: Michael Oliver --- .github/workflows/branch-build.yml | 31 +++++------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/.github/workflows/branch-build.yml b/.github/workflows/branch-build.yml index ecfa35a7..ff34993d 100644 --- a/.github/workflows/branch-build.yml +++ b/.github/workflows/branch-build.yml @@ -122,22 +122,17 @@ jobs: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} - - name: Package Ad-Hoc Signed App + - name: Sign Ad-Hoc App run: | python3 "${PWD}/src/build/sanitize_homebrew_links.py" "_build/stage/app/UTV.app" || true codesign --force --deep --sign - "_build/stage/app/UTV.app" - mkdir -p _dist - SHORT_SHA=$(git rev-parse --short=7 HEAD) - SAFE_BRANCH=$(echo "${{ github.ref_name }}" | tr '/' '-') - cd _build/stage/app - ditto -c -k --keepParent UTV.app "${GITHUB_WORKSPACE}/_dist/UTV-${SAFE_BRANCH}-${SHORT_SHA}-macOS-arm64.zip" shell: bash - name: Upload macOS Branch Artifact uses: actions/upload-artifact@v7 with: name: UTV-macOS-arm64 - path: _dist/*.zip + path: _build/stage/app/UTV.app retention-days: 5 build-windows: @@ -167,21 +162,16 @@ jobs: with: version: 'dev' - - name: Package Windows Branch Archive + - name: Stage Windows Branch Directory shell: powershell run: | - $ShortSha = git rev-parse --short=7 HEAD - $SafeBranch = "${{ github.ref_name }}".Replace("/", "-") - $ZipName = "UTV-$SafeBranch-$ShortSha-windows-x64.zip" - New-Item -ItemType Directory -Force -Path "_dist" | Out-Null Rename-Item -Path "_install" -NewName "utv-windows-x64" - Compress-Archive -Path "utv-windows-x64" -DestinationPath "_dist\$ZipName" -CompressionLevel Optimal - name: Upload Windows Branch Artifact uses: actions/upload-artifact@v7 with: name: UTV-windows-x64 - path: _dist/*.zip + path: utv-windows-x64 retention-days: 5 build-linux: @@ -265,22 +255,11 @@ jobs: path: ~/.ccache key: ${{ runner.os }}-ccache-${{ github.sha }} - - name: Package Linux Branch Archive - if: success() - run: | - mkdir -p _dist - SHORT_SHA=$(git rev-parse --short=7 HEAD) - SAFE_BRANCH=$(echo "${{ github.ref_name }}" | tr '/' '-') - if [ -d "_build/stage/app" ]; then - tar -czf "_dist/UTV-${SAFE_BRANCH}-${SHORT_SHA}-linux-x64.tar.gz" -C _build/stage/app . - fi - shell: bash - - name: Upload Linux Branch Artifact if: success() uses: actions/upload-artifact@v7 with: name: UTV-linux-x64 - path: _dist/*.tar.gz + path: _build/stage/app retention-days: 5