From ad5958b18d408c998be56fb1641b38de20291158 Mon Sep 17 00:00:00 2001 From: Nucleic Date: Tue, 28 Jul 2026 01:26:18 -0700 Subject: [PATCH] Merge nucleic/keen-maple-heron-yzg3 into dev --- build.ps1 | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 build.ps1 diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..6fc0459 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,198 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Build and test the Windows port with SQLite wired up (docs/WINDOWS_PORT.md §14.2). + +.DESCRIPTION + Windows has no system SQLite, and GRDB's `GRDBSQLite` is a .systemLibrary whose only + declared provider is apt — so `sqlite3.h` resolves nowhere and NucleicCore can't build. + A .systemLibrary is resolved through C header/library search paths rather than the + SwiftPM module graph, so nothing in Package.swift can fix this: SQLite has to exist on + disk and be discoverable. This script installs it once via vcpkg and points the compiler + and linker at it. + + Two mechanisms, deliberately chosen: + + CPATH — additive. clang appends it to the header search path without + disturbing MSVC/SDK auto-detection, so plain `swift build` + works afterwards with no flags at all. + -Xlinker -LIBPATH: — passed per-invocation rather than via %LIB%. Setting %LIB% + would be actively harmful: clang skips emitting its own + -libpath: for the VC, UCRT *and* Windows SDK directories the + moment %LIB% is set (clang/lib/Driver/ToolChains/MSVC.cpp, + `if (!GetEnv("LIB") || ...)`), so a fresh %LIB% holding only + SQLite silently strips the entire platform link path. + +.PARAMETER Target + protocol — the wire layer only (the required CI leg; needs no SQLite). + core — NucleicCore, the full Windows manifest. EXPECTED TO FAIL until the §12 + item 7-9 shims land; its output is the M0 gap list, not a regression. + broker — the C# nucleic-brokerd contract tests (no Swift involved). + all — all three, continuing past a failing leg. + +.PARAMETER Test + Run the leg's tests instead of just building it. + +.PARAMETER Persist + Also save CPATH to your *user* environment, so future shells and editors resolve + sqlite3.h without going through this script. + +.EXAMPLE + ./windows/build.ps1 -Target protocol -Test + ./windows/build.ps1 -Target core -Persist +#> +[CmdletBinding()] +param( + [ValidateSet('protocol', 'core', 'broker', 'all')] + [string]$Target = 'protocol', + [switch]$Test, + [switch]$Persist, + [string]$VcpkgRoot, + [string]$Triplet +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repo = Split-Path -Parent $PSScriptRoot + +function Write-Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } +function Write-Note($msg) { Write-Host " $msg" -ForegroundColor DarkGray } + +# ---------------------------------------------------------------- SQLite via vcpkg + +function Initialize-SQLite { + if (-not $Triplet) { + # -static-md links SQLite statically against the DYNAMIC CRT — matching what the + # Swift toolchain uses, so the CRT agrees and there's no sqlite3.dll to place + # beside every test binary. + $arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' } + $script:Triplet = "$arch-windows-static-md" + } + + $root = $VcpkgRoot + if (-not $root) { $root = $env:VCPKG_INSTALLATION_ROOT } + if (-not $root -or -not (Test-Path (Join-Path $root 'vcpkg.exe'))) { + $root = Join-Path $env:LOCALAPPDATA 'vcpkg' + } + + if (-not (Test-Path (Join-Path $root '.git'))) { + Write-Step "Cloning vcpkg to $root (one time)" + git clone --depth 1 https://github.com/microsoft/vcpkg $root | Out-Host + if ($LASTEXITCODE -ne 0) { throw "vcpkg clone failed" } + } + if (-not (Test-Path (Join-Path $root 'vcpkg.exe'))) { + Write-Step 'Bootstrapping vcpkg (one time)' + & (Join-Path $root 'bootstrap-vcpkg.bat') -disableMetrics | Out-Host + if ($LASTEXITCODE -ne 0) { throw "vcpkg bootstrap failed" } + } + + $prefix = Join-Path $root "installed\$Triplet" + if (-not (Test-Path (Join-Path $prefix 'include\sqlite3.h'))) { + Write-Step "Installing sqlite3:$Triplet (one time, a few minutes)" + & (Join-Path $root 'vcpkg.exe') install "sqlite3:$Triplet" | Out-Host + if ($LASTEXITCODE -ne 0) { throw "vcpkg install sqlite3:$Triplet failed" } + } + + $inc = Join-Path $prefix 'include' + $lib = Join-Path $prefix 'lib' + if (-not (Test-Path (Join-Path $inc 'sqlite3.h'))) { throw "sqlite3.h missing under $inc" } + if (-not (Test-Path (Join-Path $lib 'sqlite3.lib'))) { throw "sqlite3.lib missing under $lib" } + + # Additive: clang merges CPATH into the header search path and still auto-detects the + # MSVC + Windows SDK headers. Contrast %INCLUDE%, which SUPPRESSES that detection. + if ($env:CPATH -and $env:CPATH -notlike "*$inc*") { $env:CPATH = "$inc;$env:CPATH" } + elseif (-not $env:CPATH) { $env:CPATH = $inc } + + if ($Persist) { + $userCPath = [Environment]::GetEnvironmentVariable('CPATH', 'User') + if (-not $userCPath) { $userCPath = $inc } + elseif ($userCPath -notlike "*$inc*") { $userCPath = "$inc;$userCPath" } + [Environment]::SetEnvironmentVariable('CPATH', $userCPath, 'User') + Write-Note "Persisted CPATH for your user account -> $inc" + Write-Note 'New shells can now `swift build` NucleicCore with no flags.' + } + + Write-Note "sqlite3 ($Triplet): $prefix" + return @{ Include = $inc; Lib = $lib } +} + +# ---------------------------------------------------------------- legs + +function Invoke-Swift([string[]]$SwiftArgs, [string]$LibPath) { + # -Xlinker only matters when something actually links (tests, executables); a library + # build resolves purely through CPATH. + $full = @($SwiftArgs) + @('-Xlinker', "-LIBPATH:$LibPath") + Write-Note "swift $($full -join ' ')" + # Out-Host, not the pipeline: a native command's stdout would otherwise become this + # function's return value, and the caller's exit-code check would see an array. + & swift @full | Out-Host + return $LASTEXITCODE +} + +function Build-Protocol([hashtable]$sql) { + # Narrows the manifest to the wire layer. Read at MANIFEST EVALUATION time, so it has + # to be in the environment before swift starts, not exported inside the build. + $env:NUCLEIC_WINDOWS_PROTOCOL_ONLY = '1' + try { + Write-Step 'protocol: building NucleicProtocol' + $code = if ($Test) { + Invoke-Swift @('test', '--filter', 'NucleicProtocolTests') $sql.Lib + } else { + Invoke-Swift @('build', '--target', 'NucleicProtocol') $sql.Lib + } + } finally { + Remove-Item Env:\NUCLEIC_WINDOWS_PROTOCOL_ONLY -ErrorAction SilentlyContinue + } + return $code +} + +function Build-Core([hashtable]$sql) { + Write-Step 'core: building NucleicCore (full Windows manifest)' + Write-Note 'Expected to fail until the §12 item 7-9 shims land — the errors ARE the M0 gap list.' + if ($Test) { return Invoke-Swift @('test', '--filter', 'NucleicCarbonTests') $sql.Lib } + return Invoke-Swift @('build', '--target', 'NucleicCore') $sql.Lib +} + +function Build-Broker { + Write-Step 'broker: dotnet test windows\Nucleic.sln' + & dotnet test (Join-Path $repo 'windows\Nucleic.sln') --nologo | Out-Host + return $LASTEXITCODE +} + +# ---------------------------------------------------------------- main + +Push-Location $repo +try { + $results = [ordered]@{} + + if ($Target -in @('protocol', 'core', 'all')) { $sql = Initialize-SQLite } + + switch ($Target) { + 'protocol' { $results['protocol'] = Build-Protocol $sql } + 'core' { $results['core'] = Build-Core $sql } + 'broker' { $results['broker'] = Build-Broker } + 'all' { + $results['protocol'] = Build-Protocol $sql + $results['core'] = Build-Core $sql + $results['broker'] = Build-Broker + } + } + + Write-Host '' + Write-Step 'Summary' + foreach ($k in $results.Keys) { + $ok = $results[$k] -eq 0 + # core is the known-failing gap probe; don't report it as a red build. + $label = if ($ok) { 'PASS' } elseif ($k -eq 'core') { 'FAIL (expected — gap list)' } else { 'FAIL' } + $color = if ($ok) { 'Green' } elseif ($k -eq 'core') { 'Yellow' } else { 'Red' } + Write-Host (" {0,-9} {1}" -f $k, $label) -ForegroundColor $color + } + + # core's failure is informational; anything else failing is a real build break. + $hard = @($results.Keys | Where-Object { $_ -ne 'core' -and $results[$_] -ne 0 }) + if ($hard.Count -gt 0) { exit 1 } + exit 0 +} finally { + Pop-Location +}