Browse Source

CI: Add zsh-based build framework files for macOS

PatTheMav 2 years ago
parent
commit
1cfa06a2aa

+ 5 - 0
.github/scripts/.Brewfile

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

+ 22 - 0
.github/scripts/utils.zsh/check_macos

@@ -0,0 +1,22 @@
+autoload -Uz is-at-least log_group log_info log_error log_status
+
+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
+  log_error "Minimum required macOS version is 11.0, but running on macOS ${macos_version}"
+  return 2
+else
+  log_status "macOS ${macos_version} is recent"
+fi
+
+log_info 'Checking for Homebrew...'
+if (( ! ${+commands[brew]} )) {
+  log_error 'No Homebrew command found. Please install Homebrew (https://brew.sh)'
+  return 2
+}
+
+brew bundle --file ${SCRIPT_HOME}/.Brewfile
+rehash
+log_group

+ 62 - 0
.github/scripts/utils.zsh/create_diskimage

@@ -0,0 +1,62 @@
+autoload -Uz log_debug log_error log_info log_status log_group log_output
+
+local -r _usage="Usage: %B${0}%b <source> <volume name> <output_name>
+
+Create macOS disk image <volume name> <output_name> with contents of <source>"
+
+if (( ! # )) {
+  log_error 'Called without arguments.'
+  log_output ${_usage}
+  return 2
+}
+
+local source=${1}
+local volume_name=${2}
+local output_name=${3}
+
+log_group "Create macOS disk image"
+
+local _hdiutil_flags
+if (( _loglevel < 1 )) _hdiutil_flags='-quiet'
+
+trap "hdiutil detach ${_hdiutil_flags} /Volumes/${output_name}; rm temp.dmg; log_group return 2" ERR
+
+hdiutil create ${_hdiutil_flags} \
+    -volname "${volume_name}" \
+    -srcfolder ${source} \
+    -ov \
+    -fs APFS \
+    -format UDRW \
+    temp.dmg
+hdiutil attach ${_hdiutil_flags} \
+    -noverify \
+    -readwrite \
+    -mountpoint /Volumes/${output_name} \
+    temp.dmg
+
+log_info "Waiting 2 seconds to ensure mounted volume is available..."
+sleep 2
+log_status "Done"
+log_info "Setting up disk volume..."
+log_status "Volume icon"
+SetFile -c icnC /Volumes/${output_name}/.VolumeIcon.icns
+log_status "Icon positions"
+osascript package.applescript ${output_name}
+log_status "File permissions"
+chmod -Rf go-w /Volumes/${output_name}
+SetFile -a C /Volumes/${output_name}
+rm -rf -- /Volumes/${output_name}/.fseventsd(N)
+log_info "Converting disk image..."
+hdiutil detach ${_hdiutil_flags} /Volumes/${output_name}
+hdiutil convert ${_hdiutil_flags} \
+    -format ULMO \
+    -ov \
+    -o ${output_name}.dmg temp.dmg
+
+rm temp.dmg
+
+trap '' ERR
+
+log_group
+
+return 0

+ 3 - 0
.github/scripts/utils.zsh/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
.github/scripts/utils.zsh/log_error

@@ -0,0 +1,3 @@
+local icon='  ✖︎ '
+
+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 ${@}
+}

+ 7 - 0
.github/scripts/utils.zsh/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
.github/scripts/utils.zsh/log_output

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

+ 7 - 0
.github/scripts/utils.zsh/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
.github/scripts/utils.zsh/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"
+}

+ 1 - 0
.github/scripts/utils.zsh/mkcd

@@ -0,0 +1 @@
+[[ -n ${1} ]] && mkdir -p ${1} && builtin cd ${1}

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

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

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

@@ -0,0 +1,24 @@
+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}"

+ 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): '
+}

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

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

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

@@ -0,0 +1,7 @@
+if (( _loglevel > 1 )) {
+  xcodebuild ${@}
+} else {
+  local -a xcbeautify_opts=()
+  if (( _loglevel == 0 )) xcbeautify_opts+=(--quiet)
+  xcodebuild ${@} 2>&1 | xcbeautify ${xcbeautify_opts}
+}

+ 17 - 0
.github/scripts/utils.zsh/set_loglevel

