瀏覽代碼

CI: Update GitHub Actions workflows and repository actions

PatTheMav 10 月之前
父節點
當前提交
153962b3a2
共有 36 個文件被更改,包括 508 次插入642 次删除
  1. 14 11
      .github/actions/build-plugin/action.yaml
  2. 16 20
      .github/actions/package-plugin/action.yaml
  3. 14 9
      .github/actions/setup-macos-codesigning/action.yaml
  4. 0 1
      .github/scripts/.Brewfile
  5. 0 2
      .github/scripts/.Wingetfile
  6. 37 56
      .github/scripts/Build-Windows.ps1
  7. 6 31
      .github/scripts/Package-Windows.ps1
  8. 0 1
      .github/scripts/build-linux
  9. 0 1
      .github/scripts/build-macos
  10. 153 0
      .github/scripts/build-macos
  11. 16 69
      .github/scripts/build-ubuntu
  12. 0 1
      .github/scripts/package-linux
  13. 0 1
      .github/scripts/package-macos
  14. 173 0
      .github/scripts/package-macos
  15. 21 114
      .github/scripts/package-ubuntu
  16. 0 70
      .github/scripts/utils.pwsh/Expand-ArchiveExt.ps1
  17. 0 70
      .github/scripts/utils.pwsh/Install-BuildDependencies.ps1
  18. 1 1
      .github/scripts/utils.zsh/check_macos
  19. 0 0
      .github/scripts/utils.zsh/check_ubuntu
  20. 1 3
      .github/scripts/utils.zsh/log_debug
  21. 1 3
      .github/scripts/utils.zsh/log_error
  22. 7 11
      .github/scripts/utils.zsh/log_group
  23. 1 7
      .github/scripts/utils.zsh/log_info
  24. 1 7
      .github/scripts/utils.zsh/log_output
  25. 1 7
      .github/scripts/utils.zsh/log_status
  26. 1 5
      .github/scripts/utils.zsh/log_warning
  27. 0 9
      .github/scripts/utils.zsh/read_codesign
  28. 0 7
      .github/scripts/utils.zsh/read_codesign_installer
  29. 0 38
      .github/scripts/utils.zsh/read_codesign_pass
  30. 0 7
      .github/scripts/utils.zsh/read_codesign_team
  31. 0 7
      .github/scripts/utils.zsh/read_codesign_user
  32. 0 42
      .github/scripts/utils.zsh/setup_ccache
  33. 0 0
      .github/scripts/utils.zsh/setup_ubuntu
  34. 35 19
      .github/workflows/build-project.yaml
  35. 6 6
      .github/workflows/check-format.yaml
  36. 3 6
      .github/workflows/push.yaml

+ 14 - 11
.github/actions/build-plugin/action.yaml

@@ -1,23 +1,23 @@
-name: 'Set up and build plugin'
-description: 'Builds the plugin for specified architecture and build config'
+name: Set up and build plugin
+description: Builds the plugin for specified architecture and build config
 inputs:
   target:
-    description: 'Target architecture for dependencies'
+    description: Target architecture for dependencies
     required: true
   config:
-    description: 'Build configuration'
+    description: Build configuration
     required: false
-    default: 'RelWithDebInfo'
+    default: RelWithDebInfo
   codesign:
-    description: 'Enable codesigning (macOS only)'
+    description: Enable codesigning (macOS only)
     required: false
     default: 'false'
   codesignIdent:
-    description: 'Developer ID for application codesigning (macOS only)'
+    description: Developer ID for application codesigning (macOS only)
     required: false
     default: '-'
   workingDirectory:
-    description: 'Working directory for packaging'
+    description: Working directory for packaging
     required: false
     default: ${{ github.workspace }}
 runs:
@@ -28,6 +28,7 @@ runs:
       shell: zsh --no-rcs --errexit --pipefail {0}
       working-directory: ${{ inputs.workingDirectory }}
       env:
+        CCACHE_DIR: ${{ inputs.workingDirectory }}/.ccache
         CODESIGN_IDENT: ${{ inputs.codesignIdent }}
         CODESIGN_TEAM: ${{ inputs.codesignTeam }}
       run: |
@@ -55,16 +56,18 @@ runs:
       if: runner.os == 'Linux'
       shell: zsh --no-rcs --errexit --pipefail {0}
       working-directory: ${{ inputs.workingDirectory }}
+      env:
+        CCACHE_DIR: ${{ inputs.workingDirectory }}/.ccache
       run: |
         : Run Ubuntu Build
 
         local -a build_args=(
-          --target linux-${{ inputs.target }}
+          --target ubuntu-${{ inputs.target }}
           --config ${{ inputs.config }}
         )
         if (( ${+RUNNER_DEBUG} )) build_args+=(--debug)
 
-        .github/scripts/build-linux ${build_args}
+        .github/scripts/build-ubuntu ${build_args}
 
     - name: Run Windows Build
       if: runner.os == 'Windows'
@@ -86,7 +89,7 @@ runs:
       if: contains(fromJSON('["Linux", "macOS"]'),runner.os)
       shell: zsh --no-rcs --errexit --pipefail {0}
       env:
-        CCACHE_CONFIGPATH: ${{ inputs.workingDirectory }}/.ccache.conf
+        CCACHE_DIR: ${{ inputs.workingDirectory }}/.ccache
       run: |
         : Create Summary 📊
 

+ 16 - 20
.github/actions/package-plugin/action.yaml

@@ -1,47 +1,47 @@
-name: 'Package plugin'
-description: 'Packages the plugin for specified architecture and build config.'
+name: Package plugin
+description: Packages the plugin for specified architecture and build config.
 inputs:
   target:
-    description: 'Build target for dependencies'
+    description: Build target for dependencies
     required: true
   config:
-    description: 'Build configuration'
+    description: Build configuration
     required: false
-    default: 'RelWithDebInfo'
+    default: RelWithDebInfo
   codesign:
-    description: 'Enable codesigning (macOS only)'
+    description: Enable codesigning (macOS only)
     required: false
     default: 'false'
   notarize:
-    description: 'Enable notarization (macOS only)'
+    description: Enable notarization (macOS only)
     required: false
     default: 'false'
   codesignIdent:
-    description: 'Developer ID for application codesigning (macOS only)'
+    description: Developer ID for application codesigning (macOS only)
     required: false
     default: '-'
   installerIdent:
-    description: 'Developer ID for installer package codesigning (macOS only)'
+    description: Developer ID for installer package codesigning (macOS only)
     required: false
     default: ''
   codesignTeam:
-    description: 'Developer team for codesigning (macOS only)'
+    description: Developer team for codesigning (macOS only)
     required: false
     default: ''
   codesignUser:
-    description: 'Apple ID username for notarization (macOS only)'
+    description: Apple ID username for notarization (macOS only)
     required: false
     default: ''
   codesignPass:
-    description: 'Apple ID password for notarization (macOS only)'
+    description: Apple ID password for notarization (macOS only)
     required: false
     default: ''
   package:
-    description: 'Create Windows or macOS installation package'
+    description: Create Windows or macOS installation package
     required: false
     default: 'false'
   workingDirectory:
-    description: 'Working directory for packaging'
+    description: Working directory for packaging
     required: false
     default: ${{ github.workspace }}
 runs:
@@ -87,14 +87,14 @@ runs:
       run: |
         : Run Ubuntu Packaging
         package_args=(
-          --target linux-${{ inputs.target }}
+          --target ubuntu-${{ inputs.target }}
           --config ${{ inputs.config }}
         )
         if (( ${+RUNNER_DEBUG} )) build_args+=(--debug)
 
         if [[ '${{ inputs.package }}' == 'true' ]] package_args+=(--package)
 
-        .github/scripts/package-linux ${package_args}
+        .github/scripts/package-ubuntu ${package_args}
 
     - name: Run Windows Packaging
       if: runner.os == 'Windows'
@@ -110,8 +110,4 @@ runs:
           Configuration = '${{ inputs.config }}'
         }
 
-        if ( '${{ inputs.package }}' -eq 'true' ) {
-          $PackageArgs += @{BuildInstaller = $true}
-        }
-
         .github/scripts/Package-Windows.ps1 @PackageArgs

+ 14 - 9
.github/actions/setup-macos-codesigning/action.yaml

