Эх сурвалжийг харах

CI: Update zsh build framework and zsh build scripts

PatTheMav 2 жил өмнө
parent
commit
2e67148615
41 өөрчлөгдсөн 724 нэмэгдсэн , 667 устгасан
  1. 0 3
      .github/scripts/.Aptfile
  2. 1 1
      .github/scripts/.Brewfile
  3. 146 119
      .github/scripts/.build.zsh
  4. 148 67
      .github/scripts/.package.zsh
  5. 0 0
      .github/scripts/build-linux
  6. 0 13
      .github/scripts/build-linux.sh
  7. 0 0
      .github/scripts/build-macos
  8. 0 11
      .github/scripts/check-changes.sh
  9. 0 53
      .github/scripts/check-cmake.sh
  10. 0 60
      .github/scripts/check-format.sh
  11. 0 0
      .github/scripts/package-linux
  12. 0 13
      .github/scripts/package-linux.sh
  13. 0 0
      .github/scripts/package-macos
  14. 29 6
      .github/scripts/utils.zsh/check_linux
  15. 5 3
      .github/scripts/utils.zsh/check_macos
  16. 36 26
      .github/scripts/utils.zsh/check_packages
  17. 1 1
      .github/scripts/utils.zsh/log_debug
  18. 1 1
      .github/scripts/utils.zsh/log_error
  19. 16 0
      .github/scripts/utils.zsh/log_group
  20. 1 1
      .github/scripts/utils.zsh/log_warning
  21. 3 1
      .github/scripts/utils.zsh/read_codesign
  22. 1 1
      .github/scripts/utils.zsh/read_codesign_installer
  23. 8 3
      .github/scripts/utils.zsh/read_codesign_pass
  24. 7 0
      .github/scripts/utils.zsh/read_codesign_team
  25. 2 2
      .github/scripts/utils.zsh/read_codesign_user
  26. 30 2
      .github/scripts/utils.zsh/setup_ccache
  27. 31 31
      .github/scripts/utils.zsh/setup_linux
  28. 0 127
      .github/scripts/utils.zsh/setup_macos
  29. 0 122
      .github/scripts/utils.zsh/setup_obs
  30. 3 0
      build-aux/.functions/log_debug
  31. 3 0
      build-aux/.functions/log_error
  32. 16 0
      build-aux/.functions/log_group
  33. 7 0
      build-aux/.functions/log_info
  34. 7 0
      build-aux/.functions/log_output
  35. 7 0
      build-aux/.functions/log_status
  36. 7 0
      build-aux/.functions/log_warning
  37. 17 0
      build-aux/.functions/set_loglevel
  38. 188 0
      build-aux/.run-format.zsh
  39. 1 0
      build-aux/run-clang-format
  40. 1 0
      build-aux/run-cmake-format
  41. 1 0
      build-aux/run-swift-format

+ 0 - 3
.github/scripts/.Aptfile

@@ -1,9 +1,6 @@
 package 'cmake'
 package 'ccache'
-package 'curl'
 package 'git'
 package 'jq'
 package 'ninja-build', bin: 'ninja'
 package 'pkg-config'
-package 'clang'
-package 'clang-format-13'

+ 1 - 1
.github/scripts/.Brewfile

@@ -3,4 +3,4 @@ brew "coreutils"
 brew "cmake"
 brew "git"
 brew "jq"
-brew "ninja"
+brew "xcbeautify"

+ 146 - 119
.github/scripts/.build.zsh

@@ -17,56 +17,76 @@ setopt FUNCTION_ARGZERO
 # setopt XTRACE
 
 autoload -Uz is-at-least && if ! is-at-least 5.2; then
-  print -u2 -PR "%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."
+  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
 
-_trap_error() {
-  print -u2 -PR '%F{1}    ✖︎ script execution error%f'
-  print -PR -e "
+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"
+    print -PR -e "
     Callstack:
     ${(j:\n     :)funcfiletrace}
-  "
+    "
+  }
+
   exit 2
 }
 
 build() {
   if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h}
   local host_os=${${(s:-:)ZSH_ARGZERO:t:r}[2]}
-  local target="${host_os}-${CPUTYPE}"
   local project_root=${SCRIPT_HOME:A:h:h}
-  local buildspec_file="${project_root}/buildspec.json"
-
-  trap '_trap_error' ZERR
+  local buildspec_file=${project_root}/buildspec.json
 
   fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath})
-  autoload -Uz log_info log_error log_output set_loglevel check_${host_os} setup_${host_os} setup_obs setup_ccache
+  autoload -Uz log_group log_info log_error log_output set_loglevel check_${host_os} setup_ccache
 
   if [[ ! -r ${buildspec_file} ]] {
     log_error \
-      'No buildspec.json found. Please create a build specification for your project.' \
-      'A buildspec.json.template file is provided in the repository to get you started.'
+      'No buildspec.json found. Please create a build specification for your project.'
     return 2
   }
 
   typeset -g -a skips=()
-  local -i _verbosity=1
-  local -r _version='1.0.0'
+  local -i verbosity=1
+  local -r _version='2.0.0'
   local -r -a _valid_targets=(
-    macos-x86_64
-    macos-arm64
     macos-universal
     linux-x86_64
     linux-aarch64
   )
+  local target
+  local config='RelWithDebInfo'
   local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel)
-  if [[ ${host_os} == 'macos' ]] {
-    local -r -a _valid_generators=(Xcode Ninja 'Unix Makefiles')
-    local generator="${${CI:+Ninja}:-Xcode}"
-  } else {
+  local -i codesign=0
+
+  if [[ ${host_os} == linux ]] {
     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="
 Usage: %B${functrace[1]%:*}%b <option> [<options>]
 
@@ -74,26 +94,21 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
 
 %F{yellow} Build configuration options%f
  -----------------------------------------------------------------------------
-  %B-t | --target%b                     Specify target - default: %B%F{green}${host_os}-${CPUTYPE}%f%b
-  %B-c | --config%b                     Build configuration - default: %B%F{green}RelWithDebInfo%f%b
-  %B-s | --codesign%b                   Enable codesigning (macOS only)
-  %B--generator%b                       Specify build system to generate - default: %B%F{green}Ninja%f%b
-                                    Available generators:
-                                      - Ninja
-                                      - Unix Makefiles
-                                      - Xcode (macOS only)
+  %B-t | --target%b                     Specify target
+  %B-c | --config%b                     Build configuration
+  %B--skip-[all|build|deps]%b           Skip all|building|checking for dependencies
 
 %F{yellow} Output options%f
  -----------------------------------------------------------------------------
   %B-q | --quiet%b                      Quiet (error output only)
   %B-v | --verbose%b                    Verbose (more detailed output)
-  %B--skip-[all|build|deps|unpack]%b    Skip all|building OBS|checking for dependencies|unpacking dependencies
   %B--debug%b                           Debug (very detailed and added output)
 
 %F{yellow} General options%f
  -----------------------------------------------------------------------------
   %B-h | --help%b                       Print this usage help
-  %B-V | --version%b                    Print script version information"
+  %B-V | --version%b                    Print script version information
+${_usage_host:-}"
 
   local -a args
   while (( # )) {
@@ -127,27 +142,30 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
           log_output ${_usage}
           exit 2
         }
-        BUILD_CONFIG=${2}
+        config=${2}
         shift 2
         ;;
-      -s|--codesign) CODESIGN=1; shift ;;
-      -q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
-      -v|--verbose) (( _verbosity += 1 )); shift ;;
+      -s|--codesign) codesign=1; shift ;;
+      -q|--quiet) (( verbosity -= 1 )) || true; shift ;;
+      -v|--verbose) (( verbosity += 1 )); shift ;;
       -h|--help) log_output ${_usage}; exit 0 ;;
       -V|--version) print -Pr "${_version}"; exit 0 ;;