@@ -0,0 +1,17 @@
+autoload -Uz log_debug log_error log_output
+
+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}'"

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

@@ -0,0 +1,42 @@
+autoload -Uz log_debug log_warning log_error
+
+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"
+}

+ 43 - 102
.gitignore

@@ -1,108 +1,49 @@
-#binaries
-*.exe
-*.dll
-*.dylib
-*.so
-*.plugin
-*.framework
-*.systemextension
-
-#cmake
-/build*/
-!/build-aux/
-/release*/
-/debug*/
-.vs/
-*.o.d
-*.ninja
-.ninja*
-.dirstamp
-/cmake/.CMakeBuildNumber
-.deps
-CMakeUserPresets.json
-
-#xcode
-*.xcodeproj/
-/xcodebuild/
-
-#clion
-.idea/
-cmake-build-debug/
-
-#other stuff (windows stuff, qt moc stuff, etc)
-Release_MD/
-Release/
-Debug/
-x64/
-ipch/
-GeneratedFiles/
-.moc/
-/UI/obs.rc
-.vscode/
-/CI/include/*.lock.json
-install_temp/
-
-/other/
-
-#make stuff
-configure
-depcomp
-install-sh
-Makefile.in
-Makefile
-
-#python
-__pycache__
-
-#sphinx
+# Exclude everything
+/*
+
+# Except for default project files
+!/.github
+!/build-aux
+!/cmake
+!/deps
+!/docs
+!/libobs*
+!/plugins
+!/tests
+!/UI
+!.cirrus.xml
+!.clang-format
+!.cmake-format.json
+!.editorconfig
+!.git-blame-ignore-devs
+!.gitmodules
+!.gitignore
+!.mailmap
+!.swift-format
+!AUTHORS
+!buildspec.json
+!CMakeLists.txt
+!CMakePresets.json
+!COC.rst
+!COMMITMENT
+!CONTRIBUTING.rst
+!COPYING
+!INSTALL
+!README.rst
+
+# Exclude lock files
+*.lock.json
+
+# Exclude files generated by Sphinx in-tree
 /docs/sphinx/_build/*
 !/docs/sphinx/_build/.gitignore
 !/docs/sphinx/Makefile
 
-#random useless file stuff
-*.dmg
-*.app
-.directory
-.hg
-.depend
-tags
-*.trace
-*.vsp
-*.psess
-*.swp
-*.dat
-*.clbin
-*.log
-*.tlog
-*.sdf
-*.opensdf
-*.xml
-*.ipch
-*.css
-*.xslt
-*.aps
-*.suo
-*.ncb
-*.user
-*.lo
-*.ilk
-*.la
-*.o
-*.obj
-*.pdb
-*.res
-*.dep
-*.zip
-*.lnk
-*.chm
-*~
+# Exclude modified Flatpak files
+build-aux/flatpak-github-action-modified-*
+
+# Exclude macOS legacy resource forks
 .DS_Store
-*/.DS_Store
-*/**/.DS_Store
 
-#flatpak
-/.flatpak-builder/
-/_flatpak_build/
-/flatpak_app/
-/repo/
-/CI/flatpak/flatpak-github-action-modified-*
+# Exclude CMake build number cache
+/cmake/.CMakeBuildNumber

+ 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}'"

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

@@ -0,0 +1,190 @@
+#!/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
+      } else {
+        log_error "No viable clang-format version found (required 13.0.1)"
+        exit 2
+      }
+
+      local -a formatter_version=($(${formatter} --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
+
+      local -a source_files=((libobs|libobs-*|UI|plugins)/**/*.(c|cpp|h|hpp|m|mm)(.N))
+      source_files=(${source_files:#*/(obs-websocket/deps|decklink/*/decklink-sdk|enc-amf|mac-syphon/syphon-framework|obs-outputs/ftl-sdk)/*})
+
+      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=((libobs|libobs-*|UI|plugins|cmake)/**/(CMakeLists.txt|*.cmake)(.N))
+      source_files=(${source_files:#*/(obs-outputs/ftl-sdk|jansson|decklink/*/decklink-sdk|enc-amf|obs-websocket|obs-browser|win-dshow/libdshowcapture)/*})
+
+      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=((libobs|libobs-*|UI|plugins)/**/*.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 set_loglevel log_info log_error log_output 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