@@ -30,10 +30,13 @@ outputs:
     value: ${{ steps.codesign.outputs.haveCodesignIdent }}
   haveProvisioningProfile:
     description: True if necessary provisioning profile credentials were found
-    value: ${{ steps.provisioning.outputs.haveProvisioningProfile }}
+    value: ${{ steps.provisioning.outputs.haveProvisioningProfile || steps.codesign.outputs.haveProvisioningProfile }}
+  provisioningProfileUUID:
+    description: UUID of imported provisioning profile
+    value: ${{ steps.provisioning.outputs.provisioningProfileUUID }}
   haveNotarizationUser:
     description: True if necessary notarization credentials were found
-    value: ${{ steps.notarization.outputs.haveNotarizationUser }}
+    value: ${{ steps.notarization.outputs.haveNotarizationUser || steps.codesign.outputs.haveNotarizationUser }}
   codesignIdent:
     description: Codesigning identity
     value: ${{ steps.codesign.outputs.codesignIdent }}
@@ -64,7 +67,7 @@ runs:
         MAOCS_SIGNING_CERT_PASSWORD: ${{ inputs.certificatePassword }}
         MACOS_KEYCHAIN_PASSWORD: ${{ inputs.keychainPassword }}
       run: |
-        : macOS Codesigning ✍️
+        : macOS Code Signing ✍️
         if (( ${+RUNNER_DEBUG} )) setopt XTRACE
 
         if [[ ${MACOS_SIGNING_IDENTITY} && ${MACOS_SIGNING_IDENTITY_INSTALLER} && ${MACOS_SIGNING_CERT} ]] {
@@ -73,7 +76,7 @@ runs:
           local -r certificate_path="${RUNNER_TEMP}/build_certificate.p12"
           local -r keychain_path="${RUNNER_TEMP}/app-signing.keychain-db"
 
-          print -n "${MACOS_SIGNING_CERT}" | base64 --decode --output="${certificate_path}"
+          print -n "${MACOS_SIGNING_CERT}" | base64 --decode --output=${certificate_path}
 
           : "${MACOS_KEYCHAIN_PASSWORD:="$(print ${RANDOM} | shasum | head -c 32)"}"
 
@@ -100,6 +103,8 @@ runs:
           print "codesignTeam=${team_id}" >> $GITHUB_OUTPUT
         } else {
           print 'haveCodesignIdent=false' >> $GITHUB_OUTPUT
+          print 'haveProvisioningProfile=false' >> $GITHUB_OUTPUT
+          print 'haveNotarizationUser=false' >> $GITHUB_OUTPUT
         }
 
     - name: Provisioning Profile 👤
@@ -112,12 +117,12 @@ runs:
         : Provisioning Profile 👤
         if (( ${+RUNNER_DEBUG} )) setopt XTRACE
 
