Merge pull request #8881 from PatTheMav/ci-update

CI: Update GitHub Actions workflows with repository actions and updated build scripts
This commit is contained in:
Ryan Foster
2023-07-17 15:35:51 -04:00
committed by GitHub
104 changed files with 4532 additions and 3362 deletions
+118
View File
@@ -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
+57
View File
@@ -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
@@ -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
@@ -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::
+62
View File
@@ -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
+114
View File
@@ -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
@@ -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
@@ -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::
}
@@ -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::
}
@@ -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::
}
@@ -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 <commits@obsproject.com>'
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
@@ -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
}
+213
View File
@@ -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}
}
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="no"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pCustomTitle" select="/rss/channel/title" />
<xsl:param name="pCustomLink" select="/rss/channel/link" />
<xsl:param name="pSparkleUrl" select="''" />
<xsl:param name="pDeltaUrl" select="''" />
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/rss/channel/title" />
<xsl:template match="/rss/channel/link" />
<xsl:template match="/rss/channel">
<xsl:copy>
<xsl:element name="title"><xsl:value-of select="$pCustomTitle" /></xsl:element>
<xsl:element name="link"><xsl:value-of select="$pCustomLink" /></xsl:element>
<xsl:apply-templates select="@* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/rss/channel/item/sparkle:deltas/enclosure/@url">
<xsl:attribute name="url">
<xsl:choose>
<xsl:when test="starts-with(., $pDeltaUrl)">
<xsl:value-of select="." />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pDeltaUrl" />
<xsl:value-of select="substring-after(., $pSparkleUrl)" />
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</xsl:template>
<xsl:template match="/rss/channel/item/sparkle:fullReleaseNotesLink">
<xsl:element name="sparkle:releaseNotesLink"><xsl:apply-templates select="@* | node()" /></xsl:element>
</xsl:template>
</xsl:stylesheet>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="no"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/rss/channel/item[sparkle:channel[text()!='stable']]" />
<xsl:template match="/rss/channel/item/sparkle:channel" />
<xsl:template match="/rss/channel/item/sparkle:deltas" />
</xsl:stylesheet>
+286
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
package 'ccache'
package 'cmake'
package 'curl'
package 'git'
package 'jq'
package 'ninja-build', bin: 'ninja'
package 'pkg-config'
@@ -1,4 +1,5 @@
brew "cmake"
brew "ccache"
brew "coreutils"
brew "cmake"
brew "git"
brew "jq"
brew "xcbeautify"
+324
View File
@@ -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 <option> [<options>]
%BOptions%b:
%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
%B--print-config%b Print composed CMake configuration parameters
%B--skip-[all|build|deps]%b Skip all|building OBS|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--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
${_usage_host:-}"
local -a args
while (( # )) {
case ${1} {
-t|--target|--generator|-c|--config)
if (( # == 1 )) || [[ ${2:0:1} == '-' ]] {
log_error "Missing value for option %B${1}%b"
log_output ${_usage}
exit 2
}
;;
}
case ${1} {
--)
shift
args+=($@)
break
;;
-t|--target)
if (( ! ${_valid_targets[(Ie)${2}]} )) {
log_error "Invalid value %B${2}%b for option %B${1}%b"
log_output ${_usage}
exit 2
}
target=${2}
shift 2
;;
-c|--config)
if (( ! ${_valid_configs[(Ie)${2}]} )) {
log_error "Invalid value %B${2}%b for option %B${1}%b"
log_output ${_usage}
exit 2
}
config=${2}
shift 2
;;
-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 ;;
--generator)
if [[ ${host_os} == linux ]] {
if (( ! ${_valid_generators[(Ie)${2}]} )) {
log_error "Invalid value %B${2}%b for option %B${1}%b"
log_output ${_usage}
exit 2
}
generator=${2}
}
shift 2
;;
--print-config) _print_config=1; skips+=(unpack deps); shift ;;
--skip-*)
local _skip="${${(s:-:)1}[-1]}"
local _check=(all deps build)
(( ${_check[(Ie)${_skip}]} )) || log_warning "Invalid skip mode %B${_skip}%b supplied"
skips+=(${_skip})
shift
;;
*) log_error "Unknown option: %B${1}%b"; log_output ${_usage}; exit 2 ;;
}
}
: "${target:="${host_os}-${CPUTYPE}"}"
set -- ${(@)args}
set_loglevel ${verbosity}
if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
check_${host_os}
}
setup_ccache
if [[ ${host_os} == linux ]] {
autoload -Uz setup_linux && setup_linux
}
local product_name
read -r product_name <<< \
"$(jq -r '.name' ${buildspec_file})"
pushd ${project_root}
if (( ! (${skips[(Ie)all]} + ${skips[(Ie)build]}) )) {
log_group "Configuring ${product_name}..."
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) ;;
}
case ${target} {
macos-*)
cmake_args+=(
--preset "macos${CI:+-ci}"
-DCMAKE_OSX_ARCHITECTURES:STRING=${target##*-}
)
if (( ${+CI} )) typeset -gx NSUnbufferedIO=YES
if (( codesign )) {
autoload -Uz read_codesign_team && read_codesign_team
if [[ -z ${CODESIGN_TEAM} ]] {
autoload -Uz read_codesign && read_codesign
}
}
cmake_args+=(
-DOBS_CODESIGN_TEAM:STRING=${CODESIGN_TEAM:-}
-DOBS_CODESIGN_IDENTITY:STRING=${CODESIGN_IDENT:--}
)
;;
linux-*)
cmake_args+=(
-S ${PWD} -B "build_${target##*-}"
-G "${generator}"
-DCMAKE_BUILD_TYPE:STRING=${config}
-DCEF_ROOT_DIR:PATH="${project_root}/.deps/cef_binary_${CEF_VERSION}_${target//-/_}"
)
local cmake_version
read -r _ _ cmake_version <<< "$(cmake --version)"
cmake_build_args+=("build_${target##*-}" --config ${config})
if [[ ${generator} == 'Unix Makefiles' ]] {
cmake_build_args+=(--parallel $(( $(nproc) + 1 )))
} else {
cmake_build_args+=(--parallel)
}
cmake_args+=(
-DENABLE_AJA:BOOL=OFF
-DENABLE_WEBRTC:BOOL=OFF
)
if (( ! UBUNTU_2210_OR_LATER )) cmake_args+=(-DENABLE_NEW_MPEGTS_OUTPUT:BOOL=OFF)
cmake_install_args+=(build_${target##*-} --prefix ${project_root}/build_${target##*-}/install/${config})
;;
}
if (( _print_config )) { log_output "CMake configuration: ${cmake_args}"; exit 0 }
log_debug "Attempting to configure with CMake arguments: ${cmake_args}"
cmake -S ${project_root} ${cmake_args}
log_group "Building ${product_name}..."
if [[ ${host_os} == macos ]] {
local -a build_args=(
ONLY_ACTIVE_ARCH=NO
-project obs-studio.xcodeproj
-target obs-studio
-destination "generic/platform=macOS,name=Any Mac"
-configuration ${config}
-parallelizeTargets
-hideShellScriptEnvironment
build
)
local -a archive_args=(
ONLY_ACTIVE_ARCH=NO
-project obs-studio.xcodeproj
-scheme obs-studio
-destination "generic/platform=macOS,name=Any Mac"
-archivePath obs-studio.xcarchive
-parallelizeTargets
-hideShellScriptEnvironment
archive
)
local -a export_args=(
-exportArchive
-archivePath obs-studio.xcarchive
-exportOptionsPlist exportOptions.plist
-exportPath ${project_root}/build_macos
)
autoload -Uz run_xcodebuild
pushd build_macos
if (( ${+CI} )) && [[ ${GITHUB_EVENT_NAME} == push && ${GITHUB_REF_NAME} =~ [0-9]+.[0-9]+.[0-9]+(-(rc|beta).+)? ]] {
run_xcodebuild ${archive_args}
run_xcodebuild ${export_args}
} else {
run_xcodebuild ${build_args}
rm -rf OBS.app
mkdir OBS.app
ditto UI/${config}/OBS.app OBS.app
}
popd
} else {
cmake ${cmake_build_args}
log_group "Installing ${product_name}..."
if (( _loglevel > 1 )) cmake_install_args+=(--verbose)
cmake ${cmake_install_args}
popd
}
log_group
}
}
build ${@}
+296
View File
@@ -0,0 +1,296 @@
#!/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
}
package() {
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 set_loglevel log_info log_error log_output check_${host_os}
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
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)"
}
local -r _usage="
Usage: %B${functrace[1]%:*}%b <option> [<options>]
%BOptions%b:
%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
%B-p | --package%b Create package installer (macOS only)
%B--skip-deps%b Skip 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--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
${_usage_host:-}"
local -a args
while (( # )) {
case ${1} {
-t|--target|-c|--config)
if (( # == 1 )) || [[ ${2:0:1} == '-' ]] {
log_error "Missing value for option %B${1}%b"
log_output ${_usage}
exit 2
}
;;
}
case ${1} {
--)
shift
args+=($@)
break
;;
-t|--target)
if (( ! ${_valid_targets[(Ie)${2}]} )) {
log_error "Invalid value %B${2}%b for option %B${1}%b"
log_output ${_usage}
exit 2
}
target=${2}
shift 2
;;
-c|--config)
if (( ! ${_valid_configs[(Ie)${2}]} )) {
log_error "Invalid value %B${2}%b for option %B${1}%b"
log_output ${_usage}
exit 2
}
config=${2}
shift 2
;;
-s|--codesign) CODESIGN=1; shift ;;
-n|--notarize) NOTARIZE=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 ;;
*) log_error "Unknown option: %B${1}%b"; log_output ${_usage}; exit 2 ;;
}
}
: "${target:="${host_os}-${CPUTYPE}"}"
set -- ${(@)args}
set_loglevel ${verbosity}
if (( ! skip_deps )) {
check_${host_os}
}
local product_name
read -r product_name <<< \
"$(jq -r '.name' ${buildspec_file})"
local commit_version='0.0.0'
local commit_distance='0'
local commit_hash
if [[ -d ${project_root}/.git ]] {
local git_description="$(git describe --tags --long)"
commit_version="${${git_description%-*}%-*}"
commit_hash="${git_description##*-g}"
commit_distance="${${git_description%-*}##*-}"
}
local output_name
if (( commit_distance > 0 )) {
output_name="obs-studio-${commit_version}-${commit_hash}"
} else {
output_name="obs-studio-${commit_version}"
}
if [[ ${host_os} == macos ]] {
autoload -Uz read_codesign read_codesign_pass log_warning log_group
if [[ ! -d build_macos/OBS.app ]] {
log_error 'No application bundle found. Run the build script to create a valid application bundle.'
return 0
}
local -A arch_names=(x86_64 Intel arm64 Apple)
output_name="${output_name}-macos-${(L)arch_names[${target##*-}]}"
local volume_name
if (( commit_distance > 0 )) {
volume_name="OBS Studio ${commit_version}-${commit_hash} (${arch_names[${target##*-}]})"
} else {
volume_name="OBS Studio ${commit_version} (${arch_names[${target##*-}]})"
}
local _tarflags='cJf'
if (( _loglevel > 1 || ${+CI} )) _tarflags="v${_tarflags}"
if (( package )) {
pushd build_macos
mkdir -p obs-studio/.background
cp ${project_root}/cmake/macos/resources/background.tiff obs-studio/.background/
cp ${project_root}/cmake/macos/resources/AppIcon.icns obs-studio/.VolumeIcon.icns
ln -s /Applications obs-studio/Applications
mkdir -p obs-studio/OBS.app
ditto OBS.app obs-studio/OBS.app
local -i _status=0
autoload -Uz create_diskimage
create_diskimage obs-studio ${volume_name} ${output_name} || _status=1
rm -r obs-studio
if (( _status )) {
log_error "Disk image creation failed."
return 2
}
if (( codesign )) { autoload -Uz read_codesign && read_codesign }
codesign --sign "${CODESIGN_IDENT:--}" ${output_name}.dmg
if (( codesign && notarize )) {
autoload -Uz read_codesign_pass && read_codesign_pass
xcrun notarytool submit "${output_name}".dmg --keychain-profile "OBS-Codesign-Password" --wait
local -i _status=0
xcrun stapler staple ${output_name}.dmg || _status=1
if (( _status )) {
log_error "Notarization failed. Use 'xcrun notarytool log <submission ID>' to check errors."
return 2
}
}
popd
} else {
log_group "Archiving obs-studio..."
pushd build_macos
XZ_OPT=-T0 tar "-${_tarflags}" ${output_name}.tar.xz OBS.app
popd
}
if [[ ${config} == Release ]] {
log_group "Archiving debug symbols..."
mkdir -p build_macos/dSYMs
pushd build_macos/dSYMs
rm -rf -- *.dSYM(N)
cp -pR ${PWD:h}/**/*.dSYM .
XZ_OPT=-T0 tar "-${_tarflags}" ${output_name}-dSYMs.tar.xz -- *
mv ${output_name}-dSYMs.tar.xz ${PWD:h}
popd
}
log_group
} elif [[ ${host_os} == linux ]] {
local -a cmake_args=()
if (( _loglevel > 1 )) cmake_args+=(--verbose)
if (( package )) {
log_group "Packaging obs-studio..."
pushd ${project_root}
cmake --build build_${target##*-} --config ${config} -t package ${cmake_args}
output_name="${output_name}-${target##*-}-linux-gnu"
pushd ${project_root}/build_${target##*-}
local -a files=(obs-studio-*-Linux*.(ddeb|deb))
for file (${files}) {
mv ${file} ${file//obs-studio-*-Linux/${output_name}}
}
popd
popd
} else {
log_group "Archiving obs-studio..."
output_name="${output_name}-${target##*-}-linux-gnu"
local _tarflags='cJf'
if (( _loglevel > 1 || ${+CI} )) _tarflags="v${_tarflags}"
pushd ${project_root}/build_${target##*-}/install/${config}
XZ_OPT=-T0 tar "-${_tarflags}" ${project_root}/build_${target##*-}/${output_name}.tar.xz (bin|lib|share)
popd
}
log_group
}
}
package ${@}
+1
View File
@@ -0,0 +1 @@
./.build.zsh
+1
View File
@@ -0,0 +1 @@
./.build.zsh
+1
View File
@@ -0,0 +1 @@
./.package.zsh
+1
View File
@@ -0,0 +1 @@
./.package.zsh
@@ -0,0 +1,131 @@
import argparse
import json
import logging
import os
import sys
from typing import Any
from json_source_map import calculate
from json_source_map.errors import InvalidInputError
from jsonschema import Draft7Validator
def discover_schema_file(filename: str) -> tuple[str | None, Any]:
logger = logging.getLogger()
with open(filename) as json_file:
json_data = json.load(json_file)
schema_filename = json_data.get("$schema", None)
if not schema_filename:
logger.info(f"️ ${filename} has no schema definition")
return (None, None)
schema_file = os.path.join(os.path.dirname(filename), schema_filename)
with open(schema_file) as schema_file:
schema_data = json.load(schema_file)
return (str(schema_file), schema_data)
def validate_json_files(
schema_data: dict[Any, Any], json_file_name: str
) -> list[dict[str, str]]:
logger = logging.getLogger()
with open(json_file_name) as json_file:
text_data = json_file.read()
json_data = json.loads(text_data)
source_map = calculate(text_data)
validator = Draft7Validator(schema_data)
violations = []
for violation in sorted(validator.iter_errors(json_data), key=str):
logger.info(
f"⚠️ Schema violation in file '{json_file_name}':\n{violation}\n----\n"
)
if len(violation.absolute_path):
error_path = "/".join(
str(path_element) for path_element in violation.absolute_path
)
error_entry = source_map["/{}".format(error_path)]
violation_data = {
"file": json_file_name,
"title": "Validation Error",
"message": violation.message,
"annotation_level": "failure",
"start_line": error_entry.value_start.line + 1,
"end_line": error_entry.value_end.line + 1,
}
violations.append(violation_data)
return violations
def main() -> int:
parser = argparse.ArgumentParser(
description="Validate JSON files by schema definition"
)
parser.add_argument(
"json_files", metavar="FILE", type=str, nargs="+", help="JSON file to validate"
)
parser.add_argument(
"--loglevel", type=str, help="Set log level", default="WARNING", required=False
)
arguments = parser.parse_args()
logging.basicConfig(level=arguments.loglevel, format="%(levelname)s - %(message)s")
logger = logging.getLogger()
schema_mappings = {}
for json_file in arguments.json_files:
try:
(schema_file, schema_data) = discover_schema_file(json_file)
except OSError as e:
logger.error(f"❌ Failed to discover schema for file '{json_file}': {e}")
return 2
if schema_file and schema_file not in schema_mappings.keys():
schema_mappings.update(
{schema_file: {"schema_data": schema_data, "files": set()}}
)
schema_mappings[schema_file]["files"].add(json_file)
validation_errors = []
for schema_entry in schema_mappings.values():
for json_file in schema_entry["files"]:
try:
new_errors = validate_json_files(schema_entry["schema_data"], json_file)
except (InvalidInputError, OSError) as e:
logger.error(
f"❌ Failed to create JSON source map for file '{json_file}': {e}"
)
return 2
[validation_errors.append(error) for error in new_errors]
if validation_errors:
try:
with open("validation_errors.json", "w") as results_file:
json.dump(validation_errors, results_file)
except OSError as e:
logger.error(f"❌ Failed to write validation results file: {e}")
return 2
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -14,14 +14,14 @@ from collections import defaultdict
MINIMUM_PURGE_AGE = 9.75 * 24 * 60 * 60 # slightly less than 10 days
TIMEOUT = 10
SKIPPED_SERVICES = {'YouNow', 'SHOWROOM', 'Dacast'}
SERVICES_FILE = 'plugins/rtmp-services/data/services.json'
PACKAGE_FILE = 'plugins/rtmp-services/data/package.json'
CACHE_FILE = 'other/timestamps.json'
GITHUB_OUTPUT_FILE = os.environ.get('GITHUB_OUTPUT', None)
SKIPPED_SERVICES = {"YouNow", "SHOWROOM", "Dacast"}
SERVICES_FILE = "plugins/rtmp-services/data/services.json"
PACKAGE_FILE = "plugins/rtmp-services/data/package.json"
CACHE_FILE = "other/timestamps.json"
GITHUB_OUTPUT_FILE = os.environ.get("GITHUB_OUTPUT", None)
DO_NOT_PING = {'jp9000'}
PR_MESSAGE = '''This is an automatically created pull request to remove unresponsive servers and services.
DO_NOT_PING = {"jp9000"}
PR_MESSAGE = """This is an automatically created pull request to remove unresponsive servers and services.
| Service | Action Taken | Author(s) |
| ------- | ------------ | --------- |
@@ -29,10 +29,10 @@ PR_MESSAGE = '''This is an automatically created pull request to remove unrespon
If you are not responsible for an affected service and want to be excluded from future pings please let us know.
Created by workflow run: https://github.com/{repository}/actions/runs/{run_id}'''
Created by workflow run: https://github.com/{repository}/actions/runs/{run_id}"""
# GQL is great isn't it
GQL_QUERY = '''{
GQL_QUERY = """{
repositoryOwner(login: "obsproject") {
repository(name: "obs-studio") {
object(expression: "master") {
@@ -54,7 +54,7 @@ GQL_QUERY = '''{
}
}
}
}'''
}"""
context = ssl.create_default_context()
@@ -64,7 +64,7 @@ def check_ftl_server(hostname) -> bool:
try:
socket.getaddrinfo(hostname, 8084, proto=socket.IPPROTO_UDP)
except socket.gaierror as e:
print(f'⚠️ Could not resolve hostname for server: {hostname} (Exception: {e})')
print(f"⚠️ Could not resolve hostname for server: {hostname} (Exception: {e})")
return False
else:
return True
@@ -75,9 +75,9 @@ def check_hls_server(uri) -> bool:
try:
r = requests.post(uri, timeout=TIMEOUT)
if r.status_code >= 500 or r.status_code == 404:
raise Exception(f'Server responded with {r.status_code}')
raise Exception(f"Server responded with {r.status_code}")
except Exception as e:
print(f'⚠️ Could not connect to HLS server: {uri} (Exception: {e})')
print(f"⚠️ Could not connect to HLS server: {uri} (Exception: {e})")
return False
else:
return True
@@ -86,21 +86,21 @@ def check_hls_server(uri) -> bool:
def check_rtmp_server(uri) -> bool:
"""Try connecting and sending a RTMP handshake (with SSL if necessary)"""
parsed = urlparse(uri)
hostname, port = parsed.netloc.partition(':')[::2]
hostname, port = parsed.netloc.partition(":")[::2]
if port:
port = int(port)
elif parsed.scheme == 'rtmps':
elif parsed.scheme == "rtmps":
port = 443
else:
port = 1935
try:
recv = b''
recv = b""
with socket.create_connection((hostname, port), timeout=TIMEOUT) as sock:
# RTMP handshake is \x03 + 4 bytes time (can be 0) + 4 zero bytes + 1528 bytes random
handshake = b'\x03\x00\x00\x00\x00\x00\x00\x00\x00' + randbytes(1528)
if parsed.scheme == 'rtmps':
handshake = b"\x03\x00\x00\x00\x00\x00\x00\x00\x00" + randbytes(1528)
if parsed.scheme == "rtmps":
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
ssock.sendall(handshake)
while True:
@@ -117,9 +117,9 @@ def check_rtmp_server(uri) -> bool:
break
if len(recv) < 1536 or recv[0] != 3:
raise ValueError('Invalid RTMP handshake received from server')
raise ValueError("Invalid RTMP handshake received from server")
except Exception as e:
print(f'⚠️ Connection to server failed: {uri} (Exception: {e})')
print(f"⚠️ Connection to server failed: {uri} (Exception: {e})")
return False
else:
return True
@@ -127,34 +127,40 @@ def check_rtmp_server(uri) -> bool:
def get_last_artifact():
s = requests.session()
s.headers['Authorization'] = f'Bearer {os.environ["GITHUB_TOKEN"]}'
s.headers["Authorization"] = f'Bearer {os.environ["GITHUB_TOKEN"]}'
run_id = os.environ['WORKFLOW_RUN_ID']
repo = os.environ['REPOSITORY']
run_id = os.environ["WORKFLOW_RUN_ID"]
repo = os.environ["REPOSITORY"]
# fetch run first, get workflow id from there to get workflow runs
r = s.get(f'https://api.github.com/repos/{repo}/actions/runs/{run_id}')
r = s.get(f"https://api.github.com/repos/{repo}/actions/runs/{run_id}")
r.raise_for_status()
workflow_id = r.json()['workflow_id']
workflow_id = r.json()["workflow_id"]
r = s.get(
f'https://api.github.com/repos/{repo}/actions/workflows/{workflow_id}/runs',
params=dict(per_page=1, status='completed', branch='master', conclusion='success', event='schedule'),
f"https://api.github.com/repos/{repo}/actions/workflows/{workflow_id}/runs",
params=dict(
per_page=1,
status="completed",
branch="master",
conclusion="success",
event="schedule",
),
)
r.raise_for_status()
runs = r.json()
if not runs['workflow_runs']:
raise ValueError('No completed workflow runs found')
if not runs["workflow_runs"]:
raise ValueError("No completed workflow runs found")
r = s.get(runs['workflow_runs'][0]['artifacts_url'])
r = s.get(runs["workflow_runs"][0]["artifacts_url"])
r.raise_for_status()
for artifact in r.json()['artifacts']:
if artifact['name'] == 'timestamps':
artifact_url = artifact['archive_download_url']
for artifact in r.json()["artifacts"]:
if artifact["name"] == "timestamps":
artifact_url = artifact["archive_download_url"]
break
else:
raise ValueError('No previous artifact found.')
raise ValueError("No previous artifact found.")
r = s.get(artifact_url)
r.raise_for_status()
@@ -163,7 +169,7 @@ def get_last_artifact():
with zipfile.ZipFile(zip_data) as zip_ref:
for info in zip_ref.infolist():
if info.filename == 'timestamps.json':
if info.filename == "timestamps.json":
return json.loads(zip_ref.read(info.filename))
@@ -173,18 +179,22 @@ def find_people_to_blame(raw_services: str, servers: list[tuple[str, str]]) -> d
# Fetch Blame data from github
s = requests.session()
s.headers['Authorization'] = f'Bearer {os.environ["GITHUB_TOKEN"]}'
s.headers["Authorization"] = f'Bearer {os.environ["GITHUB_TOKEN"]}'
r = s.post('https://api.github.com/graphql', json=dict(query=GQL_QUERY, variables=dict()))
r = s.post(
"https://api.github.com/graphql", json=dict(query=GQL_QUERY, variables=dict())
)
r.raise_for_status()
j = r.json()
# The file is only ~2600 lines so this isn't too crazy and makes the lookup very easy
line_author = dict()
for blame in j['data']['repositoryOwner']['repository']['object']['blame']['ranges']:
for i in range(blame['startingLine'] - 1, blame['endingLine']):
if user := blame['commit']['author']['user']:
line_author[i] = user['login']
for blame in j["data"]["repositoryOwner"]["repository"]["object"]["blame"][
"ranges"
]:
for i in range(blame["startingLine"] - 1, blame["endingLine"]):
if user := blame["commit"]["author"]["user"]:
line_author[i] = user["login"]
service_authors = defaultdict(set)
for i, line in enumerate(raw_services.splitlines()):
@@ -203,40 +213,42 @@ def set_output(name, value):
return
try:
with open(GITHUB_OUTPUT_FILE, 'a', encoding='utf-8', newline='\n') as f:
f.write(f'{name}={value}\n')
with open(GITHUB_OUTPUT_FILE, "a", encoding="utf-8", newline="\n") as f:
f.write(f"{name}={value}\n")
except Exception as e:
print(f'Writing to github output files failed: {e!r}')
print(f"Writing to github output files failed: {e!r}")
def main():
try:
with open(SERVICES_FILE, encoding='utf-8') as services_file:
with open(SERVICES_FILE, encoding="utf-8") as services_file:
raw_services = services_file.read()
services = json.loads(raw_services)
with open(PACKAGE_FILE, encoding='utf-8') as package_file:
with open(PACKAGE_FILE, encoding="utf-8") as package_file:
package = json.load(package_file)
except OSError as e:
print(f'❌ Could not open services/package file: {e}')
print(f"❌ Could not open services/package file: {e}")
return 1
# attempt to load last check result cache
try:
with open(CACHE_FILE, encoding='utf-8') as check_file:
with open(CACHE_FILE, encoding="utf-8") as check_file:
fail_timestamps = json.load(check_file)
except OSError as e:
# cache might be evicted or not exist yet, so this is non-fatal
print(f'⚠️ Could not read cache file, trying to get last artifact (Exception: {e})')
print(
f"⚠️ Could not read cache file, trying to get last artifact (Exception: {e})"
)
try:
fail_timestamps = get_last_artifact()
except Exception as e:
print(f'⚠️ Could not fetch cache file, starting fresh. (Exception: {e})')
print(f"⚠️ Could not fetch cache file, starting fresh. (Exception: {e})")
fail_timestamps = dict()
else:
print('Fetched cache file from last run artifact.')
print("Fetched cache file from last run artifact.")
else:
print('Successfully loaded cache file:', CACHE_FILE)
print("Successfully loaded cache file:", CACHE_FILE)
start_time = int(time.time())
affected_services = dict()
@@ -244,120 +256,126 @@ def main():
# create temporary new list
new_services = services.copy()
new_services['services'] = []
new_services["services"] = []
for service in services['services']:
for service in services["services"]:
# skip services that do custom stuff that we can't easily check
if service['name'] in SKIPPED_SERVICES:
new_services['services'].append(service)
if service["name"] in SKIPPED_SERVICES:
new_services["services"].append(service)
continue
service_type = service.get('recommended', {}).get('output', 'rtmp_output')
if service_type not in {'rtmp_output', 'ffmpeg_hls_muxer', 'ftl_output'}:
print('Unknown service type:', service_type)
new_services['services'].append(service)
service_type = service.get("recommended", {}).get("output", "rtmp_output")
if service_type not in {"rtmp_output", "ffmpeg_hls_muxer", "ftl_output"}:
print("Unknown service type:", service_type)
new_services["services"].append(service)
continue
# create a copy to mess with
new_service = service.copy()
new_service['servers'] = []
new_service["servers"] = []
# run checks for all the servers, and store results in timestamp cache
for server in service['servers']:
if service_type == 'ftl_output':
is_ok = check_ftl_server(server['url'])
elif service_type == 'ffmpeg_hls_muxer':
is_ok = check_hls_server(server['url'])
for server in service["servers"]:
if service_type == "ftl_output":
is_ok = check_ftl_server(server["url"])
elif service_type == "ffmpeg_hls_muxer":
is_ok = check_hls_server(server["url"])
else: # rtmp
is_ok = check_rtmp_server(server['url'])
is_ok = check_rtmp_server(server["url"])
if not is_ok:
if ts := fail_timestamps.get(server['url'], None):
if ts := fail_timestamps.get(server["url"], None):
if (delta := start_time - ts) >= MINIMUM_PURGE_AGE:
print(
f'🗑️ Purging server "{server["url"]}", it has been '
f'unresponsive for {round(delta/60/60/24)} days.'
f"unresponsive for {round(delta/60/60/24)} days."
)
removed_servers.append((server['url'], service['name']))
removed_servers.append((server["url"], service["name"]))
# continuing here means not adding it to the new list, thus dropping it
continue
else:
fail_timestamps[server['url']] = start_time
elif is_ok and server['url'] in fail_timestamps:
fail_timestamps[server["url"]] = start_time
elif is_ok and server["url"] in fail_timestamps:
# remove timestamp of failed check if server is back
delta = start_time - fail_timestamps[server['url']]
print(f'💡 Server "{server["url"]}" is back after {round(delta/60/60/24)} days!')
del fail_timestamps[server['url']]
delta = start_time - fail_timestamps[server["url"]]
print(
f'💡 Server "{server["url"]}" is back after {round(delta/60/60/24)} days!'
)
del fail_timestamps[server["url"]]
new_service['servers'].append(server)
new_service["servers"].append(server)
if (diff := len(service['servers']) - len(new_service['servers'])) > 0:
if (diff := len(service["servers"]) - len(new_service["servers"])) > 0:
print(f'️ Removed {diff} server(s) from {service["name"]}')
affected_services[service['name']] = f'{diff} servers removed'
affected_services[service["name"]] = f"{diff} servers removed"
# remove services with no valid servers
if not new_service['servers']:
if not new_service["servers"]:
print(f'💀 Service "{service["name"]}" has no valid servers left, removing!')
affected_services[service['name']] = f'Service removed'
affected_services[service["name"]] = f"Service removed"
continue
new_services['services'].append(new_service)
new_services["services"].append(new_service)
# write cache file
try:
os.makedirs('other', exist_ok=True)
with open(CACHE_FILE, 'w', encoding='utf-8') as cache_file:
os.makedirs("other", exist_ok=True)
with open(CACHE_FILE, "w", encoding="utf-8") as cache_file:
json.dump(fail_timestamps, cache_file)
except OSError as e:
print(f'❌ Could not write cache file: {e}')
print(f"❌ Could not write cache file: {e}")
return 1
else:
print('Successfully wrote cache file:', CACHE_FILE)
print("Successfully wrote cache file:", CACHE_FILE)
if removed_servers:
# increment package version and save that as well
package['version'] += 1
package['files'][0]['version'] += 1
package["version"] += 1
package["files"][0]["version"] += 1
try:
with open(SERVICES_FILE, 'w', encoding='utf-8') as services_file:
with open(SERVICES_FILE, "w", encoding="utf-8") as services_file:
json.dump(new_services, services_file, indent=4, ensure_ascii=False)
services_file.write('\n')
services_file.write("\n")
with open(PACKAGE_FILE, 'w', encoding='utf-8') as package_file:
with open(PACKAGE_FILE, "w", encoding="utf-8") as package_file:
json.dump(package, package_file, indent=4)
package_file.write('\n')
package_file.write("\n")
except OSError as e:
print(f'❌ Could not write services/package file: {e}')
print(f"❌ Could not write services/package file: {e}")
return 1
else:
print(f'Successfully wrote services/package files:\n- {SERVICES_FILE}\n- {PACKAGE_FILE}')
print(
f"Successfully wrote services/package files:\n- {SERVICES_FILE}\n- {PACKAGE_FILE}"
)
# try to find authors to ping, this is optional and is allowed to fail
try:
service_authors = find_people_to_blame(raw_services, removed_servers)
except Exception as e:
print(f'⚠ Could not fetch blame for some reason: {e}')
print(f"⚠ Could not fetch blame for some reason: {e}")
service_authors = dict()
# set GitHub outputs
set_output('make_pr', 'true')
set_output("make_pr", "true")
msg = PR_MESSAGE.format(
repository=os.environ['REPOSITORY'],
run_id=os.environ['WORKFLOW_RUN_ID'],
table='\n'.join(
'| {name} | {action} | {authors} |'.format(
name=name.replace('|', '\\|'),
repository=os.environ["REPOSITORY"],
run_id=os.environ["WORKFLOW_RUN_ID"],
table="\n".join(
"| {name} | {action} | {authors} |".format(
name=name.replace("|", "\\|"),
action=action,
authors=', '.join(f'@{author}' for author in sorted(service_authors.get(name, []))),
authors=", ".join(
f"@{author}" for author in sorted(service_authors.get(name, []))
),
)
for name, action in sorted(affected_services.items())
),
)
set_output('pr_message', json.dumps(msg))
set_output("pr_message", json.dumps(msg))
else:
set_output('make_pr', 'false')
set_output("make_pr", "false")
if __name__ == '__main__':
if __name__ == "__main__":
sys.exit(main())
+61
View File
@@ -0,0 +1,61 @@
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]} )) {
log_error 'No apt-get command found. Please install apt'
return 2
} else {
log_debug "Apt-get located at ${commands[apt-get]}"
}
local -a dependencies=("${(fA)$(<${SCRIPT_HOME}/.Aptfile)}")
local -a install_list
local binary
sudo apt-get update -qq
for dependency (${dependencies}) {
local -a tokens=(${=dependency//(,|:|\')/})
if [[ ! ${tokens[1]} == package ]] continue
if [[ ${#tokens} -gt 2 && ${tokens[3]} == bin ]] {
binary=${tokens[4]}
} else {
binary=${tokens[2]}
}
if (( ! ${+commands[${binary}]} )) install_list+=(${tokens[2]})
}
log_debug "List of dependencies to install: ${install_list}"
if (( #install_list )) {
if (( ! ${+CI} )) log_warning 'Dependency installation via apt may require elevated privileges'
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
+22
View File
@@ -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
@@ -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
View File
@@ -0,0 +1,3 @@
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
if (( _loglevel > 2 )) print -PR -e "${CI:+::debug::}%F{220}DEBUG: ${@}%f"
+3
View File
@@ -0,0 +1,3 @@
local icon=' ✖︎ '
print -u2 -PR "${CI:+::error::}%F{1} ${icon} %f ${@}"
+16
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,7 @@
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
if (( _loglevel > 0 )) {
local icon=''
print -PR " ${(r:5:)icon} ${@}"
}
+7
View File
@@ -0,0 +1,7 @@
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
if (( _loglevel > 0 )) {
local icon=' >'
print -PR "%F{2} ${(r:5:)icon}%f ${@}"
}
+7
View File
@@ -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
View File
@@ -0,0 +1 @@
[[ -n ${1} ]] && mkdir -p ${1} && builtin cd ${1}
+9
View File
@@ -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')
@@ -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}"
@@ -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): '
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
}
+187
View File
@@ -0,0 +1,187 @@
autoload -Uz log_group log_error log_status log_info log_debug
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 --continue-at -)
} else {
curl_opts+=(--show-error --silent)
}
curl_opts+=(--location -O ${@})
pushd ${project_root}
typeset -g QT_VERSION
read -r QT_VERSION <<< \
"$(jq -r --arg target "${target}" \
'.platformConfig[$target] | { qtVersion } | join(" ")' \
${buildspec_file})"
if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
log_group 'Installing obs-studio build dependencies...'
mkdir -p ${project_root}/.deps
local deps_version
local deps_baseurl
local deps_label
local deps_hash
IFS=';' read -r deps_version deps_baseurl deps_label deps_hash <<< \
"$(jq -r --arg target "${target}" \
'.dependencies["cef"] | {version, baseUrl, "label", "hash": .hashes[$target]} | join(";")' \
${buildspec_file})"
if (( ! deps_version )) {
log_error 'No valid cef spec found in buildspec.json.'
return 2
}
log_group 'Setting up pre-built Chromium Embedded Framework...'
pushd ${project_root}/.deps
local _filename="cef_binary_${deps_version}_${target//-/_}.tar.xz"
local _url=${deps_baseurl}/${_filename}
local _target="cef_binary_${deps_version}_${target//-/_}"
typeset -g CEF_VERSION=${deps_version}
log_status 'Checking for available wrapper library...'
local -i _skip=0
if [[ -f ${_target}/build/libcef_dll_wrapper/libcef_dll_wrapper.a ]] {
_skip=1
}
if ! (( _skip )) {
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."
mkdir -p ${_target} && pushd ${_target}
XZ_OPT=-T0 tar --strip-components 1 -xJf ../${_filename} && log_status "${deps_label} extracted."
if [[ ! -f build/libcef_dll_wrapper/libcef_dll_wrapper.a ]] {
log_group "Configuring CEF wrapper library..."
local -a cmake_args=(
-DPROJECT_ARCH:STRING=${target##*-}
-DCEF_COMPILER_FLAGS:STRING="-Wno-deprecated-copy"
-DCMAKE_BUILD_TYPE:STRING=${config}
-DCMAKE_CXX_FLAGS:STRING="-std=c++11 -Wno-deprecated-declarations -Wno-unknonw-warning-option"
-DCMAKE_EXE_LINKER_FLAGS:STRING="-std=c++11"
)
if (( _loglevel == 0 )) cmake_args+=(-Wno-deprecated -Wno-dev --log-level=ERROR)
if (( ${+commands[ccache]} )) {
cmake_args+=(
-DCMAKE_C_COMPILER_LAUNCHER:STRING=ccache
-DCMAKE_CXX_COMPILER_LAUNCHER:STRING=ccache
)
}
cmake -S . -B build -G Ninja ${cmake_args}
log_group "Building CEF Wrapper library..."
cmake --build build
}
mkdir -p build/libcef_dll
popd
} else {
log_info 'Found existing Chromium Embedded Framework and loader library...'
}
popd
local -a apt_args=(
${CI:+-y}
--no-install-recommends
)
if (( _loglevel == 0 )) apt_args+=(--quiet)
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 ${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 libgles2-mesa \
libwayland-dev \
libpipewire-0.3-dev \
libpulse-dev \
libx264-dev \
libmbedtls-dev \
libgl1-mesa-dev \
libjansson-dev \
libluajit-5.1-dev python3-dev \
libx11-dev libxcb-randr0-dev libxcb-shm0-dev libxcb-xinerama0-dev \
libxcb-composite0-dev libxinerama-dev libxcb1-dev libx11-xcb-dev libxcb-xfixes0-dev \
swig libcmocka-dev libxss-dev libglvnd-dev \
libxkbcommon-dev \
libasound2-dev libfdk-aac-dev libfontconfig-dev libfreetype6-dev libjack-jackd2-dev \
libpulse-dev libsndio-dev libspeexdsp-dev libudev-dev libv4l-dev libva-dev libvlc-dev \
libpci-dev libdrm-dev \
nlohmann-json3-dev libwebsocketpp-dev libasio-dev libvpl-dev libqrcodegencpp-dev
if (( UBUNTU_2210_OR_LATER )) sudo apt-get install ${apt_args} librist-dev libsrt-openssl-dev
local -a _qt_packages=()
if (( QT_VERSION == 6 )) {
_qt_packages+=(
qt6-base-dev
libqt6svg6-dev
qt6-base-private-dev
)
} else {
log_error "Unsupported Qt version '${QT_VERSION}' specified."
return 2
}
sudo apt-get install ${apt_args} ${_qt_packages}
} else {
local cef_version
read -r cef_version <<< \
"$(jq -r '.dependencies | [.cef.version] | join(" ")' ${buildspec_file})"
typeset -g CEF_VERSION=${cef_version}
}
+346
View File
@@ -0,0 +1,346 @@
name: Build Project
on:
workflow_call:
jobs:
check-event:
name: Check GitHub Event Data 📡
runs-on: ubuntu-22.04
defaults:
run:
shell: bash
outputs:
package: ${{ steps.setup.outputs.package }}
codesign: ${{ steps.setup.outputs.codesign }}
notarize: ${{ steps.setup.outputs.notarize }}
config: ${{ steps.setup.outputs.config }}
commitHash: ${{ steps.setup.outputs.commitHash }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check Event Data ☑️
id: setup
env:
GH_TOKEN: ${{ github.token }}
run: |
: Check Event Data ☑️
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
case "${GITHUB_EVENT_NAME}" in
pull_request)
config_data=('codesign:false' 'notarize:false' 'package:false' 'config:RelWithDebInfo')
if gh pr view --json labels \
| jq -e -r '.labels[] | select(.name == "Seeking Testers")' > /dev/null; then
config_data[0]='codesign:true'
config_data[2]='package:true'
fi
;;
push)
config_data=('codesign:true' 'notarize:false' 'package:true' 'config:RelWithDebInfo')
if [[ ${GITHUB_REF_NAME} =~ [0-9]+.[0-9]+.[0-9]+(-(rc|beta).+)? ]]; then
config_data[1]='notarize:true'
config_data[3]='config:Release'
fi
;;
workflow_dispatch)
config_data=('codesign:true' 'notarize:false' 'package:false' 'config:RelWithDebInfo')
;;
schedule)
config_data=('codesign:true' 'notarize:false' 'package:true' 'config:RelWithDebInfo')
;;
*) ;;
esac
for config in "${config_data[@]}"; do
IFS=':' read -r key value <<< "${config}"
echo "${key}=${value}" >> $GITHUB_OUTPUT
done
echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
macos-build:
name: Build for macOS 🍏
runs-on: macos-13
needs: check-event
strategy:
fail-fast: false
matrix:
target: [arm64, x86_64]
defaults:
run:
shell: zsh --no-rcs --errexit --pipefail {0}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- name: Set Up Environment 🔧
id: setup
run: |
: Set Up Environment 🔧
if (( ${+RUNNER_DEBUG} )) setopt XTRACE
print '::group::Enable Xcode 14.3.1 and AppleScript'
sudo xcode-select --switch /Applications/Xcode_14.3.1.app/Contents/Developer
sudo sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db \
"INSERT OR REPLACE INTO access VALUES('kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552);"
sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db \
"INSERT OR REPLACE INTO access VALUES('kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552);"
print '::endgroup::'
print '::group::Clean Homebrew Environment'
local -a to_remove=()
for formula (curl) {
if [[ -d ${HOMEBREW_PREFIX}/opt/${formula} ]] to_remove+=(${formula})
}
if (( #to_remove )) brew uninstall --ignore-dependencies ${to_remove}
print '::endgroup::'
local -A arch_names=(x86_64 intel arm64 apple)
print "cpuName=${arch_names[${{ matrix.target }}]}" >> $GITHUB_OUTPUT
- uses: actions/cache/restore@v3
id: ccache-cache
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-${{ matrix.target }}-${{ needs.check-event.outputs.config }}
restore-keys: |
${{ runner.os }}-ccache-${{ matrix.target }}-
- name: Set Up Code Signing 🔑
uses: ./.github/actions/setup-macos-codesigning
if: fromJSON(needs.check-event.outputs.codesign)
id: codesign
with:
codesignIdentity: ${{ secrets.MACOS_SIGNING_IDENTITY }}
codesignCertificate: ${{ secrets.MACOS_SIGNING_CERT }}
certificatePassword: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }}
keychainPassword: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
provisioningProfile: ${{ secrets.MACOS_SIGNING_PROVISIONING_PROFILE }}
notarizationUser: ${{ secrets.MACOS_NOTARIZATION_USERNAME }}
notarizationPassword: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
- name: Build OBS Studio 🧱
uses: ./.github/actions/build-obs
env:
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
with:
target: ${{ matrix.target }}
config: ${{ needs.check-event.outputs.config }}
codesign: ${{ fromJSON(needs.check-event.outputs.codesign) }}
codesignIdent: ${{ steps.codesign.outputs.codesignIdent }}
codesignTeam: ${{ steps.codesign.outputs.codesignTeam }}
- name: Package OBS Studio 📀
uses: ./.github/actions/package-obs
with:
target: ${{ matrix.target }}
config: ${{ needs.check-event.outputs.config }}
package: ${{ fromJSON(needs.check-event.outputs.package) }}
codesign: ${{ fromJSON(needs.check-event.outputs.codesign) && fromJSON(steps.codesign.outputs.haveCodesignIdent) }}
codesignIdent: ${{ steps.codesign.outputs.codesignIdent }}
notarize: ${{ fromJSON(needs.check-event.outputs.notarize) && fromJSON(steps.codesign.outputs.haveNotarizationUser) }}
codesignUser: ${{ secrets.MACOS_NOTARIZATION_USERNAME }}
codesignPass: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v3
with:
name: obs-studio-macos-${{ matrix.target }}-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/build_macos/obs-studio-*-macos-${{ steps.setup.outputs.cpuName }}.*
- name: Upload Debug Symbol Artifacts 🪲
uses: actions/upload-artifact@v3
if: ${{ needs.check-event.outputs.config == 'Release' }}
with:
name: obs-studio-macos-${{ matrix.target }}-${{ needs.check-event.outputs.commitHash }}-dSYMs
path: ${{ github.workspace }}/build_macos/obs-studio-*-macos-${{ steps.setup.outputs.cpuName }}-dSYMs.tar.xz
- uses: actions/cache/save@v3
if: github.event_name != 'pull_request' && steps.ccache-cache.outputs.cache-hit != 'true'
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-${{ matrix.target }}-${{ needs.check-event.outputs.config }}
ubuntu-build:
name: Build for Ubuntu 🐧
runs-on: ubuntu-22.04
needs: check-event
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- uses: actions/cache/restore@v3
id: ccache-cache
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
restore-keys: |
${{ runner.os }}-ccache-x86_64-
- name: Build OBS Studio 🧱
uses: ./.github/actions/build-obs
env:
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
with:
target: x86_64
config: ${{ needs.check-event.outputs.config }}
- name: Package OBS Studio 📀
uses: ./.github/actions/package-obs
with:
target: x86_64
config: ${{ needs.check-event.outputs.config }}
package: ${{ fromJSON(needs.check-event.outputs.package) }}
- name: Upload Source Tarball 🗜️
uses: actions/upload-artifact@v3
if: ${{ ! always() }}
with:
name: obs-studio-*-sources-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/build_x86_64/obs-studio-*-sources.*
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v3
with:
name: obs-studio-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/build_x86_64/obs-studio-*-x86_64-linux-gnu.*
- name: Upload Debug Symbol Artifacts 🪲
uses: actions/upload-artifact@v3
if: ${{ fromJSON(needs.check-event.outputs.package) }}
with:
name: obs-studio-ubuntu-22.04-x86_64-${{ needs.check-event.outputs.commitHash }}-dbgsym
path: ${{ github.workspace }}/build_x86_64/obs-studio-*-x86_64-linux-gnu-dbgsym.ddeb
- uses: actions/cache/save@v3
if: github.event_name != 'pull_request' && steps.ccache-cache.outputs.cache-hit != 'true'
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
flatpak-build:
name: Build Application for Flatpak 📦
runs-on: ubuntu-22.04
needs: check-event
defaults:
run:
shell: bash
container:
image: bilelmoussaoui/flatpak-github-actions:kde-6.4
options: --privileged
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
set-safe-directory: ${{ env.GITHUB_WORKSPACE }}
- name: Set Up Environment 🔧
id: setup
env:
GH_TOKEN: ${{ github.token }}
run: |
: Set Up Environment 🔧
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
echo '::group::Install GitHub CLI tool'
dnf install -y -q gh
gh extension install actions/gh-actions-cache
echo '::endgroup::'
cache_key='flatpak-builder-${{ hashFiles('build-aux/**/*.json') }}'
cache_ref='master'
read -r key size unit _ ref _ <<< \
"$(gh actions-cache list -B ${cache_ref} --key "${cache_key}-x86_64" | head -1)"
if [[ "${key}" ]]; then
echo "cacheKey=${cache_key}" >> $GITHUB_OUTPUT
echo "cacheHit=true" >> $GITHUB_OUTPUT
else
echo "cacheHit=false" >> $GITHUB_OUTPUT
fi
- name: Build Flatpak Manifest 🧾
uses: flatpak/flatpak-github-actions/flatpak-builder@v5
with:
build-bundle: ${{ fromJSON(needs.check-event.outputs.package) }}
bundle: obs-studio-flatpak-${{ needs.check-event.outputs.commitHash }}.flatpak
manifest-path: ${{ github.workspace }}/build-aux/com.obsproject.Studio.json
cache: ${{ fromJSON(steps.setup.outputs.cacheHit) || (github.event_name == 'push' && github.ref_name == 'master')}}
restore-cache: ${{ fromJSON(steps.setup.outputs.cacheHit) }}
cache-key: ${{ steps.setup.outputs.cacheKey }}
windows-build:
name: Build for Windows 🪟
runs-on: windows-2022
needs: check-event
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- uses: actions/cache@v3
id: ccache-cache
if: github.event_name == 'pull_request'
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-x86_64-${{ needs.check-event.outputs.config }}
restore-keys: |
${{ runner.os }}-ccache-x86_64-
- name: Build OBS Studio 🧱
uses: ./.github/actions/build-obs
env:
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
GPU_PRIORITY_VAL: ${{ secrets.GPU_PRIORITY_VAL }}
with:
target: x64
config: ${{ needs.check-event.outputs.config }}
- name: Package OBS Studio 📀
uses: ./.github/actions/package-obs
with:
target: x64
config: ${{ needs.check-event.outputs.config }}
package: ${{ fromJSON(needs.check-event.outputs.package) }}
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v3
with:
name: obs-studio-windows-x64-${{ needs.check-event.outputs.commitHash }}
path: ${{ github.workspace }}/build_x64/obs-studio-*-windows-x64.zip
+63
View File
@@ -0,0 +1,63 @@
name: Check Code Formatting 🛠️
on:
workflow_call:
jobs:
clang-format:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: clang-format Check 🐉
id: clang-format
uses: ./.github/actions/run-clang-format
with:
failCondition: error
swift-format:
runs-on: macos-13
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: swift-format Check 🔥
id: swift-format
uses: ./.github/actions/run-swift-format
with:
failCondition: error
cmake-format:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: cmake-format Check 🎛️
id: cmake-format
uses: ./.github/actions/run-cmake-format
with:
failCondition: error
flatpak-validator:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Flatpak Manifest Check 📦
id: flatpak-check
uses: ./.github/actions/flatpak-manifest-validator
with:
failCondition: error
qt-xml-validator:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Qt XML Check 🖼️
id: qt-xml-check
uses: ./.github/actions/qt-xml-validator
with:
failCondition: error
-27
View File
@@ -1,27 +0,0 @@
name: Clang Format Check
on:
push:
paths-ignore: ['**.md']
branches-ignore: [master]
pull_request:
paths-ignore: ['**.md']
branches-ignore: [master]
jobs:
clang-format-check:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Install clang format
run: |
sudo apt-get install -y clang-format-13
- name: 'Run clang-format'
run: |
./CI/check-format.sh
./CI/check-changes.sh
-76
View File
@@ -1,76 +0,0 @@
name: Cache Cleanup
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
jobs:
purge:
name: Cache Cleanup
runs-on: ubuntu-latest
steps:
- name: Install Python & Modules
run: |
sudo apt install -y python3.11
python3.11 -m pip install requests
- name: Clean Caches
shell: python3.11 {0}
run: |
import re
from datetime import datetime
import requests
s = requests.session()
s.headers['Authorization'] = 'Bearer ${{ secrets.GITHUB_TOKEN }}'
r = s.get('https://api.github.com/repos/${{ github.repository }}/actions/caches', params=dict(per_page=100))
r.raise_for_status()
caches = r.json()['actions_caches']
print(f'There are {len(caches)} total caches.')
# find latest flatpak cache
flatpak_last_ts = None
flatpak_key = None
for cache in caches:
if not cache['ref'] == 'refs/heads/master' or not cache['key'].startswith('flatpak-builder'):
continue
ts = datetime.fromisoformat(cache['created_at'])
if not flatpak_last_ts or ts > flatpak_last_ts:
flatpak_key = cache['key']
flatpak_last_ts = ts
if flatpak_key:
print(f'Latest flatpak cache: {flatpak_key}')
now = datetime.utcnow()
to_be_removed = []
for cache in caches:
# add merge queue caches
if 'gh-readonly-queue' in cache['ref']:
to_be_removed.append(cache)
continue
if flatpak_key and cache['key'].startswith('flatpak-builder'):
# add non-master flatpak caches that match latest key
if cache['key'] == flatpak_key and not cache['ref'] == 'refs/heads/master':
to_be_removed.append(cache)
continue
# add master flatpak caches that do not match the latest key
elif cache['key'] != flatpak_key and cache['ref'] == 'refs/heads/master':
to_be_removed.append(cache)
continue
# add dated caches predating today
if (cache_date := re.search('[0-9]{4}\-[0-9]{2}\-[0-9]{2}', cache['key'])) is not None:
parsed_date = datetime.strptime(cache_date.group(), '%Y-%m-%d')
if (now - parsed_date).days > 0:
to_be_removed.append(cache)
print(f'Removing {len(to_be_removed)} caches...')
for cache in to_be_removed:
print(f'Deleting cache "{cache["key"]}" with ID {cache["id"]}...', end=' ')
r = s.delete(f'https://api.github.com/repos/${{ github.repository }}/actions/caches/{cache["id"]}')
print(f'[{r.status_code}]')
-40
View File
@@ -1,40 +0,0 @@
name: Compatibility Data Validator
on:
push:
paths:
- "plugins/win-capture/data/compatibility.json"
- "plugins/win-capture/data/package.json"
pull_request:
paths:
- "plugins/win-capture/data/compatibility.json"
- "plugins/win-capture/data/package.json"
jobs:
schema:
name: Schema
runs-on: [ubuntu-22.04]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install & Configure Python
run: |
sudo apt install python3-dev
python3 -m pip install jsonschema json_source_map
- name: Validate Compatibility JSON Schema
run: |
JSON_FILES=(
plugins/win-capture/data/compatibility.json
plugins/win-capture/data/package.json
)
python3 CI/check-jsonschema.py "${JSON_FILES[@]}"
- name: Annotate Errors
if: failure()
uses: yuzutech/annotations-action@v0.4.0
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
title: "Compatibility JSON Errors"
input: "./validation_errors.json"
@@ -1,16 +0,0 @@
name: "Crowdin Sync: Import latest translations"
on: workflow_dispatch
jobs:
crowdin-sync-download:
name: Import latest translations
runs-on: ubuntu-latest
if: github.repository_owner == 'obsproject'
env:
CROWDIN_PAT: ${{ secrets.CROWDIN_SYNC_CROWDIN_PAT }}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
token: ${{ secrets.CROWDIN_SYNC_GITHUB_PAT }}
- uses: obsproject/obs-crowdin-sync/download@0.2.1
-20
View File
@@ -1,20 +0,0 @@
name: "Crowdin Sync: Upload English strings"
on:
push:
branches:
- master
paths:
- "**/en-US.ini"
jobs:
crowdin-sync-upload:
name: Upload English strings
runs-on: ubuntu-latest
env:
CROWDIN_PAT: ${{ secrets.CROWDIN_SYNC_CROWDIN_PAT }}
GITHUB_EVENT_BEFORE: ${{ github.event.before }}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 100
- uses: obsproject/obs-crowdin-sync/upload@0.2.1
+146
View File
@@ -0,0 +1,146 @@
name: Dispatch
run-name: Dispatched Repository Actions - ${{ inputs.job }} ⌛️
on:
workflow_dispatch:
inputs:
job:
description: Dispatch job to run
required: true
type: choice
options:
- steam
- services
- translations
- documentation
ref:
description: GitHub reference to use for job
type: string
required: false
customAssetWindows:
description: Custom Windows build for Steam Upload
type: string
required: false
customAssetMacOSApple:
description: Custom macOS Apple Silicon build for Steam Upload
type: string
required: false
customAssetMacOSIntel:
description: Custom macOS Intel build for Steam Upload
type: string
required: false
permissions:
contents: write
jobs:
services-validation:
name: Check Services Configuration Files 🕵️
if: github.repository_owner == 'obsproject' && inputs.job == 'services'
runs-on: ubuntu-22.04
permissions:
checks: write
steps:
- uses: actions/checkout@v3
- name: Check for Defunct Services 📉
uses: ./.github/actions/services-validator
with:
repositorySecret: ${{ secrets.GITHUB_TOKEN }}
runSchemaChecks: true
runServiceChecks: true
createPullRequest: true
download-language-files:
name: Download Language Files 🌐
if: github.repository_owner == 'obsproject' && inputs.job == 'translations'
runs-on: ubuntu-22.04
env:
CROWDIN_PAT: ${{ secrets.CROWDIN_SYNC_CROWDIN_PAT }}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
token: ${{ secrets.CROWDIN_SYNC_GITHUB_PAT }}
- uses: obsproject/obs-crowdin-sync/download@0.2.1
steam-upload:
name: Upload Steam Builds 🚂
if: github.repository_owner == 'obsproject' && inputs.job == 'steam'
runs-on: macos-13
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/steam-upload
with:
steamSecret: ${{ secrets.STEAM_SHARED_SECRET }}
steamUser: ${{ secrets.STEAM_USER }}
steamPassword: ${{ secrets.STEAM_PASSWORD }}
tagName: ${{ inputs.ref }}
customAssetWindows: ${{ inputs.customAssetWindows }}
customAssetMacOSApple: ${{ inputs.customAssetMacOSApple }}
customAssetMacOSIntel: ${{ inputs.customAssetMacOSIntel }}
workflowSecret: ${{ github.token }}
preview: false
update-documentation:
name: Update Documentation 📖
if: github.repository_owner == 'obsproject' && inputs.job == 'documentation'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Get Commit Information 🆔
id: setup
run: |
: Get Commit Hash 🆔
echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
- uses: ./.github/actions/generate-docs
with:
commitHash: ${{ steps.checks.setup.commitHash }}
update-documentation-cloudflare:
name: Update Documentation for Cloudflare ☁️
if: github.repository_owner == 'obsproject' && inputs.job == 'documentation'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Get Commit Information 🆔
id: setup
run: |
: Get Commit Hash 🆔
echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
- uses: ./.github/actions/generate-docs
with:
commitHash: ${{ steps.checks.setup.commitHash }}
disableLinkExtensions: true
deploy-documentation:
name: Deploy Documentation to Cloudflare ☁️
if: github.repository_owner == 'obsproject' && inputs.job == 'documentation'
runs-on: ubuntu-22.04
needs: update-documentation-cloudflare
defaults:
run:
shell: bash
steps:
- name: Get Commit Information 🆔
id: setup
run: |
: Get Commit Hash 🆔
echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
- uses: actions/download-artifact@v3
with:
name: OBS Studio Docs (No Extensions) ${{ steps.setup.outputs.commitHash }}
path: docs
- name: Set Up Redirects 🔄
run: |
: Set Up Redirects 🔄
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
echo "/previous/27.2 https://obsproject.com/docs/27.2 302" >> docs/_redirects
echo "/previous/:major.:minor https://:major-:minor.${{ vars.CF_PAGES_PROJECT }}.pages.dev 302" >> docs/_redirects
- name: Publish to Live Page
uses: cloudflare/wrangler-action@4c10c1822abba527d820b29e6333e7f5dac2cabd
with:
workingDirectory: docs
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages publish . --project-name=${{ vars.CF_PAGES_PROJECT }} --commit-hash='${{ steps.setup.outputs.commitHash }}'
-136
View File
@@ -1,136 +0,0 @@
name: Generate Documentation
on:
push:
paths-ignore:
- "cmake/**"
branches: ['*']
tags: ['*']
pull_request:
paths:
- "docs/sphinx/**"
workflow_dispatch:
jobs:
docs:
runs-on: ubuntu-latest
outputs:
commitHash: ${{ steps.setup.outputs.commitHash }}
commitBranch: ${{ steps.setup.outputs.commitBranch }}
fullCommitHash: ${{ steps.setup.outputs.fullCommitHash }}
env:
BUILD_CF_ARTIFACT: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Environment
id: setup
run: |
BRANCH=$(git describe --exact-match --tags 2> /dev/null || git branch --show-current)
# Remove patch version from tag
BRANCH=$(echo ${BRANCH} | sed -e 's/\.[0-9]*$//')
echo "commitBranch=${BRANCH}" >> $GITHUB_OUTPUT
echo "commitHash=$(git describe --exact-match --tags 2> /dev/null || git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "fullCommitHash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- name: Bump Version Number
shell: bash
if: github.event_name != 'pull_request'
run: |
VERTEST="\#define\sLIBOBS_API_\w+_VER\s([0-9]{1,2})"
VER=""
MAJOR=""
while IFS= read -r l
do
if [[ $l =~ $VERTEST ]]; then
if [[ $VER = '' ]]; then MAJOR="${BASH_REMATCH[1]}"; else VER+="."; fi
VER+="${BASH_REMATCH[1]}"
fi
done < "libobs/obs-config.h"
SVER="version = '([0-9\.]+)'"
RVER="version = '$VER'"
SREL="release = '([0-9\.]+)'"
RREL="release = '$VER'"
SCOPY="copyright = '([A-Za-z0-9, ]+)'"
RCOPY="copyright = '2017-$(date +"%Y"), Lain Bailey'"
sed -i -E -e "s/${SVER}/${RVER}/g" -e "s/${SREL}/${RREL}/g" -e "s/${SCOPY}/${RCOPY}/g" docs/sphinx/conf.py
- uses: totaldebug/sphinx-publish-action@1.2.0
with:
sphinx_src: 'docs/sphinx'
build_only: True
target_branch: 'master'
target_path: '../home/_build'
pre_build_commands: 'pip install -Iv sphinx==5.1.1'
- name: Disable link extensions
shell: bash
if: ${{ env.BUILD_CF_ARTIFACT == 'true' }}
run: |
SOPT="html_link_suffix = None"
ROPT="html_link_suffix = ''"
sed -i -e "s/${SOPT}/${ROPT}/g" docs/sphinx/conf.py
- uses: totaldebug/sphinx-publish-action@1.2.0
if: ${{ env.BUILD_CF_ARTIFACT == 'true' }}
with:
sphinx_src: 'docs/sphinx'
build_only: True
target_branch: 'master'
target_path: '../home/_build_cf'
pre_build_commands: 'pip install -Iv sphinx==5.1.1'
- uses: actions/upload-artifact@v3
with:
name: 'OBS Studio Docs ${{ steps.setup.outputs.commitHash }}'
path: |
${{ runner.temp }}/_github_home/_build
!${{ runner.temp }}/_github_home/_build/.doctrees
- uses: actions/upload-artifact@v3
if: ${{ env.BUILD_CF_ARTIFACT == 'true' }}
with:
name: 'CF Pages ${{ steps.setup.outputs.commitHash }}'
path: |
${{ runner.temp }}/_github_home/_build_cf
!${{ runner.temp }}/_github_home/_build_cf/.doctrees
deploy:
runs-on: ubuntu-latest
needs: docs
if: ${{ github.event_name == 'workflow_dispatch' || (github.repository_owner == 'obsproject' && startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request') }}
environment:
name: cf-pages-deploy
concurrency:
group: "cf-pages-deployment"
cancel-in-progress: true
steps:
- uses: actions/download-artifact@v3
with:
name: 'CF Pages ${{ needs.docs.outputs.commitHash }}'
path: docs
- name: Setup redirects
run: |
echo "/previous/27.2 https://obsproject.com/docs/27.2 302" >> docs/_redirects
echo "/previous/:major.:minor https://:major-:minor.${{ vars.CF_PAGES_PROJECT }}.pages.dev 302" >> docs/_redirects
- name: Publish to live page
if: ${{ !contains(needs.docs.outputs.commitBranch, 'beta') && !contains(needs.docs.outputs.commitBranch, 'rc') }}
uses: cloudflare/wrangler-action@4c10c1822abba527d820b29e6333e7f5dac2cabd
with:
workingDirectory: docs
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages publish . --project-name=${{ vars.CF_PAGES_PROJECT }} --commit-hash='${{ needs.docs.outputs.fullCommitHash }}'
- name: Publish to tag alias
if: ${{ startsWith(github.ref, 'refs/tags/') }}
uses: cloudflare/wrangler-action@4c10c1822abba527d820b29e6333e7f5dac2cabd
with:
workingDirectory: docs
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages publish . --project-name=${{ vars.CF_PAGES_PROJECT }} --commit-hash='${{ needs.docs.outputs.fullCommitHash }}' --branch='${{ needs.docs.outputs.commitBranch }}'
-123
View File
@@ -1,123 +0,0 @@
---
name: Flatpak
on:
release:
types: [published]
branches: [master, 'release/**']
env:
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
jobs:
check_tag:
name: Check release tag
runs-on: [ubuntu-latest]
outputs:
valid_tag: ${{ steps.check_tag.outputs.valid_tag }}
matrix: ${{ steps.check_tag.outputs.matrix }}
steps:
- name: Check the tag
id: check_tag
run: |
shopt -s extglob
case ${GITHUB_REF##*/} in
+([0-9]).+([0-9]).+([0-9]) )
echo 'valid_tag=true' >> $GITHUB_OUTPUT
echo 'matrix=["beta", "stable"]' >> $GITHUB_OUTPUT
;;
+([0-9]).+([0-9]).+([0-9])-@(beta|rc)*([0-9]) )
echo 'valid_tag=true' >> $GITHUB_OUTPUT
echo 'matrix=["beta"]' >> $GITHUB_OUTPUT
;;
* ) echo 'valid_tag=false' >> $GITHUB_OUTPUT ;;
esac
publish:
name: Publish to Flathub
runs-on: [ubuntu-latest]
needs: check_tag
if: fromJSON(needs.check_tag.outputs.valid_tag)
env:
FLATPAK_BUILD_PATH: flatpak_app/files/share
container:
image: bilelmoussaoui/flatpak-github-actions:kde-6.4
options: --privileged
strategy:
matrix:
branch: ${{ fromJSON(needs.check_tag.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: 'Setup build environment'
id: setup
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CACHE_KEY: flatpak-builder-${{ hashFiles('build-aux/**/*.json') }}
run: |
dnf install -y -q gh
gh extension install actions/gh-actions-cache
git config --global --add safe.directory $GITHUB_WORKSPACE
KEY="$CACHE_KEY-x86_64"
CACHE_HIT=$(gh actions-cache list -B master --key $KEY | grep -q $KEY && echo 'true' || echo 'false')
echo "git_hash=$(git rev-parse --short=9 HEAD)" >> $GITHUB_OUTPUT
echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT
echo "cache_hit=$CACHE_HIT" >> $GITHUB_OUTPUT
- name: Build Flatpak Manifest
uses: flatpak/flatpak-github-actions/flatpak-builder@v6.1
with:
bundle: obs-studio-${{ steps.setup.outputs.git_hash }}.flatpak
manifest-path: build-aux/com.obsproject.Studio.json
cache: ${{ fromJSON(steps.setup.outputs.cache_hit) }}
cache-key: ${{ steps.setup.outputs.cache_key }}
mirror-screenshots-url: https://dl.flathub.org/repo/screenshots
branch: ${{ matrix.branch }}
- name: Validate AppStream
shell: bash
working-directory: ${{ env.FLATPAK_BUILD_PATH }}
run: |
appstream-util validate appdata/com.obsproject.Studio.appdata.xml
- name: Verify icon and metadata in app-info
shell: bash
working-directory: ${{ env.FLATPAK_BUILD_PATH }}
run: |
test -f app-info/icons/flatpak/128x128/com.obsproject.Studio.png || { echo "Missing 128x128 icon in app-info" ; exit 1; }
test -f app-info/xmls/com.obsproject.Studio.xml.gz || { echo "Missing com.obsproject.Studio.xml.gz in app-info" ; exit 1; }
- name: Commit screenshots to the OSTree repository
run: |
ostree commit --repo=repo --canonical-permissions --branch=screenshots/x86_64 flatpak_app/screenshots
- name: Publish to Flathub Beta
uses: flatpak/flatpak-github-actions/flat-manager@v6.1
if: matrix.branch == 'beta'
with:
flat-manager-url: https://hub.flathub.org/
repository: beta
token: ${{ secrets.FLATHUB_BETA_TOKEN }}
- name: Publish to Flathub
uses: flatpak/flatpak-github-actions/flat-manager@v6.1
if: matrix.branch == 'stable'
with:
flat-manager-url: https://hub.flathub.org/
repository: stable
token: ${{ secrets.FLATHUB_TOKEN }}
-624
View File
@@ -1,624 +0,0 @@
name: 'BUILD'
on:
push:
paths-ignore: ['**.md']
branches:
- master
- 'release/**'
tags: ['*']
pull_request:
paths-ignore: ['**.md']
branches: [master]
merge_group:
branches: [master]
env:
CACHE_REVISION: '006'
CEF_BUILD_VERSION_LINUX: '5060'
CEF_BUILD_VERSION_WIN: '5060'
QT_VERSION_MAC: '6.4.3'
QT_VERSION_WIN: '6.4.3'
DEPS_VERSION_WIN: '2023-06-22'
VLC_VERSION_WIN: '3.0.0-git'
TWITCH_CLIENTID: ${{ secrets.TWITCH_CLIENT_ID }}
TWITCH_HASH: ${{ secrets.TWITCH_HASH }}
RESTREAM_CLIENTID: ${{ secrets.RESTREAM_CLIENTID }}
RESTREAM_HASH: ${{ secrets.RESTREAM_HASH }}
YOUTUBE_CLIENTID: ${{ secrets.YOUTUBE_CLIENTID }}
YOUTUBE_CLIENTID_HASH: ${{ secrets.YOUTUBE_CLIENTID_HASH }}
YOUTUBE_SECRET: ${{ secrets.YOUTUBE_SECRET }}
YOUTUBE_SECRET_HASH: ${{ secrets.YOUTUBE_SECRET_HASH }}
GPU_PRIORITY_VAL: ${{ secrets.GPU_PRIORITY_VAL }}
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
clang_check:
name: '01 - Code Format Check'
runs-on: [ubuntu-22.04]
steps:
- name: 'Checkout'
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: 'Install clang-format'
run: sudo apt-get install -y clang-format-13
- name: 'Run clang-format'
run: |
./CI/check-format.sh
./CI/check-changes.sh
- name: 'Install cmake-format'
run: sudo pip install cmakelang
- name: 'Run cmake-format'
run: |
./CI/check-cmake.sh
- name: 'Run format-manifest.py'
run: |
python3 ./build-aux/format-manifest.py
./CI/check-changes.sh
config:
name: '01 - Configure Build Jobs'
runs-on: [ubuntu-22.04]
outputs:
create_artifacts: ${{ steps.config.outputs.create_artifacts }}
cache_date: ${{ steps.config.outputs.cache_date }}
steps:
- name: 'Configure Build Jobs'
id: config
run: |
if [[ "${{ github.event_name == 'pull_request' }}" == "true" ]]; then
if test -n "$(curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -s "${{ github.event.pull_request.url }}" | jq -e '.labels[] | select(.name == "Seeking Testers")')"; then
echo 'create_artifacts=true' >> $GITHUB_OUTPUT
else
echo 'create_artifacts=false' >> $GITHUB_OUTPUT
fi
else
echo 'create_artifacts=true' >> $GITHUB_OUTPUT
fi
echo "cache_date=$(date +"%Y-%m-%d")" >> $GITHUB_OUTPUT
macos_build:
name: '02 - macOS'
runs-on: [macos-13]
strategy:
matrix:
arch: ['x86_64', 'arm64']
if: always()
needs: [config, clang_check]
env:
BLOCKED_FORMULAS: 'speexdsp curl php composer'
defaults:
run:
shell: bash
working-directory: 'obs-studio'
steps:
- name: 'Checkout'
uses: actions/checkout@v3
with:
submodules: 'recursive'
path: 'obs-studio'
fetch-depth: 0
- name: 'Restore ccache from cache'
id: ccache-cache
uses: actions/cache@v3
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-${{ steps.github-check.outputs.generator }}-${{ matrix.arch }}-${{ github.event_name }}-${{ github.head_ref }}
restore-keys: |
${{ runner.os }}-ccache-${{ steps.github-check.outputs.generator }}-${{ matrix.arch }}-push-
- name: 'Setup build environment'
id: setup
run: |
REMOVE_FORMULAS=""
for FORMULA in ${{ env.BLOCKED_FORMULAS }}; do
if [ -d "/usr/local/opt/${FORMULA}" ]; then
REMOVE_FORMULAS="${REMOVE_FORMULAS}${FORMULA} "
fi
done
if [ -n "${REMOVE_FORMULAS}" ]; then
brew uninstall ${REMOVE_FORMULAS}
fi
echo "commitHash=$(git rev-parse --short=9 HEAD)" >> $GITHUB_OUTPUT
- name: 'Install dependencies'
run: CI/macos/01_install_dependencies.sh --architecture "${{ matrix.arch }}"
- name: 'Install Apple Developer Certificate'
id: macos-codesign
env:
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
MACOS_SIGNING_CERT: ${{ secrets.MACOS_SIGNING_CERT }}
MACOS_SIGNING_CERT_PASSWORD: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }}
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
MACOS_PROVISIONING_PROFILE: ${{ secrets.MACOS_SIGNING_PROVISIONING_PROFILE }}
run: |
if [[ "${MACOS_SIGNING_IDENTITY}" && "${MACOS_SIGNING_CERT}" ]]; then
CERTIFICATE_PATH="${RUNNER_TEMP}/build_certificate.p12"
KEYCHAIN_PATH="${RUNNER_TEMP}/app-signing.keychain-db"
echo -n "${MACOS_SIGNING_CERT}" | base64 --decode --output="${CERTIFICATE_PATH}"
: "${MACOS_KEYCHAIN_PASSWORD:="$(echo ${RANDOM} | sha1sum | head -c 32)"}"
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 "${MACOS_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'
echo "CODESIGN_IDENT=${MACOS_SIGNING_IDENTITY}" >> $GITHUB_ENV
echo "MACOS_KEYCHAIN_PASSWORD=${MACOS_KEYCHAIN_PASSWORD}" >> $GITHUB_ENV
echo "haveCodesignIdent=true" >> $GITHUB_OUTPUT
else
echo "CODESIGN_IDENT=-" >> $GITHUB_ENV
echo "haveCodesignIdent=false" >> $GITHUB_OUTPUT
fi
if [[ "${MACOS_PROVISIONING_PROFILE}" ]]; then
PROFILE_PATH="${RUNNER_TEMP}/build_profile.provisionprofile"
echo -n "${MACOS_PROVISIONING_PROFILE}" | base64 --decode --output="${PROFILE_PATH}"
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
security cms -D -i "${PROFILE_PATH}" -o "${RUNNER_TEMP}/build_profile.plist"
UUID="$(plutil -extract UUID raw "${RUNNER_TEMP}/build_profile.plist")"
TEAM_ID="$(plutil -extract TeamIdentifier.0 raw -expect string "${RUNNER_TEMP}/build_profile.plist")"
cp "${PROFILE_PATH}" ~/Library/MobileDevice/Provisioning\ Profiles/${UUID}.provisionprofile
echo "provisionprofileUUID=${UUID}" >> $GITHUB_OUTPUT
echo "haveProvisioningProfile=true" >> $GITHUB_OUTPUT
echo "CODESIGN_TEAM=${TEAM_ID}" >> $GITHUB_ENV
else
echo "haveProvisioningProfile=false" >> $GITHUB_OUTPUT
fi
if [[ "${MACOS_NOTARIZATION_USERNAME}" && "${MACOS_NOTARIZATION_PASSWORD}" ]]; then
echo "haveNotarizationUser=true" >> $GITHUB_OUTPUT
else
echo "haveNotarizationUser=false" >> $GITHUB_OUTPUT
fi
- name: 'Build OBS'
env:
PROVISIONING_PROFILE: ${{ steps.macos-codesign.outputs.provisionprofileUUID }}
run: |
sudo xcode-select --switch /Applications/Xcode_14.3.1.app/Contents/Developer
if [[ '${{ steps.github-check.outputs.generator }}' == 'Xcode' ]]; then
SEEKING_TESTERS=1 CI/macos/02_build_obs.sh --codesign --architecture "${{ matrix.arch }}"
else
CI/macos/02_build_obs.sh --codesign --architecture "${{ matrix.arch }}"
fi
- name: 'Create build artifact'
if: ${{ fromJSON(needs.config.outputs.create_artifacts) }}
run: |
: ${PACKAGE:=}
case "${GITHUB_EVENT_NAME}" in
push) if [[ ${GITHUB_REF_NAME} =~ [0-9]+.[0-9]+.[0-9]+(-(rc|beta).+)? ]]; then PACKAGE=1; fi ;;
pull_request) PACKAGE=1 ;;
esac
if [[ "${PACKAGE}" ]]; then
sudo sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db \
"INSERT OR REPLACE INTO access VALUES('kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552);"
sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db \
"INSERT OR REPLACE INTO access VALUES('kTCCServiceAppleEvents','/usr/local/opt/runner/provisioner/provisioner',1,2,3,1,NULL,NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1592919552);"
CI/macos/03_package_obs.sh --codesign --architecture "${{ matrix.arch }}"
ARTIFACT_NAME=$(basename $(/usr/bin/find build_macos -type f -name "obs-studio-*.dmg" -depth 1 | head -1))
echo "FILE_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV
fi
- name: 'Upload build Artifact'
if: ${{ fromJSON(needs.config.outputs.create_artifacts) && env.FILE_NAME != '' }}
uses: actions/upload-artifact@v3
with:
name: 'obs-studio-macos-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}'
path: '${{ github.workspace }}/obs-studio/build_macos/${{ env.FILE_NAME }}'
linux_build:
name: '02 - Linux'
runs-on: ${{ matrix.ubuntu }}
strategy:
matrix:
ubuntu: ['ubuntu-22.04']
if: always()
needs: [config, clang_check]
defaults:
run:
shell: bash
working-directory: 'obs-studio'
env:
BUILD_FOR_DISTRIBUTION: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' }}
steps:
- name: 'Checkout'
uses: actions/checkout@v3
with:
submodules: 'recursive'
path: 'obs-studio'
fetch-depth: 0
- name: 'Restore ccache from cache'
id: ccache-cache
uses: actions/cache@v3
env:
CACHE_NAME: 'ccache-cache'
with:
path: ${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-${{ matrix.ubuntu }}-${{ github.event_name }}-${{ github.head_ref }}
restore-keys: |
${{ runner.os }}-ccache-${{ matrix.ubuntu }}-push-
- name: 'Restore Chromium Embedded Framework from cache'
id: cef-cache
uses: actions/cache@v3
env:
CACHE_NAME: 'cef-cache'
with:
path: ${{ github.workspace }}/obs-build-dependencies/cef_binary_${{ env.CEF_BUILD_VERSION_LINUX }}_linux64
key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ env.CEF_BUILD_VERSION_LINUX }}-${{ env.CACHE_REVISION }}
- name: 'Setup build environment'
id: setup
run: |
echo "commitHash=$(git rev-parse --short=9 HEAD)" >> $GITHUB_OUTPUT
- name: 'Install dependencies'
env:
RESTORED_CEF: ${{ steps.cef-cache.outputs.cache-hit }}
run: CI/linux/01_install_dependencies.sh ${{ matrix.script_options }}
- name: 'Build OBS'
run: CI/linux/02_build_obs.sh ${{ matrix.script_options }}
- name: 'Run tests'
if: success()
run: cmake --build build -t test
- name: 'Create build artifact'
if: ${{ success() && fromJSON(needs.config.outputs.create_artifacts) }}
run: |
CI/linux/03_package_obs.sh
ARTIFACT_NAME=$(basename $(/usr/bin/find build -maxdepth 1 -type f -name "obs-studio-*.deb" | sort -rn | head -1))
echo "FILE_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV
echo "DEBUG_FILE_NAME=${ARTIFACT_NAME//.deb/-dbgsym.ddeb}" >> $GITHUB_ENV
- name: 'Upload build Artifact'
if: ${{ success() && fromJSON(needs.config.outputs.create_artifacts) }}
uses: actions/upload-artifact@v3
with:
name: 'obs-studio-${{ matrix.ubuntu }}-${{ steps.setup.outputs.commitHash }}'
path: '${{ github.workspace }}/obs-studio/build/${{ env.FILE_NAME }}'
- name: 'Upload debug symbol Artifact'
if: ${{ success() && fromJSON(needs.config.outputs.create_artifacts) }}
uses: actions/upload-artifact@v3
with:
name: 'obs-studio-${{ matrix.ubuntu }}-${{ steps.setup.outputs.commitHash }}-dbgsym'
path: '${{ github.workspace }}/obs-studio/build/${{ env.DEBUG_FILE_NAME }}'
windows_build:
name: '02 - Windows'
runs-on: [windows-2022]
needs: [config, clang_check]
if: always()
env:
BUILD_FOR_DISTRIBUTION: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' }}
steps:
- name: 'Checkout'
uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0
- name: Setup Environment
id: setup
run: |
$CommitHash = git rev-parse --short=9 HEAD
"commitHash=${CommitHash}" >> $env:GITHUB_OUTPUT
- name: 'Build OBS'
run: .github/scripts/Build-Windows.ps1 -Target x64 -Configuration RelWithDebInfo
- name: 'Create build artifact'
if: ${{ success() && fromJSON(needs.config.outputs.create_artifacts) }}
run: |
.github/scripts/Package-Windows.ps1 -Target x64 -Configuration RelWithDebInfo
$ArtifactName = Get-ChildItem -filter "build_x64/obs-studio-*-windows-x64.zip" -File
Write-Output "FILE_NAME=${ArtifactName}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: 'Upload build artifact'
if: ${{ success() && fromJSON(needs.config.outputs.create_artifacts) }}
uses: actions/upload-artifact@v3
with:
name: 'obs-studio-windows-x64-${{ steps.setup.outputs.commitHash }}'
path: '${{ env.FILE_NAME }}'
linux_package:
name: '02 - Flatpak'
runs-on: [ubuntu-latest]
needs: [config, clang_check]
defaults:
run:
shell: bash
container:
image: bilelmoussaoui/flatpak-github-actions:kde-6.4
options: --privileged
steps:
- name: 'Checkout'
uses: actions/checkout@v3
with:
submodules: 'recursive'
fetch-depth: 0
- name: 'Setup build environment'
id: setup
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CACHE_KEY: flatpak-builder-${{ hashFiles('build-aux/**/*.json') }}
run: |
dnf install -y -q gh
gh extension install actions/gh-actions-cache
git config --global --add safe.directory $GITHUB_WORKSPACE
KEY="$CACHE_KEY-x86_64"
CACHE_HIT=$(gh actions-cache list -B master --key $KEY | grep -q $KEY && echo 'true' || echo 'false')
echo "git_hash=$(git rev-parse --short=9 HEAD)" >> $GITHUB_OUTPUT
echo "cache_key=$CACHE_KEY" >> $GITHUB_OUTPUT
echo "cache_hit=$CACHE_HIT" >> $GITHUB_OUTPUT
- name: Build Flatpak Manifest
uses: flatpak/flatpak-github-actions/flatpak-builder@v6.1
with:
build-bundle: ${{ fromJSON(needs.config.outputs.create_artifacts) }}
bundle: obs-studio-flatpak-${{ steps.setup.outputs.git_hash }}.flatpak
manifest-path: build-aux/com.obsproject.Studio.json
cache: ${{ fromJSON(steps.setup.outputs.cache_hit) || (github.event_name == 'push' && github.ref == 'refs/heads/master') }}
restore-cache: ${{ fromJSON(steps.setup.outputs.cache_hit) }}
cache-key: ${{ steps.setup.outputs.cache_key }}
macos_release:
name: '03 - macOS notarized image'
runs-on: [macos-13]
needs: [macos_build]
env:
BUILD_FOR_DISTRIBUTION: 'ON'
HAVE_SPARKLE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY != '' }}
outputs:
run_sparkle: ${{ steps.sparkle_check.outputs.run_sparkle }}
if: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' }}
strategy:
matrix:
arch: ['x86_64', 'arm64']
defaults:
run:
shell: bash
steps:
- name: 'Install Apple Developer Certificate'
id: macos-codesign
env:
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
MACOS_SIGNING_CERT: ${{ secrets.MACOS_SIGNING_CERT }}
MACOS_SIGNING_CERT_PASSWORD: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }}
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
MACOS_PROVISIONING_PROFILE: ${{ secrets.MACOS_SIGNING_PROVISIONING_PROFILE }}
MACOS_NOTARIZATION_USERNAME: ${{ secrets.MACOS_NOTARIZATION_USERNAME }}
MACOS_NOTARIZATION_PASSWORD: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
run: |
if [[ "${MACOS_SIGNING_IDENTITY}" && "${MACOS_SIGNING_CERT}" ]]; then
CERTIFICATE_PATH="${RUNNER_TEMP}/build_certificate.p12"
KEYCHAIN_PATH="${RUNNER_TEMP}/app-signing.keychain-db"
echo -n "${MACOS_SIGNING_CERT}" | base64 --decode --output="${CERTIFICATE_PATH}"
: "${MACOS_KEYCHAIN_PASSWORD:="$(echo ${RANDOM} | sha1sum | head -c 32)"}"
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 "${MACOS_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'
echo "CODESIGN_IDENT=${MACOS_SIGNING_IDENTITY}" >> $GITHUB_ENV
echo "MACOS_KEYCHAIN_PASSWORD=${MACOS_KEYCHAIN_PASSWORD}" >> $GITHUB_ENV
echo "haveCodesignIdent=true" >> $GITHUB_OUTPUT
else
echo "CODESIGN_IDENT=-" >> $GITHUB_ENV
echo "haveCodesignIdent=false" >> $GITHUB_OUTPUT
fi
if [[ "${MACOS_PROVISIONING_PROFILE}" ]]; then
PROFILE_PATH="${RUNNER_TEMP}/build_profile.provisionprofile"
echo -n "${MACOS_PROVISIONING_PROFILE}" | base64 --decode --output="${PROFILE_PATH}"
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
security cms -D -i "${PROFILE_PATH}" -o "${RUNNER_TEMP}/build_profile.plist"
UUID="$(plutil -extract UUID raw "${RUNNER_TEMP}/build_profile.plist")"
TEAM_ID="$(plutil -extract TeamIdentifier.0 raw -expect string "${RUNNER_TEMP}/build_profile.plist")"
cp "${PROFILE_PATH}" ~/Library/MobileDevice/Provisioning\ Profiles/${UUID}.provisionprofile
echo "provisionprofileUUID=${UUID}" >> $GITHUB_OUTPUT
echo "haveProvisioningProfile=true" >> $GITHUB_OUTPUT
echo "CODESIGN_TEAM=${TEAM_ID}" >> $GITHUB_ENV
else
echo "haveProvisioningProfile=false" >> $GITHUB_OUTPUT
fi
if [[ "${MACOS_NOTARIZATION_USERNAME}" && "${MACOS_NOTARIZATION_PASSWORD}" ]]; then
echo "haveNotarizationUser=true" >> $GITHUB_OUTPUT
else
echo "haveNotarizationUser=false" >> $GITHUB_OUTPUT
fi
- name: 'Checkout'
if: ${{ fromJSON(steps.macos-codesign.outputs.haveCodesignIdent) && fromJSON(steps.macos-codesign.outputs.haveNotarizationUser) }}
uses: actions/checkout@v3
- name: 'Setup build environment'
if: ${{ fromJSON(steps.macos-codesign.outputs.haveCodesignIdent) && fromJSON(steps.macos-codesign.outputs.haveNotarizationUser) }}
id: setup
run: |
echo "commitHash=$(git rev-parse --short=9 HEAD)" >> $GITHUB_OUTPUT
- name: 'Determine if Sparkle should run'
if: env.HAVE_CODESIGN_IDENTITY == 'true'
id: sparkle_check
run: |
echo 'run_sparkle=${{ env.HAVE_SPARKLE_KEY }}' >> $GITHUB_OUTPUT
- name: 'Download artifact'
if: ${{ fromJSON(steps.macos-codesign.outputs.haveCodesignIdent) && fromJSON(steps.macos-codesign.outputs.haveNotarizationUser) }}
uses: actions/download-artifact@v3
with:
name: 'obs-studio-macos-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}'
- name: 'Create disk image for distribution'
if: ${{ fromJSON(steps.macos-codesign.outputs.haveCodesignIdent) && fromJSON(steps.macos-codesign.outputs.haveNotarizationUser) }}
env:
CODESIGN_IDENT_USER: ${{ secrets.MACOS_NOTARIZATION_USERNAME }}
CODESIGN_IDENT_PASS: ${{ secrets.MACOS_NOTARIZATION_PASSWORD }}
run: |
ARTIFACT_NAME=$(/usr/bin/find . -type f -name "obs-studio-*.dmg" -depth 1 | head -1)
CI/macos/03_package_obs.sh --notarize-image ${ARTIFACT_NAME}
echo "FILE_NAME=$(basename ${ARTIFACT_NAME})" >> $GITHUB_ENV
- name: 'Upload build Artifact'
if: ${{ fromJSON(steps.macos-codesign.outputs.haveCodesignIdent) && fromJSON(steps.macos-codesign.outputs.haveNotarizationUser) }}
uses: actions/upload-artifact@v3
with:
name: 'obs-studio-macos-${{ matrix.arch }}-notarized'
path: '${{ github.workspace }}/${{ env.FILE_NAME }}'
macos_sparkle:
name: '04 - macOS Sparkle Updates'
runs-on: [macos-13]
needs: [macos_release]
if: fromJSON(needs.macos_release.outputs.run_sparkle)
strategy:
matrix:
arch: ['x86_64', 'arm64']
env:
SPARKLE_VERSION: '2.3.2'
SPARKLE_HASH: '2b3fe6918ca20a83729aad34f8f693a678b714a17d33b5f13ca2d25edfa7eed3'
defaults:
run:
shell: bash
steps:
- name: 'Checkout'
uses: actions/checkout@v3
with:
path: 'repo'
ref: ${{ github.ref }}
- name: 'Download artifact'
uses: actions/download-artifact@v3
with:
name: 'obs-studio-macos-${{ matrix.arch }}-notarized'
path: 'artifacts'
- name: 'Install Python requirements'
run: pip3 install requests xmltodict
- name: 'Install Brew requirements'
run: brew install coreutils pandoc
- name: 'Setup Sparkle'
run: |
curl -L "https://github.com/sparkle-project/Sparkle/releases/download/${{ env.SPARKLE_VERSION }}/Sparkle-${{ env.SPARKLE_VERSION }}.tar.xz" -o Sparkle.tar.xz
if [[ '${{ env.SPARKLE_HASH }}' != "$(sha256sum Sparkle.tar.xz | cut -d " " -f 1)" ]]; then
echo "Sparkle download hash does not match!"
exit 1
fi
mkdir sparkle && cd sparkle
tar -xf ../Sparkle.tar.xz
- name: 'Setup folder structure'
run: |
mkdir builds
mkdir -p output/appcasts/stable
mkdir -p output/sparkle_deltas/${{ matrix.arch }}
- name: 'Determine branch and tag'
id: branch
run: |
pushd repo
GIT_TAG="$(git describe --tags --abbrev=0)"
if [[ ${GIT_TAG} == *'beta'* || ${GIT_TAG} == *'rc'* ]]; then
echo "branch=beta" >> $GITHUB_OUTPUT
echo "deltas=1" >> $GITHUB_OUTPUT
else
echo "branch=stable" >> $GITHUB_OUTPUT
echo "deltas=1" >> $GITHUB_OUTPUT
fi
# Write tag description to file
git tag -l --format='%(contents)' ${GIT_TAG} >> ../notes.rst
- name: 'Download existing Appcast and builds'
run: python3 repo/CI/macos/appcast_download.py --branch "${{ steps.branch.outputs.branch }}" --max-old-versions ${{ steps.branch.outputs.deltas }}
- name: 'Prepare release notes'
run: |
# Insert underline at line 2 to turn first line into heading
sed -i '' '2i\'$'\n''###################################################' notes.rst
pandoc -f rst -t html notes.rst -o output/appcasts/notes_${{ steps.branch.outputs.branch }}.html
- name: 'Setup Sparkle key'
run: echo -n "${{ secrets.SPARKLE_PRIVATE_KEY }}" >> eddsa_private.key
- name: 'Generate Appcast'
run: |
mv artifacts/*.dmg builds/
./sparkle/bin/generate_appcast \
--verbose \
--ed-key-file ./eddsa_private.key \
--download-url-prefix "https://cdn-fastly.obsproject.com/downloads/" \
--full-release-notes-url "https://obsproject.com/osx_update/notes_${{ steps.branch.outputs.branch }}.html" \
--maximum-versions 0 \
--maximum-deltas ${{ steps.branch.outputs.deltas }} \
--channel "${{ steps.branch.outputs.branch }}" builds/
# Move deltas, if any
if compgen -G "builds/*.delta" > /dev/null; then
mv builds/*.delta output/sparkle_deltas/${{ matrix.arch }}
fi
# Move appcasts
mv builds/*.xml output/appcasts/
- name: 'Create 1.x Appcast'
run: python3 repo/CI/macos/appcast_convert.py
- name: 'Upload Appcast and Deltas'
uses: actions/upload-artifact@v3
with:
name: 'macos-sparkle-updates'
path: '${{ github.workspace }}/output'
+94
View File
@@ -0,0 +1,94 @@
name: Pull Request
run-name: ${{ github.event.pull_request.title }} pull request run 🚀
on:
workflow_dispatch:
pull_request:
paths-ignore:
- '**.md'
branches: [master]
types: [ opened, synchronize, reopened, labeled, unlabeled ]
permissions:
contents: read
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
check-format:
name: Check Formatting 🔍
uses: ./.github/workflows/check-format.yaml
permissions:
contents: read
build-project:
name: Build Project 🧱
uses: ./.github/workflows/build-project.yaml
secrets: inherit
permissions:
contents: read
compatibility-validation:
name: Validate Compatibility Data 🕵️
if: github.base_ref == 'master'
runs-on: ubuntu-22.04
permissions:
checks: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: origin/${{ github.base_ref }}
checkGlob: plugins/win-capture/data/*.json
- name: Check for Invalid Compatibility Data 📉
if: fromJSON(steps.checks.outputs.hasChangedFiles)
uses: ./.github/actions/compatibility-validator
services-validation:
name: Validate Services Data 🕵️
if: github.base_ref == 'master'
runs-on: ubuntu-22.04
permissions:
checks: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: origin/${{ github.base_ref }}
checkGlob: plugins/rtmp-services/data/*.json
- name: Check Services JSON Schema 📉
if: fromJSON(steps.checks.outputs.hasChangedFiles)
uses: ./.github/actions/services-validator
with:
repositorySecret: ${{ secrets.GITHUB_TOKEN }}
runSchemaChecks: true
runServiceChecks: false
update-documentation:
name: Update Documentation 📖
if: github.repository_owner == 'obsproject' && github.base_ref == 'master'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: origin/${{ github.base_ref }}
checkGlob: docs/sphinx
- uses: ./.github/actions/generate-docs
if: fromJSON(steps.checks.outputs.hasChangedFiles)
+150
View File
@@ -0,0 +1,150 @@
name: Publish
run-name: Publish Repository Actions 🛫
on:
release:
types:
- published
branches:
- master
- 'release/**'
permissions:
contents: read
concurrency:
group: '${{ github.workflow }} @ ${{ github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
check-tag:
name: Check Release Tag
if: github.repository_owner == 'obsproject'
runs-on: ubuntu-22.04
outputs:
validTag: ${{ steps.check.outputs.validTag }}
flatpakMatrix: ${{ steps.check.outputs.flatpakMatrix }}
steps:
- name: Check Release Tag ☑️
id: check
run: |
: Check Release Tag ☑️
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
shopt -s extglob
case "${GITHUB_REF_NAME}" in
+([0-9]).+([0-9]).+([0-9]) )
echo 'validTag=true' >> $GITHUB_OUTPUT
echo 'flatpakMatrix=["beta", "stable"]' >> $GITHUB_OUTPUT
;;
+([0-9]).+([0-9]).+([0-9])-@(beta|rc)*([0-9]) )
echo 'validTag=true' >> $GITHUB_OUTPUT
echo 'flatpakMatrix=["beta"]' >> $GITHUB_OUTPUT
;;
*) echo 'validTag=false' >> $GITHUB_OUTPUT ;;
esac
flatpak-publish:
name: Publish to Flathub 📦
needs: check-tag
if: github.repository_owner == 'obsproject' && fromJSON(needs.check-tag.outputs.validTag)
runs-on: ubuntu-22.04
defaults:
run:
shell: bash
env:
FLATPAK_BUILD_PATH: flatpak_app/files/share
container:
image: bilelmoussaoui/flatpak-github-actions:kde-6.4
options: --privileged
strategy:
matrix:
branch: ${{ fromJSON(needs.check-tag.outputs.flatpakMatrix) }}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
set-safe-directory: ${{ github.workspace }}
- name: Set Up Environment 🔧
id: setup
env:
GH_TOKEN: ${{ github.token }}
run: |
: Set Up Environment 🔧
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
dnf install -y -q gh
gh extension install actions/gh-actions-cache
cache_key='flatpak-builder-${{ hashFiles('build-aux/**/*.json') }}'
cache_ref='master'
read -r key size unit _ ref _ <<< \
"$(gh actions-cache list -B ${cache_ref} --key "${cache_key}-x86_64" | head -1)"
if [[ "${key}" ]]; then
echo "cacheKey=${cache_key}" >> $GITHUB_OUTPUT
echo "cacheHit=true" >> $GITHUB_OUTPUT
else
echo "cacheHit=false" >> $GITHUB_OUTPUT
fi
echo "commitHash=$(git rev-parse --short=9 HEAD)" >> $GITHUB_OUTPUT
- name: Build Flatpak Manifest
uses: flatpak/flatpak-github-actions/flatpak-builder@v5
with:
bundle: obs-studio-${{ steps.setup.outputs.commitHash }}.flatpak
manifest-path: ${{ github.workspace }}/build-aux/com.obsproject.Studio.json
cache: ${{ fromJSON(steps.setup.outputs.cacheHit) }}
cache-key: ${{ steps.setup.outputs.cacheKey }}
mirror-screenshots-url: https://dl.flathub.org/repo/screenshots
branch: ${{ matrix.branch }}
- name: Validate AppStream
working-directory: ${{ env.FLATPAK_BUILD_PATH }}
run: |
: Validate AppStream
appstream-util validate appdata/com.obsproject.Studio.appdata.xml
- name: Verify Icon and Metadata in app-info
working-directory: ${{ env.FLATPAK_BUILD_PATH }}
run: |
: Verify Icon and Metadata in app-info
test -f app-info/icons/flatpak/128x128/com.obsproject.Studio.png || { echo "Missing 128x128 icon in app-info!"; exit 1; }
test -f app-info/xmls/com.obsproject.Studio.xml.gz || { echo "Missing com.obsproject.Studio.xml.gz in app-info!"; exit 1; }
- name: Commit Screenshots to OSTree Repository
run: |
: Commit Screenshots to OSTree Repository
ostree commit --repo=repo --canonical-permissions --branch=screenshots/x86_64 flatpak_app/screenshots
- name: Publish to Flathub Beta
uses: flatpak/flatpak-github-actions/flat-manager@v5
if: ${{ matrix.branch == 'beta' }}
with:
flat-manager-url: https://hub.flathub.org/
repository: beta
token: ${{ secrets.FLATHUB_BETA_TOKEN }}
- name: Publish to Flathub
uses: flatpak/flatpak-github-actions/flat-manager@v5
if: ${{ matrix.branch == 'stable' }}
with:
flat-manager-url: https://hub.flathub.org/
repository: stable
token: ${{ secrets.FLATHUB_TOKEN }}
steam-upload:
name: Upload Steam Builds 🚂
needs: check-tag
if: github.repository_owner == 'obsproject' && fromJSON(needs.check-tag.outputs.validTag)
runs-on: macos-13
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/steam-upload
with:
steamSecret: ${{ secrets.STEAM_SHARED_SECRET }}
steamUser: ${{ secrets.STEAM_USER }}
steamPassword: ${{ secrets.STEAM_PASSWORD }}
workflowSecret: ${{ github.token }}
preview: false
+313
View File
@@ -0,0 +1,313 @@
name: Push to master and release branches
run-name: ${{ github.ref_name }} push run 🚀
on:
push:
paths-ignore:
- '**.md'
branches:
- master
- 'release/**'
tags:
- '*'
permissions:
contents: write
concurrency:
group: '${{ github.workflow }} @ ${{ github.ref }}'
cancel-in-progress: ${{ github.ref_type == 'tag' }}
jobs:
check-format:
name: Check Formatting 🔍
if: github.ref_name == 'master'
uses: ./.github/workflows/check-format.yaml
permissions:
contents: read
build-project:
name: Build Project 🧱
uses: ./.github/workflows/build-project.yaml
secrets: inherit
permissions:
contents: read
compatibility-validation:
name: Validate Compatibility Data 🕵️
if: github.ref_name == 'master'
runs-on: ubuntu-22.04
permissions:
checks: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: ${{ github.event.before }}
checkGlob: plugins/win-capture/data/*.json
- name: Check for Invalid Compatibility Data 📉
if: fromJSON(steps.checks.outputs.hasChangedFiles)
uses: ./.github/actions/compatibility-validator
with:
repositorySecret: ${{ github.token }}
services-validation:
name: Validate Service Data 🕵️
if: github.ref_name == 'master'
runs-on: ubuntu-22.04
permissions:
checks: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: ${{ github.event.before }}
checkGlob: plugins/rtmp-services/data/*.json
- name: Check Services JSON Schema 📉
if: fromJSON(steps.checks.outputs.hasChangedFiles)
uses: ./.github/actions/services-validator
with:
repositorySecret: ${{ github.token }}
runSchemaChecks: true
runServiceChecks: false
upload-language-files:
name: Upload Language Files 🌐
if: github.repository_owner == 'obsproject' && github.ref_name == 'master'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- name: Check for Changed Files ✅
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: ${{ github.event.before }}
checkGlob: '**/en-US.ini'
- name: Upload US English Language Files 🇺🇸
if: fromJSON(steps.checks.outputs.hasChangedFiles)
uses: obsproject/obs-crowdin-sync/upload@0.2.1
env:
CROWDIN_PAT: ${{ secrets.CROWDIN_SYNC_CROWDIN_PAT }}
GITHUB_EVENT_BEFORE: ${{ github.event.before }}
update-documentation:
name: Update Documentation 📖
if: github.repository_owner == 'obsproject' && (github.ref_name == 'master' || github.ref_type == 'tag')
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check for Changed Files ✅
if: github.ref_name != 'tag'
uses: ./.github/actions/check-changes
id: checks
with:
baseRef: ${{ github.event.before }}
checkGlob: '!(cmake*)'
- uses: ./.github/actions/generate-docs
if: github.ref_type == 'tag' || fromJSON(steps.checks.outputs.hasChangedFiles)
with:
disableLinkExtensions: ${{ github.ref_type == 'tag' }}
deploy-documentation:
name: Deploy Documentation to Cloudflare ☁️
if: github.repository_owner == 'obsproject' && github.ref_type == 'tag'
runs-on: ubuntu-22.04
needs: update-documentation
defaults:
run:
shell: bash
steps:
- name: Get Commit Information 🆔
id: setup
run: |
: Get Commit Hash 🆔
echo "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
- uses: actions/download-artifact@v3
with:
name: OBS Studio Docs (No Extensions) ${{ steps.setup.outputs.commitHash }}
path: docs
- name: Set Up Redirects 🔄
run: |
: Set Up Redirects 🔄
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
echo "/previous/27.2 https://obsproject.com/docs/27.2 302" >> docs/_redirects
echo "/previous/:major.:minor https://:major-:minor.${{ vars.CF_PAGES_PROJECT }}.pages.dev 302" >> docs/_redirects
- name: Publish to Live Page
uses: cloudflare/wrangler-action@4c10c1822abba527d820b29e6333e7f5dac2cabd
with:
workingDirectory: docs
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
command: pages publish . --project-name=${{ vars.CF_PAGES_PROJECT }} --commit-hash='${{ steps.setup.outputs.commitHash }}'
create-appcast:
name: Create Sparkle Appcast 🎙️
if: github.repository_owner == 'obsproject' && github.ref_type == 'tag'
runs-on: macos-13
needs: build-project
strategy:
fail-fast: false
matrix:
target: [arm64, x86_64]
defaults:
run:
shell: zsh --no-rcs --errexit --pipefail {0}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
ref: ${{ github.ref }}
- name: Set Up Environment 🔧
id: setup
run: |
: Set Up Environment 🔧
if (( ${+RUNNER_DEBUG} )) setopt XTRACE
local channel='stable'
if [[ ${GITHUB_REF_NAME} == *(beta|rc)* ]] {
channel='beta'
}
local -A arch_names=(x86_64 intel arm64 apple)
print "cpuName=${arch_names[${{ matrix.target }}]}" >> $GITHUB_OUTPUT
print "commitHash=${GITHUB_SHA:0:9}" >> $GITHUB_OUTPUT
print "channel=${channel}" >> $GITHUB_OUTPUT
- name: Download Artifact 📥
uses: actions/download-artifact@v3
with:
name: obs-studio-macos-${{ matrix.target }}-${{ steps.setup.outputs.commitHash }}
- name: Generate Appcast 🎙️
id: generate-appcast
uses: ./.github/actions/sparkle-appcast
with:
sparklePrivateKey: ${{ secrets.SPARKLE_PRIVATE_KEY }}
baseImage: ${{ github.workspace }}/obs-studio-*-macos-${{ steps.setup.outputs.cpuName }}.dmg
channel: ${{ steps.setup.outputs.channel }}
count: 1
urlPrefix: 'https://cdn-fastly.obsproject.com/downloads'
customTitle: 'OBS Studio'
customLink: 'https://obsproject.com/'
- name: Upload Artifacts 📡
uses: actions/upload-artifact@v3
with:
name: macos-sparkle-update-${{ matrix.target }}
path: ${{ github.workspace }}/output
create-release:
name: Create Release 🛫
if: github.ref_type == 'tag'
runs-on: ubuntu-22.04
needs: build-project
defaults:
run:
shell: bash
steps:
- name: Check Release Tag ☑️
id: check
run: |
: Check Release Tag ☑️
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
shopt -s extglob
case "${GITHUB_REF_NAME}" in
+([0-9]).+([0-9]).+([0-9]) )
echo 'validTag=true' >> $GITHUB_OUTPUT
echo 'prerelease=false' >> $GITHUB_OUTPUT
echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
;;
+([0-9]).+([0-9]).+([0-9])-@(beta|rc)*([0-9]) )
echo 'validTag=true' >> $GITHUB_OUTPUT
echo 'prerelease=true' >> $GITHUB_OUTPUT
echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
;;
*) echo 'validTag=false' >> $GITHUB_OUTPUT ;;
esac
- name: Download Build Artifacts 📥
uses: actions/download-artifact@v3
if: ${{ fromJSON(steps.check.outputs.validTag) }}
- name: Rename Files 🏷️
if: fromJSON(steps.check.outputs.validTag)
run: |
: Rename Files 🏷️
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
root_dir="${PWD}"
commit_hash="${GITHUB_SHA:0:9}"
macos_arm64_artifact_name="obs-studio-macos-arm64-${commit_hash}"
macos_arm64_dsym_artifact_name="obs-studio-macos-arm64-${commit_hash}-dSYMs"
macos_intel_artifact_name="obs-studio-macos-x86_64-${commit_hash}"
macos_intel_dsym_artifact_name="obs-studio-macos-x86_64-${commit_hash}-dSYMs"
ubuntu_x86_64_artifact_name="obs-studio-ubuntu-22.04-x86_64-${commit_hash}"
ubuntu_x86_64_debug_name="obs-studio-ubuntu-22.04-x86_64-${commit_hash}-dbgsym"
echo '::group::Renaming Artifacts'
mv -v "${macos_arm64_artifact_name}/"obs-studio-*-macos-apple.dmg \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-macOS-Apple.dmg
mv -v "${macos_arm64_dsym_artifact_name}/"obs-studio-*-macos-apple-dSYMs.tar.xz \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-macOS-Apple-dSYMs.tar.xz
mv -v "${macos_intel_artifact_name}/"obs-studio-*-macos-intel.dmg \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-macOS-Intel.dmg
mv -v "${macos_intel_dsym_artifact_name}/"obs-studio-*-macos-intel-dSYMs.tar.xz \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-macOS-Intel-dSYMs.tar.xz
mv -v "${ubuntu_x86_64_artifact_name}/"obs-studio-*-x86_64-linux-gnu.deb \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-x86_64.deb
mv -v "${ubuntu_x86_64_debug_name}/"obs-studio-*-x86_64-linux-gnu-dbgsym.ddeb \
"${root_dir}"/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-x86_64-dbsym.ddeb
echo '::endgroup::'
- name: Generate Checksums 🪪
if: fromJSON(steps.check.outputs.validTag)
run: |
: Generate Checksums 🪪
if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi
shopt -s extglob
echo "### Checksums" > ${{ github.workspace }}/CHECKSUMS.txt
for file in ${{ github.workspace }}/@(*.deb|*.ddeb|*.dmg|*.tar.xz); do
echo " ${file##*/}: $(sha256sum "${file}" | cut -d " " -f 1)" >> ${{ github.workspace }}/CHECKSUMS.txt
done
- name: Create Release 🛫
if: fromJSON(steps.check.outputs.validTag)
id: create_release
uses: softprops/action-gh-release@d4e8205d7e959a9107da6396278b2f1f07af0f9b
with:
draft: true
prerelease: ${{ fromJSON(steps.check.outputs.prerelease) }}
tag_name: ${{ steps.check.outputs.version }}
name: OBS Studio ${{ steps.check.outputs.version }}
body_path: ${{ github.workspace }}/CHECKSUMS.txt
files: |
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-macOS-*.dmg
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-macOS-*-dSYMs.tar.xz
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-*.deb
${{ github.workspace }}/OBS-Studio-${{ steps.check.outputs.version }}-Ubuntu-*.ddeb
-30
View File
@@ -1,30 +0,0 @@
name: UI XML Validator
on:
push:
paths-ignore:
- "cmake/**"
pull_request:
paths:
- "UI/forms/**"
jobs:
qt-xml-validator:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Install xmllint
run: |
sudo apt-get -qq update
sudo apt-get install --no-install-recommends -y libxml2-utils
- name: Register Annotations
uses: korelstar/xmllint-problem-matcher@v1
- name: Validate
run: |
xmllint --schema UI/forms/XML-Schema-Qt5.15.xsd --noout UI/forms/*.ui UI/forms/**/*.ui
+121
View File
@@ -0,0 +1,121 @@
name: Scheduled
run-name: Scheduled Repository Actions ⏰
on:
schedule:
- cron: 17 0 * * *
permissions:
contents: write
concurrency:
group: '${{ github.workflow }} @ ${{ github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
services-availability:
name: Check Service Availability 🛜
if: github.repository_owner == 'obsproject'
runs-on: ubuntu-22.04
permissions:
checks: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set Up Homebrew 🍺
uses: Homebrew/actions/setup-homebrew@master
- name: Check for Defunct Services 📉
uses: ./.github/actions/services-validator
with:
repositorySecret: ${{ secrets.GITHUB_TOKEN }}
runSchemaChecks: false
runServiceChecks: true
createPullRequest: true
cache-cleanup:
name: Cache Cleanup 🧹
runs-on: ubuntu-22.04
permissions:
actions: write
steps:
- name: Remove Stale Ccache Caches
env:
GH_TOKEN: ${{ github.token }}
run: |
: Remove Stale Ccache Caches
echo '::group::Processing master branch cache entries'
while IFS=";" read -r cache_id cache_name; do
if [[ "${cache_name}" ]]; then
result=true
gh api -X DELETE repos/${GITHUB_REPOSITORY}/actions/caches?key=${cache_name} --jq '.total_count' &> /dev/null || result=false
if ${result}; then
echo "Deleted cache entry ${cache_name}"
else
echo "::warning::Unable to delete cache entry ${cache_name}"
fi
fi
done <<< \
"$(gh api repos/${GITHUB_REPOSITORY}/actions/caches \
--jq '.actions_caches.[] | select(.ref|test("refs/heads/master")) | select(.key|test(".*-ccache-*")) | {id, key} | join(";")')"
echo '::endgroup::'
echo '::group::Processing pull request cache entries'
while IFS=";" read -r cache_id cache_name cache_ref; do
if [[ "${cache_name}" ]]; then
result=true
gh api -X DELETE repos/${GITHUB_REPOSITORY}/actions/caches?key=${cache_name} --jq '.total_count' &> /dev/null || result=false
pr_number=$(echo ${cache_ref} | cut -d '/' -f 3)
if ${result}; then
echo "Deleted PR #${pr_number} cache entry ${cache_name}"
else
echo "::warning::Unable to delete PR #${pr_number} cache entry ${cache_name}"
fi
fi
done <<< \
"$(gh api repos/${GITHUB_REPOSITORY}/actions/caches \
--jq '.actions_caches.[] | select(.ref|test("refs/heads/master")|not) | select(.key|test(".*-ccache-*")) | {id, key, ref} | join(";")')"
echo '::endgroup::'
build-project:
name: Build Project 🧱
uses: ./.github/workflows/build-project.yaml
needs: cache-cleanup
secrets: inherit
steam-upload:
name: Upload Steam Builds 🚂
needs: [build-project]
if: github.repository_owner == 'obsproject'
runs-on: macos-13
defaults:
run:
shell: zsh --no-rcs --errexit --pipefail {0}
steps:
- uses: actions/checkout@v3
- name: Check Nightly Runs ☑️
id: checks
env:
GH_TOKEN: ${{ github.token }}
run: |
: Check Nightly Runs ☑️
if (( ${+RUNNER_DEBUG} )) setopt XTRACE
local last_nightly=$(gh run list --workflow scheduled.yaml --limit 1 --json headSha --jq '.[0].headSha')
if [[ "${GITHUB_SHA}" == "${last_nightly}" ]] {
print "passed=false" >> $GITHUB_OUTPUT
} else {
print "passed=true" >> $GITHUB_OUTPUT
}
- uses: ./.github/actions/steam-upload
if: fromJSON(steps.checks.outputs.passed)
with:
steamSecret: ${{ secrets.STEAM_SHARED_SECRET }}
steamUser: ${{ secrets.STEAM_USER }}
steamPassword: ${{ secrets.STEAM_PASSWORD }}
workflowSecret: ${{ secrets.GITHUB_TOKEN }}
preview: ${{ github.repository_owner != 'obsproject' }}
-93
View File
@@ -1,93 +0,0 @@
name: Services Validator
on:
push:
paths:
- "plugins/rtmp-services/data/services.json"
- "plugins/rtmp-services/data/package.json"
pull_request:
paths:
- "plugins/rtmp-services/data/services.json"
- "plugins/rtmp-services/data/package.json"
schedule:
- cron: 0 0 * * *
workflow_dispatch:
jobs:
schema:
name: Schema
runs-on: [ubuntu-20.04]
if: ${{ github.repository_owner == 'obsproject' || github.event_name != 'schedule' }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install & Configure Python
run: |
sudo apt install python3.9-dev
python3.9 -m pip install jsonschema json_source_map
- name: Validate Service JSON Schema
run: |
JSON_FILES=(
plugins/rtmp-services/data/services.json
plugins/rtmp-services/data/package.json
)
python3.9 CI/check-jsonschema.py "${JSON_FILES[@]}"
- name: Annotate Errors
if: failure()
uses: yuzutech/annotations-action@v0.4.0
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
title: "Service JSON Errors"
input: "./validation_errors.json"
service_check:
name: Service Check
runs-on: macos-latest
needs: schema
if: ${{ github.repository_owner == 'obsproject' && github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Restore cache
uses: actions/cache@v3
with:
path: ${{ github.workspace }}/other
# Workaround to create a new cache every time, since a cache key is immutable:
# https://github.com/actions/cache/blob/main/workarounds.md#update-a-cache
key: service-check-${{ github.run_id }}
restore-keys: service-check
- name: Install & Configure Python
run: |
python3 -m pip install requests
- name: Check Services
id: check
run: python3 -u CI/check-services.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WORKFLOW_RUN_ID: ${{ github.run_id }}
REPOSITORY: ${{ github.repository }}
- uses: actions/upload-artifact@v3
with:
name: timestamps
path: ${{ github.workspace }}/other/*
- name: Create Pull Request
uses: peter-evans/create-pull-request@36a56dac0739df8d3d8ebb9e6e41026ba248ec27
if: steps.check.outputs.make_pr == 'true'
with:
author: "Service Checker <commits@obsproject.com>"
commit-message: "rtmp-services: Remove defunct servers/services"
title: "rtmp-services: Remove defunct servers/services"
branch: "automated/clean-services"
body: ${{ fromJSON(steps.check.outputs.pr_message) }}
delete-branch: true
-266
View File
@@ -1,266 +0,0 @@
name: Steam Upload
on:
release:
types:
- published
workflow_dispatch:
inputs:
tag:
description: 'Tag to fetch and upload (nightly if none)'
required: false
win_url_override:
description: 'Windows build to use (.zip only)'
required: false
mac_url_override:
description: 'Mac build to use (.dmg only)'
required: false
mac_arm_url_override:
description: 'Mac ARM build to use (.dmg only)'
required: false
env:
WORKFLOW_ID: 583765
GIT_NIGHTLY_BRANCH: master
STEAM_NIGHTLY_BRANCH: nightly
STEAM_STABLE_BRANCH: staging
STEAM_BETA_BRANCH: beta_staging
STEAM_PLAYTEST_BRANCH: staging
jobs:
upload:
name: Steam upload
runs-on: macos-latest
if: github.repository_owner == 'obsproject'
steps:
- name: Checkout
uses: actions/checkout@v3
with:
path: source
- name: Get build information
id: build-info
run: |
EVENT='${{ github.event_name }}'
if [[ ${EVENT} == 'release' || ( ${EVENT} == 'workflow_dispatch' && -n '${{ github.event.inputs.tag }}') ]]; then
if [[ ${EVENT} == "release" ]]; then
DESC='${{ github.event.release.tag_name }}'
if [[ '${{ github.event.release.prerelease }}' == 'true' ]]; then
BRANCH='${{ env.STEAM_BETA_BRANCH }}'
else
BRANCH='${{ env.STEAM_STABLE_BRANCH }}'
fi
ASSETS_URL='${{ github.event.release.assets_url }}'
else
RELEASE="$(curl -s '${{ github.api_url }}/repos/obsproject/obs-studio/releases/tags/${{ github.event.inputs.tag }}')"
DESC="$(jq -r '.tag_name' <<< ${RELEASE})"
if [[ "$(jq -r '.prerelease' <<< ${RELEASE})" == 'true' ]]; then
BRANCH='${{ env.STEAM_BETA_BRANCH }}'
else
BRANCH='${{ env.STEAM_STABLE_BRANCH }}'
fi
ASSETS_URL="$(jq -r '.assets_url' <<< ${RELEASE})"
fi
ASSETS="$(curl -s "${ASSETS_URL}")"
WIN_ASSET_URL="$(jq -r '.[] | select(.name|test(".*.zip")) .browser_download_url' <<< ${ASSETS})"
MAC_ASSET_URL="$(jq -r '.[] | select(.name|test(".*x86_64.*.dmg")) .browser_download_url' <<< ${ASSETS})"
MAC_ARM_ASSET_URL="$(jq -r '.[] | select(.name|test(".*arm64.*.dmg")) .browser_download_url' <<< ${ASSETS})"
TYPE='release'
else
BRANCH='${{ env.STEAM_NIGHTLY_BRANCH }}'
BUILDS="$(curl -s '${{ github.api_url }}/repos/obsproject/obs-studio/actions/workflows/${{ env.WORKFLOW_ID }}/runs?per_page=1&event=push&status=success&branch=${{ env.GIT_NIGHTLY_BRANCH }}')"
ARTIFACTS_URL="$(jq -r '.workflow_runs[].artifacts_url' <<< ${BUILDS})"
DESC="g$(jq -r '.workflow_runs[].head_sha' <<< "${BUILDS}" | cut -c1-9)"
ARTIFACTS="$(curl -s ${ARTIFACTS_URL})"
WIN_ASSET_URL="$(jq -r '.artifacts[] | select(.name|test(".*windows-x64.*")) .archive_download_url' <<< ${ARTIFACTS})"
MAC_ASSET_URL="$(jq -r '.artifacts[] | select(.name|test(".*macos-x86_64.*")) .archive_download_url' <<< ${ARTIFACTS})"
MAC_ARM_ASSET_URL="$(jq -r '.artifacts[] | select(.name|test(".*macos-arm64.*")) .archive_download_url' <<< ${ARTIFACTS})"
TYPE='nightly'
fi
# Apply overrides from workflow_dispatch
if [[ ${EVENT} == 'workflow_dispatch' ]]; then
if [[ -n '${{ github.event.inputs.win_url_override }}' ]]; then
WIN_ASSET_URL='${{ github.event.inputs.win_url_override }}'
fi
if [[ -n '${{ github.event.inputs.mac_url_override }}' ]]; then
MAC_ASSET_URL='${{ github.event.inputs.mac_url_override }}'
fi
if [[ -n '${{ github.event.inputs.mac_arm_url_override }}' ]]; then
MAC_ARM_ASSET_URL='${{ github.event.inputs.mac_arm_url_override }}'
fi
fi
if [[ -z ${WIN_ASSET_URL} || -z ${MAC_ASSET_URL} || -z ${MAC_ARM_ASSET_URL} ]]; then
echo "Missing at least one asset URL!"
exit 1
fi
# set env variables for subsequent steps
echo "type=${TYPE}" >> $GITHUB_OUTPUT
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
echo "desc=${DESC}" >> $GITHUB_OUTPUT
echo "win_url=${WIN_ASSET_URL}" >> $GITHUB_OUTPUT
echo "mac_intel_url=${MAC_ASSET_URL}" >> $GITHUB_OUTPUT
echo "mac_arm_url=${MAC_ARM_ASSET_URL}" >> $GITHUB_OUTPUT
- name: Restore build cache
id: cache
uses: actions/cache@v3
with:
path: ${{ github.workspace }}/steam/build
key: ${{ steps.build-info.outputs.branch }}-${{ steps.build-info.outputs.desc }}
# Using "restore-keys" will restore the most recent cache for the branch, even if the exact cache doesn't exist.
# This doesn't set cache-hit to true so it won't skip the upload for nightlies.
restore-keys: ${{ steps.build-info.outputs.branch }}
- name: Determine if Steam upload should run
# If the nightly build has already been uploaded and thus a cache exists skip this and the following steps.
# Steam does not prevent us from uploading duplicate builds so this would just pollute the dashboard.
# This is a bit of a hack and can fail to work if our cache has been evicted or we somehow have no commits for 7 days,
# but it's better than nothing!
id: should-run
run: |
if [[ '${{ steps.build-info.outputs.type }}' == 'release' || '${{ steps.cache.outputs.cache-hit }}' != 'true' ]]; then
echo "result=true" >> $GITHUB_OUTPUT
if [[ '${{ steps.build-info.outputs.branch }}' == '${{ env.STEAM_BETA_BRANCH }}' ]]; then
echo "result_playtest=true" >> $GITHUB_OUTPUT
else
echo "result_playtest=false" >> $GITHUB_OUTPUT
fi
else
echo "result=false" >> $GITHUB_OUTPUT
echo "result_playtest=false" >> $GITHUB_OUTPUT
fi
- name: Download and prepare builds
if: steps.should-run.outputs.result == 'true'
run: |
echo "::group::Download Windows build"
if [[ '${{ steps.build-info.outputs.win_url }}' == *'api.github.com'* ]]; then
curl -L -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' '${{ steps.build-info.outputs.win_url }}' -o windows.zip
else
curl -L '${{ steps.build-info.outputs.win_url }}' -o windows.zip
fi
echo "::endgroup::"
echo "::group::Download Mac builds"
if [[ '${{ steps.build-info.outputs.mac_intel_url }}' == *'api.github.com'* ]]; then
curl -L -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' '${{ steps.build-info.outputs.mac_intel_url }}' -o mac_x86.dmg.zip
else
curl -L '${{ steps.build-info.outputs.mac_intel_url }}' -o mac_x86.dmg
fi
if [[ '${{ steps.build-info.outputs.mac_arm_url }}' == *'api.github.com'* ]]; then
curl -L -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' '${{ steps.build-info.outputs.mac_arm_url }}' -o mac_arm64.dmg.zip
else
curl -L '${{ steps.build-info.outputs.mac_arm_url }}' -o mac_arm64.dmg
fi
echo "::endgroup::"
mkdir -p steam && cd steam
echo "::group::Extract and prepare Win64"
mkdir steam-windows
(
cd steam-windows
unzip ../../windows.zip
# CI builds can be double-zipped
if compgen -G "*.zip" > /dev/null; then
unzip *.zip
rm *.zip
fi
# copy install scripts and create sentinel file
cp -r ../../source/CI/steam/scripts_windows scripts
touch disable_updater
)
echo "::endgroup::"
echo "::group::Extract macOS (x86)"
mkdir -p steam-macos/x86
# CI builds are zipped
if [[ -f ../mac_x86.dmg.zip ]]; then
unzip ../mac_x86.dmg.zip
else
mv ../mac_x86.dmg .
fi
hdiutil attach -noverify -readonly -noautoopen -mountpoint /Volumes/x86 *.dmg
cp -R /Volumes/x86/OBS.app steam-macos/x86
hdiutil unmount /Volumes/x86
rm *.dmg
echo "::endgroup::"
echo "::group::Extract and prepare macOS (arm64)"
mkdir -p steam-macos/arm64
if [[ -f ../mac_arm64.dmg.zip ]]; then
unzip ../mac_arm64.dmg.zip
else
mv ../mac_arm64.dmg .
fi
hdiutil attach -noverify -readonly -noautoopen -mountpoint /Volumes/arm64 *.dmg
cp -R /Volumes/arm64/OBS.app steam-macos/arm64
hdiutil unmount /Volumes/arm64
rm *.dmg
cp ../source/CI/steam/scripts_macos/launch.sh steam-macos/launch.sh
echo "::endgroup::"
- name: Setup steamcmd
if: steps.should-run.outputs.result == 'true'
uses: CyberAndrii/setup-steamcmd@b786e0da44db3d817e66fa3910a9560cb28c9323
- name: Generate Steam auth code
if: steps.should-run.outputs.result == 'true'
id: steam-totp
uses: CyberAndrii/steam-totp@c7f636bc64e77f1b901e0420b7890813141508ee
with:
shared_secret: ${{ secrets.STEAM_SHARED_SECRET }}
- name: Upload to Steam
if: steps.should-run.outputs.result == 'true'
run: |
cd steam
echo "::group::Prepare Steam build script"
# The description in Steamworks for the build will be "github_<branch>-<tag/short hash>", e.g. "github_nightly-gaa73de952"
sed 's/@@DESC@@/${{ steps.build-info.outputs.branch }}-${{ steps.build-info.outputs.desc }}/;s/@@BRANCH@@/${{ steps.build-info.outputs.branch }}/' ../source/CI/steam/obs_build.vdf > build.vdf
echo "Generated file:"
cat build.vdf
echo "::endgroup::"
echo "::group::Upload to Steam"
steamcmd +login '${{ secrets.STEAM_USER }}' '${{ secrets.STEAM_PASSWORD }}' '${{ steps.steam-totp.outputs.code }}' +run_app_build "$(pwd)/build.vdf" +quit
echo "::endgroup::"
- name: Generate Steam auth code (Playtest)
if: steps.should-run.outputs.result_playtest == 'true'
id: steam-totp-playtest
uses: CyberAndrii/steam-totp@c7f636bc64e77f1b901e0420b7890813141508ee
with:
shared_secret: ${{ secrets.STEAM_SHARED_SECRET }}
- name: Upload to Steam (Playtest)
if: steps.should-run.outputs.result_playtest == 'true'
run: |
cd steam
echo "::group::Prepare Steam build script"
sed 's/@@DESC@@/${{ steps.build-info.outputs.branch }}-${{ steps.build-info.outputs.desc }}/;s/@@BRANCH@@/${{ env.STEAM_PLAYTEST_BRANCH }}/' ../source/CI/steam/obs_playtest_build.vdf > build_playtest.vdf
echo "Generated file:"
cat build_playtest.vdf
echo "::endgroup::"
echo "::group::Upload to Steam"
steamcmd +login '${{ secrets.STEAM_USER }}' '${{ secrets.STEAM_PASSWORD }}' '${{ steps.steam-totp-playtest.outputs.code }}' +run_app_build "$(pwd)/build_playtest.vdf" +quit
echo "::endgroup::"
- name: Upload Steam build logs
if: steps.should-run.outputs.result == 'true'
uses: actions/upload-artifact@v3
with:
name: steam-build-logs
path: ${{ github.workspace }}/steam/build/*.log
+41 -100
View File
@@ -1,108 +1,49 @@
#binaries
*.exe
*.dll
*.dylib
*.so
*.plugin
*.framework
*.systemextension
# Exclude everything
/*
#cmake
/build*/
!/build-aux/
/release*/
/debug*/
.vs/
*.o.d
*.ninja
.ninja*
.dirstamp
/cmake/.CMakeBuildNumber
.deps
CMakeUserPresets.json
# 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
#xcode
*.xcodeproj/
/xcodebuild/
# Exclude lock files
*.lock.json
#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 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
*~
.DS_Store
*/.DS_Store
*/**/.DS_Store
# Exclude modified Flatpak files
build-aux/flatpak-github-action-modified-*
#flatpak
/.flatpak-builder/
/_flatpak_build/
/flatpak_app/
/repo/
/CI/flatpak/flatpak-github-action-modified-*
# Exclude macOS legacy resource forks
.DS_Store
# Exclude CMake build number cache
/cmake/.CMakeBuildNumber
-105
View File
@@ -1,105 +0,0 @@
#!/bin/bash
##############################################################################
# Linux full build script
##############################################################################
#
# This script contains all steps necessary to:
#
# * Build OBS with all default plugins and dependencies
# * Package a Linux deb package
#
# Parameters:
# -h, --help : Print usage help
# -q, --quiet : Suppress most build process output
# -v, --verbose : Enable more verbose build process output
# -d, --skip-dependency-checks : Skip dependency checks (default: off)
# -p, --portable : Create portable build (default: off)
# -pkg, --package : Create distributable disk image
# (default: off)
# --build-dir : Specify alternative build directory
# (default: build)"
#
##############################################################################
# Halt on errors
set -eE
## SET UP ENVIRONMENT ##
_RUN_OBS_BUILD_SCRIPT=TRUE
PRODUCT_NAME="OBS-Studio"
CHECKOUT_DIR="$(git rev-parse --show-toplevel)"
DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies"
source "${CHECKOUT_DIR}/CI/include/build_support.sh"
source "${CHECKOUT_DIR}/CI/include/build_support_linux.sh"
## DEPENDENCY INSTALLATION
source "${CHECKOUT_DIR}/CI/linux/01_install_dependencies.sh"
## BUILD OBS ##
source "${CHECKOUT_DIR}/CI/linux/02_build_obs.sh"
## PACKAGE OBS AND NOTARIZE ##
source "${CHECKOUT_DIR}/CI/linux/03_package_obs.sh"
## MAIN SCRIPT FUNCTIONS ##
print_usage() {
echo "build-linux.sh - Build script for OBS-Studio\n"
echo -e "Usage: ${0}\n" \
"-h, --help : Print this help\n" \
"-q, --quiet : Suppress most build process output\n" \
"-v, --verbose : Enable more verbose build process output\n" \
"-d, --skip-dependency-checks : Skip dependency checks (default: off)\n" \
"-p, --portable : Create portable build (default: off)\n" \
"-pkg, --package : Create distributable disk image (default: off)\n" \
"--disable-pipewire : Disable building with PipeWire support (default: off)\n" \
"--build-dir : Specify alternative build directory (default: build)\n"
}
obs-build-main() {
while true; do
case "${1}" in
-h | --help ) print_usage; exit 0 ;;
-q | --quiet ) export QUIET=TRUE; shift ;;
-v | --verbose ) export VERBOSE=TRUE; shift ;;
-d | --skip-dependency-checks ) SKIP_DEP_CHECKS=TRUE; shift ;;
-p | --portable ) PORTABLE=TRUE; shift ;;
-pkg | --package ) PACKAGE=TRUE; shift ;;
--disable-pipewire ) DISABLE_PIPEWIRE=TRUE; shift ;;
--build-dir ) BUILD_DIR="${2}"; shift 2 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
ensure_dir "${CHECKOUT_DIR}"
step "Fetching OBS tags..."
git fetch origin --tags
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
GIT_HASH=$(git rev-parse --short HEAD)
GIT_TAG=$(git describe --tags --abbrev=0)
if [ "${BUILD_FOR_DISTRIBUTION}" ]; then
VERSION_STRING="${GIT_TAG}"
else
VERSION_STRING="${GIT_TAG}-${GIT_HASH}"
fi
FILE_NAME="obs-studio-${VERSION_STRING}-Linux.deb"
if [ -z "${SKIP_DEP_CHECKS}" ]; then
install_dependencies
fi
build_obs
if [ "${PACKAGE}" ]; then
package_obs
fi
cleanup
}
obs-build-main $*
-143
View File
@@ -1,143 +0,0 @@
#!/bin/bash
##############################################################################
# macOS build script
##############################################################################
#
# This script contains all steps necessary to:
#
# * Build OBS with all default plugins and dependencies
# * Create a macOS application bundle
# * Codesign the macOS application bundle
# * Package a macOS installation image
# * Notarize macOS application bundle and/or installation image
#
# Parameters:
# -h, --help : Print usage help
# -q, --quiet : Suppress most build process output
# -v, --verbose : Enable more verbose build process output
# -d, --skip-dependency-checks : Skip dependency checks (default: off)
# -b, --bundle : Create relocatable application bundle
# (default: off)
# -p, --package : Create distributable disk image
# (default: off)
# -c, --codesign : Codesign OBS and all libraries
# (default: ad-hoc only)
# -n, --notarize : Notarize OBS (default: off)
#
##############################################################################
# Halt on errors
set -eE
## SET UP ENVIRONMENT ##
_RUN_OBS_BUILD_SCRIPT=TRUE
PRODUCT_NAME="OBS-Studio"
CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)"
DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies"
source "${CHECKOUT_DIR}/CI/include/build_support.sh"
source "${CHECKOUT_DIR}/CI/include/build_support_macos.sh"
## INSTALL DEPENDENCIES ##
source "${CHECKOUT_DIR}/CI/macos/01_install_dependencies.sh"
## BUILD OBS ##
source "${CHECKOUT_DIR}/CI/macos/02_build_obs.sh"
## PACKAGE OBS AND NOTARIZE ##
source "${CHECKOUT_DIR}/CI/macos/03_package_obs.sh"
## MAIN SCRIPT FUNCTIONS ##
print_usage() {
echo "build-macos.sh - Build script for OBS-Studio"
echo -e "Usage: ${0}\n" \
"-h, --help : Print this help\n" \
"-q, --quiet : Suppress most build process output\n" \
"-v, --verbose : Enable more verbose build process output\n" \
"-a, --architecture : Specify build architecture (default: x86_64, alternative: arm64)\n" \
"-d, --skip-dependency-checks : Skip dependency checks (default: off)\n" \
"-b, --bundle : Create relocatable application bundle (default: off)\n" \
"-p, --package : Create distributable disk image (default: off)\n" \
"-c, --codesign : Codesign OBS and all libraries (default: ad-hoc only)\n" \
"-n, --notarize : Notarize OBS (default: off)\n"
}
print_deprecation() {
echo -e "DEPRECATION ERROR:\n" \
"The '${1}' switch has been deprecated!\n"
if [ "${1}" = "-s" ]; then
echo -e "The macOS build script system has changed:\n" \
" - To configure and build OBS, run the script 'CI/macos/02_build_obs.sh'\n" \
" - To bundle OBS into a relocatable application bundle, run the script 'CI/macos/02_build_obs.sh --bundle\n" \
" - To package OBS, run the script 'CI/macos/03_package_obs.sh'\n" \
" - To notarize OBS, run the script 'CI/macos/03_package_obs.sh --notarize'\n"
fi
}
obs-build-main() {
while true; do
case "${1}" in
-h | --help ) print_usage; exit 0 ;;
-q | --quiet ) export QUIET=TRUE; shift ;;
-v | --verbose ) export VERBOSE=TRUE; shift ;;
-a | --architecture ) ARCH="${2}"; shift 2 ;;
-d | --skip-dependency-checks ) SKIP_DEP_CHECKS=TRUE; shift ;;
-p | --package ) PACKAGE=TRUE; shift ;;
-c | --codesign ) CODESIGN=TRUE; shift ;;
-n | --notarize ) NOTARIZE=TRUE; PACKAGE=TRUE CODESIGN=TRUE; shift ;;
-b | --bundle ) BUNDLE=TRUE; shift ;;
-s ) print_deprecation ${1}; exit 1 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
ensure_dir "${CHECKOUT_DIR}"
check_archs
check_macos_version
step "Fetching OBS tags..."
/usr/bin/git fetch origin --tags
GIT_BRANCH=$(/usr/bin/git rev-parse --abbrev-ref HEAD)
GIT_HASH=$(/usr/bin/git rev-parse --short HEAD)
GIT_TAG=$(/usr/bin/git describe --tags --abbrev=0)
if [ "${BUILD_FOR_DISTRIBUTION}" ]; then
VERSION_STRING="${GIT_TAG}"
else
VERSION_STRING="${GIT_TAG}-${GIT_HASH}"
fi
if [ "${ARCH}" = "arm64" ]; then
FILE_NAME="obs-studio-${VERSION_STRING}-macOS-Apple.dmg"
elif [ "${ARCH}" = "universal" ]; then
FILE_NAME="obs-studio-${VERSION_STRING}-macOS.dmg"
else
FILE_NAME="obs-studio-${VERSION_STRING}-macOS-Intel.dmg"
fi
if [ -z "${SKIP_DEP_CHECKS}" ]; then
install_dependencies
fi
build_obs
if [ "${BUNDLE}" ]; then
bundle_obs
fi
if [ "${PACKAGE}" ]; then
package_obs
fi
if [ "${NOTARIZE}" ]; then
notarize_obs
fi
cleanup
}
obs-build-main $*
-11
View File
@@ -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
-53
View File
@@ -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 ./.deps -o \
-path ./\*build\* -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}
-59
View File
@@ -1,59 +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 ./.deps -o \
-path ./\*build\* -o \
-path ./cmake -o \
-path ./plugins/decklink/\*/decklink-sdk -o \
-path ./plugins/enc-amf -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
-84
View File
@@ -1,84 +0,0 @@
import json
from jsonschema import Draft7Validator
from json_source_map import calculate
from json_source_map.errors import InvalidInputError
import os
import sys
errors = []
def main():
if len(sys.argv) < 2:
print("JSON path required.")
return 1
for filename in sys.argv[1:]:
prep(filename)
try:
with open('validation_errors.json', 'w') as outfile:
json.dump(errors, outfile)
except OSError as e:
print(f'Failed to write validation output to file: {e}')
return 1
if errors:
return 1
return 0
def prep(filename):
try:
with open(filename) as json_file:
json_string = json_file.read()
json_data = json.loads(json_string)
except OSError as e:
print(f'Failed to load file "{filename}": {e}')
return
schema_filename = json_data.get('$schema')
if not schema_filename:
print('File has no schema:', filename)
return
file_path = os.path.split(filename)[0]
schema_file = os.path.join(file_path, schema_filename)
try:
with open(schema_file) as json_file:
schema = json.load(json_file)
except OSError as e:
print(f'Failed to load schema file "{schema_file}": {e}')
return
validate(filename, json_data, json_string, schema)
def validate(filename, json_data, json_string, schema):
try:
servicesPaths = calculate(json_string)
except InvalidInputError as e:
print("Error with file:", e)
return
cls = Draft7Validator(schema)
for e in sorted(cls.iter_errors(json_data), key=str):
print(f'{e}\nIn "{filename}"\n\n')
errorPath = '/'.join(str(v) for v in e.absolute_path)
errorEntry = servicesPaths['/' + errorPath]
errors.append({
"file": filename,
"start_line": errorEntry.value_start.line + 1,
"end_line": errorEntry.value_end.line + 1,
"title": "Validation Error",
"message": e.message,
"annotation_level": "failure"
})
if __name__ == '__main__':
sys.exit(main())
-44
View File
@@ -1,44 +0,0 @@
#!/bin/bash
##############################################################################
# Linux support functions
##############################################################################
#
# This script file can be included in build scripts for Linux.
#
##############################################################################
# Setup build environment
CI_LINUX_CEF_VERSION=$(cat "${CI_WORKFLOW}" | sed -En "s/[ ]+CEF_BUILD_VERSION_LINUX: '([0-9]+)'/\1/p")
if [ "${TERM-}" -a -z "${CI}" ]; then
COLOR_RED=$(tput setaf 1)
COLOR_GREEN=$(tput setaf 2)
COLOR_BLUE=$(tput setaf 4)
COLOR_ORANGE=$(tput setaf 3)
COLOR_RESET=$(tput sgr0)
else
COLOR_RED=""
COLOR_GREEN=""
COLOR_BLUE=""
COLOR_ORANGE=""
COLOR_RESET=""
fi
if [ "${CI}" -o "${QUIET}" ]; then
export CURLCMD="curl --silent --show-error --location -O"
else
export CURLCMD="curl --progress-bar --location --continue-at - -O"
fi
_add_ccache_to_path() {
if [ "${CMAKE_CCACHE_OPTIONS}" ]; then
PATH="/usr/local/opt/ccache/libexec:${PATH}"
status "Compiler Info:"
local IFS=$'\n'
for COMPILER_INFO in $(type cc c++ gcc g++ clang clang++ || true); do
info "${COMPILER_INFO}"
done
fi
}
-156
View File
@@ -1,156 +0,0 @@
#!/bin/bash
##############################################################################
# macOS support functions
##############################################################################
#
# This script file can be included in build scripts for macOS.
#
##############################################################################
# Setup build environment
WORKFLOW_CONTENT=$(/bin/cat "${CI_WORKFLOW}")
MACOS_VERSION="$(/usr/bin/sw_vers -productVersion)"
MACOS_MAJOR="$(echo ${MACOS_VERSION} | /usr/bin/cut -d '.' -f 1)"
MACOS_MINOR="$(echo ${MACOS_VERSION} | /usr/bin/cut -d '.' -f 2)"
if [ "${TERM-}" -a -z "${CI}" ]; then
COLOR_RED=$(/usr/bin/tput setaf 1)
COLOR_GREEN=$(/usr/bin/tput setaf 2)
COLOR_BLUE=$(/usr/bin/tput setaf 4)
COLOR_ORANGE=$(/usr/bin/tput setaf 3)
COLOR_RESET=$(/usr/bin/tput sgr0)
else
COLOR_RED=""
COLOR_GREEN=""
COLOR_BLUE=""
COLOR_ORANGE=""
COLOR_RESET=""
fi
## DEFINE UTILITIES ##
check_macos_version() {
ARCH="${ARCH:-${CURRENT_ARCH}}"
case "${ARCH}" in
x86_64) ;;
arm64) ;;
*) caught_error "Unsupported architecture '${ARCH}' provided" ;;
esac
step "Check macOS version..."
MIN_VERSION="11.0"
MIN_MAJOR=$(echo ${MIN_VERSION} | /usr/bin/cut -d '.' -f 1)
MIN_MINOR=$(echo ${MIN_VERSION} | /usr/bin/cut -d '.' -f 2)
if [ "${MACOS_MAJOR}" -lt "11" -a "${MACOS_MINOR}" -lt "${MIN_MINOR}" ]; then
error "ERROR: Minimum required macOS version is ${MIN_VERSION}, but running on ${MACOS_VERSION}"
fi
export CODESIGN_LINKER="ON"
}
install_homebrew_deps() {
if ! exists brew; then
caught_error "Homebrew not found - please install Homebrew (https://brew.sh)"
fi
brew bundle --file "${CHECKOUT_DIR}/CI/include/Brewfile" ${QUIET:+--quiet}
check_curl
}
check_curl() {
if [ "${MACOS_MAJOR}" -lt "11" -a "${MACOS_MINOR}" -lt "15" ]; then
if [ ! -d /usr/local/opt/curl ]; then
step "Install Homebrew curl..."
brew install curl
fi
CURLCMD="/usr/local/opt/curl/bin/curl"
else
CURLCMD="curl"
fi
if [ "${CI}" -o "${QUIET}" ]; then
export CURLCMD="${CURLCMD} --silent --show-error --location -O"
else
export CURLCMD="${CURLCMD} --progress-bar --location --continue-at - -O"
fi
}
check_archs() {
step "Check Architecture..."
ARCH="${ARCH:-${CURRENT_ARCH}}"
if [ "${ARCH}" = "universal" ]; then
CMAKE_ARCHS="x86_64;arm64"
elif [ "${ARCH}" != "x86_64" -a "${ARCH}" != "arm64" ]; then
caught_error "Unsupported architecture '${ARCH}' provided"
else
CMAKE_ARCHS="${ARCH}"
fi
}
_add_ccache_to_path() {
if [ "${CMAKE_CCACHE_OPTIONS}" ]; then
if [ "${CURRENT_ARCH}" == "arm64" ]; then
PATH="/opt/homebrew/opt/ccache/libexec:${PATH}"
else
PATH="/usr/local/opt/ccache/libexec:${PATH}"
fi
status "Compiler Info:"
local IFS=$'\n'
for COMPILER_INFO in $(type cc c++ gcc g++ clang clang++ || true); do
info "${COMPILER_INFO}"
done
fi
}
## SET UP CODE SIGNING AND NOTARIZATION CREDENTIALS ##
##############################################################################
# Apple Developer Identity needed:
#
# + Signing the code requires a developer identity in the system's keychain
# + codesign will look up and find the identity automatically
#
##############################################################################
read_codesign_ident() {
if [ -z "${CODESIGN_IDENT}" ]; then
step "Set up code signing..."
read -p "${COLOR_ORANGE} + Apple developer identity: ${COLOR_RESET}" CODESIGN_IDENT
fi
CODESIGN_IDENT_SHORT=$(echo "${CODESIGN_IDENT}" | /usr/bin/sed -En "s/.+\((.+)\)/\1/p")
}
##############################################################################
# Apple Developer credentials necessary:
#
# + Signing for distribution and notarization require an active Apple
# Developer membership
# + An Apple Development identity is needed for code signing
# (i.e. 'Apple Development: YOUR APPLE ID (PROVIDER)')
# + Your Apple developer ID is needed for notarization
# + An app-specific password is necessary for notarization from CLI
# + This password will be stored in your macOS keychain under the identifier
# 'OBS-Codesign-Password' with access Apple's 'notarytool' only.
##############################################################################
read_codesign_pass() {
step "Set up notarization..."
if [ -z "${CODESIGN_IDENT_USER}" ]; then
read -p "${COLOR_ORANGE} + Apple account id: ${COLOR_RESET}" CODESIGN_IDENT_USER
fi
if [ -z "${CODESIGN_IDENT_PASS}" ]; then
CODESIGN_IDENT_PASS=$(stty -echo; read -p "${COLOR_ORANGE} + Apple developer password: ${COLOR_RESET}" secret; stty echo; echo $secret)
echo ""
fi
step "Update notarization keychain..."
echo -n "${COLOR_ORANGE}"
/usr/bin/xcrun notarytool store-credentials "OBS-Codesign-Password" --apple-id "${CODESIGN_IDENT_USER}" --team-id "${CODESIGN_IDENT_SHORT}" --password "${CODESIGN_IDENT_PASS}"
echo -n "${COLOR_RESET}"
}
-152
View File
@@ -1,152 +0,0 @@
#!/bin/bash
##############################################################################
# Linux dependency management function
##############################################################################
#
# This script file can be included in build scripts for Linux or run directly
# with the -s/--standalone switch
#
##############################################################################
# Halt on errors
set -eE
install_build-deps() {
shift
status "Install OBS build dependencies"
trap "caught_error 'install_build-deps'" ERR
sudo apt-get install --no-install-recommends -y $@
}
install_obs-deps() {
shift
status "Install OBS dependencies"
trap "caught_error 'install_obs-deps'" ERR
if [ -z "${DISABLE_PIPEWIRE}" ]; then
sudo apt-get install --no-install-recommends -y $@ libpipewire-0.3-dev
else
sudo apt-get install --no-install-recommends -y $@
fi
}
install_qt5-deps() {
shift
status "Install Qt5 dependencies"
trap "caught_error 'install_qt5-deps'" ERR
_QT6_AVAILABLE="$(sudo apt-cache madison qt6-base-dev)"
if [ -z "${_QT6_AVAILABLE}" ]; then
sudo apt-get install --no-install-recommends -y $@
fi
}
install_qt6-deps() {
shift
status "Install Qt6 dependencies"
trap "caught_error 'install_qt6-deps'" ERR
_QT6_AVAILABLE="$(sudo apt-cache madison ${1})"
if [ "${_QT6_AVAILABLE}" ]; then
sudo apt-get install --no-install-recommends -y $@
fi
}
install_cef() {
shift
status "Setup for dependency CEF v${1}"
ensure_dir "${DEPS_BUILD_DIR}"
if [ "${CI}" -a "${RESTORED_CEF}" ]; then
_SKIP=TRUE
elif [ -d "${DEPS_BUILD_DIR}/cef_binary_${1}_linux64" -a -f "${DEPS_BUILD_DIR}/cef_binary_${1}_linux64/build/libcef_dll_wrapper/libcef_dll_wrapper.a" ]; then
_SKIP=TRUE
fi
if [ -z "${_SKIP}" ]; then
step "Download..."
${CURLCMD:-curl -O} https://cdn-fastly.obsproject.com/downloads/cef_binary_${1}_linux64.tar.bz2
step "Unpack..."
tar -xf cef_binary_${1}_linux64.tar.bz2
else
step "Found existing Chromium Embedded Framework and loader library..."
fi
}
install_plugin-deps() {
shift
status "Install plugin dependencies"
trap "caught_error 'install_plugin-deps'" ERR
sudo apt-get install --no-install-recommends -y $@
}
install_dependencies() {
status "Set up apt"
trap "caught_error 'install_dependencies'" ERR
BUILD_DEPS=(
"build-deps cmake ninja-build pkg-config clang clang-format build-essential curl ccache"
"obs-deps libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libswresample-dev \
libswscale-dev libx264-dev libcurl4-openssl-dev libmbedtls-dev libgl1-mesa-dev libjansson-dev \
libluajit-5.1-dev python3-dev libx11-dev libxcb-randr0-dev libxcb-shm0-dev libxcb-xinerama0-dev \
libxcb-composite0-dev libxinerama-dev libxcb1-dev libx11-xcb-dev libxcb-xfixes0-dev swig libcmocka-dev \
libpci-dev libxss-dev libglvnd-dev libgles2-mesa libgles2-mesa-dev libwayland-dev \
libxkbcommon-dev"
"qt5-deps qtbase5-dev qtbase5-private-dev libqt5svg5-dev qtwayland5"
"qt6-deps qt6-base-dev qt6-base-private-dev libqt6svg6-dev qt6-wayland"
"cef ${LINUX_CEF_BUILD_VERSION:-${CI_LINUX_CEF_VERSION}}"
"plugin-deps libasound2-dev libfdk-aac-dev libfontconfig-dev libfreetype6-dev libjack-jackd2-dev \
libpulse-dev libsndio-dev libspeexdsp-dev libudev-dev libv4l-dev libva-dev libvlc-dev libdrm-dev \
nlohmann-json3-dev libwebsocketpp-dev libasio-dev libvpl-dev libqrcodegencpp-dev"
)
sudo apt-get -qq update
for DEPENDENCY in "${BUILD_DEPS[@]}"; do
set -- ${DEPENDENCY}
trap "caught_error ${DEPENDENCY}" ERR
FUNC_NAME="install_${1}"
${FUNC_NAME} ${@}
done
}
install-dependencies-standalone() {
CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)"
PRODUCT_NAME="OBS-Studio"
DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies"
source "${CHECKOUT_DIR}/CI/include/build_support.sh"
source "${CHECKOUT_DIR}/CI/include/build_support_linux.sh"
status "Setup of OBS build dependencies"
install_dependencies
}
print_usage() {
echo -e "Usage: ${0}\n" \
"-h, --help : Print this help\n" \
"-q, --quiet : Suppress most build process output\n" \
"-v, --verbose : Enable more verbose build process output\n" \
"--disable-pipewire : Disable building with PipeWire support (default: off)\n"
}
install-dependencies-main() {
if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then
while true; do
case "${1}" in
-h | --help ) print_usage; exit 0 ;;
-q | --quiet ) export QUIET=TRUE; shift ;;
-v | --verbose ) export VERBOSE=TRUE; shift ;;
--disable-pipewire ) DISABLE_PIPEWIRE=TRUE; shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
install-dependencies-standalone
fi
}
install-dependencies-main $*
-135
View File
@@ -1,135 +0,0 @@
#!/bin/bash
##############################################################################
# Linux build function
##############################################################################
#
# This script file can be included in build scripts for Linux or run directly
#
##############################################################################
# Halt on errors
set -eE
build_obs() {
status "Build OBS"
trap "caught_error 'build app'" ERR
if [ -z "${CI}" ]; then
_backup_artifacts
fi
step "Configure OBS..."
_configure_obs
ensure_dir "${CHECKOUT_DIR}/"
step "Build OBS targets..."
cmake --build ${BUILD_DIR}
}
# Function to configure OBS build
_configure_obs() {
ensure_dir "${CHECKOUT_DIR}"
status "Configuration of OBS build system..."
trap "caught_error 'configure build'" ERR
check_ccache
if [ "${TWITCH_CLIENTID}" -a "${TWITCH_HASH}" ]; then
TWITCH_OPTIONS="-DTWITCH_CLIENTID='${TWITCH_CLIENTID}' -DTWITCH_HASH='${TWITCH_HASH}'"
fi
if [ "${RESTREAM_CLIENTID}" -a "${RESTREAM_HASH}" ]; then
RESTREAM_OPTIONS="-DRESTREAM_CLIENTID='${RESTREAM_CLIENTID}' -DRESTREAM_HASH='${RESTREAM_HASH}'"
fi
if [ "${YOUTUBE_CLIENTID}" -a "${YOUTUBE_CLIENTID_HASH}" -a "${YOUTUBE_SECRET}" -a "{YOUTUBE_SECRET_HASH}" ]; then
YOUTUBE_OPTIONS="-DYOUTUBE_CLIENTID='${YOUTUBE_CLIENTID}' -DYOUTUBE_CLIENTID_HASH='${YOUTUBE_CLIENTID_HASH}' -DYOUTUBE_SECRET='${YOUTUBE_SECRET}' -DYOUTUBE_SECRET_HASH='${YOUTUBE_SECRET_HASH}'"
fi
if [ "${PORTABLE}" ]; then
PORTABLE_BUILD="ON"
fi
if [ "${DISABLE_PIPEWIRE}" ]; then
PIPEWIRE_OPTION="-DENABLE_PIPEWIRE=OFF"
fi
if [ "${DISABLE_QSV}" ]; then
QSV_OPTION="-DENABLE_QSV11=OFF"
fi
cmake -S . -B ${BUILD_DIR} -G Ninja \
-DCEF_ROOT_DIR="${DEPS_BUILD_DIR}/cef_binary_${LINUX_CEF_BUILD_VERSION:-${CI_LINUX_CEF_VERSION}}_linux64" \
-DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \
-DLINUX_PORTABLE=${PORTABLE_BUILD:-OFF} \
-DENABLE_AJA=OFF \
-DENABLE_NEW_MPEGTS_OUTPUT=OFF \
-DENABLE_WEBRTC=OFF \
${PIPEWIRE_OPTION} \
${QSV_OPTION} \
${YOUTUBE_OPTIONS} \
${TWITCH_OPTIONS} \
${RESTREAM_OPTIONS} \
${CI:+-DENABLE_UNIT_TESTS=ON -DBUILD_FOR_DISTRIBUTION=${BUILD_FOR_DISTRIBUTION} -DOBS_BUILD_NUMBER=${GITHUB_RUN_ID}} \
${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR}
}
# Function to backup previous build artifacts
_backup_artifacts() {
ensure_dir "${CHECKOUT_DIR}"
if [ -d ${BUILD_DIR} ]; then
status "Backup of old OBS build artifacts"
CUR_DATE=$(date +"%Y-%m-%d@%H%M%S")
NIGHTLY_DIR="${CHECKOUT_DIR}/nightly-${CUR_DATE}"
PACKAGE_NAME=$(find ${BUILD_DIR} -maxdepth 1 -name "*.deb" | sort -rn | head -1)
if [ "${PACKAGE_NAME}" ]; then
step "Back up $(basename "${PACKAGE_NAME}")..."
ensure_dir "${NIGHTLY_DIR}"
mv "../${BUILD_DIR}/$(basename "${PACKAGE_NAME}")" ${NIGHTLY_DIR}/
info "You can find ${PACKAGE_NAME} in ${NIGHTLY_DIR}"
fi
fi
}
build-obs-standalone() {
CHECKOUT_DIR="$(git rev-parse --show-toplevel)"
PRODUCT_NAME="OBS-Studio"
DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies"
source "${CHECKOUT_DIR}/CI/include/build_support.sh"
source "${CHECKOUT_DIR}/CI/include/build_support_linux.sh"
build_obs
}
print_usage() {
echo -e "Usage: ${0}\n" \
"-h, --help : Print this help\n" \
"-q, --quiet : Suppress most build process output\n" \
"-v, --verbose : Enable more verbose build process output\n" \
"-p, --portable : Create portable build (default: off)\n" \
"--disable-pipewire : Disable building with PipeWire support (default: off)\n" \
"--build-dir : Specify alternative build directory (default: build)\n"
}
build-obs-main() {
if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then
while true; do
case "${1}" in
-h | --help ) print_usage; exit 0 ;;
-q | --quiet ) export QUIET=TRUE; shift ;;
-v | --verbose ) export VERBOSE=TRUE; shift ;;
-p | --portable ) export PORTABLE=TRUE; shift ;;
--disable-pipewire ) DISABLE_PIPEWIRE=TRUE; shift ;;
--disable-qsv ) DISABLE_QSV=TRUE; shift ;;
--build-dir ) BUILD_DIR="${2}"; shift 2 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
build-obs-standalone
fi
}
build-obs-main $*
-90
View File
@@ -1,90 +0,0 @@
#!/bin/bash
##############################################################################
# Linux libobs plugin package function
##############################################################################
#
# This script file can be included in build scripts for Linux or run directly
#
##############################################################################
# Halt on errors
set -eE
package_obs() {
status "Create Linux debian package"
trap "caught_error 'package app'" ERR
ensure_dir "${CHECKOUT_DIR}"
step "Package OBS..."
cmake --build ${BUILD_DIR} -t package
DEB_NAME=$(find ${BUILD_DIR} -maxdepth 1 -type f -name "obs*.deb" | sort -rn | head -1)
DEBUG_NAME="${DEB_NAME//.deb/-dbgsym.ddeb}"
if [ "${DEB_NAME}" ]; then
mv "${DEB_NAME}" "${BUILD_DIR}/${FILE_NAME}"
if [ "${DEBUG_NAME}" ]; then
mv "${DEBUG_NAME}" "${BUILD_DIR}/${FILE_NAME//.deb/-dbgsym.ddeb}"
fi
else
error "ERROR No suitable OBS debian package generated"
fi
}
package-obs-standalone() {
PRODUCT_NAME="OBS-Studio"
CHECKOUT_DIR="$(git rev-parse --show-toplevel)"
DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies"
source "${CHECKOUT_DIR}/CI/include/build_support.sh"
source "${CHECKOUT_DIR}/CI/include/build_support_linux.sh"
if [ -z "${CI}" ]; then
step "Fetch OBS tags..."
git fetch --tags origin
fi
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
GIT_HASH=$(git rev-parse --short=9 HEAD)
GIT_TAG=$(git describe --tags --abbrev=0)
UBUNTU_VERSION=$(lsb_release -sr)
if [ "${BUILD_FOR_DISTRIBUTION}" = "true" ]; then
VERSION_STRING="${GIT_TAG}"
else
VERSION_STRING="${GIT_TAG}-${GIT_HASH}"
fi
FILE_NAME="obs-studio-${VERSION_STRING}-ubuntu-${UBUNTU_VERSION}.deb"
package_obs
}
print_usage() {
echo -e "Usage: ${0}\n" \
"-h, --help : Print this help\n" \
"-q, --quiet : Suppress most build process output\n" \
"-v, --verbose : Enable more verbose build process output\n" \
"--build-dir : Specify alternative build directory (default: build)\n"
}
package-obs-main() {
if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then
while true; do
case "${1}" in
-h | --help ) print_usage; exit 0 ;;
-q | --quiet ) export QUIET=TRUE; shift ;;
-v | --verbose ) export VERBOSE=TRUE; shift ;;
--build-dir ) BUILD_DIR="${2}"; shift 2 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
package-obs-standalone
fi
}
package-obs-main $*
-60
View File
@@ -1,60 +0,0 @@
#!/bin/bash
##############################################################################
# macOS dependency management function
##############################################################################
#
# This script file can be included in build scripts for macOS or run directly.
#
##############################################################################
# Halt on errors
set -eE
install_dependencies() {
status "Install Homebrew dependencies"
trap "caught_error 'install_dependencies'" ERR
install_homebrew_deps
}
install-dependencies-standalone() {
CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)"
PRODUCT_NAME="OBS-Studio"
DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies"
source "${CHECKOUT_DIR}/CI/include/build_support.sh"
source "${CHECKOUT_DIR}/CI/include/build_support_macos.sh"
status "Setup of OBS build dependencies"
check_macos_version
check_archs
install_dependencies
}
print_usage() {
echo -e "Usage: ${0}\n" \
"-h, --help : Print this help\n" \
"-q, --quiet : Suppress most build process output\n" \
"-v, --verbose : Enable more verbose build process output\n" \
"-a, --architecture : Specify build architecture (default: x86_64, alternative: arm64)\n"
}
install-dependencies-main() {
if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then
while true; do
case "${1}" in
-h | --help ) print_usage; exit 0 ;;
-q | --quiet ) export QUIET=TRUE; shift ;;
-v | --verbose ) export VERBOSE=TRUE; shift ;;
-a | --architecture ) ARCH="${2}"; shift 2 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
install-dependencies-standalone
fi
}
install-dependencies-main $*
-147
View File
@@ -1,147 +0,0 @@
#!/bin/bash
##############################################################################
# macOS build function
##############################################################################
#
# This script file can be included in build scripts for macOS or run directly
#
##############################################################################
# Halt on errors
set -eE
build_obs() {
status "Build OBS"
trap "caught_error 'build app'" ERR
step "Configure OBS..."
_configure_obs
ensure_dir "${CHECKOUT_DIR}/"
step "Build OBS targets..."
export NSUnbufferedIO=YES
: "${PACKAGE:=}"
case "${GITHUB_EVENT_NAME}" in
push) if [[ ${GITHUB_REF_NAME} =~ [0-9]+.[0-9]+.[0-9]+(-(rc|beta).+)? ]]; then PACKAGE=1; fi ;;
pull_request) PACKAGE=1 ;;
esac
pushd "build_macos" > /dev/null
if [[ "${PACKAGE}" && "${CODESIGN_IDENT:--}" != '-' ]]; then
set -o pipefail && xcodebuild ONLY_ACTIVE_ARCH=NO -archivePath "obs-studio.xcarchive" -scheme obs-studio -destination "generic/platform=macOS,name=Any Mac" -parallelizeTargets -hideShellScriptEnvironment archive 2>&1 | xcbeautify
set -o pipefail && xcodebuild -exportArchive -archivePath "obs-studio.xcarchive" -exportOptionsPlist "exportOptions.plist" -exportPath "." 2>&1 | xcbeautify
else
set -o pipefail && xcodebuild ONLY_ACTIVE_ARCH=NO -project obs-studio.xcodeproj -target obs-studio -destination "generic/platform=macOS,name=Any Mac" -parallelizeTargets -configuration RelWithDebInfo -hideShellScriptEnvironment build 2>&1 | xcbeautify
rm -rf OBS.app && mkdir OBS.app
ditto UI/RelWithDebInfo/OBS.app OBS.app
fi
popd > /dev/null
unset NSUnbufferedIO
}
bundle_obs() {
status "Create relocatable macOS application bundle"
trap "caught_error 'package app'" ERR
ensure_dir "${CHECKOUT_DIR}"
step "Install OBS application bundle..."
find "build_macos/UI/${BUILD_CONFIG}" -type d -name "OBS.app" | xargs -I{} cp -r {} "build_${ARCH}"/
}
# Function to configure OBS build
_configure_obs() {
if [ "${CODESIGN}" ]; then
read_codesign_ident
fi
ensure_dir "${CHECKOUT_DIR}"
status "Configure OBS build system..."
trap "caught_error 'configure build'" ERR
check_ccache
if [ "${SPARKLE_APPCAST_URL}" -a "${SPARKLE_PUBLIC_KEY}" ]; then
SPARKLE_OPTIONS="-DSPARKLE_APPCAST_URL=\"${SPARKLE_APPCAST_URL}\" -DSPARKLE_PUBLIC_KEY=\"${SPARKLE_PUBLIC_KEY}\""
fi
PRESET="macos"
if [ "${CI}" ]; then
case "${GITHUB_EVENT_NAME}" in
push)
if [ "${GITHUB_REF_TYPE}" != 'tag' ]; then
PRESET="macos-ci"
fi
;;
*) PRESET="macos-ci" ;;
esac
fi
cmake -S . --preset ${PRESET} \
-DCMAKE_OSX_ARCHITECTURES=${ARCH} \
-DCMAKE_INSTALL_PREFIX=${BUILD_DIR}/install \
-DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \
-DOBS_CODESIGN_IDENTITY="${CODESIGN_IDENT:--}" \
-DOBS_CODESIGN_TEAM="${CODESIGN_TEAM:-}" \
-DOBS_PROVISIONING_PROFILE="${PROVISIONING_PROFILE:-}" \
${YOUTUBE_OPTIONS} \
${TWITCH_OPTIONS} \
${RESTREAM_OPTIONS} \
${SPARKLE_OPTIONS} \
${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR}
}
build-obs-standalone() {
CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)"
PRODUCT_NAME="OBS-Studio"
DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies"
source "${CHECKOUT_DIR}/CI/include/build_support.sh"
source "${CHECKOUT_DIR}/CI/include/build_support_macos.sh"
check_archs
check_macos_version
build_obs
if [ "${BUNDLE}" ]; then
bundle_obs
fi
}
print_usage() {
echo -e "Usage: ${0}\n" \
"-h, --help : Print this help\n" \
"-q, --quiet : Suppress most build process output\n" \
"-v, --verbose : Enable more verbose build process output\n" \
"-a, --architecture : Specify build architecture (default: x86_64, alternative: arm64)\n" \
"-c, --codesign : Codesign OBS and all libraries (default: ad-hoc only)\n" \
"-b, --bundle : Create relocatable OBS application bundle in build directory (default: build/install/OBS.app)\n"
}
build-obs-main() {
if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then
while true; do
case "${1}" in
-h | --help ) print_usage; exit 0 ;;
-q | --quiet ) export QUIET=TRUE; shift ;;
-v | --verbose ) export VERBOSE=TRUE; shift ;;
-a | --architecture ) ARCH="${2}"; shift 2 ;;
-c | --codesign ) CODESIGN=TRUE; shift ;;
-b | --bundle ) BUNDLE=TRUE; shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
build-obs-standalone
fi
}
build-obs-main $*
-184
View File
@@ -1,184 +0,0 @@
#!/bin/bash
##############################################################################
# macOS libobs plugin package function
##############################################################################
#
# This script file can be included in build scripts for macOS or run directly
#
##############################################################################
# Halt on errors
set -eE
package_obs() {
if [ "${CODESIGN}" ]; then
read_codesign_ident
fi
status "Create macOS disk image"
trap "caught_error 'package app'" ERR
ensure_dir "${CHECKOUT_DIR}"
step "Package OBS..."
BUILD_DIR="build_macos"
root_dir="$(pwd)"
pushd "${BUILD_DIR}" > /dev/null > /dev/null
mkdir -p "${FILE_NAME//.dmg/}/.background"
cp "${root_dir}/cmake/macos/resources/background.tiff" "${FILE_NAME//.dmg/}/.background/"
cp "${root_dir}/cmake/macos/resources/AppIcon.icns" "${FILE_NAME//.dmg/}/.VolumeIcon.icns"
ln -s /Applications "${FILE_NAME//.dmg/}/Applications"
mkdir -p "${FILE_NAME//.dmg/}/OBS.app"
ditto OBS.app "${FILE_NAME//.dmg/}/OBS.app"
hdiutil create -volname "${FILE_NAME//.dmg/}" -srcfolder "${FILE_NAME//.dmg/}" -ov -fs APFS -format UDRW temp.dmg
hdiutil attach -noverify -readwrite temp.dmg
SetFile -c icnC /Volumes/"${FILE_NAME//.dmg/}"/.VolumeIcon.icns
SetFile -a C /Volumes/"${FILE_NAME//.dmg/}"
osascript package.applescript "${FILE_NAME//.dmg/}"
hdiutil detach "/Volumes/${FILE_NAME//.dmg/}"
hdiutil convert -format ULMO -o "${FILE_NAME}" temp.dmg
rm temp.dmg
step "Codesign OBS disk image..."
/usr/bin/codesign --force --sign "${CODESIGN_IDENT:--}" "${FILE_NAME}"
rm -rf "${FILE_NAME//.dmg/}"
popd > /dev/null
}
notarize_obs() {
status "Notarize OBS"
trap "caught_error 'notarizing app'" ERR
if ! exists brew; then
error "ERROR Homebrew not found - please install homebrew (https://brew.sh)"
exit 1
fi
ensure_dir "${CHECKOUT_DIR}"
if [ "${NOTARIZE_IMAGE}" ]; then
trap "_caught_error_hdiutil_verify '${NOTARIZE_IMAGE}'" ERR
step "Verify OBS disk image ${NOTARIZE_IMAGE}..."
hdiutil verify "${NOTARIZE_IMAGE}"
NOTARIZE_TARGET="${NOTARIZE_IMAGE}"
elif [ "${NOTARIZE_BUNDLE}" ]; then
NOTARIZE_TARGET="${NOTARIZE_BUNDLE}"
else
OBS_IMAGE="${BUILD_DIR}/${FILE_NAME}"
if [ -f "${OBS_IMAGE}" ]; then
NOTARIZE_TARGET="${OBS_IMAGE}"
else
error "No notarization application bundle ('OBS.app') or disk image ('${NOTARIZE_IMAGE:-${FILE_NAME}}') found"
return
fi
fi
if [ "$?" -eq 0 ]; then
read_codesign_ident
read_codesign_pass
step "Notarize ${NOTARIZE_TARGET}..."
/usr/bin/xcrun notarytool submit "${NOTARIZE_TARGET}" --keychain-profile "OBS-Codesign-Password" --wait
step "Staple the ticket to ${NOTARIZE_TARGET}..."
/usr/bin/xcrun stapler staple "${NOTARIZE_TARGET}"
fi
}
_caught_error_hdiutil_verify() {
error "ERROR during verifying image '${1}'"
cleanup
exit 1
}
package-obs-standalone() {
PRODUCT_NAME="OBS-Studio"
CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)"
DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies"
source "${CHECKOUT_DIR}/CI/include/build_support.sh"
source "${CHECKOUT_DIR}/CI/include/build_support_macos.sh"
check_archs
check_macos_version
if [ -z "${CI}" ]; then
step "Fetch OBS tags..."
/usr/bin/git fetch --tags origin
fi
GIT_BRANCH=$(/usr/bin/git rev-parse --abbrev-ref HEAD)
GIT_HASH=$(/usr/bin/git rev-parse --short=9 HEAD)
GIT_TAG=$(/usr/bin/git describe --tags --abbrev=0)
if [ "${BUILD_FOR_DISTRIBUTION}" ]; then
VERSION_STRING="${GIT_TAG}"
else
VERSION_STRING="${GIT_TAG}-${GIT_HASH}"
fi
if [ -z "${NOTARIZE_IMAGE}" -a -z "${NOTARIZE_BUNDLE}" ]; then
if [ "${ARCH}" = "arm64" ]; then
FILE_NAME="obs-studio-${VERSION_STRING}-macos-arm64.dmg"
elif [ "${ARCH}" = "universal" ]; then
FILE_NAME="obs-studio-${VERSION_STRING}-macos.dmg"
else
FILE_NAME="obs-studio-${VERSION_STRING}-macos-x86_64.dmg"
fi
package_obs
fi
if [ "${NOTARIZE}" ]; then
notarize_obs
fi
}
print_usage() {
echo -e "Usage: ${0}\n" \
"-h, --help : Print this help\n" \
"-q, --quiet : Suppress most build process output\n" \
"-v, --verbose : Enable more verbose build process output\n" \
"-a, --architecture : Specify build architecture (default: x86_64, alternative: arm64)\n" \
"-c, --codesign : Codesign OBS and all libraries (default: ad-hoc only)\n" \
"-n, --notarize : Notarize OBS (default: off)\n" \
"--notarize-image [IMAGE] : Specify existing OBS disk image for notarization\n" \
"--notarize-bundle [BUNDLE] : Specify existing OBS application bundle for notarization\n" \
"--build-dir : Specify alternative build directory (default: build)\n"
}
package-obs-main() {
if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then
while true; do
case "${1}" in
-h | --help ) print_usage; exit 0 ;;
-q | --quiet ) export QUIET=TRUE; shift ;;
-v | --verbose ) export VERBOSE=TRUE; shift ;;
-a | --architecture ) ARCH="${2}"; shift 2 ;;
-c | --codesign ) CODESIGN=TRUE; shift ;;
-n | --notarize ) NOTARIZE=TRUE; CODESIGN=TRUE; shift ;;
--build-dir ) BUILD_DIR="${2}"; shift 2 ;;
--notarize-image ) NOTARIZE_IMAGE="${2}"; NOTARIZE=TRUE; CODESIGN=TRUE; shift 2 ;;
--notarize-bundle ) NOTARIZE_BUNDLE="${2}"; NOTARIZE=TRUE; CODESIGN=TRUE; shift 2 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
package-obs-standalone
fi
}
package-obs-main $*
-96
View File
@@ -1,96 +0,0 @@
import os
from copy import deepcopy
import xmltodict
DELTA_BASE_URL = "https://cdn-fastly.obsproject.com/downloads/sparkle_deltas"
def convert_appcast(filename):
print("Converting", filename)
in_path = os.path.join("output/appcasts", filename)
out_path = os.path.join("output/appcasts/stable", filename.replace("_v2", ""))
with open(in_path, "rb") as f:
xml_data = f.read()
if not xml_data:
return
appcast = xmltodict.parse(xml_data, force_list=("item",))
out_appcast = deepcopy(appcast)
# Remove anything but stable channel items.
new_list = []
for _item in appcast["rss"]["channel"]["item"]:
item = deepcopy(_item)
branch = item.pop("sparkle:channel", "stable")
if branch != "stable":
continue
# Remove delta information (incompatible with Sparkle 1.x)
item.pop("sparkle:deltas", None)
new_list.append(item)
out_appcast["rss"]["channel"]["item"] = new_list
with open(out_path, "wb") as f:
xmltodict.unparse(out_appcast, output=f, pretty=True)
# Also create legacy appcast from x86 version.
if "x86" in filename:
out_path = os.path.join("output/appcasts/stable", "updates.xml")
with open(out_path, "wb") as f:
xmltodict.unparse(out_appcast, output=f, pretty=True)
def adjust_appcast(filename):
print("Adjusting", filename)
file_path = os.path.join("output/appcasts", filename)
with open(file_path, "rb") as f:
xml_data = f.read()
if not xml_data:
return
arch = "arm64" if "arm64" in filename else "x86_64"
appcast = xmltodict.parse(xml_data, force_list=("item", "enclosure"))
out_appcast = deepcopy(appcast)
out_appcast["rss"]["channel"]["title"] = "OBS Studio"
out_appcast["rss"]["channel"]["link"] = "https://obsproject.com/"
new_list = []
for _item in appcast["rss"]["channel"]["item"]:
item = deepcopy(_item)
# Fix changelog URL
# Sparkle doesn't allow us to specify the URL for a specific update,
# so we set the full release notes link instead and then rewrite the
# appcast. Yay.
if release_notes_link := item.pop("sparkle:fullReleaseNotesLink", None):
item["sparkle:releaseNotesLink"] = release_notes_link
# If deltas exist, update their URLs to match server layout
# (generate_appcast doesn't allow this).
if deltas := item.get("sparkle:deltas", None):
for delta_item in deltas["enclosure"]:
delta_filename = delta_item["@url"].rpartition("/")[2]
delta_item["@url"] = f"{DELTA_BASE_URL}/{arch}/{delta_filename}"
new_list.append(item)
out_appcast["rss"]["channel"]["item"] = new_list
with open(file_path, "wb") as f:
xmltodict.unparse(out_appcast, output=f, pretty=True)
if __name__ == "__main__":
for ac_file in os.listdir("output/appcasts"):
if ".xml" not in ac_file:
continue
if "v2" not in ac_file:
# generate_appcast may download legacy appcast files and update them as well.
# Those generated files are not backwards-compatible, so delete whatever v1
# files it may have created and recreate them manually.
os.remove(os.path.join("output/appcasts", ac_file))
continue
adjust_appcast(ac_file)
convert_appcast(ac_file)
-125
View File
@@ -1,125 +0,0 @@
import os
import sys
import plistlib
import glob
import subprocess
import argparse
import requests
import xmltodict
def download_build(url):
print(f'Downloading build "{url}"...')
filename = url.rpartition("/")[2]
r = requests.get(url)
if r.status_code == 200:
with open(f"artifacts/{filename}", "wb") as f:
f.write(r.content)
else:
print(f"Build download failed, status code: {r.status_code}")
sys.exit(1)
def read_appcast(url):
print(f"Downloading feed {url} ...")
r = requests.get(url)
if r.status_code != 200:
print(f"Appcast download failed, status code: {r.status_code}")
sys.exit(1)
filename = url.rpartition("/")[2]
with open(f"builds/{filename}", "wb") as f:
f.write(r.content)
appcast = xmltodict.parse(r.content, force_list=("item",))
dl = 0
for item in appcast["rss"]["channel"]["item"]:
channel = item.get("sparkle:channel", "stable")
if channel != target_branch:
continue
if dl == max_old_vers:
break
download_build(item["enclosure"]["@url"])
dl += 1
def get_appcast_url(artifact_dir):
dmgs = glob.glob(artifact_dir + "/*.dmg")
if not dmgs:
raise ValueError("No artifacts!")
elif len(dmgs) > 1:
raise ValueError("Too many artifacts!")
dmg = dmgs[0]
print(f"Mounting {dmg} ...")
out = subprocess.check_output(
[
"hdiutil",
"attach",
"-readonly",
"-noverify",
"-noautoopen",
"-plist",
dmg,
]
)
d = plistlib.loads(out)
mountpoint = ""
for item in d["system-entities"]:
if "mount-point" not in item:
continue
mountpoint = item["mount-point"]
break
url = None
plist_files = glob.glob(mountpoint + "/*.app/Contents/Info.plist")
if plist_files:
plist_file = plist_files[0]
print(f"Reading plist {plist_file} ...")
plist = plistlib.load(open(plist_file, "rb"))
url = plist.get("SUFeedURL", None)
else:
print("No Plist file found!")
print(f"Unmounting {mountpoint}")
subprocess.check_call(["hdiutil", "detach", mountpoint])
return url
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--artifacts-dir",
dest="artifacts",
action="store",
default="artifacts",
help="Folder containing artifact",
)
parser.add_argument(
"--branch",
dest="branch",
action="store",
default="stable",
help="Channel/Branch",
)
parser.add_argument(
"--max-old-versions",
dest="max_old_ver",
action="store",
type=int,
default=1,
help="Maximum old versions to download",
)
args = parser.parse_args()
target_branch = args.branch
max_old_vers = args.max_old_ver
url = get_appcast_url(args.artifacts)
if not url:
raise ValueError("Failed to get Sparkle URL from DMG!")
read_appcast(url)
-27
View File
@@ -1,27 +0,0 @@
#!/bin/zsh
arch_name="$(uname -m)"
# When the script is launched from Steam, it'll be run through Rosetta.
# Manually override arch to arm64 in that case.
if [ "$(sysctl -in sysctl.proc_translated)" = "1" ]; then
arch_name="arm64"
fi
# Allow users to force Rosetta
if [[ "$@" =~ \-\-intel ]]; then
arch_name="x86_64"
fi
# legacy app installation
if [ -d OBS.app ]; then
exec open OBS.app -W --args "$@"
fi
if [ "${arch_name}" = "x86_64" ]; then
exec open x86/OBS.app -W --args "$@"
elif [ "${arch_name}" = "arm64" ]; then
exec open arm64/OBS.app -W --args "$@"
else
echo "Unknown architecture: ${arch_name}"
fi
@@ -9,9 +9,7 @@
<key>com.obsproject.obs-studio</key>
<string>${OBS_PROVISIONING_PROFILE}</string>
</dict>
<key>signingCertificate</key>
<string>${OBS_CODESIGN_IDENTITY}</string>
<key>signingStyle</key>
<string>manual</string>
<string>automatic</string>
</dict>
</plist>
+1 -3
View File
@@ -4,9 +4,7 @@
<dict>
<key>method</key>
<string>developer-id</string>
<key>signingCertificate</key>
<string>${OBS_CODESIGN_IDENTITY}</string>
<key>signingStyle</key>
<string>manual</string>
<string>automatic</string>
</dict>
</plist>
+3
View File
@@ -0,0 +1,3 @@
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
if (( _loglevel > 2 )) print -PR -e -- "${CI:+::debug::}%F{220}DEBUG: ${@}%f"
+3
View File
@@ -0,0 +1,3 @@
local icon=' ✖︎ '
print -u2 -PR "${CI:+::error::}%F{1} ${icon} %f ${@}"
+16
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,7 @@
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
if (( _loglevel > 0 )) {
local icon=''
print -PR " ${(r:5:)icon} ${@}"
}
+7
View File
@@ -0,0 +1,7 @@
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
if (( _loglevel > 0 )) {
local icon=' >'
print -PR "%F{2} ${(r:5:)icon}%f ${@}"
}
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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 ${@}
+83 -27
View File
@@ -1,40 +1,96 @@
import argparse
import glob
import json
import logging
import os
import sys
from typing import Any
MAIN_MANIFEST_FILENAME = "com.obsproject.Studio.json"
def main():
dir_path = os.path.dirname(os.path.realpath(__file__))
if not os.path.isfile(os.path.join(dir_path, MAIN_MANIFEST_FILENAME)):
print("The script is not ran in the same folder as the manifest")
return 1
def main() -> int:
parser = argparse.ArgumentParser(description="Format Flatpak manifest")
parser.add_argument(
"manifest_file",
metavar="FILE",
type=str,
help="Manifest file to adjust format for",
)
parser.add_argument(
"--check",
action="store_true",
help="Check for necessary changes only",
default=False,
required=False,
)
parser.add_argument(
"--loglevel", type=str, help="Set log level", default="WARNING", required=False
)
for root, dirs, files in os.walk(dir_path):
for file in files:
if not file.endswith(".json"):
continue
arguments = parser.parse_args()
print(f"Formatting {file}")
# Load JSON file
with open(os.path.join(root, file), "r") as f:
j = json.load(f)
logging.basicConfig(level=arguments.loglevel, format="%(message)s")
logger = logging.getLogger()
if file == MAIN_MANIFEST_FILENAME:
# Sort module files order in the manifest
# Assumption: All modules except the last are strings
file_modules = j["modules"][0:-1]
last_module = j["modules"][-1]
file_modules.sort(key=lambda file_name: file_name)
j["modules"] = file_modules
j["modules"].append(last_module)
manifest_file = arguments.manifest_file
# Overwrite JSON file
with open(os.path.join(root, file), "w") as f:
json.dump(j, f, indent=4, ensure_ascii=False)
f.write("\n")
try:
with open(manifest_file, "r+") as manifest:
manifest_path = os.path.dirname(manifest_file)
manifest_string = manifest.read()
manifest_data = json.loads(manifest_string)
module_list = manifest_data.get("modules", [])
obs_object = module_list[-1]
if type(obs_object) != dict:
logger.error(
f"❌ Last element in modules list is not the obs-studio object"
)
return 2
new_module_list = []
for module in module_list:
if type(module) == str:
if not os.path.isfile(os.path.join(manifest_path, module)):
logger.warning(
f"⚠️ Specified module {os.path.basename(module)} not found."
)
continue
new_module_list.append(module)
new_module_list.sort()
new_module_list.append(obs_object)
manifest_data["modules"] = new_module_list
new_manifest_string = (
f"{json.dumps(manifest_data, indent=4, ensure_ascii=False)}\n"
)
if arguments.check:
if new_module_list != module_list:
logger.error(f"❌ Module list failed order validation")
return 2
elif new_manifest_string != manifest_string:
logger.error(f"❌ Manifest file is not correctly formatted")
return 2
else:
logger.info(f"✅ Module list passed order validation")
return 0
manifest.seek(0)
manifest.truncate()
manifest.write(new_manifest_string)
logger.info(f"✅ Updated manifest file '{manifest_file}")
except IOError:
logger.error(f"❌ Unable to read manifest file '{manifest_file}'")
return 2
return 0
if __name__ == '__main__':
if __name__ == "__main__":
sys.exit(main())
+1
View File
@@ -0,0 +1 @@
.run-format.zsh
+1
View File
@@ -0,0 +1 @@
.run-format.zsh
+1
View File
@@ -0,0 +1 @@
.run-format.zsh
+14
View File
@@ -0,0 +1,14 @@
#!/bin/zsh
arch_name="${CPUTYPE}"
is_translated="$(sysctl -in sysctl.proc_translated)"
if (( is_translated )) arch_name="arm64"
if [[ ${@} == *'--intel'* ]] arch_name="x86_64"
if [[ -d OBS.app ]] exec open OBS.app -W --args "${@}"
case ${arch_name} {
x86_64) exec open x86_64/OBS.app -W --args "${@}" ;;
arm64) exec open arm64/OBS.app -W --args "${@}" ;;
*) echo "Unknown architecture: ${arch_name}"; exit 2 ;;
}

Some files were not shown because too many files have changed in this diff Show More