diff --git a/.github/actions/build-obs/action.yaml b/.github/actions/build-obs/action.yaml new file mode 100644 index 000000000..c0d117fad --- /dev/null +++ b/.github/actions/build-obs/action.yaml @@ -0,0 +1,118 @@ +name: Set Up and Build obs-studio +description: Builds obs-studio for specified architecture and build config +inputs: + target: + description: Build target for obs-studio + required: true + config: + description: Build configuration + required: false + default: RelWithDebInfo + codesign: + description: Enable codesigning (macOS only) + required: false + default: 'false' + codesignIdent: + description: Developer ID for application codesigning (macOS only) + required: false + default: '-' + codesignTeam: + description: Team ID for application codesigning (macOS only) + required: false + default: '' + workingDirectory: + description: Working directory for packaging + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Run macOS Build + if: runner.os == 'macOS' + shell: zsh --no-rcs --errexit --pipefail {0} + working-directory: ${{ inputs.workingDirectory }} + env: + CODESIGN_IDENT: ${{ inputs.codesignIdent }} + CODESIGN_TEAM: ${{ inputs.codesignTeam }} + run: | + : Run macOS Build + + local -a build_args=( + --config ${{ inputs.config }} + --target macos-${{ inputs.target }} + ) + if (( ${+RUNNER_DEBUG} )) build_args+=(--debug) + + if [[ '${{ inputs.codesign }}' == true ]] build_args+=(--codesign) + + git fetch origin --no-tags --no-recurse-submodules -q + .github/scripts/build-macos ${build_args} + + - name: Install Dependencies 🛍️ + if: runner.os == 'Linux' + shell: bash + run: | + : Install Dependencies 🛍️ + echo ::group::Install Dependencies + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH + brew install --quiet zsh + echo ::endgroup:: + + - name: Run Ubuntu Build + if: runner.os == 'Linux' + shell: zsh --no-rcs --errexit --pipefail {0} + working-directory: ${{ inputs.workingDirectory }} + run: | + : Run Ubuntu Build + + local -a build_args=( + --config ${{ inputs.config }} + --target linux-${{ inputs.target }} + --generator Ninja + ) + if (( ${+RUNNER_DEBUG} )) build_args+=(--debug) + + git fetch origin --no-tags --no-recurse-submodules -q + .github/scripts/build-linux ${build_args} + + - name: Run Windows Build + if: runner.os == 'Windows' + shell: pwsh + working-directory: ${{ inputs.workingDirectory }} + run: | + # Run Windows Build + $BuildArgs = @{ + Target = '${{ inputs.target }}' + Configuration = '${{ inputs.config }}' + } + + if ( $Env:RUNNER_DEBUG -ne $null ) { + $BuildArgs += @{ Debug = $true } + } + + git fetch origin --no-tags --no-recurse-submodules -q + .github/scripts/Build-Windows.ps1 @BuildArgs + + - name: Create Summary 📊 + if: contains(fromJSON('["Linux", "macOS"]'), runner.os) + shell: zsh --no-rcs --errexit --pipefail {0} + env: + CCACHE_CONFIGPATH: ${{ inputs.workingDirectory }}/.ccache.conf + run: | + : Create Summary 📊 + + local -a ccache_data + if (( ${+RUNNER_DEBUG} )) { + setopt XTRACE + ccache_data=("${(fA)$(ccache -s -vv)}") + } else { + ccache_data=("${(fA)$(ccache -s)}") + } + + print '### ${{ runner.os }} Ccache Stats (${{ inputs.target }})' >> $GITHUB_STEP_SUMMARY + print '```' >> $GITHUB_STEP_SUMMARY + for line (${ccache_data}) { + print ${line} >> $GITHUB_STEP_SUMMARY + } + print '```' >> $GITHUB_STEP_SUMMARY diff --git a/.github/actions/check-changes/action.yaml b/.github/actions/check-changes/action.yaml new file mode 100644 index 000000000..597218bd8 --- /dev/null +++ b/.github/actions/check-changes/action.yaml @@ -0,0 +1,57 @@ +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: true + 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' +outputs: + hasChangedFiles: + value: ${{ steps.checks.outputs.hasChangedFiles }} + description: True if specified files were changed in comparison to specified git reference + changedFiles: + value: ${{ toJSON(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 }} + USE_FALLBACK: ${{ inputs.useFallback }} + run: | + : Check for Changed Files ✅ + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + shopt -s extglob + shopt -s dotglob + + if ! git cat-file -e ${GIT_BASE_REF}; then + echo "::warning::Provided base reference ${GIT_BASE_REF} is invalid" + if [[ "${USE_FALLBACK}" == 'true' ]]; then + GIT_BASE_REF='HEAD~1' + fi + fi + + changes=($(git diff --name-only ${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 diff --git a/.github/actions/compatibility-validator/action.yaml b/.github/actions/compatibility-validator/action.yaml new file mode 100644 index 000000000..0af5af1a3 --- /dev/null +++ b/.github/actions/compatibility-validator/action.yaml @@ -0,0 +1,58 @@ +name: Compatibility Data Validator +description: Checks Windows compatibility data files +inputs: + repositorySecret: + description: GitHub token for API access + required: true + workingDirectory: + description: Working directory for checks + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os == 'Windows' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo "services-validation action requires a macOS-based or Linux-based runner." + exit 2 + + - name: Install and Configure Python 🐍 + shell: bash + run: | + : Install and Configure Python 🐍 + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + + echo ::group::Python Set Up + if [[ "${RUNNER_OS}" == Linux ]]; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH + fi + brew install --quiet python3 + python3 -m pip install jsonschema json_source_map + echo ::endgroup:: + + - name: Validate Compatibility Files JSON Schema 🕵️ + shell: bash + working-directory: ${{ inputs.workingDirectory }} + run: | + : Validate services file JSON schema 🕵️ + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + shopt -s extglob + + echo ::group::Schema Validation + python3 -u \ + .github/scripts/utils.py/check-jsonschema.py \ + --loglevel INFO \ + plugins/win-capture/data/@(compatibility|package).json + echo ::endgroup:: + + - name: Annotate Schema Validation Errors 🏷️ + uses: yuzutech/annotations-action@v0.4.0 + if: failure() + with: + repo-token: ${{ inputs.repositorySecret }} + title: Compatibility JSON Errors + input: ${{ inputs.workingDirectory }}/validation_errors.json diff --git a/.github/actions/flatpak-manifest-validator/action.yaml b/.github/actions/flatpak-manifest-validator/action.yaml new file mode 100644 index 000000000..a848ec762 --- /dev/null +++ b/.github/actions/flatpak-manifest-validator/action.yaml @@ -0,0 +1,38 @@ +name: Flatpak Manifest Validator +description: Checks order of Flatpak modules in manifest file +inputs: + manifestFile: + description: Flatpak manifest file to check + failCondition: + description: Controls whether failed checks also fail the workflow run + required: false + default: never + workingDirectory: + description: Working directory for checks + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os == 'Windows' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo "services-validation action requires a macOS-based or Linux-based runner." + exit 2 + + - name: Validate Flatpak Manifest 🕵️ + shell: bash + working-directory: ${{ inputs.workingDirectory }} + run: | + : Validate Flatpak Manifest 🕵️ + + echo ::group::Run Validation + if [[ '${{ inputs.failCondition }}' == 'never' ]]; then set +e; fi + python3 -u \ + build-aux/format-manifest.py \ + build-aux/com.obsproject.Studio.json \ + --check \ + --loglevel INFO + echo ::endgroup:: diff --git a/.github/actions/generate-docs/action.yaml b/.github/actions/generate-docs/action.yaml new file mode 100644 index 000000000..97c30a849 --- /dev/null +++ b/.github/actions/generate-docs/action.yaml @@ -0,0 +1,62 @@ +name: Generate Documentation +description: Updates Sphinx-based documentation +inputs: + sourceDirectory: + description: Path to repository checkout + required: false + default: ${{ github.workspace }} + disableLinkExtensions: + description: Disable Sphinx link extensions + required: false + default: 'false' +runs: + using: composite + steps: + - name: Update Version Number and Copyright ↗️ + id: setup + shell: bash + run: | + : Update Version Number and Copyright ↗️ + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + + : "${major:=}" + : "${minor:=}" + : "${patch:=}" + + read -r _ major _ minor _ patch _ <<< \ + "$(grep -E -e "#define LIBOBS_API_(MAJOR|MINOR|PATCH)_VER *" libobs/obs-config.h \ + | sed 's/#define //g' \ + | tr -s ' ' \ + | tr '\n' ' ')" + + sed -i -E \ + -e "s/version = '([0-9]+\.[0-9]+\.[0-9]+)'/version = '${major}.${minor}.${patch}'/g" \ + -e "s/release = '([0-9]+\.[0-9]+\.[0-9]+)'/release = '${major}.${minor}.${patch}'/g" \ + -e "s/copyright = '(2017-[0-9]+, Lain Bailey)'/copyright = '2017-$(date +"%Y"), Lain Bailey'/g" \ + ${{ inputs.sourceDirectory }}/docs/sphinx/conf.py + + if [[ '${{ inputs.disableLinkExtensions }}' == 'true' ]]; then + sed -i -e "s/html_link_suffix = None/html_link_suffix = ''/g" \ + ${{ inputs.sourceDirectory }}/docs/sphinx/conf.py + echo "artifactName=OBS Studio Docs (No Extensions)" >> $GITHUB_OUTPUT + else + echo "artifactName=OBS Studio Docs" >> $GITHUB_OUTPUT + fi + + echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT + + - name: Install Sphinx 📜 + uses: totaldebug/sphinx-publish-action@1.2.0 + with: + sphinx_src: ${{ inputs.sourceDirectory }}/docs/sphinx + build_only: true + target_branch: master + target_path: '../home/_build' + pre_build_commands: 'pip install -Iv sphinx==5.1.1' + + - uses: actions/upload-artifact@v3 + with: + name: ${{ steps.setup.outputs.artifactName }} ${{ steps.setup.outputs.commitHash }} + path: | + ${{ runner.temp }}/_github_home/_build + !${{ runner.temp }}/_github_home/_build/.doctrees diff --git a/.github/actions/package-obs/action.yaml b/.github/actions/package-obs/action.yaml new file mode 100644 index 000000000..0c5fa42e5 --- /dev/null +++ b/.github/actions/package-obs/action.yaml @@ -0,0 +1,114 @@ +name: Package obs-studio +description: Packages obs-studio for specified architecture and build config +inputs: + target: + description: Build target for dependencies + required: true + config: + description: Build configuration + required: false + default: Release + codesign: + description: Enable codesigning (macOS only) + required: false + default: 'false' + notarize: + description: Enable notarization (macOS only) + required: false + default: 'false' + codesignIdent: + description: Developer ID for application codesigning (macOS only) + required: false + default: '-' + codesignUser: + description: Apple ID username for notarization (macOS only) + required: false + default: '' + codesignPass: + description: Apple ID password for notarization (macOS only) + required: false + default: '' + package: + description: Create platform-specific packages instead of archives + required: false + default: 'false' + workingDirectory: + description: Working directory for packaging + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Run macOS packaging + if: runner.os == 'macOS' + shell: zsh --no-rcs --errexit --pipefail {0} + working-directory: ${{ inputs.workingDirectory }} + env: + CODESIGN_IDENT: ${{ inputs.codesignIdent }} + CODESIGN_IDENT_USER: ${{ inputs.codesignUser }} + CODESIGN_IDENT_PASS: ${{ inputs.codesignPass }} + run: | + : Run macOS Packaging + + local -a package_args=( + --target macos-${{ inputs.target }} + --config ${{ inputs.config }} + ) + if (( ${+RUNNER_DEBUG} )) build_args+=(--debug) + + if [[ '${{ inputs.codesign }}' == true ]] package_args+=(--codesign) + if [[ '${{ inputs.notarize }}' == true ]] package_args+=(--notarize) + if [[ '${{ inputs.package }}' == true ]] package_args+=(--package) + + .github/scripts/package-macos ${package_args} + + - name: Install Dependencies 🛍️ + if: runner.os == 'Linux' + shell: bash + run: | + : Install Dependencies 🛍️ + echo ::group::Install Dependencies + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH + brew install --quiet zsh + echo ::endgroup:: + + - name: Run Ubuntu packaging + if: runner.os == 'Linux' + shell: zsh --no-rcs --errexit --pipefail {0} + working-directory: ${{ inputs.workingDirectory }} + run: | + : Run Ubuntu Packaging + + local -a package_args=( + --target linux-${{ inputs.target }} + --config ${{ inputs.config }} + ) + if (( ${+RUNNER_DEBUG} )) build_args+=(--debug) + + if [[ '${{ inputs.package }}' == true ]] package_args+=(--package) + + ${{ inputs.workingDirectory }}/.github/scripts/package-linux ${package_args} + + - name: Run Windows packaging + if: runner.os == 'Windows' + shell: pwsh + working-directory: ${{ inputs.workingDirectory }} + run: | + # Run Windows Packaging + $PackageArgs = @{ + Target = '${{ inputs.target }}' + Configuration = '${{ inputs.config }}' + } + + if ( $Env:RUNNER_DEBUG -ne $null ) { + $PackageArgs += @{ Debug = $true } + } + + if ( ( Test-Path env:CI ) -and ( Test-Path env:RUNNER_DEBUG ) ) { + $BuildArgs += @{ + Debug = $true + } + } + + .github/scripts/Package-windows.ps1 @PackageArgs diff --git a/.github/actions/qt-xml-validator/action.yaml b/.github/actions/qt-xml-validator/action.yaml new file mode 100644 index 000000000..ca7a2f992 --- /dev/null +++ b/.github/actions/qt-xml-validator/action.yaml @@ -0,0 +1,64 @@ +name: Validate UI XML +description: Validates Qt UI XML files +inputs: + failCondition: + description: Controls whether failed checks also fail the workflow run + required: false + default: never + workingDirectory: + description: Working directory for checks + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os == 'Windows' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo "::notice::qt-xml-validator action requires an Linux-based or macOS-based runner." + exit 2 + + - name: Install xmllint 🕵️ + if: runner.os == 'Linux' + shell: bash + run: | + : Install xmllint 🕵️ + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + + echo ::group::Installing libxml2-utils + sudo apt-get -qq update + sudo apt-get install --no-install-recommends -y libxml2-utils + echo ::endgroup:: + + - name: Register Annotations 📝 + uses: korelstar/xmllint-problem-matcher@v1 + + - name: Validate XML 💯 + shell: bash + env: + GITHUB_EVENT_FORCED: ${{ github.event.forced }} + GITHUB_REF_BEFORE: ${{ github.event.before }} + run: | + : Validate XML 💯 + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + shopt -s extglob + + changes=($(git diff --name-only HEAD~1 HEAD -- UI/forms)) + case "${GITHUB_EVENT_NAME}" in + pull_request) changes=($(git diff --name-only origin/"${GITHUB_BASE_REF}" HEAD -- UI/forms)) ;; + push) + if [[ "${GITHUB_EVENT_FORCED}" == false ]]; then + changes=($(git diff --name-only ${GITHUB_REF_BEFORE} HEAD -- UI/forms)) + fi + ;; + *) ;; + esac + + if (( ${#changes[@]} )); then + if [[ '${{ inputs.failCondition }}' == never ]]; then set +e; fi + xmllint \ + --schema ${{ github.workspace }}/UI/forms/XML-Schema-Qt5.15.xsd \ + --noout "${changes[@]}" + fi diff --git a/.github/actions/run-clang-format/action.yaml b/.github/actions/run-clang-format/action.yaml new file mode 100644 index 000000000..8fa7a79ba --- /dev/null +++ b/.github/actions/run-clang-format/action.yaml @@ -0,0 +1,61 @@ +name: Run clang-format +description: Runs clang-format and checks for any changes introduced by it +inputs: + failCondition: + description: Controls whether failed checks also fail the workflow run + required: false + default: never + workingDirectory: + description: Working directory for checks + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os == 'Windows' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo "::notice::run-clang-format action requires a macOS-based or Linux-based runner." + exit 2 + + - name: Install Dependencies 🛍️ + if: runner.os == 'Linux' + shell: bash + run: | + : Install Dependencies 🛍️ + echo ::group::Install Dependencies + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH + echo "/home/linuxbrew/.linuxbrew/opt/clang-format@13/bin" >> $GITHUB_PATH + brew install --quiet zsh + echo ::endgroup:: + + - name: Run clang-format 🐉 + id: result + shell: zsh --no-rcs --errexit --pipefail {0} + working-directory: ${{ inputs.workingDirectory }} + env: + GITHUB_EVENT_FORCED: ${{ github.event.forced }} + GITHUB_REF_BEFORE: ${{ github.event.before }} + run: | + : Run clang-format 🐉 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local -a changes=($(git diff --name-only HEAD~1 HEAD)) + case ${GITHUB_EVENT_NAME} { + pull_request) changes=($(git diff --name-only origin/${GITHUB_BASE_REF} HEAD)) ;; + push) if [[ ${GITHUB_EVENT_FORCED} != true ]] changes=($(git diff --name-only ${GITHUB_REF_BEFORE} HEAD)) ;; + *) ;; + } + + if (( ${changes[(I)(*.c|*.h|*.cpp|*.hpp|*.m|*.mm)]} )) { + print ::group::Install clang-format-13 + brew install --quiet obsproject/tools/clang-format@13 + print ::endgroup:: + + print ::group::Run clang-format-13 + ./build-aux/run-clang-format --fail-${{ inputs.failCondition }} --check + print ::endgroup:: + } diff --git a/.github/actions/run-cmake-format/action.yaml b/.github/actions/run-cmake-format/action.yaml new file mode 100644 index 000000000..835fdb82b --- /dev/null +++ b/.github/actions/run-cmake-format/action.yaml @@ -0,0 +1,60 @@ +name: Run cmake-format +description: Runs cmake-format and checks for any changes introduced by it +inputs: + failCondition: + description: Controls whether failed checks also fail the workflow run + required: false + default: never + workingDirectory: + description: Working directory for checks + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os == 'Windows' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo "::notice::run-cmake-format action requires a macOS-based or Linux-based runner." + exit 2 + + - name: Install Dependencies 🛍️ + if: runner.os == 'Linux' + shell: bash + run: | + : Install Dependencies 🛍️ + echo ::group::Install Dependencies + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH + brew install --quiet zsh + echo ::endgroup:: + + - name: Run cmake-format 🎛️ + id: result + shell: zsh --no-rcs --errexit --pipefail {0} + working-directory: ${{ github.workspace }} + env: + GITHUB_EVENT_FORCED: ${{ github.event.forced }} + GITHUB_REF_BEFORE: ${{ github.event.before }} + run: | + : Run cmake-format 🎛️ + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local -a changes=($(git diff --name-only HEAD~1 HEAD)) + case ${GITHUB_EVENT_NAME} { + pull_request) changes=($(git diff --name-only origin/${GITHUB_BASE_REF} HEAD)) ;; + push) if [[ ${GITHUB_EVENT_FORCED} != true ]] changes=($(git diff --name-only ${GITHUB_REF_BEFORE} HEAD)) ;; + *) ;; + } + + if (( ${changes[(I)*.cmake|*CMakeLists.txt]} )) { + print ::group::Install cmakelang + pip3 install cmakelang + print ::endgroup:: + + print ::group::Run cmake-format + ./build-aux/run-cmake-format --fail-${{ inputs.failCondition }} --check + print ::endgroup:: + } diff --git a/.github/actions/run-swift-format/action.yaml b/.github/actions/run-swift-format/action.yaml new file mode 100644 index 000000000..e595c3f30 --- /dev/null +++ b/.github/actions/run-swift-format/action.yaml @@ -0,0 +1,60 @@ +name: Run swift-format +description: Runs swift-format and checks for any changes introduced by it +inputs: + failCondition: + description: Controls whether failed checks also fail the workflow run + required: false + default: never + workingDirectory: + description: Working directory for checks + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os == 'Windows' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo "::notice::run-swift-format action requires a macOS-based or Linux-based runner." + exit 2 + + - name: Install Dependencies 🛍️ + if: runner.os == 'Linux' + shell: bash + run: | + : Install Dependencies 🛍️ + echo ::group::Install Dependencies + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH + brew install --quiet zsh + echo ::endgroup:: + + - name: Run swift-format 🔥 + id: result + shell: zsh --no-rcs --errexit --pipefail {0} + working-directory: ${{ github.workspace }} + env: + GITHUB_EVENT_FORCED: ${{ github.event.forced }} + GITHUB_REF_BEFORE: ${{ github.event.before }} + run: | + : Run swift-format 🔥 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local -a changes=($(git diff --name-only HEAD~1 HEAD)) + case ${GITHUB_EVENT_NAME} { + pull_request) changes=($(git diff --name-only origin/${GITHUB_BASE_REF} HEAD)) ;; + push) if [[ ${GITHUB_EVENT_FORCED} != true ]] changes=($(git diff --name-only ${GITHUB_REF_BEFORE} HEAD)) ;; + *) ;; + } + + if (( ${changes[(I)*.swift]} )) { + print ::group::Install swift-format + brew install --quiet swift-format + print ::endgroup:: + + print ::group::Run swift-format + ./build-aux/run-swift-format --fail-${{ inputs.failCondition }} --check + print ::endgroup:: + } diff --git a/.github/actions/services-validator/action.yaml b/.github/actions/services-validator/action.yaml new file mode 100644 index 000000000..4aac45819 --- /dev/null +++ b/.github/actions/services-validator/action.yaml @@ -0,0 +1,113 @@ +name: Services Validation +description: Checks services configuration file and checks for defunct services +inputs: + repositorySecret: + description: GitHub token for API access + required: true + runSchemaChecks: + description: Enable schema checking + required: false + default: 'true' + runServiceChecks: + description: Enable defunct service checking + required: false + default: 'false' + createPullRequest: + description: Enable pull request creation after service checks + required: false + default: 'false' + workingDirectory: + description: Working directory for checks + required: false + default: ${{ github.workspace }} +outputs: + hasDefunctServices: + description: True if defunct services were found in configuration + value: ${{ steps.check.outputs.make_pr }} +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os == 'Windows' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo "::notice::services-validation action requires a macOS-based or Linux-based runner." + exit 2 + + - name: Install and Configure Python 🐍 + shell: bash + run: | + : Install and Configure Python 🐍 + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + + echo ::group::Python Set Up + if [[ "${RUNNER_OS}" == Linux ]]; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH + fi + brew install --quiet python3 + python3 -m pip install jsonschema json_source_map requests + echo ::endgroup:: + + - name: Validate Services File JSON Schema 🕵️ + if: fromJSON(inputs.runSchemaChecks) + shell: bash + working-directory: ${{ inputs.workingDirectory }} + run: | + : Validate Services File JSON Schema 🕵️ + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + shopt -s extglob + + echo ::group::Run Validation + python3 -u \ + .github/scripts/utils.py/check-jsonschema.py \ + plugins/rtmp-services/data/@(services|package).json \ + --loglevel INFO + echo ::endgroup:: + + - name: Annotate schema validation errors 🏷️ + if: fromJSON(inputs.runSchemaChecks) && failure() + uses: yuzutech/annotations-action@v0.4.0 + with: + repo-token: ${{ inputs.repositorySecret }} + title: Service JSON Errors + input: ${{ inputs.workingDirectory }}/validation_errors.json + + - name: Restore Timestamp Cache ⏳ + if: fromJSON(inputs.runServiceChecks) + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/other + key: service-check-${{ github.run_id }} + restore-keys: service-check- + + - name: Check for defunct services 📉 + id: services-check + if: fromJSON(inputs.runServiceChecks) + shell: bash + working-directory: ${{ inputs.workingDirectory }} + env: + GITHUB_TOKEN: ${{ inputs.repositorySecret }} + WORKFLOW_RUN_ID: ${{ github.run_id }} + REPOSITORY: ${{ github.repository }} + run: | + : Check for defunct services 📉 + python3 -u .github/scripts/utils.py/check-services.py + + - uses: actions/upload-artifact@v3 + if: fromJSON(inputs.runServiceChecks) + with: + name: timestamps + path: ${{ inputs.workingDirectory }}/other/* + + - name: Create pull request 🔧 + uses: peter-evans/create-pull-request@f094b77505fb89581e68a1163fbd2fffece39da1 + if: fromJSON(inputs.createPullRequest) && fromJSON(inputs.runServiceChecks) && fromJSON(steps.services-check.outputs.make_pr) + with: + author: 'Service Checker ' + commit-message: 'rtmp-services: Remove defunct servers/services' + title: 'rtmp-services: Remove defunct servers/services' + branch: 'automated/clean-services' + body: ${{ fromJSON(steps.services-check.outputs.pr_message) }} + delete-branch: true diff --git a/.github/actions/setup-macos-codesigning/action.yaml b/.github/actions/setup-macos-codesigning/action.yaml new file mode 100644 index 000000000..43ff06b7e --- /dev/null +++ b/.github/actions/setup-macos-codesigning/action.yaml @@ -0,0 +1,146 @@ +name: Set up macOS Code Signing +description: Sets up code signing certificates, provisioning profiles, and notarization information +inputs: + codesignIdentity: + description: Code signing identity + required: true + codesignCertificate: + description: PKCS12 certificate in base64 format + required: true + certificatePassword: + description: Password required to install PKCS12 certificate + required: true + keychainPassword: + description: Password to use for temporary keychain + required: false + notarizationUser: + description: Apple ID to use for notarization + required: false + notarizationPassword: + description: Application password for notarization + provisioningProfile: + description: Provisioning profile in base64 format + required: false +outputs: + haveCodesignIdent: + description: True if necessary code signing credentials were found + value: ${{ steps.codesign.outputs.haveCodesignIdent }} + haveProvisioningProfile: + description: True if necessary provisioning profile credentials were found + value: ${{ steps.provisioning.outputs.haveProvisioningProfile }} + haveNotarizationUser: + description: True if necessary notarization credentials were found + value: ${{ steps.notarization.outputs.haveNotarizationUser }} + codesignIdent: + description: Code signing identity + value: ${{ steps.codesign.outputs.codesignIdent }} + codesignTeam: + description: Code signing team + value: ${{ steps.codesign.outputs.codesignTeam }} +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os != 'macOS' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo "setup-macos-codesigning action requires a macOS-based runner." + exit 2 + + - name: macOS Code Signing ✍️ + id: codesign + shell: zsh --no-rcs --errexit --pipefail {0} + env: + MACOS_SIGNING_IDENTITY: ${{ inputs.codesignIdentity }} + MACOS_SIGNING_CERT: ${{ inputs.codesignCertificate }} + MAOCS_SIGNING_CERT_PASSWORD: ${{ inputs.certificatePassword }} + MACOS_KEYCHAIN_PASSWORD: ${{ inputs.keychainPassword }} + run: | + : macOS Code Signing ✍️ + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + if [[ ${MACOS_SIGNING_IDENTITY} && ${MACOS_SIGNING_CERT} ]] { + print 'haveCodesignIdent=true' >> $GITHUB_OUTPUT + + local -r certificate_path="${RUNNER_TEMP}/build_certificate.p12" + local -r keychain_path="${RUNNER_TEMP}/app-signing.keychain-db" + + print -n "${MACOS_SIGNING_CERT}" | base64 --decode --output=${certificate_path} + + : "${MACOS_KEYCHAIN_PASSWORD:="$(print ${RANDOM} | sha1sum | head -c 32)"}" + + print '::group::Keychain setup' + security create-keychain -p "${MACOS_KEYCHAIN_PASSWORD}" ${keychain_path} + security set-keychain-settings -lut 21600 ${keychain_path} + security unlock-keychain -p "${MACOS_KEYCHAIN_PASSWORD}" ${keychain_path} + + security import "${certificate_path}" -P "${MAOCS_SIGNING_CERT_PASSWORD}" -A \ + -t cert -f pkcs12 -k ${keychain_path} \ + -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/xcrun + + security set-key-partition-list -S 'apple-tool:,apple:' -k "${MACOS_KEYCHAIN_PASSWORD}" \ + ${keychain_path} &> /dev/null + + security list-keychain -d user -s ${keychain_path} 'login-keychain' + print '::endgroup::' + + local -r team_id="${${MACOS_SIGNING_IDENTITY##* }//(\(|\))/}" + + print "codesignIdent=${MACOS_SIGNING_IDENTITY}" >> $GITHUB_OUTPUT + print "MACOS_KEYCHAIN_PASSWORD=${MACOS_KEYCHAIN_PASSWORD}" >> $GITHUB_ENV + print "codesignTeam=${team_id}" >> $GITHUB_OUTPUT + } else { + print 'haveCodesignIdent=false' >> $GITHUB_OUTPUT + } + + - name: Provisioning Profile 👤 + id: provisioning + if: fromJSON(steps.codesign.outputs.haveCodesignIdent) + shell: zsh --no-rcs --errexit --pipefail {0} + env: + MACOS_SIGNING_PROVISIONING_PROFILE: ${{ inputs.provisioningProfile }} + run: | + : Provisioning Profile 👤 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + if [[ "${MACOS_SIGNING_PROVISIONING_PROFILE}" ]] { + print 'haveProvisioningProfile=true' >> $GITHUB_OUTPUT + + local -r profile_path="${RUNNER_TEMP}/build_profile.provisionprofile" + print -n "${MACOS_SIGNING_PROVISIONING_PROFILE}" \ + | base64 --decode --output="${profile_path}" + + print '::group::Provisioning Profile Setup' + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + security cms -D -i ${profile_path} -o ${RUNNER_TEMP}/build_profile.plist + local -r uuid="$(plutil -extract UUID raw ${RUNNER_TEMP}/build_profile.plist)" + local -r team_id="$(plutil -extract TeamIdentifier.0 raw -expect string ${RUNNER_TEMP}/build_profile.plist)" + + if [[ ${team_id} != '${{ steps.codesign.codesignTeam }}' ]] { + print '::notice::Code Signing team in provisioning profile does not match certificate.' + } + + cp ${profile_path} ~/Library/MobileDevice/Provisioning\ Profiles/${uuid}.provisionprofile + print "provisioningProfileUUID=${uuid}" >> $GITHUB_OUTPUT + print '::endgroup::' + } else { + print 'haveProvisioningProfile=false' >> $GITHUB_OUTPUT + } + + - name: Notarization 🧑‍💼 + id: notarization + if: fromJSON(steps.codesign.outputs.haveCodesignIdent) + shell: zsh --no-rcs --errexit --pipefail {0} + env: + MACOS_NOTARIZATION_USERNAME: ${{ inputs.notarizationUser }} + MACOS_NOTARIZATION_PASSWORD: ${{ inputs.notarizationPassword }} + run: | + : Notarization 🧑‍💼 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + if [[ ${MACOS_NOTARIZATION_USERNAME} && ${MACOS_NOTARIZATION_PASSWORD} ]] { + print 'haveNotarizationUser=true' >> $GITHUB_OUTPUT + } else { + print 'haveNotarizationUser=false' >> $GITHUB_OUTPUT + } diff --git a/.github/actions/sparkle-appcast/action.yaml b/.github/actions/sparkle-appcast/action.yaml new file mode 100644 index 000000000..fd45e7b6c --- /dev/null +++ b/.github/actions/sparkle-appcast/action.yaml @@ -0,0 +1,213 @@ +name: Generate Sparkle Appcast +description: Creates Sparkle Appcast for a new release and generates delta patch files +inputs: + sparklePrivateKey: + description: Private key used for Sparkle signing + required: true + baseImage: + description: Disk image to base the Sparkle Appcast on + required: true + channel: + description: Sparkle Appcast channel to use + required: false + default: stable + count: + description: Number of old versions to generate deltas for + required: false + default: '1' + urlPrefix: + description: URL prefix to use for Sparkle downloads + required: true + customTitle: + description: Custom title to use for Appcast + required: false + customLink: + description: Custom link to use for Appcast + required: false +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os != 'macOS' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo '::notice::sparkle-appcast action requires a macOS-based runner.' + exit 2 + + - name: Install Dependencies + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Install Dependencies + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + print ::group::Install Dependencies + brew install --quiet coreutils pandoc + print ::endgroup:: + + - name: Set Up Sparkle ✨ + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Set Up Sparkle ✨ + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local version + local base_url + local hash + IFS=';' read -r version base_url hash <<< \ + "$(jq -r '.tools.sparkle | {version, baseUrl, hash} | join(";")' buildspec.json)" + + mkdir -p Sparkle && pushd Sparkle + curl -s -L -O "${base_url}/${version}/Sparkle-${version}.tar.xz" + + local checksum="$(sha256sum Sparkle-${version}.tar.xz | cut -d " " -f 1)" + + if [[ ${hash} != ${checksum} ]] { + print "::error::Sparkle-${version}.tar.xz checksum mismatch: ${checksum} (expected: ${hash})" + exit 2 + } + + tar -xJf "Sparkle-${version}.tar.xz" + popd + + mkdir builds + mkdir -p output/appcasts/stable + mkdir -p output/sparkle_deltas + + - name: Download Builds 📥 + id: builds + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Download Builds 📥 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + pushd builds + local image_location=(${{ inputs.baseImage }}) + hdiutil attach -readonly -noverify -noautoopen -plist ${image_location} > 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 + } + } + + local feed_url + local info_plist=(${mount_point}/*.app/Contents/Info.plist) + + if [[ -f ${info_plist} ]] { + feed_url=$(plutil -extract SUFeedURL raw -- ${info_plist}) + } else { + print '::error:: No Info.plist file found in specified disk image.' + hdiutil detach ${mount_point} + exit 2 + } + + print "feedUrl=${feed_url}" >> $GITHUB_OUTPUT + hdiutil detach ${mount_point} + + curl -s -L -O ${feed_url} + local -a artifacts=($(\ + xmllint \ + -xpath "//rss/channel/item[*[local-name()='channel'][text()='${{ inputs.channel }}']]/enclosure/@url" \ + ${feed_url:t} \ + | sed -n 's/.*url="\(.*\)"/\1/p') + ) + + local url + local file_name + for i ({1..${{ inputs.count }}}) { + url="${artifacts[${i}]}" + file_name="${artifacts[${i}]:t}" + curl -s -L -O ${url} + } + + mv ${{ inputs.baseImage }} ${PWD} + rm -rf - result.plist + popd + + - name: Prepare Release Notes 📝 + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Prepare Release Notes 📝 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + git tag -l --format='%(contents)' ${GITHUB_REF_NAME} \ + | tr '\n' '\\n' \ + | sed 's/-----BEGIN SSH SIGNATURE-----.*-----END SSH SIGNATURE-----//g' \ + | tr '\\n' '\n' > notes.rst + + sed -i '' '2i\'$'\n''###################################################' notes.rst + pandoc -f rst -t html notes.rst -o output/appcasts/notes_${{ inputs.channel }}.html + + - name: Generate Appcast 🎙️ + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Generate Appcast 🎙️ + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + print -n '${{ inputs.sparklePrivateKey }}' >> eddsa_private.key + local feed_url='${{ steps.builds.outputs.feedUrl }}' + + Sparkle/bin/generate_appcast \ + --verbose \ + --ed-key-file eddsa_private.key \ + --download-url-prefix '${{ inputs.urlPrefix }}/' \ + --full-release-notes-url "${feed_url//updates_*/notes_${{ inputs.channel }}.html}" \ + --maximum-versions 0 \ + --maximum-deltas ${{ inputs.count }} \ + --channel '${{ inputs.channel }}' \ + builds + + local -a deltas=(builds/*.delta(N)) + + if (( #deltas )) { + mv ${deltas} output/sparkle_deltas + } + + mv builds/*.xml output/appcasts + + - name: Adjust Appcast 🎙️ + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Adjust Appcast 🎙️ + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local feed_url='${{ steps.builds.outputs.feedUrl }}' + local arch=${${${(s:_:)feed_url:t}[2]}//x86/x86_64} + local -a appcasts=(output/appcasts/*_v2.xml) + local adjusted + for appcast (${appcasts}) { + adjusted="${appcast//.xml/-adjusted.xml}" + xsltproc \ + --stringparam pDeltaUrl "${{ inputs.urlPrefix }}/sparkle_deltas/${arch}/" \ + --stringparam pSparkleUrl '${{ inputs.urlPrefix }}/' \ + --stringparam pCustomTitle '${{ inputs.customTitle }}' \ + --stringparam pCustomLink '${{ inputs.customLink }}' \ + -o ${adjusted} ${GITHUB_ACTION_PATH}/appcast_adjust.xslt ${appcast} + + xmllint --format ${adjusted} >! ${appcast} + rm ${adjusted} + } + + - name: Create Legacy Appcast 📟 + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Create Legacy Appcast 📟 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local -a appcasts=(output/appcasts/*_v2.xml) + local legacy + + for appcast (${appcasts}) { + legacy="${appcast//.xml/-legacy.xml}" + xsltproc \ + -o ${legacy} ${GITHUB_ACTION_PATH}/appcast_legacy.xslt ${appcast} + + xmllint --format ${legacy} >! output/appcasts/stable/${${appcast:t}//-v2.xml/.xml} + rm ${legacy} + } diff --git a/.github/actions/sparkle-appcast/appcast_adjust.xslt b/.github/actions/sparkle-appcast/appcast_adjust.xslt new file mode 100644 index 000000000..da34b7cae --- /dev/null +++ b/.github/actions/sparkle-appcast/appcast_adjust.xslt @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/actions/sparkle-appcast/appcast_legacy.xslt b/.github/actions/sparkle-appcast/appcast_legacy.xslt new file mode 100644 index 000000000..5f3d41b1e --- /dev/null +++ b/.github/actions/sparkle-appcast/appcast_legacy.xslt @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/.github/actions/steam-upload/action.yaml b/.github/actions/steam-upload/action.yaml new file mode 100644 index 000000000..9023842d0 --- /dev/null +++ b/.github/actions/steam-upload/action.yaml @@ -0,0 +1,286 @@ +name: Steam Upload +description: Creates and uploads stable and nightly builds of obs-studio and beta builds (if available) +inputs: + steamSecret: + description: Steam auth code + required: true + steamUser: + description: Steam user name + required: true + steamPassword: + description: Steam user password + required: true + workflowSecret: + description: GitHub API token to use for API calls + required: true + tagName: + description: Tag name to use for packaging + required: false + default: '' + stableBranch: + description: Name of the stable branch to use + required: false + default: staging + betaBranch: + description: Name of the beta branch to use + required: false + default: beta_staging + nightlyBranch: + description: Name of the nightly branch to use + required: false + default: nightly + playtestBranch: + description: Name of the playtest branch to use + required: false + default: staging + customAssetWindows: + description: Custom asset for Windows + required: false + default: '' + customAssetMacOSApple: + description: Custom asset for macOS Apple Silicon + required: false + default: '' + customAssetMacOSIntel: + description: Custom asset for macOS Intel + required: false + default: '' + preview: + description: Enable preview mode (no uploads done) + required: false + default: '' +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os != 'macOS' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo '::error::steam-upload action requires a macOS-based runner.' + exit 2 + + - name: Check GitHub Event 🔬 + if: contains(fromJSON('["release", "workflow_dispatch", "schedule"]'), github.event_name) != true + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Check GitHub Event 🔬 + print "::error:steam-upload action can only be used with 'release', 'workflow-dispatch', or 'schedule' events." + exit 2 + + - name: Download Assets 📥 + id: asset-info + shell: zsh --no-rcs --errexit --pipefail {0} + env: + GH_TOKEN: ${{ inputs.workflowSecret }} + windows_custom_asset: ${{ steps.asset-info.outputs.windowsAssetUrl }} + macos_apple_custom_asset: ${{ steps.asset-info.outputs.macos_appleAssetUrl }} + macos_intel_custom_asset: ${{ steps.asset-info.outputs.macos_intelAssetUrl }} + run: | + : Download Assets 📥 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local root_dir="${PWD}" + local description + local is_prerelease + + case ${GITHUB_EVENT_NAME} { + release) + gh release download \ + --pattern '*macOS*.dmg' \ + --pattern '*Windows*' \ + --pattern '*.zip' \ + --clobber + + IFS=';' read -r description is_prerelease <<< \ + "$(gh release view --json tagName,isPrerelease --jq 'join(";")')" + ;; + workflow_dispatch) + if [[ '${{ inputs.tagName }}' =~ [0-9]+\.[0-9]+\.[0-9]+(-(rc|beta)[0-9]+)*$ ]] { + gh release download ${{ inputs.tagName }} \ + --pattern '*macOS*.dmg' \ + --pattern '*Windows*' \ + --pattern '*.zip' \ + --clobber + + description='${{ inputs.tagName }}' + read -r is_prerelease <<< \ + "$(gh release view ${{ inputs.tagName }} --json isPrerelease --jq '.isPrerelease')" + asset_names=(gh release view ${{ inputs.tagName }} --json assets \ + --jq '.assets[] | select(.name|test(".*(macos|Full-x64|windows).*")) | .name') + + local -A custom_assets=( + windows "Windows x64;${windows_custom_asset}" + macos_apple "macOS Apple;${macos_apple_custom_asset}" + macos_intel "macOS Intel;${macos_intel_custom_asset}" + ) + + local display_name + local url + mkdir -p custom_assets && pushd custom_assets + for platform (windows macos_apple macos_intel) { + IFS=';' read -r display_name url <<< "${custom_assets[${platform}]}" + if [[ ${url} ]] { + print "::group::Download of ${display_name} custom asset" + curl --location --silent --remote-name ${url} + + if [[ ! -f ${root_dir}/${url:t} ]] { + print "::warning::Custom asset for ${display_name} does not replace an existing release asset" + } else { + rm -rf -- ${root_dir}/${url:t} + } + mv ${url:t} ${root_dir} + print '::endgroup::' + } + } + popd + } else { + print "::error::Invalid tag name for non-release workflow run: '${{ inputs.tagName }}'." + exit 2 + } + ;; + schedule) + gh run download ${GITHUB_RUN_ID} \ + --pattern '*macos*' \ + --pattern '*windows*' + + local short_hash="${GITHUB_SHA:0:9}" + mv obs-studio-windows-x64-${short_hash}/obs-studio-*-windows-x64.zip \ + ${root_dir} + mv obs-studio-macos-arm64-${short_hash}/obs-studio-*-macos-apple.dmg \ + ${root_dir} + mv obs-studio-macos-intel-${short_hash}/obs-studio-*-macos-intel.dmg \ + ${root_dir} + + description="g${GITHUB_SHA}" + is_prerelease='false' + ;; + } + + print "description=${description}" >> $GITHUB_OUTPUT + print "is_prerelease=${is_prerelease}" >> $GITHUB_OUTPUT + + - name: Prepare Builds for Steam 🍜 + shell: zsh --no-rcs --errexit --pipefail --extendedglob {0} + run: | + : Prepare Builds for Steam 🍜 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local root_dir="${PWD}" + mkdir -p steam && pushd steam + + print '::group::Prepare Windows x64 assets' + mkdir -p steam-windows && pushd steam-windows + unzip ${root_dir}/(#i)obs-studio-*.zip + rm ${root_dir}/(#i)obs-studio-*.zip + + cp -r ${root_dir}/build-aux/steam/scripts_windows scripts + touch disable_updater + popd + print '::endgroup::' + + print '::group::Prepare macOS Apple assets' + mkdir -p steam-macos/arm64/OBS.app + hdiutil attach -noverify -readonly -noautoopen -mountpoint /Volumes/obs-studio-arm64 ${root_dir}/(#i)obs-studio-*-macos-apple.dmg + ditto /Volumes/obs-studio-arm64/OBS.app steam-macos/arm64/OBS.app + hdiutil unmount /Volumes/obs-studio-arm64 + rm ${root_dir}/(#i)obs-studio-*-macos-apple.dmg + print '::endgroup::' + + print '::group::Prepare macOS Intel assets' + mkdir -p steam-macos/x86_64/OBS.app + hdiutil attach -noverify -readonly -noautoopen -mountpoint /Volumes/obs-studio-x86_64 ${root_dir}/(#i)obs-studio-*-macos-intel.dmg + ditto /Volumes/obs-studio-x86_64/OBS.app steam-macos/x86_64/OBS.app + hdiutil unmount /Volumes/obs-studio-x86_64 + rm ${root_dir}/(#i)obs-studio-*-macos-intel.dmg + print '::endgroup::' + + cp ${root_dir}/build-aux/steam/scripts_macos/launch.sh steam-macos/launch.sh + + popd + + - name: Set Up steamcmd 🚂 + uses: CyberAndrii/setup-steamcmd@b786e0da44db3d817e66fa3910a9560cb28c9323 + + - name: Generate Steam auth code 🔐 + id: steam-totp + uses: CyberAndrii/steam-totp@c7f636bc64e77f1b901e0420b7890813141508ee + if: ${{ ! fromJSON(inputs.preview) }} + with: + shared_secret: ${{ inputs.steamSecret }} + + - name: Upload to Steam 📤 + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Upload to Steam 📤 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local root_dir="${PWD}" + local build_file='build.vdf' + local branch_name + + pushd steam + print '::group::Prepare Steam Build Script' + + case ${GITHUB_EVENT_NAME} { + schedule) branch_name='${{ inputs.nightlyBranch }}' ;; + release|workflow_dispatch) + if [[ '${{ steps.asset-info.outputs.is_prerelease }}' == 'true' ]] { + branch_name='${{ inputs.betaBranch }}' + } else { + branch_name='${{ inputs.stableBranch }}' + } + ;; + } + + sed "s/@@DESC@@/${branch_name}-${{ steps.asset-info.outputs.description }}/;s/@@BRANCH@@/${branch_name}/" \ + ${root_dir}/build-aux/steam/obs_build.vdf > ${build_file} + + print "Generated ${build_file}:\n$(<${build_file})" + print '::endgroup::' + + print '::group::Upload to Steam' + local preview='${{ inputs.preview }}' + + steamcmd \ + +login '${{ inputs.steamUser }}' '${{ inputs.steamPassword }}' '${{ steps.steam-totp.outputs.code }}' \ + +run_app_build ${preview:+-preview} ${build_file} \ + +quit + print '::endgroup' + popd + + - name: Upload to Steam (Playtest) 📤 + if: fromJSON(steps.asset-info.outputs.is_prerelease) + shell: zsh --no-rcs --errexit --pipefail {0} + run: | + : Upload to Steam (Playtest) 📤 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + local build_file='build_playtest.vdf' + local branch_name='${{ inputs.playtestBranch }}' + + pushd steam + print '::group::Prepare Steam Build Script' + + set "s/@@DESC@@/${branch_name}-${{ steps.asset-info.outputs.description }}/;s/@@BRANCH@@/${branch_name}" \ + ${root_dir}/build-aux/steam/obs_playtest_build.vdf > ${build_file} + + print "Generated ${build_file}:\n$(<${build_file})" + print '::endgroup::' + + print '::group::Upload to Steam' + local preview + if [[ '${{ inputs.preview }}' == 'true' ]] preview='-preview' + + steamcmd \ + +login '${{ inputs.steamUser }}' '${{ inputs.steamPassword }}' '${{ steps.steam-totp.outputs.code }}' \ + +run_app_build ${preview} ${build_file} \ + +quit + print '::endgroup' + popd + + - name: Upload Steam build logs + uses: actions/upload-artifact@v3 + with: + name: steam-build-logs + path: ${{ github.workspace }}/steam/build/*.log diff --git a/.github/scripts/.Aptfile b/.github/scripts/.Aptfile new file mode 100644 index 000000000..40a5f1159 --- /dev/null +++ b/.github/scripts/.Aptfile @@ -0,0 +1,7 @@ +package 'ccache' +package 'cmake' +package 'curl' +package 'git' +package 'jq' +package 'ninja-build', bin: 'ninja' +package 'pkg-config' diff --git a/CI/include/Brewfile b/.github/scripts/.Brewfile similarity index 68% rename from CI/include/Brewfile rename to .github/scripts/.Brewfile index 9f36a4a61..cdc51f13e 100644 --- a/CI/include/Brewfile +++ b/.github/scripts/.Brewfile @@ -1,4 +1,5 @@ -brew "cmake" brew "ccache" -brew "coreutils" +brew "cmake" +brew "git" +brew "jq" brew "xcbeautify" diff --git a/.github/scripts/.build.zsh b/.github/scripts/.build.zsh new file mode 100755 index 000000000..7670c880f --- /dev/null +++ b/.github/scripts/.build.zsh @@ -0,0 +1,324 @@ +#!/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 + +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 project_root=${SCRIPT_HOME:A:h:h} + local buildspec_file=${project_root}/buildspec.json + + fpath=(${SCRIPT_HOME}/utils.zsh ${fpath}) + autoload -Uz log_group log_info log_status 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.' + return 2 + } + + typeset -g -a skips=() + local -i verbosity=1 + local -r _version='1.0.0' + local -r -a _valid_targets=( + macos-x86_64 + macos-arm64 + linux-x86_64 + ) + local target + local config='RelWithDebInfo' + local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel) + local -i codesign=0 + + if [[ ${host_os} == linux ]] { + local -r -a _valid_generators=(Ninja 'Unix Makefiles') + local generator='Ninja' + local -r _usage_host=" +%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