-      --debug) _verbosity=3; shift ;;
+      --debug) verbosity=3; shift ;;
       --generator)
-        if (( ! ${_valid_generators[(Ie)${2}]} )) {
-          log_error "Invalid value %B${2}%b for option %B${1}%b"
-          log_output ${_usage}
-          exit 2
+        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}
         }
-        generator=${2}
         shift 2
         ;;
+      --print-config) print_config=1; skips+=(deps); shift ;;
       --skip-*)
-        local _skip="${${(s:-:)1}[-1]}"
-        local _check=(all deps unpack build)
+        local -r _skip="${${(s:-:)1}[-1]}"
+        local -r -a _check=(all build deps)
         (( ${_check[(Ie)${_skip}]} )) || log_warning "Invalid skip mode %B${_skip}%b supplied"
         typeset -g -a skips=(${skips} ${_skip})
         shift
@@ -156,110 +174,119 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
     }
   }
 
+  : "${target:="${host_os}-${CPUTYPE}"}"
+
   set -- ${(@)args}
-  set_loglevel ${_verbosity}
+  set_loglevel ${verbosity}
 
-  check_${host_os}
-  setup_ccache
+  if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
+    check_${host_os}
+    setup_ccache
+  }
 
-  typeset -g QT_VERSION
-  typeset -g DEPLOYMENT_TARGET
-  typeset -g OBS_DEPS_VERSION
-  setup_${host_os}
+  if [[ ${host_os} == linux ]] {
+    autoload -Uz setup_linux && setup_linux
+  }
 
   local product_name
   local product_version
-  local product_author
-  local product_email
-
-  IFS=/ read -r product_name product_version product_author product_email <<< \
-    "$(jq -r '. | {name, version, author, email} | join("/")' ${buildspec_file})"
-
-  product_author=\"${product_author}\"
-  product_email=\"${product_email}\"
-
-  case ${host_os} {
-    macos)
-      sed -i '' \
-        "s/project(\(.*\) VERSION \(.*\))/project(${product_name} VERSION ${product_version})/" \
-        "${project_root}/CMakeLists.txt"
-      sed -i '' \
-        "s/set(PLUGIN_AUTHOR \(.*\))/set(PLUGIN_AUTHOR ${product_author})/"\
-        "${project_root}/CMakeLists.txt"
-      sed -i '' \
-        "s/set(LINUX_MAINTAINER_EMAIL \(.*\))/set(LINUX_MAINTAINER_EMAIL ${product_email})/"\
-        "${project_root}/CMakeLists.txt"
-      ;;
-    linux)
-      sed -i'' \
-        "s/project(\(.*\) VERSION \(.*\))/project(${product_name} VERSION ${product_version})/"\
-        "${project_root}/CMakeLists.txt"
-      sed -i'' \
-        "s/set(PLUGIN_AUTHOR \(.*\))/set(PLUGIN_AUTHOR ${product_author})/"\
-        "${project_root}/CMakeLists.txt"
-      sed -i'' \
-        "s/set(LINUX_MAINTAINER_EMAIL \(.*\))/set(LINUX_MAINTAINER_EMAIL ${product_email})/"\
-        "${project_root}/CMakeLists.txt"
-      ;;
-  }
-
-  setup_obs
+  read -r product_name product_version <<< \
+    "$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})"
 
   pushd ${project_root}
   if (( ! (${skips[(Ie)all]} + ${skips[(Ie)build]}) )) {
-    log_info "Configuring ${product_name}..."
-
-    local _plugin_deps="${project_root:h}/obs-build-dependencies/plugin-deps-${OBS_DEPS_VERSION}-qt${QT_VERSION}-${target##*-}"
-    local -a cmake_args=(
-      -DCMAKE_BUILD_TYPE=${BUILD_CONFIG:-RelWithDebInfo}
-      -DQT_VERSION=${QT_VERSION}
-      -DCMAKE_PREFIX_PATH="${_plugin_deps}"
-    )
-
-    if (( _loglevel == 0 )) cmake_args+=(-Wno_deprecated -Wno-dev --log-level=ERROR)
-    if (( _loglevel > 2 )) cmake_args+=(--debug-output)
+    log_group "Configuring ${product_name}..."
 
-    local num_procs
+    local -a cmake_args=()
+    local -a cmake_build_args=(--build)
+    local -a cmake_install_args=(--install)
+
+    case ${_loglevel} {
+      0) cmake_args+=(-Wno_deprecated -Wno-dev --log-level=ERROR) ;;
+      1) ;;
+      2) cmake_build_args+=(--verbose) ;;
+      *) cmake_args+=(--debug-output) ;;
+    }
 
+    local -r _preset="${target%%-*}${CI:+-ci}"
     case ${target} {
       macos-*)
