yams/setup.ps1
trvon 8250b061fc
build: release buildtype with split debug symbols
Release used buildtype=debugoptimized (-O2 -g, strip=false) and relied on
scripts/prune-runtime-install.sh to strip the staged payload, discarding
the debug info. Crash reports from shipped binaries could not be
symbolicated and the build type label did not match what shipped.

- setup.sh/setup.ps1 Release and the Meson project default use
  buildtype=release. setup.sh adds -Ddebug=true for Release so -O3 code
  keeps DWARF for the split, and passes -Db_pie=true explicitly (also a
  project default) so reconfigured build dirs pick it up. Windows keeps
  release without -g so no PDBs land in the MSI stage. Debug/Fuzzing are
  unchanged (-O0 -g, assertions on).
- scripts/split-debug-symbols.sh replaces prune-runtime-install.sh: ELF
  files get objcopy --only-keep-debug (compressed) into <debug-dir>/
  <path>.debug plus .build-id/xx/yyyy.debug links, strip
  --strip-unneeded and --add-gnu-debuglink; Mach-O files get dsymutil
  dSYMs and strip -S -x. Without a debug dir it only strips.
- The release job splits into $BUILD_DIR/debug-symbols and uploads it as
  a debug-symbols-<suffix> workflow artifact (30 days) instead of
  shipping or dropping it. build-deb.sh, the Dockerfile, PKGBUILD and
  build-arch-pkg.sh call the split script in strip-only mode.

Measured on grim (Linux x86_64, clang 21, stripped staged payload):

  file                         debugoptimized  release -O3   delta
  bin/yams-cli                     35,528,968   36,733,304   +3.4%
  bin/yams-daemon                  29,731,312   30,685,792   +3.2%
  bin/yams-mcp-server              19,504,928   19,865,472   +1.8%
  libyams_onnx_resource.so            477,280      481,488   +0.9%
  libyams_object_storage_s3.so      8,226,608    8,239,016   +0.2%
  yams_zyp.so                          79,936       80,040   +0.1%

Debug symbols: 309 MiB (compressed .debug files); gdb resolves
`info line main` on the stripped daemon through the .build-id tree.
check_install_surface: no debug sections or .symtab remain; the
remaining violations are the export leaks fixed later in this series.

Signed-off-by: trvon <me@trevon.dev>
2026-10-01 16:53:23 -06:00

1294 lines
52 KiB
PowerShell