-        if [[ ${MACOS_SIGNING_PROVISIONING_PROFILE} ]] {
+        if [[ "${MACOS_SIGNING_PROVISIONING_PROFILE}" ]] {
           print 'haveProvisioningProfile=true' >> $GITHUB_OUTPUT
 
           local -r profile_path="${RUNNER_TEMP}/build_profile.provisionprofile"
           print -n "${MACOS_SIGNING_PROVISIONING_PROFILE}" \
-            | base64 --decode --output ${profile_path}
+            | base64 --decode --output="${profile_path}"
 
           print '::group::Provisioning Profile Setup'
           mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
@@ -125,7 +130,7 @@ runs:
           local -r uuid="$(plutil -extract UUID raw ${RUNNER_TEMP}/build_profile.plist)"
           local -r team_id="$(plutil -extract TeamIdentifier.0 raw -expect string ${RUNNER_TEMP}/build_profile.plist)"
 
-          if [[ ${team_id} != '${{ steps.codesign.codesignTeam }}' ]] {
+          if [[ ${team_id} != '${{ steps.codesign.outputs.codesignTeam }}' ]] {
             print '::notice::Code Signing team in provisioning profile does not match certificate.'
           }
 
@@ -137,9 +142,9 @@ runs:
         }
 
     - name: Notarization 🧑‍💼
-      shell: zsh --no-rcs --errexit --pipefail {0}
       id: notarization
-      if: ${{ fromJSON(steps.codesign.outputs.haveCodesignIdent) }}
+      if: fromJSON(steps.codesign.outputs.haveCodesignIdent)
+      shell: zsh --no-rcs --errexit --pipefail {0}
       env:
           MACOS_NOTARIZATION_USERNAME: ${{ inputs.notarizationUser }}
           MACOS_NOTARIZATION_PASSWORD: ${{ inputs.notarizationPassword }}

+ 0 - 1
.github/scripts/.Brewfile

@@ -1,6 +1,5 @@
 brew "ccache"
 brew "coreutils"
 brew "cmake"
-brew "git"
 brew "jq"
 brew "xcbeautify"

+ 0 - 2
.github/scripts/.Wingetfile

@@ -1,2 +0,0 @@
-package 'cmake', path: 'Cmake\bin', bin: 'cmake'
-package 'innosetup', path: 'Inno Setup 6', bin: 'iscc'

+ 37 - 56
.github/scripts/Build-Windows.ps1

@@ -3,10 +3,7 @@ param(
     [ValidateSet('x64')]
     [string] $Target = 'x64',
     [ValidateSet('Debug', 'RelWithDebInfo', 'Release', 'MinSizeRel')]
-    [string] $Configuration = 'RelWithDebInfo',
-    [switch] $SkipAll,
-    [switch] $SkipBuild,
-    [switch] $SkipDeps
+    [string] $Configuration = 'RelWithDebInfo'
 )
 
 $ErrorActionPreference = 'Stop'
@@ -16,12 +13,16 @@ if ( $DebugPreference -eq 'Continue' ) {
     $InformationPreference = 'Continue'
 }
 
+if ( $env:CI -eq $null ) {
+    throw "Build-Windows.ps1 requires CI environment"
+}
+
 if ( ! ( [System.Environment]::Is64BitOperatingSystem ) ) {
     throw "A 64-bit system is required to build the project."
 }
 
-if ( $PSVersionTable.PSVersion -lt '7.0.0' ) {
-    Write-Warning 'The obs-deps PowerShell build script requires PowerShell Core 7. Install or upgrade your PowerShell version: https://aka.ms/pscore6'
+if ( $PSVersionTable.PSVersion -lt '7.2.0' ) {
+    Write-Warning 'The obs-studio PowerShell build script requires PowerShell Core 7. Install or upgrade your PowerShell version: https://aka.ms/pscore6'
     exit 2
 }
 
@@ -35,7 +36,6 @@ function Build {
 
     $ScriptHome = $PSScriptRoot
     $ProjectRoot = Resolve-Path -Path "$PSScriptRoot/../.."
-    $BuildSpecFile = "${ProjectRoot}/buildspec.json"
 
     $UtilityFunctions = Get-ChildItem -Path $PSScriptRoot/utils.pwsh/*.ps1 -Recurse
 
@@ -44,58 +44,39 @@ function Build {
         . $Utility.FullName
     }
 
-    $BuildSpec = Get-Content -Path ${BuildSpecFile} -Raw | ConvertFrom-Json
-    $ProductName = $BuildSpec.name
-    $ProductVersion = $BuildSpec.version
+    Push-Location -Stack BuildTemp
+    Ensure-Location $ProjectRoot
 
-    if ( ! $SkipDeps ) {
-        Install-BuildDependencies -WingetFile "${ScriptHome}/.Wingetfile"
-    }
+    $CmakeArgs = @('--preset', "windows-ci-${Target}")
+    $CmakeBuildArgs = @('--build')
+    $CmakeInstallArgs = @()
 
-    Push-Location -Stack BuildTemp
-    if ( ! ( ( $SkipAll ) -or ( $SkipBuild ) ) ) {
-        Ensure-Location $ProjectRoot
-
-        $CmakeArgs = @()
-        $CmakeBuildArgs = @()
-        $CmakeInstallArgs = @()
-
-        if ( $VerbosePreference -eq 'Continue' ) {
-            $CmakeBuildArgs += ('--verbose')
-            $CmakeInstallArgs += ('--verbose')
-        }
-
-        if ( $DebugPreference -eq 'Continue' ) {
-            $CmakeArgs += ('--debug-output')
-        }
-
-        $Preset = "windows-$(if ( $Env:CI -ne $null ) { 'ci-' })${Target}"
-
-        $CmakeArgs += @(
-            '--preset', $Preset
-        )
-
-        $CmakeBuildArgs += @(
-            '--build'
-            '--preset', $Preset
-            '--config', $Configuration
-            '--parallel'
-            '--', '/consoleLoggerParameters:Summary', '/noLogo'
-        )
-
-        $CmakeInstallArgs += @(
-            '--install', "build_${Target}"
-            '--prefix', "${ProjectRoot}/release/${Configuration}"
-            '--config', $Configuration
-        )
-
-        Log-Group "Configuring ${ProductName}..."
-        Invoke-External cmake @CmakeArgs
-
-        Log-Group "Building ${ProductName}..."
-        Invoke-External cmake @CmakeBuildArgs
+    if ( $DebugPreference -eq 'Continue' ) {
+        $CmakeArgs += ('--debug-output')
+        $CmakeBuildArgs += ('--verbose')
+        $CmakeInstallArgs += ('--verbose')
     }
-    Log-Group "Install ${ProductName}..."
+
+    $CmakeBuildArgs += @(
+        '--preset', "windows-${Target}"
+        '--config', $Configuration
+        '--parallel'
+        '--', '/consoleLoggerParameters:Summary', '/noLogo'
+    )
+
+    $CmakeInstallArgs += @(
+        '--install', "build_${Target}"
+        '--prefix', "${ProjectRoot}/release/${Configuration}"
+        '--config', $Configuration
+    )
+
+    Log-Group "Configuring ${ProductName}..."
+    Invoke-External cmake @CmakeArgs
+
+    Log-Group "Building ${ProductName}..."
+    Invoke-External cmake @CmakeBuildArgs
+
+    Log-Group "Installing ${ProductName}..."
     Invoke-External cmake @CmakeInstallArgs
 
     Pop-Location -Stack BuildTemp

+ 6 - 31
.github/scripts/Package-Windows.ps1

@@ -3,9 +3,7 @@ param(
     [ValidateSet('x64')]
     [string] $Target = 'x64',
     [ValidateSet('Debug', 'RelWithDebInfo', 'Release', 'MinSizeRel')]
-    [string] $Configuration = 'RelWithDebInfo',
-    [switch] $BuildInstaller,
-    [switch] $SkipDeps
+    [string] $Configuration = 'RelWithDebInfo'
 )
 
 $ErrorActionPreference = 'Stop'
@@ -15,21 +13,22 @@ if ( $DebugPreference -eq 'Continue' ) {
     $InformationPreference = 'Continue'
 }
 
+if ( $env:CI -eq $null ) {
+    throw "Package-Windows.ps1 requires CI environment"
+}
+
 if ( ! ( [System.Environment]::Is64BitOperatingSystem ) ) {
     throw "Packaging script requires a 64-bit system to build and run."
 }
 
-
-if ( $PSVersionTable.PSVersion -lt '7.0.0' ) {
+if ( $PSVersionTable.PSVersion -lt '7.2.0' ) {
     Write-Warning 'The packaging script requires PowerShell Core 7. Install or upgrade your PowerShell version: https://aka.ms/pscore6'
     exit 2
 }
 
 function Package {
     trap {
-        Pop-Location -Stack BuildTemp -ErrorAction 'SilentlyContinue'
         Write-Error $_
-        Log-Group
         exit 2
     }
 
@@ -50,15 +49,10 @@ function Package {
 
     $OutputName = "${ProductName}-${ProductVersion}-windows-${Target}"
 
-    if ( ! $SkipDeps ) {
-        Install-BuildDependencies -WingetFile "${ScriptHome}/.Wingetfile"
-    }
-
     $RemoveArgs = @{
         ErrorAction = 'SilentlyContinue'
         Path = @(
             "${ProjectRoot}/release/${ProductName}-*-windows-*.zip"
-            "${ProjectRoot}/release/${ProductName}-*-windows-*.exe"
         )
     }
 
@@ -73,25 +67,6 @@ function Package {
     }
     Compress-Archive -Force @CompressArgs
     Log-Group
-
-    if ( ( $BuildInstaller ) ) {
-        Log-Group "Packaging ${ProductName}..."
-
-        $IsccFile = "${ProjectRoot}/build_${Target}/installer-Windows.generated.iss"
-        if ( ! ( Test-Path -Path $IsccFile ) ) {
-            throw 'InnoSetup install script not found. Run the build script or the CMake build and install procedures first.'
-        }
-
-        Log-Information 'Creating InnoSetup installer...'
-        Push-Location -Stack BuildTemp
-        Ensure-Location -Path "${ProjectRoot}/release"
-        Copy-Item -Path ${Configuration} -Destination Package -Recurse
-        Invoke-External iscc ${IsccFile} /O"${ProjectRoot}/release" /F"${OutputName}-Installer"
-        Remove-Item -Path Package -Recurse
-        Pop-Location -Stack BuildTemp
-
-        Log-Group
-    }
 }
 
 Package

+ 0 - 1
.github/scripts/build-linux

@@ -1 +0,0 @@
-.build.zsh

+ 0 - 1
.github/scripts/build-macos

@@ -1 +0,0 @@
-.build.zsh

+ 153 - 0
.github/scripts/build-macos

@@ -0,0 +1,153 @@
+#!/usr/bin/env zsh
+
+builtin emulate -L zsh
+setopt EXTENDED_GLOB
+setopt PUSHD_SILENT
+setopt ERR_EXIT
+setopt ERR_RETURN
+setopt NO_UNSET
+setopt PIPE_FAIL
+setopt NO_AUTO_PUSHD
+setopt NO_PUSHD_IGNORE_DUPS
+setopt FUNCTION_ARGZERO
+
+## Enable for script debugging
+# setopt WARN_CREATE_GLOBAL
+# setopt WARN_NESTED_VAR
+# setopt XTRACE
+
+if (( ! ${+CI} )) {
+  print -u2 -PR "%F{1}    ✖︎ ${ZSH_ARGZERO:t:r} requires CI environment.%f"
+  exit 1
+}
+
+autoload -Uz is-at-least && if ! is-at-least 5.9; then
+  print -u2 -PR "${CI:+::error::}%F{1}${funcstack[1]##*/}:%f Running on Zsh version %B${ZSH_VERSION}%b, but Zsh %B5.2%b is the minimum supported version. Upgrade Zsh to fix this issue."
+  exit 1
+fi
+
+TRAPZERR() {
+  print -u2 -PR "::error::%F{1}    ✖︎ script execution error%f"
+  print -PR -e "
+  Callstack:
+  ${(j:\n     :)funcfiletrace}
+  "
+
+  exit 2
+}
+
+build() {
+  if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h}
+  local host_os='macos'
+  local project_root=${SCRIPT_HOME:A:h:h}
+  local buildspec_file=${project_root}/buildspec.json
+
+  fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath})
+  autoload -Uz log_group log_info log_error log_output check_macos setup_ccache
+
+  if [[ ! -r ${buildspec_file} ]] {
+    log_error \
+      'No buildspec.json found. Please create a build specification for your project.'
+    return 2
+  }
+
+  local -i debug=0
+
+  local config='RelWithDebInfo'
+  local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel)
+  local -i codesign=0
+
+  local -a args
+  while (( # )) {
+    case ${1} {
+      -c|--config)
+        if (( # == 1 )) || [[ ${2:0:1} == '-' ]] {
+          log_error "Missing value for option %B${1}%b"
+          log_output ${_usage}
+          exit 2
+        }
+        ;;
+    }
+    case ${1} {
+      --) shift; args+=($@); break ;;
+      -c|--config)
+        if (( ! ${_valid_configs[(Ie)${2}]} )) {
+          log_error "Invalid value %B${2}%b for option %B${1}%b"
+          exit 2
+        }
+        config=${2}
+        shift 2
+        ;;
+      -s|--codesign) codesign=1; shift ;;
+      --debug) debug=1; shift ;;
+      *) log_error "Unknown option: %B${1}%b"; exit 2 ;;
+    }
+  }
+
+  set -- ${(@)args}
+
+  check_macos
+
+  local product_name
+  local product_version
+  read -r product_name product_version <<< \
+    "$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})"
+
+  pushd ${project_root}
+
+  local -a cmake_args=()
+  local -a cmake_build_args=(--build)
+  local -a cmake_install_args=(--install)
+
+  if (( debug )) cmake_args+=(--debug-output)
+
+  cmake_args+=(--preset 'macos-ci')
+
+  typeset -gx NSUnbufferedIO=YES
+
+  typeset -gx CODESIGN_IDENT="${CODESIGN_IDENT:--}"
+  if (( codesign )) && [[ -z ${CODESIGN_TEAM} ]] {
+    typeset -gx CODESIGN_TEAM="$(print "${CODESIGN_IDENT}" | /usr/bin/sed -En 's/.+\((.+)\)/\1/p')"
+  }
+
+  log_group "Configuring ${product_name}..."
+  cmake -S ${project_root} ${cmake_args}
+
+  log_group "Building ${product_name}..."
+  run_xcodebuild() {
+    if (( debug )) {
+      xcodebuild ${@}
+    } else {
+      if [[ ${GITHUB_EVENT_NAME} == push ]] {
+        xcodebuild ${@} 2>&1 | xcbeautify --renderer terminal
+      } else {
+        xcodebuild ${@} 2>&1 | xcbeautify --renderer github-actions
+      }
+    }
+  }
+
+  local -a build_args=(
+    ONLY_ACTIVE_ARCH=NO
+    -arch arm64
+    -arch x86_64
+    -project ${product_name}.xcodeproj
+    -target ${product_name}
+    -destination "generic/platform=macOS,name=Any Mac"
+    -configuration ${config}
+    -parallelizeTargets
+    -hideShellScriptEnvironment
+    build
+  )
+
+  pushd build_macos
+  run_xcodebuild ${build_args}
+  popd
+
+  log_group "Installing ${product_name}..."
+  cmake --install build_macos --config ${config} --prefix "${project_root}/release/${config}"
+
+  popd
+  log_group
+}
+
+build ${@}