-        autoload -Uz read_codesign
-        if (( ${+CODESIGN} )) {
-          read_codesign
+        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+=(
-          -DCMAKE_FRAMEWORK_PATH="${_plugin_deps}/Frameworks"
-          -DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
-          -DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-          -DOBS_CODESIGN_LINKER=ON
-          -DOBS_BUNDLE_CODESIGN_IDENTITY="${CODESIGN_IDENT:--}"
+          -DCODESIGN_TEAM=${CODESIGN_TEAM:-}
+          -DCODESIGN_IDENTITY=${CODESIGN_IDENT:--}
         )
-        num_procs=$(( $(sysctl -n hw.ncpu) + 1 ))
+
+        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-*)
-        if (( ${+CI} )) {
-          cmake_args+=(-DCMAKE_INSTALL_PREFIX=/usr -DLINUX_PORTABLE=OFF)
+        cmake_args+=(
+          --preset ${_preset}-${target##*-}
+          -G "${generator}"
+          -DQT_VERSION=${QT_VERSION:-6}
+          -DCMAKE_BUILD_TYPE=${config}
+        )
+
+        local cmake_version
+        read -r _ _ cmake_version <<< "$(cmake --version)"
+
+        if [[ ${CPUTYPE} != ${target##*-} ]] {
+          if is-at-least 3.21.0 ${cmake_version}; then
+            cmake_args+=(--toolchain "${project_root}/cmake/linux/toolchains/${target##*-}-linux-gcc.cmake")
+          else
+            cmake_args+=(-D"CMAKE_TOOLCHAIN_FILE=${project_root}/cmake/linux/toolchains/${target##*-}-linux-gcc.cmake")
+          fi
         }
-        num_procs=$(( $(nproc) + 1 ))
+
+        cmake_build_args+=(--preset ${_preset}-${target##*-} --config ${config})
+        if [[ ${generator} == 'Unix Makefiles' ]] {
+          cmake_build_args+=(--parallel $(( $(nproc) + 1 )))
+        } else {
+          cmake_build_args+=(--parallel)
+        }
+
+        cmake_install_args+=(build_${target##*-} --prefix ${project_root}/release/${config})
         ;;
     }
 
-    log_debug "Attempting to configure ${product_name} with CMake arguments: ${cmake_args}"
-    cmake -S . -B build_${target##*-} -G ${generator} ${cmake_args}
+    log_debug "Attempting to configure with CMake arguments: ${cmake_args}"
 
-    log_info "Building ${product_name}..."
-    local -a cmake_args=()
-    if (( _loglevel > 1 )) cmake_args+=(--verbose)
-    if [[ ${generator} == 'Unix Makefiles' ]] cmake_args+=(--parallel ${num_procs})
-    cmake --build build_${target##*-} --config ${BUILD_CONFIG:-RelWithDebInfo} ${cmake_args}
+    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}
+    }
   }
 
-  log_info "Installing ${product_name}..."
-  local -a cmake_args=()
-  if (( _loglevel > 1 )) cmake_args+=(--verbose)
-  cmake --install build_${target##*-} --config ${BUILD_CONFIG:-RelWithDebInfo} --prefix "${project_root}/release" ${cmake_args}
+  log_group "Installing ${product_name}..."
+  if (( _loglevel > 1 )) cmake_install_args+=(--verbose)
+  cmake ${cmake_install_args}
   popd
+  log_group
 }
 
 build ${@}

+ 148 - 67
.github/scripts/.package.zsh

@@ -17,40 +17,70 @@ setopt FUNCTION_ARGZERO
 # setopt XTRACE
 
 autoload -Uz is-at-least && if ! is-at-least 5.2; then
-  print -u2 -PR "%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."
+  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
 
-_trap_error() {
-  print -u2 -PR '%F{1}    ✖︎ script execution error%f'
-  print -PR -e "
+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"
+    print -PR -e "
     Callstack:
     ${(j:\n     :)funcfiletrace}
-  "
+    "
+  }
+
   exit 2
 }
 
 package() {
   if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h}
   local host_os=${${(s:-:)ZSH_ARGZERO:t:r}[2]}
-  local target="${host_os}-${CPUTYPE}"
   local project_root=${SCRIPT_HOME:A:h:h}
-  local buildspec_file="${project_root}/buildspec.json"
-  trap '_trap_error' ZERR
+  local buildspec_file=${project_root}/buildspec.json
 
   fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath})
-  autoload -Uz set_loglevel log_info log_error log_output check_${host_os}
+  autoload -Uz set_loglevel log_info log_group log_error log_output check_${host_os}
+
+  if [[ ! -r ${buildspec_file} ]] {
+    log_error \
+      'No buildspec.json found. Please create a build specification for your project.'
+    return 2
+  }
 
-  local -i _verbosity=1
-  local -r _version='1.0.0'
+  local -i verbosity=1
+  local -r _version='2.0.0'
   local -r -a _valid_targets=(
-    macos-x86_64
-    macos-arm64
     macos-universal
     linux-x86_64
-    linux-aarch64
   )
+  local target
+  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 -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>]
 
@@ -58,10 +88,9 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
 
 %F{yellow} Package configuration options%f
  -----------------------------------------------------------------------------
-  %B-t | --target%b                     Specify target - default: %B%F{green}${host_os}-${CPUTYPE}%f%b
-  %B-c | --config%b                     Build configuration - default: %B%F{green}RelWithDebInfo%f%b
-  %B-s | --codesign%b                   Enable codesigning (macOS only)
-  %B-n | --notarize%b                   Enable notarization (macOS only)
+  %B-t | --target%b                     Specify target
+  %B-c | --config%b                     Build configuration
+  %B--skip-deps%b                       Skip checking for dependencies
 
 %F{yellow} Output options%f
  -----------------------------------------------------------------------------
@@ -72,7 +101,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
 %F{yellow} General options%f
  -----------------------------------------------------------------------------
   %B-h | --help%b                       Print this usage help
-  %B-V | --version%b                    Print script version information"
+  %B-V | --version%b                    Print script version information
+${_usage_host:-}"
 
   local -a args
   while (( # )) {
@@ -101,92 +131,143 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
         shift 2
         ;;
       -c|--config)
-        if (( ! ${_valid_configs[(Ie)${2}]} )) {
+        if (( !${_valid_configs[(Ie)${2}]} )) {
           log_error "Invalid value %B${2}%b for option %B${1}%b"
           log_output ${_usage}
           exit 2
         }
-        BUILD_CONFIG=${2}
+        config=${2}
         shift 2
         ;;
-      -s|--codesign) typeset -g CODESIGN=1; shift ;;
-      -n|--notarize) typeset -g NOTARIZE=1; typeset -g CODESIGN=1; shift ;;
-      -q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
-      -v|--verbose) (( _verbosity += 1 )); shift ;;
+      -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 ;;
+      --skip-deps) typeset -g skip_deps=1; shift ;;
+      -q|--quiet) (( verbosity -= 1 )) || true; shift ;;
+      -v|--verbose) (( verbosity += 1 )); shift ;;
       -h|--help) log_output ${_usage}; exit 0 ;;
       -V|--version) print -Pr "${_version}"; exit 0 ;;
-      --debug) _verbosity=3; shift ;;
+      --debug) verbosity=3; shift ;;
       *) log_error "Unknown option: %B${1}%b"; log_output ${_usage}; exit 2 ;;
     }
   }
 
+  : "${target:="${host_os}-${CPUTYPE}"}"
+
   set -- ${(@)args}
-  set_loglevel ${_verbosity}
+  set_loglevel ${verbosity}
 
-  check_${host_os}
+  if (( ! skip_deps )) {
+    check_${host_os}
+  }
 
   local product_name
   local product_version
   read -r product_name product_version <<< \
-    "$(jq -r '. | {name, version} | join(" ")' ${project_root}/buildspec.json)"
+    "$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})"
 
