Skip to content

Geramy/macosx support stg1 #1619

Geramy/macosx support stg1

Geramy/macosx support stg1 #1619

name: C++ Server Build, Test, and Release 🚀
on:
push:
branches: ["main"]
tags:
- v*
pull_request:
merge_group:
workflow_dispatch:
permissions:
contents: write
jobs:
# ========================================================================
# BUILD JOBS - Run on rai-160-sdk workers
# ========================================================================
build-lemonade-server-installer:
name: Build Lemonade Server Installer
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
clean: true
fetch-depth: 0
- name: Install CMake if not available
shell: PowerShell
run: |
# Check if CMake is already installed
$cmakeInstalled = Get-Command cmake -ErrorAction SilentlyContinue
if (-not $cmakeInstalled) {
Write-Host "CMake not found, installing..." -ForegroundColor Yellow
# Download CMake installer
$cmakeVersion = "3.28.1"
$cmakeUrl = "https://github.com/Kitware/CMake/releases/download/v$cmakeVersion/cmake-$cmakeVersion-windows-x86_64.msi"
$cmakeInstaller = "cmake-installer.msi"
Invoke-WebRequest -Uri $cmakeUrl -OutFile $cmakeInstaller
# Install CMake silently
Start-Process msiexec.exe -ArgumentList "/i $cmakeInstaller /quiet /norestart" -Wait
# Add CMake to PATH for this session AND future steps
$cmakePath = "C:\Program Files\CMake\bin"
$env:PATH = "$cmakePath;$env:PATH"
# Persist to GITHUB_PATH for future steps
echo $cmakePath >> $env:GITHUB_PATH
# Verify installation
cmake --version
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: CMake installation failed!" -ForegroundColor Red
exit 1
}
Write-Host "CMake installed successfully and added to PATH!" -ForegroundColor Green
} else {
Write-Host "CMake is already installed:" -ForegroundColor Green
cmake --version
}
- name: Install WiX Toolset 5.0.2 (CLI)
shell: PowerShell
run: |
$ErrorActionPreference = "Stop"
Write-Host "Downloading WiX Toolset 5.0.2 CLI..." -ForegroundColor Cyan
$wixUri = "https://github.com/wixtoolset/wix/releases/download/v5.0.2/wix-cli-x64.msi"
$msiPath = "$env:RUNNER_TEMP\wix-cli-x64.msi"
Invoke-WebRequest -UseBasicParsing -Uri $wixUri -OutFile $msiPath
Write-Host "Installing WiX Toolset 5.0.2 CLI..." -ForegroundColor Cyan
$p = Start-Process "msiexec.exe" -ArgumentList @("/i", "`"$msiPath`"", "/qn", "/norestart") -PassThru -Wait
if ($p.ExitCode -ne 0) {
Write-Host "WiX installer exited with code $($p.ExitCode)" -ForegroundColor Red
exit $p.ExitCode
}
- name: Verify WiX installation
shell: PowerShell
run: |
$ErrorActionPreference = "Stop"
# WiX CLI MSI does not always add itself to PATH in non-interactive installs,
# so we locate it explicitly and then update PATH for subsequent steps.
$wixDirs = @(
"C:\Program Files\WiX Toolset v5.0\bin",
"C:\Program Files (x86)\WiX Toolset v5.0\bin"
)
$wixExe = $null
foreach ($dir in $wixDirs) {
if (Test-Path (Join-Path $dir "wix.exe")) {
$wixExe = Join-Path $dir "wix.exe"
break
}
}
if (-not $wixExe) {
Write-Host "ERROR: wix.exe not found after installation." -ForegroundColor Red
Get-ChildItem -Recurse "C:\Program Files" -Filter wix.exe -ErrorAction SilentlyContinue | Select-Object -First 20 | Format-List FullName
exit 1
}
$wixDir = Split-Path $wixExe -Parent
# Persist wix.exe directory to PATH for all subsequent steps
"$wixDir" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
Write-Host "Using WiX from: $wixExe" -ForegroundColor Green
& $wixExe --version
- name: Build C++ Server with CMake
shell: PowerShell
run: |
$ErrorActionPreference = "Stop"
Write-Host "Building lemonade-router and lemonade-server..." -ForegroundColor Cyan
# Create build directory
if (Test-Path "build") {
Remove-Item -Recurse -Force "build"
}
# Configure with preset
cmake --preset windows
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
# Build
cmake --build build --config Release
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
# Verify binaries exist
if (-not (Test-Path "build\Release\lemonade-router.exe")) {
Write-Host "ERROR: lemonade-router.exe not found!" -ForegroundColor Red
exit 1
}
if (-not (Test-Path "build\Release\lemonade-server.exe")) {
Write-Host "ERROR: lemonade-server.exe not found!" -ForegroundColor Red
exit 1
}
if (-not (Test-Path "build\Release\lemonade-tray.exe")) {
Write-Host "ERROR: lemonade-tray.exe not found!" -ForegroundColor Red
exit 1
}
Write-Host "C++ build successful!" -ForegroundColor Green
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Build Electron App
shell: PowerShell
run: |
$ErrorActionPreference = "Stop"
Write-Host "Building Electron app via CMake target..." -ForegroundColor Cyan
# Build using CMake electron-app target (single source of truth)
cmake --build --preset windows --target electron-app
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: Electron app build failed!" -ForegroundColor Red
exit $LASTEXITCODE
}
# Verify the build output exists
if (-not (Test-Path "build\app\win-unpacked\Lemonade.exe")) {
Write-Host "ERROR: Electron app executable not found!" -ForegroundColor Red
exit 1
}
Write-Host "Electron app build successful!" -ForegroundColor Green
- name: Build Web App
shell: PowerShell
run: |
$ErrorActionPreference = "Stop"
Write-Host "Building Web app..." -ForegroundColor Cyan
cd src\web-app
# Install dependencies
Write-Host "Installing npm dependencies..." -ForegroundColor Yellow
npm install
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: npm install failed!" -ForegroundColor Red
exit $LASTEXITCODE
}
# Build the Web app
Write-Host "Building Web app..." -ForegroundColor Yellow
npm run build
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: Web app build failed!" -ForegroundColor Red
exit $LASTEXITCODE
}
# Verify the build output exists
if (-not (Test-Path "dist\renderer\index.html")) {
Write-Host "ERROR: Web app build output not found!" -ForegroundColor Red
exit 1
}
Write-Host "Web app build successful!" -ForegroundColor Green
# Copy web app output to the location expected by CMake/WiX installer
Write-Host "Copying web app to build\resources\web-app..." -ForegroundColor Yellow
$targetDir = "..\..\build\resources\web-app"
if (-not (Test-Path "..\..\build\resources")) {
New-Item -ItemType Directory -Force -Path "..\..\build\resources" | Out-Null
}
if (Test-Path $targetDir) {
Remove-Item -Recurse -Force $targetDir
}
Copy-Item -Path "dist\renderer" -Destination $targetDir -Recurse -Force
# Verify the copy succeeded
if (-not (Test-Path "..\..\build\resources\web-app\index.html")) {
Write-Host "ERROR: Failed to copy web app to build directory!" -ForegroundColor Red
exit 1
}
Write-Host "Web app copied to build directory successfully!" -ForegroundColor Green
- name: Build the Lemonade Server Installer
shell: PowerShell
run: |
$ErrorActionPreference = "Stop"
# Run the build installer script
.\src\cpp\build_installer.ps1
# Verify installers were created
if (-not (Test-Path "lemonade-server-minimal.msi")) {
Write-Host "ERROR: Minimal installer not created!" -ForegroundColor Red
exit 1
}
if (-not (Test-Path "lemonade.msi")) {
Write-Host "ERROR: Full installer not created!" -ForegroundColor Red
exit 1
}
Write-Host "Installers created successfully!" -ForegroundColor Green
- name: Upload Lemonade Server Installers
uses: actions/upload-artifact@v4
with:
name: Lemonade_Server_MSI
path: |
lemonade-server-minimal.msi
lemonade.msi
retention-days: 7
build-lemonade-server-deb:
name: Build Lemonade Server .deb Package
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
clean: true
fetch-depth: 0
- name: Get version from CMakeLists.txt
id: get_version
uses: ./.github/actions/get-version
- name: Build Linux .deb package (minimal)
uses: ./.github/actions/build-linux-deb
with:
include-electron: 'false'
- name: Upload .deb package
uses: actions/upload-artifact@v4
with:
name: lemonade-server-deb
path: build/lemonade-server-minimal_${{ env.LEMONADE_VERSION }}_amd64.deb
retention-days: 7
build-lemonade-deb-full:
name: Build Lemonade Full .deb Package (with Electron App)
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
clean: true
fetch-depth: 0
- name: Get version from CMakeLists.txt
id: get_version
uses: ./.github/actions/get-version
- name: Build Linux .deb package (full with Electron)
uses: ./.github/actions/build-linux-deb
with:
include-electron: 'true'
- name: Upload full .deb package
uses: actions/upload-artifact@v4
with:
name: lemonade-deb-full
path: build/lemonade_${{ env.LEMONADE_VERSION }}_amd64.deb
retention-days: 7
# ========================================================================
# TEST JOBS - Inference tests on self-hosted runners
# ========================================================================
test-exe-inference:
name: Test .exe - ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
needs: build-lemonade-server-installer
strategy:
fail-fast: false
matrix:
include:
# LLM - Llamacpp backends
- name: llamacpp (vulkan)
test_type: llm
wrapped_server: llamacpp
backend: vulkan
runner: [rai300_400, Windows]
- name: llamacpp (rocm)
test_type: llm
wrapped_server: llamacpp
backend: rocm
runner: [stx-halo, Windows]
# LLM - RyzenAI backends
- name: ryzenai (cpu)
test_type: llm
wrapped_server: ryzenai
backend: cpu
runner: [rai300_400, Windows]
- name: ryzenai (hybrid)
test_type: llm
wrapped_server: ryzenai
backend: hybrid
runner: [rai300_400, Windows]
- name: ryzenai (npu)
test_type: llm
wrapped_server: ryzenai
backend: npu
runner: [rai300_400, Windows]
# LLM - FLM
- name: flm
test_type: llm
wrapped_server: flm
backend: ""
runner: [rai300_400, Windows]
# Whisper
- name: whisper (cpu)
test_type: whisper
backend: cpu
runner: [rai300_400, Windows]
- name: whisper (npu)
test_type: whisper
backend: npu
runner: [rai300_400, Windows]
# Stable Diffusion
- name: stable-diffusion
test_type: sd
runner: [rai300_400, Windows]
# Text to speech
- name: text-to-speech
test_type: tts
runner: [rai300_400, Windows]
env:
LEMONADE_CI_MODE: "True"
LEMONADE_CACHE_DIR: ".\\ci-cache"
PYTHONIOENCODING: utf-8
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Cleanup processes
uses: ./.github/actions/cleanup-processes-windows
- name: Set environment variables
shell: PowerShell
run: |
$cwd = (Get-Item .\).FullName
echo "HF_HOME=$cwd\hf-cache" >> $Env:GITHUB_ENV
echo "LEMONADE_INSTALL_PATH=$cwd\lemonade_server_install" >> $Env:GITHUB_ENV
- name: Install and Verify Lemonade Server
uses: ./.github/actions/install-lemonade-server-msi
with:
install-path: ${{ env.LEMONADE_INSTALL_PATH }}
- name: Setup Python and virtual environment
uses: ./.github/actions/setup-venv
with:
venv-name: '.venv'
python-version: '3.10'
requirements-file: 'test/requirements.txt'
- name: Run tests
shell: PowerShell
env:
HF_HOME: ${{ env.HF_HOME }}
run: |
$ErrorActionPreference = "Stop"
$venvPython = ".\.venv\Scripts\python.exe"
$serverExe = Join-Path $env:LEMONADE_INSTALL_PATH "bin\lemonade-server.exe"
Write-Host "Running ${{ matrix.name }} tests..." -ForegroundColor Cyan
switch ("${{ matrix.test_type }}") {
"llm" {
if ("${{ matrix.backend }}") {
& $venvPython test/server_llm.py --wrapped-server ${{ matrix.wrapped_server }} --backend ${{ matrix.backend }} --server-binary $serverExe
} else {
& $venvPython test/server_llm.py --wrapped-server ${{ matrix.wrapped_server }} --server-binary $serverExe
}
}
"whisper" {
if ("${{ matrix.backend }}") {
& $venvPython test/server_whisper.py --backend ${{ matrix.backend }} --server-binary $serverExe
} else {
& $venvPython test/server_whisper.py --server-binary $serverExe
}
}
"sd" {
& $venvPython test/server_sd.py --server-binary $serverExe
}
"tts" {
& $venvPython test/server_tts.py --server-binary $serverExe
}
}
if ($LASTEXITCODE -ne 0) {
Write-Host "ERROR: ${{ matrix.name }} tests FAILED with exit code: $LASTEXITCODE" -ForegroundColor Red
exit $LASTEXITCODE
}
Write-Host "${{ matrix.name }} tests PASSED!" -ForegroundColor Green
- name: Cleanup
if: always()
uses: ./.github/actions/cleanup-processes-windows
test-deb-inference:
name: Test .deb - ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
needs: build-lemonade-server-deb
strategy:
fail-fast: false
matrix:
include:
# LLM - Llamacpp backends
- name: llamacpp (vulkan)
test_type: llm
backend: vulkan
runner: [rai300_400, Linux]
- name: llamacpp (rocm)
test_type: llm
backend: rocm
runner: [stx-halo, Linux]
# Stable Diffusion
- name: stable-diffusion
test_type: sd
runner: [rai300_400, Linux]
# Text to speech
- name: text-to-speech
test_type: tts
runner: [rai300_400, Linux]
env:
LEMONADE_CI_MODE: "True"
PYTHONIOENCODING: utf-8
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
LEMONADE_VERSION: ${{ needs.build-lemonade-server-deb.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Cleanup processes
uses: ./.github/actions/cleanup-processes-linux
- name: Set HF_HOME environment variable
run: echo "HF_HOME=$PWD/hf-cache" >> $GITHUB_ENV
- name: Install Lemonade Server (.deb)
uses: ./.github/actions/install-lemonade-server-deb
with:
version: ${{ env.LEMONADE_VERSION }}
- name: Setup Python and virtual environment
uses: ./.github/actions/setup-venv
with:
venv-name: '.venv'
python-version: '3.10'
requirements-file: 'test/requirements.txt'
- name: Run tests
env:
HF_HOME: ${{ env.HF_HOME }}
run: |
set -e # Exit on error
VENV_PYTHON=".venv/bin/python"
echo "Running ${{ matrix.name }} tests..."
case "${{ matrix.test_type }}" in
llm)
$VENV_PYTHON test/server_llm.py --wrapped-server llamacpp --backend ${{ matrix.backend }} --server-binary lemonade-server
;;
sd)
$VENV_PYTHON test/server_sd.py --server-binary lemonade-server
;;
tts)
$VENV_PYTHON test/server_tts.py --server-binary lemonade-server
;;
esac
echo "${{ matrix.name }} tests PASSED!"
- name: Print server logs on failure
if: failure()
run: |
echo "=== Server Log (if exists) ==="
if [ -f /tmp/lemonade-server.log ]; then
echo "Last 100 lines of /tmp/lemonade-server.log:"
tail -100 /tmp/lemonade-server.log
else
echo "Log file /tmp/lemonade-server.log not found"
fi
echo ""
echo "=== Router PID file (if exists) ==="
if [ -f /tmp/lemonade-router.pid ]; then
cat /tmp/lemonade-router.pid
else
echo "PID file not found"
fi
echo ""
echo "=== Lock file status ==="
ls -la /tmp/lemonade*.lock 2>/dev/null || echo "No lock files found"
- name: Cleanup
if: always()
uses: ./.github/actions/cleanup-processes-linux
# ========================================================================
# CLI AND ENDPOINTS TESTS - Run on GitHub-hosted runners (no GPU needed)
# ========================================================================
test-cli-endpoints:
name: Test ${{ matrix.test_type }} (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs:
- build-lemonade-server-installer
- build-lemonade-server-deb
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
test_type: [cli, endpoints, system-info]
env:
LEMONADE_CI_MODE: "True"
PYTHONIOENCODING: utf-8
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
LEMONADE_VERSION: ${{ needs.build-lemonade-server-deb.outputs.version }}
steps:
- uses: actions/checkout@v4
# ---- Linux Setup ----
- name: Download .deb package
if: runner.os == 'Linux'
uses: actions/download-artifact@v4
with:
name: lemonade-server-deb
path: .
- name: Install Lemonade Server (.deb)
if: runner.os == 'Linux'
shell: bash
run: |
DEB_FILE="lemonade-server-minimal_${{ env.LEMONADE_VERSION }}_amd64.deb"
echo "Installing .deb package: $DEB_FILE"
sudo apt install ./"$DEB_FILE"
# Verify binaries installed
lemonade-server --version
# Verify the systemd service started automatically
sudo systemctl status lemonade-server
- name: Set environment (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
echo "HF_HOME=$PWD/hf-cache" >> $GITHUB_ENV
echo "VENV_PYTHON=.venv/bin/python" >> $GITHUB_ENV
echo "SERVER_BINARY=lemonade-server" >> $GITHUB_ENV
# ---- Windows Setup ----
- name: Setup (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$cwd = (Get-Item .).FullName
echo "HF_HOME=$cwd\hf-cache" >> $Env:GITHUB_ENV
echo "LEMONADE_INSTALL_PATH=$cwd\lemonade_server_install" >> $Env:GITHUB_ENV
- name: Install Lemonade Server (Windows)
if: runner.os == 'Windows'
uses: ./.github/actions/install-lemonade-server-msi
with:
install-path: ${{ env.LEMONADE_INSTALL_PATH }}
- name: Set paths (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
echo "VENV_PYTHON=.venv/Scripts/python.exe" >> $Env:GITHUB_ENV
echo "SERVER_BINARY=$Env:LEMONADE_INSTALL_PATH\bin\lemonade-server.exe" >> $Env:GITHUB_ENV
# ---- Common Setup ----
- name: Setup Python and virtual environment
uses: ./.github/actions/setup-venv
with:
venv-name: '.venv'
python-version: '3.10'
requirements-file: 'test/requirements.txt'
# ---- Run Tests ----
- name: Stop systemd service before tests (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
echo "Stopping lemonade-server systemd service..."
sudo systemctl stop lemonade-server || true
sudo systemctl status lemonade-server || true
- name: Run ${{ matrix.test_type }} tests
shell: bash
env:
HF_HOME: ${{ env.HF_HOME }}
run: |
set -e # Exit on error
if [ "${{ matrix.test_type }}" = "cli" ]; then
echo "Running CLI tests..."
$VENV_PYTHON test/server_cli.py --server-binary "$SERVER_BINARY"
$VENV_PYTHON test/server_cli.py --server-binary "$SERVER_BINARY" --ephemeral
$VENV_PYTHON test/server_cli.py --server-binary "$SERVER_BINARY" --listen-all
$VENV_PYTHON test/server_cli.py --server-binary "$SERVER_BINARY" --api-key
elif [ "${{ matrix.test_type }}" = "endpoints" ]; then
echo "Running endpoint tests..."
$VENV_PYTHON test/server_endpoints.py --server-binary "$SERVER_BINARY"
$VENV_PYTHON test/server_endpoints.py --server-binary "$SERVER_BINARY" --server-per-test
elif [ "${{ matrix.test_type }}" = "system-info" ]; then
echo "Running system-info mock hardware tests..."
# Unset LEMONADE_CI_MODE so the server uses mock cache files
unset LEMONADE_CI_MODE
$VENV_PYTHON test/server_system_info.py --server-binary "$SERVER_BINARY"
fi
echo "${{ matrix.test_type }} tests PASSED!"
# ========================================================================
# RELEASE JOB - Add artifacts to GitHub release
# ========================================================================
release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs:
- build-lemonade-server-installer
- build-lemonade-server-deb
- build-lemonade-deb-full
- test-exe-inference
- test-deb-inference
- test-cli-endpoints
if: startsWith(github.ref, 'refs/tags/v')
env:
LEMONADE_VERSION: ${{ needs.build-lemonade-server-deb.outputs.version }}
steps:
- name: Checkout for release notes action
uses: actions/checkout@v4
with:
sparse-checkout: .github
- name: Download Lemonade Server Installer (Windows)
uses: actions/download-artifact@v4
with:
name: Lemonade_Server_MSI
path: .
- name: Download Lemonade Server .deb Package (Linux - Minimal)
uses: actions/download-artifact@v4
with:
name: lemonade-server-deb
path: .
- name: Download Lemonade Full .deb Package (Linux - with Electron App)
uses: actions/download-artifact@v4
with:
name: lemonade-deb-full
path: .
- name: Verify release artifacts
run: |
echo "Release artifacts:"
ls -lh lemonade-server-minimal.msi
ls -lh lemonade.msi
ls -lh lemonade-server-minimal_${LEMONADE_VERSION}_amd64.deb
ls -lh lemonade_${LEMONADE_VERSION}_amd64.deb
- name: Generate release notes
id: release-notes
uses: ./.github/actions/generate-release-notes
with:
version: ${{ env.LEMONADE_VERSION }}
repo: ${{ github.repository }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
body_path: ${{ steps.release-notes.outputs.release_notes_file }}
files: |
lemonade-server-minimal.msi
lemonade.msi
lemonade-server-minimal_${{ env.LEMONADE_VERSION }}_amd64.deb
lemonade_${{ env.LEMONADE_VERSION }}_amd64.deb
fail_on_unmatched_files: true