From 0c3cc6684b6a085f2243fbfd9c15c5b55d696923 Mon Sep 17 00:00:00 2001 From: Ryan Foster Date: Fri, 11 Oct 2024 14:18:39 -0400 Subject: [PATCH 1/7] CI: Port check-changes action from obs-studio Co-authored-by: PatTheMav --- .github/actions/check-changes/action.yaml | 82 +++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 .github/actions/check-changes/action.yaml diff --git a/.github/actions/check-changes/action.yaml b/.github/actions/check-changes/action.yaml new file mode 100644 index 000000000..e5690b8a7 --- /dev/null +++ b/.github/actions/check-changes/action.yaml @@ -0,0 +1,82 @@ +name: Check For Changed Files +description: Checks for changed files compared to specific git reference and glob expression +inputs: + baseRef: + description: Git reference to check against + required: false + ref: + description: Git reference to check with + required: false + default: HEAD + checkGlob: + description: Glob expression to limit check to specific files + required: false + useFallback: + description: Use fallback compare against prior commit + required: false + default: 'true' + diffFilter: + description: git diff-filter string to use + required: false + default: '' +outputs: + hasChangedFiles: + value: ${{ steps.checks.outputs.hasChangedFiles }} + description: True if specified files were changed in comparison to specified git reference + changedFiles: + value: ${{ steps.checks.outputs.changedFiles }} + description: List of changed files +runs: + using: composite + steps: + - name: Check For Changed Files ✅ + shell: bash + id: checks + env: + GIT_BASE_REF: ${{ inputs.baseRef }} + GIT_REF: ${{ inputs.ref }} + GITHUB_EVENT_FORCED: ${{ github.event.forced }} + GITHUB_REF_BEFORE: ${{ github.event.before }} + USE_FALLBACK: ${{ inputs.useFallback }} + DIFF_FILTER: ${{ inputs.diffFilter }} + run: | + : Check for Changed Files ✅ + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + shopt -s extglob + shopt -s dotglob + + # 4b825dc642cb6eb9a060e54bf8d69288fbee4904 is a "hidden" sha1 hash of + # the "empty tree", retrived via 'git hash-object -t tree /dev/null', + # and used here as a last-resort fallback to always provide a valid + # git ref. + + if [[ "${GIT_BASE_REF}" ]]; then + if ! git cat-file -e "${GIT_BASE_REF}" &> /dev/null; then + echo "::warning::Provided base reference ${GIT_BASE_REF} is invalid" + if [[ "${USE_FALLBACK}" == 'true' ]]; then + GIT_BASE_REF='HEAD~1' + fi + fi + else + if ! git cat-file -e ${GITHUB_REF_BEFORE} &> /dev/null; then + GITHUB_REF_BEFORE='4b825dc642cb6eb9a060e54bf8d69288fbee4904' + fi + + GIT_BASE_REF='HEAD~1' + case "${GITHUB_EVENT_NAME}" in + pull_request) GIT_BASE_REF="origin/${GITHUB_BASE_REF}" ;; + push) if [[ "${GITHUB_EVENT_FORCED}" != 'true' ]]; then GIT_BASE_REF="${GITHUB_REF_BEFORE}"; fi ;; + *) ;; + esac + fi + + changes=($(git diff --name-only --diff-filter="${DIFF_FILTER}" ${GIT_BASE_REF} ${GIT_REF} -- ${{ inputs.checkGlob }})) + + if (( ${#changes[@]} )); then + file_string="${changes[*]}" + echo "hasChangedFiles=true" >> $GITHUB_OUTPUT + echo "changedFiles=[\"${file_string// /\",\"}\"]" >> $GITHUB_OUTPUT + else + echo "hasChangedFiles=false" >> $GITHUB_OUTPUT + echo "changedFiles=[]" >> GITHUB_OUTPUT + fi From 9d2f6a5e0cd2acd8decefa8b22424d1aece9606c Mon Sep 17 00:00:00 2001 From: Ryan Foster Date: Fri, 11 Oct 2024 14:52:31 -0400 Subject: [PATCH 2/7] build-aux: Add clang-format scripts --- build-aux/.functions/log_debug | 3 + build-aux/.functions/log_error | 3 + build-aux/.functions/log_group | 16 ++ build-aux/.functions/log_info | 7 + build-aux/.functions/log_output | 7 + build-aux/.functions/log_status | 7 + build-aux/.functions/log_warning | 7 + build-aux/.functions/set_loglevel | 17 ++ build-aux/.run-format.zsh | 271 ++++++++++++++++++++++++++++++ build-aux/run-clang-format | 1 + 10 files changed, 339 insertions(+) create mode 100644 build-aux/.functions/log_debug create mode 100644 build-aux/.functions/log_error create mode 100644 build-aux/.functions/log_group create mode 100644 build-aux/.functions/log_info create mode 100644 build-aux/.functions/log_output create mode 100644 build-aux/.functions/log_status create mode 100644 build-aux/.functions/log_warning create mode 100644 build-aux/.functions/set_loglevel create mode 100755 build-aux/.run-format.zsh create mode 120000 build-aux/run-clang-format diff --git a/build-aux/.functions/log_debug b/build-aux/.functions/log_debug new file mode 100644 index 000000000..6a477a181 --- /dev/null +++ b/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" diff --git a/build-aux/.functions/log_error b/build-aux/.functions/log_error new file mode 100644 index 000000000..f1c7b4364 --- /dev/null +++ b/build-aux/.functions/log_error @@ -0,0 +1,3 @@ +local icon=' ✖︎ ' + +print -u2 -PR "${CI:+::error::}%F{1} ${icon} %f ${@}" diff --git a/build-aux/.functions/log_group b/build-aux/.functions/log_group new file mode 100644 index 000000000..7b6aca978 --- /dev/null +++ b/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 ${@} +} diff --git a/build-aux/.functions/log_info b/build-aux/.functions/log_info new file mode 100644 index 000000000..d437c292d --- /dev/null +++ b/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" +} diff --git a/build-aux/.functions/log_output b/build-aux/.functions/log_output new file mode 100644 index 000000000..4d9b52f39 --- /dev/null +++ b/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} ${@}" +} diff --git a/build-aux/.functions/log_status b/build-aux/.functions/log_status new file mode 100644 index 000000000..950e68187 --- /dev/null +++ b/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 ${@}" +} diff --git a/build-aux/.functions/log_warning b/build-aux/.functions/log_warning new file mode 100644 index 000000000..ceff982a9 --- /dev/null +++ b/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" +} diff --git a/build-aux/.functions/set_loglevel b/build-aux/.functions/set_loglevel new file mode 100644 index 000000000..e32f4bb35 --- /dev/null +++ b/build-aux/.functions/set_loglevel @@ -0,0 +1,17 @@ +autoload -Uz log_debug log_error + +local -r _usage="Usage: %B${0}%b + +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}'" diff --git a/build-aux/.run-format.zsh b/build-aux/.run-format.zsh new file mode 100755 index 000000000..26c92f8f1 --- /dev/null +++ b/build-aux/.run-format.zsh @@ -0,0 +1,271 @@ +#!/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 + } + + local formatter="${1}" + shift + local -a source_files=(${@}) + + case ${formatter} { + clang) + if (( ${+commands[clang-format-17]} )) { + local formatter=clang-format-17 + } elif (( ${+commands[clang-format]} )) { + local formatter=clang-format + } else { + log_error "No viable clang-format version found (required 17.0.3)" + exit 2 + } + + local -a formatter_version=($(${formatter} --version)) + + if ! is-at-least 17.0.3 ${formatter_version[-1]}; then + log_error "clang-format is not version 17.0.3 or above (found ${formatter_version[-1]}." + exit 2 + fi + + if ! is-at-least ${formatter_version[-1]} 17.0.3; then + log_error "clang-format is more recent than version 17.0.3 (found ${formatter_version[-1]})." + exit 2 + fi + + if (( ! #source_files )) source_files=((libobs|libobs-*|UI|plugins|deps|shared)/**/*.(c|cpp|h|hpp|m|mm)(.N)) + + source_files=(${source_files:#*/(obs-websocket/deps|decklink/*/decklink-sdk|mac-syphon/syphon-framework|libdshowcapture)/*}) + + local -a format_args=(-style=file -fallback-style=none) + if (( _loglevel > 2 )) format_args+=(--verbose) + + check_files() { + local -i num_failures=0 + local -a source_files=($@) + local file + local -a format_args=(-style=file -fallback-style=none) + if (( _loglevel > 2 )) format_args+=(--verbose) + + local -a command=(${formatter} ${format_args}) + + for file (${source_files}) { + if ! ${command} "${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 )) + fi + } + if (( num_failures && fail_on_error == 1 )) return 2 + } + + format_files() { + local -a source_files=($@) + + if (( ${#source_files} )) { + local -a format_args=(-style=file -fallback-style=none -i) + if (( _loglevel > 2 )) format_args+=(--verbose) + + "${formatter}" ${format_args} ${source_files} + } + } + ;; + gersemi) + local formatter=gersemi + if (( ${+commands[gersemi]} )) { + local gersemi_version=($(gersemi --version)) + + if ! is-at-least 0.12.0 ${gersemi_version[2]}; then + log_error "gersemi is not version 0.12.0 or above (found ${gersemi_version[2]}." + exit 2 + fi + } + + if (( ! #source_files )) source_files=(CMakeLists.txt (libobs|libobs-*|UI|plugins|deps|shared|cmake|test)/**/(CMakeLists.txt|*.cmake)(.N)) + + source_files=(${source_files:#*/(jansson|decklink/*/decklink-sdk|obs-websocket|obs-browser|libdshowcapture)/*}) + source_files=(${source_files:#(cmake/Modules/*|*/legacy.cmake)}) + + check_files() { + local -i num_failures=0 + local -a source_files=($@) + local file + local -a command=(${formatter} -c --no-cache ${source_files}) + + if (( ${#source_files} )) { + while read -r line; do + local -a line_tokens=(${(z)line}) + file=${line_tokens[1]//*obs-studio\//} + + log_error "${file} requires formatting changes." + + if (( fail_on_error == 2 )) return 2 + num_failures=$(( num_failures + 1 )) + done < <(${command} 2>&1) + + if (( num_failures && fail_on_error == 1 )) return 2 + } + } + + format_files() { + local -a source_files=($@) + + if (( ${#source_files} )) { + "${formatter}" -i ${source_files} + } + } + ;; + 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 + } + + if (( ! #source_files )) source_files=((libobs|libobs-*|UI|plugins)/**/*.swift(.N)) + + check_files() { + local -i num_failures=0 + local -a source_files=($@) + local file + local -a format_args=() + + local -a command=(${formatter} ${format_args}) + + for file (${source_files}) { + if ! "${command}" "${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 )) + fi + } + if (( num_failures && fail_on_error == 1 )) return 2 + } + + format_files() { + local -a source_files=($@) + + if (( ${#source_files} )) { + local -a format_args=(-i) + + "${formatter}" ${format_args} ${source_files} + } + } + ;; + *) log_error "Invalid formatter specified: ${1}. Valid options are clang-format, gersemi, and swift-format."; exit 2 ;; + } + + local file + local -i num_failures=0 + if (( check_only )) { + if (( ${+functions[check_files]} )) { + check_files ${source_files} + } else { + log_error "No format check function defined for formatter '${formatter}'" + exit 2 + } + } else { + if (( ${+functions[format_files]} )) { + format_files ${source_files} + } else { + log_error "No format function defined for formatter '${formatter}'" + exit 2 + } + } +} + +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