+ 16 - 69
.github/scripts/.build.zsh → .github/scripts/build-ubuntu

@@ -21,14 +21,6 @@ autoload -Uz is-at-least && if ! is-at-least 5.2; then
   exit 1
 fi
 
-TRAPEXIT() {
-  local return_value=$?
-
-  if (( ${+CI} )) unset NSUnbufferedIO
-
-  return ${return_value}
-}
-
 TRAPZERR() {
   if (( ${_loglevel:-3} > 2 )) {
     print -u2 -PR "${CI:+::error::}%F{1}    ✖︎ script execution error%f"
@@ -43,12 +35,12 @@ TRAPZERR() {
 
 build() {
   if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h}
-  local host_os=${${(s:-:)ZSH_ARGZERO:t:r}[2]}
+  local host_os='ubuntu'
   local project_root=${SCRIPT_HOME:A:h:h}
   local buildspec_file=${project_root}/buildspec.json
 
   fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath})
-  autoload -Uz log_group log_info log_error log_output set_loglevel check_${host_os} setup_ccache
+  autoload -Uz log_group log_info log_error log_output set_loglevel check_ubuntu setup_ccache
 
   if [[ ! -r ${buildspec_file} ]] {
     log_error \
@@ -56,35 +48,27 @@ build() {
     return 2
   }
 
+  local -i debug=0
   typeset -g -a skips=()
   local -i verbosity=1
   local -r _version='2.0.0'
   local -r -a _valid_targets=(
-    macos-universal
-    linux-x86_64
-    linux-aarch64
+    ubuntu-x86_64
   )
   local target
   local config='RelWithDebInfo'
   local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel)
   local -i codesign=0
 
-  if [[ ${host_os} == linux ]] {
-    local -r -a _valid_generators=(Ninja 'Unix Makefiles')
-    local generator='Ninja'
-    local -r _usage_host="
+  local -r -a _valid_generators=(Ninja 'Unix Makefiles')
+  local generator='Ninja'
+  local -r _usage_host="
 %F{yellow} Additional options for Linux builds%f
  -----------------------------------------------------------------------------
   %B--generator%b                       Specify build system to generate
                                     Available generators:
                                       - Ninja
                                       - Unix Makefiles"
-  } elif [[ ${host_os} == macos ]] {
-    local -r _usage_host="
-%F{yellow} Additional options for macOS builds%f
- -----------------------------------------------------------------------------
-  %B-s | --codesign%b                   Enable codesigning (macOS only)"
-  }
 
   local -i print_config=0
   local -r _usage="
@@ -152,14 +136,12 @@ ${_usage_host:-}"
       -V|--version) print -Pr "${_version}"; exit 0 ;;
       --debug) verbosity=3; shift ;;
       --generator)
-        if [[ ${host_os} == linux ]] {
-          if (( ! ${_valid_generators[(Ie)${2}]} )) {
-            log_error "Invalid value %B${2}%b for option %B${1}%b"
-            log_output ${_usage}
-            exit 2
-          }
-          generator=${2}
+        if (( ! ${_valid_generators[(Ie)${2}]} )) {
+          log_error "Invalid value %B${2}%b for option %B${1}%b"
+          log_output ${_usage}
+          exit 2
         }
+        generator=${2}
         shift 2
         ;;
       --print-config) print_config=1; skips+=(deps); shift ;;
@@ -181,11 +163,10 @@ ${_usage_host:-}"
 
   if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
     check_${host_os}
-    setup_ccache
   }
 
