Skip to content

Continuous Build & Release #47

Continuous Build & Release

Continuous Build & Release #47

name: Continuous Build & Release
on:
workflow_dispatch:
push:
branches: [master]
paths:
- "cmake_modules/**"
- "lib/**"
- "cli/**"
- "tests/**"
- "CMakeLists.txt"
- "CMakePresets.json"
- "vcpkg.json"
- ".github/workflows/continuos-release.yml"
# Cancel in-progress runs when a new commit is pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
defaults:
run:
shell: ${{ matrix.shell }}
strategy:
fail-fast: false
matrix:
include:
# Windows (MSVC)
- os: windows-latest
os_name: windows
architecture: x86_64
shell: pwsh
build_type: msvc
# Linux
- os: ubuntu-22.04
os_name: linux
architecture: x86_64
shell: bash
build_type: unix
# macOS (ARM64)
- os: macos-latest
os_name: macos
architecture: arm64
shell: bash
build_type: unix
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
# =========== Linux Setup ===========
- name: Install dependencies (Linux)
if: matrix.os_name == 'linux'
run: |
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install -y gcc-13 g++-13 libhidapi-dev rpm ccache
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 100
- name: Cache ccache (Linux)
if: matrix.os_name == 'linux'
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.cache/ccache
key: ${{ matrix.os_name }}-ccache-${{ github.sha }}
restore-keys: |
${{ matrix.os_name }}-ccache-
- name: Configure ccache (Linux)
if: matrix.os_name == 'linux'
run: |
ccache --set-config=max_size=500M
ccache --set-config=compression=true
ccache -z
- name: Cache linuxdeploy (Linux)
if: matrix.os_name == 'linux'
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: /tmp/linuxdeploy
key: linuxdeploy-x86_64-continuous
# =========== macOS Setup ===========
- name: Cache Homebrew
if: matrix.os_name == 'macos'
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: |
~/Library/Caches/Homebrew
/usr/local/Cellar/hidapi
/opt/homebrew/Cellar/hidapi
key: ${{ runner.os }}-brew-${{ hashFiles('.github/workflows/continuos-release.yml') }}
restore-keys: |
${{ runner.os }}-brew-
- name: Install dependencies (macOS)
if: matrix.os_name == 'macos'
run: |
brew install hidapi || true
brew link hidapi || true
# =========== Windows Setup ===========
- name: Setup vcpkg (Windows)
if: matrix.build_type == 'msvc'
uses: lukka/run-vcpkg@5e0cab206a5ea620130caf672fce3e4a6b5666a1 # v11.5
with:
vcpkgGitCommitId: '84bab45d415d22042bd0b9081aea57f362da3f35' # 2025.12.12
vcpkgJsonGlob: 'vcpkg.json'
- name: Setup CMake and Ninja (Windows)
if: matrix.build_type == 'msvc'
uses: lukka/get-cmake@9e07ecdcee1b12e5037e42f410b67f03e2f626e1 # v4.2.1
- name: Install NSIS with Large Strings (Windows)
if: matrix.build_type == 'msvc'
run: |
# Install NSIS with 8k string support (8192 byte buffer instead of default 1024)
# This fixes "Path too long" errors when modifying PATH on systems with long PATH variables
# See: https://github.com/Sapd/HeadsetControl/issues/462
$nsisVersion = "3.10"
$nsisPath = "C:\NSIS"
# 1. Download and extract base NSIS
Invoke-WebRequest -Uri "https://sourceforge.net/projects/nsis/files/NSIS%203/$nsisVersion/nsis-$nsisVersion.zip/download" -OutFile nsis.zip -UserAgent "Wget"
Expand-Archive nsis.zip -DestinationPath nsis-base
Move-Item "nsis-base\nsis-$nsisVersion" $nsisPath
# 2. Layer large strings build on top
Invoke-WebRequest -Uri "https://sourceforge.net/projects/nsis/files/NSIS%203/$nsisVersion/nsis-$nsisVersion-strlen_8192.zip/download" -OutFile nsis-large.zip -UserAgent "Wget"
Expand-Archive nsis-large.zip -DestinationPath nsis-large
Copy-Item "nsis-large\*" $nsisPath -Recurse -Force
# Add to PATH for subsequent steps
echo $nsisPath | Out-File -FilePath $env:GITHUB_PATH -Append
# Verify installation
if (Test-Path "$nsisPath\makensis.exe") {
Write-Host "NSIS installed successfully"
& "$nsisPath\makensis.exe" /VERSION
} else {
Write-Error "makensis.exe not found at $nsisPath"
exit 1
}
# =========== Unix Setup ===========
- name: Setup CMake and Ninja (Unix)
if: matrix.build_type == 'unix'
uses: lukka/get-cmake@9e07ecdcee1b12e5037e42f410b67f03e2f626e1 # v4.2.1
# =========== Build ===========
- name: Build and test (Windows)
if: matrix.build_type == 'msvc'
uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8
with:
configurePreset: 'windows-msvc'
buildPreset: 'windows-msvc'
testPreset: 'windows-msvc'
- name: Build and test (Unix)
if: matrix.build_type == 'unix'
uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8
with:
configurePreset: 'default'
buildPreset: 'default'
testPreset: 'default'
env:
CMAKE_C_COMPILER_LAUNCHER: ${{ matrix.os_name == 'linux' && 'ccache' || '' }}
CMAKE_CXX_COMPILER_LAUNCHER: ${{ matrix.os_name == 'linux' && 'ccache' || '' }}
- name: Show ccache stats (Linux)
if: matrix.os_name == 'linux'
run: ccache -s
# =========== Package ===========
- name: Strip binaries (Unix)
if: matrix.build_type == 'unix'
run: strip build/headsetcontrol
# Linux packages
- name: Generate Linux packages
if: matrix.os_name == 'linux'
run: |
cd build
cpack -G DEB
cpack -G RPM
for pkg in *.deb *.rpm; do
sha256sum "$pkg" > "$pkg.sha256"
done
- name: Generate AppImage (Linux)
if: matrix.os_name == 'linux'
run: |
cd build
# Use cached linuxdeploy or download
if [ -f /tmp/linuxdeploy/linuxdeploy-x86_64.AppImage ]; then
cp /tmp/linuxdeploy/linuxdeploy-x86_64.AppImage .
else
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
mkdir -p /tmp/linuxdeploy
cp linuxdeploy-x86_64.AppImage /tmp/linuxdeploy/
fi
chmod +x linuxdeploy-x86_64.AppImage
# Create .desktop file
printf '%s\n' '[Desktop Entry]' 'Type=Application' 'Name=HeadsetControl' 'Exec=headsetcontrol' 'Icon=headsetcontrol' 'Categories=Utility;' 'Terminal=true' 'NoDisplay=true' > headsetcontrol.desktop
cp ../assets/headsetcontrol.png headsetcontrol.png
DEPLOY_LIBSTDCPP=1 ./linuxdeploy-x86_64.AppImage --appimage-extract-and-run \
--appdir AppDir \
--executable headsetcontrol \
--desktop-file headsetcontrol.desktop \
--icon-file headsetcontrol.png \
--output appimage
mv HeadsetControl-*.AppImage headsetcontrol-${{ matrix.architecture }}.AppImage
sha256sum headsetcontrol-${{ matrix.architecture }}.AppImage > headsetcontrol-${{ matrix.architecture }}.AppImage.sha256
- name: Package portable binary (Linux)
if: matrix.os_name == 'linux'
run: |
cd build
zip -9 headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip headsetcontrol
sha256sum headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip > headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip.sha256
- name: Package portable binary (macOS)
if: matrix.os_name == 'macos'
run: |
cd build
zip -9 headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip headsetcontrol
shasum -a 256 headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip > headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip.sha256
# Windows: installer + portable exe
- name: Generate Windows installer
if: matrix.build_type == 'msvc'
run: |
cd build-msvc
cpack -G NSIS -D CPACK_NSIS_EXECUTABLE="C:/NSIS/makensis.exe"
$installer = Get-ChildItem -Filter "headsetcontrol-*.exe" | Where-Object { $_.Name -ne "headsetcontrol.exe" } | Select-Object -First 1
if ($installer) {
Rename-Item -Path $installer.FullName -NewName "headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}-setup.exe"
$hash = (Get-FileHash -Algorithm SHA256 "headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}-setup.exe").Hash.ToLower()
[System.IO.File]::WriteAllText("headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}-setup.exe.sha256", "$hash headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}-setup.exe`n")
}
- name: Package portable binary (Windows)
if: matrix.build_type == 'msvc'
run: |
cd build-msvc
Copy-Item headsetcontrol.exe "headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.exe"
$hash = (Get-FileHash -Algorithm SHA256 "headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.exe").Hash.ToLower()
[System.IO.File]::WriteAllText("headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.exe.sha256", "$hash headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.exe`n")
# =========== Upload ===========
- name: Upload artifacts (Linux)
if: matrix.os_name == 'linux'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: headsetcontrol-${{ matrix.os_name }}
path: |
build/headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip
build/headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip.sha256
build/*.deb
build/*.deb.sha256
build/*.rpm
build/*.rpm.sha256
build/headsetcontrol-*.AppImage
build/headsetcontrol-*.AppImage.sha256
retention-days: 90
- name: Upload artifacts (macOS)
if: matrix.os_name == 'macos'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: headsetcontrol-${{ matrix.os_name }}
path: |
build/headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip
build/headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.zip.sha256
retention-days: 90
- name: Upload artifacts (Windows)
if: matrix.build_type == 'msvc'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: headsetcontrol-${{ matrix.os_name }}
path: |
build-msvc/headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}-setup.exe
build-msvc/headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}-setup.exe.sha256
build-msvc/headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.exe
build-msvc/headsetcontrol-${{ matrix.os_name }}-${{ matrix.architecture }}.exe.sha256
retention-days: 90
create-release:
needs: [build]
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
merge-multiple: true
path: ./artifacts
- name: Generate changelog
run: |
cat > CHANGELOG.md << 'HEADER'
## Continuous Build
Latest automated build from `master` branch.
### Downloads
#### Windows
HEADER
for file in artifacts/*-setup.exe; do
[ -f "$file" ] && echo "- [$(basename "$file")](https://github.com/${{ github.repository }}/releases/download/continuous/$(basename "$file")) - Installer (recommended)" >> CHANGELOG.md
done
for file in artifacts/*windows*.exe; do
[[ -f "$file" && ! "$file" == *-setup.exe ]] && echo "- [$(basename "$file")](https://github.com/${{ github.repository }}/releases/download/continuous/$(basename "$file")) - Portable" >> CHANGELOG.md
done
echo "" >> CHANGELOG.md
echo "#### Linux" >> CHANGELOG.md
for file in artifacts/*.AppImage; do
[ -f "$file" ] && echo "- [$(basename "$file")](https://github.com/${{ github.repository }}/releases/download/continuous/$(basename "$file")) - AppImage (universal)" >> CHANGELOG.md
done
for file in artifacts/*.deb; do
[ -f "$file" ] && echo "- [$(basename "$file")](https://github.com/${{ github.repository }}/releases/download/continuous/$(basename "$file")) - Debian/Ubuntu" >> CHANGELOG.md
done
for file in artifacts/*.rpm; do
[ -f "$file" ] && echo "- [$(basename "$file")](https://github.com/${{ github.repository }}/releases/download/continuous/$(basename "$file")) - Fedora/RHEL" >> CHANGELOG.md
done
for file in artifacts/*linux*.zip; do
[ -f "$file" ] && echo "- [$(basename "$file")](https://github.com/${{ github.repository }}/releases/download/continuous/$(basename "$file")) - Portable" >> CHANGELOG.md
done
echo "" >> CHANGELOG.md
echo "#### macOS" >> CHANGELOG.md
for file in artifacts/*macos*.zip; do
[ -f "$file" ] && echo "- [$(basename "$file")](https://github.com/${{ github.repository }}/releases/download/continuous/$(basename "$file")) - Portable" >> CHANGELOG.md
done
cat >> CHANGELOG.md << 'FOOTER'
### Checksums (SHA256)
```
FOOTER
cat artifacts/*.sha256 >> CHANGELOG.md 2>/dev/null || true
echo '```' >> CHANGELOG.md
echo "" >> CHANGELOG.md
echo "---" >> CHANGELOG.md
echo "*Built from [\`${GITHUB_SHA:0:7}\`](https://github.com/${{ github.repository }}/commit/${{ github.sha }})*" >> CHANGELOG.md
- name: Create/Update release
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
with:
tag: continuous
name: 'Continuous Build'
bodyFile: CHANGELOG.md
artifacts: "artifacts/*"
allowUpdates: true
removeArtifacts: true
prerelease: true
makeLatest: false
token: ${{ secrets.GITHUB_TOKEN }}