-  if [[ ${host_os} == 'macos' ]] {
+  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}-${target##*-}.pkg"
+    local output_name="${product_name}-${product_version}-${host_os}-universal"
 
-    if [[ ! -d ${project_root}/release/${product_name}.plugin ]] {
+    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 [[ ! -f ${project_root}/build_${target##*-}/installer-macos.generated.pkgproj ]] {
-      log_error 'Packages project file not found. Run the build script or the CMake build and install procedures first.'
-      return 2
-    }
+    local _tarflags='cJf'
+    if (( _loglevel > 1  || ${+CI} )) _tarflags="v${_tarflags}"
 
-    check_packages
+    if (( package )) {
+      if [[ ! -f ${project_root}/build_macos/installer-macos.generated.pkgproj ]] {
+        log_error 'Packages project file not found. Run the build script or the CMake build and install procedures first.'
+        return 2
+      }
 
-    log_info "Packaging ${product_name}..."
-    pushd ${project_root}
-    packagesbuild \
-      --build-folder ${project_root}/release \
-      ${project_root}/build_${target##*-}/installer-macos.generated.pkgproj
-
-    if (( ${+CODESIGN} )) {
-      read_codesign_installer
-      productsign \
-        --sign "${CODESIGN_IDENT_INSTALLER}" \
-        "${project_root}/release/${product_name}.pkg" \
-        "${project_root}/release/${output_name}"
-
-      rm "${project_root}/release/${product_name}.pkg"
-    } else {
-      mv "${project_root}/release/${product_name}.pkg" \
-        "${project_root}/release/${output_name}"
-    }
+      check_packages
 
-    if (( ${+CODESIGN} && ${+NOTARIZE} )) {
-      if [[ ! -f "${project_root}/release/${output_name}" ]] {
-        log_error "No package for notarization found."
-        return 2
+      log_group "Packaging ${product_name}..."
+      pushd ${project_root}
+      packagesbuild \
+        --build-folder ${project_root}/release/${config} \
+        ${project_root}/build_macos/installer-macos.generated.pkgproj
+
+      if (( codesign )) {
+        read_codesign_installer
+        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
       }
 
-      read_codesign_installer
-      read_codesign_pass
+      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
 
-      xcrun notarytool submit "${project_root}/release/${output_name}" \
-        --keychain-profile "OBS-Codesign-Password" --wait
-      xcrun stapler staple "${project_root}/release/${output_name}"
+        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
     }
-    popd
-  } elif [[ ${host_os} == 'linux' ]] {
+
+    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}..."
     pushd ${project_root}
-    cmake --build build_${target##*-} --config ${BUILD_CONFIG:-RelWithDebInfo} -t package ${cmake_args}
+    cmake --build build_${target##*-} --config ${config} -t package_source ${cmake_args}
     popd
+
+    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
   }
 }
 

+ 0 - 0
.github/scripts/build-linux.zsh → .github/scripts/build-linux


+ 0 - 13
.github/scripts/build-linux.sh

@@ -1,13 +0,0 @@
-#!/bin/sh
-
-if ! type zsh > /dev/null 2>&1; then
-    echo ' => Installing script dependency Zsh.'
-
-    sudo apt-get -y update
-    sudo apt-get -y install zsh
-fi
-
-SCRIPT=$(readlink -f "${0}")
-SCRIPT_DIR=$(dirname "${SCRIPT}")
-
-zsh ${SCRIPT_DIR}/build-linux.zsh "${@}"

+ 0 - 0
.github/scripts/build-macos.zsh → .github/scripts/build-macos


+ 0 - 11
.github/scripts/check-changes.sh

@@ -1,11 +0,0 @@
-#!/bin/bash
-dirty=$(git ls-files --modified)
-
-set +x
-if [[ $dirty ]]; then
-	echo "================================="
-    echo "Files were not formatted properly"
-    echo "$dirty"
-    echo "================================="
-    exit 1
-fi

+ 0 - 53
.github/scripts/check-cmake.sh

@@ -1,53 +0,0 @@
-#!/usr/bin/env bash
-
-set -o errexit
-set -o pipefail
-
-if [ ${#} -eq 1 -a "${1}" = "VERBOSE" ]; then
-    VERBOSITY="-l debug"
-else
-    VERBOSITY=""
-fi
-
-if [ "${CI}" ]; then
-    MODE="--check"
-else
-    MODE="-i"
-fi
-
-# Runs the formatter in parallel on the code base.
-# Return codes:
-#  - 1 there are files to be formatted
-#  - 0 everything looks fine
-
-# Get CPU count
-OS=$(uname)
-NPROC=1
-if [[ ${OS} = "Linux" ]] ; then
-    NPROC=$(nproc)
-elif [[ ${OS} = "Darwin" ]] ; then
-    NPROC=$(sysctl -n hw.physicalcpu)
-fi
-
-# Discover clang-format
-if ! type cmake-format 2> /dev/null ; then
-    echo "Required cmake-format not found"
-    exit 1
-fi
-
-find . -type d \( \
-    -path ./\*build\* -o \
-    -path ./release -o \
-    -path ./deps/jansson -o \
-    -path ./plugins/decklink/\*/decklink-sdk -o \
-    -path ./plugins/enc-amf -o \
-    -path ./plugins/mac-syphon/syphon-framework -o \
-    -path ./plugins/obs-outputs/ftl-sdk -o \
-    -path ./plugins/obs-vst -o \
-    -path ./plugins/obs-browser -o \
-    -path ./plugins/win-dshow/libdshowcapture -o \
-    -path ./plugins/obs-websocket/deps \
-\) -prune -false -type f -o \
-    -name 'CMakeLists.txt' -or \
-    -name '*.cmake' \
- | xargs -L10 -P ${NPROC} cmake-format ${MODE} ${VERBOSITY}

+ 0 - 60
.github/scripts/check-format.sh

@@ -1,60 +0,0 @@
-#!/usr/bin/env bash
-# Original source https://github.com/Project-OSRM/osrm-backend/blob/master/scripts/format.sh
-
-set -o errexit
-set -o pipefail
-set -o nounset
-
-if [ ${#} -eq 1 ]; then
-    VERBOSITY="--verbose"
-else
-    VERBOSITY=""
-fi
-
-# Runs the Clang Formatter in parallel on the code base.
-# Return codes:
-#  - 1 there are files to be formatted
-#  - 0 everything looks fine
-
-# Get CPU count
-OS=$(uname)
-NPROC=1
-if [[ ${OS} = "Linux" ]] ; then
-    NPROC=$(nproc)
-elif [[ ${OS} = "Darwin" ]] ; then
-    NPROC=$(sysctl -n hw.physicalcpu)
-fi
-
-# Discover clang-format
-if type clang-format-13 2> /dev/null ; then
-    CLANG_FORMAT=clang-format-13
-elif type clang-format 2> /dev/null ; then
-    # Clang format found, but need to check version
-    CLANG_FORMAT=clang-format
-    V=$(clang-format --version)
-    if [[ $V != *"version 13.0"* ]]; then
-        echo "clang-format is not 13.0 (returned ${V})"
-        exit 1
-    fi
-else
-    echo "No appropriate clang-format found (expected clang-format-13.0.0, or clang-format)"
-    exit 1
-fi
-
-find . -type d \( \
-    -path ./\*build\* -o \
-    -path ./release -o \
-    -path ./cmake -o \
-    -path ./plugins/decklink/\*/decklink-sdk -o \
-    -path ./plugins/enc-amf -o \
-    -path ./plugins/mac-syphon/syphon-framework -o \
-    -path ./plugins/obs-outputs/ftl-sdk -o \
-    -path ./plugins/obs-websocket/deps \
-\) -prune -false -type f -o \
-    -name '*.h' -or \
-    -name '*.hpp' -or \
-    -name '*.m' -or \
-    -name '*.mm' -or \
-    -name '*.c' -or \
-    -name '*.cpp' \
- | xargs -L100 -P ${NPROC} "${CLANG_FORMAT}" ${VERBOSITY} -i -style=file -fallback-style=none

+ 0 - 0
.github/scripts/package-linux.zsh → .github/scripts/package-linux


+ 0 - 13
.github/scripts/package-linux.sh

@@ -1,13 +0,0 @@
-#!/bin/sh
-
-if ! type zsh > /dev/null 2>&1; then
-    echo ' => Installing script dependency Zsh.'
-
-    sudo apt-get update
-    sudo apt-get install zsh
-fi
-
-SCRIPT=$(readlink -f "${0}")
-SCRIPT_DIR=$(dirname "${SCRIPT}")
-
-zsh ${SCRIPT_DIR}/package-linux.zsh "${@}"

+ 0 - 0
.github/scripts/package-macos.zsh → .github/scripts/package-macos


+ 29 - 6
.github/scripts/utils.zsh/check_linux

@@ -1,4 +1,20 @@
-autoload -Uz log_info log_status log_error log_debug log_warning
+autoload -Uz log_info log_status log_error log_debug log_warning log_group
+
+log_group 'Check Linux build requirements'
+log_debug 'Checking Linux distribution name and version...'
+
+# Check for Ubuntu version 22.10 or later, which have srt and librist available via apt-get
+typeset -g -i UBUNTU_2210_OR_LATER=0
+if [[ -f /etc/os_release ]] {
+  local dist_name
+  local dist_version
+  read -r dist_name dist_version <<< "$(source /etc/os_release; print "${NAME} ${VERSION_ID}")"
+
+  autoload -Uz is-at-least
+  if [[ ${dist_name} == Ubuntu ]] && is-at-least 22.10 ${dist_version}; then
+    typeset -g -i UBUNTU_2210_OR_LATER=1
+  fi
+}
 
 log_debug 'Checking for apt-get...'
 if (( ! ${+commands[apt-get]} )) {
@@ -8,14 +24,14 @@ if (( ! ${+commands[apt-get]} )) {
   log_debug "Apt-get located at ${commands[apt-get]}"
 }
 
-local -a dependencies=("${(f)$(<${SCRIPT_HOME}/.Aptfile)}")
+local -a dependencies=("${(fA)$(<${SCRIPT_HOME}/.Aptfile)}")
 local -a install_list
 local binary
 
 sudo apt-get update -qq
 
 for dependency (${dependencies}) {
-  local -a tokens=(${(s: :)dependency//(,|:|\')/})
+  local -a tokens=(${=dependency//(,|:|\')/})
 
   if [[ ! ${tokens[1]} == 'package' ]] continue
 
@@ -28,11 +44,18 @@ for dependency (${dependencies}) {
   if (( ! ${+commands[${binary}]} )) install_list+=(${tokens[2]})
 }
 
-local -a _quiet=('' '--quiet')
-
 log_debug "List of dependencies to install: ${install_list}"
 if (( ${#install_list} )) {
   if (( ! ${+CI} )) log_warning 'Dependency installation via apt may require elevated privileges'
 
-  sudo apt-get -y install ${install_list} ${_quiet[(( (_loglevel == 0) + 1 ))]}
+  local -a apt_args=(
+    ${CI:+-y}
+    --no-install-recommends
+  )
+  if (( _loglevel == 0 )) apt_args+=(--quiet)
+
+  sudo apt-get ${apt_args} install ${install_list}
 }
+
+rehash
+log_group

+ 5 - 3
.github/scripts/utils.zsh/check_macos

@@ -1,9 +1,10 @@
-autoload -Uz is-at-least log_info log_error log_status read_codesign
+autoload -Uz is-at-least log_group log_info log_error log_status read_codesign
 
 local macos_version=$(sw_vers -productVersion)
 
+log_group 'Install macOS build requirements'
 log_info 'Checking macOS version...'
-if ! is-at-least 11.0 "${macos_version}"; then
+if ! is-at-least 11.0 ${macos_version}; then
   log_error "Minimum required macOS version is 11.0, but running on macOS ${macos_version}"
   return 2
 else
@@ -16,5 +17,6 @@ if (( ! ${+commands[brew]} )) {
   return 2
 }
 
-brew bundle --file "${SCRIPT_HOME}/.Brewfile"
+brew bundle --file ${SCRIPT_HOME}/.Brewfile
 rehash
+log_group

+ 36 - 26
.github/scripts/utils.zsh/check_packages

@@ -1,5 +1,5 @@
 if (( ! ${+commands[packagesbuild]} )) {
-  autoload -Uz log_info log_status mkcd
+  autoload -Uz log_group log_info log_status mkcd
 
   if (( ! ${+commands[curl]} )) {
     log_error 'curl not found. Please install curl.'
@@ -12,41 +12,51 @@ if (( ! ${+commands[packagesbuild]} )) {
   }
 
   local -a curl_opts=()
-  if (( ! ${+CI} )) {
-    curl_opts+=(--progress-bar)
-  } else {
+  if (( ${+CI} )) {
     curl_opts+=(--show-error --silent)
+  } else {
+    curl_opts+=(--progress-bar)
   }
-  curl_opts+=(--location -O ${@})
+  curl_opts+=(--location -O)
 
-  log_info 'Installing Packages.app...'
+  log_group 'Installing Packages.app...'
 
-  pushd
-  mkcd ${project_root:h}/obs-build-dependencies
+  local version
+  local base_url
+  local hash
+  IFS=';' read -r version base_url hash <<< \
+    "$(jq -r '.tools.packages | {version, baseUrl, hash} | join(";")' buildspec.json)"
 
-  local packages_url='http://s.sudre.free.fr/Software/files/Packages.dmg'
-  local packages_hash='6afdd25386295974dad8f078b8f1e41cabebd08e72d970bf92f707c7e48b16c9'
+  mkdir -p ${project_root}/.deps && pushd ${project_root}/.deps
+  curl ${curl_opts} "${base_url}/Packages.dmg"
 
-  if [[ ! -f Packages.dmg ]] {
-    log_status 'Download Packages.app'
-    curl ${curl_opts} ${packages_url}
-  }
-
-  local image_checksum
-  read -r image_checksum _ <<< "$(sha256sum Packages.dmg)"
+  local checksum="$(sha256sum Packages.dmg | cut -d " " -f 1)"
 
-  if [[ ${packages_hash} != ${image_checksum} ]] {
-    log_error "Checksum mismatch of Packages.app download.
-Expected : ${packages_hash}
-Actual   : ${image_checksum}"
+  if [[ ${hash} != ${checksum} ]] {
+    log_error "Checksum mismatch of Packages.dmg download.
+Expected : ${hash}
+Actual   : ${checksum}"
     return 2
   }
 
-  hdiutil attach -noverify Packages.dmg &> /dev/null && log_status 'Packages.dmg image mounted.'
+  hdiutil attach -readonly -noverify -noautoopen -plist Packages.dmg > .result.plist
+  local -i num_entities=$(( $(plutil -extract system-entities raw -- .result.plist) - 1 ))
+  local keys
+  local mount_point
+  for i ({0..${num_entities}}) {
+    keys=($(plutil -extract system-entities.${i} raw -- .result.plist))
+    if [[ ${keys} == *mount-point* ]] {
+      mount_point=$(plutil -extract system-entities.${i}.mount-point raw -- .result.plist)
+      break
+    }
+  }
+  rm .result.plist
+
+  log_status 'Installing Packages.app requires elevated privileges!'
 
-  log_info 'Installing Packages.app...'
-  packages_volume=$(hdiutil info -plist | grep '<string>/Volumes/Packages' | sed 's/.*<string>\(\/Volumes\/[^<]*\)<\/string>/\1/')
+  sudo installer -pkg ${mount_point}/packages/Packages.pkg -target / && rehash
+  hdiutil detach ${mount_point} &> /dev/null && log_status 'Packages.dmg image unmounted.'
+  popd
 
-  sudo installer -pkg "${packages_volume}/packages/Packages.pkg" -target / && rehash
-  hdiutil detach ${packages_volume} &> /dev/null && log_status 'Packages.dmg image unmounted.'
+  log_group
 }

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

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

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

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

+ 16 - 0
.github/scripts/utils.zsh/log_group

@@ -0,0 +1,16 @@
+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 ${@}
+}

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

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

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

@@ -2,6 +2,8 @@ autoload -Uz log_info
 
 if (( ! ${+CODESIGN_IDENT} )) {
   typeset -g CODESIGN_IDENT
-  log_info 'Setting up identity for application codesigning...'
+  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')

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

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

+ 8 - 3
.github/scripts/utils.zsh/read_codesign_pass

@@ -17,8 +17,6 @@ if (( ! ${+CODESIGN_IDENT} )) {
   read_codesign
 }
 
-local codesign_ident_short=$(print "${CODESIGN_IDENT}" | /usr/bin/sed -En 's/.+\((.+)\)/\1/p')
-
 if (( ! ${+CODESIGN_IDENT_USER} )) {
   read_codesign_user
 }
@@ -30,4 +28,11 @@ if (( ! ${+CODESIGN_IDENT_PASS} )) {
 
 print ''
 log_info 'Setting up notarization keychain...'
-xcrun notarytool store-credentials 'OBS-Codesign-Password' --apple-id "${CODESIGN_IDENT_USER}" --team-id "${codesign_ident_short}" --password "${CODESIGN_IDENT_PASS}"
+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}"
+

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

@@ -0,0 +1,7 @@
+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): '
+}

+ 2 - 2
.github/scripts/utils.zsh/read_codesign_user

@@ -2,6 +2,6 @@ autoload -Uz log_info
 
 if (( ! ${+CODESIGN_IDENT_USER} )) {
   typeset -g CODESIGN_IDENT_USER
-  log_info 'Setting up developer id for codesigning...'
-  read CODESIGN_IDENT_USER'?Apple Developer ID: '
+  log_info 'Setting up Apple ID for notarization...'
+  read CODESIGN_IDENT_USER'?Apple ID: '
 }

+ 30 - 2
.github/scripts/utils.zsh/setup_ccache

@@ -1,12 +1,40 @@
 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:-500M}"
-    ccache --set-config=compression=true
+    ccache --set-config=max_size="${CCACHE_SIZE:-1G}"
     ccache -z > /dev/null
   }
 } else {

+ 31 - 31
.github/scripts/utils.zsh/setup_linux

@@ -13,50 +13,50 @@ if (( ! ${+target} )) {
 pushd ${project_root}
 
 typeset -g QT_VERSION
-read -r QT_VERSION <<< \
-  "$(jq -r --arg target "${target}" \
-    '.platformConfig[$target] | { qtVersion } | join(" ")' \
-    ${project_root}/buildspec.json)"
+
+local -a apt_args=(
+  ${CI:+-y}
+  --no-install-recommends
+)
+if (( _loglevel == 0 )) apt_args+=(--quiet)
 
 if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
-  log_info 'Installing obs build dependencies...'
+  log_group 'Installing obs-studio build dependencies...'
+
+  local suffix
+  if [[ ${CPUTYPE} != "${target##*-}" ]] {
+    local -A arch_mappings=(
+      aarch64 arm64
+      x86_64 amd64
+    )
+
+    suffix=":${arch_mappings[${target##*-}]}"
+
+    sudo apt-get install ${apt_args} gcc-${${target##*-}//_/-}-linux-gnu g++-${${target##*-}//_/-}-linux-gnu
+  }
 
-  sudo apt-get install -y \
+  sudo apt-get install ${apt_args} \
     build-essential \
-    libcurl4-openssl-dev \
-    libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev \
-    libswresample-dev libswscale-dev \
-    libjansson-dev \
-    libx11-xcb-dev \
     libgles2-mesa-dev \
-    libwayland-dev \
-    libpulse-dev
+    libobs-dev${suffix}
 
   local -a _qt_packages=()
 
   if (( QT_VERSION == 5 )) {
     _qt_packages+=(
-      qtbase5-dev
-      libqt5svg5-dev
-      qtbase5-private-dev
-      libqt5x11extras5-dev
+      qtbase5-dev${suffix}
+      libqt5svg5-dev${suffix}
+      qtbase5-private-dev${suffix}
+      libqt5x11extras5-dev${suffix}
     )
-  } elif (( QT_VERSION == 6 )) {
+  } else {
     _qt_packages+=(
-      qt6-base-dev
-      libqt6svg6-dev
-      qt6-base-private-dev
+      qt6-base-dev${suffix}
+      libqt6svg6-dev${suffix}
+      qt6-base-private-dev${suffix}
     )
-  } else {
-    log_error "Unsupported Qt version '${QT_VERSION}' specified."
-    return 2
   }
 
-  sudo apt-get install -y ${_qt_packages}
+  sudo apt-get install ${apt_args} ${_qt_packages}
+  log_group
 }
-
-local deps_version
-read -r deps_version <<< \
-  "$(jq -r '.dependencies.prebuilt.version' ${buildspec_file})"
-
-typeset -g OBS_DEPS_VERSION=${deps_version}

+ 0 - 127
.github/scripts/utils.zsh/setup_macos

@@ -1,127 +0,0 @@
-autoload -Uz log_error log_status log_info mkcd
-
-if (( ! ${+commands[curl]} )) {
-  log_error 'curl not found. Please install curl.'
-  return 2
-}
-
-if (( ! ${+commands[jq]} )) {
-  log_error 'jq not found. Please install jq.'
-  return 2
-}
-
-if (( ! ${+project_root} )) {
-  log_error "'project_root' not set. Please set before running ${0}."
-  return 2
-}
-
-if (( ! ${+target} )) {
-  log_error "'target' not set. Please set before running ${0}."
-  return 2
-}
-
-local -a curl_opts=()
-if (( ! ${+CI} )) {
-    curl_opts+=(--progress-bar)
-} else {
-    curl_opts+=(--show-error --silent)
-}
-curl_opts+=(--location -O ${@})
-
-pushd ${project_root}
-
-local _qt_version
-local _deployment_target
-read -r _qt_version _deployment_target <<< \
-  "$(jq -r --arg target "${target}" \
-    '.platformConfig[$target] | { qtVersion, deploymentTarget } | join (" ")' \
-    ${buildspec_file})"
-
-typeset -g QT_VERSION=${_qt_version}
-typeset -g DEPLOYMENT_TARGET=${_deployment_target}
-
-if (( ! (${skips[(Ie)all]}  + ${skips[(Ie)deps]}) )) {
-  mkdir -p ${project_root:h}/obs-build-dependencies
-
-  local dependency
-  local deps_version
-  local deps_baseurl
-  local deps_label
-  local deps_hash
-  local _filename
-  local _url
-  local _target
-  local artifact_checksum
-
-  for dependency ('prebuilt' "qt${QT_VERSION}") {
-    IFS=';' read -r deps_version deps_baseurl deps_label deps_hash <<< \
-      "$(jq -r --arg dependency "${dependency}" --arg target "${target}" \
-        '.dependencies[$dependency] | {version, baseUrl, "label", "hash": .hashes[$target]} | join(";")' \
-        ${buildspec_file})"
-
-    if [[ -z "${deps_version}" ]] {
-      log_error "No ${dependency} spec found in ${buildspec_file}."
-      return 2
-    }
-    log_info "Setting up ${deps_label}..."
-
-    pushd ${project_root:h}/obs-build-dependencies
-
-    case ${dependency} {
-      prebuilt)
-        _filename="macos-deps-${deps_version}-${target##*-}.tar.xz"
-        _url="${deps_baseurl}/${deps_version}/${_filename}"
-        _target="plugin-deps-${deps_version}-qt${QT_VERSION}-${target##*-}"
-        typeset -g OBS_DEPS_VERSION=${deps_version}
-        ;;
-      qt*)
-        if (( ${+CI} )) {
-          _filename="macos-deps-qt${QT_VERSION}-${deps_version}-universal.tar.xz"
-          deps_hash="$(jq -r --arg dependency "${dependency}" \
-            '.dependencies[$dependency].hashes["macos-universal"]' \
-            ${buildspec_file})"
-        } else {
-          _filename="macos-deps-qt${QT_VERSION}-${deps_version}-${target##*-}.tar.xz"
-        }
-        _url="${deps_baseurl}/${deps_version}/${_filename}"
-        _target="plugin-deps-${deps_version}-qt${QT_VERSION}-${target##*-}"
-        ;;
-    }
-
-    if [[ ! -f ${_filename} ]] {
-      log_debug "Running curl ${curl_opts} ${_url}"
-      curl ${curl_opts} ${_url} && \
-      log_status "Downloaded ${deps_label} for ${target}."
-    } else {
-      log_status "Found downloaded ${deps_label}"
-    }
-
-    read -r artifact_checksum _ <<< "$(sha256sum ${_filename})"
-    if [[ ${deps_hash} != ${artifact_checksum} ]] {
-      log_error "Checksum of downloaded ${deps_label} does not match specification.
-  Expected : ${deps_hash}
-  Actual   : ${artifact_checksum}"
-      return 2
-    }
-    log_status "Checksum of downloaded ${deps_label} matches."
-
-    if (( ! (${skips[(Ie)all]} + ${skips[(Ie)unpack]}) )) {
-      mkdir -p ${_target} && pushd ${_target}
-
-      XZ_OPT=-T0 tar -xzf ../${_filename} && log_status "${deps_label} extracted."
-      popd
-    }
-  }
-
-  popd
-  pushd ${project_root:h}/obs-build-dependencies
-  xattr -r -d com.apple.quarantine *
-  log_status 'Removed quarantine flag from downloaded dependencies...'
-  popd
-} else {
-  local deps_version
-  read -r deps_version <<< \
-    "$(jq -r '.dependencies.prebuilt.version' ${buildspec_file})"
-
-  typeset -g OBS_DEPS_VERSION=${deps_version}
-}

+ 0 - 122
.github/scripts/utils.zsh/setup_obs

@@ -1,122 +0,0 @@
-autoload -Uz log_error log_info log_status
-
-if (( ! ${+buildspec_file} )) {
-  log_error "'buildspec_file' not set. Please set before running ${0}."
-  return 2
-}
-
-if (( ! ${+commands[git]} )) {
-  log_error 'git not found. Please install git.'
-  return 2
-}
-
-if (( ! ${+commands[jq]} )) {
-  log_error 'jq not found. Please install jq.'
-  return 2
-}
-
-if (( ! ${+project_root} )) {
-  log_error "'project_root' not set. Please set before running ${0}."
-  return 2
-}
-
-if (( ! ${+target} )) {
-  log_error "'target' not set. Please set before running ${0}."
-  return 2
-}
-
-log_info 'Setting up OBS-Studio...'
-
-local obs_version
-local obs_repo
-local obs_branch
-local obs_hash
-
-read -r obs_version obs_repo obs_branch obs_hash <<< \
-  "$(jq -r --arg key "obs-studio" \
-     '.dependencies[$key] | {version, repository, branch, hash} | join(" ")' \
-     ${buildspec_file})"
-
-if [[ -z ${obs_version} ]] {
-  log_error "No obs-studio version found in buildspec.json"
-  return 2
-}
-
-pushd
-mkcd ${project_root:h}/obs-studio
-
-if (( ! (${skips[(Ie)all]} + ${skips[(Ie)unpack]}) )) {
-  if [[ -d .git ]] {
-    git config advice.detachedHead false
-    git config remote.pluginbuild.url "${obs_repo:-https://github.com/obsproject/obs-studio.git}"
-    git config remote.pluginbuild.fetch "+refs/heads/${obs_branch:-master}:refs/remotes/origin/${obs_branch:-master}"
-
-    git rev-parse -q --verify "${obs_hash}^{commit}" > /dev/null || git fetch pluginbuild
-    git checkout ${obs_branch:-master} -B ${product_name}
-    git reset --hard "${obs_hash}"
-    log_status 'Found existing obs-studio repository.'
-  } else {
-    git clone "${obs_repo:-https://github.com/obsproject/obs-studio.git}" "${PWD}"
-    git config advice.detachedHead false
-    git checkout -f "${obs_hash}" --
-    git checkout ${obs_branch:-master} -b ${product_name}
-    log_status 'obs-studio checked out.'
-  }
-
-  git submodule foreach --recursive git submodule sync
-  git submodule update --init --recursive
-}
-
-if (( ! (${skips[(Ie)all]} + ${skips[(Ie)build]}) )) {
-  log_info 'Configuring obs-studio...'
-
-  local -a cmake_args=(
-    -DCMAKE_BUILD_TYPE=${BUILD_CONFIG:-Release}
-    -DQT_VERSION=${QT_VERSION}
-    -DENABLE_PLUGINS=OFF
-    -DENABLE_UI=OFF
-    -DENABLE_SCRIPTING=OFF
-    -DCMAKE_INSTALL_PREFIX="${project_root:h}/obs-build-dependencies/plugin-deps-${OBS_DEPS_VERSION}-qt${QT_VERSION}-${target##*-}"
-    -DCMAKE_PREFIX_PATH="${project_root:h}/obs-build-dependencies/plugin-deps-${OBS_DEPS_VERSION}-qt${QT_VERSION}-${target##*-}"
-  )
-
-  if (( _loglevel == 0 )) cmake_args+=(-Wno_deprecated -Wno-dev --log-level=ERROR)
-  if (( _loglevel > 2 )) cmake_args+=(--debug-output)
-
-  local num_procs
-
-  case ${target} {
-    macos-*)
-      autoload -Uz read_codesign
-      if (( ${+CODESIGN} )) {
-        read_codesign
-      }
-
-      cmake_args+=(
-        -DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
-        -DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-        -DOBS_CODESIGN_LINKER=ON
-        -DOBS_BUNDLE_CODESIGN_IDENTITY="${CODESIGN_IDENT:--}"
-      )
-      num_procs=$(( $(sysctl -n hw.ncpu) + 1 ))
-      ;;
-    linux-*)
-      cmake_args+=(
-        -DENABLE_PIPEWIRE=OFF
-      )
-      num_procs=$(( $(nproc) + 1 ))
-      ;;
-  }
-
-  log_debug "Attempting to configure OBS with CMake arguments: ${cmake_args}"
-  cmake -S . -B plugin_build_${target##*-} -G ${generator} ${cmake_args}
-
-  log_info 'Building libobs and obs-frontend-api...'
-  local -a cmake_args=()
-  if (( _loglevel > 1 )) cmake_args+=(--verbose)
-  if [[ ${generator} == 'Unix Makefiles' ]] cmake_args+=(--parallel ${num_procs})
-  cmake --build plugin_build_${target##*-} --config ${BUILD_CONFIG:-Release} ${cmake_args} -t obs-frontend-api
-  cmake --install plugin_build_${target##*-} --config ${BUILD_CONFIG:-Release} --component obs_libraries ${cmake_args}
-}
-
-popd

+ 3 - 0
build-aux/.functions/log_debug

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

+ 3 - 0
build-aux/.functions/log_error

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

+ 16 - 0
build-aux/.functions/log_group

@@ -0,0 +1,16 @@
+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 ${@}
+}

+ 7 - 0
build-aux/.functions/log_info

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

+ 7 - 0
build-aux/.functions/log_output

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

+ 7 - 0
build-aux/.functions/log_status

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

+ 7 - 0
build-aux/.functions/log_warning

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

+ 17 - 0
build-aux/.functions/set_loglevel

@@ -0,0 +1,17 @@
+autoload -Uz log_debug log_error
+
+local -r _usage="Usage: %B${0}%b <loglevel>
+
+Set log level, following levels are supported: 0 (quiet), 1 (normal), 2 (verbose), 3 (debug)"
+
+if (( ! # )); then
+  log_error 'Called without arguments.'
+  log_output ${_usage}
+  return 2
+elif (( ${1} >= 4 )); then
+  log_error 'Called with loglevel > 3.'
+  log_output ${_usage}
+fi
+
+typeset -g -i -r _loglevel=${1}
+log_debug "Log level set to '${1}'"

+ 188 - 0
build-aux/.run-format.zsh

@@ -0,0 +1,188 @@
+#!/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
+
+autoload -Uz is-at-least && if ! is-at-least 5.2; then
+  print -u2 -PR "%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
+
+invoke_formatter() {
+  if (( # < 1 )) {
+    log_error "Usage invoke_formatter [formatter_name]"
+    exit 2
+  }
+
+  case ${1} {
+    clang)
+      if (( ${+commands[clang-format-13]} )) {
+        local formatter=clang-format-13
+      } elif (( ${+commands[clang-format]} )) {
+        local formatter=clang-format
+        local -a formatter_version=($(clang-format --version))
+
+        if ! is-at-least 13.0.1 ${formatter_version[-1]}; then
+          log_error "clang-format is not version 13.0.1 or above (found ${formatter_version[-1]}."
+          exit 2
+        fi
+
+        if ! is-at-least ${formatter_version[-1]} 13.0.1; then
+          log_error "clang-format is more recent than version 13.0.1 (found ${formatter_version[-1]})."
+          exit 2
+        fi
+      } else {
+        log_error "No viable clang-format version found (required 13.0.1)"
+        exit 2
+      }
+
+      local -a source_files=(src/**/*.(c|cpp|h|hpp|m|mm)(.N))
+
+      local -a format_args=(-style=file -fallback-style=none)
+      if (( _loglevel > 2 )) format_args+=(--verbose)
+      ;;
+    cmake)
+      local formatter=cmake-format
+      if (( ${+commands[cmake-format]} )) {
+        local cmake_format_version=$(cmake-format --version)
+
+        if ! is-at-least 0.6.13 ${cmake_format_version}; then
+          log_error "cmake-format is not version 0.6.13 or above (found ${cmake_format_version})."
+          exit 2
+        fi
+      } else {
+        log_error "No viable cmake-format version found (required 0.6.13)"
+        exit 2
+      }
+
+      local -a source_files=(**/(CMakeLists.txt|*.cmake)(.N))
+      source_files=(${source_files:#(build_*)/*})
+
+      local -a format_args=()
+      if (( _loglevel > 2 )) format_args+=(--log-level debug)
+      ;;
+    swift)
+      local formatter=swift-format
+      if (( ${+commands[swift-format]} )) {
+        local swift_format_version=$(swift-format --version)
+
+        if ! is-at-least 508.0.0 ${swift_format_version}; then
+          log_error "swift-format is not version 508.0.0 or above (found ${swift_format_version})."
+          exit 2
+        fi
+      } else {
+        log_error "No viable swift-format version found (required 508.0.0)"
+        exit 2
+      }
+
+      local -a source_files=(**/*.swift(.N))
+
+      local -a format_args=()
+      ;;
+    *) log_error "Invalid formatter specified: ${1}. Valid options are clang-format, cmake-format, and swift-format."; exit 2 ;;
+  }
+
+  local file
+  local -i num_failures=0
+  if (( check_only )) {
+    for file (${source_files}) {
+      if (( _loglevel > 1 )) log_info "Checking format of ${file}..."
+
+      if ! "${formatter}" ${format_args} "${file}" | diff -q "${file}" - &> /dev/null; then
+        log_error "${file} requires formatting changes."
+
+        if (( fail_on_error == 2 )) return 2;
+        num_failures=$(( num_failures + 1 ))
+      else
+        if (( _loglevel > 1 )) log_status "${file} requires no formatting changes."
+      fi
+    }
+    if (( fail_on_error && num_failures )) return 2;
+  } elif (( ${#source_files} )) {
+    format_args+=(-i)
+    "${formatter}" ${format_args} ${source_files}
+  }
+}
+
+run_format() {
+  if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h}
+  if (( ! ${+FORMATTER_NAME} )) typeset -g FORMATTER_NAME=${${(s:-:)ZSH_ARGZERO:t:r}[2]}
+
+  typeset -g host_os=${${(L)$(uname -s)}//darwin/macos}
+  local -i fail_on_error=0
+  local -i check_only=0
+  local -i verbosity=1
+  local -r _version='1.0.0'
+
+  fpath=("${SCRIPT_HOME}/.functions" ${fpath})
+  autoload -Uz log_info log_error log_output set_loglevel log_status log_warning
+
+  local -r _usage="
+Usage: %B${functrace[1]%:*}%b <option>
+
+%BOptions%b:
+
+%F{yellow} Formatting options%f
+ -----------------------------------------------------------------------------
+  %B-c | --check%b                      Check only, no actual formatting takes place
+
+%F{yellow} Output options%f
+ -----------------------------------------------------------------------------
+  %B-v | --verbose%b                    Verbose (more detailed output)
+  %B--fail-[never|error]                Fail script never/on formatting change - default: %B%F{green}never%f%b
+  %B--debug%b                           Debug (very detailed and added output)
+
+%F{yellow} General options%f
+ -----------------------------------------------------------------------------
+  %B-h | --help%b                       Print this usage help
+  %B-V | --version%b                    Print script version information"
+
+  local -a args
+  while (( # )) {
+    case ${1} {
+      --)
+        shift
+        args+=($@)
+        break
+        ;;
+      -c|--check) check_only=1; shift ;;
+      -v|--verbose) (( _verbosity += 1 )); shift ;;
+      -h|--help) log_output ${_usage}; exit 0 ;;
+      -V|--version) print -Pr "${_version}"; exit 0 ;;
+      --debug) verbosity=3; shift ;;
+      --fail-never)
+        fail_on_error=0
+        shift
+        ;;
+      --fail-error)
+        fail_on_error=1
+        shift
+        ;;
+      --fail-fast)
+        fail_on_error=2
+        shift
+        ;;
+      *) log_error "Unknown option: %B${1}%b"; log_output ${_usage}; exit 2 ;;
+    }
+  }
+
+  set -- ${(@)args}
+  set_loglevel ${verbosity}
+
+  invoke_formatter ${FORMATTER_NAME}
+}
+
+run_format ${@}

+ 1 - 0
build-aux/run-clang-format

@@ -0,0 +1 @@
+.run-format.zsh

+ 1 - 0
build-aux/run-cmake-format

@@ -0,0 +1 @@
+.run-format.zsh

+ 1 - 0
build-aux/run-swift-format

@@ -0,0 +1 @@
+.run-format.zsh