-  if [[ ${host_os} == linux ]] {
-    autoload -Uz setup_linux && setup_linux
+  if [[ ${host_os} == ubuntu ]] {
+    autoload -Uz setup_ubuntu && setup_ubuntu
   }
 
   local product_name
@@ -210,33 +191,7 @@ ${_usage_host:-}"
 
     local -r _preset="${target%%-*}${CI:+-ci}"
     case ${target} {
-      macos-*)
-        if (( ${+CI} )) typeset -gx NSUnbufferedIO=YES
-
-        cmake_args+=(
-          --preset ${_preset}
-        )
-
-        if (( codesign )) {
-          autoload -Uz read_codesign_team && read_codesign_team
-
-          if [[ -z ${CODESIGN_TEAM} ]] {
-            autoload -Uz read_codesign && read_codesign
-          }
-        }
-
-        cmake_args+=(
-          -DCODESIGN_TEAM=${CODESIGN_TEAM:-}
-          -DCODESIGN_IDENTITY=${CODESIGN_IDENT:--}
-        )
-
-        cmake_build_args+=(--preset ${_preset} --parallel --config ${config} -- ONLY_ACTIVE_ARCH=NO -arch arm64 -arch x86_64)
-        cmake_install_args+=(build_macos --config ${config} --prefix "${project_root}/release/${config}")
-
-        local -a xcbeautify_opts=()
-        if (( _loglevel == 0 )) xcbeautify_opts+=(--quiet)
-        ;;
-      linux-*)
+      ubuntu-*)
         cmake_args+=(
           --preset ${_preset}-${target##*-}
           -G "${generator}"
@@ -272,15 +227,7 @@ ${_usage_host:-}"
     cmake ${cmake_args}
 
     log_group "Building ${product_name}..."
-    if [[ ${host_os} == macos ]] {
-      if (( _loglevel > 1 )) {
-        cmake ${cmake_build_args}
-      } else {
-        cmake ${cmake_build_args} 2>&1 | xcbeautify ${xcbeautify_opts}
-      }
-    } else {
-      cmake ${cmake_build_args}
-    }
+    cmake ${cmake_build_args}
   }
 
   log_group "Installing ${product_name}..."

+ 0 - 1
.github/scripts/package-linux

@@ -1 +0,0 @@
-.package.zsh

+ 0 - 1
.github/scripts/package-macos

@@ -1 +0,0 @@
-.package.zsh

+ 173 - 0
.github/scripts/package-macos

@@ -0,0 +1,173 @@
+#!/usr/bin/env zsh
+
+builtin emulate -L zsh
+setopt EXTENDED_GLOB
+setopt PUSHD_SILENT
+setopt ERR_EXIT
+setopt ERR_RETURN
+setopt NO_UNSET
+setopt PIPE_FAIL
+setopt NO_AUTO_PUSHD
+setopt NO_PUSHD_IGNORE_DUPS
+setopt FUNCTION_ARGZERO
+
+## Enable for script debugging
+# setopt WARN_CREATE_GLOBAL
+# setopt WARN_NESTED_VAR
+# setopt XTRACE
+
+if (( ! ${+CI} )) {
+  print -u2 -PR "%F{1}    ✖︎ ${ZSH_ARGZERO:t:r} requires CI environment%f"
+  exit 1
+}
+
+autoload -Uz is-at-least && if ! is-at-least 5.9; then
+  print -u2 -PR "${CI:+::error::}%F{1}${funcstack[1]##*/}:%f Running on Zsh version %B${ZSH_VERSION}%b, but Zsh %B5.2%b is the minimum supported version. Upgrade Zsh to fix this issue."
+  exit 1
+fi
+
+TRAPZERR() {
+  print -u2 -PR "::error::%F{1}    ✖︎ script execution error%f"
+  print -PR -e "
+  Callstack:
+  ${(j:\n     :)funcfiletrace}
+  "
+
+  exit 2
+}
+
+package() {
+  if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h}
+  local host_os='macos'
+  local project_root=${SCRIPT_HOME:A:h:h}
+  local buildspec_file=${project_root}/buildspec.json
+
+  fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath})
+  autoload -Uz log_group log_error log_output check_macos
+
+  if [[ ! -r ${buildspec_file} ]] {
+    log_error \
+      'No buildspec.json found. Please create a build specification for your project.'
+    return 2
+  }
+
+  local -i debug=0
+
+  local config='RelWithDebInfo'
+  local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel)
+
+  local -i codesign=0
+  local -i notarize=0
+  local -i package=0
+
+  local -a args
+  while (( # )) {
+    case ${1} {
+      -c|--config)
+        if (( # == 1 )) || [[ ${2:0:1} == '-' ]] {
+          log_error "Missing value for option %B${1}%b"
+          exit 2
+        }
+        ;;
+    }
+    case ${1} {
+      --) shift; args+=($@); break ;;
+      -c|--config)
+        if (( !${_valid_configs[(Ie)${2}]} )) {
+          log_error "Invalid value %B${2}%b for option %B${1}%b"
+          exit 2
+        }
+        config=${2}
+        shift 2
+        ;;
+      -s|--codesign) typeset -g codesign=1; shift ;;
+      -n|--notarize) typeset -g notarize=1; typeset -g codesign=1; shift ;;
+      -p|--package) typeset -g package=1; shift ;;
+      --debug) debug=1; shift ;;
+      *) log_error "Unknown option: %B${1}%b"; exit 2 ;;
+    }
+  }
+
+  set -- ${(@)args}
+
+  check_macos
+
+  local product_name
+  local product_version
+  read -r product_name product_version <<< \
+    "$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})"
+
+  local output_name="${product_name}-${product_version}-${host_os}-universal"
+
+  if [[ ! -d ${project_root}/release/${config}/${product_name}.plugin ]] {
+    log_error 'No release artifact found. Run the build script or the CMake install procedure first.'
+    return 2
+  }
+
+  if (( package )) {
+    if [[ ! -f ${project_root}/release/${config}/${product_name}.pkg ]] {
+      log_error 'Installer Package not found. Run the build script or the CMake build and install procedures first.'
+      return 2
+    }
+
+    log_group "Packaging ${product_name}..."
+    pushd ${project_root}
+
+    typeset -gx CODESIGN_IDENT="${CODESIGN_IDENT:--}"
+    typeset -gx CODESIGN_IDENT_INSTALLER="${CODESIGN_IDENT_INSTALLER:--}"
+    typeset -gx CODESIGN_TEAM="$(print "${CODESIGN_IDENT}" | /usr/bin/sed -En 's/.+\((.+)\)/\1/p')"
+
+    if (( codesign )) {
+      productsign \
+        --sign "${CODESIGN_IDENT_INSTALLER}" \
+        ${project_root}/release/${config}/${product_name}.pkg \
+        ${project_root}/release/${output_name}.pkg
+
+      rm ${project_root}/release/${config}/${product_name}.pkg
+    } else {
+      mv ${project_root}/release/${config}/${product_name}.pkg \
+        ${project_root}/release/${output_name}.pkg
+    }
+
+    if (( codesign && notarize )) {
+      if ! [[ ${CODESIGN_IDENT} != '-' && ${CODESIGN_TEAM} && {CODESIGN_IDENT_USER} && ${CODESIGN_IDENT_PASS} ]] {
+        log_error "Notarization requires Apple ID and application password."
+        return 2
+      }
+
+      if [[ ! -f ${project_root}/release/${output_name}.pkg ]] {
+        log_error "No package for notarization found."
+        return 2
+      }
+
+      xcrun notarytool store-credentials "${product_name}-Codesign-Password" --apple-id "${CODESIGN_IDENT_USER}" --team-id "${CODESIGN_TEAM}" --password "${CODESIGN_IDENT_PASS}"
+      xcrun notarytool submit ${project_root}/release/${output_name}.pkg --keychain-profile "${product_name}-Codesign-Password" --wait
+
+      local -i _status=0
+
+      xcrun stapler staple ${project_root}/release/${output_name}.pkg || _status=1
+
+      if (( _status )) {
+        log_error "Notarization failed. Use 'xcrun notarytool log <submission ID>' to check for errors."
+        return 2
+      }
+    }
+    popd
+  } else {
+    log_group "Archiving ${product_name}..."
+    pushd ${project_root}/release/${config}
+    XZ_OPT=-T0 tar -cvJf ${project_root}/release/${output_name}.tar.xz ${product_name}.plugin
+    popd
+  }
+
+  if [[ ${config} == Release ]] {
+    log_group "Archiving ${product_name} Debug Symbols..."
+    pushd ${project_root}/release/${config}
+    XZ_OPT=-T0 tar -cvJf ${project_root}/release/${output_name}-dSYMs.tar.xz ${product_name}.plugin.dSYM
+    popd
+  }
+
+  log_group
+}
+
+package ${@}

+ 21 - 114
.github/scripts/.package.zsh → .github/scripts/package-ubuntu

@@ -21,16 +21,6 @@ autoload -Uz is-at-least && if ! is-at-least 5.2; then
   exit 1
 fi
 
