Browse Source

CI: Update GitHub Actions workflows and repository actions

PatTheMav 10 tháng trước cách đây
mục cha
commit
153962b3a2
36 tập tin đã thay đổi với 508 bổ sung642 xóa
  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: