Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ jobs:
run: cargo build --manifest-path app/src-tauri/Cargo.toml -p wisp-engine-sherpa
- name: Build the app
run: cargo build --manifest-path app/src-tauri/Cargo.toml
- name: Verify staged Windows runtime
shell: bash
run: ./scripts/verify-windows-runtime.sh app/src-tauri/windows-runtime

# Proves the macOS app builds — including the macOS-ONLY code no other CI job ever compiles:
# ScreenCaptureKit system audio, the WebRTC AEC3 echo canceller, Metal whisper.cpp, and Apple
Expand Down
21 changes: 20 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
# with whisper.cpp's automatic CPU fallback when there's no Vulkan device. sherpa stays CPU.
- os: windows-latest
label: Windows x64
args: "--features whisper-vulkan"
args: "--features whisper-vulkan --config src-tauri/tauri.vulkan.conf.json"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -104,6 +104,13 @@ jobs:
with:
version: 1.4.309.0
cache: true
# The SDK supplies headers + the import library, but not the redistributable loader DLL. Stage
# LunarG's official, checksum-pinned x64 runtime so Wisp can launch and use the CPU fallback even
# on machines whose GPU driver did not install vulkan-1.dll.
- name: Stage the official Vulkan loader
if: runner.os == 'Windows'
shell: bash
run: ./scripts/stage-vulkan-loader.sh app/src-tauri/windows-runtime

- name: Install frontend deps
working-directory: app
Expand Down Expand Up @@ -155,3 +162,15 @@ jobs:
- name: Verify bundled macOS entitlements
if: runner.os == 'macOS'
run: ./scripts/verify-macos-entitlements.sh app/src-tauri/target/release/bundle/dmg/Wisp_*.dmg

- name: Verify bundled Windows runtime
if: runner.os == 'Windows'
shell: bash
run: ./scripts/verify-windows-runtime.sh D:/b/release/bundle/nsis/Wisp_*-setup.exe --require-vulkan

- name: Smoke-test the packaged Windows app
if: runner.os == 'Windows'
shell: pwsh
run: |
$installer = Get-ChildItem 'D:\b\release\bundle\nsis\Wisp_*-setup.exe' | Select-Object -First 1
./scripts/smoke-windows-installer.ps1 -Installer $installer.FullName
28 changes: 23 additions & 5 deletions .github/workflows/windows-vulkan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ name: Windows Vulkan whisper smoke

on:
workflow_dispatch: {}
# Validate on pushes to the feature branch (a brand-new workflow can't be dispatched from a
# non-default branch). Drop this trigger once the path is proven and the workflow lands on main.
push:
branches: [feat/windows-vulkan-whisper]
pull_request:
branches: [main]
paths:
- ".github/workflows/windows-vulkan.yml"
- "app/src-tauri/build.rs"
- "app/src-tauri/Cargo.toml"
- "app/src-tauri/tauri*.json"
- "crates/wisp-engine-whisper-cpp/**"
- "scripts/smoke-windows-installer.ps1"
- "scripts/stage-vulkan-loader.sh"
- "scripts/verify-windows-runtime.sh"

env:
CARGO_TERM_COLOR: always
Expand Down Expand Up @@ -44,6 +51,9 @@ jobs:
with:
version: 1.4.309.0
cache: true
- name: Stage the official Vulkan loader
shell: bash
run: ./scripts/stage-vulkan-loader.sh app/src-tauri/windows-runtime
- name: Install the frontend deps
working-directory: app
run: npm ci
Expand All @@ -56,7 +66,7 @@ jobs:
shell: pwsh
run: |
Write-Host "VULKAN_SDK = $env:VULKAN_SDK"
npm run tauri build -- --features whisper-vulkan
npm run tauri build -- --features whisper-vulkan --config src-tauri/tauri.vulkan.conf.json
# Show where the NSIS bundler dropped the installer so the release.yml recipe can target the same
# path (confirms CARGO_TARGET_DIR is honoured by the bundler, not just the compiler).
- name: Locate the installer (diagnostic)
Expand All @@ -65,6 +75,14 @@ jobs:
run: |
Get-ChildItem -Recurse "D:\b","app\src-tauri\target" -Include *-setup.exe -ErrorAction SilentlyContinue |
Select-Object FullName, Length | Format-Table -Auto
- name: Verify the installer runtime
shell: bash
run: ./scripts/verify-windows-runtime.sh D:/b/release/bundle/nsis/Wisp_*-setup.exe --require-vulkan
- name: Smoke-test the packaged app
shell: pwsh
run: |
$installer = Get-ChildItem 'D:\b\release\bundle\nsis\Wisp_*-setup.exe' | Select-Object -First 1
./scripts/smoke-windows-installer.ps1 -Installer $installer.FullName
# Upload the installer so it can be downloaded and tested on real Windows hardware (GPU + non-GPU).
- name: Upload the Windows GPU installer
if: always()
Expand Down
9 changes: 9 additions & 0 deletions app/src-tauri/tauri.vulkan.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://schema.tauri.app/config/2",
"bundle": {
"resources": {
"windows-runtime/vulkan-1.dll": "vulkan-1.dll",
"windows-runtime/VulkanRT-License.txt": "VulkanRT-License.txt"
}
}
}
38 changes: 38 additions & 0 deletions scripts/smoke-windows-installer.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
param(
[Parameter(Mandatory = $true)]
[string]$Installer
)

$ErrorActionPreference = "Stop"

if (-not (Test-Path -LiteralPath $Installer -PathType Leaf)) {
throw "Windows installer not found: $Installer"
}

$extractDirectory = Join-Path ([System.IO.Path]::GetTempPath()) "wisp-windows-smoke-$([guid]::NewGuid())"
$process = $null

try {
New-Item -ItemType Directory -Path $extractDirectory | Out-Null
& 7z x -y "-o$extractDirectory" $Installer | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "7z failed to extract $Installer"
}

$executable = Join-Path $extractDirectory "Wisp.exe"
$process = Start-Process -FilePath $executable -WorkingDirectory $extractDirectory -PassThru
if ($process.WaitForExit(15000)) {
throw "Packaged Wisp.exe exited during startup with code $($process.ExitCode)"
}

Write-Host "Packaged Wisp.exe remained running for 15 seconds"
}
finally {
if ($null -ne $process -and -not $process.HasExited) {
Stop-Process -Id $process.Id -Force
$process.WaitForExit()
}
if (Test-Path -LiteralPath $extractDirectory) {
Remove-Item -LiteralPath $extractDirectory -Recurse -Force
}
}
56 changes: 56 additions & 0 deletions scripts/stage-vulkan-loader.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ $# -ne 1 ]]; then
echo "Usage: $0 <destination-directory>" >&2
exit 2
fi

# Official LunarG runtime components matching the Vulkan SDK used by the Windows workflows.
# The component archive is intended for redistribution and includes the runtime license.
version=1.4.309.0
archive_name="VulkanRT-${version}-Components.zip"
archive_sha256=7d969f4d7b44e387667d3148f61559497c22d50cbe3d50adc9e5409afbce2df1
archive_url="https://sdk.lunarg.com/sdk/download/${version}/windows/${archive_name}"
destination=$1
work_dir=$(mktemp -d "${TMPDIR:-/tmp}/wisp-vulkan-runtime.XXXXXX")
archive="$work_dir/$archive_name"

cleanup() {
if [[ -d "$work_dir" ]]; then
rm -rf "$work_dir"
fi
}
trap cleanup EXIT

command -v curl >/dev/null 2>&1 || {
echo "curl is required to download the Vulkan runtime" >&2
exit 1
}
command -v 7z >/dev/null 2>&1 || {
echo "7z is required to extract the Vulkan runtime" >&2
exit 1
}

curl --fail --location --retry 3 --output "$archive" "$archive_url"
if command -v shasum >/dev/null 2>&1; then
actual_sha256=$(shasum -a 256 "$archive" | cut -d ' ' -f 1)
else
actual_sha256=$(sha256sum "$archive" | cut -d ' ' -f 1)
fi
[[ $actual_sha256 == "$archive_sha256" ]] || {
echo "Vulkan runtime checksum mismatch: expected $archive_sha256, got $actual_sha256" >&2
exit 1
}

mkdir -p "$destination"
7z e -y "-o$destination" "$archive" "*/x64/vulkan-1.dll" "*/VulkanRT-License.txt" >/dev/null

for file in vulkan-1.dll VulkanRT-License.txt; do
[[ -s "$destination/$file" ]] || {
echo "Failed to stage $file from the Vulkan runtime archive" >&2
exit 1
}
done

echo "Staged LunarG Vulkan runtime $version in $destination"
74 changes: 74 additions & 0 deletions scripts/verify-windows-runtime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail

usage() {
echo "Usage: $0 <runtime-directory|Wisp_*_x64-setup.exe> [--require-vulkan]" >&2
exit 2
}

[[ $# -ge 1 && $# -le 2 ]] || usage
input=$1
require_vulkan=false
if [[ $# -eq 2 ]]; then
[[ $2 == "--require-vulkan" ]] || usage
require_vulkan=true
fi
extract_dir=

cleanup() {
if [[ -n "$extract_dir" && -d "$extract_dir" ]]; then
rm -rf "$extract_dir"
fi
}
trap cleanup EXIT

if [[ -f "$input" ]]; then
command -v 7z >/dev/null 2>&1 || {
echo "7z is required to inspect the NSIS installer" >&2
exit 1
}
extract_dir=$(mktemp -d "${TMPDIR:-/tmp}/wisp-windows-package.XXXXXX")
7z x -y "-o$extract_dir" "$input" >/dev/null
runtime_dir=$extract_dir
elif [[ -d "$input" ]]; then
runtime_dir=$input
else
echo "Runtime directory or installer not found: $input" >&2
exit 1
fi

files=(
"onnxruntime.dll:1000000"
"onnxruntime_providers_shared.dll:1000"
"sherpa-onnx-c-api.dll:1000000"
)

if [[ $require_vulkan == true ]]; then
files+=(
"vulkan-1.dll:100000"
"VulkanRT-License.txt:1000"
)
fi

if [[ -n "$extract_dir" ]]; then
files+=(
"Wisp.exe:1000000"
"resources/silero_vad.onnx:100000"
)
fi

for expected in "${files[@]}"; do
name=${expected%%:*}
minimum=${expected##*:}
file="$runtime_dir/$name"
if [[ ! -f "$file" ]]; then
echo "Missing Windows package file: $name" >&2
exit 1
fi
size=$(wc -c < "$file" | tr -d '[:space:]')
if (( size < minimum )); then
echo "Windows package file is truncated: $name is $size bytes (minimum $minimum)" >&2
exit 1
fi
echo "Verified $name ($size bytes)"
done
Loading