Param(
[ValidateSet('Debug','Release','Profiling','Fuzzing')]
[string]$BuildType = 'Release',
[switch]$Package,
[switch]$Offline,
[switch]$SystemDeps,
# ONNX Runtime is deprecated and opt-in.
[switch]$WithOnnx,
[string]$Version = ''
)
$ErrorActionPreference = 'Stop'
$initialMeson = Get-Command meson -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -First 1
$initialNinja = Get-Command ninja -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -First 1
function Import-BatchEnvironment {
param(
[Parameter(Mandatory = $true)]
[string]$BatchFile,
[string[]]$Arguments = @()
)
if (-not (Test-Path $BatchFile)) {
return $false
}
$commandParts = @("`"$BatchFile`"")
foreach ($arg in $Arguments) {
if ($null -ne $arg -and $arg -ne '') {
$commandParts += $arg
}
}
$varsLoaded = $false
& $env:COMSPEC /s /c (($commandParts -join ' ') + ' && set') | ForEach-Object {
$name, $value = $_ -split '=', 2
if ($name -and $value) {
Set-Item -Force -Path "ENV:\$name" -Value $value
$varsLoaded = $true
}
}
return $varsLoaded
}
if (-not $Offline -and $env:YAMS_OFFLINE -match '^(1|true|yes|on)$') {
$Offline = $true
}
if (-not $SystemDeps -and $env:YAMS_USE_SYSTEM_DEPS -match '^(1|true|yes|on)$') {
$SystemDeps = $true
}
# Initialize Visual Studio environment if not already set
if (-not $env:VSINSTALLDIR -or -not (Get-Command cl.exe -ErrorAction SilentlyContinue)) {
Write-Host 'Visual Studio environment not detected, initializing...'
# Find Visual Studio installation
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
if (-not (Test-Path $vswhere)) {
$vswhere = "$env:ProgramFiles\Microsoft Visual Studio\Installer\vswhere.exe"
}
if (Test-Path $vswhere) {
# Prefer the newest installed MSVC toolchain by default.
$vsPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if (-not $vsPath) {
$vsPath = & $vswhere -version "[17.0,18.0)" -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
}
if ($vsPath) {
$vsDevCmd = Join-Path $vsPath "Common7\Tools\VsDevCmd.bat"
if (Test-Path $vsDevCmd) {
Write-Host "Found Visual Studio at: $vsPath"
$toolsetArg = ""
# Resolve installationVersion via JSON and matching installationPath.
$vsInstallVersion = $null
try {
$instancesJson = & $vswhere -all -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -format json
$instances = $instancesJson | ConvertFrom-Json
$thisInstance = $instances | Where-Object { $_.installationPath -eq $vsPath } | Select-Object -First 1
if ($thisInstance) {
$vsInstallVersion = $thisInstance.installationVersion
}
} catch {
# Best-effort; continue without installationVersion string.
$vsInstallVersion = $null
}
$vcToolsPath = Join-Path $vsPath "VC\Tools\MSVC"
$v143Dir = $null
$v144Dir = $null
if (Test-Path $vcToolsPath) {
$v143Dir = Get-ChildItem -Path $vcToolsPath -Filter "14.3*" -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1
$v144Dir = Get-ChildItem -Path $vcToolsPath -Filter "14.4*" -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1
}
if ($vsPath -match "\\18\\" -or ($vsInstallVersion -and $vsInstallVersion -match '^18\.')) {
if ($vsInstallVersion) {
Write-Host "VS 2025 detected (Version $vsInstallVersion)"
} else {
Write-Host "VS 2025 detected"
}
if ($v143Dir -or $v144Dir) {
$legacyToolsets = @()
if ($v143Dir) { $legacyToolsets += $v143Dir.Name }
if ($v144Dir) { $legacyToolsets += $v144Dir.Name }
Write-Host "Optional legacy MSVC toolsets detected: $($legacyToolsets -join ', ')"
}
Write-Host "Using the default VS 2025 toolset unless YAMS_VS_TOOLSET is explicitly set."
}
# Allow manual override
if ($env:YAMS_VS_TOOLSET) {
Write-Host "Forcing Toolset Version (Override): $($env:YAMS_VS_TOOLSET)"
$toolsetArg = "-vcvars_ver=$($env:YAMS_VS_TOOLSET)"
}
# Import VS environment variables into current PowerShell session for x64 architecture.
$vsDevCmdArgs = @('-arch=amd64', '-host_arch=amd64')
if ($toolsetArg) {
$vsDevCmdArgs += $toolsetArg
}
$vsDevCmdArgs += '-no_logo'
$varsLoaded = Import-BatchEnvironment -BatchFile $vsDevCmd -Arguments $vsDevCmdArgs
if (-not $varsLoaded) {
throw "Failed to initialize Visual Studio environment. The toolset ($($toolsetArg -replace '-vcvars_ver=','')) might be missing or broken."
}
Write-Host 'Visual Studio environment initialized successfully (x64)'
}
}
}
# Verify compiler is now available
if (-not (Get-Command cl.exe -ErrorAction SilentlyContinue)) {
Write-Error @"
MSVC compiler (cl.exe) not found or could not be initialized.
Detected Visual Studio instance:
$vsPath
Common fixes:
1. Install the 'Desktop development with C++' workload for your Visual Studio installation.
2. Ensure the Windows 10/11 SDK is installed.
3. If using VS 2025 and you need a specific MSVC toolset:
- Install the matching optional MSVC toolset in Visual Studio Installer, OR
- Override toolset selection via: `$env:YAMS_VS_TOOLSET = '14.5'`
4. Ensure vswhere.exe exists at: ${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe
For installation: https://visualstudio.microsoft.com/downloads/
"@
}
}
# Detect MSVC version
$msvcVersion = $null
$msvcVersionFromEnv = $null
$msvcVersionFromCl = $null
$vsVersion = $null
# Try environment variable first (fastest and safest)
if ($env:VCToolsVersion -match '^(\d+)\.(\d+)') {
$toolsetMajor = [int]$matches[1]
# MSVC version is Toolset Version + 5 (e.g. 14.x -> 19.x)
$msvcMajor = $toolsetMajor + 5
$msvcVersionFromEnv = "$msvcMajor$($matches[2].Substring(0,1))"
Write-Host "Detected MSVC Version (from env): $msvcVersionFromEnv"
}
# Fallback to cl.exe output
elseif (Get-Command cl.exe -ErrorAction SilentlyContinue) {
try {
# Run via cmd to avoid PowerShell exit code issues with Stop preference
$clOutput = cmd /c "cl.exe 2>&1"
# Output format: Microsoft (R) C/C++ Optimizing Compiler Version 19.40.33811 for x64
if ($clOutput -match 'Version (\d+)\.(\d+)') {
$major = $matches[1]
$minor = $matches[2]
$msvcVersionFromCl = "$major$($minor.Substring(0,1))"
Write-Host "Detected MSVC Version (from cl.exe): $msvcVersionFromCl"
}
} catch {
Write-Warning "Failed to detect MSVC version from cl.exe, using defaults. Error: $_"
}
}
if ($msvcVersionFromEnv -and $msvcVersionFromCl -and $msvcVersionFromEnv -ne $msvcVersionFromCl) {
Write-Warning "MSVC version mismatch: env=$msvcVersionFromEnv, cl.exe=$msvcVersionFromCl. Using env toolset version."
}
if ($msvcVersionFromEnv) {
$msvcVersion = $msvcVersionFromEnv
} elseif ($msvcVersionFromCl) {
$msvcVersion = $msvcVersionFromCl
} else {
$msvcVersion = '195' # Default fallback
}
# Map MSVC version to VS version
# Prefer actual VS installation version when available.
if ($vsInstallVersion -and $vsInstallVersion -match '^(\d+)\.') {
$vsVersion = $matches[1]
} else {
# 193 -> VS 17 (2022)
# 194 -> VS 17 (2022) updates
# 195 -> VS 18 (2025)
if ($msvcVersion -ge 195) {
$vsVersion = '18'
} elseif ($msvcVersion -ge 193) {
$vsVersion = '17'
} elseif ($msvcVersion -ge 192) {
$vsVersion = '16'
} else {
$vsVersion = '18'
}
}
# Basic config (match setup.sh semantics as closely as practical)
if (-not $env:YAMS_CPPSTD) {
# Default to C++20 for MSVC to ensure stability with Boost and other dependencies
$env:YAMS_CPPSTD = '20'
}
# Enforce minimum C++20
$cppStdValue = $null
if ([int]::TryParse($env:YAMS_CPPSTD, [ref]$cppStdValue)) {
if ($cppStdValue -lt 20) {
Write-Error "YAMS requires C++20 or newer on Windows. Detected YAMS_CPPSTD=$($env:YAMS_CPPSTD)."
}
} else {
Write-Warning "Could not parse YAMS_CPPSTD='$($env:YAMS_CPPSTD)'. Proceeding with compiler detection."
}
# Safety check: C++23 with MSVC + Boost 1.86 is currently problematic on Windows
if ($env:YAMS_CPPSTD -eq '23' -and $msvcVersion -ge 190) {
Write-Warning "C++23 with MSVC and Boost is currently unstable. Forcing downgrade to C++20 for this build."
$env:YAMS_CPPSTD = '20'
}
# C++20 capability probe (non-modules)
$cxx20ProbeOk = $false
$cxx20TestCode = '#include <vector>\nint main(){std::vector<int> v; return (int)v.size(); }'
$cxx20TestFile = [System.IO.Path]::GetTempFileName() + '.cpp'
try {
$cxx20TestCode | Out-File -FilePath $cxx20TestFile -Encoding ascii
$clOutput = & cl.exe /nologo /std:c++20 /EHsc /TP /c $cxx20TestFile /Fo:NUL 2>&1
if ($LASTEXITCODE -eq 0) {
$cxx20ProbeOk = $true
Write-Host "C++20 compile probe succeeded"
} else {
Write-Warning "C++20 compile probe failed. Compiler output: $clOutput"
}
} catch {
Write-Warning "C++20 compile probe failed: $_"
} finally {
Remove-Item -Path $cxx20TestFile -ErrorAction SilentlyContinue
}
if (-not $cxx20ProbeOk) {
Write-Error "C++20 support is required but not detected. Ensure MSVC toolset supports /std:c++20."
}
# C++20 Modules auto-detection
if (-not $env:YAMS_ENABLE_MODULES) {
$enableModules = $false
$moduleTestCode = 'export module test; export int foo() { return 42; }'
$moduleTestFile = [System.IO.Path]::GetTempFileName() + '.cppm'
try {
$moduleTestCode | Out-File -FilePath $moduleTestFile -Encoding utf8
$clOutput = & cl.exe /std:c++20 /interface /TP /c $moduleTestFile /Fo:NUL 2>&1
if ($LASTEXITCODE -eq 0) {
$enableModules = $true
Write-Host "Auto-detected C++20 module support (MSVC /interface)"
} else {
Write-Host "C++20 modules not fully supported by compiler, disabled"
}
} catch {
Write-Host "C++20 modules not fully supported by compiler, disabled"
} finally {
Remove-Item -Path $moduleTestFile -ErrorAction SilentlyContinue
}
} else {
if ($env:YAMS_ENABLE_MODULES -eq 'true') {
$enableModules = $true
Write-Host "C++20 modules enabled via YAMS_ENABLE_MODULES=true"
} else {
$enableModules = $false
Write-Host "C++20 modules disabled via YAMS_ENABLE_MODULES=false"
}
}
Write-Host "Decision trace: VS=$vsVersion (install=$vsInstallVersion), MSVC=$msvcVersion, Toolset=$($env:VCToolsVersion), C++=$($env:YAMS_CPPSTD), Modules=$enableModules"
function Resolve-PythonExecutable {
$candidates = [System.Collections.Generic.List[string]]::new()
$pyLauncher = Get-Command py -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -First 1
if ($pyLauncher) {
try {
$resolved = & $pyLauncher -3 -c "import sys; print(sys.executable)" 2>$null
if ($LASTEXITCODE -eq 0 -and $resolved) {
$candidates.Add($resolved.Trim())
}
} catch {}
}
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -First 1
if ($pythonCmd -and $pythonCmd -notmatch '\\WindowsApps\\') {
$candidates.Add($pythonCmd)
}
$scoopPythonRoot = Join-Path $env:USERPROFILE 'scoop\apps\python'
if (Test-Path $scoopPythonRoot) {
Get-ChildItem -Path $scoopPythonRoot -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match '^\d' } |
Sort-Object Name -Descending |
ForEach-Object {
$candidate = Join-Path $_.FullName 'python.exe'
if (Test-Path $candidate) {
$candidates.Add($candidate)
}
}
}
$seen = @{}
foreach ($candidate in $candidates) {
if (-not $candidate) {
continue
}
try {
$normalized = [System.IO.Path]::GetFullPath($candidate)
} catch {
$normalized = $candidate
}
if ($seen.ContainsKey($normalized)) {
continue
}
$seen[$normalized] = $true
try {
$resolved = & $normalized -c "import sys; print(sys.executable)" 2>$null
if ($LASTEXITCODE -eq 0 -and $resolved) {
return $resolved.Trim()
}
} catch {}
}
return $null
}
function Prepend-PathEntry {
param(
[Parameter(Mandatory = $true)]
[string]$PathEntry
)
if ((Test-Path $PathEntry) -and ($env:PATH -notlike "*$PathEntry*")) {
$env:PATH = "$PathEntry;$env:PATH"
}
}
function Resolve-ToolExecutable {
param(
[Parameter(Mandatory = $true)]
[string]$CommandName,
[string[]]$ScoopRelativePaths = @("$CommandName.exe"),
[string[]]$VersionArguments = @('--version')
)
$candidates = [System.Collections.Generic.List[string]]::new()
$commandPath = Get-Command $CommandName -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -First 1
if ($commandPath -and $commandPath -notmatch '\\WindowsApps\\' -and $commandPath -notmatch '\\scoop\\shims\\') {
$candidates.Add($commandPath)
}
$scoopToolRoot = Join-Path $env:USERPROFILE ("scoop\\apps\\" + $CommandName)
if (Test-Path $scoopToolRoot) {
Get-ChildItem -Path $scoopToolRoot -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match '^\d' } |
Sort-Object Name -Descending |
ForEach-Object {
foreach ($relativePath in $ScoopRelativePaths) {
$candidate = Join-Path $_.FullName $relativePath
if (Test-Path $candidate) {
$candidates.Add($candidate)
}
}
}
}
$seen = @{}
foreach ($candidate in $candidates) {
if (-not $candidate) {
continue
}
try {
$normalized = [System.IO.Path]::GetFullPath($candidate)
} catch {
$normalized = $candidate
}
if ($seen.ContainsKey($normalized)) {
continue
}
$seen[$normalized] = $true
try {
& $normalized @VersionArguments *> $null
if ($LASTEXITCODE -eq 0) {
return $normalized
}
} catch {}
}
return $null
}
# Ensure Python tools
$pythonExe = Resolve-PythonExecutable
if (-not $pythonExe) {
Write-Error 'python not found in PATH. Please install Python 3.x and retry.'
}
$pythonDir = Split-Path -Parent $pythonExe
Prepend-PathEntry $pythonDir
$pythonScriptsDir = Join-Path $pythonDir 'Scripts'
if (Test-Path $pythonScriptsDir) {
Prepend-PathEntry $pythonScriptsDir
}
$env:PYTHON = $pythonExe
$env:YAMS_PYTHON = $pythonExe
Write-Host "Using Python: $pythonExe"
$conanExe = Resolve-ToolExecutable -CommandName 'conan'
$mesonExe = Resolve-ToolExecutable -CommandName 'meson'
$ninjaExe = Resolve-ToolExecutable -CommandName 'ninja'
$gitExe = Resolve-ToolExecutable -CommandName 'git' -ScoopRelativePaths @('cmd\\git.exe', 'bin\\git.exe')
foreach ($toolExe in @($gitExe, $ninjaExe, $mesonExe, $conanExe)) {
if ($toolExe) {
Prepend-PathEntry (Split-Path -Parent $toolExe)
}
}
if ($conanExe) { Write-Host "Using Conan: $conanExe" }
if ($mesonExe) { Write-Host "Using Meson: $mesonExe" }
if ($ninjaExe) { Write-Host "Using Ninja: $ninjaExe" }
if ($gitExe) { Write-Host "Using Git: $gitExe" }
# Check if required tools are already installed (e.g., by CI workflow)
# Only install if missing to avoid version conflicts with globally installed packages
$needsInstall = @()
if (-not $conanExe) { $needsInstall += 'conan' }
if (-not $mesonExe) { $needsInstall += 'meson' }
if (-not $ninjaExe) { $needsInstall += 'ninja' }
if ($needsInstall.Count -gt 0) {
if ($Offline -or $SystemDeps) {
Write-Error "Missing required build tools in offline/system dependency mode: $($needsInstall -join ', '). Install them ahead of time and retry."
}
Write-Host "Installing missing tools: $($needsInstall -join ', ')"
& $pythonExe -m pip install --user --upgrade pip | Out-Null
# Install numpy as it is often required by boost build checks even if python is disabled
& $pythonExe -m pip install --user conan meson ninja numpy | Out-Null
} else {
Write-Host "Build tools already installed (conan, meson, ninja)"
}
# Make sure user-local Python scripts are on PATH
$UserBase = & $pythonExe -m site --user-base
$UserBin = Join-Path $UserBase 'Scripts'
if ($env:PATH -notlike "*$UserBin*") {
$env:PATH = "$UserBin;$env:PATH"
}
# Map BuildType to build directories similar to setup.sh
switch ($BuildType) {
'Debug' { $buildDir = 'build/debug'; $conanSubdir = 'build-debug' }
'Profiling' { $buildDir = 'build/profiling'; $conanSubdir = 'build-profiling' }
'Fuzzing' { $buildDir = 'build/fuzzing'; $conanSubdir = 'build-fuzzing' }
Default { $buildDir = "build/$($BuildType.ToLower())"; $conanSubdir = "build-$($BuildType.ToLower())" }
}
$buildDir = $buildDir -replace '/', '\\'
Write-Host "Build Type: $BuildType"
Write-Host "Build Dir: $buildDir"
Write-Host "C++ Std (Conan): $($env:YAMS_CPPSTD)"
Write-Host "Offline Mode: $Offline"
Write-Host "System Deps: $SystemDeps"
# Conan args (mirror setup.sh defaults, but with MSVC settings)
# Allow overriding MSVC version via env vars for CI (e.g. windows-2022 uses 193/17)
if ($env:YAMS_MSVC_VERSION) { $msvcVersion = $env:YAMS_MSVC_VERSION }
if ($env:YAMS_VS_VERSION) { $vsVersion = $env:YAMS_VS_VERSION }
$conanArgs = @(
'.',
'-of', $buildDir,
'-s', "build_type=$BuildType",
'-s', 'os=Windows',
'-s', 'arch=x86_64',
'-s', 'compiler=msvc',
'-s', "compiler.version=$msvcVersion",
'-s', 'compiler.runtime=dynamic',
'-s', "compiler.cppstd=$($env:YAMS_CPPSTD)"
)
if ($Offline) {
$conanArgs += @('-nr', '--build=never')
} else {
$conanArgs += @('--update', '--build=missing')
}
# Prefer Ninja generator on VS 2025 to avoid unsupported VS generator names
$cmakeGenerator = $env:YAMS_CMAKE_GENERATOR
$vsVersionNumber = $null
if (-not $cmakeGenerator -and [int]::TryParse($vsVersion, [ref]$vsVersionNumber)) {
if ($vsVersionNumber -ge 18) {
$cmakeGenerator = 'Ninja'
Write-Host "Using CMake generator: $cmakeGenerator (override with YAMS_CMAKE_GENERATOR)"
}
}
if ($cmakeGenerator) {
$conanArgs += @('-c', "tools.cmake.cmaketoolchain:generator=$cmakeGenerator")
}
# Configure VS version for bzip2 and other packages that need MSBuild detection
# VS 2025/2024 is version 18, MSVC compiler version 195
if (-not $env:YAMS_CONAN_COMPILER_VERSION) {
$env:YAMS_CONAN_COMPILER_VERSION = $msvcVersion
Write-Host "Using detected Conan compiler.version: $($env:YAMS_CONAN_COMPILER_VERSION)"
} else {
Write-Host "Using pinned Conan compiler.version from YAMS_CONAN_COMPILER_VERSION: $($env:YAMS_CONAN_COMPILER_VERSION)"
}
if (-not $env:YAMS_MSBUILD_VS_VERSION) {
$env:YAMS_MSBUILD_VS_VERSION = $vsVersion
Write-Host "Using detected MSBuild Visual Studio version: $($env:YAMS_MSBUILD_VS_VERSION)"
} else {
Write-Host "Using pinned MSBuild Visual Studio version from YAMS_MSBUILD_VS_VERSION: $($env:YAMS_MSBUILD_VS_VERSION)"
}
if (-not $SystemDeps) {
# Export custom Conan recipes before install (must happen before conan install)
Write-Host '--- Exporting custom Conan recipes... ---'
if (Test-Path 'conan/onnxruntime/conanfile.py') {
Write-Host 'Exporting onnxruntime/1.23.0 from conan/onnxruntime/'
conan export conan/onnxruntime --name=onnxruntime --version=1.23.0
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to export onnxruntime recipe"
exit $LASTEXITCODE
}
}
}
$conanHostProfile = $env:YAMS_CONAN_HOST_PROFILE
if (-not $conanHostProfile) {
$conanHostProfile = '.\conan\profiles\host-windows-msvc'
}
if (-not (Test-Path $conanHostProfile)) {
Write-Error "Conan host profile not found: $conanHostProfile"
exit 1
}
Write-Host "Using Conan host profile: $conanHostProfile"
$conanArgs += @('-pr:h', $conanHostProfile, '-pr:b', 'default')
# Enable tests/benchmarks for Debug/Profiling/Fuzzing builds (parity with setup.sh)
if ($BuildType -in @('Debug','Profiling','Fuzzing')) {
$conanArgs += @('-o', '&:build_tests=True', '-o', '&:build_benchmarks=True')
}
# ONNX Runtime is deprecated and opt-in (-WithOnnx); YAMS_DISABLE_ONNX=true still forces it off.
$enableOnnx = $WithOnnx.IsPresent -and ($env:YAMS_DISABLE_ONNX -ne 'true')
if (-not $enableOnnx) {
Write-Host 'ONNX support disabled (deprecated; pass -WithOnnx to build it)'
$conanArgs += @('-o', 'yams/*:enable_onnx=False')
} else {
$conanArgs += @('-o', 'yams/*:enable_onnx=True')
# Auto-detect GPU for ONNX acceleration
# Override with YAMS_ONNX_GPU=cuda|directml|none
$onnxGpu = $env:YAMS_ONNX_GPU
if (-not $onnxGpu -or $onnxGpu -eq 'auto') {
# Windows: Check for DirectX 12 capable GPU (DirectML)
# DirectML works with any DX12 GPU (NVIDIA, AMD, Intel)
$hasDx12Gpu = $false
try {
# Check for any display adapter that supports DX12
$adapters = Get-CimInstance -ClassName Win32_VideoController -ErrorAction SilentlyContinue
if ($adapters) {
foreach ($adapter in $adapters) {
# Most modern GPUs (2015+) support DX12
if ($adapter.AdapterRAM -gt 0 -and $adapter.Name -notmatch 'Microsoft Basic') {
$hasDx12Gpu = $true
Write-Host "GPU detected: $($adapter.Name)"
break
}
}
}
} catch {
Write-Host "Could not detect GPU: $_"
}
# Also check for NVIDIA GPU (prefer CUDA if available)
$hasCuda = $false
$hasCudaToolkit = $false
if (Get-Command nvidia-smi -ErrorAction SilentlyContinue) {
try {
$null = nvidia-smi 2>$null
if ($LASTEXITCODE -eq 0) {
$hasCuda = $true
Write-Host "NVIDIA GPU detected via nvidia-smi"
# Validate CUDA toolkit is actually available (not just the GPU driver)
# nvidia-smi alone doesn't guarantee CUDA runtime is usable
if (Get-Command nvcc -ErrorAction SilentlyContinue) {
$hasCudaToolkit = $true
Write-Host "CUDA toolkit (nvcc) detected"
} elseif ($env:CUDA_PATH -and (Test-Path "$env:CUDA_PATH\bin\nvcc.exe")) {
$hasCudaToolkit = $true
Write-Host "CUDA toolkit detected at $env:CUDA_PATH"
} elseif (Get-ChildItem -Path "$env:ProgramFiles\NVIDIA GPU Computing Toolkit\CUDA\*\bin\cudart64_*.dll" -ErrorAction SilentlyContinue) {
$hasCudaToolkit = $true
Write-Host "CUDA runtime (cudart64) detected"
}
if (-not $hasCudaToolkit) {
Write-Host "NVIDIA GPU detected but CUDA toolkit missing - will use DirectML or CPU"
Write-Host " Install CUDA toolkit for GPU acceleration: https://developer.nvidia.com/cuda-downloads"
}
}
} catch {}
}
if ($hasCuda -and $hasCudaToolkit) {
$onnxGpu = 'cuda'
Write-Host "Enabling CUDA GPU acceleration"
} elseif ($hasDx12Gpu) {
$onnxGpu = 'directml'
Write-Host "Enabling DirectML GPU acceleration (DX12)"
} else {
$onnxGpu = 'none'
Write-Host "No GPU detected: using CPU-only ONNX Runtime"
}
}
if ($onnxGpu -ne 'none') {
$conanArgs += @('-o', "onnxruntime/*:with_gpu=$onnxGpu")
Write-Host "ONNX GPU provider: $onnxGpu"
}
}
if ($env:YAMS_DISABLE_PDF -eq 'true') {
Write-Host 'PDF support disabled (YAMS_DISABLE_PDF=true)'
$conanArgs += @('-o', 'yams/*:enable_pdf=False')
}
if (-not $SystemDeps) {
Write-Host '--- Running conan install (Windows/MSVC)... ---'
try {
# Use runtime_deploy to copy DLLs next to executables
conan install @conanArgs --deployer=runtime_deploy --deployer-folder=$buildDir
if ($LASTEXITCODE -ne 0) {
throw "Conan install failed with exit code $LASTEXITCODE"
}
} catch {
Write-Error $_
Write-Warning "Build failed. Common fixes:"
Write-Warning "1. Verify setup.ps1 selected the installed MSVC toolset you expect in the decision trace."
Write-Warning "2. Try cleaning the cache: conan remove boost/* -c"
Write-Warning "3. If you need a non-default MSVC toolset, set YAMS_VS_TOOLSET before rerunning setup.ps1."
exit 1
}
}
# Workaround: libiconv static libs may be missing from Conan package on Windows
$libiconvPc = Join-Path $buildDir "$conanSubdir/conan/libiconv.pc"
if ((-not $SystemDeps) -and (Test-Path $libiconvPc)) {
$libiconvPrefix = $null
$libiconvLibDir = $null
try {
$libiconvPrefix = (Get-Content $libiconvPc | Where-Object { $_ -match '^prefix=' }) -replace '^prefix=', ''
if ($libiconvPrefix) {
$libiconvLibDir = Join-Path $libiconvPrefix 'lib'
}
} catch {
$libiconvPrefix = $null
}
if ($libiconvLibDir) {
if (-not (Test-Path $libiconvLibDir)) {
New-Item -ItemType Directory -Path $libiconvLibDir | Out-Null
}
$iconvLibPath = Join-Path $libiconvLibDir 'iconv.lib'
$charsetLibPath = Join-Path $libiconvLibDir 'charset.lib'
if (-not (Test-Path $iconvLibPath) -or -not (Test-Path $charsetLibPath)) {
$conanCacheRoot = Join-Path $env:USERPROFILE '.conan2\p'
$iconvNoI18n = $null
$iconvDllLib = $null
$charsetLib = $null
$charsetDllLib = $null
if (Test-Path $conanCacheRoot) {
$iconvNoI18n = Get-ChildItem -Path $conanCacheRoot -Filter 'iconv_no_i18n.lib' -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
$iconvDllLib = Get-ChildItem -Path $conanCacheRoot -Filter 'iconv.dll.lib' -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
$charsetLib = Get-ChildItem -Path $conanCacheRoot -Filter 'charset.lib' -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
$charsetDllLib = Get-ChildItem -Path $conanCacheRoot -Filter 'charset.dll.lib' -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
}
if (-not (Test-Path $iconvLibPath)) {
if ($iconvNoI18n) {
Copy-Item -Path $iconvNoI18n.FullName -Destination $iconvLibPath -Force
Write-Host "Patched libiconv: copied iconv_no_i18n.lib to $iconvLibPath"
} elseif ($iconvDllLib) {
Copy-Item -Path $iconvDllLib.FullName -Destination $iconvLibPath -Force
Write-Host "Patched libiconv: copied iconv.dll.lib to $iconvLibPath"
}
}
if (-not (Test-Path $charsetLibPath)) {
if ($charsetLib) {
Copy-Item -Path $charsetLib.FullName -Destination $charsetLibPath -Force
Write-Host "Patched libiconv: copied charset.lib to $charsetLibPath"
} elseif ($charsetDllLib) {
Copy-Item -Path $charsetDllLib.FullName -Destination $charsetLibPath -Force
Write-Host "Patched libiconv: copied charset.dll.lib to $charsetLibPath"
}
}
}
}
}
# Meson setup: prefer explicit overrides first, then Conan-generated files
$nativeFile = Join-Path $buildDir "$conanSubdir/conan/conan_meson_native.ini"
$crossFile = Join-Path $buildDir "$conanSubdir/conan/conan_meson_cross.ini"
if (-not (Test-Path $buildDir)) {
New-Item -ItemType Directory -Path $buildDir | Out-Null
}
$mesonToolchainArg = $null
$mesonToolchainFile = $null
if ($env:YAMS_MESON_NATIVE_FILE -and $env:YAMS_MESON_CROSS_FILE) {
Write-Error 'Set only one of YAMS_MESON_NATIVE_FILE or YAMS_MESON_CROSS_FILE.'
}
if ($env:YAMS_MESON_NATIVE_FILE) {
if (-not (Test-Path $env:YAMS_MESON_NATIVE_FILE)) {
Write-Error "Meson native file not found: $($env:YAMS_MESON_NATIVE_FILE)"
}
$mesonToolchainArg = '--native-file'
$mesonToolchainFile = $env:YAMS_MESON_NATIVE_FILE
} elseif ($env:YAMS_MESON_CROSS_FILE) {
if (-not (Test-Path $env:YAMS_MESON_CROSS_FILE)) {
Write-Error "Meson cross file not found: $($env:YAMS_MESON_CROSS_FILE)"
}
$mesonToolchainArg = '--cross-file'
$mesonToolchainFile = $env:YAMS_MESON_CROSS_FILE
} elseif (-not $SystemDeps) {
if (Test-Path $nativeFile) {
$mesonToolchainArg = '--native-file'
$mesonToolchainFile = $nativeFile
} elseif (Test-Path $crossFile) {
$mesonToolchainArg = '--cross-file'
$mesonToolchainFile = $crossFile
} else {
Write-Error "Conan Meson toolchain file not found. Expected: $nativeFile or $crossFile"
}
}
# Activate Conan build environment to ensure tools like pkg-config are in PATH.
# Conan can generate either PowerShell or batch activation scripts depending on host settings.
$conanBuildPs1 = Join-Path $buildDir "$conanSubdir/conan/conanbuild.ps1"
$conanBuildBat = Join-Path $buildDir "$conanSubdir/conan/conanbuild.bat"
$conanBuildEnvScript = $null
if (-not $SystemDeps) {
if (Test-Path $conanBuildPs1) {
Write-Host "Activating Conan build environment: $conanBuildPs1"
. $conanBuildPs1
$conanBuildEnvScript = $conanBuildPs1
} elseif (Test-Path $conanBuildBat) {
Write-Host "Activating Conan build environment: $conanBuildBat"
if (-not (Import-BatchEnvironment -BatchFile $conanBuildBat)) {
throw "Failed to import Conan batch environment: $conanBuildBat"
}
$conanBuildEnvScript = $conanBuildBat
}
}
# Export BOOST_ROOT so meson's built-in Boost dep finds the Conan-installed
# Boost (Windows has no system Boost fallback). Resolve the prefix from Conan's
# boost.pc. Conan 2.x writes a relocatable form (`prefix=${pcfiledir}/..`)
# which pkg-config substitutes at query time — a naive Select-String capture
# yields the literal placeholder, so call pkgconf when available, otherwise
# expand `${pcfiledir}` manually.
if ((-not $SystemDeps) -and (-not $env:BOOST_ROOT)) {
$conanGenDir = (Join-Path $buildDir "$conanSubdir/conan")
$boostPc = Join-Path $conanGenDir 'boost.pc'
if (Test-Path $boostPc) {
$boostPath = $null
# Preferred: ask pkgconf to do the substitution. Conan installs a
# pkgconf binary alongside the generators; meson's PkgConfigDeps also
# points PKG_CONFIG_PATH at this dir.
$pkgconf = Get-Command pkg-config -ErrorAction SilentlyContinue
if (-not $pkgconf) { $pkgconf = Get-Command pkgconf -ErrorAction SilentlyContinue }
if ($pkgconf) {
$env:PKG_CONFIG_PATH = "$conanGenDir;$($env:PKG_CONFIG_PATH)"
try {
$resolved = & $pkgconf.Source --variable=prefix boost 2>$null
if ($LASTEXITCODE -eq 0 -and $resolved) {
$boostPath = $resolved.Trim()
}
} catch {}
}
# Fallback: parse `prefix=` line and expand ${pcfiledir} ourselves.
if (-not $boostPath) {
$prefixLine = Select-String -Path $boostPc `
-Pattern '^\s*prefix\s*=' -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($prefixLine) {
$raw = ($prefixLine.Line -replace '^\s*prefix\s*=\s*', '').Trim()
$raw = $raw -replace '\$\{pcfiledir\}', $conanGenDir
try {
$boostPath = [System.IO.Path]::GetFullPath($raw)
} catch { $boostPath = $raw }
}
}
if ($boostPath -and (Test-Path (Join-Path $boostPath 'include/boost'))) {
$env:BOOST_ROOT = $boostPath
$env:BOOST_INCLUDEDIR = (Join-Path $boostPath 'include')
$env:BOOST_LIBRARYDIR = (Join-Path $boostPath 'lib')
Write-Host "BOOST_ROOT set to: $boostPath"
Write-Host "BOOST_INCLUDEDIR set to: $($env:BOOST_INCLUDEDIR)"
Write-Host "BOOST_LIBRARYDIR set to: $($env:BOOST_LIBRARYDIR)"
# Persist to GITHUB_ENV so any downstream workflow step (e.g.
# 'Run Tests with Meson (Windows)') still sees the same hints.
# No-op when not running under GitHub Actions.
if ($env:GITHUB_ENV) {
Add-Content -Path $env:GITHUB_ENV -Value "BOOST_ROOT=$boostPath"
Add-Content -Path $env:GITHUB_ENV -Value "BOOST_INCLUDEDIR=$($env:BOOST_INCLUDEDIR)"
Add-Content -Path $env:GITHUB_ENV -Value "BOOST_LIBRARYDIR=$($env:BOOST_LIBRARYDIR)"
Write-Host "Persisted Boost env hints to GITHUB_ENV"
}
} elseif ($boostPath) {
Write-Warning "boost.pc resolved prefix '$boostPath' has no include/boost subdir; not exporting BOOST_ROOT"
} else {
Write-Warning "boost.pc found at $boostPc but prefix could not be resolved"
}
}
}
$activeMeson = Get-Command meson -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -First 1
$activeNinja = Get-Command ninja -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -First 1
$conanToolsOverride = ($activeMeson -ne $initialMeson) -or ($activeNinja -ne $initialNinja)
$mesonWrapperPs1 = $null
if ((-not $SystemDeps) -and $conanBuildEnvScript) {
$mesonWrapperPs1 = Join-Path $buildDir 'mesonw.ps1'
$wrapperContent = @"
Param(
[Parameter(ValueFromRemainingArguments = `$true)]
[string[]]`$MesonArgs
)
`$ErrorActionPreference = 'Stop'
`$conanBuildPs1 = '$($conanBuildPs1 -replace "'", "''")'
`$conanBuildBat = '$($conanBuildBat -replace "'", "''")'
function Import-BatchEnvironment {
param(
[Parameter(Mandatory = `$true)]
[string]`$BatchFile
)
if (-not (Test-Path `$BatchFile)) {
return `$false
}
`$varsLoaded = `$false
& `$env:COMSPEC /s /c "`"`$BatchFile`" && set" | ForEach-Object {
`$name, `$value = `$_ -split '=', 2
if (`$name -and `$value) {
Set-Item -Force -Path "ENV:\`$name" -Value `$value
`$varsLoaded = `$true
}
}
return `$varsLoaded
}
if (Test-Path `$conanBuildPs1) {
. `$conanBuildPs1
} elseif (Test-Path `$conanBuildBat) {
if (-not (Import-BatchEnvironment -BatchFile `$conanBuildBat)) {
throw "Failed to import Conan batch environment: `$conanBuildBat"
}
}
& meson @MesonArgs
exit `$LASTEXITCODE
"@
Set-Content -Path $mesonWrapperPs1 -Value $wrapperContent -Encoding ASCII
}
# Detect install prefix
if ($env:YAMS_INSTALL_PREFIX) {
$InstallPrefix = $env:YAMS_INSTALL_PREFIX
} else {
# Check if running as Administrator
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if ($isAdmin) {
$InstallPrefix = "C:\Program Files\yams"
} else {
# Default to user-local install if not admin
$InstallPrefix = Join-Path $env:LOCALAPPDATA "Programs\yams"
Write-Host "Running as non-admin. Defaulting install prefix to user-local path." -ForegroundColor Yellow
}
}
Write-Host "Install Prefix: $InstallPrefix"
# Mobile bindings are off by default; allow CI/users to opt-in.
$enableMobileBindingsArg = $null
if ($env:YAMS_ENABLE_MOBILE_BINDINGS -eq 'true') {
$enableMobileBindingsArg = '-Denable-mobile-bindings=true'
Write-Host 'Mobile bindings enabled (YAMS_ENABLE_MOBILE_BINDINGS=true)'
} elseif ($env:YAMS_ENABLE_MOBILE_BINDINGS -eq 'false') {
$enableMobileBindingsArg = '-Denable-mobile-bindings=false'
Write-Host 'Mobile bindings disabled (YAMS_ENABLE_MOBILE_BINDINGS=false)'
}
if (-not (Test-Path (Join-Path $buildDir 'meson-private'))) {
Write-Host 'Configuring Meson builddir...'
$buildTypeLower = if ($BuildType -eq 'Release') { 'release' } else { $BuildType.ToLower() }
# Parse extra Meson flags from environment variable
# PowerShell can expand environment variables character-by-character in some contexts
# We need to ensure we get a proper string array
$extraMesonFlags = @()
if ($env:YAMS_EXTRA_MESON_FLAGS) {
# Force conversion to string and trim any whitespace
$flagsString = "$($env:YAMS_EXTRA_MESON_FLAGS)".Trim()
Write-Host "DEBUG: Raw YAMS_EXTRA_MESON_FLAGS = '$flagsString'"
# Split on whitespace, filter empty entries
$extraMesonFlags = @($flagsString -split '\s+' | Where-Object { $_.Length -gt 0 })
Write-Host "DEBUG: Parsed $($extraMesonFlags.Count) flag(s):"
foreach ($i in 0..($extraMesonFlags.Count - 1)) {
Write-Host "DEBUG: [$i] = '$($extraMesonFlags[$i])'"
}
}
$enableModulesFlag = if ($enableModules) { 'true' } else { 'false' }
# Auto-enable zyp plugin if Zig 0.15.2+ is installed
$enableZyp = $false
if (Get-Command zig -ErrorAction SilentlyContinue) {
$zigVersion = (zig version 2>$null)
if ($zigVersion -match '^(\d+)\.(\d+)\.(\d+)') {
$zigMajor = [int]$matches[1]
$zigMinor = [int]$matches[2]
$zigPatch = [int]$matches[3]
# Check if >= 0.15.2
if ($zigMajor -gt 0 -or ($zigMajor -eq 0 -and $zigMinor -gt 15) -or ($zigMajor -eq 0 -and $zigMinor -eq 15 -and $zigPatch -ge 2)) {
Write-Host "Zig $zigVersion detected, enabling zyp PDF plugin"
$enableZyp = $true
} else {
Write-Host "Zig $zigVersion found but requires 0.15.2+ for zyp plugin"
}
}
}
# Glint NL entity extractor plugin (GLiNER-based) requires ONNX Runtime.
$enableGlint = $false
if ($enableOnnx) {
Write-Host "Enabling Glint NL entity extractor plugin"
$enableGlint = $true
}
# Auto-detect libSQL backend: prefer libsql when subproject or pkg-config is available
$dbBackend = if ($env:YAMS_DATABASE_BACKEND) { $env:YAMS_DATABASE_BACKEND } else { 'libsql' }
$dbBackend = $dbBackend.ToLowerInvariant()
if ($dbBackend -ne 'libsql' -and $dbBackend -ne 'sqlite') {
Write-Error "Unknown database backend: $dbBackend. Expected libsql or sqlite."
}
if ($dbBackend -eq 'libsql' -and $SystemDeps) {
$libsqlPkgFound = $false
if (Get-Command pkg-config -ErrorAction SilentlyContinue) {
if ((pkg-config --exists libsql) -or (pkg-config --exists libsql-sqlite3)) {
$libsqlPkgFound = $true
}
}
if (-not $libsqlPkgFound) {
Write-Warning 'System dependency mode: libSQL not found via pkg-config; falling back to sqlite'
$dbBackend = 'sqlite'
}
}
if ($dbBackend -eq 'libsql') {
$libsqlPkgFound = $false
if (Get-Command pkg-config -ErrorAction SilentlyContinue) {
if ((pkg-config --exists libsql) -or (pkg-config --exists libsql-sqlite3)) {
$libsqlPkgFound = $true
}
}
if (-not $libsqlPkgFound -and -not (Test-Path 'subprojects\libsql')) {
if (Test-Path 'subprojects\libsql.wrap') {
if ($SystemDeps) {
Write-Warning 'libSQL not found via system dependencies; falling back to sqlite'
$dbBackend = 'sqlite'
} elseif ($Offline) {
Write-Warning 'Offline mode: not fetching libSQL subproject; falling back to sqlite'
$dbBackend = 'sqlite'
} elseif (Get-Command meson -ErrorAction SilentlyContinue) {
Write-Host 'Fetching libSQL subproject (meson wrap)...'
meson subprojects download libsql | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Warning 'Failed to fetch libSQL subproject; falling back to sqlite'
$dbBackend = 'sqlite'
}
} else {
Write-Warning 'Meson not found; cannot fetch libSQL subproject. Falling back to sqlite'
$dbBackend = 'sqlite'
}
} else {
Write-Warning 'libSQL wrap not found; falling back to sqlite'
$dbBackend = 'sqlite'
}
}
}
if ($dbBackend -eq 'libsql') {
$libsqlPatchMeson = 'subprojects\packagefiles\libsql\meson.build'
$libsqlSubprojectMeson = 'subprojects\libsql\meson.build'
if ((Test-Path $libsqlPatchMeson) -and (Test-Path $libsqlSubprojectMeson)) {
Copy-Item -Force $libsqlPatchMeson $libsqlSubprojectMeson
}
}
Write-Host "Database backend: $dbBackend"
$wrapMode = if ($env:YAMS_MESON_WRAP_MODE) {
$env:YAMS_MESON_WRAP_MODE
} elseif ($SystemDeps) {
'nofallback'
} elseif ($Offline) {
'nodownload'
} else {
'default'
}
# Build meson command arguments as a proper array
$mesonArgs = @('setup', $buildDir, "--buildtype=$buildTypeLower", "--prefix=$InstallPrefix", "-Denable-modules=$enableModulesFlag", "-Ddatabase-backend=$dbBackend")
if ($enableZyp) { $mesonArgs += '-Dplugin-zyp=true' }
if ($enableGlint) { $mesonArgs += '-Dplugin-glint=true' }
if ($enableOnnx) { $mesonArgs += '-Denable-onnx=enabled' } else { $mesonArgs += @('-Denable-onnx=disabled', '-Dplugin-onnx=false') }
if ($enableMobileBindingsArg) { $mesonArgs += $enableMobileBindingsArg }
if ($mesonToolchainArg) { $mesonArgs += $mesonToolchainArg }
if ($mesonToolchainFile) { $mesonArgs += $mesonToolchainFile }
if ($wrapMode -ne 'default') { $mesonArgs += @('--wrap-mode', $wrapMode) }
if ($env:YAMS_PKG_CONFIG_PATH) { $mesonArgs += @('--pkg-config-path', $env:YAMS_PKG_CONFIG_PATH) }
if ($env:YAMS_BUILD_PKG_CONFIG_PATH) { $mesonArgs += @('--build.pkg-config-path', $env:YAMS_BUILD_PKG_CONFIG_PATH) }
if ($env:YAMS_CMAKE_PREFIX_PATH) { $mesonArgs += @('--cmake-prefix-path', $env:YAMS_CMAKE_PREFIX_PATH) }
if ($env:YAMS_BUILD_CMAKE_PREFIX_PATH) { $mesonArgs += @('--build.cmake-prefix-path', $env:YAMS_BUILD_CMAKE_PREFIX_PATH) }
if ($buildTypeLower -eq 'release') { $mesonArgs += '-Db_ndebug=true' } else { $mesonArgs += '-Db_ndebug=false' }
if ($env:YAMS_DISABLE_RE2 -eq 'true') { $mesonArgs += '-Denable-re2=disabled' }
# Enable vector tests for Debug/Profiling/Fuzzing builds (parity with setup.sh)
if ($BuildType -in @('Debug','Profiling','Fuzzing')) {
Write-Host "Enabling vector/embedding tests for $BuildType build"
$mesonArgs += '-Denable-vector-tests=true'
}
$mesonArgs += $extraMesonFlags
Write-Host "DEBUG: Final meson command: meson $($mesonArgs -join ' ')"
& meson @mesonArgs
} else {
Write-Host 'Meson builddir already configured, reconfiguring...'
Write-Host "Ensuring prefix is set to: $InstallPrefix"
$buildTypeLower = if ($BuildType -eq 'Release') { 'release' } else { $BuildType.ToLower() }
# Auto-detect libSQL backend for reconfigure as well
$dbBackend = if ($env:YAMS_DATABASE_BACKEND) { $env:YAMS_DATABASE_BACKEND } else { 'libsql' }
$dbBackend = $dbBackend.ToLowerInvariant()
if ($dbBackend -ne 'libsql' -and $dbBackend -ne 'sqlite') {
Write-Error "Unknown database backend: $dbBackend. Expected libsql or sqlite."
}
if ($dbBackend -eq 'libsql' -and $SystemDeps) {
$libsqlPkgFound = $false
if (Get-Command pkg-config -ErrorAction SilentlyContinue) {
if ((pkg-config --exists libsql) -or (pkg-config --exists libsql-sqlite3)) {
$libsqlPkgFound = $true
}
}
if (-not $libsqlPkgFound) {
Write-Warning 'System dependency mode: libSQL not found via pkg-config; falling back to sqlite'
$dbBackend = 'sqlite'
}
}
if ($dbBackend -eq 'libsql') {
$libsqlPkgFound = $false
if (Get-Command pkg-config -ErrorAction SilentlyContinue) {
if ((pkg-config --exists libsql) -or (pkg-config --exists libsql-sqlite3)) {
$libsqlPkgFound = $true
}
}
if (-not $libsqlPkgFound -and -not (Test-Path 'subprojects\libsql')) {
if (Test-Path 'subprojects\libsql.wrap') {
if ($SystemDeps) {
Write-Warning 'libSQL not found via system dependencies; falling back to sqlite'
$dbBackend = 'sqlite'
} elseif ($Offline) {
Write-Warning 'Offline mode: not fetching libSQL subproject; falling back to sqlite'
$dbBackend = 'sqlite'
} elseif (Get-Command meson -ErrorAction SilentlyContinue) {
Write-Host 'Fetching libSQL subproject (meson wrap)...'
meson subprojects download libsql | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Warning 'Failed to fetch libSQL subproject; falling back to sqlite'
$dbBackend = 'sqlite'
}
} else {
Write-Warning 'Meson not found; cannot fetch libSQL subproject. Falling back to sqlite'
$dbBackend = 'sqlite'
}
} else {
Write-Warning 'libSQL wrap not found; falling back to sqlite'
$dbBackend = 'sqlite'
}
}
}
if ($dbBackend -eq 'libsql') {
$libsqlPatchMeson = 'subprojects\packagefiles\libsql\meson.build'
$libsqlSubprojectMeson = 'subprojects\libsql\meson.build'
if ((Test-Path $libsqlPatchMeson) -and (Test-Path $libsqlSubprojectMeson)) {
Copy-Item -Force $libsqlPatchMeson $libsqlSubprojectMeson
}
}
Write-Host "Database backend: $dbBackend"
$wrapMode = if ($env:YAMS_MESON_WRAP_MODE) {
$env:YAMS_MESON_WRAP_MODE
} elseif ($SystemDeps) {
'nofallback'
} elseif ($Offline) {
'nodownload'
} else {
'default'
}
# Use --reconfigure to handle Meson version upgrades gracefully
$reconfigureArgs = @('setup', '--reconfigure', $buildDir, "-Dprefix=$InstallPrefix", "-Ddatabase-backend=$dbBackend")
if ($mesonToolchainArg) { $reconfigureArgs += $mesonToolchainArg }
if ($mesonToolchainFile) { $reconfigureArgs += $mesonToolchainFile }
if ($enableMobileBindingsArg) { $reconfigureArgs += $enableMobileBindingsArg }
if ($wrapMode -ne 'default') { $reconfigureArgs += @('--wrap-mode', $wrapMode) }
if ($env:YAMS_PKG_CONFIG_PATH) { $reconfigureArgs += @('--pkg-config-path', $env:YAMS_PKG_CONFIG_PATH) }
if ($env:YAMS_BUILD_PKG_CONFIG_PATH) { $reconfigureArgs += @('--build.pkg-config-path', $env:YAMS_BUILD_PKG_CONFIG_PATH) }
if ($env:YAMS_CMAKE_PREFIX_PATH) { $reconfigureArgs += @('--cmake-prefix-path', $env:YAMS_CMAKE_PREFIX_PATH) }
if ($env:YAMS_BUILD_CMAKE_PREFIX_PATH) { $reconfigureArgs += @('--build.cmake-prefix-path', $env:YAMS_BUILD_CMAKE_PREFIX_PATH) }
if ($buildTypeLower -eq 'release') { $reconfigureArgs += '-Db_ndebug=true' } else { $reconfigureArgs += '-Db_ndebug=false' }
if ($env:YAMS_DISABLE_RE2 -eq 'true') { $reconfigureArgs += '-Denable-re2=disabled' }
# Ensure vector tests are enabled for Debug/Profiling/Fuzzing builds (parity with setup.sh)
if ($BuildType -in @('Debug','Profiling','Fuzzing')) {
$reconfigureArgs += '-Denable-vector-tests=true'
}
& meson @reconfigureArgs
}
Write-Host 'Compiling...'
$compileArgs = @('compile', '-C', $buildDir)
if ($env:YAMS_NINJA_JOBS) {
$ninjaJobs = 0
if ([int]::TryParse($env:YAMS_NINJA_JOBS, [ref]$ninjaJobs) -and $ninjaJobs -gt 0) {
Write-Host "Limiting Ninja parallelism to $ninjaJobs job(s) (YAMS_NINJA_JOBS)"
$compileArgs += @('--jobs', $ninjaJobs.ToString())
} else {
Write-Warning "Ignoring invalid YAMS_NINJA_JOBS='$($env:YAMS_NINJA_JOBS)'"
}
}
meson @compileArgs
if ($LASTEXITCODE -ne 0) {
Write-Error "Meson compile failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
# Note: Windows runtime DLLs (onnxruntime.dll, tbb*.dll) are now deployed
# automatically by Meson custom_target during the build phase.
# See meson.build files in src/daemon, tools/yams-cli, tools/yams-mcp, and plugins/onnx.
Write-Host "----------------------------------------------------------------"
Write-Host "Build complete."
if ($Package) {
Write-Host "----------------------------------------------------------------"
Write-Host "Packaging MSI installer..." -ForegroundColor Cyan
# Stage the install for MSI packaging (use absolute path to avoid meson nesting issues)
$stageDir = Join-Path (Resolve-Path $buildDir).Path "stage"
Write-Host "Staging install to: $stageDir"
# Remove old stage if exists
if (Test-Path $stageDir) {
Remove-Item -Recurse -Force $stageDir
}
New-Item -ItemType Directory -Path $stageDir -Force | Out-Null
# Install to stage directory (destdir prepends to prefix, build-msi.ps1 handles finding files).
# --no-rebuild: skip the implicit recompile; setup.ps1 already built above.
meson install -C $buildDir --destdir $stageDir --no-rebuild
if ($LASTEXITCODE -ne 0) {
Write-Error "Meson install failed"
exit 1
}
# Determine version
if ([string]::IsNullOrEmpty($Version)) {
# Try to extract from meson.build
$mesonBuild = Join-Path $PSScriptRoot "meson.build"
if (Test-Path $mesonBuild) {
$content = Get-Content $mesonBuild -Raw
if ($content -match "version\s*:\s*'([^']+)'") {
$Version = $matches[1]
}
}
if ([string]::IsNullOrEmpty($Version)) {
$Version = "0.0.0"
}
}
Write-Host "Package version: $Version"
# Build MSI - the script handles finding yams.exe wherever meson placed it
$msiScript = Join-Path $PSScriptRoot "packaging\windows\build-msi.ps1"
if (-not (Test-Path $msiScript)) {
Write-Error "MSI build script not found at: $msiScript"
exit 1
}
& $msiScript -StageDir $stageDir -Version $Version -OutputDir $buildDir
if ($LASTEXITCODE -ne 0) {
Write-Error "MSI build failed"
exit 1
}
Write-Host "----------------------------------------------------------------"
Write-Host "MSI package created in: $buildDir" -ForegroundColor Green
Get-ChildItem $buildDir -Filter "*.msi" | ForEach-Object {
Write-Host " $($_.Name)" -ForegroundColor Green
}
} else {
if ($conanToolsOverride -and $mesonWrapperPs1) {
Write-Host "Conan provided Meson/Ninja for this build directory."
Write-Host "To install, run: $mesonWrapperPs1 install -C $buildDir"
} else {
Write-Host "To install, run: meson install -C $buildDir"
if ($mesonWrapperPs1) {
Write-Host "Fallback wrapper (replays Conan build env if needed): $mesonWrapperPs1"
}
}
Write-Host "To build MSI: .\setup.ps1 -BuildType $BuildType -Package"
Write-Host "Note: You may need to add '$InstallPrefix\bin' to your PATH."
}
Write-Host "----------------------------------------------------------------"