-TRAPEXIT() {
-  local return_value=$?
-
-  if (( ${+CI} )) {
-    unset NSUnbufferedIO
-  }
-
-  return ${return_value}
-}
-
 TRAPZERR() {
   if (( ${_loglevel:-3} > 2 )) {
     print -u2 -PR "${CI:+::error::}%F{1}    ✖︎ script execution error%f"
@@ -45,7 +35,7 @@ TRAPZERR() {
 
 package() {
   if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h}
-  local host_os=${${(s:-:)ZSH_ARGZERO:t:r}[2]}
+  local host_os='ubuntu'
   local project_root=${SCRIPT_HOME:A:h:h}
   local buildspec_file=${project_root}/buildspec.json
 
@@ -58,11 +48,11 @@ package() {
     return 2
   }
 
+  local -i debug=0
   local -i verbosity=1
   local -r _version='2.0.0'
   local -r -a _valid_targets=(
-    macos-universal
-    linux-x86_64
+    ubuntu-x86_64
   )
   local target
   local config='RelWithDebInfo'
@@ -72,15 +62,6 @@ package() {
   local -i package=0
   local -i skip_deps=0
 
-  if [[ ${host_os} == macos ]] {
-    local -r _usage_host="
-%F{yellow} Additional options for macOS builds%f
- -----------------------------------------------------------------------------
-  %B-s | --codesign%b                   Enable codesigning (macOS only)
-  %B-n | --notarize%b                   Enable notarization (macOS only)
-  %B-p | --package%b                    Create package installer (macOS only)"
-  }
-
   local -r _usage="
 Usage: %B${functrace[1]%:*}%b <option> [<options>]
 
@@ -166,104 +147,30 @@ ${_usage_host:-}"
   read -r product_name product_version <<< \
     "$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})"
 
-  if [[ ${host_os} == macos ]] {
-    autoload -Uz check_packages read_codesign read_codesign_installer read_codesign_pass
-
-    local output_name="${product_name}-${product_version}-${host_os}-universal"
-
-    if [[ ! -d ${project_root}/release/${config}/${product_name}.plugin ]] {
-      log_error 'No release artifact found. Run the build script or the CMake install procedure first.'
-      return 2
-    }
-
-    local _tarflags='cJf'
-    if (( _loglevel > 1  || ${+CI} )) _tarflags="v${_tarflags}"
-
-    if (( package )) {
-      if [[ ! -f ${project_root}/release/${config}/${product_name}.pkg ]] {
-        log_error 'Installer Package not found. Run the build script or the CMake build and install procedures first.'
-        return 2
-      }
-
-      log_group "Packaging ${product_name}..."
-      pushd ${project_root}
-
-      if (( codesign )) {
-        read_codesign_installer
-        productsign \
-          --sign "${CODESIGN_IDENT_INSTALLER}" \
-          ${project_root}/release/${config}/${product_name}.pkg \
-          ${project_root}/release/${output_name}.pkg
+  local -a cmake_args=()
+  if (( _loglevel > 1 )) cmake_args+=(--verbose)
 
-        rm ${project_root}/release/${config}/${product_name}.pkg
-      } else {
-        mv ${project_root}/release/${config}/${product_name}.pkg \
-          ${project_root}/release/${output_name}.pkg
-      }
+  log_group "Creating source tarball for ${product_name}..."
+  pushd ${project_root}
+  cmake --build build_${target##*-} --config ${config} -t package_source ${cmake_args}
+  popd
 
-      if (( codesign && notarize )) {
-        if [[ ! -f ${project_root}/release/${output_name}.pkg ]] {
-          log_error "No package for notarization found."
-          return 2
-        }
-
-        read_codesign_installer
-        read_codesign_pass
-
-        xcrun notarytool submit ${project_root}/release/${output_name}.pkg \
-          --keychain-profile "OBS-Codesign-Password" --wait
-
-        local -i _status=0
-
-        xcrun stapler staple ${project_root}/release/${output_name}.pkg || _status=1
-
-        if (( _status )) {
-          log_error "Notarization failed. Use 'xcrun notarytool log <submission ID>' to check for errors."
-          return 2
-        }
-      }
-      popd
-    } else {
-      log_group "Archiving ${product_name}..."
-      pushd ${project_root}/release/${config}
-      XZ_OPT=-T0 tar "-${_tarflags}" ${project_root}/release/${output_name}.tar.xz ${product_name}.plugin
-      popd
-    }
-
-    if [[ ${config} == Release ]] {
-      log_group "Archiving ${product_name} Debug Symbols..."
-      pushd ${project_root}/release/${config}
-      XZ_OPT=-T0 tar "-${_tarflags}" ${project_root}/release/${output_name}-dSYMs.tar.xz ${product_name}.plugin.dSYM
-      popd
-    }
-
-    log_group
-  } elif [[ ${host_os} == linux ]] {
-    local -a cmake_args=()
-    if (( _loglevel > 1 )) cmake_args+=(--verbose)
-
-    log_group "Creating source tarball for ${product_name}..."
+  if (( package )) {
+    log_group "Packaging ${product_name}..."
     pushd ${project_root}
-    cmake --build build_${target##*-} --config ${config} -t package_source ${cmake_args}
+    cmake --build build_${target##*-} --config ${config} -t package ${cmake_args}
     popd
+  } else {
+    log_group "Archiving ${product_name}..."
+    local output_name="${product_name}-${product_version}-${target##*-}-ubuntu-gnu"
+    local _tarflags='cJf'
+    if (( _loglevel > 1 || ${+CI} )) _tarflags="v${_tarflags}"
 
-    if (( package )) {
-      log_group "Packaging ${product_name}..."
-      pushd ${project_root}
-      cmake --build build_${target##*-} --config ${config} -t package ${cmake_args}
-      popd
-    } else {
-      log_group "Archiving ${product_name}..."
-      local output_name="${product_name}-${product_version}-${target##*-}-linux-gnu"
-      local _tarflags='cJf'
-      if (( _loglevel > 1 || ${+CI} )) _tarflags="v${_tarflags}"
-
-      pushd ${project_root}/release/${config}
-      XZ_OPT=-T0 tar "-${_tarflags}" ${project_root}/release/${output_name}.tar.xz (lib|share)
-      popd
-    }
-    log_group
+    pushd ${project_root}/release/${config}
+    XZ_OPT=-T0 tar "-${_tarflags}" ${project_root}/release/${output_name}.tar.xz (lib|share)
+    popd
   }
+  log_group
 }
 
 package ${@}

+ 0 - 70
.github/scripts/utils.pwsh/Expand-ArchiveExt.ps1

@@ -1,70 +0,0 @@
-function Expand-ArchiveExt {
-    <#
-        .SYNOPSIS
-            Expands archive files.
-        .DESCRIPTION
-            Allows extraction of zip, 7z, gz, and xz archives.
-            Requires tar and 7-zip to be available on the system.
-            Archives ending with .zip but created using LZMA compression are
-            expanded using 7-zip as a fallback.
-        .EXAMPLE
-            Expand-ArchiveExt -Path <Path-To-Your-Archive>
-            Expand-ArchiveExt -Path <Path-To-Your-Archive> -DestinationPath <Expansion-Path>
-    #>
-
-    param(
-        [Parameter(Mandatory)]
-        [string] $Path,
-        [string] $DestinationPath = [System.IO.Path]::GetFileNameWithoutExtension($Path),
-        [switch] $Force
-    )
-
-    switch ( [System.IO.Path]::GetExtension($Path) ) {
-        .zip {
-            try {
-                Expand-Archive -Path $Path -DestinationPath $DestinationPath -Force:$Force
-            } catch {
-                if ( Get-Command 7z ) {
-                    Invoke-External 7z x -y $Path "-o${DestinationPath}"
-                } else {
-                    throw "Fallback utility 7-zip not found. Please install 7-zip first."
-                }
-            }
-            break
-        }
-        { ( $_ -eq ".7z" ) -or ( $_ -eq ".exe" ) } {
-            if ( Get-Command 7z ) {
-                Invoke-External 7z x -y $Path "-o${DestinationPath}"
-            } else {
-                throw "Extraction utility 7-zip not found. Please install 7-zip first."
-            }
-            break
-        }
-        .gz {
-            try {
-                Invoke-External tar -x -o $DestinationPath -f $Path
-            } catch {
-                if ( Get-Command 7z ) {
-                    Invoke-External 7z x -y $Path "-o${DestinationPath}"
-                } else {
-                    throw "Fallback utility 7-zip not found. Please install 7-zip first."
-                }
-            }
-            break
-        }
-        .xz {
-            try {
-                Invoke-External tar -x -o $DestinationPath -f $Path
-            } catch {
-                if ( Get-Command 7z ) {
-                    Invoke-External 7z x -y $Path "-o${DestinationPath}"
-                } else {
-                    throw "Fallback utility 7-zip not found. Please install 7-zip first."
-                }
-            }
-        }
-        default {
-            throw "Unsupported archive extension provided."
-        }
-    }
-}

+ 0 - 70
.github/scripts/utils.pwsh/Install-BuildDependencies.ps1

@@ -1,70 +0,0 @@
-function Install-BuildDependencies {
-    <#
-        .SYNOPSIS
-            Installs required build dependencies.
-        .DESCRIPTION
-            Additional packages might be needed for successful builds. This module contains additional
-            dependencies available for installation via winget and, if possible, adds their locations
-            to the environment path for future invocation.
-        .EXAMPLE
-            Install-BuildDependencies
-    #>
-
-    param(
-        [string] $WingetFile = "$PSScriptRoot/.Wingetfile"
-    )
-
-    if ( ! ( Test-Path function:Log-Warning ) ) {
-        . $PSScriptRoot/Logger.ps1
-    }
-
-    $Prefixes = @{
-        'x64' = ${Env:ProgramFiles}
-        'x86' = ${Env:ProgramFiles(x86)}
-        'arm64' = ${Env:ProgramFiles(arm)}
-    }
-
-    $Paths = $Env:Path -split [System.IO.Path]::PathSeparator
-
-    $WingetOptions = @('install', '--accept-package-agreements', '--accept-source-agreements')
-
-    if ( $script:Quiet ) {
-        $WingetOptions += '--silent'
-    }
-
-    Log-Group 'Check Windows build requirements'
-    Get-Content $WingetFile | ForEach-Object {
-        $_, $Package, $_, $Path, $_, $Binary, $_, $Version = $_ -replace ',','' -split " +(?=(?:[^\']*\'[^\']*\')*[^\']*$)" -replace "'",''
-
-        $Prefixes.GetEnumerator() | ForEach-Object {
-            $Prefix = $_.value
-            $FullPath = "${Prefix}\${Path}"
-            if ( ( Test-Path $FullPath  ) -and ! ( $Paths -contains $FullPath ) ) {
-                $Paths = @($FullPath) + $Paths
-                $Env:Path = $Paths -join [System.IO.Path]::PathSeparator
-            }
-        }
-
-        Log-Debug "Checking for command ${Binary}"
-        $Found = Get-Command -ErrorAction SilentlyContinue $Binary
-
-        if ( $Found ) {
-            Log-Status "Found dependency ${Binary} as $($Found.Source)"
-        } else {
-            Log-Status "Installing package ${Package} $(if ( $Version -ne $null ) { "Version: ${Version}" } )"
-
-            if ( $Version -ne $null ) {
-                $WingetOptions += @('--version', ${Version})
-            }
-
-            try {
-                $Params = $WingetOptions + $Package
-
-                winget @Params
-            } catch {
-                throw "Error while installing winget package ${Package}: $_"
-            }
-        }
-    }
-    Log-Group
-}

+ 1 - 1
.github/scripts/utils.zsh/check_macos

@@ -1,4 +1,4 @@
-autoload -Uz is-at-least log_group log_info log_error log_status read_codesign
+autoload -Uz is-at-least log_group log_info log_error log_status
 
 local macos_version=$(sw_vers -productVersion)
 

+ 0 - 0
.github/scripts/utils.zsh/check_linux → .github/scripts/utils.zsh/check_ubuntu


+ 1 - 3
.github/scripts/utils.zsh/log_debug

@@ -1,3 +1 @@
-if (( ! ${+_loglevel} )) typeset -g _loglevel=1
-
-if (( _loglevel > 2 )) print -PR -e -- "${CI:+::debug::}%F{220}DEBUG: ${@}%f"
+if (( debug )) print -PR -e "::debug::%F{220}DEBUG: ${@}%f"

+ 1 - 3
.github/scripts/utils.zsh/log_error

@@ -1,3 +1 @@
-local icon='  ✖︎ '
-
-print -u2 -PR "${CI:+::error::}%F{1} ${icon} %f ${@}"
+print -u2 -PR "::error::%F{1}    ✖︎%f  ${@}"

+ 7 - 11
.github/scripts/utils.zsh/log_group

@@ -2,15 +2,11 @@ autoload -Uz log_info
 
 if (( ! ${+_log_group} )) typeset -g _log_group=0
 
-if (( ${+CI} )) {
-  if (( _log_group )) {
-    print "::endgroup::"
-    typeset -g _log_group=0
-  }
-  if (( # )) {
-    print "::group::${@}"
-    typeset -g _log_group=1
-  }
-} else {
-  if (( # )) log_info ${@}
+if (( _log_group )) {
+  print "::endgroup::"
+  typeset -g _log_group=0
+}
+if (( # )) {
+  print "::group::${@}"
+  typeset -g _log_group=1
 }

+ 1 - 7
.github/scripts/utils.zsh/log_info

@@ -1,7 +1 @@
-if (( ! ${+_loglevel} )) typeset -g _loglevel=1
-
-if (( _loglevel > 0 )) {
-  local icon=' =>'
-
-  print -PR "%F{4}  ${(r:5:)icon}%f %B${@}%b"
-}
+print -PR "%F{4}   =>%f  %B${@}%b"

+ 1 - 7
.github/scripts/utils.zsh/log_output

@@ -1,7 +1 @@
-if (( ! ${+_loglevel} )) typeset -g _loglevel=1
-
-if (( _loglevel > 0 )) {
-  local icon=''
-
-  print -PR "  ${(r:5:)icon} ${@}"
-}
+print -PR "        ${@}"

+ 1 - 7
.github/scripts/utils.zsh/log_status

@@ -1,7 +1 @@
-if (( ! ${+_loglevel} )) typeset -g _loglevel=1
-
-if (( _loglevel > 0 )) {
-  local icon='  >'
-
-  print -PR "%F{2}  ${(r:5:)icon}%f ${@}"
-}
+print -PR "%F{2}    >%f  ${@}"

+ 1 - 5
.github/scripts/utils.zsh/log_warning

@@ -1,5 +1 @@
-if (( _loglevel > 0 )) {
-  local icon=' =>'
-
-  print -PR "${CI:+::warning::}%F{3}  ${(r:5:)icon} ${@}%f"
-}
+print -PR "::warning::%F{3}   =>  ${@}%f"

+ 0 - 9
.github/scripts/utils.zsh/read_codesign

@@ -1,9 +0,0 @@
-autoload -Uz log_info
-
-if (( ! ${+CODESIGN_IDENT} )) {
-  typeset -g CODESIGN_IDENT
-  log_info 'Setting up Apple Developer ID for application codesigning...'
-  read CODESIGN_IDENT'?Apple Developer Application ID: '
-}
-
-typeset -g CODESIGN_TEAM=$(print "${CODESIGN_IDENT}" | /usr/bin/sed -En 's/.+\((.+)\)/\1/p')

+ 0 - 7
.github/scripts/utils.zsh/read_codesign_installer

@@ -1,7 +0,0 @@
-autoload -Uz log_info
-
-if (( ! ${+CODESIGN_IDENT_INSTALLER} )) {
-  typeset -g CODESIGN_IDENT_INSTALLER
-  log_info 'Setting up Apple Developer Installer ID for installer package codesigning...'
-  read CODESIGN_IDENT_INSTALLER'?Apple Developer Installer ID: '
-}

+ 0 - 38
.github/scripts/utils.zsh/read_codesign_pass

@@ -1,38 +0,0 @@
-##############################################################################
-# Apple Developer credentials necessary:
-#
-#   + Signing for distribution and notarization require an active Apple
-#     Developer membership
-#   + An Apple Development identity is needed for code signing
-#     (i.e. 'Apple Development: YOUR APPLE ID (PROVIDER)')
-#   + Your Apple developer ID is needed for notarization
-#   + An app-specific password is necessary for notarization from CLI
-#   + This password will be stored in your macOS keychain under the identifier
-#     'OBS-Codesign-Password'with access Apple's 'altool' only.
-##############################################################################
-
-autoload -Uz read_codesign read_codesign_user log_info log_warning
-
-if (( ! ${+CODESIGN_IDENT} )) {
-  read_codesign
-}
-
-if (( ! ${+CODESIGN_IDENT_USER} )) {
-  read_codesign_user
-}
-
-log_info 'Setting up password for notarization keychain...'
-if (( ! ${+CODESIGN_IDENT_PASS} )) {
-  read -s CODESIGN_IDENT_PASS'?Apple Developer ID password: '
-}
-
-print ''
-log_info 'Setting up notarization keychain...'
-log_warning "
- + Your Apple ID and an app-specific password is necessary for notarization from CLI
- + This password will be stored in your macOS keychain under the identifier
-   'OBS-Codesign-Password' with access Apple's 'altool' only.
-
-"
-xcrun notarytool store-credentials 'OBS-Codesign-Password' --apple-id "${CODESIGN_IDENT_USER}" --team-id "${CODESIGN_TEAM}" --password "${CODESIGN_IDENT_PASS}"
-

+ 0 - 7
.github/scripts/utils.zsh/read_codesign_team

@@ -1,7 +0,0 @@
-autoload -Uz log_info
-
-if (( ! ${+CODESIGN_TEAM} )) {
-  typeset -g CODESIGN_TEAM
-  log_info 'Setting up Apple Developer Team ID for codesigning...'
-  read CODESIGN_TEAM'?Apple Developer Team ID (leave empty to use Apple Developer ID instead): '
-}

+ 0 - 7
.github/scripts/utils.zsh/read_codesign_user

@@ -1,7 +0,0 @@
-autoload -Uz log_info
-
-if (( ! ${+CODESIGN_IDENT_USER} )) {
-  typeset -g CODESIGN_IDENT_USER
-  log_info 'Setting up Apple ID for notarization...'
-  read CODESIGN_IDENT_USER'?Apple ID: '
-}

+ 0 - 42
.github/scripts/utils.zsh/setup_ccache

@@ -1,42 +0,0 @@
-autoload -Uz log_debug log_warning
-
-if (( ! ${+project_root} )) {
-  log_error "'project_root' not set. Please set before running ${0}."
-  return 2
-}
-
-if (( ${+commands[ccache]} )) {
-  log_debug "Found ccache at ${commands[ccache]}"
-
-  typeset -gx CCACHE_CONFIGPATH="${project_root}/.ccache.conf"
-
-  ccache --set-config=run_second_cpp=true
-  ccache --set-config=direct_mode=true
-  ccache --set-config=inode_cache=true
-  ccache --set-config=compiler_check=content
-  ccache --set-config=file_clone=true
-
-  local -a sloppiness=(
-    include_file_mtime
-    include_file_ctime
-    file_stat_matches
-    system_headers
-  )
-
-  if [[ ${host_os} == macos ]] {
-    sloppiness+=(
-      modules
-      clang_index_store
-    )
-
-    ccache --set-config=sloppiness=${(j:,:)sloppiness}
-  }
-
-  if (( ${+CI} )) {
-    ccache --set-config=cache_dir="${GITHUB_WORKSPACE:-${HOME}}/.ccache"
-    ccache --set-config=max_size="${CCACHE_SIZE:-1G}"
-    ccache -z > /dev/null
-  }
-} else {
-  log_warning "No ccache found on the system"
-}

+ 0 - 0
.github/scripts/utils.zsh/setup_linux → .github/scripts/utils.zsh/setup_ubuntu


+ 35 - 19
.github/workflows/build-project.yaml

@@ -3,12 +3,12 @@ on:
   workflow_call:
     outputs:
       pluginName:
-        description: 'Project name detected by parsing build spec file'
+        description: Project name detected by parsing build spec file
         value: ${{ jobs.check-event.outputs.pluginName }}
 jobs:
   check-event:
     name: Check GitHub Event Data 🔎
-    runs-on: ubuntu-22.04
+    runs-on: ubuntu-24.04
     defaults:
       run:
         shell: bash
@@ -73,7 +73,7 @@ jobs:
 
   macos-build:
     name: Build for macOS 🍏
-    runs-on: macos-14
+    runs-on: macos-15
     needs: check-event
     defaults:
       run:
@@ -90,14 +90,18 @@ jobs:
           : Set Up Environment 🔧
           if (( ${+RUNNER_DEBUG} )) setopt XTRACE
 
-          print '::group::Enable Xcode 15.2'
-          sudo xcode-select --switch /Applications/Xcode_15.2.app/Contents/Developer
+          print '::group::Enable Xcode 16.1'
+          sudo xcode-select --switch /Applications/Xcode_16.1.0.app/Contents/Developer
           print '::endgroup::'
 
           print '::group::Clean Homebrew Environment'
-          local -a to_remove=()
+          local -a unwanted_formulas=()
+          local -a remove_formulas=()
+          for formula (${unwanted_formulas}) {
+            if [[ -d ${HOMEBREW_PREFIX}/Cellar/${formula} ]] remove_formulas+=(${formula})
+          }
 
-          if (( #to_remove )) brew uninstall --ignore-dependencies ${to_remove}
+          if (( #remove_formulas )) brew uninstall --ignore-dependencies ${remove_formulas}
           print '::endgroup::'
 
           local product_name
@@ -108,7 +112,7 @@ jobs:
           print "pluginName=${product_name}" >> $GITHUB_OUTPUT
           print "pluginVersion=${product_version}" >> $GITHUB_OUTPUT
 
-      - uses: actions/cache@v4
+      - uses: actions/cache/restore@v4
         id: ccache-cache
         with:
           path: ${{ github.workspace }}/.ccache
@@ -165,9 +169,18 @@ jobs:
           name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-${{ needs.check-event.outputs.commitHash }}-dSYMs
           path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-macos-universal-dSYMs.*
 
+      - uses: actions/cache/save@v4
+        if: github.event_name != 'pull_request' && steps.ccache-cache.outputs.cache-hit != 'true'
+        with:
+          path: ${{ github.workspace }}/.ccache
+          key: ${{ runner.os }}-ccache-${{ needs.check-event.outputs.config }}
+
   ubuntu-build:
     name: Build for Ubuntu 🐧
-    runs-on: ubuntu-22.04
+    strategy:
+      matrix:
+        os: [ubuntu-24.04]
+    runs-on: ${{ matrix.os }}
     needs: check-event
     defaults:
       run:
@@ -190,16 +203,13 @@ jobs:
           echo "pluginName=${product_name}" >> $GITHUB_OUTPUT
           echo "pluginVersion=${product_version}" >> $GITHUB_OUTPUT
 
-      - uses: actions/cache@v4
+      - uses: actions/cache/restore@v4
         id: ccache-cache
         with:
           path: ${{ github.workspace }}/.ccache
-          key: ${{ runner.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
+          key: ${{ runner.os }}-${{ matrix.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
           restore-keys: |
-            ${{ runner.os }}-ccache-x86_64-
-
-      - name: Set up Homebrew 🍺
-        uses: Homebrew/actions/setup-homebrew@master
+            ${{ runner.os }}-${{ matrix.os }}-ccache-x86_64-
 
       - name: Build Plugin 🧱
         uses: ./.github/actions/build-plugin
@@ -210,29 +220,35 @@ jobs:
       - name: Package Plugin 📀
         uses: ./.github/actions/package-plugin
         with:
-          package: ${{ fromJSON(needs.check-event.outputs.package) }}
           target: x86_64
           config: ${{ needs.check-event.outputs.config }}
+          package: ${{ fromJSON(needs.check-event.outputs.package) }}
 
       - name: Upload Source Tarball 🗜️
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-sources-${{ needs.check-event.outputs.commitHash }}
+          name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-${{ matrix.os }}-sources-${{ needs.check-event.outputs.commitHash }}
           path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-source.*
 
       - name: Upload Artifacts 📡
         uses: actions/upload-artifact@v4
         with:
-          name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}
+          name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-${{ matrix.os }}-x86_64-${{ needs.check-event.outputs.commitHash }}
           path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-x86_64*.*
 
       - name: Upload debug symbol artifacts 🪲
         uses: actions/upload-artifact@v4
         if: ${{ fromJSON(needs.check-event.outputs.package) }}
         with:
-          name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
+          name: ${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-${{ matrix.os }}-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
           path: ${{ github.workspace }}/release/${{ steps.setup.outputs.pluginName }}-${{ steps.setup.outputs.pluginVersion }}-x86_64*-dbgsym.ddeb
 
+      - uses: actions/cache/save@v4
+        if: github.event_name != 'pull_request' && steps.ccache-cache.outputs.cache-hit != 'true'
+        with:
+          path: ${{ github.workspace }}/.ccache
+          key: ${{ runner.os }}-${{ matrix.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
+
   windows-build:
     name: Build for Windows 🪟
     runs-on: windows-2022

+ 6 - 6
.github/workflows/check-format.yaml

@@ -3,7 +3,7 @@ on:
   workflow_call:
 jobs:
   clang-format:
-    runs-on: ubuntu-22.04
+    runs-on: ubuntu-24.04
     steps:
       - uses: actions/checkout@v4
         with:
@@ -14,14 +14,14 @@ jobs:
         with:
           failCondition: error
 
-  cmake-format:
-    runs-on: ubuntu-22.04
+  gersemi:
+    runs-on: ubuntu-24.04
     steps:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
-      - name: cmake-format check 🎛️
-        id: cmake-format
-        uses: ./.github/actions/run-cmake-format
+      - name: gersemi Check 🎛️
+        id: gersemi
+        uses: ./.github/actions/run-gersemi
         with:
           failCondition: error

+ 3 - 6
.github/workflows/push.yaml

@@ -1,4 +1,4 @@
-name: Push to master
+name: Push
 run-name: ${{ github.ref_name }} push run 🚀
 on:
   push:
@@ -10,13 +10,10 @@ on:
       - '*'
 permissions:
   contents: write
-concurrency:
-  group: '${{ github.workflow }} @ ${{ github.ref }}'
-  cancel-in-progress: ${{ github.ref_type == 'tag' }}
 jobs:
   check-format:
     name: Check Formatting 🔍
-    if: github.ref_name == 'master'
+    if: github.ref_name == 'master' || github.ref_name == 'main'
     uses: ./.github/workflows/check-format.yaml
     permissions:
       contents: read
@@ -31,7 +28,7 @@ jobs:
   create-release:
     name: Create Release 🛫
     if: github.ref_type == 'tag'
-    runs-on: ubuntu-22.04
+    runs-on: ubuntu-24.04
     needs: build-project
     defaults:
       run: